Repository: NoryCao/zhuishushenqi Branch: master Commit: a9d225cce6c6 Files: 1638 Total size: 121.4 MB Directory structure: gitextract_zvr8h67z/ ├── .gitignore ├── .idea/ │ ├── codeStyles/ │ │ └── Project.xml │ ├── modules.xml │ ├── vcs.xml │ ├── workspace.xml │ ├── xcode.xml │ └── zhuishushenqi.iml ├── Podfile ├── README.md ├── Script.sh ├── addSource.md ├── en.lproj/ │ └── Localizable.strings ├── images/ │ └── aptv.plist ├── zh-Hans.lproj/ │ └── Localizable.strings ├── zh-Hant.lproj/ │ └── Localizable.strings ├── zhuishushenqi/ │ ├── App/ │ │ ├── AppDelegate.swift │ │ ├── AppOpenAdManager.swift │ │ ├── AppStyle.swift │ │ ├── BUAdManager.swift │ │ ├── BookManager.swift │ │ ├── Config.swift │ │ ├── FBEncryptorAES.h │ │ ├── FBEncryptorAES.m │ │ ├── FBEncryptorAESUtils.h │ │ ├── FBEncryptorAESUtils.mm │ │ ├── Reader.swift │ │ ├── Theme.swift │ │ ├── ZSDBManager.h │ │ ├── ZSDBManager.m │ │ ├── ZSDBPropertyModel.h │ │ ├── ZSDBPropertyModel.m │ │ ├── ZSDatabase.swift │ │ ├── ZSEncryptorAESUtils.swift │ │ ├── ZSJSON.swift │ │ ├── ZSLogin.swift │ │ ├── ZSMobileLogin.swift │ │ └── ZSThirdLogin.swift │ ├── Assets.xcassets/ │ │ ├── AppIcon.appiconset/ │ │ │ └── Contents.json │ │ ├── Contents.json │ │ ├── Default-640x1136.imageset/ │ │ │ └── Contents.json │ │ ├── LaunchImage.launchimage/ │ │ │ └── Contents.json │ │ ├── New Version/ │ │ │ ├── Contents.json │ │ │ ├── QDReaderSetting_brightnessDown_24x24_.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── QDReaderSetting_brightnessUp_24x24_.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── bbs_icon_like02_26_26.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── bbs_icon_message_20_20.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── bbs_icon_message_34_34_34x34_.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── bbs_icon_more_big_26_26.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── bbs_icon_personal_34_34_34x34_.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── bbs_icon_share_26_26_26x26_.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── bbs_icon_topic_26_26_26x26_.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── blackboard_bg_375x667_.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── bookshelf_bg_logo_80_19_80x19_.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── bookshelf_icon_gift_34_34.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── bookshelf_icon_gift_new_34_34.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── bookshelf_icon_history_34_34.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── bookshelf_icon_more_10_10_10x10_.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── bookshelf_icon_more_34_34.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── bookshelf_icon_more_wifi_24_24.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── bookshelf_icon_seach_34_34.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── bookshelf_top_bg_one_line.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── bookshelf_top_bg_two_line.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── bookstore_bg.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── bookstore_def_classify_20_20_20x20_.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── chapter_review_send_tip_24x24_.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── community_noReview_sofa_icon.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── community_star_p_32x32_.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── details_icon_star_n_32_32_32x32_.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── discover_Ranking_109x50_.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── discover_booklist_108x50_.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── discover_classify_108x50_.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── discover_icon_audiobook_26_26_26x26_.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── discover_icon_comics_26_26_26x26_.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── discover_icon_exclusive_26_26_26x26_.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── discover_icon_free_26_26_26x26_.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── discover_icon_random_26_26_26x26_.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── discover_icon_timing_24_24_24x24_.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── discover_icon_vip_26_26_26x26_.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── f_author_icon_14x14_.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── f_commentator_icon_14x14_.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── f_doyen_icon_12x12_.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── f_moderator_icon_14x14_.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── f_official_icon_12x12_.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── forum_image_15x13_.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── icon_delete_1_24x24_.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── icon_download_a_24x24_.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── icon_night_30x30_.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── icon_tools_menu_30x30_.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── icon_tools_setting_30x30_.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── icon_top_selected_24x24_.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── mj_refresh.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── nav_goldCoin_34_34_24x24_.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── no_network_140x128_.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── notification_personal_icon_message_24_24_24x24_.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── official_icon_30x30_.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── peach_reader_def_xuance2_32_32_32x32_.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── personal_icon_account_24_24_24x24_.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── personal_icon_booklist_24_24_24x24_.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── personal_icon_darkmode_24_24_23x23_.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── personal_icon_history_24_24_24x24_.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── personal_icon_huntbook_24_24_24x24_.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── personal_icon_id_24_24_24x24_.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── personal_icon_level _24_24_24x24_.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── personal_icon_level3 _24_24_24x24_.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── personal_icon_opinion_24_24_24x24_.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── personal_icon_reply_14_14_14x14_.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── personal_icon_setting_24_24_24x24_.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── personal_icon_topic_14_14_14x14_.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── personal_icon_vip_24_24_24x24_.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── personal_icon_warn_16_16_10x10_.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── personal_icon_zslike_24_24_24x24_.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── pic_day_46x46_.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── pic_night_46x46_.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── profile_personal_icon_message_24_24_24x24_.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── slice_bg_515_88x32_.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── slice_bg_barbie_88x32_.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── slice_bg_jade_88x32_.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── slice_bg_linen_88x32_.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── slice_bg_night_88x32_.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── slice_bg_paper_88x32_.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── slice_bg_white_88x32_.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── slice_bg_wood_88x32_.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── slice_bg_yexiu_88x32_.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── source_checkbox_normal.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── source_checkbox_selected.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── source_delete.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── tab_bbs.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── tab_bbs_sel.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── tab_bookshelf.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── tab_bookshelf_sel.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── tab_bookstore.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── tab_bookstore_sel.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── tab_discover.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── tab_discover_sel.imageset/ │ │ │ │ └── Contents.json │ │ │ ├── tab_profile.imageset/ │ │ │ │ └── Contents.json │ │ │ └── tab_profile_sel.imageset/ │ │ │ └── Contents.json │ │ ├── bg_back_white.imageset/ │ │ │ └── Contents.json │ │ ├── catelog.imageset/ │ │ │ └── Contents.json │ │ ├── fzhongjun.imageset/ │ │ │ └── Contents.json │ │ ├── fzmiaowu.imageset/ │ │ │ └── Contents.json │ │ ├── fzqingke.imageset/ │ │ │ └── Contents.json │ │ ├── fzshusong.imageset/ │ │ │ └── Contents.json │ │ ├── fzsuxinshil.imageset/ │ │ │ └── Contents.json │ │ ├── fzxijinling.imageset/ │ │ │ └── Contents.json │ │ ├── fzyouhei.imageset/ │ │ │ └── Contents.json │ │ ├── kai.imageset/ │ │ │ └── Contents.json │ │ ├── lantingh.imageset/ │ │ │ └── Contents.json │ │ ├── leftbar.imageset/ │ │ │ └── Contents.json │ │ ├── li.imageset/ │ │ │ └── Contents.json │ │ ├── loadside.imageset/ │ │ │ └── Contents.json │ │ ├── longwangchuanshuo.imageset/ │ │ │ └── Contents.json │ │ ├── navigationbar-sidebar.imageset/ │ │ │ └── Contents.json │ │ ├── pianpian.imageset/ │ │ │ └── Contents.json │ │ ├── rightbar.imageset/ │ │ │ └── Contents.json │ │ ├── riwen.imageset/ │ │ │ └── Contents.json │ │ ├── sure_placeholder_error.imageset/ │ │ │ └── Contents.json │ │ ├── weibei.imageset/ │ │ │ └── Contents.json │ │ ├── yapi.imageset/ │ │ │ └── Contents.json │ │ └── zhuishushenqi/ │ │ ├── AppIcon29x29.imageset/ │ │ │ └── Contents.json │ │ ├── AppIcon40x40.imageset/ │ │ │ └── Contents.json │ │ ├── AppIcon57x57.imageset/ │ │ │ └── Contents.json │ │ ├── AppIcon60x60.imageset/ │ │ │ └── Contents.json │ │ ├── AppIcon72x72.imageset/ │ │ │ └── Contents.json │ │ ├── AppIcon76x76.imageset/ │ │ │ └── Contents.json │ │ ├── Apprecommend.imageset/ │ │ │ └── Contents.json │ │ ├── Contents.json │ │ ├── Default-1242x2208.imageset/ │ │ │ └── Contents.json │ │ ├── Default-750x1334.imageset/ │ │ │ └── Contents.json │ │ ├── Default-iPad.imageset/ │ │ │ └── Contents.json │ │ ├── Default.imageset/ │ │ │ └── Contents.json │ │ ├── IQButtonBarArrowDown.imageset/ │ │ │ └── Contents.json │ │ ├── IQButtonBarArrowUp.imageset/ │ │ │ └── Contents.json │ │ ├── Icon.imageset/ │ │ │ └── Contents.json │ │ ├── Icon_114.imageset/ │ │ │ └── Contents.json │ │ ├── Icon_120.imageset/ │ │ │ └── Contents.json │ │ ├── Icon_58.imageset/ │ │ │ └── Contents.json │ │ ├── Icon_80.imageset/ │ │ │ └── Contents.json │ │ ├── NavBack.imageset/ │ │ │ └── Contents.json │ │ ├── NavForward.imageset/ │ │ │ └── Contents.json │ │ ├── NavHome.imageset/ │ │ │ └── Contents.json │ │ ├── NavRefresh.imageset/ │ │ │ └── Contents.json │ │ ├── a_charge_bg.imageset/ │ │ │ └── Contents.json │ │ ├── a_charge_icon.imageset/ │ │ │ └── Contents.json │ │ ├── a_charge_record.imageset/ │ │ │ └── Contents.json │ │ ├── a_charge_record_new.imageset/ │ │ │ └── Contents.json │ │ ├── a_exchange.imageset/ │ │ │ └── Contents.json │ │ ├── a_exchange_new.imageset/ │ │ │ └── Contents.json │ │ ├── a_noadvs.imageset/ │ │ │ └── Contents.json │ │ ├── a_purchase_record.imageset/ │ │ │ └── Contents.json │ │ ├── a_purchase_record_new.imageset/ │ │ │ └── Contents.json │ │ ├── a_zhuishubi.imageset/ │ │ │ └── Contents.json │ │ ├── a_zhuishubi_new.imageset/ │ │ │ └── Contents.json │ │ ├── a_zhuishuquan.imageset/ │ │ │ └── Contents.json │ │ ├── a_zhuishuquan_new.imageset/ │ │ │ └── Contents.json │ │ ├── actionbar_back.imageset/ │ │ │ └── Contents.json │ │ ├── actionbar_close.imageset/ │ │ │ └── Contents.json │ │ ├── actionbar_forward.imageset/ │ │ │ └── Contents.json │ │ ├── actionbar_refresh.imageset/ │ │ │ └── Contents.json │ │ ├── activity_image.imageset/ │ │ │ └── Contents.json │ │ ├── add_book_hint.imageset/ │ │ │ └── Contents.json │ │ ├── add_success.imageset/ │ │ │ └── Contents.json │ │ ├── adwall_blue_pic.imageset/ │ │ │ └── Contents.json │ │ ├── adwall_blue_text.imageset/ │ │ │ └── Contents.json │ │ ├── adwall_white_pic.imageset/ │ │ │ └── Contents.json │ │ ├── adwall_white_text.imageset/ │ │ │ └── Contents.json │ │ ├── alert_error_icon.imageset/ │ │ │ └── Contents.json │ │ ├── alert_success_icon.imageset/ │ │ │ └── Contents.json │ │ ├── app_image.imageset/ │ │ │ └── Contents.json │ │ ├── appointActivity_bg.imageset/ │ │ │ └── Contents.json │ │ ├── appointActivity_close.imageset/ │ │ │ └── Contents.json │ │ ├── ar_shadow.imageset/ │ │ │ └── Contents.json │ │ ├── arrow.imageset/ │ │ │ └── Contents.json │ │ ├── artboard.imageset/ │ │ │ └── Contents.json │ │ ├── autoreading_start.imageset/ │ │ │ └── Contents.json │ │ ├── autoreading_start_landscape.imageset/ │ │ │ └── Contents.json │ │ ├── avatar_wrapper.imageset/ │ │ │ └── Contents.json │ │ ├── background_green.imageset/ │ │ │ └── Contents.json │ │ ├── background_green_selected.imageset/ │ │ │ └── Contents.json │ │ ├── background_white.imageset/ │ │ │ └── Contents.json │ │ ├── background_white_selected.imageset/ │ │ │ └── Contents.json │ │ ├── background_yellow.imageset/ │ │ │ └── Contents.json │ │ ├── background_yellow_selected.imageset/ │ │ │ └── Contents.json │ │ ├── baidu_logo.imageset/ │ │ │ └── Contents.json │ │ ├── baidubutton.imageset/ │ │ │ └── Contents.json │ │ ├── bd_add.imageset/ │ │ │ └── Contents.json │ │ ├── bd_add_count.imageset/ │ │ │ └── Contents.json │ │ ├── bd_add_selected.imageset/ │ │ │ └── Contents.json │ │ ├── bd_arrow_down.imageset/ │ │ │ └── Contents.json │ │ ├── bd_arrow_right.imageset/ │ │ │ └── Contents.json │ │ ├── bd_cancel.imageset/ │ │ │ └── Contents.json │ │ ├── bd_cancel_selected.imageset/ │ │ │ └── Contents.json │ │ ├── bd_mark.imageset/ │ │ │ └── Contents.json │ │ ├── bd_retention_ratio.imageset/ │ │ │ └── Contents.json │ │ ├── bd_search.imageset/ │ │ │ └── Contents.json │ │ ├── bd_search_selected.imageset/ │ │ │ └── Contents.json │ │ ├── bd_share.imageset/ │ │ │ └── Contents.json │ │ ├── bd_share_red.imageset/ │ │ │ └── Contents.json │ │ ├── bd_share_white.imageset/ │ │ │ └── Contents.json │ │ ├── bd_star_empty.imageset/ │ │ │ └── Contents.json │ │ ├── bd_star_filled.imageset/ │ │ │ └── Contents.json │ │ ├── bd_useful.imageset/ │ │ │ └── Contents.json │ │ ├── beijing.imageset/ │ │ │ └── Contents.json │ │ ├── blackGreen_mode_bg.imageset/ │ │ │ └── Contents.json │ │ ├── bofang.imageset/ │ │ │ └── Contents.json │ │ ├── bofangNotify.imageset/ │ │ │ └── Contents.json │ │ ├── bookDirectory_selected.imageset/ │ │ │ └── Contents.json │ │ ├── bookDirectory_separator.imageset/ │ │ │ └── Contents.json │ │ ├── bookShelf_check.imageset/ │ │ │ └── Contents.json │ │ ├── bookShelf_uncheck.imageset/ │ │ │ └── Contents.json │ │ ├── booklist_delete.imageset/ │ │ │ └── Contents.json │ │ ├── brightess_high.imageset/ │ │ │ └── Contents.json │ │ ├── brightess_low.imageset/ │ │ │ └── Contents.json │ │ ├── brightess_white_high.imageset/ │ │ │ └── Contents.json │ │ ├── brightess_white_low.imageset/ │ │ │ └── Contents.json │ │ ├── bs_delete.imageset/ │ │ │ └── Contents.json │ │ ├── bs_delete_selected.imageset/ │ │ │ └── Contents.json │ │ ├── bs_download.imageset/ │ │ │ └── Contents.json │ │ ├── bs_download_cancel.imageset/ │ │ │ └── Contents.json │ │ ├── bs_download_finished.imageset/ │ │ │ └── Contents.json │ │ ├── bs_download_waiting.imageset/ │ │ │ └── Contents.json │ │ ├── bs_downloading.imageset/ │ │ │ └── Contents.json │ │ ├── bs_feed.imageset/ │ │ │ └── Contents.json │ │ ├── bs_feed_icon.imageset/ │ │ │ └── Contents.json │ │ ├── bs_feed_selected.imageset/ │ │ │ └── Contents.json │ │ ├── bs_feed_tip.imageset/ │ │ │ └── Contents.json │ │ ├── bs_feeded.imageset/ │ │ │ └── Contents.json │ │ ├── bs_last_read.imageset/ │ │ │ └── Contents.json │ │ ├── bs_last_read_selected.imageset/ │ │ │ └── Contents.json │ │ ├── bs_stick.imageset/ │ │ │ └── Contents.json │ │ ├── bs_stick_cancel.imageset/ │ │ │ └── Contents.json │ │ ├── bs_stick_icon.imageset/ │ │ │ └── Contents.json │ │ ├── bs_stick_selected.imageset/ │ │ │ └── Contents.json │ │ ├── btn_slider_off.imageset/ │ │ │ └── Contents.json │ │ ├── btn_slider_on.imageset/ │ │ │ └── Contents.json │ │ ├── btn_slider_thumb.imageset/ │ │ │ └── Contents.json │ │ ├── btn_slider_thumb_pressed.imageset/ │ │ │ └── Contents.json │ │ ├── btn_slider_track.imageset/ │ │ │ └── Contents.json │ │ ├── btn_slider_track_pressed.imageset/ │ │ │ └── Contents.json │ │ ├── c_arrow_down.imageset/ │ │ │ └── Contents.json │ │ ├── c_check.imageset/ │ │ │ └── Contents.json │ │ ├── cell_selected_background.imageset/ │ │ │ └── Contents.json │ │ ├── cell_selected_tip.imageset/ │ │ │ └── Contents.json │ │ ├── change_mode_checked.imageset/ │ │ │ └── Contents.json │ │ ├── change_mode_unchecked.imageset/ │ │ │ └── Contents.json │ │ ├── change_resource.imageset/ │ │ │ └── Contents.json │ │ ├── close.imageset/ │ │ │ └── Contents.json │ │ ├── coffee_mode_bg.imageset/ │ │ │ └── Contents.json │ │ ├── common_button_big_blue.imageset/ │ │ │ └── Contents.json │ │ ├── common_button_big_blue_disable.imageset/ │ │ │ └── Contents.json │ │ ├── common_button_big_blue_highlighted.imageset/ │ │ │ └── Contents.json │ │ ├── common_button_white.imageset/ │ │ │ └── Contents.json │ │ ├── common_button_white_highlighted.imageset/ │ │ │ └── Contents.json │ │ ├── common_icon_arrow.imageset/ │ │ │ └── Contents.json │ │ ├── compose_keyboardbutton_background.imageset/ │ │ │ └── Contents.json │ │ ├── compose_toolbar_background.imageset/ │ │ │ └── Contents.json │ │ ├── cover_wrapper.imageset/ │ │ │ └── Contents.json │ │ ├── d_comment.imageset/ │ │ │ └── Contents.json │ │ ├── d_default.imageset/ │ │ │ └── Contents.json │ │ ├── d_delete.imageset/ │ │ │ └── Contents.json │ │ ├── d_follow.imageset/ │ │ │ └── Contents.json │ │ ├── d_followed.imageset/ │ │ │ └── Contents.json │ │ ├── d_forward.imageset/ │ │ │ └── Contents.json │ │ ├── d_forward_large.imageset/ │ │ │ └── Contents.json │ │ ├── d_forwarded_large.imageset/ │ │ │ └── Contents.json │ │ ├── d_icon.imageset/ │ │ │ └── Contents.json │ │ ├── d_votes.imageset/ │ │ │ └── Contents.json │ │ ├── day_mode.imageset/ │ │ │ └── Contents.json │ │ ├── day_mode_bg.imageset/ │ │ │ └── Contents.json │ │ ├── default_avatar_deep.imageset/ │ │ │ └── Contents.json │ │ ├── default_avatar_light.imageset/ │ │ │ └── Contents.json │ │ ├── default_book_cover.imageset/ │ │ │ └── Contents.json │ │ ├── delete_book_tip.imageset/ │ │ │ └── Contents.json │ │ ├── dingshi.imageset/ │ │ │ └── Contents.json │ │ ├── directory.imageset/ │ │ │ └── Contents.json │ │ ├── directory_close.imageset/ │ │ │ └── Contents.json │ │ ├── directory_close_pressed.imageset/ │ │ │ └── Contents.json │ │ ├── directory_not_previewed.imageset/ │ │ │ └── Contents.json │ │ ├── directory_previewed.imageset/ │ │ │ └── Contents.json │ │ ├── directory_vip_chapter.imageset/ │ │ │ └── Contents.json │ │ ├── discover_icon_back_24_24.imageset/ │ │ │ └── Contents.json │ │ ├── discover_icon_bookshelf_24_24.imageset/ │ │ │ └── Contents.json │ │ ├── discover_icon_bookshelf_24_24_p.imageset/ │ │ │ └── Contents.json │ │ ├── discover_icon_more_24_24.imageset/ │ │ │ └── Contents.json │ │ ├── discover_icon_next_36_36.imageset/ │ │ │ └── Contents.json │ │ ├── discover_icon_palying_20_14.imageset/ │ │ │ └── Contents.json │ │ ├── discover_icon_play_76_76.imageset/ │ │ │ └── Contents.json │ │ ├── discover_icon_pouse_76_76.imageset/ │ │ │ └── Contents.json │ │ ├── discover_icon_timing_24_24.imageset/ │ │ │ └── Contents.json │ │ ├── discover_icon_up_36_36.imageset/ │ │ │ └── Contents.json │ │ ├── draft_defaultImage.imageset/ │ │ │ └── Contents.json │ │ ├── empty_failed.imageset/ │ │ │ └── Contents.json │ │ ├── enter_icon.imageset/ │ │ │ └── Contents.json │ │ ├── erciyuanv3.imageset/ │ │ │ └── Contents.json │ │ ├── error.imageset/ │ │ │ └── Contents.json │ │ ├── exchange_bg.imageset/ │ │ │ └── Contents.json │ │ ├── exchange_center.imageset/ │ │ │ └── Contents.json │ │ ├── exchange_duihuan.imageset/ │ │ │ └── Contents.json │ │ ├── exchange_jiantou.imageset/ │ │ │ └── Contents.json │ │ ├── exchange_jinru.imageset/ │ │ │ └── Contents.json │ │ ├── exchange_jinru_h.imageset/ │ │ │ └── Contents.json │ │ ├── exchange_kuang.imageset/ │ │ │ └── Contents.json │ │ ├── exchange_tixian.imageset/ │ │ │ └── Contents.json │ │ ├── exp_current.imageset/ │ │ │ └── Contents.json │ │ ├── exp_more.imageset/ │ │ │ └── Contents.json │ │ ├── exp_paiming.imageset/ │ │ │ └── Contents.json │ │ ├── exp_progress.imageset/ │ │ │ └── Contents.json │ │ ├── exp_publishBook.imageset/ │ │ │ └── Contents.json │ │ ├── exp_publishTopic.imageset/ │ │ │ └── Contents.json │ │ ├── exp_updateHeader.imageset/ │ │ │ └── Contents.json │ │ ├── f_author_icon.imageset/ │ │ │ └── Contents.json │ │ ├── f_commentator_icon.imageset/ │ │ │ └── Contents.json │ │ ├── f_distillate.imageset/ │ │ │ └── Contents.json │ │ ├── f_doyen_icon.imageset/ │ │ │ └── Contents.json │ │ ├── f_game_center_icon.imageset/ │ │ │ └── Contents.json │ │ ├── f_girl_icon.imageset/ │ │ │ └── Contents.json │ │ ├── f_hot.imageset/ │ │ │ └── Contents.json │ │ ├── f_invent_icon.imageset/ │ │ │ └── Contents.json │ │ ├── f_like_comment.imageset/ │ │ │ └── Contents.json │ │ ├── f_like_comment_selected.imageset/ │ │ │ └── Contents.json │ │ ├── f_like_icon.imageset/ │ │ │ └── Contents.json │ │ ├── f_like_icon_select.imageset/ │ │ │ └── Contents.json │ │ ├── f_moderator_icon.imageset/ │ │ │ └── Contents.json │ │ ├── f_official_icon.imageset/ │ │ │ └── Contents.json │ │ ├── f_ramble_default_cover.imageset/ │ │ │ └── Contents.json │ │ ├── f_ramble_icon.imageset/ │ │ │ └── Contents.json │ │ ├── f_sort.imageset/ │ │ │ └── Contents.json │ │ ├── f_today_topic.imageset/ │ │ │ └── Contents.json │ │ ├── fanhui.imageset/ │ │ │ └── Contents.json │ │ ├── fb_cancel.imageset/ │ │ │ └── Contents.json │ │ ├── fb_cancel_selected.imageset/ │ │ │ └── Contents.json │ │ ├── fb_gray_angle.imageset/ │ │ │ └── Contents.json │ │ ├── fb_set.imageset/ │ │ │ └── Contents.json │ │ ├── fb_set_selected.imageset/ │ │ │ └── Contents.json │ │ ├── fb_white_angle.imageset/ │ │ │ └── Contents.json │ │ ├── fd_tip.imageset/ │ │ │ └── Contents.json │ │ ├── fd_tip_title.imageset/ │ │ │ └── Contents.json │ │ ├── feedback.imageset/ │ │ │ └── Contents.json │ │ ├── fenxiang.imageset/ │ │ │ └── Contents.json │ │ ├── float_forum_icon.imageset/ │ │ │ └── Contents.json │ │ ├── float_forum_icon_selected.imageset/ │ │ │ └── Contents.json │ │ ├── font_decrease.imageset/ │ │ │ └── Contents.json │ │ ├── font_decrease_selected.imageset/ │ │ │ └── Contents.json │ │ ├── font_decrease_unable.imageset/ │ │ │ └── Contents.json │ │ ├── font_increase.imageset/ │ │ │ └── Contents.json │ │ ├── font_increase_selected.imageset/ │ │ │ └── Contents.json │ │ ├── font_increase_unable.imageset/ │ │ │ └── Contents.json │ │ ├── forum_avatar_small.imageset/ │ │ │ └── Contents.json │ │ ├── forum_book_entry.imageset/ │ │ │ └── Contents.json │ │ ├── forum_collect_icon.imageset/ │ │ │ └── Contents.json │ │ ├── forum_collect_icon_selected.imageset/ │ │ │ └── Contents.json │ │ ├── forum_comment_cancel.imageset/ │ │ │ └── Contents.json │ │ ├── forum_comment_cancel_selected.imageset/ │ │ │ └── Contents.json │ │ ├── forum_comment_confirm.imageset/ │ │ │ └── Contents.json │ │ ├── forum_comment_confirm_selected.imageset/ │ │ │ └── Contents.json │ │ ├── forum_comment_confirm_unable.imageset/ │ │ │ └── Contents.json │ │ ├── forum_comment_input_bg.imageset/ │ │ │ └── Contents.json │ │ ├── forum_comment_reply.imageset/ │ │ │ └── Contents.json │ │ ├── forum_comment_reply_selected.imageset/ │ │ │ └── Contents.json │ │ ├── forum_comment_start.imageset/ │ │ │ └── Contents.json │ │ ├── forum_comment_start_selected.imageset/ │ │ │ └── Contents.json │ │ ├── forum_enter.imageset/ │ │ │ └── Contents.json │ │ ├── forum_gray_star.imageset/ │ │ │ └── Contents.json │ │ ├── forum_help_default_cover.imageset/ │ │ │ └── Contents.json │ │ ├── forum_hint_arrow_right_up.imageset/ │ │ │ └── Contents.json │ │ ├── forum_like_icon.imageset/ │ │ │ └── Contents.json │ │ ├── forum_liked_icon.imageset/ │ │ │ └── Contents.json │ │ ├── forum_more_icon.imageset/ │ │ │ └── Contents.json │ │ ├── forum_more_icon_selected.imageset/ │ │ │ └── Contents.json │ │ ├── forum_more_option.imageset/ │ │ │ └── Contents.json │ │ ├── forum_more_option_selected.imageset/ │ │ │ └── Contents.json │ │ ├── forum_not_voted.imageset/ │ │ │ └── Contents.json │ │ ├── forum_post_comment_icon.imageset/ │ │ │ └── Contents.json │ │ ├── forum_post_red.imageset/ │ │ │ └── Contents.json │ │ ├── forum_post_red_selected.imageset/ │ │ │ └── Contents.json │ │ ├── forum_post_shadow.imageset/ │ │ │ └── Contents.json │ │ ├── forum_public_help_icon.imageset/ │ │ │ └── Contents.json │ │ ├── forum_public_review_icon.imageset/ │ │ │ └── Contents.json │ │ ├── forum_red_star.imageset/ │ │ │ └── Contents.json │ │ ├── forum_share_icon.imageset/ │ │ │ └── Contents.json │ │ ├── forum_share_icon_selected.imageset/ │ │ │ └── Contents.json │ │ ├── forum_topic_type_post.imageset/ │ │ │ └── Contents.json │ │ ├── forum_topic_type_vote.imageset/ │ │ │ └── Contents.json │ │ ├── forum_vote_add.imageset/ │ │ │ └── Contents.json │ │ ├── forum_vote_add_selected.imageset/ │ │ │ └── Contents.json │ │ ├── forum_vote_delete.imageset/ │ │ │ └── Contents.json │ │ ├── forum_voted.imageset/ │ │ │ └── Contents.json │ │ ├── fuliv3.imageset/ │ │ │ └── Contents.json │ │ ├── fullscreen.imageset/ │ │ │ └── Contents.json │ │ ├── g_boy.imageset/ │ │ │ └── Contents.json │ │ ├── g_boy_avatar.imageset/ │ │ │ └── Contents.json │ │ ├── g_boy_selected.imageset/ │ │ │ └── Contents.json │ │ ├── g_boy_tip.imageset/ │ │ │ └── Contents.json │ │ ├── g_close.imageset/ │ │ │ └── Contents.json │ │ ├── g_girl.imageset/ │ │ │ └── Contents.json │ │ ├── g_girl_avatar.imageset/ │ │ │ └── Contents.json │ │ ├── g_girl_selected.imageset/ │ │ │ └── Contents.json │ │ ├── g_girl_tip.imageset/ │ │ │ └── Contents.json │ │ ├── g_left_line.imageset/ │ │ │ └── Contents.json │ │ ├── g_right_line.imageset/ │ │ │ └── Contents.json │ │ ├── g_sex_tip.imageset/ │ │ │ └── Contents.json │ │ ├── g_tip_overview.imageset/ │ │ │ └── Contents.json │ │ ├── game_image.imageset/ │ │ │ └── Contents.json │ │ ├── gou.imageset/ │ │ │ └── Contents.json │ │ ├── green_mode_bg.imageset/ │ │ │ └── Contents.json │ │ ├── guanbi.imageset/ │ │ │ └── Contents.json │ │ ├── guanbiNotify.imageset/ │ │ │ └── Contents.json │ │ ├── hsm_default_avatar.imageset/ │ │ │ └── Contents.json │ │ ├── hsm_icon_1.imageset/ │ │ │ └── Contents.json │ │ ├── hsm_icon_2.imageset/ │ │ │ └── Contents.json │ │ ├── hsm_icon_3.imageset/ │ │ │ └── Contents.json │ │ ├── hsm_new_topic_tip.imageset/ │ │ │ └── Contents.json │ │ ├── interstitialIcon.imageset/ │ │ │ └── Contents.json │ │ ├── interstitialIconclose.imageset/ │ │ │ └── Contents.json │ │ ├── jiantou.imageset/ │ │ │ └── Contents.json │ │ ├── jingdian.imageset/ │ │ │ └── Contents.json │ │ ├── jinjiv3.imageset/ │ │ │ └── Contents.json │ │ ├── landscape.imageset/ │ │ │ └── Contents.json │ │ ├── last_read_resource.imageset/ │ │ │ └── Contents.json │ │ ├── liantu.imageset/ │ │ │ └── Contents.json │ │ ├── liebiao.imageset/ │ │ │ └── Contents.json │ │ ├── lishiv3.imageset/ │ │ │ └── Contents.json │ │ ├── listbofang.imageset/ │ │ │ └── Contents.json │ │ ├── listpaixu.imageset/ │ │ │ └── Contents.json │ │ ├── listshengyin.imageset/ │ │ │ └── Contents.json │ │ ├── listxiazai.imageset/ │ │ │ └── Contents.json │ │ ├── listxuanji.imageset/ │ │ │ └── Contents.json │ │ ├── login_background.imageset/ │ │ │ └── Contents.json │ │ ├── login_country_background.imageset/ │ │ │ └── Contents.json │ │ ├── login_country_background_highlighted.imageset/ │ │ │ └── Contents.json │ │ ├── logo.imageset/ │ │ │ └── Contents.json │ │ ├── logo_recommend.imageset/ │ │ │ └── Contents.json │ │ ├── logo_recommend_ipad.imageset/ │ │ │ └── Contents.json │ │ ├── lsm_view_shadow.imageset/ │ │ │ └── Contents.json │ │ ├── m_success_icon.imageset/ │ │ │ └── Contents.json │ │ ├── mode_baidu.imageset/ │ │ │ └── Contents.json │ │ ├── mode_cp.imageset/ │ │ │ └── Contents.json │ │ ├── mode_easou.imageset/ │ │ │ └── Contents.json │ │ ├── mode_juhe.imageset/ │ │ │ └── Contents.json │ │ ├── mode_leidian.imageset/ │ │ │ └── Contents.json │ │ ├── mode_shenma.imageset/ │ │ │ └── Contents.json │ │ ├── mode_sogou.imageset/ │ │ │ └── Contents.json │ │ ├── mode_soso.imageset/ │ │ │ └── Contents.json │ │ ├── mode_tieba.imageset/ │ │ │ └── Contents.json │ │ ├── mode_zhineng.imageset/ │ │ │ └── Contents.json │ │ ├── month_jiaobiao.imageset/ │ │ │ └── Contents.json │ │ ├── month_kuang.imageset/ │ │ │ └── Contents.json │ │ ├── month_kuang1.imageset/ │ │ │ └── Contents.json │ │ ├── month_touxiang.imageset/ │ │ │ └── Contents.json │ │ ├── monthly_book_mark.imageset/ │ │ │ └── Contents.json │ │ ├── monthly_mark.imageset/ │ │ │ └── Contents.json │ │ ├── move-down.imageset/ │ │ │ └── Contents.json │ │ ├── move-up.imageset/ │ │ │ └── Contents.json │ │ ├── mp_arrow_down.imageset/ │ │ │ └── Contents.json │ │ ├── mp_arrow_left.imageset/ │ │ │ └── Contents.json │ │ ├── mp_arrow_right.imageset/ │ │ │ └── Contents.json │ │ ├── mp_arrow_up.imageset/ │ │ │ └── Contents.json │ │ ├── mp_refresh.imageset/ │ │ │ └── Contents.json │ │ ├── mp_selected.imageset/ │ │ │ └── Contents.json │ │ ├── mp_unselected.imageset/ │ │ │ └── Contents.json │ │ ├── mv_ad_shadow.imageset/ │ │ │ └── Contents.json │ │ ├── mv_arrow.imageset/ │ │ │ └── Contents.json │ │ ├── mv_avatar_wrapper.imageset/ │ │ │ └── Contents.json │ │ ├── mv_book_icon.imageset/ │ │ │ └── Contents.json │ │ ├── mv_header_background.imageset/ │ │ │ └── Contents.json │ │ ├── mv_header_shadow.imageset/ │ │ │ └── Contents.json │ │ ├── mv_home_icon.imageset/ │ │ │ └── Contents.json │ │ ├── mv_hot_vote.imageset/ │ │ │ └── Contents.json │ │ ├── mv_mani_icon.imageset/ │ │ │ └── Contents.json │ │ ├── mv_post_icon.imageset/ │ │ │ └── Contents.json │ │ ├── mv_reply_icon.imageset/ │ │ │ └── Contents.json │ │ ├── mv_separator.imageset/ │ │ │ └── Contents.json │ │ ├── mv_title.imageset/ │ │ │ └── Contents.json │ │ ├── mv_topic_icon.imageset/ │ │ │ └── Contents.json │ │ ├── mv_vote_icon.imageset/ │ │ │ └── Contents.json │ │ ├── n_comment.imageset/ │ │ │ └── Contents.json │ │ ├── n_icon.imageset/ │ │ │ └── Contents.json │ │ ├── n_link.imageset/ │ │ │ └── Contents.json │ │ ├── n_reply.imageset/ │ │ │ └── Contents.json │ │ ├── n_vote.imageset/ │ │ │ └── Contents.json │ │ ├── nav_add_book.imageset/ │ │ │ └── Contents.json │ │ ├── nav_add_book_selected.imageset/ │ │ │ └── Contents.json │ │ ├── nav_arrow_down.imageset/ │ │ │ └── Contents.json │ │ ├── nav_arrow_up.imageset/ │ │ │ └── Contents.json │ │ ├── nav_back_red.imageset/ │ │ │ └── Contents.json │ │ ├── nav_back_red_selected.imageset/ │ │ │ └── Contents.json │ │ ├── nav_back_white.imageset/ │ │ │ └── Contents.json │ │ ├── nav_back_white_selected.imageset/ │ │ │ └── Contents.json │ │ ├── nav_home_side_menu.imageset/ │ │ │ └── Contents.json │ │ ├── nav_home_side_menu_selected.imageset/ │ │ │ └── Contents.json │ │ ├── navigationbar_background.imageset/ │ │ │ └── Contents.json │ │ ├── navigationbar_background_os7.imageset/ │ │ │ └── Contents.json │ │ ├── network_cut.imageset/ │ │ │ └── Contents.json │ │ ├── newUser_bg.imageset/ │ │ │ └── Contents.json │ │ ├── newUser_close.imageset/ │ │ │ └── Contents.json │ │ ├── new_nav_hilighted.imageset/ │ │ │ └── Contents.json │ │ ├── new_nav_night_hilighted.imageset/ │ │ │ └── Contents.json │ │ ├── new_nav_night_normal.imageset/ │ │ │ └── Contents.json │ │ ├── new_nav_night_selected.imageset/ │ │ │ └── Contents.json │ │ ├── new_nav_normal.imageset/ │ │ │ └── Contents.json │ │ ├── new_nav_selected.imageset/ │ │ │ └── Contents.json │ │ ├── new_nav_vertical_separator.imageset/ │ │ │ └── Contents.json │ │ ├── new_notification_icon.imageset/ │ │ │ └── Contents.json │ │ ├── new_notification_tip.imageset/ │ │ │ └── Contents.json │ │ ├── night_mode.imageset/ │ │ │ └── Contents.json │ │ ├── nl_qq.imageset/ │ │ │ └── Contents.json │ │ ├── nl_wechat.imageset/ │ │ │ └── Contents.json │ │ ├── nl_weibo.imageset/ │ │ │ └── Contents.json │ │ ├── nl_xiaomi.imageset/ │ │ │ └── Contents.json │ │ ├── no_network.imageset/ │ │ │ └── Contents.json │ │ ├── no_resource.imageset/ │ │ │ └── Contents.json │ │ ├── notice_image.imageset/ │ │ │ └── Contents.json │ │ ├── nvshengv3.imageset/ │ │ │ └── Contents.json │ │ ├── official_icon.imageset/ │ │ │ └── Contents.json │ │ ├── p_anywhere.imageset/ │ │ │ └── Contents.json │ │ ├── p_anywhere_ipad.imageset/ │ │ │ └── Contents.json │ │ ├── p_anywhere_title.imageset/ │ │ │ └── Contents.json │ │ ├── p_anywhere_title_ipad.imageset/ │ │ │ └── Contents.json │ │ ├── p_btn.imageset/ │ │ │ └── Contents.json │ │ ├── p_btn_selected.imageset/ │ │ │ └── Contents.json │ │ ├── p_fast.imageset/ │ │ │ └── Contents.json │ │ ├── p_fast_ipad.imageset/ │ │ │ └── Contents.json │ │ ├── p_fast_title.imageset/ │ │ │ └── Contents.json │ │ ├── p_fast_title_ipad.imageset/ │ │ │ └── Contents.json │ │ ├── p_guest_tip.imageset/ │ │ │ └── Contents.json │ │ ├── p_guest_tip_ipad.imageset/ │ │ │ └── Contents.json │ │ ├── p_logo.imageset/ │ │ │ └── Contents.json │ │ ├── p_logo_ipad.imageset/ │ │ │ └── Contents.json │ │ ├── p_logo_small.imageset/ │ │ │ └── Contents.json │ │ ├── p_logo_small_ipad.imageset/ │ │ │ └── Contents.json │ │ ├── p_qq_icon.imageset/ │ │ │ └── Contents.json │ │ ├── p_sina_icon.imageset/ │ │ │ └── Contents.json │ │ ├── p_synchronize.imageset/ │ │ │ └── Contents.json │ │ ├── p_synchronize_ipad.imageset/ │ │ │ └── Contents.json │ │ ├── p_synchronize_title.imageset/ │ │ │ └── Contents.json │ │ ├── p_synchronize_title_ipad.imageset/ │ │ │ └── Contents.json │ │ ├── pay_alipay.imageset/ │ │ │ └── Contents.json │ │ ├── pay_apple.imageset/ │ │ │ └── Contents.json │ │ ├── pay_record_option.imageset/ │ │ │ └── Contents.json │ │ ├── pay_weixin.imageset/ │ │ │ └── Contents.json │ │ ├── pf_header_bg.imageset/ │ │ │ └── Contents.json │ │ ├── pf_new_notification.imageset/ │ │ │ └── Contents.json │ │ ├── pf_ratebottom.imageset/ │ │ │ └── Contents.json │ │ ├── pf_rateupper.imageset/ │ │ │ └── Contents.json │ │ ├── pink_mode_bg.imageset/ │ │ │ └── Contents.json │ │ ├── play_big_image.imageset/ │ │ │ └── Contents.json │ │ ├── player_back.imageset/ │ │ │ └── Contents.json │ │ ├── player_pause.imageset/ │ │ │ └── Contents.json │ │ ├── player_play.imageset/ │ │ │ └── Contents.json │ │ ├── portrait.imageset/ │ │ │ └── Contents.json │ │ ├── post_tip_icon.imageset/ │ │ │ └── Contents.json │ │ ├── preview_btn.imageset/ │ │ │ └── Contents.json │ │ ├── previewed_icon.imageset/ │ │ │ └── Contents.json │ │ ├── progress_cancel.imageset/ │ │ │ └── Contents.json │ │ ├── progress_cancel_selected.imageset/ │ │ │ └── Contents.json │ │ ├── progresshud_background.imageset/ │ │ │ └── Contents.json │ │ ├── promotion_image.imageset/ │ │ │ └── Contents.json │ │ ├── qqicon.imageset/ │ │ │ └── Contents.json │ │ ├── ranking_arrow_down.imageset/ │ │ │ └── Contents.json │ │ ├── ranking_other.imageset/ │ │ │ └── Contents.json │ │ ├── rd_func_bg.imageset/ │ │ │ └── Contents.json │ │ ├── rd_reading_add_icon.imageset/ │ │ │ └── Contents.json │ │ ├── rd_reading_added_icon.imageset/ │ │ │ └── Contents.json │ │ ├── rd_reading_change_icon.imageset/ │ │ │ └── Contents.json │ │ ├── readAloud.imageset/ │ │ │ └── Contents.json │ │ ├── readAloud_exit.imageset/ │ │ │ └── Contents.json │ │ ├── readAloud_more.imageset/ │ │ │ └── Contents.json │ │ ├── readAloud_more_highLighted.imageset/ │ │ │ └── Contents.json │ │ ├── readAloud_pause.imageset/ │ │ │ └── Contents.json │ │ ├── readAloud_play.imageset/ │ │ │ └── Contents.json │ │ ├── reading_landscape.imageset/ │ │ │ └── Contents.json │ │ ├── reading_more_setting.imageset/ │ │ │ └── Contents.json │ │ ├── reading_more_setting_landscape.imageset/ │ │ │ └── Contents.json │ │ ├── reading_record_icon.imageset/ │ │ │ └── Contents.json │ │ ├── reading_setting.imageset/ │ │ │ └── Contents.json │ │ ├── reading_tip_menu.imageset/ │ │ │ └── Contents.json │ │ ├── reading_tip_next.imageset/ │ │ │ └── Contents.json │ │ ├── reading_tip_pre.imageset/ │ │ │ └── Contents.json │ │ ├── recommend_new.imageset/ │ │ │ └── Contents.json │ │ ├── recommend_top.imageset/ │ │ │ └── Contents.json │ │ ├── red_menu_icon.imageset/ │ │ │ └── Contents.json │ │ ├── refresh_arrow.imageset/ │ │ │ └── Contents.json │ │ ├── refresh_finish.imageset/ │ │ │ └── Contents.json │ │ ├── reward.imageset/ │ │ │ └── Contents.json │ │ ├── reward_launch.imageset/ │ │ │ └── Contents.json │ │ ├── rp_arrow.imageset/ │ │ │ └── Contents.json │ │ ├── rp_badge_disabled.imageset/ │ │ │ └── Contents.json │ │ ├── rp_badge_normal.imageset/ │ │ │ └── Contents.json │ │ ├── rp_checkbox.imageset/ │ │ │ └── Contents.json │ │ ├── rp_checkbox_unchecked.imageset/ │ │ │ └── Contents.json │ │ ├── rp_disabled_bg.imageset/ │ │ │ └── Contents.json │ │ ├── rp_info.imageset/ │ │ │ └── Contents.json │ │ ├── rp_no_network.imageset/ │ │ │ └── Contents.json │ │ ├── rp_normal_bg.imageset/ │ │ │ └── Contents.json │ │ ├── rp_selected_bg.imageset/ │ │ │ └── Contents.json │ │ ├── rsm_icon_0.imageset/ │ │ │ └── Contents.json │ │ ├── rsm_icon_1.imageset/ │ │ │ └── Contents.json │ │ ├── rsm_icon_10.imageset/ │ │ │ └── Contents.json │ │ ├── rsm_icon_2.imageset/ │ │ │ └── Contents.json │ │ ├── rsm_icon_3.imageset/ │ │ │ └── Contents.json │ │ ├── rsm_icon_4.imageset/ │ │ │ └── Contents.json │ │ ├── rsm_icon_5.imageset/ │ │ │ └── Contents.json │ │ ├── rsm_icon_6.imageset/ │ │ │ └── Contents.json │ │ ├── rsm_icon_7.imageset/ │ │ │ └── Contents.json │ │ ├── rsm_icon_8.imageset/ │ │ │ └── Contents.json │ │ ├── rsm_icon_9.imageset/ │ │ │ └── Contents.json │ │ ├── rsm_icon_comic.imageset/ │ │ │ └── Contents.json │ │ ├── rsm_icon_exclusive.imageset/ │ │ │ └── Contents.json │ │ ├── rsm_icon_monthly.imageset/ │ │ │ └── Contents.json │ │ ├── rsm_icon_recommended.imageset/ │ │ │ └── Contents.json │ │ ├── rsm_icon_store.imageset/ │ │ │ └── Contents.json │ │ ├── rsm_icon_wifi.imageset/ │ │ │ └── Contents.json │ │ ├── rsm_view_shadow.imageset/ │ │ │ └── Contents.json │ │ ├── s_wechat.imageset/ │ │ │ └── Contents.json │ │ ├── sdk_weibo_logo.imageset/ │ │ │ └── Contents.json │ │ ├── search_bg.imageset/ │ │ │ └── Contents.json │ │ ├── search_cannot_delete.imageset/ │ │ │ └── Contents.json │ │ ├── search_cannot_refresh.imageset/ │ │ │ └── Contents.json │ │ ├── search_delete.imageset/ │ │ │ └── Contents.json │ │ ├── search_history_mark.imageset/ │ │ │ └── Contents.json │ │ ├── search_icon.imageset/ │ │ │ └── Contents.json │ │ ├── search_refresh.imageset/ │ │ │ └── Contents.json │ │ ├── setting_fontFanJian_jian.imageset/ │ │ │ └── Contents.json │ │ ├── setting_font_bigger_nomal.imageset/ │ │ │ └── Contents.json │ │ ├── setting_font_bigger_nomal_h.imageset/ │ │ │ └── Contents.json │ │ ├── setting_font_bigger_uneable.imageset/ │ │ │ └── Contents.json │ │ ├── setting_font_fan.imageset/ │ │ │ └── Contents.json │ │ ├── setting_font_jian.imageset/ │ │ │ └── Contents.json │ │ ├── setting_font_nomal.imageset/ │ │ │ └── Contents.json │ │ ├── setting_font_selected.imageset/ │ │ │ └── Contents.json │ │ ├── setting_font_snaller_normal.imageset/ │ │ │ └── Contents.json │ │ ├── setting_font_snaller_normal_h.imageset/ │ │ │ └── Contents.json │ │ ├── setting_font_snaller_uneable.imageset/ │ │ │ └── Contents.json │ │ ├── setting_orientation_hengpin.imageset/ │ │ │ └── Contents.json │ │ ├── setting_orientation_shupin.imageset/ │ │ │ └── Contents.json │ │ ├── setting_paly.imageset/ │ │ │ └── Contents.json │ │ ├── setting_set.imageset/ │ │ │ └── Contents.json │ │ ├── setting_space_big_nomal.imageset/ │ │ │ └── Contents.json │ │ ├── setting_space_big_selected.imageset/ │ │ │ └── Contents.json │ │ ├── setting_space_nomal.imageset/ │ │ │ └── Contents.json │ │ ├── setting_space_selected.imageset/ │ │ │ └── Contents.json │ │ ├── setting_space_small_nomal.imageset/ │ │ │ └── Contents.json │ │ ├── setting_space_small_selected.imageset/ │ │ │ └── Contents.json │ │ ├── setting_theme_selected.imageset/ │ │ │ └── Contents.json │ │ ├── shang.imageset/ │ │ │ └── Contents.json │ │ ├── sheepskin_mode_bg.imageset/ │ │ │ └── Contents.json │ │ ├── shengyin.imageset/ │ │ │ └── Contents.json │ │ ├── show_right_sidemenu_icon.imageset/ │ │ │ └── Contents.json │ │ ├── sign_bg.imageset/ │ │ │ └── Contents.json │ │ ├── sign_gotoSign.imageset/ │ │ │ └── Contents.json │ │ ├── sign_signed.imageset/ │ │ │ └── Contents.json │ │ ├── sign_success.imageset/ │ │ │ └── Contents.json │ │ ├── slider.imageset/ │ │ │ └── Contents.json │ │ ├── slider_black.imageset/ │ │ │ └── Contents.json │ │ ├── slider_red.imageset/ │ │ │ └── Contents.json │ │ ├── sm_arrow_down.imageset/ │ │ │ └── Contents.json │ │ ├── sm_bottom_background.imageset/ │ │ │ └── Contents.json │ │ ├── sm_bottom_shadow.imageset/ │ │ │ └── Contents.json │ │ ├── sm_exit.imageset/ │ │ │ └── Contents.json │ │ ├── sm_exit_selected.imageset/ │ │ │ └── Contents.json │ │ ├── sm_forum_bg.imageset/ │ │ │ └── Contents.json │ │ ├── sm_forum_bg_selected.imageset/ │ │ │ └── Contents.json │ │ ├── sm_topic_tip_bg.imageset/ │ │ │ └── Contents.json │ │ ├── sm_web_back.imageset/ │ │ │ └── Contents.json │ │ ├── sm_web_forward.imageset/ │ │ │ └── Contents.json │ │ ├── sm_web_refresh.imageset/ │ │ │ └── Contents.json │ │ ├── splash_bottom_icon.imageset/ │ │ │ └── Contents.json │ │ ├── splash_bottom_logo.imageset/ │ │ │ └── Contents.json │ │ ├── success.imageset/ │ │ │ └── Contents.json │ │ ├── switch.imageset/ │ │ │ └── Contents.json │ │ ├── task_lanch.imageset/ │ │ │ └── Contents.json │ │ ├── task_person.imageset/ │ │ │ └── Contents.json │ │ ├── task_praise.imageset/ │ │ │ └── Contents.json │ │ ├── task_share.imageset/ │ │ │ └── Contents.json │ │ ├── timeline_relationship_icon_addattention.imageset/ │ │ │ └── Contents.json │ │ ├── timeline_relationship_icon_attention.imageset/ │ │ │ └── Contents.json │ │ ├── toolbar_baidu.imageset/ │ │ │ └── Contents.json │ │ ├── toolbar_baidu_active.imageset/ │ │ │ └── Contents.json │ │ ├── toolbar_bg.imageset/ │ │ │ └── Contents.json │ │ ├── toolbar_bg1.imageset/ │ │ │ └── Contents.json │ │ ├── toolbar_close.imageset/ │ │ │ └── Contents.json │ │ ├── toolbar_fold.imageset/ │ │ │ └── Contents.json │ │ ├── toolbar_message.imageset/ │ │ │ └── Contents.json │ │ ├── toolbar_message_active.imageset/ │ │ │ └── Contents.json │ │ ├── toolbar_navBack.imageset/ │ │ │ └── Contents.json │ │ ├── toolbar_navBack_active.imageset/ │ │ │ └── Contents.json │ │ ├── toolbar_navForward.imageset/ │ │ │ └── Contents.json │ │ ├── toolbar_navForward_active.imageset/ │ │ │ └── Contents.json │ │ ├── toolbar_unfold.imageset/ │ │ │ └── Contents.json │ │ ├── toolbar_weibo.imageset/ │ │ │ └── Contents.json │ │ ├── toolbar_weibo_active.imageset/ │ │ │ └── Contents.json │ │ ├── update_image.imageset/ │ │ │ └── Contents.json │ │ ├── userCenter_account-1.imageset/ │ │ │ └── Contents.json │ │ ├── userCenter_account.imageset/ │ │ │ └── Contents.json │ │ ├── userCenter_bookList-1.imageset/ │ │ │ └── Contents.json │ │ ├── userCenter_bookList.imageset/ │ │ │ └── Contents.json │ │ ├── userCenter_comment.imageset/ │ │ │ └── Contents.json │ │ ├── userCenter_exchange.imageset/ │ │ │ └── Contents.json │ │ ├── userCenter_experience-1.imageset/ │ │ │ └── Contents.json │ │ ├── userCenter_experience.imageset/ │ │ │ └── Contents.json │ │ ├── userCenter_history.imageset/ │ │ │ └── Contents.json │ │ ├── userCenter_msg-1.imageset/ │ │ │ └── Contents.json │ │ ├── userCenter_msg.imageset/ │ │ │ └── Contents.json │ │ ├── userCenter_rate-1.imageset/ │ │ │ └── Contents.json │ │ ├── userCenter_rate.imageset/ │ │ │ └── Contents.json │ │ ├── userCenter_setting-1.imageset/ │ │ │ └── Contents.json │ │ ├── userCenter_setting.imageset/ │ │ │ └── Contents.json │ │ ├── userCenter_setting2.imageset/ │ │ │ └── Contents.json │ │ ├── userCenter_task.imageset/ │ │ │ └── Contents.json │ │ ├── userCenter_topic-1.imageset/ │ │ │ └── Contents.json │ │ ├── userCenter_topic.imageset/ │ │ │ └── Contents.json │ │ ├── userCenter_wechat.imageset/ │ │ │ └── Contents.json │ │ ├── verify_code_button.imageset/ │ │ │ └── Contents.json │ │ ├── verify_code_button_highlighted.imageset/ │ │ │ └── Contents.json │ │ ├── video_volumeoff.imageset/ │ │ │ └── Contents.json │ │ ├── video_volumeon.imageset/ │ │ │ └── Contents.json │ │ ├── violet_mode_bg.imageset/ │ │ │ └── Contents.json │ │ ├── voicefenxiang.imageset/ │ │ │ └── Contents.json │ │ ├── volumeoff.imageset/ │ │ │ └── Contents.json │ │ ├── volumeon.imageset/ │ │ │ └── Contents.json │ │ ├── wangwenv3.imageset/ │ │ │ └── Contents.json │ │ ├── water_mode_bg.imageset/ │ │ │ └── Contents.json │ │ ├── wechat.imageset/ │ │ │ └── Contents.json │ │ ├── wechatQR.imageset/ │ │ │ └── Contents.json │ │ ├── weekGreen_mode_bg.imageset/ │ │ │ └── Contents.json │ │ ├── weekPink_mode_bg.imageset/ │ │ │ └── Contents.json │ │ ├── white_mode_bg.imageset/ │ │ │ └── Contents.json │ │ ├── xia.imageset/ │ │ │ └── Contents.json │ │ ├── yellow_mode_bg.imageset/ │ │ │ └── Contents.json │ │ ├── yuan.imageset/ │ │ │ └── Contents.json │ │ ├── yuanchuangv3.imageset/ │ │ │ └── Contents.json │ │ ├── zanting.imageset/ │ │ │ └── Contents.json │ │ ├── zantingNotify.imageset/ │ │ │ └── Contents.json │ │ ├── zhonghev3.imageset/ │ │ │ └── Contents.json │ │ ├── zhui.imageset/ │ │ │ └── Contents.json │ │ ├── zssq_image.imageset/ │ │ │ └── Contents.json │ │ ├── zuire.imageset/ │ │ │ └── Contents.json │ │ ├── zuixin.imageset/ │ │ │ └── Contents.json │ │ ├── 方正兰亭黑.imageset/ │ │ │ └── Contents.json │ │ ├── 日文字体.imageset/ │ │ │ └── Contents.json │ │ ├── 楷体.imageset/ │ │ │ └── Contents.json │ │ ├── 翩翩体.imageset/ │ │ │ └── Contents.json │ │ ├── 隶变.imageset/ │ │ │ └── Contents.json │ │ ├── 雅痞.imageset/ │ │ │ └── Contents.json │ │ ├── 魏碑.imageset/ │ │ │ └── Contents.json │ │ ├── 黑体.imageset/ │ │ │ └── Contents.json │ │ └── 默认.imageset/ │ │ └── Contents.json │ ├── Base/ │ │ ├── Controllers/ │ │ │ ├── BaseViewController.swift │ │ │ ├── SideViewController.swift │ │ │ ├── ZSBaseNavigationViewController.swift │ │ │ ├── ZSBaseTableViewController.swift │ │ │ ├── ZSSegmentViewController.swift │ │ │ └── ZSSegmenuViewController.swift │ │ ├── Models/ │ │ │ ├── SwiftyJSON.swift │ │ │ ├── XYCBaseModel.h │ │ │ ├── XYCBaseModel.m │ │ │ └── ZSCacheHelper.swift │ │ ├── ViewManager/ │ │ │ ├── ZSAppInfo.swift │ │ │ ├── ZSBaseTableViewManger.swift │ │ │ ├── ZSBookDownloader.swift │ │ │ └── ZSCellAdapterProtocol.swift │ │ └── Views/ │ │ ├── CommunityView.swift │ │ ├── DarkStarView.swift │ │ ├── DarkView.swift │ │ ├── EmptyView.swift │ │ ├── EmptyView.xib │ │ ├── LightStarView.swift │ │ ├── LightView.swift │ │ ├── QSLoadingView.swift │ │ ├── RateView.swift │ │ ├── UIScrollView+StateView.h │ │ ├── UIScrollView+StateView.m │ │ ├── V2FPSLabel.swift │ │ ├── XYCActionSheet.swift │ │ ├── ZSBaseCellAdapter.swift │ │ └── ZSBaseSectionAdapter.swift │ ├── Base.lproj/ │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── Client/ │ │ ├── QSAPI.swift │ │ └── QSNetworkManager.swift │ ├── Extension/ │ │ ├── Alamofire+ZSExtension.swift │ │ ├── ApplicationExtension.swift │ │ ├── Array+ZSExtension.swift │ │ ├── Date+Extension.swift │ │ ├── DateIntervalFormatter+formatter.swift │ │ ├── Dictionary+QSExtension.swift │ │ ├── DispatchTime+Extension.swift │ │ ├── LocalizedUtils.swift │ │ ├── NSDate+Extension.h │ │ ├── NSDate+Extension.m │ │ ├── NSObject+Extension.swift │ │ ├── NSString+Encode.h │ │ ├── NSString+Encode.m │ │ ├── NotificationCenter+QSExtension.swift │ │ ├── Reachability.swift │ │ ├── SQLite+Extension.swift │ │ ├── String+QSExtension.swift │ │ ├── SwiftStdlib/ │ │ │ ├── FloatExtensions.swift │ │ │ └── IntExtensions.swift │ │ ├── UIButton+Extension.swift │ │ ├── UICollectionView+QSExtension.swift │ │ ├── UIColor+Theme.swift │ │ ├── UIFont+Extension.swift │ │ ├── UIFont+ZSExtension.h │ │ ├── UIFont+ZSExtension.m │ │ ├── UIImage+QSData.swift │ │ ├── UIImageView+zhuishu.swift │ │ ├── UILabel+zhuishu.swift │ │ ├── UINavigationItem+BackItem.h │ │ ├── UINavigationItem+BackItem.m │ │ ├── UIStoryboardExtension.swift │ │ ├── UITableView+FINAutomaticHeightCell.swift │ │ ├── UITableView+QSGeneric.swift │ │ ├── UITableView+swizzling.swift │ │ ├── UITableViewCell+ZSExtension.swift │ │ ├── UIView+ScreenShot.swift │ │ ├── UIViewController+Alert.swift │ │ ├── UserDefaultsExtension.swift │ │ ├── Value.swift │ │ └── copy.txt │ ├── Info.plist │ ├── IntroducePage/ │ │ ├── QSIntroduceCell.swift │ │ ├── QSIntroduceCell.xib │ │ ├── QSIntroduceReadCell.swift │ │ ├── QSIntroduceReadCell.xib │ │ ├── QSLastIntroduceCell.swift │ │ ├── QSLastIntroduceCell.xib │ │ └── ZSIntroducePage.swift │ ├── NewVersion/ │ │ ├── BookShelf/ │ │ │ ├── NavigationBar.swift │ │ │ ├── ZSBookLocalShelfViewController.swift │ │ │ ├── ZSBookShelfHeaderView.swift │ │ │ ├── ZSBookShelfViewController.swift │ │ │ ├── ZSBookShelfViewModel.swift │ │ │ ├── ZSRefreshTextHeader.swift │ │ │ ├── ZSShelfManager.swift │ │ │ ├── ZSShelfOperatingView.swift │ │ │ ├── ZSShelfStorage.swift │ │ │ ├── ZSShelfTableViewCell.swift │ │ │ ├── ZSSyncStorage.swift │ │ │ ├── ZSToast.swift │ │ │ └── mjRefreshHeadTitle.plist │ │ ├── BookStore/ │ │ │ └── ZSBookStoreViewController.swift │ │ ├── Comment/ │ │ │ ├── ZSForumComment.swift │ │ │ ├── ZSForumPageCell.swift │ │ │ ├── ZSForumPageFooterView.swift │ │ │ ├── ZSForumPageHeaderView.swift │ │ │ ├── ZSForumPageTitleHeaderView.swift │ │ │ ├── ZSForumPageViewController.swift │ │ │ ├── ZSForumTextView.swift │ │ │ ├── ZSForumToolBar.swift │ │ │ ├── ZSForumViewModel.swift │ │ │ ├── ZSPostReview.swift │ │ │ ├── ZSPostReviewAuthor.swift │ │ │ ├── ZSPostReviewBook.swift │ │ │ └── ZSPostReviewHelpful.swift │ │ ├── Community/ │ │ │ ├── ZSCommunityCell.swift │ │ │ ├── ZSCommunityHot.swift │ │ │ ├── ZSCommunityHotCell.swift │ │ │ ├── ZSCommunityNavigationBar.swift │ │ │ ├── ZSCommunityViewController.swift │ │ │ ├── ZSCommunityViewModel.swift │ │ │ ├── ZSDynamicHeaderView.swift │ │ │ ├── ZSDynamicViewModel.swift │ │ │ ├── ZSFollowings.swift │ │ │ ├── ZSInsertedBookScoreView.swift │ │ │ ├── ZSNoNetworkView.swift │ │ │ ├── ZSNotification.swift │ │ │ ├── ZSNotificationCell.swift │ │ │ ├── ZSNotificationViewController.swift │ │ │ ├── ZSNotificationViewModel.swift │ │ │ ├── ZSRefreshFooter.swift │ │ │ └── ZSUserDynamicViewController.swift │ │ ├── CoreText/ │ │ │ ├── CTSettings.swift │ │ │ ├── MarkupParser.swift │ │ │ ├── ZSDisplayView.swift │ │ │ └── ZSTouchAnchorView.swift │ │ ├── Device/ │ │ │ ├── ZSFloatingManager.swift │ │ │ ├── ZSFloatingView.swift │ │ │ ├── ZSFloatingViewController.swift │ │ │ ├── ZSFloatingWindow.swift │ │ │ └── ZSMemoryFloatingView.swift │ │ ├── Discover/ │ │ │ ├── ZSDiscoverHeaderView.swift │ │ │ ├── ZSDiscoverItem.swift │ │ │ ├── ZSDiscoverNavigationBar.swift │ │ │ ├── ZSDiscoverViewController.swift │ │ │ └── ZSSearchViewController.swift │ │ ├── File/ │ │ │ └── EncrtptorText │ │ ├── Mine/ │ │ │ ├── ZSAddSourceTextField.h │ │ │ ├── ZSAddSourceTextField.m │ │ │ ├── ZSAddSourceViewController.swift │ │ │ ├── ZSDetailButtonCell.swift │ │ │ ├── ZSMineHeaderView.swift │ │ │ ├── ZSMineMenuItem.swift │ │ │ ├── ZSMineNavigationBar.swift │ │ │ ├── ZSMineViewController.swift │ │ │ ├── ZSMineViewModel.swift │ │ │ ├── ZSRegularVerifyViewController.swift │ │ │ ├── ZSSetting.swift │ │ │ ├── ZSSourceCell.swift │ │ │ └── ZSSourcesViewController.swift │ │ ├── Network/ │ │ │ └── ZSNetwork.swift │ │ ├── Reader/ │ │ │ ├── Color.swift │ │ │ ├── ReaderBar.swift │ │ │ ├── ReaderNavigationBar.swift │ │ │ ├── ThemeManager.swift │ │ │ ├── ZSBookCache.swift │ │ │ ├── ZSBookChapter.swift │ │ │ ├── ZSBookDiskCache.swift │ │ │ ├── ZSBookMemoryCache.swift │ │ │ ├── ZSHorizonalViewController.swift │ │ │ ├── ZSNormalViewController.swift │ │ │ ├── ZSPageTableViewCell.swift │ │ │ ├── ZSPageViewController.swift │ │ │ ├── ZSReadHistory.swift │ │ │ ├── ZSReader.swift │ │ │ ├── ZSReaderBaseViewModel.swift │ │ │ ├── ZSReaderBottomBar.swift │ │ │ ├── ZSReaderBottomBigBar.swift │ │ │ ├── ZSReaderCache.swift │ │ │ ├── ZSReaderCatalogViewController.swift │ │ │ ├── ZSReaderController.swift │ │ │ ├── ZSReaderDownloader.swift │ │ │ ├── ZSReaderStyleSelectionView.swift │ │ │ ├── ZSReaderThemeSelectionView.swift │ │ │ ├── ZSReaderToolbar.swift │ │ │ ├── ZSReaderTopbar.swift │ │ │ ├── ZSReaderTouchArea.swift │ │ │ ├── ZSReaderVCProtocol.swift │ │ │ └── ZSVerticalViewController.swift │ │ ├── Search/ │ │ │ ├── AikanHtmlParser.h │ │ │ ├── AikanHtmlParser.m │ │ │ ├── AikanParserModel.h │ │ │ ├── AikanParserModel.m │ │ │ ├── AikanParserModel.swift │ │ │ ├── ZSAikanHtmlParser.swift │ │ │ ├── ZSBookInfoHeaderView.swift │ │ │ ├── ZSHeaderSearch.swift │ │ │ ├── ZSHeaderSearchCell.swift │ │ │ ├── ZSHotWord.swift │ │ │ ├── ZSSearchBookView.swift │ │ │ ├── ZSSearchBookViewController.swift │ │ │ ├── ZSSearchBookViewModel.swift │ │ │ ├── ZSSearchHistory.swift │ │ │ ├── ZSSearchHotView.swift │ │ │ ├── ZSSearchHotwords.swift │ │ │ ├── ZSSearchInfoBottomView.swift │ │ │ ├── ZSSearchInfoTableViewCell.swift │ │ │ ├── ZSSearchInfoViewController.swift │ │ │ ├── ZSSearchRecommendView.swift │ │ │ ├── ZSSearchResultCell.swift │ │ │ ├── ZSSearchResultView.swift │ │ │ ├── ZSSourceManager.swift │ │ │ └── ZSTopSearchBar.swift │ │ ├── Tabbar/ │ │ │ └── ZSTabBarController.swift │ │ ├── ZSAPI/ │ │ │ ├── LICENSE │ │ │ ├── ZSAPI/ │ │ │ │ └── Classes/ │ │ │ │ └── ZSAPI.swift │ │ │ └── ZSAPI.podspec │ │ ├── ZSAppConfig/ │ │ │ ├── LICENSE │ │ │ ├── ZSAppConfig/ │ │ │ │ └── Classes/ │ │ │ │ ├── AppStyle.swift │ │ │ │ ├── Config.swift │ │ │ │ ├── Reader.swift │ │ │ │ └── Theme.swift │ │ │ └── ZSAppConfig.podspec │ │ ├── ZSExtension/ │ │ │ ├── LICENSE │ │ │ ├── ZSExtension/ │ │ │ │ └── Classes/ │ │ │ │ ├── Alamofire+ZSExtension.swift │ │ │ │ ├── Array+ZSExtension.swift │ │ │ │ ├── Date+Extension.swift │ │ │ │ ├── DateIntervalFormatter+formatter.swift │ │ │ │ ├── Dictionary+QSExtension.swift │ │ │ │ ├── DispatchTime+Extension.swift │ │ │ │ ├── LocalizedUtils.swift │ │ │ │ ├── NSData+Base64.h │ │ │ │ ├── NSData+Base64.m │ │ │ │ ├── NSDate+Extension.h │ │ │ │ ├── NSDate+Extension.m │ │ │ │ ├── NSObject+Extension.swift │ │ │ │ ├── NSString+Encode.h │ │ │ │ ├── NSString+Encode.m │ │ │ │ ├── Network.swift │ │ │ │ ├── NotificationCenter+QSExtension.swift │ │ │ │ ├── Reachability.swift │ │ │ │ ├── SQLite+Extension.swift │ │ │ │ ├── String+QSExtension.swift │ │ │ │ ├── SwiftStdlib/ │ │ │ │ │ ├── FloatExtensions.swift │ │ │ │ │ └── IntExtensions.swift │ │ │ │ ├── UIButton+Extension.swift │ │ │ │ ├── UICollectionView+QSExtension.swift │ │ │ │ ├── UIColor+Theme.swift │ │ │ │ ├── UIFont+Extension.swift │ │ │ │ ├── UIFont+ZSExtension.h │ │ │ │ ├── UIFont+ZSExtension.m │ │ │ │ ├── UIImage+QSData.swift │ │ │ │ ├── UIImageView+zhuishu.swift │ │ │ │ ├── UILabel+zhuishu.swift │ │ │ │ ├── UINavigationItem+BackItem.h │ │ │ │ ├── UINavigationItem+BackItem.m │ │ │ │ ├── UITableView+FINAutomaticHeightCell.swift │ │ │ │ ├── UITableView+QSGeneric.swift │ │ │ │ ├── UITableView+swizzling.swift │ │ │ │ ├── UITableViewCell+ZSExtension.swift │ │ │ │ ├── UIView+ScreenShot.swift │ │ │ │ ├── UIViewController+Alert.swift │ │ │ │ ├── Value.swift │ │ │ │ └── ZSBaseService.swift │ │ │ └── ZSExtension.podspec │ │ └── ZSThirdPartSDK/ │ │ ├── LICENSE │ │ ├── ZSThirdPartSDK/ │ │ │ └── Classes/ │ │ │ ├── TencentOpenAPI.framework/ │ │ │ │ ├── Headers/ │ │ │ │ │ ├── QQApiInterface.h │ │ │ │ │ ├── QQApiInterfaceObject.h │ │ │ │ │ ├── TencentOAuth.h │ │ │ │ │ └── sdkdef.h │ │ │ │ └── TencentOpenAPI │ │ │ ├── ThirdPardSDK-Info.plist │ │ │ ├── WXApiRequestHandler.h │ │ │ ├── WXApiRequestHandler.m │ │ │ ├── WeChatSDK1.8.3/ │ │ │ │ ├── README.txt │ │ │ │ ├── WXApi.h │ │ │ │ ├── WXApiObject.h │ │ │ │ ├── WechatAuthSDK.h │ │ │ │ └── libWeChatSDK.a │ │ │ ├── ZSLogin.h │ │ │ ├── ZSLogin.m │ │ │ ├── ZSLoginHelper.h │ │ │ ├── ZSLoginHelper.m │ │ │ ├── ZSLoginService.swift │ │ │ ├── ZSMobileLogin.swift │ │ │ ├── ZSQQUser.swift │ │ │ ├── ZSThirdLogin.h │ │ │ ├── ZSThirdLogin.m │ │ │ ├── ZSThirdPartSDK.h │ │ │ ├── ZSWXAccessTokenResp.swift │ │ │ └── libWeiboSDK/ │ │ │ ├── WBHttpRequest.h │ │ │ ├── WeiboSDK.bundle/ │ │ │ │ └── others/ │ │ │ │ └── mfp.cer │ │ │ ├── WeiboSDK.h │ │ │ └── libWeiboSDK.a │ │ └── ZSThirdPartSDK.podspec │ ├── RightSide/ │ │ ├── Category/ │ │ │ ├── Controllers/ │ │ │ │ ├── Category/ │ │ │ │ │ └── ZSCatelogViewController.swift │ │ │ │ └── CategoryDetail/ │ │ │ │ ├── ZSBaseSegmentItemViewController.swift │ │ │ │ ├── ZSCategoryDetailViewController.swift │ │ │ │ └── ZSCatelogItemViewController.swift │ │ │ ├── Models/ │ │ │ │ ├── AddBookAPI.swift │ │ │ │ ├── AllChapterAPI.swift │ │ │ │ ├── BookDetailAPI.swift │ │ │ │ ├── BookShelfAPI.swift │ │ │ │ ├── CategoryModel.swift │ │ │ │ ├── DynamicAPI.swift │ │ │ │ ├── LatestChapterAPI.swift │ │ │ │ ├── RankingAPI.swift │ │ │ │ ├── RankingDetailAPI.swift │ │ │ │ ├── ShelfMessageAPI.swift │ │ │ │ ├── ZSCatelogHeaderView.swift │ │ │ │ ├── ZSCatelogModel.swift │ │ │ │ └── ZSCatelogParameterModel.swift │ │ │ ├── ViewModel/ │ │ │ │ ├── ZSCatelogDetailViewModel.swift │ │ │ │ ├── ZSCatelogViewModel.swift │ │ │ │ └── ZSSegmentBaseViewModel.swift │ │ │ └── Views/ │ │ │ ├── .DS_Store~876a9ee6afa162f4fb71eff5fa02f2e0dbe52ae2 │ │ │ ├── .DS_Store~HEAD │ │ │ └── ZSCatelogCell.swift │ │ ├── Network/ │ │ │ └── QSHotwords.swift │ │ ├── Ranking/ │ │ │ ├── Controllers/ │ │ │ │ ├── QSRankViewController.swift │ │ │ │ ├── RankingViewController.swift │ │ │ │ ├── TopDetailViewController.swift │ │ │ │ └── ZSRankViewController.swift │ │ │ ├── Models/ │ │ │ │ └── QSRankModel.swift │ │ │ ├── Service/ │ │ │ │ ├── ZSRankDetailWebService.swift │ │ │ │ └── ZSRankService.swift │ │ │ ├── ViewModel/ │ │ │ │ ├── ZSRankDetailViewModel.swift │ │ │ │ └── ZSRankViewModel.swift │ │ │ └── Views/ │ │ │ ├── RankingViewCell.swift │ │ │ ├── ReadHistoryCell.swift │ │ │ ├── ReadHistoryCell.xib │ │ │ ├── TopDetailCell.swift │ │ │ └── TopDetailCell.xib │ │ ├── Search/ │ │ │ ├── Controllers/ │ │ │ │ ├── SearchDetailViewController.swift │ │ │ │ └── SearchViewController.swift │ │ │ ├── Models/ │ │ │ │ └── QSSearchItem.swift │ │ │ ├── QSHistoryHeaderView.swift │ │ │ ├── QSSearchAutoCompleteTable.swift │ │ │ ├── QSSearchHeaderView.swift │ │ │ ├── QSSearchInteractor.swift │ │ │ ├── QSSearchPresenter.swift │ │ │ ├── QSSearchProtocols.swift │ │ │ ├── QSSearchResultTable.swift │ │ │ ├── QSSearchRouter.swift │ │ │ ├── QSSearchViewController+SearchBar.swift │ │ │ ├── QSSearchViewController+Transition.swift │ │ │ ├── QSSearchViewController.swift │ │ │ ├── Views/ │ │ │ │ ├── QSHistoryCell.swift │ │ │ │ ├── QSHistoryCell.swift~876a9ee6afa162f4fb71eff5fa02f2e0dbe52ae2 │ │ │ │ ├── QSHistoryCell.swift~HEAD │ │ │ │ ├── QSHistoryCell.xib │ │ │ │ ├── SearchView.swift │ │ │ │ ├── ZSHistoryHeaderView.swift │ │ │ │ ├── ZSSearchHeaderView.swift │ │ │ │ ├── ZSSearchViewCell.swift │ │ │ │ └── ZSSearchViewCell.xib │ │ │ └── ZSSearchResultViewController.swift │ │ └── Topic/ │ │ ├── Controllers/ │ │ │ ├── Filter/ │ │ │ │ └── ZSFilterThemeViewController.swift │ │ │ ├── ThemeTopic/ │ │ │ │ ├── QSThemeTopicInteractor.swift │ │ │ │ ├── QSThemeTopicPresenter.swift │ │ │ │ ├── QSThemeTopicProtocols.swift │ │ │ │ ├── QSThemeTopicRouter.swift │ │ │ │ └── QSThemeTopicViewController.swift │ │ │ └── TopicDetail/ │ │ │ ├── QSTopicDetailInteractor.swift │ │ │ ├── QSTopicDetailPresenter.swift │ │ │ ├── QSTopicDetailProtocols.swift │ │ │ ├── QSTopicDetailRouter.swift │ │ │ ├── QSTopicDetailViewController.swift │ │ │ └── TopicDetailViewController.swift │ │ ├── Models/ │ │ │ ├── ThemeTopicModel.swift │ │ │ ├── TopicDetailHeader.swift │ │ │ ├── TopicDetailModel.swift │ │ │ └── ZSFilterThemeModel.swift │ │ ├── ViewModel/ │ │ │ ├── ZSFilterThemeViewModel.swift │ │ │ └── ZSThemeTopicViewModel.swift │ │ └── Views/ │ │ ├── ThemeTopicCell.swift │ │ ├── ThemeTopicCell.xib │ │ ├── TopicDetailCell.swift │ │ ├── TopicDetailCell.xib │ │ ├── TopicDetailHeaderCell.swift │ │ ├── TopicDetailHeaderCell.xib │ │ └── ZSFilterThemeCell.swift │ ├── Root/ │ │ ├── Controllers/ │ │ │ ├── DynamicViewController.swift │ │ │ ├── LeftViewController.swift │ │ │ ├── LookBookViewController.swift │ │ │ ├── ReadHistoryViewController.swift │ │ │ ├── RightViewController.swift │ │ │ ├── RootViewController+FetchData.swift │ │ │ ├── RootViewController+Subviews.swift │ │ │ ├── RootViewController.swift │ │ │ ├── ZSBookShelvesViewController.swift │ │ │ ├── ZSForumViewController.swift │ │ │ ├── ZSImportBookViewController.swift │ │ │ ├── ZSLocalShelfViewController.swift │ │ │ ├── ZSLoginViewController.swift │ │ │ ├── ZSModifyNicknameViewController.swift │ │ │ ├── ZSMyViewController.swift │ │ │ ├── ZSRootViewController.swift │ │ │ ├── ZSSettingViewController.swift │ │ │ ├── ZSShelfViewController.swift │ │ │ ├── ZSUserAccountViewController.swift │ │ │ ├── ZSUserInfoViewController.swift │ │ │ ├── ZSVoiceBookCategoryViewController.swift │ │ │ ├── ZSVoiceBookSegmentViewController.swift │ │ │ ├── ZSVoicePlayListViewController.swift │ │ │ ├── ZSVoicePlayViewController.swift │ │ │ ├── ZSVoicePlayerViewController.swift │ │ │ ├── ZSVoucherViewController.swift │ │ │ ├── ZSWebItem.swift │ │ │ ├── ZSWebStoreViewController.swift │ │ │ ├── ZSWebViewController.swift │ │ │ └── ZSWebViewControllerDelegate.swift │ │ ├── Models/ │ │ │ ├── QSHotModel.swift │ │ │ ├── ZSAccount.swift │ │ │ ├── ZSCoin.swift │ │ │ ├── ZSConfigUtil.swift │ │ │ ├── ZSQQUser.swift │ │ │ ├── ZSShelfMessage.swift │ │ │ ├── ZSUserBind.swift │ │ │ ├── ZSUserBookshelf.swift │ │ │ ├── ZSUserDetail.swift │ │ │ ├── ZSVoiceAlbum.swift │ │ │ ├── ZSVoucher.swift │ │ │ ├── ZSWebBIHandler.swift │ │ │ ├── ZSWebContext.swift │ │ │ ├── ZSWebJumpHandler.swift │ │ │ ├── ZSWebSpeakHandler.swift │ │ │ ├── ZSWebToolHandler.swift │ │ │ ├── ZSWebUserHandler.swift │ │ │ └── ZSYJSchemeHandle.swift │ │ ├── Service/ │ │ │ ├── ZSBaseService.swift │ │ │ ├── ZSDiscussWebService.swift │ │ │ ├── ZSLoginService.swift │ │ │ ├── ZSMyService.swift │ │ │ ├── ZSRootWebService.swift │ │ │ └── ZSShelfWebService.swift │ │ ├── ViewModel/ │ │ │ ├── ZSDiscussViewModel.swift │ │ │ ├── ZSHomeViewModel.swift │ │ │ ├── ZSLocalShelfViewModel.swift │ │ │ ├── ZSMyViewModel.swift │ │ │ ├── ZSProtocol.swift │ │ │ ├── ZSRootViewModel.swift │ │ │ ├── ZSSetting.plist │ │ │ ├── ZSSettingViewModel.swift │ │ │ ├── ZSShelfViewModel.swift │ │ │ └── ZSVoicePlayViewModel.swift │ │ └── Views/ │ │ ├── BarButton.swift │ │ ├── DynamicCell.swift │ │ ├── DynamicCell.xib │ │ ├── DynamicCell.xib~876a9ee6afa162f4fb71eff5fa02f2e0dbe52ae2 │ │ ├── DynamicCell.xib~HEAD │ │ ├── HomeListViewCell.swift │ │ ├── QSHelpViewCell.swift │ │ ├── QSHelpViewCell.xib │ │ ├── QSHomeDeleteBtn.swift │ │ ├── QSLaunchRecView.swift │ │ ├── QSLaunchRecView.xib │ │ ├── QSSegmentDropView.swift │ │ ├── RightTableViewCell.swift │ │ ├── RootNavigationView.swift │ │ ├── SegMenu.swift │ │ ├── SwipableCell.swift │ │ ├── ZSLeftViewCell.swift │ │ ├── ZSLoginVerifyView.swift │ │ ├── ZSLoginView.swift │ │ ├── ZSMyCell.swift │ │ ├── ZSMyHeaderView.swift │ │ ├── ZSReviewsCell.swift │ │ ├── ZSReviewsCell.xib │ │ ├── ZSSwipeCell.swift │ │ ├── ZSThirdLoginView.swift │ │ ├── ZSUserBindCell.swift │ │ ├── ZSVoiceCategoryCell.swift │ │ ├── ZSVoiceCategoryHeaderView.swift │ │ ├── ZSVoicePlayProgressView.swift │ │ ├── ZSVoicePlayerCatelogHeaderView.swift │ │ ├── ZSVoicePlayerCatelogView.swift │ │ ├── ZSVoicePlayerView.swift │ │ ├── ZSVoiceSegmentView.swift │ │ └── ZSVoucherView.swift │ ├── Splash/ │ │ ├── QSSplashScreen.swift │ │ └── SplashViewController.swift │ ├── TXTReader/ │ │ ├── BookComment/ │ │ │ ├── Model/ │ │ │ │ └── ZSBookCTLayoutModel.swift │ │ │ ├── Service/ │ │ │ │ ├── ZSBookCTService.swift │ │ │ │ └── ZSBookCommentService.swift │ │ │ ├── View/ │ │ │ │ ├── BookCommentViewController.swift │ │ │ │ ├── QSBookCommentInteractor.swift │ │ │ │ ├── QSBookCommentPresenter.swift │ │ │ │ ├── QSBookCommentProtocols.swift │ │ │ │ ├── QSBookCommentRouter.swift │ │ │ │ ├── QSBookCommentViewController.swift │ │ │ │ ├── TXTReader.storyboard │ │ │ │ ├── ZSBestReviewView.swift │ │ │ │ ├── ZSBookReviewViewController.swift │ │ │ │ ├── ZSFeelingView.swift │ │ │ │ ├── ZSHottwitterViewController.swift │ │ │ │ ├── ZSReviewDetailView.swift │ │ │ │ └── ZSWriteReview.swift │ │ │ └── ViewModel/ │ │ │ ├── ZSBookCTViewModel.swift │ │ │ └── ZSBookCommentViewModel.swift │ │ ├── BookDetail/ │ │ │ ├── BookDetailViewController.swift │ │ │ ├── Models/ │ │ │ │ ├── Book.swift │ │ │ │ ├── BookComment.swift │ │ │ │ ├── BookCommentDetail.swift │ │ │ │ ├── BookDetail.swift │ │ │ │ ├── BookShelf.json │ │ │ │ ├── BookShelf.swift │ │ │ │ ├── BookShelfInfo.swift │ │ │ │ ├── ChapterInfo.json │ │ │ │ ├── ChapterInfo.swift │ │ │ │ ├── Chapters.json │ │ │ │ ├── PageInfo.swift │ │ │ │ ├── QSBook.swift │ │ │ │ ├── QSBookList.swift │ │ │ │ ├── QSChapter.swift │ │ │ │ ├── QSHotComment.swift │ │ │ │ ├── QSPage.swift │ │ │ │ ├── QSReaderParse.swift │ │ │ │ ├── QSReaderSetting.swift │ │ │ │ ├── QSReaderViewFlowLayout.swift │ │ │ │ ├── QSRecomment.swift │ │ │ │ ├── QSRecord.swift │ │ │ │ ├── ResourceModel.swift │ │ │ │ ├── UpdateInfo.swift │ │ │ │ ├── User.swift │ │ │ │ └── ZSReadRecord.swift │ │ │ ├── QSBookDetailInteractor.swift │ │ │ ├── QSBookDetailPresenter.swift │ │ │ ├── QSBookDetailProtocols.swift │ │ │ ├── QSBookDetailRouter.swift │ │ │ ├── QSBookDetailViewController.swift │ │ │ ├── QSInterestedViewController.swift │ │ │ ├── Views/ │ │ │ │ ├── .DS_Store~876a9ee6afa162f4fb71eff5fa02f2e0dbe52ae2 │ │ │ │ ├── .DS_Store~HEAD │ │ │ │ ├── BookCommentCell.swift │ │ │ │ ├── BookCommentCell.xib │ │ │ │ ├── BookCommentViewCell.swift │ │ │ │ ├── BookCommentViewCell.xib │ │ │ │ ├── BookDetailHeader.swift │ │ │ │ ├── BookDetailHeader.xib │ │ │ │ ├── CategoryButton.swift │ │ │ │ ├── CategoryTableViewCell.swift │ │ │ │ ├── CategoryTableViewCell.xib │ │ │ │ ├── ChangeSourceCell.swift │ │ │ │ ├── ChangeSourceCell.xib │ │ │ │ ├── HotCommentCell.swift │ │ │ │ ├── HotCommentCell.xib │ │ │ │ ├── PageView.swift │ │ │ │ ├── ProgressView.swift │ │ │ │ ├── QSBatteryView.swift │ │ │ │ ├── QSBookDetailContentView.swift │ │ │ │ ├── QSBookDetailRateView.swift │ │ │ │ ├── QSBookDetailTagsView.swift │ │ │ │ ├── QSBookListViewCell.swift │ │ │ │ ├── QSBookListViewCell.xib │ │ │ │ ├── QSDiscussCell.swift │ │ │ │ ├── QSDiscussCell.xib │ │ │ │ ├── QSRecommendCell.swift │ │ │ │ ├── QSRecommendCell.xib │ │ │ │ ├── ToolBar.swift │ │ │ │ ├── UserfulCell.swift │ │ │ │ └── UserfulCell.xib │ │ │ ├── ZSDetailInfoCell.swift │ │ │ ├── ZSDetailSection.swift │ │ │ └── ZSDetailViewController.swift │ │ ├── Category/ │ │ │ ├── CategoryController.swift │ │ │ ├── CategoryViewController.swift │ │ │ ├── ChangeSourceViewController.swift │ │ │ ├── QSCategoryInteractor.swift │ │ │ ├── QSCategoryPresenter.swift │ │ │ ├── QSCategoryProtocols.swift │ │ │ ├── QSCategoryReaderViewController.swift │ │ │ └── QSCategoryRouter.swift │ │ ├── Community/ │ │ │ ├── QSCommunityInteractor.swift │ │ │ ├── QSCommunityPresenter.swift │ │ │ ├── QSCommunityProtocols.swift │ │ │ ├── QSCommunityRouter.swift │ │ │ └── QSCommunityViewController.swift │ │ ├── Reader/ │ │ │ ├── Model/ │ │ │ │ ├── MonitorFileChangeHelp.h │ │ │ │ ├── MonitorFileChangeHelp.m │ │ │ │ ├── ZSBoughtInfo.swift │ │ │ │ ├── ZSChapterBody.swift │ │ │ │ ├── ZSChapterInfo.swift │ │ │ │ ├── ZSChapterSelectModel.swift │ │ │ │ ├── ZSHTTPConnection.h │ │ │ │ ├── ZSHTTPConnection.m │ │ │ │ ├── ZSHTTPTool.h │ │ │ │ ├── ZSHTTPTool.m │ │ │ │ ├── ZSReaderProtocol.swift │ │ │ │ └── web/ │ │ │ │ ├── index.html │ │ │ │ ├── s.css │ │ │ │ └── upload.html │ │ │ ├── PageViewController.swift │ │ │ ├── QSMoreSettingController.swift │ │ │ ├── QSReaderBackgroundViewController.swift │ │ │ ├── QSReaderViewController.swift │ │ │ ├── QSTextProtocols.swift │ │ │ ├── QSTextReaderController.swift │ │ │ ├── QSTextRouter.swift │ │ │ ├── Service/ │ │ │ │ ├── ZSFontService.swift │ │ │ │ ├── ZSReaderManager.swift │ │ │ │ └── ZSReaderWebService.swift │ │ │ ├── TXTReaderViewController.swift │ │ │ ├── View/ │ │ │ │ ├── ZSChapterPayView.swift │ │ │ │ ├── ZSChapterSelectView.swift │ │ │ │ ├── ZSFontViewController.swift │ │ │ │ ├── ZSHorizonalMoveCell.swift │ │ │ │ ├── ZSHorizonalMoveViewController.swift │ │ │ │ ├── ZSMultiplePayView.swift │ │ │ │ ├── ZSNoneAnimationViewController.swift │ │ │ │ ├── ZSReaderBaseViewController.swift │ │ │ │ └── ZSReaderViewController.swift │ │ │ ├── ViewModel/ │ │ │ │ ├── ZSBookBoughtViewModel.swift │ │ │ │ ├── ZSFontViewModel.swift │ │ │ │ ├── ZSReaderViewModel+Bought.swift │ │ │ │ └── ZSReaderViewModel.swift │ │ │ ├── ZSSpeakerCell.swift │ │ │ └── ZSSpeakerViewController.swift │ │ └── Speech/ │ │ ├── Model/ │ │ │ ├── Network.swift │ │ │ ├── Speaker.swift │ │ │ ├── TTSConfig.swift │ │ │ ├── TTSResource/ │ │ │ │ ├── common.jet │ │ │ │ ├── speakers.plist │ │ │ │ └── xiaoyan.jet │ │ │ ├── VoiceBook.swift │ │ │ ├── iflyMSC.framework/ │ │ │ │ ├── Headers/ │ │ │ │ │ ├── IFlyAudioSession.h │ │ │ │ │ ├── IFlyDataUploader.h │ │ │ │ │ ├── IFlyDebugLog.h │ │ │ │ │ ├── IFlyISVDelegate.h │ │ │ │ │ ├── IFlyISVRecognizer.h │ │ │ │ │ ├── IFlyMSC.h │ │ │ │ │ ├── IFlyPcmRecorder.h │ │ │ │ │ ├── IFlyRecognizerView.h │ │ │ │ │ ├── IFlyRecognizerViewDelegate.h │ │ │ │ │ ├── IFlyResourceUtil.h │ │ │ │ │ ├── IFlySetting.h │ │ │ │ │ ├── IFlySpeechConstant.h │ │ │ │ │ ├── IFlySpeechError.h │ │ │ │ │ ├── IFlySpeechEvaluator.h │ │ │ │ │ ├── IFlySpeechEvaluatorDelegate.h │ │ │ │ │ ├── IFlySpeechEvent.h │ │ │ │ │ ├── IFlySpeechRecognizer.h │ │ │ │ │ ├── IFlySpeechRecognizerDelegate.h │ │ │ │ │ ├── IFlySpeechSynthesizer.h │ │ │ │ │ ├── IFlySpeechSynthesizerDelegate.h │ │ │ │ │ ├── IFlySpeechUtility.h │ │ │ │ │ ├── IFlyUserWords.h │ │ │ │ │ ├── IFlyVoiceWakeuper.h │ │ │ │ │ └── IFlyVoiceWakeuperDelegate.h │ │ │ │ └── iflyMSC │ │ │ ├── pcmPlayerCode/ │ │ │ │ ├── PcmPlayer.h │ │ │ │ ├── PcmPlayer.m │ │ │ │ └── PcmPlayerDelegate.h │ │ │ ├── speaker.json │ │ │ └── speakers.plist │ │ └── View/ │ │ ├── AKPickerView.h │ │ ├── AKPickerView.m │ │ └── ZSSpeechView.swift │ ├── Vendor/ │ │ ├── CTDisplayText/ │ │ │ └── Source/ │ │ │ ├── CTDisplayText.h │ │ │ ├── CTFrameParser.h │ │ │ ├── CTFrameParser.m │ │ │ ├── CTFrameParserConfig.h │ │ │ ├── CTFrameParserConfig.m │ │ │ ├── CoreTextData.h │ │ │ ├── CoreTextData.m │ │ │ ├── CoreTextImageData.h │ │ │ ├── CoreTextImageData.m │ │ │ ├── CoreTextLinkData.h │ │ │ ├── CoreTextLinkData.m │ │ │ ├── CoreTextUtils.h │ │ │ ├── CoreTextUtils.m │ │ │ ├── RegexKitLite.h │ │ │ ├── RegexKitLite.m │ │ │ └── Views/ │ │ │ ├── CTDisplayView.h │ │ │ ├── CTDisplayView.m │ │ │ ├── MagnifiterView.h │ │ │ ├── MagnifiterView.m │ │ │ ├── UIView+frameAdjust.h │ │ │ └── UIView+frameAdjust.m │ │ ├── CocoaHTTPServer/ │ │ │ ├── Core/ │ │ │ │ ├── Categories/ │ │ │ │ │ ├── DDData.h │ │ │ │ │ ├── DDData.m │ │ │ │ │ ├── DDNumber.h │ │ │ │ │ ├── DDNumber.m │ │ │ │ │ ├── DDRange.h │ │ │ │ │ └── DDRange.m │ │ │ │ ├── HTTPAuthenticationRequest.h │ │ │ │ ├── HTTPAuthenticationRequest.m │ │ │ │ ├── HTTPConnection.h │ │ │ │ ├── HTTPConnection.m │ │ │ │ ├── HTTPLogging.h │ │ │ │ ├── HTTPMessage.h │ │ │ │ ├── HTTPMessage.m │ │ │ │ ├── HTTPResponse.h │ │ │ │ ├── HTTPServer.h │ │ │ │ ├── HTTPServer.m │ │ │ │ ├── Mime/ │ │ │ │ │ ├── MultipartFormDataParser.h │ │ │ │ │ ├── MultipartFormDataParser.m │ │ │ │ │ ├── MultipartMessageHeader.h │ │ │ │ │ ├── MultipartMessageHeader.m │ │ │ │ │ ├── MultipartMessageHeaderField.h │ │ │ │ │ └── MultipartMessageHeaderField.m │ │ │ │ ├── Responses/ │ │ │ │ │ ├── HTTPAsyncFileResponse.h │ │ │ │ │ ├── HTTPAsyncFileResponse.m │ │ │ │ │ ├── HTTPDataResponse.h │ │ │ │ │ ├── HTTPDataResponse.m │ │ │ │ │ ├── HTTPDynamicFileResponse.h │ │ │ │ │ ├── HTTPDynamicFileResponse.m │ │ │ │ │ ├── HTTPFileResponse.h │ │ │ │ │ ├── HTTPFileResponse.m │ │ │ │ │ ├── HTTPRedirectResponse.h │ │ │ │ │ └── HTTPRedirectResponse.m │ │ │ │ ├── WebSocket.h │ │ │ │ └── WebSocket.m │ │ │ ├── Extensions/ │ │ │ │ └── WebDAV/ │ │ │ │ ├── DAVConnection.h │ │ │ │ ├── DAVConnection.m │ │ │ │ ├── DAVResponse.h │ │ │ │ ├── DAVResponse.m │ │ │ │ ├── DELETEResponse.h │ │ │ │ ├── DELETEResponse.m │ │ │ │ ├── PUTResponse.h │ │ │ │ └── PUTResponse.m │ │ │ ├── LICENSE.txt │ │ │ └── README.markdown │ │ ├── M80AttributedLabel/ │ │ │ ├── M80AttributedLabel.h │ │ │ ├── M80AttributedLabel.m │ │ │ ├── M80AttributedLabelAttachment.h │ │ │ ├── M80AttributedLabelAttachment.m │ │ │ ├── M80AttributedLabelDefines.h │ │ │ ├── M80AttributedLabelURL.h │ │ │ ├── M80AttributedLabelURL.m │ │ │ ├── NSMutableAttributedString+M80.h │ │ │ └── NSMutableAttributedString+M80.m │ │ ├── OCGumbo/ │ │ │ ├── OCGumbo+Query.h │ │ │ ├── OCGumbo+Query.m │ │ │ ├── OCGumbo.h │ │ │ ├── OCGumbo.m │ │ │ └── gumbo/ │ │ │ ├── attribute.c │ │ │ ├── attribute.h │ │ │ ├── char_ref.c │ │ │ ├── char_ref.h │ │ │ ├── char_ref.rl │ │ │ ├── error.c │ │ │ ├── error.h │ │ │ ├── gumbo.h │ │ │ ├── insertion_mode.h │ │ │ ├── parser.c │ │ │ ├── parser.h │ │ │ ├── string_buffer.c │ │ │ ├── string_buffer.h │ │ │ ├── string_piece.c │ │ │ ├── string_piece.h │ │ │ ├── tag.c │ │ │ ├── tag.in │ │ │ ├── tag_enum.h │ │ │ ├── tag_gperf.h │ │ │ ├── tag_sizes.h │ │ │ ├── tag_strings.h │ │ │ ├── token_type.h │ │ │ ├── tokenizer.c │ │ │ ├── tokenizer.h │ │ │ ├── tokenizer_states.h │ │ │ ├── utf8.c │ │ │ ├── utf8.h │ │ │ ├── util.c │ │ │ ├── util.h │ │ │ ├── vector.c │ │ │ └── vector.h │ │ ├── PullToRefresh/ │ │ │ └── QSPullToRefresh/ │ │ │ ├── QSPullToRefresh/ │ │ │ │ ├── DefaultRefreshView.swift │ │ │ │ ├── DefaultViewAnimator.swift │ │ │ │ ├── Info.plist │ │ │ │ ├── PullToRefresh.swift │ │ │ │ ├── QSPullToRefresh.h │ │ │ │ ├── RefreshViewAnimator.swift │ │ │ │ ├── State.swift │ │ │ │ └── UIScrollView+PullToRefresh.swift │ │ │ ├── QSPullToRefresh.xcodeproj/ │ │ │ │ ├── project.pbxproj │ │ │ │ └── project.xcworkspace/ │ │ │ │ └── contents.xcworkspacedata │ │ │ └── QSPullToRefreshTests/ │ │ │ ├── Info.plist │ │ │ └── QSPullToRefreshTests.swift │ │ ├── ThirdLoginSDK/ │ │ │ └── TencentOpenAPI.framework/ │ │ │ ├── Headers/ │ │ │ │ ├── QQApiInterface.h │ │ │ │ ├── QQApiInterfaceObject.h │ │ │ │ ├── TencentOAuth.h │ │ │ │ └── sdkdef.h │ │ │ └── TencentOpenAPI │ │ ├── XimalayaSDK_iOS_5.5.1/ │ │ │ ├── include/ │ │ │ │ ├── Authorization/ │ │ │ │ │ └── XMLYAuthorize.h │ │ │ │ ├── Downloader/ │ │ │ │ │ └── XMSDKDownloadManager.h │ │ │ │ ├── FMDB/ │ │ │ │ │ ├── FMDatabase.h │ │ │ │ │ ├── FMDatabase.m │ │ │ │ │ ├── FMDatabaseAdditions.h │ │ │ │ │ ├── FMDatabaseAdditions.m │ │ │ │ │ ├── FMDatabasePool.h │ │ │ │ │ ├── FMDatabasePool.m │ │ │ │ │ ├── FMDatabaseQueue.h │ │ │ │ │ ├── FMDatabaseQueue.m │ │ │ │ │ ├── FMResultSet.h │ │ │ │ │ └── FMResultSet.m │ │ │ │ ├── MBProgressHUD/ │ │ │ │ │ ├── MBProgressHUD.h │ │ │ │ │ └── MBProgressHUD.m │ │ │ │ ├── Model/ │ │ │ │ │ ├── XMAlbum.h │ │ │ │ │ ├── XMAlbumColumn.h │ │ │ │ │ ├── XMAlbumColumnItem.h │ │ │ │ │ ├── XMAlbumGuessLike.h │ │ │ │ │ ├── XMAnnouncer.h │ │ │ │ │ ├── XMAnnouncerCategory.h │ │ │ │ │ ├── XMAttribute.h │ │ │ │ │ ├── XMBanner.h │ │ │ │ │ ├── XMCacheTrack.h │ │ │ │ │ ├── XMCategory.h │ │ │ │ │ ├── XMCategoryHumanRecommend.h │ │ │ │ │ ├── XMColdbootDetail.h │ │ │ │ │ ├── XMColdbootTag.h │ │ │ │ │ ├── XMColumn.h │ │ │ │ │ ├── XMColumnDetail.h │ │ │ │ │ ├── XMColumnEditor.h │ │ │ │ │ ├── XMColumnList.h │ │ │ │ │ ├── XMDimension.h │ │ │ │ │ ├── XMErrorModel.h │ │ │ │ │ ├── XMHotTrack.h │ │ │ │ │ ├── XMHotword.h │ │ │ │ │ ├── XMIndexRankItem.h │ │ │ │ │ ├── XMLastUptrack.h │ │ │ │ │ ├── XMLiveAnnouncer.h │ │ │ │ │ ├── XMLiveCity.h │ │ │ │ │ ├── XMMetadata.h │ │ │ │ │ ├── XMProvince.h │ │ │ │ │ ├── XMRadio.h │ │ │ │ │ ├── XMRadioSchedule.h │ │ │ │ │ ├── XMRankSectionList.h │ │ │ │ │ ├── XMRelatedProgram.h │ │ │ │ │ ├── XMSubordinatedAlbum.h │ │ │ │ │ ├── XMTag.h │ │ │ │ │ ├── XMTrack.h │ │ │ │ │ ├── XMTrackColumn.h │ │ │ │ │ ├── XMTrackColumnItem.h │ │ │ │ │ └── XMTrackDownloadStatus.h │ │ │ │ ├── Player/ │ │ │ │ │ ├── XMADAudioPlayer.h │ │ │ │ │ ├── XMSDKPlayer.h │ │ │ │ │ └── XMSDKPlayerDataCollector.h │ │ │ │ ├── Request/ │ │ │ │ │ └── XMReqMgr.h │ │ │ │ └── Utility/ │ │ │ │ ├── XMSDK.h │ │ │ │ ├── XMSDKInfo.h │ │ │ │ └── XMSingleTone.h │ │ │ └── libXMOpenPlatform.a │ │ ├── YTK/ │ │ │ ├── YTKKeyValueStore.h │ │ │ └── YTKKeyValueStore.m │ │ └── uchardet/ │ │ ├── libuchardet-ios.a │ │ └── uchardet.h │ ├── ViewController.swift │ └── zhuishushenqi-Bridge-Header.h ├── zhuishushenqi.xcodeproj/ │ ├── project.pbxproj │ ├── project.xcworkspace/ │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata/ │ │ └── zhuishushenqi.xcscmblueprint │ └── xcshareddata/ │ └── xcschemes/ │ └── zhuishushenqi.xcscheme ├── zhuishushenqi.xcworkspace/ │ ├── contents.xcworkspacedata │ └── xcshareddata/ │ └── IDEWorkspaceChecks.plist ├── zhuishushenqiTests/ │ ├── Info.plist │ └── zhuishushenqiTests.swift └── zhuishushenqiUITests/ ├── Info.plist └── zhuishushenqiUITests.swift ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ # Xcode # # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore ## Build generated build/ DerivedData/ ## Various settings *.pbxuser !default.pbxuser *.mode1v3 !default.mode1v3 *.mode2v3 !default.mode2v3 *.perspectivev3 !default.perspectivev3 xcuserdata/ ## Other *.moved-aside *.xcuserstate ## Obj-C/Swift specific *.hmap *.ipa *.dSYM.zip *.dSYM ## Playgrounds timeline.xctimeline playground.xcworkspace # Swift Package Manager # # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. # Packages/ .build/ # CocoaPods # # We recommend against adding the Pods directory to your .gitignore. However # you should judge for yourself, the pros and cons are mentioned at: # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control # Pods/ # Carthage # # Add this line if you want to avoid checking in source code from Carthage dependencies. Carthage/Checkouts Carthage/Build # fastlane # # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the # screenshots whenever they are needed. # For more information about the recommended setup visit: # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md fastlane/report.xml fastlane/Preview.html fastlane/screenshots fastlane/test_output #ds .DS_Store ================================================ FILE: .idea/codeStyles/Project.xml ================================================ ================================================ FILE: .idea/modules.xml ================================================ ================================================ FILE: .idea/vcs.xml ================================================ ================================================ FILE: .idea/workspace.xml ================================================ project 1496934697985 ================================================ FILE: .idea/xcode.xml ================================================ ================================================ FILE: .idea/zhuishushenqi.iml ================================================ ================================================ FILE: Podfile ================================================ source 'https://github.com/CocoaPods/Specs.git' source 'https://github.com/sinaweibosdk/weibo_ios_sdk.git' # 对于Swift应用来说下面两句是必须的 platform :ios, '10.0' use_frameworks! # Swift静态库方式 #use_modular_headers! inhibit_all_warnings! branch = ENV['sha'] # target的名字一般与你的项目名字相同 target 'zhuishushenqi' do project './zhuishushenqi.xcodeproj' pod 'YYText' pod 'YYModel' pod 'YYImage' pod 'MBProgressHUD' pod 'MJRefresh' pod 'CocoaAsyncSocket' pod 'CocoaLumberjack', '3.4.2' pod 'YYCategories' pod 'FMDB' pod 'WechatOpenSDK', '1.8.3' pod "Weibo_SDK", :git => "https://github.com/sinaweibosdk/weibo_ios_sdk.git" pod 'FLEX', :configurations => ['Debug'] pod 'MLeaksFinder' pod 'FBRetainCycleDetector' # DoraemonKit不支持模拟器 #pod 'DoraemonKit/Core', '~> 3.0.4', :configurations => ['Debug'] #必选 #pod 'DoraemonKit/WithGPS', '~> 3.0.4', :configurations => ['Debug'] #可选 #pod 'DoraemonKit/WithLoad', '~> 3.0.4', :configurations => ['Debug'] #可选 #pod 'DoraemonKit/WithLogger', '~> 3.0.4', :configurations => ['Debug'] #可选 #pod 'DoraemonKit/WithDatabase', '~> 3.0.4', :configurations => ['Debug'] #可选 #pod 'DoraemonKit/WithMLeaksFinder', '~> 3.0.4', :configurations => ['Debug'] #可选 #pod 'DoraemonKit/WithWeex', '~> 3.0.4', :configurations => ['Debug'] #可选 # swift libraries pod 'Alamofire' pod 'Cache' pod 'HandyJSON' #pod 'Kingfisher' pod 'PKHUD' pod 'SnapKit', '5.0.0' pod 'SQLite.swift' #pod 'Then' pod 'Zip' pod 'RxAlamofire' pod 'RxCocoa' pod 'UICircularProgressRing' pod 'RCBacktrace', '~> 0.1.6' pod 'Google-Mobile-Ads-SDK','~> 7.69' pod 'Ads-CN' pod 'YungCache', :git => 'https://github.com/Noah37/YungCache.git' pod 'YungNetworkTool', :git => 'https://github.com/Noah37/YungNetworkTool.git' # local pods pod 'ZSAPI', :path => "zhuishushenqi/NewVersion/ZSAPI" pod 'ZSAppConfig', :path => "zhuishushenqi/NewVersion/ZSAppConfig" pod 'ZSExtension', :path => "zhuishushenqi/NewVersion/ZSExtension" end post_install do |installer| ## Fix for XCode 12.5 find_and_replace("Pods/FBRetainCycleDetector/FBRetainCycleDetector/Layout/Classes/FBClassStrongLayout.mm", "layoutCache[currentClass] = ivars;", "layoutCache[(id)currentClass] = ivars;") # installer.pods_project.targets.each do |target| # # 我們也可以懶惰不用 if,讓所有 pod 的版本都設為一樣的 # target.build_configurations.each do |config| # config.build_settings['SWIFT_VERSION'] = '5.0' # end # if ['RxSwift', 'RxSwiftExt', 'RxCocoa', 'RxDataSources', 'ProtocolBuffers-Swift'].include? target.name # end # end end def find_and_replace(dir, findstr, replacestr) Dir[dir].each do |name| text = File.read(name) replace = text.gsub(findstr,replacestr) if text != replace puts "Fix: " + name File.open(name, "w") { |file| file.puts replace } STDOUT.flush end end Dir[dir + '*/'].each(&method(:find_and_replace)) end ================================================ FILE: README.md ================================================ # zhuishushenqi ![Platform](https://img.shields.io/badge/platforms-iOS%208.0+%20%7C%20macOS%2010.10+%20%7C%20tvOS%209.0+%20%7C%20watchOS%202.0+-333333.svg) [![Language](https://img.shields.io/badge/language-Swift-brightgreen.svg?style=flat)](https://developer.apple.com/Objective-C) [![License](http://img.shields.io/badge/license-MIT-lightgrey.svg?style=flat)](http://mit-license.org) **采用Swift语言,仿追书神器做的,主要是练习阅读器的一些技术,包括仿真阅读等。不断更新中......** #### changelog - **20170607:增加书架删除,缓存,修复一些缺陷** - **20170612: 增加开屏广告,增加首次安装新特性,推荐书籍等** - **20170808:充分利用Swift语言特性,整合API,引入CocoaPods管理三方库** - **20171219: 适配iPhone X** - **20180110: 修复了iOS 11 上阅读器文字不显示问题** - **20180403: 阅读器内存优化,内存保持稳定** - **20180606: Swift语言更新至4.0** - **20180726: 阅读器代码优化** - **20180803: 增加本地书籍阅读功能** - **20180819: 追书社区新增大量功能,侧滑菜单新增大量功能** - **20180828: 评论区增加图文混排功能,正确展示评论中的图片及链接** - **20180919: 适配Swift4.2及Xcode10** - **20181002: 添加手机登录及三方登录功能** - **20181025: 添加登录后书架信息同步** - **20181102: 添加自动签到功能,进入APP即可自动签到** - **20190102: 修复书架刷新卡顿&阅读过的书籍置顶(branch dev_db)** - **20180321:修复多个缺陷,分类页面优化** - **20190409:修复书架删除书籍时误删整个书架的问题** - **20190622: Swift 5.0支持** - **20200103: 搜索页增加热门推荐与搜索历史** - **20200106: 来源功能手动添加支持&删除carthage支持** - **20200221:切换翻页方式功能添加(支持pageCurl,左右平移,无动画效果)** - **20200517:书架内容展示添加最近更新章节** - **20201219:添加规则验证页面,添加新的书源前可逐条验证规则后添加** - **20210613:图片加载方式变更,大图片不再缓存,优化内存占用** - **20211223: 添加来源** ---- **仅供学习交流,请勿用于商业用途** ## Requirements - iOS 9.0+ / macOS 10.14+ / tvOS 9.0+ / watchOS 2.0+ - Xcode 10.2+ - Swift 5.0+ Main development of zhuishushenqi olny support Swift 5.0+. ## Install 请执行以下两步操作,确保所有的依赖库安装完成后使用. **CocoaPods:** `$ pod install` **Carthage:** `$ carthage update --platform iOS` ### Here you can see blow. ======= ## regular [添加来源](addSource.md) ### 效果图如下: ## Contact Follow and contact me on mail [2252055382@qq.com](https://mail.qq.com/). If you find an issue, just [open a ticket](https://github.com/NoryCao/zhuishushenqi/issues/new). Pull requests are warmly welcome as well. ## License zhuishushenqi is released under the MIT license. See LICENSE for details. ================================================ FILE: Script.sh ================================================ #!/bin/sh # Script.sh # zhuishushenqi # # Created by yung on 2017/8/10. # Copyright © 2017年 QS. All rights reserved. # Localizable.strings文件路径 localizableFile="en.lproj/Localizable.strings" # 生成的swift文件路径(根据个人习惯修改) localizedFile="zhuishushenqi/Extension/LocalizedUtils.swift" # 将localizable.strings中的文本转为swift格式的常量,存入一个临时文件 cat ${localizableFile} sed "s/^\"/ static var localized_/g" "${localizableFile}" | sed "s/\" = \"/: String { return \"/g" | sed "s/;$/.localized }/g" > "${localizedFile}.tmp" # 先将localized作为计算属性输出到目标文件 echo -e "import Foundation\n\nextension String {\n var localized: String { return NSLocalizedString(self, comment: self) }" > "${localizedFile}" # 再将临时文件中的常量增量输出到目标文件 cat "${localizedFile}.tmp" >> "${localizedFile}" # 最后增量输出一个"}"到目标文件,完成输出 echo -e "\n}" >> "${localizedFile}" # 删除临时文件 rm "${localizedFile}.tmp" ================================================ FILE: addSource.md ================================================ ### 添加来源 如果默认的一些书籍来源搜索不到小说,该怎么办? 这时候,你需要自定义小说来源网站 以 [笔趣阁](http://www.boquku.com)为例 #### 1. 打开 [笔趣阁](http://www.boquku.com)网站,搜索【夜的命名术】 ![](images/boquge_search_result@2x.png) #### 2. 在网页点击右键,选择检查网页 ![](images/boquge_search_html@2x.png) #### 3. 找到唯一能定位搜索结果的标签 ````
```` #### 4. 根据xpath取值获取,获取所有搜索结果 例如 - books: .panel-body ul li - bookName: @.col-xs-3 a@0@ - bookAuthor: @.col-xs-2@0@ - bookUrl: @.col-xs-3 a@0@abs:href - bookLastChapter: @.col-xs-4 a@0@ **@**为分隔符,分割标签与序号 以 **bookName** 为例: bookName为定位某一个具体的标签 第一个【@】后面的**.col-xs-3 a**指定对应的标签的定位,一般指定的标签具有唯一性 第二个【@】后面的数字为序号,一般为0,特殊情况也可取具体数字 第三个【@】后面的一般用于获取链接的具体值,固定abs:href或者abs:src,根据具体的标签来看 > .col-xs-3 表示class为col-xs-3的标签,如果没有class,而是只有id,则需要遵循css选择器,改为 `#col-xs-3` #### 5. 获取详情页内容 这一步获取到小说的以下内容 - detailBookIcon: @.col-xs-2 img@0@abs:src - chapters: .list-group-item li a - detailChaptersUrl: @.list-group li a@12@abs:href - detailBookDesc: @p#shot@0@ > 【detailChaptersUrl 这里有一个更简洁的写法, @.list-group-item tac a@0@abs:href, 为啥不用他呢,因为list-group-item后面的tac并不是一个标签层级,而我们用空格区分标签的层级,所以识别会失败,后面考虑将空格分割法改为其他的特殊符号】 > 【chapters比较特殊,假如从小说页面找不到所有的章节,而需要点击链接进入下一级页面的话,就需要detailChaptersUrl的值】 > 【获取列表的时候,直接写标签路径就可以了,如chapters这种】 > 【detailChaptersUrl 章节要获取到a标签,方便后面取值】 #### 6. 获取具体章节名及章节链接 - chapterName: @@@ - chapterUrl: @@@abs:href #### 7. 获取章节内容 - content: @div#txtContent@0@ - contentReplace: - contentRemove: #### 8. 获取网页编码,搜索链接 - searchEncoding: .gbk - searchUrl: http://www.boquge.com/search.htm?keyword=%@ - host: https://www.boquge.com/ - name: boquge #### 9. 验证规则正确性 在 我的->设置->书籍规则验证页面进行验证 附上[http://www.yetianlian.com/](http://www.yetianlian.com/) 网站实例 - searchEncoding: .utf8 - searchUrl: http://www.yetianlian.com/s.php?ie=utf-8&q=%@ - books: .bookbox - bookName: @.bookname a@0@ - bookAuthor: @.author@0@ - bookUrl: @.bookname a@0@abs:href - bookLastChapter: @.update a@0@ - bookIcon: @.bookimg a@0@abs:href - bookUpdateTime: - chapters: .listmain dl dd a - detailBookDesc: @.intro@0@ - chapterName: @@@ - chapterUrl: @@@abs:href - content: @div#content@0@ - host: http://www.yetianlian.com/ - name: 何以笙箫默 ================================================ FILE: en.lproj/Localizable.strings ================================================ /* Localizable.strings zhuishushenqi Created by yung on 2017/8/10. Copyright © 2017年 QS. All rights reserved. */ "login" = "Login"; "logout" = "Logout"; ================================================ FILE: images/aptv.plist ================================================ items assets kind software-package url http://192.168.0.100:8080/hello/aptv.ipa kind full-size-image needs-shine url kind display-image needs-shine url http://mywebserver.com/显示的图标.png metadata bundle-identifier com.kimen.aptv bundle-version 1.0.0 kind software title 游戏名字 ================================================ FILE: zh-Hans.lproj/Localizable.strings ================================================ /* Localizable.strings zhuishushenqi Created by yung on 2017/8/10. Copyright © 2017年 QS. All rights reserved. */ "login" = "登录"; "logout" = "退出"; ================================================ FILE: zh-Hant.lproj/Localizable.strings ================================================ /* Localizable.strings zhuishushenqi Created by yung on 2017/8/10. Copyright © 2017年 QS. All rights reserved. */ "login" = "log in"; "logout" = "log out"; ================================================ FILE: zhuishushenqi/App/AppDelegate.swift ================================================ // // AppDelegate.swift // zhuishushenqi // // Created by Nory Cao on 16/9/16. // Copyright © 2016年 QS. All rights reserved. // import UIKit import RxSwift import GoogleMobileAds #if DEBUG //import DoraemonKit import FLEX import ZSAppConfig #endif let rightScaleX:CGFloat = 0.2 let rootVCKey = "rootVCKey" let GADUnitID = "ca-app-pub-6271484308025079/5733340734" @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? var lastTabVC:UIViewController? var appOpenAd:GADAppOpenAd? var loadTime:Date? func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Cause API of UI called on a background thread issue. // GADMobileAds.sharedInstance().start(completionHandler: nil) // AppOpenAdManager.shared.loadAd() BUAdManager.shared.loadAd() #if DEBUG // DoraemonManager.shareInstance().install() FLEXManager.shared().showExplorer() #endif let notificationCenter = NotificationCenter.default notificationCenter.addObserver(self, selector: #selector(type(of: self).removeAllObjects), name: UIApplication.didReceiveMemoryWarningNotification, object: nil) // 新版本特性 let firstRun = USER_DEFAULTS.bool(forKey: UserDefaults.firstRunKey) if !firstRun { USER_DEFAULTS.set(true, forKey: UserDefaults.firstRunKey) USER_DEFAULTS.synchronize() ZSIntroducePage.shared.show { // 根据性别推荐书籍(第一次安装才会出现) 由home页面自己发起 NotificationCenter.default.post(Notification(name: Notification.Name(rawValue:SHOW_RECOMMEND))) } } if #available(iOS 9.0, *) { } else { UIApplication.shared.setStatusBarHidden(false, with: .fade) UIApplication.shared.statusBarStyle = .lightContent } #if DEBUG let fpsLabel = V2FPSLabel(frame: CGRect(x:15, y:ScreenHeight-100, width:55,height: 20)); self.window?.addSubview(fpsLabel); #else #endif configureDataBase() /** 设置 UINavigationNar 外观 */ UINavigationBar.appearance().barTintColor = UIColor.white UINavigationBar.appearance().tintColor = UIColor ( red: 0.7235, green: 0.0, blue: 0.1146, alpha: 1.0 ) let navbarTitleTextAttributes = [NSAttributedString.Key.foregroundColor:UIColor ( red: 0.7235, green: 0.0, blue: 0.1146, alpha: 1.0 )] UINavigationBar.appearance().titleTextAttributes = navbarTitleTextAttributes UITabBar.appearance().tintColor = UIColor ( red: 0.7235, green: 0.0, blue: 0.1146, alpha: 1.0 ) // let APP_KEY = "e31646fa4555ea3472d4114921ee192e" // let APP_SECRET = "b961a55b60fbd7129e49a986e44352fb" // XMSDKPlayer.shared()?.setAutoNexTrack(true) // XMReqMgr.sharedInstance()?.registerXMReqInfo(withKey: APP_KEY, appSecret: APP_SECRET) IFlySetting.setLogFile(LOG_LEVEL.LVL_ALL) IFlySetting.showLogcat(true) let paths = NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true).first ?? "" IFlySetting.setLogFilePath(paths) // let appid = "5ba0b197" // let xfyj = "5445f87d" // let zssq = "566551f4" // let xfyj2 = "591a4d99" // let initString = "appid=\(zssq)" // IFlySpeechUtility.createUtility(initString) // 提前解析 DispatchQueue.global().async { TTSConfig.share.getSpeakers() } ZSBookManager.shared let path = ZSShelfConstant.inboxPath ZSShelfManager.share.scanPath(path: path) // WeiboSDK.enableDebugMode(false) // WeiboSDK.registerApp(ZSThirdLogin.WBAppID) // database /*测试代码 let database = ZSDatabase() database.createBookshelf() if let book = ZSBookManager.shared.books.allValues().first as? BookDetail { database.insertBookshelf(book: book) } let books = database.queryBookshelf() QSLog("books:\(books)") let testModel = ZSDBTestModel() let subModel = ZSDBTestSubModel() subModel.key = "IU6rIl2d2GWnEio" testModel.id = "Yg5fsKPOvpii0qAH2lpmJhjFmRhfe4dshhjY5Oim2Y" testModel.num = 1 testModel.subModel = subModel ZSDBManager.share()?.getPropertys(testModel) */ decryptedStr() return true } func configureDataBase(){ let store = YTKKeyValueStore(dbWithName: dbName) if store?.isTableExists(searchHistory) == false { store?.createTable(withName: searchHistory) } } @objc func removeAllObjects() { let alert = UIAlertController(title: "提示", message: "内存警告", preferredStyle: .alert) let confirmAction = UIAlertAction(title: "确定", style: .default) { (action) in } alert.addAction(confirmAction) if let vc = self.window?.rootViewController { vc.present(alert, animated: true, completion: nil) } } func decryptedStr() { // zhuishu的解密方法 let textFile = Bundle.main.path(forResource: "EncrtptorText", ofType: nil) ?? "" if let cpContent = try? String(contentsOfFile: textFile) { let str = FBEncryptorAESUtils.getDecryptedStr(withKey: "inTv0kKl4pI1BMk2munvAg==", cipherText: cpContent) print("FBEncryptorAESUtils:\(str)") } } func showSplash() { // 后台超过3分钟才展示开屏广告 let splashTime = UserDefaults.standard.double(forKey: UserDefaults.splashTimeKey) let currentTime = Date().timeIntervalSince1970 if currentTime - splashTime > 180 { UserDefaults.standard.set(currentTime, forKey: UserDefaults.splashTimeKey) UserDefaults.standard.synchronize() let splashViewController = UIStoryboard.main.instantiateInitialViewController() let keyWindow = UIApplication.shared.windows.first(where: { $0.isKeyWindow }) keyWindow?.rootViewController = splashViewController } } func application(_ application: UIApplication, open url: URL, sourceApplication: String?, annotation: Any) -> Bool { let path = ZSShelfConstant.inboxPath ZSShelfManager.share.scanPath(path: path) return true } func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool { if url.isFileURL { NotificationCenter.default.post(name: NSNotification.Name.LocalShelfChanged, object: nil) return true } QQApiInterface.handleOpen(url, delegate: ZSThirdLogin.share) if TencentOAuth.canHandleOpen(url) { return TencentOAuth.handleOpen(url) } var result = WXApi.handleOpen(url, delegate: WXApiRequestHandler.share) result = WeiboSDK.handleOpen(url, delegate: ZSThirdLogin.share) return result } func application(_ application: UIApplication, handleOpen url: URL) -> Bool { QQApiInterface.handleOpen(url, delegate: ZSThirdLogin.share) if TencentOAuth.canHandleOpen(url) { return TencentOAuth.handleOpen(url) } var result = WXApi.handleOpen(url, delegate: WXApiRequestHandler.share) result = WeiboSDK.handleOpen(url, delegate: ZSThirdLogin.share) return result } func applicationWillResignActive(_ application: UIApplication) { // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. } func applicationDidEnterBackground(_ application: UIApplication) { // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. } func applicationWillEnterForeground(_ application: UIApplication) { // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. showSplash() let pasteboard = UIPasteboard.general let items = pasteboard.items for item in items { if let vcnList = item["IFlySpeechPlusVcnList"] as? Data { if let files = NSKeyedUnarchiver.unarchiveObject(with: vcnList) as? [[String:Any]] { for file in files { if let vcn = file["vcn"] as? [String:Any] { if let data = file["data"] as? Data { let path = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.userDomainMask, true)[0] let filePath = "\(path)/\(vcn["name"] ?? "").jet" let url = URL(fileURLWithPath: filePath) _ = try? data.write(to: url) } } } } } else if ZSThirdLoginStorage.share.canHandle(pasteData: item) { ZSThirdLoginStorage.share.handle(pasteData: item as! [String : Data]) } } } func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask { return .all } func applicationDidBecomeActive(_ application: UIApplication) { // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. } func applicationWillTerminate(_ application: UIApplication) { // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. } } ================================================ FILE: zhuishushenqi/App/AppOpenAdManager.swift ================================================ // // Copyright 2021 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // import GoogleMobileAds protocol AppOpenAdManagerDelegate: AnyObject { /// Method to be invoked when an app open ad is complete (i.e. dismissed or fails to show). func appOpenAdManagerAdDidComplete(_ appOpenAdManager: AppOpenAdManager) } class AppOpenAdManager: NSObject { /// Ad references in the app open beta will time out after four hours, /// but this time limit may change in future beta versions. For details, see: /// https://support.google.com/admob/answer/9341964?hl=en let timeoutInterval: TimeInterval = 4 * 3_600 /// The app open ad. var appOpenAd: GADAppOpenAd? /// Maintains a reference to the delegate. weak var appOpenAdManagerDelegate: AppOpenAdManagerDelegate? /// Keeps track of if an app open ad is loading. var isLoadingAd = false /// Keeps track of if an app open ad is showing. var isShowingAd = false /// Keeps track of the time when an app open ad was loaded to discard expired ad. var loadTime: Date? static let shared = AppOpenAdManager() private func wasLoadTimeLessThanNHoursAgo(timeoutInterval: TimeInterval) -> Bool { // Check if ad was loaded more than n hours ago. if let loadTime = loadTime { return Date().timeIntervalSince(loadTime) < timeoutInterval } return false } private func isAdAvailable() -> Bool { // Check if ad exists and can be shown. return appOpenAd != nil && wasLoadTimeLessThanNHoursAgo(timeoutInterval: timeoutInterval) } private func appOpenAdManagerAdDidComplete() { // The app open ad is considered to be complete when it dismisses or fails to show, // call the delegate's appOpenAdManagerAdDidComplete method if the delegate is not nil. appOpenAdManagerDelegate?.appOpenAdManagerAdDidComplete(self) } func loadAd() { // Do not load ad if there is an unused ad or one is already loading. if isLoadingAd || isAdAvailable() { return } isLoadingAd = true print("Start loading app open ad.") GADAppOpenAd.load( withAdUnitID: "ca-app-pub-3940256099942544/5662855259", request: GADRequest(), orientation: UIInterfaceOrientation.portrait ) { ad, error in self.isLoadingAd = false if let error = error { self.appOpenAd = nil self.loadTime = nil print("App open ad failed to load with error: \(error.localizedDescription).") return } self.appOpenAd = ad self.appOpenAd?.fullScreenContentDelegate = self self.loadTime = Date() print("App open ad loaded successfully.") } } func showAdIfAvailable(viewController: UIViewController) { return // If the app open ad is already showing, do not show the ad again. if isShowingAd { print("App open ad is already showing.") return } // If the app open ad is not available yet but it is supposed to show, // it is considered to be complete in this example. Call the appOpenAdManagerAdDidComplete // method and load a new ad. if !isAdAvailable() { print("App open ad is not ready yet.") appOpenAdManagerAdDidComplete() loadAd() return } if let ad = appOpenAd { print("App open ad will be displayed.") isShowingAd = true ad.present(fromRootViewController: viewController) } } } extension AppOpenAdManager: GADFullScreenContentDelegate { func adDidPresentFullScreenContent(_ ad: GADFullScreenPresentingAd) { print("App open ad is presented.") } func adDidDismissFullScreenContent(_ ad: GADFullScreenPresentingAd) { appOpenAd = nil isShowingAd = false print("App open ad was dismissed.") appOpenAdManagerAdDidComplete() loadAd() } func ad( _ ad: GADFullScreenPresentingAd, didFailToPresentFullScreenContentWithError error: Error ) { appOpenAd = nil isShowingAd = false print("App open ad failed to present with error: \(error.localizedDescription).") appOpenAdManagerAdDidComplete() loadAd() } } ================================================ FILE: zhuishushenqi/App/AppStyle.swift ================================================ // // AppStyle.swift // zhuishushenqi // // Created by yung on 2017/8/8. // Copyright © 2017年 QS. All rights reserved. // import Foundation let nightKey = "light.key" let fontSizeKey = "fontSize.key" let animationStyleKey = "animationStyle.key" struct AppStyle { static var shared = AppStyle() var readFontSize:Int { set { UserDefaults.standard.set(newValue, forKey: fontSizeKey) } get { let size = UserDefaults.standard.integer(forKey: fontSizeKey) if size == 0 { return 20; } return size } } var reader:Reader = AppStyle.getReader() { didSet{ AppStyle.setReader(reader) } } var theme:AppTheme = UserDefaults.standard.bool(forKey: nightKey) ? .night : .day { didSet{ UserDefaults.standard.set(theme == .night, forKey: nightKey) } } var animationStyle:ZSReaderAnimationStyle = ZSReaderAnimationStyle(rawValue: UserDefaults.standard.integer(forKey: animationStyleKey)) ?? .none { didSet { UserDefaults.standard.set(animationStyle.rawValue, forKey: animationStyleKey) UserDefaults.standard.synchronize() } } private init(){} static func getReader()->Reader{ let value = UserDefaults.standard.integer(forKey: readerKey) switch value { case 1: return .yellow case 2: return .green default: return .white } } static func setReader(_ reader:Reader){ var value = 0 switch reader { case .yellow: value = 1 case .green: value = 2 default: value = 0 } UserDefaults.standard.set(value, forKey: readerKey) } } ================================================ FILE: zhuishushenqi/App/BUAdManager.swift ================================================ // // BUAdManager.swift // zhuishushenqi // // Created by daye on 2021/12/27. // Copyright © 2021 QS. All rights reserved. // import Foundation import BUAdSDK import UIKit typealias BUAdHandler = ()->Void class BUAdManager:NSObject { private static let appKey = "5256970" private static let secretKey = "" static let shared = BUAdManager() private var splashAdView:BUSplashAdView? private var startTime:CFTimeInterval = 0 private var loadAdSuccess:Bool = false var completion:BUAdHandler? var success:BUAdHandler? func loadAd() { let territory = UserDefaults.standard.integer(forKey: "territory") let isNoCN = territory > 0 && territory != BUAdSDKTerritory.CN.rawValue BUAdSDKManager.setAppID(BUAdManager.appKey) BUAdSDKManager.setTerritory(isNoCN ? BUAdSDKTerritory.NO_CN:BUAdSDKTerritory.CN) BUAdSDKManager.setGDPR(0) BUAdSDKManager.setCoppa(0) BUAdSDKManager.setCCPA(1) #if DEBUG BUAdSDKManager.setLoglevel(.verbose) #endif BUAdSDKManager.start(asyncCompletionHandler: { [weak self] success, error in self?.loadAdSuccess = success }) } func startBUAdSDK(viewController:UIViewController) { if loadAdSuccess { addSplashAD(viewController: viewController) } else { completion?() } } private func addSplashAD(viewController:UIViewController) { let frame = UIScreen.main.bounds splashAdView = BUSplashAdView(slotID: BUAdSlotID.normal_splash_ID, frame: frame) splashAdView?.tolerateTimeout = 3 splashAdView?.delegate = self startTime = CACurrentMediaTime() splashAdView?.loadAdData() viewController.view.addSubview(splashAdView!) splashAdView?.rootViewController = viewController } private func removeSplashAd() { splashAdView?.removeFromSuperview() } } extension BUAdManager:BUSplashAdDelegate { func splashAdDidLoad(_ splashAd: BUSplashAdView) { success?() } func splashAdDidClose(_ splashAd: BUSplashAdView) { completion?() removeSplashAd() } func splashAdDidClickSkip(_ splashAd: BUSplashAdView) { completion?() removeSplashAd() } func splashAd(_ splashAd: BUSplashAdView, didFailWithError error: Error?) { completion?() removeSplashAd() } func splashAdCountdown(toZero splashAd: BUSplashAdView) { } func splashAdDidCloseOtherController(_ splashAd: BUSplashAdView, interactionType: BUInteractionType) { } } class BUAdSlotID { static let normal_splash_ID = "887655509" } ================================================ FILE: zhuishushenqi/App/BookManager.swift ================================================ // // BookManager.swift // zhuishushenqi // // Created by yung on 2017/8/9. // Copyright © 2017年 QS. All rights reserved. // import Foundation // 书籍信息保存类 public class ZSBookManager { // 策略 // 书架信息:保存书架书籍的所有id的list到本地 // 根据id从本地文件中查询是否存在该书籍,如果不存在,从list中删除该id,如果存在,在内存中保存信息 // 书架中书籍的id保存为list的key,对key取md5即为保存的文件名 let ZSBookShelfIDSKey = "ZSBookShelfIDSKey" let ZSReadHistorySaveIDKey = "ZSReadHistorySaveKey" let ZSReadHistoryBooksKey = "ZSReadHistoryBooksKey" fileprivate static var _ids:[String] = [] fileprivate static var _books:[String:BookDetail] = [:] fileprivate static var _historyIds:[String] = [] fileprivate static var _historyBooks:[String:BookDetail] = [:] // 书架的所有书籍的id var ids:[String] { get { return booksID() } set {} } // 书架中的所有书籍的model,请勿使用set方法 var books:[String:BookDetail] { get { return booksInfo() } set {} } /// 阅读历史 var historyBooks:[String:BookDetail] { get { return historyBooksInfo() } set {} } var historyIds:[String] { get { return historyBooksID() } set {} } var _diskQueue:DispatchQueue! static let shared = ZSBookManager() private init() { ZSBookManager.calTime { ZSBookManager._ids = ZSBookManager._ids.filterDuplicates({$0}) let ids = ZSBookManager._ids var index = 0 for id in ids { if id == "" { ZSBookManager._ids.remove(at: index) } index += 1 } if ZSBookManager._ids.count > 0 { self.saveBooksID(booksID: ZSBookManager._ids) } } } //MARK: - 置顶 func topBook(key:String) { var book_index = 0 for index in 0.. Bool { var exist_id:Bool = false for id in ZSBookManager._ids { if bookId == id { exist_id = true } } return exist_id } //MARK: - 浏览记录 fileprivate func existHistoryId(id:String) ->Bool { var exist_id:Bool = false for historyId in ZSBookManager._historyIds { if historyId == id { exist_id = true } } return exist_id } func addHistory(book:BookDetail) { addHistoryId(id: book._id) addHistoryBook(book: book) } fileprivate func addHistoryId(id:String) { if existHistoryId(id: id) { return } if id == "" { return } ZSBookManager._historyIds.append(id) let path = ZSCacheHelper.historyPath let idString = ZSBookManager._historyIds.joined(separator: ",") let success = ZSCacheHelper.shared.storage(obj: idString, for: ZSReadHistorySaveIDKey, cachePath: path) if success { QSLog("阅读历史保存成功") } } fileprivate func addHistoryBook(book:BookDetail) { if book._id == "" { return } ZSBookManager.calTime { let path = ZSCacheHelper.historyPath let success = ZSCacheHelper.shared.storage(obj: book, for: book._id, cachePath: path) if success { QSLog("保存'\(book.title)'的阅读记录成功@_@") } } } /// 删除id数组中的该书id与本地id fileprivate func clearId(id:String) { var index = 0 for bookId in ZSBookManager._ids { if id == bookId { break } index += 1 } ZSBookManager._ids.remove(at: index) saveBooksID(booksID: ZSBookManager._ids) } /// 书籍数组中是否存在该书籍 fileprivate func existBookInfo(book:BookDetail) -> Bool { var book_exist = false for item in ZSBookManager._books { if item.key == book._id { book_exist = true } } return book_exist } /// 添加书籍的id到id数组中 private func addBookId(book:BookDetail) { if book._id == "" { // 为空,不添加 return } if existBookId(bookId: book._id) { // 书架中已存在,不添加 return } ZSBookManager._ids.append(book._id) saveBooksID(booksID: ZSBookManager._ids) } /// 添加书籍到书籍数组中 private func addBookInfo(book:BookDetail) { if book._id == "" { // 为空,不添加 return } if existBookInfo(book: book) { // 书架中已存在,不添加 return } ZSBookManager._books["\(book._id)"] = book saveBooks(book: book) } //MARK: - 保存该书籍信息到本地 fileprivate func saveBooks(book:BookDetail) { ZSBookManager._books = books // 只更新这本书,防止多次重复无用更新 ZSBookManager.calTime { let cachePath = ZSCacheHelper.bookshelfBooksPath let success = ZSCacheHelper.shared.storage(obj: book, for: book._id, cachePath: cachePath) if success { QSLog("保存'\(book.title)'成功@_@") } } } //MARK: - 保存id数组到本地 fileprivate func saveBooksID(booksID:[String]) { ZSBookManager._ids = booksID let path = ZSCacheHelper.bookshelfPath let idString = booksID.joined(separator: ",") let success = ZSCacheHelper.shared.storage(obj: idString, for: ZSBookShelfIDSKey, cachePath: path) if success { QSLog("书架List保存成功") } } /// 清除本地书籍信息 fileprivate func clearBook(book:BookDetail) { ZSBookManager.calTime { let path = ZSCacheHelper.bookshelfBooksPath ZSCacheHelper.shared.clear(for: book._id, cachePath: path) } } //MARK: - 历史信息 fileprivate func historyBooksInfo() -> [String:BookDetail] { if ZSBookManager._historyBooks.count > 0 { return ZSBookManager._historyBooks } var models:[String:BookDetail] = [:] for id in self.historyIds { let path = ZSCacheHelper.historyPath if let obj = ZSCacheHelper.shared.cachedObj(for: id, cachePath: path) as? BookDetail { models[id] = obj } } ZSBookManager._historyBooks = models return models } fileprivate func historyBooksID() ->[String] { if ZSBookManager._historyIds.count > 0 { return ZSBookManager._historyIds } let path = ZSCacheHelper.historyPath if let ids = ZSCacheHelper.shared.cachedObj(for: ZSReadHistorySaveIDKey, cachePath: path) as? String { let idArr = ids.components(separatedBy: ",") ZSBookManager._historyIds = idArr return idArr } ZSBookManager._historyIds = [] return [] } /// 获取本地保存的所有的书籍信息 fileprivate func booksInfo() -> [String:BookDetail] { if ZSBookManager._books.count > 0 && ZSBookManager._books.count == self.ids.count { return ZSBookManager._books } var models:[String:BookDetail] = [:] for id in self.ids { let path = ZSCacheHelper.bookshelfBooksPath if let obj = ZSCacheHelper.shared.cachedObj(for: id, cachePath: path) as? BookDetail { models[id] = obj } } ZSBookManager._books = models return models } // 如果ids存在,返回ids,如果不存在,读文件后保存到ids中,修改时页需要更新ids中的内容 fileprivate func booksID() ->[String] { if ZSBookManager._ids.count > 0 { return ZSBookManager._ids.filter{ return $0 != "" } } let key = ZSBookShelfIDSKey let path = ZSCacheHelper.bookshelfPath if let ids = ZSCacheHelper.shared.cachedObj(for: key, cachePath: path) as? String { let idArr = ids.components(separatedBy: ",").filter{ $0 != "" } ZSBookManager._ids = idArr return idArr } ZSBookManager._ids = [] return [] } // 计算耗时的方法 static func calTime(_ action: @escaping () ->Void){ let startTime = CFAbsoluteTimeGetCurrent() action() let linkTime = (CFAbsoluteTimeGetCurrent() - startTime) QSLog("Linked in \(linkTime * 1000.0) ms") } } // 由于采用数组时,每次遍历的时间会很长,无法快速保存,因此采用NSDictionary来保存书架中的书籍,key为对应的书籍的_id // 书架信息只保存_id 数组,这样可以对书籍进行排序 // 根据_id从本地保存的数据中取出NSDictionary // 每 一本书籍单独保存,避免读取时影响速度 // 该类为书架书籍管理类,管理书架上的书籍的信息,缓存等 public class BookManager { let bookshelfSaveKey = "bookshelfSaveKey" let readHistorySaveKey = "readHistorySaveKey" let zsbookshelvesSaveKey = "zsbookshelvesSaveKey" let zshistorysavekey = "zshistorysavekey" var _diskQueue:DispatchQueue! static let shared = BookManager() private init() { BookManager.calTime { //在这写入要计算时间的代码 self.booksID = self.bookshelf() self.books = [String:Any]() for bookId in self.booksID { let bookInfo = self.bookInfo(id: bookId) self.books[bookId] = bookInfo } } } static func calTime(_ action: @escaping () ->Void){ let startTime = CFAbsoluteTimeGetCurrent() action() let linkTime = (CFAbsoluteTimeGetCurrent() - startTime) QSLog("Linked in \(linkTime * 1000.0) ms") } // key为_id,value 为BookDetail var books:[String:Any]! // booksID 为所有的书籍的id var booksID:[String]! func bookExist(book:BookDetail?) ->Bool { if books[book?._id ?? ""] != nil { return true } return false } // 更新bookdetail的信息,bookdetail的数组与updateinfo数组是一一对应的 func updateInfoUpdate(updateInfo:[BookShelf]){ if updateInfo.count != books.allKeys().count { return } for index in 0..Bool{ if let updatedString = bookInfo?.updated { if updatedString != updateInfo.updated { return true } } return false } // 删除书籍时,只删除记录,缓存的书籍信息保留 func deleteBook(book:BookDetail){ removeBook(book: book) saveBooksID() } func removeBook(book:BookDetail){ if let books:[String] = self.booksID { var index = 0 for bookid in books { if book._id == bookid { self.booksID?.remove(at: index) self.books?.removeValue(forKey: bookid) } index += 1 } } } // 更新书架中保存的书籍信息,从内存更新 @discardableResult func modifyBookshelf(book:BookDetail)->[String:Any]{ var tmpBook = books["\(book._id)"] if let _ = tmpBook{ tmpBook = book books["\(book._id)"] = tmpBook! } else { // 书籍不存在,则添加 books["\(book._id)"] = book } DispatchQueue.global().async { // 持久化时,章节内容置为空 book.book.chapters = [QSChapter()] self.addBookInfo(info: book) } return books } // 更新书架中保存的书籍信息,当用户同意添加到书架时才会调用这个方法 // 首次安装推荐时获取的书籍列表 func modifyBookshelf(books:[BookDetail]?){ if let models = books { for model in models { modifyBookshelf(book: model) } } } // 更新书籍的记录 func modifyRecord(_ book:BookDetail,_ chapter:Int?,_ page:Int?) { modifyRecord(book, chapter, page, nil) } func modifyRecord(_ book:BookDetail,_ chapter:Int?,_ page:Int?,_ bookId:String?) { books[book._id] = book DispatchQueue.global().async { // 持久化时,章节内容置为空,避免内容过大 book.book.chapters = [QSChapter()] self.saveBookInfo(info: book) } } // 数据持久化 func saveBookshelf(shelf:BookDetail){ self.booksID.append(shelf._id) let data = NSKeyedArchiver.archivedData(withRootObject: shelf) setData(data: data, forKey: bookshelfSaveKey) } public func bookshelf()->[String]{ let data = qs_data(forKey: bookshelfSaveKey) if let bookData = data { let obj = NSKeyedUnarchiver.unarchiveObject(with: bookData) as? [String] return obj ?? [] } return [] } func localBookInfo(key:String)->BookDetail?{ let path = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).last?.appending("/\(key)") let url = URL(fileURLWithPath: path ?? "https://www.baidu.com") if let data = try? Data(contentsOf: url, options: Data.ReadingOptions.mappedIfSafe) { let obj = NSKeyedUnarchiver.unarchiveObject(with: data) as? BookDetail return obj } return nil } // 从本地根据id获取书籍的信息 func bookInfo(id:String) ->BookDetail?{ let data = qs_data(forKey: id) if let bookData = data { let obj = NSKeyedUnarchiver.unarchiveObject(with: bookData) as? BookDetail books[id] = obj return obj } return nil } //MARK: - 浏览记录 func readHistory()->[BookDetail]{ if let data = qs_data(forKey: readHistorySaveKey) { if let books = NSKeyedUnarchiver.unarchiveObject(with: data) as? [BookDetail] { return books } } return [] } // 数组中是否存在当前书籍,以id作为标志 func bookExist(book:BookDetail,at:[BookDetail])->Bool{ var exist = false for model in at { if model._id == book._id { exist = true } } return exist } func addReadHistory(book:BookDetail){ // 先匹配 var history = readHistory() if !bookExist(book: book, at: history) { history.insert(book, at: 0) let data = NSKeyedArchiver.archivedData(withRootObject: history) setData(data: data, forKey: readHistorySaveKey) } } //MARK: - 添加书籍保存 func addBook(book:BookDetail){ addBookID(book: book) addBookInfo(info: book) } func addBookID(book:BookDetail){ if book._id != ""{ self.booksID.append(book._id) saveBooksID() } } // 将书籍对应的BookDetail 模型存储到沙盒 func addBookInfo(info:BookDetail){ if info._id != ""{ self.books[info._id] = info saveBookInfo(info: info) } } func saveBookInfo(info:BookDetail) { let data = NSKeyedArchiver.archivedData(withRootObject: info) setData(data: data, forKey: info._id) } func saveBooksID(){ let booksData = NSKeyedArchiver.archivedData(withRootObject: self.booksID) setData(data: booksData, forKey: bookshelfSaveKey) } //MARK: - 数据持久化方法 func qs_data(forKey:String) ->Data?{ let path = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).last?.appending("/\(forKey.md5())") let url = URL(fileURLWithPath: path ?? "https://www.baidu.com") let data = try? Data(contentsOf: url, options: Data.ReadingOptions.mappedIfSafe) return data } func setData(data:Data,forKey:String){ let path = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).last?.appending("/\(forKey.md5())") if _diskQueue == nil { let label = "com.norycao.zhuishushenqi.disk" let qos = DispatchQoS.default let attributes = DispatchQueue.Attributes.concurrent let autoreleaseFrequency:DispatchQueue.AutoreleaseFrequency! if #available(iOS 10.0, *) { autoreleaseFrequency = DispatchQueue.AutoreleaseFrequency.never } else { // Fallback on earlier versions autoreleaseFrequency = DispatchQueue.AutoreleaseFrequency.inherit } _diskQueue = DispatchQueue(label: label, qos: qos, attributes: attributes, autoreleaseFrequency: autoreleaseFrequency, target: nil) } _diskQueue.async { let url = URL(fileURLWithPath: path ?? "https://www.baidu.com") do{ try data.write(to: url) }catch let error { QSLog(error) } } } } ================================================ FILE: zhuishushenqi/App/Config.swift ================================================ // // Config.swift // zhuishushenqi // // Created by Nory Cao on 16/9/17. // Copyright © 2016年 QS. All rights reserved. // import Foundation import UIKit // FIXME: comparison operators with optionals were removed from the Swift Standard Libary. // Consider refactoring the code to use the non-optional operators. fileprivate func < (lhs: T?, rhs: T?) -> Bool { switch (lhs, rhs) { case let (l?, r?): return l < r case (nil, _?): return true default: return false } } // FIXME: comparison operators with optionals were removed from the Swift Standard Libary. // Consider refactoring the code to use the non-optional operators. fileprivate func >= (lhs: T?, rhs: T?) -> Bool { switch (lhs, rhs) { case let (l?, r?): return l >= r default: return !(lhs < rhs) } } //MARK:- API let BASEURL = "http://api.zhuishushenqi.com" let IMAGE_BASEURL = "http://statics.zhuishushenqi.com" let CHAPTERURL = "http://chapter2.zhuishushenqi.com/chapter" let BOOKSHELF = "user/bookshelf" let RANKING = "ranking/gender" // db let searchHistory = "searchHistory" let dbName = "QS.zhuishushenqi.searchHistory" //MARK: - 常用frame let BOUNDS = UIScreen.main.bounds let ScreenWidth = UIScreen.main.bounds.size.width let ScreenHeight = UIScreen.main.bounds.size.height let SCALE = (ScreenWidth / 320.0) let TOP_BAR_Height:CGFloat = 64 let FOOT_BAR_Height:CGFloat = 49 + kTabbarBlankHeight let STATEBARHEIGHT = UIApplication.shared.statusBarFrame.height let kNavgationBarHeight:CGFloat = (IPHONEX ? 88:64) let kTabbarBlankHeight:CGFloat = (IPHONEX ? 34:0) let kQSReaderTopMargin:CGFloat = (IPHONEX ? 30:0) //区分屏幕 let IPHONE4 = UIScreen.instancesRespond(to: #selector(getter: RunLoop.currentMode)) ? CGSize(width: 640, height: 960).equalTo((UIScreen.main.currentMode?.size)!) : false let IPHONE5 = UIScreen.instancesRespond(to: #selector(getter: RunLoop.currentMode)) ? CGSize(width: 640, height: 1136).equalTo((UIScreen.main.currentMode?.size)!) : false let IPHONE6 = UIScreen.instancesRespond(to: #selector(getter: RunLoop.currentMode)) ? CGSize(width: 750, height: 1334).equalTo((UIScreen.main.currentMode?.size)!) : false let IPHONE6Plus = UIScreen.instancesRespond(to: #selector(getter: RunLoop.currentMode)) ? CGSize(width: 1242, height: 2208).equalTo((UIScreen.main.currentMode?.size)!) : false let IPHONEX_SMALL = UIScreen.instancesRespond(to: #selector(getter: RunLoop.currentMode)) ? CGSize(width: 828, height: 1792).equalTo((UIScreen.main.currentMode?.size)!) : false let IPHONEX_MID = UIScreen.instancesRespond(to: #selector(getter: RunLoop.currentMode)) ? CGSize(width: 1125, height: 2436).equalTo((UIScreen.main.currentMode?.size)!) : false let IPHONEX_BIG = UIScreen.instancesRespond(to: #selector(getter: RunLoop.currentMode)) ? CGSize(width: 1242, height: 2688).equalTo((UIScreen.main.currentMode?.size)!) : false let IPHONEX = IPHONEX_SMALL || IPHONEX_MID || IPHONEX_BIG || iPhone12Series() //根据系统判断 获取iPad的屏幕尺寸 let IOS9_OR_LATER = (Float(UIDevice.current.systemVersion) >= 9.0) let IOS8_OR_LATER = (Float(UIDevice.current.systemVersion) >= 8.0) let IOS7_OR_LATER = (Float(UIDevice.current.systemVersion) >= 7.0) let APP_DELEGATE = (UIApplication.shared.delegate as! AppDelegate) let APP_DELEGATEKeyWindow = UIApplication.shared.delegate?.window let USER_DEFAULTS = UserDefaults.standard let KeyWindow = UIApplication.shared.keyWindow let SideVC = SideViewController.shared let ReaderBg = "ReaderBg" let FontSize = "FontSize" let OriginalBrightness = "OriginalBrightness" let Brightness = "Brightness" let ReadingProgress = "ReadingProgress" let PostLink = "PostLink" // notification let SHOW_RECOMMEND = "ShowRecomend" let BOOKSHELF_REFRESH = "BookShelfRefresh" let BOOKSHELF_ADD = "BOOKSHELF_ADD" let BOOKSHELF_DELETE = "BOOKSHELF_DELETE" let RootDisappearNotificationName = "RootDisappearNotificationName" func getAttributes(with lineSpave:CGFloat,font:UIFont)->NSDictionary{ let paraStyle = NSMutableParagraphStyle() paraStyle.lineBreakMode = .byCharWrapping paraStyle.alignment = .left paraStyle.lineSpacing = lineSpave paraStyle.hyphenationFactor = 1.0 paraStyle.firstLineHeadIndent = 0.0 paraStyle.paragraphSpacingBefore = 0.0 paraStyle.headIndent = 0 paraStyle.tailIndent = 0 let dict = [NSAttributedString.Key.font:font,NSAttributedString.Key.kern:1.5,NSAttributedString.Key.paragraphStyle:paraStyle] as [NSAttributedString.Key : Any] return dict as NSDictionary } func attributeText(with lineSpace:CGFloat,text:String,font:UIFont)->NSAttributedString{ let paraStyle = NSMutableParagraphStyle() paraStyle.lineBreakMode = .byCharWrapping paraStyle.alignment = .left paraStyle.hyphenationFactor = 1.0 paraStyle.firstLineHeadIndent = 0.0 paraStyle.paragraphSpacingBefore = 0.0 paraStyle.headIndent = 0 paraStyle.tailIndent = 0 let dict = [NSAttributedString.Key.font:font,NSAttributedString.Key.kern:1.5,NSAttributedString.Key.paragraphStyle:paraStyle] as [NSAttributedString.Key : Any] let attributeStr = NSAttributedString(string: text, attributes: dict) return attributeStr } func QSLog(_ message:T,fileName:String = #file,lineName:Int = #line,funcName:String = #function){ #if DEBUG print("QSLog:\((fileName as NSString).lastPathComponent)[\(lineName)]\(funcName):\n\(message)\n") #endif } func calTime(_ action: @escaping () ->Void){ let startTime = CFAbsoluteTimeGetCurrent() action() let linkTime = (CFAbsoluteTimeGetCurrent() - startTime) QSLog("Linked in \(linkTime * 1000.0) ms") } func iPhone12Series() ->Bool { let _12MiniSize = CGSize(width: 360, height: 780) let _12ProSize = CGSize(width: 390, height: 844) let _12ProMaxSize = CGSize(width: 428, height: 926) let _12MiniLandscapeSize = CGSize(width: 780, height: 360) let _12ProLandscapeSize = CGSize(width: 844, height: 390) let _12ProMaxLandscapeSize = CGSize(width: 926, height: 428) let isIPhone12Series:Bool = UIScreen.main.bounds.size.equalTo(_12MiniSize) || UIScreen.main.bounds.size.equalTo(_12ProSize) || UIScreen.main.bounds.size.equalTo(_12ProMaxSize) || UIScreen.main.bounds.size.equalTo(_12MiniLandscapeSize) || UIScreen.main.bounds.size.equalTo(_12ProLandscapeSize) || UIScreen.main.bounds.size.equalTo(_12ProMaxLandscapeSize) return isIPhone12Series } ================================================ FILE: zhuishushenqi/App/FBEncryptorAES.h ================================================ // // FBEncryptorAES.h // zhuishushenqi // // Created by yung on 2018/11/9. // Copyright © 2018年 QS. All rights reserved. // #import #import #define FBENCRYPT_ALGORITHM kCCAlgorithmAES128 #define FBENCRYPT_BLOCK_SIZE kCCBlockSizeAES128 #define FBENCRYPT_KEY_SIZE kCCKeySizeAES128 @interface FBEncryptorAES : NSObject { } //----------------- // API (raw data) //----------------- + (NSData*)generateIv; + (NSData*)encryptData:(NSData*)data key:(NSData*)key iv:(NSData*)iv; + (NSData*)decryptData:(NSData*)data key:(NSData*)key iv:(NSData*)iv; //----------------- // API (base64) //----------------- // the return value of encrypteMessage: and 'encryptedMessage' are encoded with base64. // + (NSString*)encryptBase64String:(NSString*)string keyString:(NSString*)keyString separateLines:(BOOL)separateLines; + (NSString*)decryptBase64String:(NSString*)encryptedBase64String keyString:(NSString*)keyString; //----------------- // API (utilities) //----------------- + (NSString*)hexStringForData:(NSData*)data; + (NSData*)dataForHexString:(NSString*)hexString; @end ================================================ FILE: zhuishushenqi/App/FBEncryptorAES.m ================================================ // // FBEncryptorAES.m // zhuishushenqi // // Created by yung on 2018/11/9. // Copyright © 2018年 QS. All rights reserved. // #import "FBEncryptorAES.h" #import "NSData+Base64.h" @implementation FBEncryptorAES #pragma mark - #pragma mark Initialization and deallcation #pragma mark - #pragma mark Praivate #pragma mark - #pragma mark API + (NSData*)encryptData:(NSData*)data key:(NSData*)key iv:(NSData*)iv; { NSData* result = nil; // setup key unsigned char cKey[FBENCRYPT_KEY_SIZE]; bzero(cKey, sizeof(cKey)); [key getBytes:cKey length:FBENCRYPT_KEY_SIZE]; // setup iv char cIv[FBENCRYPT_BLOCK_SIZE]; bzero(cIv, FBENCRYPT_BLOCK_SIZE); if (iv) { [iv getBytes:cIv length:FBENCRYPT_BLOCK_SIZE]; } // setup output buffer size_t bufferSize = [data length] + FBENCRYPT_BLOCK_SIZE; void *buffer = malloc(bufferSize); // do encrypt size_t encryptedSize = 0; CCCryptorStatus cryptStatus = CCCrypt(kCCEncrypt, FBENCRYPT_ALGORITHM, kCCOptionPKCS7Padding, cKey, FBENCRYPT_KEY_SIZE, cIv, [data bytes], [data length], buffer, bufferSize, &encryptedSize); if (cryptStatus == kCCSuccess) { result = [NSData dataWithBytesNoCopy:buffer length:encryptedSize]; } else { free(buffer); NSLog(@"[ERROR] failed to encrypt|CCCryptoStatus: %d", cryptStatus); } return result; } + (NSData*)decryptData:(NSData*)data key:(NSData*)key iv:(NSData*)iv; { NSData* result = nil; // setup key unsigned char cKey[FBENCRYPT_KEY_SIZE]; bzero(cKey, sizeof(cKey)); [key getBytes:cKey length:FBENCRYPT_KEY_SIZE]; // setup iv char cIv[FBENCRYPT_BLOCK_SIZE]; bzero(cIv, FBENCRYPT_BLOCK_SIZE); if (iv) { [iv getBytes:cIv length:FBENCRYPT_BLOCK_SIZE]; } // setup output buffer size_t bufferSize = [data length] + FBENCRYPT_BLOCK_SIZE; void *buffer = malloc(bufferSize); // do decrypt size_t decryptedSize = 0; CCCryptorStatus cryptStatus = CCCrypt(kCCDecrypt, FBENCRYPT_ALGORITHM, kCCOptionPKCS7Padding, cKey, FBENCRYPT_KEY_SIZE, cIv, [data bytes], [data length], buffer, bufferSize, &decryptedSize); if (cryptStatus == kCCSuccess) { result = [NSData dataWithBytesNoCopy:buffer length:decryptedSize]; } else { free(buffer); NSLog(@"[ERROR] failed to decrypt| CCCryptoStatus: %d", cryptStatus); } return result; } + (NSString*)encryptBase64String:(NSString*)string keyString:(NSString*)keyString separateLines:(BOOL)separateLines { NSData* data = [self encryptData:[string dataUsingEncoding:NSUTF8StringEncoding] key:[keyString dataUsingEncoding:NSUTF8StringEncoding] iv:nil]; return [data base64EncodedStringWithSeparateLines:separateLines]; } + (NSString*)decryptBase64String:(NSString*)encryptedBase64String keyString:(NSString*)keyString { NSData* encryptedData = [NSData dataFromBase64String:encryptedBase64String]; NSData* data = [self decryptData:encryptedData key:[keyString dataUsingEncoding:NSUTF8StringEncoding] iv:nil]; if (data) { return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; } else { return nil; } } #define FBENCRYPT_IV_HEX_LEGNTH (FBENCRYPT_BLOCK_SIZE*2) + (NSData*)generateIv { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ srand((unsigned)time(NULL)); }); char cIv[FBENCRYPT_BLOCK_SIZE]; for (int i=0; i < FBENCRYPT_BLOCK_SIZE; i++) { cIv[i] = rand() % 256; } return [NSData dataWithBytes:cIv length:FBENCRYPT_BLOCK_SIZE]; } + (NSString*)hexStringForData:(NSData*)data { if (data == nil) { return nil; } NSMutableString* hexString = [NSMutableString string]; const unsigned char *p = [data bytes]; for (int i=0; i < [data length]; i++) { [hexString appendFormat:@"%02x", *p++]; } return hexString; } + (NSData*)dataForHexString:(NSString*)hexString { if (hexString == nil) { return nil; } const char* ch = [[hexString lowercaseString] cStringUsingEncoding:NSUTF8StringEncoding]; NSMutableData* data = [NSMutableData data]; while (*ch) { char byte = 0; if ('0' <= *ch && *ch <= '9') { byte = *ch - '0'; } else if ('a' <= *ch && *ch <= 'f') { byte = *ch - 'a' + 10; } ch++; byte = byte << 4; if (*ch) { if ('0' <= *ch && *ch <= '9') { byte += *ch - '0'; } else if ('a' <= *ch && *ch <= 'f') { byte += *ch - 'a' + 10; } ch++; } [data appendBytes:&byte length:1]; } return data; } @end ================================================ FILE: zhuishushenqi/App/FBEncryptorAESUtils.h ================================================ // // FBEncryptorAESUtils.h // zhuishushenqi // // Created by yung on 2018/11/9. // Copyright © 2018年 QS. All rights reserved. // #import NS_ASSUME_NONNULL_BEGIN @interface FBEncryptorAESUtils : NSObject + (NSString *)getDecryptedStrWithKey:(NSString *)key cipherText:(NSString *)cipherText; @end NS_ASSUME_NONNULL_END ================================================ FILE: zhuishushenqi/App/FBEncryptorAESUtils.mm ================================================ // // FBEncryptorAESUtils.m // zhuishushenqi // // Created by yung on 2018/11/9. // Copyright © 2018年 QS. All rights reserved. // #import "FBEncryptorAESUtils.h" #import "FBEncryptorAES.h" @implementation FBEncryptorAESUtils + (NSString *)getDecryptedStrWithKey:(NSString *)key cipherText:(NSString *)cipherText { NSData *v9 = [[NSData alloc] initWithBase64EncodedString:key options:(NSDataBase64DecodingIgnoreUnknownCharacters)]; NSData *v11 = [[NSData alloc] initWithBase64EncodedString:cipherText options:(NSDataBase64DecodingIgnoreUnknownCharacters)]; if (v11.length > 16) { NSData *v12 = [v11 subdataWithRange:NSMakeRange(0, 16)]; NSInteger v14 = v11.length; NSData *v15 = [v11 subdataWithRange:NSMakeRange(16, v14 - 16)]; NSData *resultData = [FBEncryptorAES decryptData:v15 key:v9 iv:v12]; NSString *resultText = [[NSString alloc] initWithData:resultData encoding:(NSUTF8StringEncoding)]; return resultText; } return @""; } @end ================================================ FILE: zhuishushenqi/App/Reader.swift ================================================ // // ReaderStyle.swift // zhuishushenqi // // Created by yung on 2017/8/9. // Copyright © 2017年 QS. All rights reserved. // import Foundation enum Reader:Int { case white case yellow case green case blackgreen case pink case sheepskin case violet case water case weekGreen case weekPink case coffee } extension Reader { var backgroundImage:UIImage { switch self { case .yellow: return #imageLiteral(resourceName: "yellow_mode_bg") case .white: return #imageLiteral(resourceName: "white_mode_bg") case .green: return #imageLiteral(resourceName: "green_mode_bg") case .blackgreen: return #imageLiteral(resourceName: "new_nav_night_normal") case .pink: return #imageLiteral(resourceName: "violet_mode_bg") case .sheepskin: return #imageLiteral(resourceName: "beijing") case .violet: return #imageLiteral(resourceName: "violet_mode_bg") case .water: return #imageLiteral(resourceName: "sheepskin_mode_bg") case .weekPink: return #imageLiteral(resourceName: "violet_mode_bg") case .weekGreen: return #imageLiteral(resourceName: "violet_mode_bg") case .coffee: return #imageLiteral(resourceName: "pf_header_bg") default: return #imageLiteral(resourceName: "violet_mode_bg") } } var textColor:UIColor { switch self { case .yellow,.white,.green: return UIColor.black case .blackgreen,.coffee: return UIColor.white default: return UIColor.black } } var batteryColor:UIColor { switch self { case .yellow,.white,.green: return UIColor.darkGray case .blackgreen,.coffee: return UIColor.white default: return UIColor.darkGray } } } ================================================ FILE: zhuishushenqi/App/Theme.swift ================================================ // // Theme.swift // zhuishushenqi // // Created by yung on 2017/8/9. // Copyright © 2017年 QS. All rights reserved. // import Foundation let readerKey = "reader.key" enum AppTheme { case day case night } extension AppTheme { } ================================================ FILE: zhuishushenqi/App/ZSDBManager.h ================================================ // // ZSDBManager.h // zhuishushenqi // // Created by yung on 2018/11/22. // Copyright © 2018年 QS. All rights reserved. // #import #import "ZSDBPropertyModel.h" @protocol ZSDBModel - (NSString *)tableName; - (NSString *)primaryKey; - (NSString *)foreignKey; - (NSDictionary *)dbColumnMapping; - (NSArray *)ignoredKeys; @end @interface ZSDBManager : NSObject + (instancetype)share; - (NSArray *)getPropertys:(NSObject *)model; @end ================================================ FILE: zhuishushenqi/App/ZSDBManager.m ================================================ // // ZSDBManager.m // zhuishushenqi // // Created by yung on 2018/11/22. // Copyright © 2018年 QS. All rights reserved. // #import "ZSDBManager.h" #import #import /** SQLite五种数据类型 */ #define SQLTEXT @"TEXT" #define SQLINTEGER @"INTEGER" #define SQLREAL @"REAL" #define SQLBLOB @"BLOB" #define SQLNULL @"NULL" #define PrimaryKey @"primary key" #define primaryId @"pk" @interface ZSDBManager () @property (nonatomic, strong) FMDatabaseQueue *dbQueue; @end @implementation ZSDBManager + (instancetype)share { static ZSDBManager *share = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ share = [[ZSDBManager alloc] init]; }); return share; } - (BOOL)isTableExist:(NSObject *)model { __block BOOL res = NO; [self.dbQueue inDatabase:^(FMDatabase *db) { NSString *tableName = [model tableName]; res = [db tableExists:tableName]; }]; return res; } - (NSArray *)getColumnsFrom:(NSObject *)model { NSMutableArray *columns = [NSMutableArray array]; [self.dbQueue inDatabase:^(FMDatabase *db) { NSString *tableName = NSStringFromClass(self.class); FMResultSet *resultSet = [db getTableSchema:tableName]; while ([resultSet next]) { NSString *column = [resultSet stringForColumn:@"name"]; [columns addObject:column]; } }]; return [columns copy]; } + (NSString *)getColumnAndTypeString:(NSArray *)models { NSMutableString *pars = [NSMutableString string]; for (ZSDBPropertyModel *model in models) { if (![model.type isEqualToString:NSStringFromProtocol(@protocol(ZSDBModel))]) { if (model.mappingKey) { [pars appendString:model.mappingKey]; } else { [pars appendString:model.originalKey]; } [pars appendString:model.type]; NSInteger count = [models indexOfObject:model]; if (count != models.count - 1) { [pars appendString:@","]; } } } return pars; } + (BOOL)createTable:(NSObject *)model { __block BOOL res = YES; [ZSDBManager.share.dbQueue inTransaction:^(FMDatabase *db, BOOL *rollback) { NSString *tableName = [model tableName]; NSArray * propertys = [[ZSDBManager share]getPropertys:model]; NSString *columeAndType = [self.class getColumnAndTypeString:propertys]; NSString *sql = [NSString stringWithFormat:@"CREATE TABLE IF NOT EXISTS %@(%@);",tableName,columeAndType]; if (![db executeUpdate:sql]) { res = NO; *rollback = YES; return; }; NSMutableArray *columns = [NSMutableArray array]; FMResultSet *resultSet = [db getTableSchema:tableName]; while ([resultSet next]) { NSString *column = [resultSet stringForColumn:@"name"]; [columns addObject:column]; } NSMutableArray *propertyStrings = @[].mutableCopy; for (ZSDBPropertyModel *model in propertys) { if (model.mappingKey) { [propertyStrings addObject:model.mappingKey]; } else { [propertyStrings addObject:model.originalKey]; } } NSPredicate *filterPredicate = [NSPredicate predicateWithFormat:@"NOT (SELF IN %@)",columns]; //过滤数组 NSArray *resultArray = [propertyStrings filteredArrayUsingPredicate:filterPredicate]; for (NSString *column in resultArray) { NSUInteger index = [propertyStrings indexOfObject:column]; NSString *proType = [[propertys objectAtIndex:index] type]; if (![proType isEqualToString:NSStringFromProtocol(@protocol(ZSDBModel))]) { NSString *fieldSql = [NSString stringWithFormat:@"%@ %@",column,proType]; NSString *sql = [NSString stringWithFormat:@"ALTER TABLE %@ ADD COLUMN %@ ",[model tableName],fieldSql]; if (![db executeUpdate:sql]) { res = NO; *rollback = YES; return ; } } } }]; return res; } //- (BOOL)saveOrUpdate:(NSObject *)model { // NSString *primaryKey = [model primaryKey]; // id primaryValue = [model valueForKey:primaryKey]; // if ([primaryValue intValue] <= 0) { // return [self save]; // } // return [self update]; //} - (BOOL)save:(NSObject *)model { NSString *tableName = [model tableName]; NSMutableString *keyString = [NSMutableString string]; NSMutableString *valueString = [NSMutableString string]; NSMutableArray *insertValues = [NSMutableArray array]; NSArray *columns = [self getPropertys:model]; for (int i = 0; i < columns.count; i++) { NSString *columnName = columns[i].mappingKey ? columns[i].mappingKey:columns[i].originalKey; [keyString appendFormat:@"%@,", columnName]; [valueString appendString:@"?,"]; id value = [model valueForKey:columnName]; if (!value) { value = @""; } [insertValues addObject:value]; } [keyString deleteCharactersInRange:NSMakeRange(keyString.length - 1, 1)]; [valueString deleteCharactersInRange:NSMakeRange(valueString.length - 1, 1)]; __block BOOL res = NO; [self.dbQueue inDatabase:^(FMDatabase *db) { NSString *sql = [NSString stringWithFormat:@"INSERT INTO %@(%@) VALUES (%@);", tableName, keyString, valueString]; res = [db executeUpdate:sql withArgumentsInArray:insertValues]; NSLog(res?@"插入成功":@"插入失败"); }]; return res; } /** 批量保存用户对象 */ + (BOOL)saveObjects:(NSArray *)array { __block BOOL res = YES; // 如果要支持事务 [ZSDBManager.share.dbQueue inTransaction:^(FMDatabase *db, BOOL *rollback) { for (NSObject *model in array) { NSString *tableName = [model tableName]; NSMutableString *keyString = [NSMutableString string]; NSMutableString *valueString = [NSMutableString string]; NSMutableArray *insertValues = [NSMutableArray array]; NSArray *columns = [ZSDBManager.share getPropertys:model]; for (int i = 0; i < columns.count; i++) { NSString *columnName = columns[i].mappingKey ? columns[i].mappingKey:columns[i].originalKey; [keyString appendFormat:@"%@,", columnName]; [valueString appendString:@"?,"]; id value = [model valueForKey:columnName]; if (!value) { value = @""; } [insertValues addObject:value]; } [keyString deleteCharactersInRange:NSMakeRange(keyString.length - 1, 1)]; [valueString deleteCharactersInRange:NSMakeRange(valueString.length - 1, 1)]; NSString *sql = [NSString stringWithFormat:@"INSERT INTO %@(%@) VALUES (%@);", tableName, keyString, valueString]; BOOL flag = [db executeUpdate:sql withArgumentsInArray:insertValues]; NSLog(flag?@"插入成功":@"插入失败"); if (!flag) { res = NO; *rollback = YES; return; } } }]; return res; } /** 更新单个对象 */ - (BOOL)update:(NSObject *)model { __block BOOL res = NO; [self.dbQueue inDatabase:^(FMDatabase *db) { NSString *tableName = [model tableName]; id primaryValue = [model valueForKey:[model primaryKey]]; if (!primaryValue || primaryValue <= 0) { return ; } NSMutableString *keyString = [NSMutableString string]; NSMutableArray *updateValues = [NSMutableArray array]; NSArray *columns = [ZSDBManager.share getPropertys:model]; for (int i = 0; i < columns.count; i++) { NSString *columnName = columns[i].mappingKey ? columns[i].mappingKey:columns[i].originalKey; [keyString appendFormat:@" %@=?,", columnName]; id value = [model valueForKey:columnName]; if (!value) { value = @""; } [updateValues addObject:value]; } //删除最后那个逗号 [keyString deleteCharactersInRange:NSMakeRange(keyString.length - 1, 1)]; NSString *sql = [NSString stringWithFormat:@"UPDATE %@ SET %@ WHERE %@ = ?;", tableName, keyString, primaryId]; [updateValues addObject:primaryValue]; res = [db executeUpdate:sql withArgumentsInArray:updateValues]; NSLog(res?@"更新成功":@"更新失败"); }]; return res; } /** 批量更新用户对象*/ + (BOOL)updateObjects:(NSArray *)array { __block BOOL res = YES; // 如果要支持事务 [ZSDBManager.share.dbQueue inTransaction:^(FMDatabase *db, BOOL *rollback) { for (NSObject *model in array) { id primaryValue = [model valueForKey:[model primaryKey]]; if (!primaryValue || primaryValue <= 0) { res = NO; *rollback = YES; return; } NSMutableString *keyString = [NSMutableString string]; NSMutableArray *updateValues = [NSMutableArray array]; NSArray *columns = [ZSDBManager.share getPropertys:model]; for (int i = 0; i < columns.count; i++) { NSString *columnName = columns[i].mappingKey ? columns[i].mappingKey:columns[i].originalKey; [keyString appendFormat:@" %@=?,", columnName]; id value = [model valueForKey:columnName]; if (!value) { value = @""; } [updateValues addObject:value]; } //删除最后那个逗号 [keyString deleteCharactersInRange:NSMakeRange(keyString.length - 1, 1)]; NSString *sql = [NSString stringWithFormat:@"UPDATE %@ SET %@ WHERE %@=?;", [model tableName], keyString, [model primaryKey]]; [updateValues addObject:primaryValue]; BOOL flag = [db executeUpdate:sql withArgumentsInArray:updateValues]; NSLog(flag?@"更新成功":@"更新失败"); if (!flag) { res = NO; *rollback = YES; return; } } }]; return res; } /** 删除单个对象 */ - (BOOL)deleteObject:(NSObject *)model { __block BOOL res = NO; [self.dbQueue inDatabase:^(FMDatabase *db) { NSString *tableName = [model tableName]; id primaryValue = [model valueForKey:[model primaryKey]]; if (!primaryValue || primaryValue <= 0) { return ; } NSString *sql = [NSString stringWithFormat:@"DELETE FROM %@ WHERE %@ = ?",tableName,[model primaryKey]]; res = [db executeUpdate:sql withArgumentsInArray:@[primaryValue]]; NSLog(res?@"删除成功":@"删除失败"); }]; return res; } /** 批量删除用户对象 */ + (BOOL)deleteObjects:(NSArray *)array { __block BOOL res = YES; // 如果要支持事务 [ZSDBManager.share.dbQueue inTransaction:^(FMDatabase *db, BOOL *rollback) { for (NSObject *model in array) { NSString *tableName = [model tableName]; id primaryValue = [model valueForKey:[model primaryKey]]; if (!primaryValue || primaryValue <= 0) { return ; } NSString *sql = [NSString stringWithFormat:@"DELETE FROM %@ WHERE %@ = ?",tableName,[model primaryKey]]; BOOL flag = [db executeUpdate:sql withArgumentsInArray:@[primaryValue]]; NSLog(flag?@"删除成功":@"删除失败"); if (!flag) { res = NO; *rollback = YES; return; } } }]; return res; } /** 通过条件删除数据 */ + (BOOL)deleteObjectsByCriteria:(NSString *)criteria model:(NSObject *)model { __block BOOL res = NO; [ZSDBManager.share.dbQueue inDatabase:^(FMDatabase *db) { NSString *tableName = [model tableName]; NSString *sql = [NSString stringWithFormat:@"DELETE FROM %@ %@ ",tableName,criteria]; res = [db executeUpdate:sql]; NSLog(res?@"删除成功":@"删除失败"); }]; return res; } /** 通过条件删除 (多参数)--2 */ + (BOOL)deleteObjectsWithModel:(NSObject *)model Format:(NSString *)format, ... { va_list ap; va_start(ap, format); NSString *criteria = [[NSString alloc] initWithFormat:format locale:[NSLocale currentLocale] arguments:ap]; va_end(ap); return [self deleteObjectsByCriteria:criteria model:model]; } /** 清空表 */ + (BOOL)clearTable:(NSObject *)model { __block BOOL res = NO; [ZSDBManager.share.dbQueue inDatabase:^(FMDatabase *db) { NSString *tableName = [model tableName]; NSString *sql = [NSString stringWithFormat:@"DELETE FROM %@",tableName]; res = [db executeUpdate:sql]; NSLog(res?@"清空成功":@"清空失败"); }]; return res; } - (NSArray *)queryAll:(NSObject *)model { NSLog(@"ZSDBModel---%s",__func__); NSMutableArray *models = [NSMutableArray array]; [self.dbQueue inDatabase:^(FMDatabase * _Nonnull db) { NSString *tableName = [model tableName]; NSString *sql = [NSString stringWithFormat:@"SELECT * FROM %@",tableName]; FMResultSet *resultSet = [db executeQuery:sql]; while ([resultSet next]) { Class modelClass = [model class]; NSObject *queryModel = [[modelClass alloc] init]; NSArray *columns = [self getPropertys:queryModel]; for (NSInteger i = 0; i< columns.count; i++) { NSString *columnName = columns[i].mappingKey ? columns[i].mappingKey:columns[i].originalKey; NSString *columnType = columns[i].type; if ([columnType isEqualToString:SQLTEXT]) { [queryModel setValue:[resultSet stringForColumn:columnName] forKey:columnName]; } else if ([columnType isEqualToString:SQLBLOB]) { [queryModel setValue:[resultSet dataForColumn:columnName] forKey:columnName]; } else if ([columnName isEqualToString:NSStringFromProtocol(@protocol(ZSDBModel))]) { // 单独查询这个表即可 } else { [queryModel setValue:[NSNumber numberWithLongLong:[resultSet longLongIntForColumn:columnName]] forKey:columnName]; } } [models addObject:queryModel]; FMDBRelease(queryModel); } }]; return models; } /** 查找某条数据 */ + (instancetype)findFirstByCriteria:(NSString *)criteria model:(NSObject *)model { NSArray *results = [ZSDBManager findByCriteria:criteria model:model]; if (results.count < 1) { return nil; } return [results firstObject]; } + (instancetype)findByPK:(int)inPk model:(NSObject *)model { NSString *condition = [NSString stringWithFormat:@"WHERE %@=%d",primaryId,inPk]; return [self findFirstByCriteria:condition model:model]; } + (NSArray *)findWithModel:(NSObject *)model Format:(NSString *)format, ... { va_list ap; va_start(ap, format); NSString *criteria = [[NSString alloc] initWithFormat:format locale:[NSLocale currentLocale] arguments:ap]; va_end(ap); return [self findByCriteria:criteria model:model]; } /** 通过条件查找数据 */ + (NSArray *)findByCriteria:(NSString *)criteria model:(NSObject *)model { NSMutableArray *users = [NSMutableArray array]; [ZSDBManager.share.dbQueue inDatabase:^(FMDatabase *db) { NSString *tableName = [model tableName]; NSString *sql = [NSString stringWithFormat:@"SELECT * FROM %@ %@",tableName,criteria]; FMResultSet *resultSet = [db executeQuery:sql]; while ([resultSet next]) { NSObject *result = [[[model class] alloc] init]; NSArray *columns = [ZSDBManager.share getPropertys:result]; for (int i=0; i< columns.count; i++) { NSString *columnName = columns[i].mappingKey ? columns[i].mappingKey:columns[i].originalKey; NSString *columnType = columns[i].type; if ([columnType isEqualToString:SQLTEXT]) { [result setValue:[resultSet stringForColumn:columnName] forKey:columnName]; } else if ([columnType isEqualToString:SQLBLOB]) { [result setValue:[resultSet dataForColumn:columnName] forKey:columnName]; } else if ([columnName isEqualToString:NSStringFromProtocol(@protocol(ZSDBModel))]) { // 单独查询这个表即可 } else { [result setValue:[NSNumber numberWithLongLong:[resultSet longLongIntForColumn:columnName]] forKey:columnName]; } } [users addObject:model]; FMDBRelease(model); } }]; return users; } - (NSArray *)getPropertys:(NSObject *)model { NSString *foreignKey; NSString *primaryKey; NSDictionary *mapping; NSArray *ignoredKeys; mapping = [model dbColumnMapping]; primaryKey = [model primaryKey]; foreignKey = [model foreignKey]; ignoredKeys = [model ignoredKeys]; NSMutableArray *propertys = @[].mutableCopy; unsigned int outCount, i; objc_property_t *properties = class_copyPropertyList([model class], &outCount); for (i = 0; i < outCount; i++) { objc_property_t property = properties[i]; //获取属性名 NSString *propertyName = [NSString stringWithCString:property_getName(property) encoding:NSUTF8StringEncoding]; // 如果在忽略列表中,不处理 if ([ignoredKeys containsObject:propertyName]) { continue; } id propertyValue = [model valueForKey:propertyName]; ZSDBPropertyModel *propertyModel = [[ZSDBPropertyModel alloc] init]; propertyModel.originalKey = propertyName; propertyModel.value = propertyValue; if (mapping[propertyName]) { propertyModel.mappingKey = mapping[propertyName]; } if ([propertyName isEqualToString:primaryKey]) { propertyModel.isPrimaryKey = YES; } else { propertyModel.isPrimaryKey = NO; } if ([propertyValue conformsToProtocol:@protocol(ZSDBModel)]) { propertyModel.type = [NSString stringWithFormat:@"%@",@protocol(ZSDBModel)]; propertyModel.value = [self getPropertys:propertyValue]; continue; } //获取属性类型等参数 NSString *propertyType = [NSString stringWithCString: property_getAttributes(property) encoding:NSUTF8StringEncoding]; /* 各种符号对应类型,部分类型在新版SDK中有所变化,如long 和long long c char C unsigned char i int I unsigned int l long L unsigned long s short S unsigned short d double D unsigned double f float F unsigned float q long long Q unsigned long long B BOOL @ 对象类型 //指针 对象类型 如NSString 是@“NSString” 64位下long 和long long 都是Tq SQLite 默认支持五种数据类型TEXT、INTEGER、REAL、BLOB、NULL 因为在项目中用的类型不多,故只考虑了少数类型 */ if ([propertyType hasPrefix:@"T@\"NSString\""]) { propertyModel.type = SQLTEXT; } else if ([propertyType hasPrefix:@"T@\"NSData\""]) { propertyModel.type = SQLBLOB; } else if ([propertyType hasPrefix:@"Ti"]||[propertyType hasPrefix:@"TI"]||[propertyType hasPrefix:@"Ts"]||[propertyType hasPrefix:@"TS"]||[propertyType hasPrefix:@"TB"]||[propertyType hasPrefix:@"Tq"]||[propertyType hasPrefix:@"TQ"] || [propertyType hasPrefix:@"T@\"NSNumber\""]) { propertyModel.type = SQLINTEGER; } else { propertyModel.type = SQLREAL; } } return propertys; } + (NSString *)dbPathWithDirectoryName:(NSString *)directoryName { NSString *docsdir = [NSSearchPathForDirectoriesInDomains( NSDocumentDirectory, NSUserDomainMask, YES) lastObject]; NSFileManager *filemanage = [NSFileManager defaultManager]; if (directoryName == nil || directoryName.length == 0) { docsdir = [docsdir stringByAppendingPathComponent:@"zhuishushenqi"]; } else { docsdir = [docsdir stringByAppendingPathComponent:directoryName]; } BOOL isDir; BOOL exit =[filemanage fileExistsAtPath:docsdir isDirectory:&isDir]; if (!exit || !isDir) { [filemanage createDirectoryAtPath:docsdir withIntermediateDirectories:YES attributes:nil error:nil]; } NSString *dbpath = [docsdir stringByAppendingPathComponent:@"zssq.sqlite"]; return dbpath; } + (NSString *)dbPath { return [self dbPathWithDirectoryName:nil]; } - (FMDatabaseQueue *)dbQueue { if (_dbQueue == nil) { _dbQueue = [[FMDatabaseQueue alloc] initWithPath:[self.class dbPath]]; } return _dbQueue; } @end ================================================ FILE: zhuishushenqi/App/ZSDBPropertyModel.h ================================================ // // ZSDBPropertyModel.h // zhuishushenqi // // Created by yung on 2018/11/23. // Copyright © 2018年 QS. All rights reserved. // #import @interface ZSDBPropertyModel : NSObject @property (nonatomic, copy) NSString *originalKey; @property (nonatomic, copy) NSString *mappingKey; @property (nonatomic, strong) id value; @property (nonatomic, copy) NSString *type; @property (nonatomic, assign) BOOL isPrimaryKey; @end ================================================ FILE: zhuishushenqi/App/ZSDBPropertyModel.m ================================================ // // ZSDBPropertyModel.m // zhuishushenqi // // Created by yung on 2018/11/23. // Copyright © 2018年 QS. All rights reserved. // #import "ZSDBPropertyModel.h" @implementation ZSDBPropertyModel @end ================================================ FILE: zhuishushenqi/App/ZSDatabase.swift ================================================ // // ZSDatabase.swift // zhuishushenqi // // Created by caony on 2018/11/6. // Copyright © 2018 QS. All rights reserved. // import UIKit import SQLite struct ZSDatabase { var db:Connection! init() { connectDatabase() } // 与数据库建立连接 mutating func connectDatabase(filePath: String = "/Documents") -> Void { let sqlFilePath = NSHomeDirectory() + filePath + "/db.sqlite3" do { // 与数据库建立连接 db = try Connection(sqlFilePath) print("与数据库建立连接 成功") } catch { print("与数据库建立连接 失败:\(error)") } } //MARK: - 书架信息记录需要基本信息表,阅读记录(record) let TABLE_LAMP = Table("bookshelf") // 表名称 let TABLE_LAMP_ID = Expression("lamp_id") // 列表项及项类型 let TABLE_LAMP_BOOKNAME = Expression("bookName") let TABLE_LAMP_BOOKID = Expression("bookId") let TABLE_LAMP_COVER = Expression("cover") let TABLE_LAMP_AUTHOR = Expression("author") let TABLE_LAMP_LAST_CHAPTER = Expression("lastChapter") let TABLE_LAMP_LAST_UPDATE_TIME = Expression("lastUpdateTime") let TABLE_LAMP_LAST_HAVE_UPDATE = Expression("haveUpdate") let TABLE_LAMP_LAST_UPDATED = Expression("updated") let TABLE_LAMP_LAST_CHAPTER_COUNT = Expression("lastChapterCount") let TABLE_LAMP_LAST_VISIT_TIME = Expression("lastVisitTime") let TABLE_LAMP_REFERENCE_SOURCE = Expression("referenceSource") let TABLE_LAMP_TOPIC_COUNT = Expression("topicCount") func createBookshelf() { do { try db.run(TABLE_LAMP.create(temporary: false, ifNotExists: true, withoutRowid: false, block: { (table) in table.column(TABLE_LAMP_ID, primaryKey: .autoincrement) // 主键自加且不为空 table.column(TABLE_LAMP_BOOKNAME) table.column(TABLE_LAMP_BOOKID, unique:true) table.column(TABLE_LAMP_COVER) table.column(TABLE_LAMP_AUTHOR) table.column(TABLE_LAMP_LAST_CHAPTER) table.column(TABLE_LAMP_LAST_UPDATE_TIME) table.column(TABLE_LAMP_LAST_HAVE_UPDATE) table.column(TABLE_LAMP_LAST_UPDATED) table.column(TABLE_LAMP_LAST_CHAPTER_COUNT) table.column(TABLE_LAMP_LAST_VISIT_TIME) table.column(TABLE_LAMP_REFERENCE_SOURCE) table.column(TABLE_LAMP_TOPIC_COUNT) })) print("创建表 bookshelf 成功") } catch { print("创建表 bookshelf 失败:\(error)") } } func queryBookshelf() ->[BookDetail] { var books:[BookDetail] = [] for item in (try! db.prepare(TABLE_LAMP)) { let book = BookDetail() let updateInfo = BookShelf() updateInfo.lastChapter = item[TABLE_LAMP_LAST_CHAPTER] updateInfo.updated = item[TABLE_LAMP_LAST_UPDATE_TIME] book.title = item[TABLE_LAMP_BOOKNAME] book._id = item[TABLE_LAMP_BOOKID] book.cover = item[TABLE_LAMP_COVER] book.author = item[TABLE_LAMP_AUTHOR] book.updated = item[TABLE_LAMP_LAST_UPDATE_TIME] book.isUpdated = item[TABLE_LAMP_LAST_HAVE_UPDATE] book.updateInfo = updateInfo books.append(book) } return books } func updateInfo(updateInfo:UpdateInfo) { let item = TABLE_LAMP.filter(TABLE_LAMP_BOOKID == (updateInfo._id ?? "")) do { if try db.run(item.update(TABLE_LAMP_LAST_CHAPTER <- (updateInfo.lastChapter ?? ""), TABLE_LAMP_LAST_UPDATE_TIME <- (updateInfo.updated ?? ""))) > 0 { print("书籍\(String(describing: updateInfo._id)) 更新成功") } else { print("书籍\(String(describing: updateInfo._id)) 更新失败") } } catch { print("书籍\(String(describing: updateInfo._id)) 更新失败") } } func insertBookshelf(book:BookDetail) ->Bool { // 2016-08-22T12:05:34.501Z var result = false let timeInterval = Date().timeIntervalSince1970 let insert = TABLE_LAMP.insert(TABLE_LAMP_BOOKNAME <- book.title,TABLE_LAMP_BOOKID <- book._id, TABLE_LAMP_COVER <- book.cover, TABLE_LAMP_AUTHOR <- book.author, TABLE_LAMP_LAST_CHAPTER <- (book.updateInfo?.lastChapter ?? ""), TABLE_LAMP_LAST_UPDATE_TIME <- (book.updateInfo?.updated ?? ""), TABLE_LAMP_LAST_HAVE_UPDATE <- book.isUpdated,TABLE_LAMP_LAST_UPDATED <- (book.updateInfo?.updated ?? ""),TABLE_LAMP_LAST_CHAPTER_COUNT <- (Int64(book.chaptersInfo?.count ?? 0)),TABLE_LAMP_LAST_VISIT_TIME <- timeInterval,TABLE_LAMP_REFERENCE_SOURCE <- book.record?.source?.name ?? "",TABLE_LAMP_TOPIC_COUNT <- 0) do { let rowID = try db.run(insert) print("插入数据成功:\(rowID)") result = true } catch { print("插入数据失败\(error)") } return result } } ================================================ FILE: zhuishushenqi/App/ZSEncryptorAESUtils.swift ================================================ // // ZSEncryptorAESUtils.swift // zhuishushenqi // // Created by yung on 2018/11/9. // Copyright © 2018年 QS. All rights reserved. // import UIKit class ZSEncryptorAESUtils { func getDecryptedStr(key:String, cipherText:String) { let keyData = Data(base64Encoded: key, options: Data.Base64DecodingOptions(rawValue: 0)) let data = Data(base64Encoded: cipherText, options: Data.Base64DecodingOptions(rawValue: 0)) } } ================================================ FILE: zhuishushenqi/App/ZSJSON.swift ================================================ // // ZSJSON.swift // zhuishushenqi // // Created by caony on 2018/11/20. // Copyright © 2018 QS. All rights reserved. // import UIKit protocol ZSJSON { func toJSONModel() -> Any? } extension ZSJSON { func toJSONModel() -> Any? { let mirror = Mirror(reflecting: self) if mirror.children.count > 0 { var result : [String:Any] = [:] for children in mirror.children { let propertyNameString = children.label! let value = children.value if let jsonValue = value as? ZSJSON { result[propertyNameString] = jsonValue.toJSONModel() } } return result } return self } } extension Optional : ZSJSON { func toJSONModel() -> Any? { if let x = self { if let value = x as? ZSJSON { return value.toJSONModel() } } return nil } } extension String : ZSJSON {} extension Int : ZSJSON {} extension Bool : ZSJSON {} extension CGFloat : ZSJSON {} extension Float : ZSJSON {} extension Double : ZSJSON {} extension Dictionary : ZSJSON { func encode(obj:Any) -> Any? { let mirror = Mirror(reflecting: obj) return nil } } extension Array : ZSJSON {} ================================================ FILE: zhuishushenqi/App/ZSLogin.swift ================================================ // // ZSLogin.swift // zhuishushenqi // // Created by yung on 2018/10/20. // Copyright © 2018年 QS. All rights reserved. // import UIKit import ZSAPI class ZSLogin { var token:String = "" var lastLoginType = ZSThirdLoginStorage.share.lastLoginType var mobileLogin:Bool = false // 登录用户的关注与被关注者 var followings:[ZSFollowings]? var followers:[ZSFollowings]? static let share = ZSLogin() private init() { if let token = ZSThirdLogin.share.userInfo?.token { self.token = token } else if let token = ZSMobileLogin.share.userInfo?.token { self.mobileLogin = true self.token = token } if self.token.count > 0 { fetchFollowings() fetchFollowers() } } func hasLogin() ->Bool { if token != "" { return true } return false } func userInfo() ->ZSQQLoginResponse? { if let userInfo = ZSThirdLogin.share.userInfo { return userInfo } else if let userInfo = ZSMobileLogin.share.userInfo { return userInfo } return nil } func logout() { self.token = "" ZSThirdLoginStorage.share.resetLocalUserInfo() ZSThirdLogin.share.userInfo = nil ZSThirdLogin.share.wxTokenResp = nil } func login() { } func fetchFollowings() { let api = ZSAPI.userFollowings("\(self.userInfo()?.user?._id ?? "")") zs_get(api.path) { (json) in guard let followings = json?["followings"] as? [Any] else { return } if let followingModels = [ZSFollowings].deserialize(from: followings) as? [ZSFollowings] { self.followings = followingModels } } } func fetchFollowers() { let api = ZSAPI.userFollowers("\(self.userInfo()?.user?._id ?? "")") zs_get(api.path) { (json) in guard let followers = json?["followers"] as? [Any] else { return } if let followingModels = [ZSFollowings].deserialize(from: followers) as? [ZSFollowings] { self.followers = followingModels } } } } ================================================ FILE: zhuishushenqi/App/ZSMobileLogin.swift ================================================ // // ZSMobileLogin.swift // zhuishushenqi // // Created by yung on 2018/10/24. // Copyright © 2018年 QS. All rights reserved. // import UIKit class ZSMobileLogin { let tencentCaptChaAppID = "2061491951" var mobile:String = "" var userInfo:ZSQQLoginResponse? let MobileLoginQQUserFilePathKey = "MobileLoginQQUserFilePathKey" let MobileLoginLastLoginTypeKey = "MobileLoginLastLoginTypeKey" static let share = ZSMobileLogin() private init() { let user = self.localUserInfo() self.userInfo = user } func startVerifyHTML() ->String { let sdkOpts = ["sdkOpts":["height":140,"width":140]] let data = try! JSONSerialization.data(withJSONObject: sdkOpts, options: .prettyPrinted) if let string = String(data: data, encoding: .utf8) { if let percentString = string.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed) { let radom = self.getRandomSeq() let jsString = "
" return jsString } } return "" } func fetchSMSCode() { } func getRandomSeq() ->String { let v2 = arc4random() var v3:CLongLong = 0 if v2 != 0 { v3 = CLongLong(v2) } else { v3 = 1 } let v4 = Date() let v6 = v4.timeIntervalSince1970 let v7 = v6 * 1000.0 let result = String(format: "%llu_%ld", v7,v3) return result } func saveUserInfo(userInfo:ZSQQLoginResponse) { let filePath = "\(NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0])/\(MobileLoginQQUserFilePathKey.md5())" NSKeyedArchiver.archiveRootObject(userInfo, toFile: filePath) UserDefaults.standard.setValue("mobile", forKey: MobileLoginLastLoginTypeKey.md5()) UserDefaults.standard.synchronize() } func localUserInfo() -> ZSQQLoginResponse? { let filePath = "\(NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0])/\(MobileLoginQQUserFilePathKey.md5())" let user = NSKeyedUnarchiver.unarchiveObject(withFile: filePath) as? ZSQQLoginResponse return user } func resetLocalUserInfo() { let filePath = "\(NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0])/\(MobileLoginQQUserFilePathKey.md5())" try? FileManager.default.removeItem(atPath: filePath) } } ================================================ FILE: zhuishushenqi/App/ZSThirdLogin.swift ================================================ // // ZSThirdLogin.swift // zhuishushenqi // // Created by yung on 2018/10/19. // Copyright © 2018年 QS. All rights reserved. // import UIKit import HandyJSON import AdSupport import ZSAPI typealias ZSThirdLoginResultHandler = (_ success:Bool)->Void enum ThirdLoginType:String { case None = "None" case WX = "WX" case QQ = "QQ" case WB = "WB" case XM = "XM" } typealias ZSLoginSuccess = ()->Void class ZSThirdLogin: NSObject { var tencentOAuth:TencentOAuth! var webService:ZSLoginService = ZSLoginService() var userInfo:ZSQQLoginResponse? var wxTokenResp:ZSWXAccessTokenResp? var wbAuthorizeResp:WBAuthorizeResponse? var successHandler:ZSLoginSuccess? var loginResultHandler:ZSThirdLoginResultHandler? var permissions = ["get_user_info","get_simple_userinfo","add_t"] let wxAuthScope = "snsapi_userinfo,snsapi_friend,snsapi_contact,snsapi_message" static let QQAppID = "100497199" static let WXAppID = "wxaf0fdeed6872dfcf" static let WXAppSecret = "0464c67bdd87c303c5bfdc5761beb329" static let WBAppID = "2023668704" static let WBAppSecret = "26efa7a6a6bed540092c9535bda75db9" static let WBRedirectURI = "http://ushaqi.com" static let share = ZSThirdLogin() private override init() { super.init() tencentOAuth = TencentOAuth(appId: ZSThirdLogin.QQAppID, andDelegate: self) let (user,token) = ZSThirdLoginStorage.share.localUserInfo() self.userInfo = user self.wxTokenResp = token } func QQAuth() { tencentOAuth.authorize(permissions, inSafari: false) } func WXAuth() { WXApi.registerApp(ZSThirdLogin.WXAppID) WXApiRequestHandler.share.sendWXAuth(scope: wxAuthScope, state: "YouShaQi") } func WBAuth() { let request = WBAuthorizeRequest() request.redirectURI = ZSThirdLogin.WBRedirectURI request.scope = "all" WeiboSDK.send(request) } func login(type:ThirdLoginType) { switch type { case .QQ: // QQ登录 QQLogin() break case .WX: // 微信登录 WXLogin() break case .WB: // 微博登录 WBLogin() break default: break } } func refreshWXToken() { // https://api.weixin.qq.com/sns/oauth2/refresh_token?appid=APPID&grant_type=refresh_token&refresh_token=REFRESH_TOKEN // 一般第一次获取到access_token后直接登录追书d,获取token,后面基本不需要access_token了 } private func QQLogin() { let idfa = ASIdentifierManager.shared().advertisingIdentifier.uuidString let platform_code = "QQ" let platform_token = tencentOAuth.accessToken ?? "" let platform_uid = tencentOAuth.openId ?? "" let version = "2" KeyWindow?.showProgress() let loginApi:ZSAPI = ZSAPI.login(idfa: idfa, platform_code: platform_code, platform_token: platform_token, platform_uid: platform_uid, version: version ,tag: "") webService.QQLogin(url: loginApi.path, parameter: loginApi.parameters) { (json) in KeyWindow?.hideProgress() if let user = json { // 登录成功 KeyWindow?.showTip(tip: "登录成功") self.userInfo = user ZSLogin.share.token = self.userInfo?.token ?? "" ZSThirdLoginStorage.share.saveUserInfo(userInfo: user, type: .QQ) self.successHandler?() self.loginResultHandler?(true) } else { KeyWindow?.showTip(tip: "登录失败") self.userInfo = nil self.loginResultHandler?(false) } } } private func WXLogin() { if let resp = self.wxTokenResp { let idfa = ASIdentifierManager.shared().advertisingIdentifier.uuidString let platform_code = "WeixinNew" let platform_token = resp.access_token let platform_uid = resp.openid let version = "2" let tag = "zssq" KeyWindow?.showProgress() let loginApi = ZSAPI.login(idfa: idfa, platform_code: platform_code, platform_token: platform_token, platform_uid: platform_uid, version: version, tag: tag) webService.WXLogin(url: loginApi.path, parameter: loginApi.parameters) { (json) in KeyWindow?.hideProgress() if let user = json { // 登录成功 KeyWindow?.showTip(tip: "登录成功") self.userInfo = user ZSLogin.share.token = self.userInfo?.token ?? "" ZSThirdLoginStorage.share.saveUserInfo(userInfo: user, type: .WX) self.successHandler?() self.loginResultHandler?(true) } else { KeyWindow?.showTip(tip: "登录失败") self.userInfo = nil self.loginResultHandler?(false) } } } } private func WBLogin() { if let resp = self.wbAuthorizeResp { let idfa = ASIdentifierManager.shared().advertisingIdentifier.uuidString let platform_code = "SinaWeibo" let platform_token = resp.accessToken ?? "" let platform_uid = resp.userID ?? "" let version = "2" let tag = "zssq" KeyWindow?.showProgress() let loginApi = ZSAPI.login(idfa: idfa, platform_code: platform_code, platform_token: platform_token, platform_uid: platform_uid, version: version, tag: tag) webService.WBLogin(url: loginApi.path, parameter: loginApi.parameters) { (json) in KeyWindow?.hideProgress() if let user = json, let _ = json?.token { // 登录成功 KeyWindow?.showTip(tip: "登录成功") self.userInfo = user ZSLogin.share.token = self.userInfo?.token ?? "" ZSThirdLoginStorage.share.saveUserInfo(userInfo: user, type: .WX) self.successHandler?() self.loginResultHandler?(true) } else { KeyWindow?.showTip(tip: "登录失败") self.userInfo = nil self.loginResultHandler?(false) } } } } } extension ZSThirdLogin:WeiboSDKDelegate { func didReceiveWeiboRequest(_ request: WBBaseRequest!) { } func didReceiveWeiboResponse(_ response: WBBaseResponse!) { if let authorize = response as? WBAuthorizeResponse { self.wbAuthorizeResp = authorize login(type: .WB) } } } extension ZSThirdLogin:QQApiInterfaceDelegate { func onReq(_ req: QQBaseReq!) { } func onResp(_ resp: QQBaseResp!) { } func isOnlineResponse(_ response: [AnyHashable : Any]!) { } } extension ZSThirdLogin:TencentSessionDelegate { func tencentDidLogin() { self.login(type: .QQ) } func tencentDidNotLogin(_ cancelled: Bool) { if cancelled { KeyWindow?.showTip(tip: "用户取消了操作") } } func tencentDidNotNetWork() { KeyWindow?.showTip(tip: "网络连接失败") } } class WXApiRequestHandler: NSObject,WXApiDelegate { static let share = WXApiRequestHandler() private override init() {} @discardableResult func sendWXAuthRequestScope(scope:String, state:String, inViewController:UIViewController) -> Bool { let req = SendAuthReq() req.scope = scope req.state = state return WXApi.sendAuthReq(req, viewController: inViewController, delegate: self) } @discardableResult func sendWXAuth(scope:String,state:String) -> Bool { let req = SendAuthReq() req.scope = scope req.state = state return WXApi.send(req) } func onReq(_ req: BaseReq!) { } func onResp(_ resp: BaseResp!) { if let res = resp as? SendAuthResp { print(res) if res.errCode == 0 { // 获取code成功 let code = res.code let getAccessTokenUrl = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=\(ZSThirdLogin.WXAppID)&secret=\(ZSThirdLogin.WXAppSecret)&code=\(code ?? "")&grant_type=authorization_code" zs_get(getAccessTokenUrl, parameters: nil) { (json) in let tokenResp = ZSWXAccessTokenResp.deserialize(from: json) ZSThirdLoginStorage.share.saveWXToken(wxTokenResp: tokenResp) ZSThirdLogin.share.login(type: .WX) } } else { KeyWindow?.showTip(tip: "请求Code出错") } } } } class ZSThirdLoginStorage: NSObject { var rawData:[String:Any] = [:] var works:[Any] = [] var educations:[Any] = [] var level:CLongLong = 0 var regAt:Double = 0 var shareCount:CLongLong = 0 var friendCount:CLongLong = 0 var followerCount:CLongLong = 0 var birthday:Date? var verifyReason:String = "" var verifyType:CLongLong = 0 var aboutMe:String = "" var url:String = "" var gender:CLongLong = 0 var icon:String = "" var nickname:String = "" var uid:String = "" var credential:SSDKCredential? var data:SSDKQQData? var platformType:CLongLong = 0 // 只作为上次登录方式的记录,如果该登录方式的数据不存在,则应该将该值置为None var lastLoginType:ThirdLoginType = .None let ThirdLoginQQUserFilePathKey = "ThirdLoginQQUserFilePathKey" let ThirdLoginWXTokenFilePathKey = "ThirdLoginWXTokenFilePathKey" let ThirdLoginLastLoginTypeKey = "ThirdLoginLastLoginTypeKey" static let share = ZSThirdLoginStorage() private override init() { super.init() let lastTypeObj = UserDefaults.standard.string(forKey: ThirdLoginLastLoginTypeKey.md5()) self.lastLoginType = ThirdLoginType(rawValue: lastTypeObj ?? "") ?? .None } func canHandle(pasteData:[String:Any]) -> Bool { if pasteData.allKeys().count > 0 { if pasteData.allKeys()[0].contains("tencent") { return true } } return false } func handle(pasteData:[String:Data]) { for (key,value) in pasteData { if key.contains("tencent") { if let obj = NSKeyedUnarchiver.unarchiveObject(with: value) as? [String:Any] { self.rawData = obj self.data = SSDKQQData.deserialize(from: obj) print(self.rawData) } } } } func saveWXToken(wxTokenResp:ZSWXAccessTokenResp?) { if let resp = wxTokenResp { let TokenFilePath = "\(NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0])/\(ThirdLoginWXTokenFilePathKey.md5())" NSKeyedArchiver.archiveRootObject(resp, toFile: TokenFilePath) } } func localUserInfo() -> (ZSQQLoginResponse?,ZSWXAccessTokenResp?) { let filePath = "\(NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0])/\(ThirdLoginQQUserFilePathKey.md5())" let TokenFilePath = "\(NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0])/\(ThirdLoginWXTokenFilePathKey.md5())" let user = NSKeyedUnarchiver.unarchiveObject(withFile: filePath) as? ZSQQLoginResponse let token = NSKeyedUnarchiver.unarchiveObject(withFile: TokenFilePath) as? ZSWXAccessTokenResp return (user,token) } func resetLocalUserInfo() { let filePath = "\(NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0])/\(ThirdLoginQQUserFilePathKey.md5())" let TokenFilePath = "\(NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0])/\(ThirdLoginWXTokenFilePathKey.md5())" try? FileManager.default.removeItem(atPath: filePath) try? FileManager.default.removeItem(atPath: TokenFilePath) } func saveUserInfo(userInfo:ZSQQLoginResponse, type:ThirdLoginType) { let filePath = "\(NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0])/\(ThirdLoginQQUserFilePathKey.md5())" NSKeyedArchiver.archiveRootObject(userInfo, toFile: filePath) switch type { case .QQ: UserDefaults.standard.setValue(ThirdLoginType.QQ.rawValue, forKey: ThirdLoginLastLoginTypeKey.md5()) UserDefaults.standard.synchronize() break case .WX: // 要自己保存微信登录成功拿到的token信息 saveWXToken(wxTokenResp: ZSThirdLogin.share.wxTokenResp) UserDefaults.standard.setValue(ThirdLoginType.WX.rawValue, forKey: ThirdLoginLastLoginTypeKey.md5()) UserDefaults.standard.synchronize() break case .WB: break case .None: break default: break } } } class SSDKCredential { var expirationDate:Date? var openId:String = "" var available:Bool = false var rawData:[String:Any] = [:] var expired:String = "" var secret:String = "" var token:String = "" var uid:String = "" } class SSDKQQData:HandyJSON { var pf:String = "" var pfkey:String = "" var access_token:String = "" var passDataResp:[Any] = [] var pay_token:String = "" var msg:String = "" var user_cancelled:Bool = false var ret:Int = 0 var encrytoken:String = "" var expires_in:TimeInterval = 7776000 required init() {} } class ZSWXAccessTokenResp: HandyJSON, NSCoding{ var openid:String = "" var scope:String = "" var expires_in:Float = 0 var access_token:String = "" var unionid:String = "" var refresh_token:String = "" required init() {} required init?(coder aDecoder: NSCoder) { aDecoder.decodeObject(forKey: "openid") aDecoder.decodeObject(forKey: "scope") aDecoder.decodeObject(forKey: "expires_in") aDecoder.decodeObject(forKey: "access_token") aDecoder.decodeObject(forKey: "unionid") aDecoder.decodeObject(forKey: "refresh_token") } func encode(with aCoder: NSCoder) { aCoder.encode(self.openid, forKey: "openid") aCoder.encode(self.scope, forKey: "scope") aCoder.encode(self.expires_in, forKey: "expires_in") aCoder.encode(self.access_token, forKey: "access_token") aCoder.encode(self.unionid, forKey: "unionid") aCoder.encode(self.refresh_token, forKey: "refresh_token") } } ================================================ FILE: zhuishushenqi/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "idiom" : "iphone", "size" : "20x20", "scale" : "2x" }, { "idiom" : "iphone", "size" : "20x20", "scale" : "3x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "AppIcon29x29@2x.png", "scale" : "2x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "AppIcon29x29@3x.png", "scale" : "3x" }, { "size" : "40x40", "idiom" : "iphone", "filename" : "AppIcon40x40@2x.png", "scale" : "2x" }, { "size" : "40x40", "idiom" : "iphone", "filename" : "AppIcon40x40@3x.png", "scale" : "3x" }, { "size" : "60x60", "idiom" : "iphone", "filename" : "AppIcon60x60@2x.png", "scale" : "2x" }, { "size" : "60x60", "idiom" : "iphone", "filename" : "AppIcon60x60@3x.png", "scale" : "3x" }, { "idiom" : "ipad", "size" : "20x20", "scale" : "1x" }, { "idiom" : "ipad", "size" : "20x20", "scale" : "2x" }, { "size" : "29x29", "idiom" : "ipad", "filename" : "AppIcon29x29~ipad.png", "scale" : "1x" }, { "size" : "29x29", "idiom" : "ipad", "filename" : "AppIcon29x29@2x~ipad.png", "scale" : "2x" }, { "size" : "40x40", "idiom" : "ipad", "filename" : "AppIcon40x40~ipad.png", "scale" : "1x" }, { "size" : "40x40", "idiom" : "ipad", "filename" : "AppIcon40x40@2x~ipad.png", "scale" : "2x" }, { "size" : "76x76", "idiom" : "ipad", "filename" : "AppIcon76x76~ipad.png", "scale" : "1x" }, { "size" : "76x76", "idiom" : "ipad", "filename" : "AppIcon76x76@2x~ipad.png", "scale" : "2x" }, { "size" : "83.5x83.5", "idiom" : "ipad", "filename" : "AppIcon83.5x83.5@2x~ipad.png", "scale" : "2x" }, { "size" : "1024x1024", "idiom" : "ios-marketing", "filename" : "AppIcon1024x1024.png", "scale" : "1x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/Contents.json ================================================ { "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/Default-640x1136.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "Default-640x1136@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/LaunchImage.launchimage/Contents.json ================================================ { "images" : [ { "extent" : "full-screen", "idiom" : "iphone", "subtype" : "2688h", "filename" : "Brand Assets-800-Portrait-812h@3x-2.png", "minimum-system-version" : "12.0", "orientation" : "portrait", "scale" : "3x" }, { "extent" : "full-screen", "idiom" : "iphone", "subtype" : "1792h", "filename" : "Brand Assets-800-Portrait-812h@3x-1.png", "minimum-system-version" : "12.0", "orientation" : "portrait", "scale" : "2x" }, { "extent" : "full-screen", "idiom" : "iphone", "subtype" : "2436h", "filename" : "Brand Assets-800-Portrait-812h@3x.png", "minimum-system-version" : "11.0", "orientation" : "portrait", "scale" : "3x" }, { "extent" : "full-screen", "idiom" : "iphone", "subtype" : "736h", "filename" : "Brand Assets-800-Portrait-736h@3x.png", "minimum-system-version" : "8.0", "orientation" : "portrait", "scale" : "3x" }, { "extent" : "full-screen", "idiom" : "iphone", "subtype" : "667h", "filename" : "Brand Assets-800-667h@2x.png", "minimum-system-version" : "8.0", "orientation" : "portrait", "scale" : "2x" }, { "orientation" : "portrait", "idiom" : "iphone", "filename" : "Brand Assets-700@2x.png", "extent" : "full-screen", "minimum-system-version" : "7.0", "scale" : "2x" }, { "extent" : "full-screen", "idiom" : "iphone", "subtype" : "retina4", "filename" : "Default-640x1136@2x.png", "minimum-system-version" : "7.0", "orientation" : "portrait", "scale" : "2x" }, { "orientation" : "portrait", "idiom" : "ipad", "filename" : "Brand Assets-700-Portrait~ipad.png", "extent" : "full-screen", "minimum-system-version" : "7.0", "scale" : "1x" }, { "orientation" : "portrait", "idiom" : "ipad", "filename" : "Brand Assets-700-Portrait@2x~ipad.png", "extent" : "full-screen", "minimum-system-version" : "7.0", "scale" : "2x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/New Version/Contents.json ================================================ { "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/New Version/QDReaderSetting_brightnessDown_24x24_.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "QDReaderSetting_brightnessDown_24x24_@1x.png", "scale" : "1x" }, { "idiom" : "universal", "filename" : "QDReaderSetting_brightnessDown_24x24_@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "QDReaderSetting_brightnessDown_24x24_@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/New Version/QDReaderSetting_brightnessUp_24x24_.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "QDReaderSetting_brightnessUp_24x24_@1x.png", "scale" : "1x" }, { "idiom" : "universal", "filename" : "QDReaderSetting_brightnessUp_24x24_@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "QDReaderSetting_brightnessUp_24x24_@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/New Version/bbs_icon_like02_26_26.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "bbs_icon_like02_26_26@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "bbs_icon_like02_26_26@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/New Version/bbs_icon_message_20_20.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "bbs_icon_message_20_20@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "bbs_icon_message_20_20@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/New Version/bbs_icon_message_34_34_34x34_.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "bbs_icon_message_34_34_34x34_@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "bbs_icon_message_34_34_34x34_@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/New Version/bbs_icon_more_big_26_26.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "bbs_icon_more_big_26_26@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "bbs_icon_more_big_26_26@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/New Version/bbs_icon_personal_34_34_34x34_.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "bbs_icon_personal_34_34_34x34_@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "bbs_icon_personal_34_34_34x34_@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/New Version/bbs_icon_share_26_26_26x26_.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "bbs_icon_share_26_26_26x26_@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "bbs_icon_share_26_26_26x26_@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/New Version/bbs_icon_topic_26_26_26x26_.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "bbs_icon_topic_26_26_26x26_@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "bbs_icon_topic_26_26_26x26_@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/New Version/blackboard_bg_375x667_.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "blackboard_bg_375x667_@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "blackboard_bg_375x667_@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/New Version/bookshelf_bg_logo_80_19_80x19_.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "bookshelf_bg_logo_80_19_80x19_@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "bookshelf_bg_logo_80_19_80x19_@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/New Version/bookshelf_icon_gift_34_34.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "bookshelf_icon_gift_34_34@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "bookshelf_icon_gift_34_34@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/New Version/bookshelf_icon_gift_new_34_34.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "bookshelf_icon_gift_new_34_34@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "bookshelf_icon_gift_new_34_34@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/New Version/bookshelf_icon_history_34_34.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "bookshelf_icon_history_34_34@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "bookshelf_icon_history_34_34@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/New Version/bookshelf_icon_more_10_10_10x10_.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "bookshelf_icon_more_10_10_10x10_@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "bookshelf_icon_more_10_10_10x10_@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/New Version/bookshelf_icon_more_34_34.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "bookshelf_icon_more_34_34@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "bookshelf_icon_more_34_34@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/New Version/bookshelf_icon_more_wifi_24_24.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "bookshelf_icon_more_wifi_24_24@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "bookshelf_icon_more_wifi_24_24@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/New Version/bookshelf_icon_seach_34_34.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "bookshelf_icon_seach_34_34@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "bookshelf_icon_seach_34_34@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/New Version/bookshelf_top_bg_one_line.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "bookshelf_top_bg_one_line@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "bookshelf_top_bg_one_line@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/New Version/bookshelf_top_bg_two_line.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "bookshelf_top_bg_two_line@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "bookshelf_top_bg_two_line@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/New Version/bookstore_bg.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "bookstore_bg@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "bookstore_bg@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/New Version/bookstore_def_classify_20_20_20x20_.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "bookstore_def_classify_20_20_20x20_@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "bookstore_def_classify_20_20_20x20_@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/New Version/chapter_review_send_tip_24x24_.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "chapter_review_send_tip_24x24_@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "chapter_review_send_tip_24x24_@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/New Version/community_noReview_sofa_icon.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "community_noReview_sofa_icon@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "community_noReview_sofa_icon@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/New Version/community_star_p_32x32_.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "community_star_p_32x32_@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "community_star_p_32x32_@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/New Version/details_icon_star_n_32_32_32x32_.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "details_icon_star_n_32_32_32x32_@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "details_icon_star_n_32_32_32x32_@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/New Version/discover_Ranking_109x50_.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "discover_Ranking_109x50_@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "discover_Ranking_109x50_@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/New Version/discover_booklist_108x50_.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "discover_booklist_108x50_@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "discover_booklist_108x50_@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/New Version/discover_classify_108x50_.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "discover_classify_108x50_@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "discover_classify_108x50_@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/New Version/discover_icon_audiobook_26_26_26x26_.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "discover_icon_audiobook_26_26_26x26_@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "discover_icon_audiobook_26_26_26x26_@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/New Version/discover_icon_comics_26_26_26x26_.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "discover_icon_comics_26_26_26x26_@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "discover_icon_comics_26_26_26x26_@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/New Version/discover_icon_exclusive_26_26_26x26_.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "discover_icon_exclusive_26_26_26x26_@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "discover_icon_exclusive_26_26_26x26_@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/New Version/discover_icon_free_26_26_26x26_.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "discover_icon_free_26_26_26x26_@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "discover_icon_free_26_26_26x26_@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/New Version/discover_icon_random_26_26_26x26_.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "discover_icon_random_26_26_26x26_@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "discover_icon_random_26_26_26x26_@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/New Version/discover_icon_timing_24_24_24x24_.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "filename" : "discover_icon_timing_24_24_24x24_@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/New Version/discover_icon_vip_26_26_26x26_.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "discover_icon_vip_26_26_26x26_@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "discover_icon_vip_26_26_26x26_@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/New Version/f_author_icon_14x14_.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "f_author_icon_14x14_@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "f_author_icon_14x14_@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/New Version/f_commentator_icon_14x14_.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "f_commentator_icon_14x14_@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "f_commentator_icon_14x14_@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/New Version/f_doyen_icon_12x12_.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "f_doyen_icon_12x12_@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "f_doyen_icon_12x12_@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/New Version/f_moderator_icon_14x14_.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "f_moderator_icon_14x14_@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "f_moderator_icon_14x14_@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/New Version/f_official_icon_12x12_.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "f_official_icon_12x12_@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "f_official_icon_12x12_@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/New Version/forum_image_15x13_.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "forum_image_15x13_@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "forum_image_15x13_@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/New Version/icon_delete_1_24x24_.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "icon_delete_1_24x24_@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "icon_delete_1_24x24_@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/New Version/icon_download_a_24x24_.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "icon_download_a_24x24_@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "icon_download_a_24x24_@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/New Version/icon_night_30x30_.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "icon_night_30x30_@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/New Version/icon_tools_menu_30x30_.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "icon_tools_menu_30x30_@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/New Version/icon_tools_setting_30x30_.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "icon_tools_setting_30x30_@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/New Version/icon_top_selected_24x24_.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "icon_top_selected_24x24_@1x.png", "scale" : "1x" }, { "idiom" : "universal", "filename" : "icon_top_selected_24x24_@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "icon_top_selected_24x24_@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/New Version/mj_refresh.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "mj_refresh@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/New Version/nav_goldCoin_34_34_24x24_.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "nav_goldCoin_34_34_24x24_@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "nav_goldCoin_34_34_24x24_@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/New Version/no_network_140x128_.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "no_network_140x128_@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "no_network_140x128_@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/New Version/notification_personal_icon_message_24_24_24x24_.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "notification_personal_icon_message_24_24_24x24_@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "notification_personal_icon_message_24_24_24x24_@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/New Version/official_icon_30x30_.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "official_icon_30x30_@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "official_icon_30x30_@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/New Version/peach_reader_def_xuance2_32_32_32x32_.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "peach_reader_def_xuance2_32_32_32x32_@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "peach_reader_def_xuance2_32_32_32x32_@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/New Version/personal_icon_account_24_24_24x24_.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "personal_icon_account_24_24_24x24_@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "personal_icon_account_24_24_24x24_@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/New Version/personal_icon_booklist_24_24_24x24_.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "personal_icon_booklist_24_24_24x24_@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "personal_icon_booklist_24_24_24x24_@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/New Version/personal_icon_darkmode_24_24_23x23_.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "personal_icon_darkmode_24_24_23x23_@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "personal_icon_darkmode_24_24_23x23_@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/New Version/personal_icon_history_24_24_24x24_.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "personal_icon_history_24_24_24x24_@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "personal_icon_history_24_24_24x24_@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/New Version/personal_icon_huntbook_24_24_24x24_.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "personal_icon_huntbook_24_24_24x24_@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "personal_icon_huntbook_24_24_24x24_@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/New Version/personal_icon_id_24_24_24x24_.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "personal_icon_id_24_24_24x24_@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "personal_icon_id_24_24_24x24_@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/New Version/personal_icon_level _24_24_24x24_.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "personal_icon_level _24_24_24x24_@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "personal_icon_level _24_24_24x24_@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/New Version/personal_icon_level3 _24_24_24x24_.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "personal_icon_level3 _24_24_24x24_@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "personal_icon_level3 _24_24_24x24_@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/New Version/personal_icon_opinion_24_24_24x24_.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "personal_icon_opinion_24_24_24x24_@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "personal_icon_opinion_24_24_24x24_@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/New Version/personal_icon_reply_14_14_14x14_.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "personal_icon_reply_14_14_14x14_@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "personal_icon_reply_14_14_14x14_@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/New Version/personal_icon_setting_24_24_24x24_.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "personal_icon_setting_24_24_24x24_@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "personal_icon_setting_24_24_24x24_@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/New Version/personal_icon_topic_14_14_14x14_.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "personal_icon_topic_14_14_14x14_@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "personal_icon_topic_14_14_14x14_@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/New Version/personal_icon_vip_24_24_24x24_.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "personal_icon_vip_24_24_24x24_@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "personal_icon_vip_24_24_24x24_@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/New Version/personal_icon_warn_16_16_10x10_.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "personal_icon_warn_16_16_10x10_@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "personal_icon_warn_16_16_10x10_@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/New Version/personal_icon_zslike_24_24_24x24_.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "personal_icon_zslike_24_24_24x24_@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "personal_icon_zslike_24_24_24x24_@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/New Version/pic_day_46x46_.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "pic_day_46x46_@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/New Version/pic_night_46x46_.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "pic_night_46x46_@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/New Version/profile_personal_icon_message_24_24_24x24_.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "profile_personal_icon_message_24_24_24x24_@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "profile_personal_icon_message_24_24_24x24_@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/New Version/slice_bg_515_88x32_.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "filename" : "slice_bg_515_88x32_@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/New Version/slice_bg_barbie_88x32_.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "slice_bg_barbie_88x32_@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "slice_bg_barbie_88x32_@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/New Version/slice_bg_jade_88x32_.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "slice_bg_jade_88x32_@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "slice_bg_jade_88x32_@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/New Version/slice_bg_linen_88x32_.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "slice_bg_linen_88x32_@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "slice_bg_linen_88x32_@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/New Version/slice_bg_night_88x32_.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "slice_bg_night_88x32_@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "slice_bg_night_88x32_@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/New Version/slice_bg_paper_88x32_.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "slice_bg_paper_88x32_@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "slice_bg_paper_88x32_@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/New Version/slice_bg_white_88x32_.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "slice_bg_white_88x32_@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "slice_bg_white_88x32_@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/New Version/slice_bg_wood_88x32_.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "slice_bg_wood_88x32_@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "slice_bg_wood_88x32_@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/New Version/slice_bg_yexiu_88x32_.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "filename" : "slice_bg_yexiu_88x32_@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/New Version/source_checkbox_normal.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "source_checkbox_normal@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "source_checkbox_normal@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/New Version/source_checkbox_selected.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "source_checkbox_selected@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "source_checkbox_selected@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/New Version/source_delete.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "source_delete@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "source_delete@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/New Version/tab_bbs.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "tab_bbs@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "tab_bbs@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/New Version/tab_bbs_sel.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "tab_bbs_sel@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "tab_bbs_sel@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/New Version/tab_bookshelf.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "tab_bookshelf@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "tab_bookshelf@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/New Version/tab_bookshelf_sel.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "tab_bookshelf_sel@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "tab_bookshelf_sel@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/New Version/tab_bookstore.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "tab_bookstore@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "tab_bookstore@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/New Version/tab_bookstore_sel.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "tab_bookstore_sel@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "tab_bookstore_sel@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/New Version/tab_discover.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "tab_discover@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "tab_discover@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/New Version/tab_discover_sel.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "tab_discover_sel@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "tab_discover_sel@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/New Version/tab_profile.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "tab_profile@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "tab_profile@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/New Version/tab_profile_sel.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "tab_profile_sel@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "tab_profile_sel@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/bg_back_white.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "bg_back_white.png", "scale" : "1x" }, { "idiom" : "universal", "filename" : "bg_back_white@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/catelog.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "catelog.png", "scale" : "1x" }, { "idiom" : "universal", "filename" : "catelog@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/fzhongjun.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "fzhongjun@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "fzhongjun@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/fzmiaowu.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "fzmiaowu@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "fzmiaowu@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/fzqingke.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "fzqingke@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "fzqingke@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/fzshusong.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "fzshusong@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "fzshusong@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/fzsuxinshil.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "fzsuxinshil@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "fzsuxinshil@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/fzxijinling.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "fzxijinling@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "fzxijinling@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/fzyouhei.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "fzyouhei@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "fzyouhei@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/kai.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "kai@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "kai@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/lantingh.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "lantingh@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "lantingh@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/leftbar.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "leftbar.png", "scale" : "1x" }, { "idiom" : "universal", "filename" : "leftbar@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/li.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "li@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "li@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/loadside.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "loadside@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/longwangchuanshuo.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "longwangchuanshuo.jpg", "scale" : "1x" }, { "idiom" : "universal", "filename" : "longwangchuanshuo-1.jpg", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/navigationbar-sidebar.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "navigationbar-sidebar.png", "scale" : "1x" }, { "idiom" : "universal", "filename" : "navigationbar-sidebar@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "navigationbar-sidebar@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/pianpian.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "pianpian@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "pianpian@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/rightbar.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "rightbar.png", "scale" : "1x" }, { "idiom" : "universal", "filename" : "rightbar@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/riwen.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "riwen@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "riwen@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/sure_placeholder_error.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "sure_placeholder_error@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "sure_placeholder_error@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/weibei.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "weibei@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "weibei@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/yapi.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "yapi@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "yapi@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/AppIcon29x29.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "AppIcon29x29@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/AppIcon40x40.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "AppIcon40x40@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/AppIcon57x57.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "AppIcon57x57.png", "scale" : "1x" }, { "idiom" : "universal", "filename" : "AppIcon57x57@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/AppIcon60x60.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "AppIcon60x60@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "AppIcon60x60@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/AppIcon72x72.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "ipad", "filename" : "AppIcon72x72~ipad.png", "scale" : "1x" }, { "idiom" : "ipad", "filename" : "AppIcon72x72@2x~ipad.png", "scale" : "2x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/AppIcon76x76.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "ipad", "filename" : "AppIcon76x76~ipad.png", "scale" : "1x" }, { "idiom" : "ipad", "filename" : "AppIcon76x76@2x~ipad.png", "scale" : "2x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/Apprecommend.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "Apprecommend.jpg", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/Contents.json ================================================ { "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/Default-1242x2208.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "filename" : "Default-1242x2208@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/Default-750x1334.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "Default-750x1334@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/Default-iPad.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "Default-iPad@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/Default.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "Default@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/IQButtonBarArrowDown.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "IQButtonBarArrowDown@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "IQButtonBarArrowDown@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/IQButtonBarArrowUp.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "IQButtonBarArrowUp@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "IQButtonBarArrowUp@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/Icon.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "Icon.png", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/Icon_114.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "Icon_114@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/Icon_120.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "Icon_120@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/Icon_58.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "Icon_58@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/Icon_80.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "Icon_80@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/NavBack.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "NavBack.png", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/NavForward.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "NavForward.png", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/NavHome.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "NavHome.png", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/NavRefresh.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "NavRefresh.png", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/a_charge_bg.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "a_charge_bg@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "a_charge_bg@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/a_charge_icon.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "a_charge_icon@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "a_charge_icon@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/a_charge_record.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "a_charge_record@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "a_charge_record@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/a_charge_record_new.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "a_charge_record_new@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/a_exchange.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "a_exchange@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "a_exchange@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/a_exchange_new.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "a_exchange_new@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/a_noadvs.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "a_noadvs@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "a_noadvs@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/a_purchase_record.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "a_purchase_record@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "a_purchase_record@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/a_purchase_record_new.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "a_purchase_record_new@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/a_zhuishubi.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "a_zhuishubi@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "a_zhuishubi@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/a_zhuishubi_new.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "a_zhuishubi_new@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/a_zhuishuquan.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "a_zhuishuquan@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "a_zhuishuquan@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/a_zhuishuquan_new.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "a_zhuishuquan_new@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/actionbar_back.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "actionbar_back.png", "scale" : "1x" }, { "idiom" : "universal", "filename" : "actionbar_back@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/actionbar_close.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "actionbar_close.png", "scale" : "1x" }, { "idiom" : "universal", "filename" : "actionbar_close@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/actionbar_forward.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "actionbar_forward.png", "scale" : "1x" }, { "idiom" : "universal", "filename" : "actionbar_forward@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/actionbar_refresh.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "actionbar_refresh.png", "scale" : "1x" }, { "idiom" : "universal", "filename" : "actionbar_refresh@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/activity_image.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "activity_image@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "activity_image@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/add_book_hint.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "add_book_hint@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/add_success.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "add_success@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "add_success@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/adwall_blue_pic.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "adwall_blue_pic.png", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/adwall_blue_text.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "adwall_blue_text.png", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/adwall_white_pic.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "adwall_white_pic.png", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/adwall_white_text.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "adwall_white_text.png", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/alert_error_icon.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "alert_error_icon@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/alert_success_icon.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "alert_success_icon@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/app_image.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "app_image@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "app_image@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/appointActivity_bg.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "appointActivity_bg.png", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/appointActivity_close.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "appointActivity_close.png", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/ar_shadow.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "ar_shadow@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "ar_shadow@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/arrow.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "arrow@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/artboard.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "artboard@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "artboard@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/autoreading_start.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "autoreading_start@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "autoreading_start@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/autoreading_start_landscape.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "autoreading_start_landscape@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "autoreading_start_landscape@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/avatar_wrapper.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "avatar_wrapper@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/background_green.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "background_green@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "background_green@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/background_green_selected.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "background_green_selected@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "background_green_selected@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/background_white.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "background_white@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "background_white@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/background_white_selected.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "background_white_selected@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "background_white_selected@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/background_yellow.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "background_yellow@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "background_yellow@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/background_yellow_selected.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "background_yellow_selected@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "background_yellow_selected@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/baidu_logo.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "baidu_logo.png", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/baidubutton.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "baidubutton.png", "scale" : "1x" }, { "idiom" : "universal", "filename" : "baidubutton@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/bd_add.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "bd_add@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "bd_add@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/bd_add_count.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "bd_add_count@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "bd_add_count@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/bd_add_selected.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "bd_add_selected@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "bd_add_selected@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/bd_arrow_down.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "bd_arrow_down@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "bd_arrow_down@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/bd_arrow_right.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "bd_arrow_right@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "bd_arrow_right@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/bd_cancel.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "bd_cancel@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "bd_cancel@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/bd_cancel_selected.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "bd_cancel_selected@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "bd_cancel_selected@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/bd_mark.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "bd_mark@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "bd_mark@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/bd_retention_ratio.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "bd_retention_ratio@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "bd_retention_ratio@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/bd_search.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "bd_search@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "bd_search@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/bd_search_selected.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "bd_search_selected@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "bd_search_selected@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/bd_share.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "bd_share@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "bd_share@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/bd_share_red.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "bd_share_red@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "bd_share_red@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/bd_share_white.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "bd_share_white@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "bd_share_white@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/bd_star_empty.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "bd_star_empty@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "bd_star_empty@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/bd_star_filled.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "bd_star_filled@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "bd_star_filled@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/bd_useful.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "bd_useful@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "bd_useful@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/beijing.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "beijing@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "beijing@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/blackGreen_mode_bg.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "blackGreen_mode_bg@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/bofang.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "bofang@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "bofang@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/bofangNotify.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "bofangNotify@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "bofangNotify@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/bookDirectory_selected.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "bookDirectory_selected@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "bookDirectory_selected@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/bookDirectory_separator.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "bookDirectory_separator@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/bookShelf_check.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "bookShelf_check@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "bookShelf_check@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/bookShelf_uncheck.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "bookShelf_uncheck@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "bookShelf_uncheck@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/booklist_delete.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "booklist_delete@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "booklist_delete@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/brightess_high.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "brightess_high@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "brightess_high@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/brightess_low.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "brightess_low@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "brightess_low@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/brightess_white_high.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "brightess_white_high@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "brightess_white_high@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/brightess_white_low.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "brightess_white_low@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "brightess_white_low@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/bs_delete.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "bs_delete@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "bs_delete@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/bs_delete_selected.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "bs_delete_selected@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "bs_delete_selected@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/bs_download.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "bs_download@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "bs_download@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/bs_download_cancel.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "bs_download_cancel@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "bs_download_cancel@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/bs_download_finished.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "bs_download_finished@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "bs_download_finished@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/bs_download_waiting.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "bs_download_waiting@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "bs_download_waiting@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/bs_downloading.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "bs_downloading@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "bs_downloading@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/bs_feed.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "bs_feed@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "bs_feed@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/bs_feed_icon.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "bs_feed_icon@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "bs_feed_icon@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/bs_feed_selected.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "bs_feed_selected@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "bs_feed_selected@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/bs_feed_tip.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "bs_feed_tip@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "bs_feed_tip@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/bs_feeded.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "bs_feeded@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "bs_feeded@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/bs_last_read.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "bs_last_read@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "bs_last_read@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/bs_last_read_selected.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "bs_last_read_selected@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "bs_last_read_selected@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/bs_stick.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "bs_stick@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "bs_stick@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/bs_stick_cancel.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "bs_stick_cancel@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "bs_stick_cancel@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/bs_stick_icon.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "bs_stick_icon@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "bs_stick_icon@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/bs_stick_selected.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "bs_stick_selected@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "bs_stick_selected@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/btn_slider_off.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "btn_slider_off@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/btn_slider_on.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "btn_slider_on@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/btn_slider_thumb.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "btn_slider_thumb@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/btn_slider_thumb_pressed.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "btn_slider_thumb_pressed@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/btn_slider_track.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "btn_slider_track@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/btn_slider_track_pressed.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "btn_slider_track_pressed@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/c_arrow_down.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "c_arrow_down@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "c_arrow_down@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/c_check.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "c_check@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "c_check@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/cell_selected_background.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "cell_selected_background@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/cell_selected_tip.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "cell_selected_tip@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "cell_selected_tip@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/change_mode_checked.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "change_mode_checked@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "change_mode_checked@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/change_mode_unchecked.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "change_mode_unchecked@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "change_mode_unchecked@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/change_resource.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "change_resource@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "change_resource@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/close.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "close.png", "scale" : "1x" }, { "idiom" : "universal", "filename" : "close@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/coffee_mode_bg.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "coffee_mode_bg@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/common_button_big_blue.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "common_button_big_blue@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/common_button_big_blue_disable.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "common_button_big_blue_disable@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/common_button_big_blue_highlighted.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "common_button_big_blue_highlighted@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/common_button_white.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "common_button_white.png", "scale" : "1x" }, { "idiom" : "universal", "filename" : "common_button_white@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/common_button_white_highlighted.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "common_button_white_highlighted.png", "scale" : "1x" }, { "idiom" : "universal", "filename" : "common_button_white_highlighted@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/common_icon_arrow.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "common_icon_arrow@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/compose_keyboardbutton_background.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "compose_keyboardbutton_background.png", "scale" : "1x" }, { "idiom" : "universal", "filename" : "compose_keyboardbutton_background@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/compose_toolbar_background.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "compose_toolbar_background.png", "scale" : "1x" }, { "idiom" : "universal", "filename" : "compose_toolbar_background@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/cover_wrapper.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "cover_wrapper@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/d_comment.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "d_comment@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "d_comment@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/d_default.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "d_default@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "d_default@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/d_delete.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "d_delete@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "d_delete@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/d_follow.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "d_follow@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "d_follow@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/d_followed.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "d_followed@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "d_followed@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/d_forward.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "d_forward@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "d_forward@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/d_forward_large.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "d_forward_large@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "d_forward_large@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/d_forwarded_large.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "d_forwarded_large@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "d_forwarded_large@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/d_icon.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "d_icon@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "d_icon@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/d_votes.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "d_votes@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "d_votes@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/day_mode.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "day_mode@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "day_mode@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/day_mode_bg.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "day_mode_bg@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "day_mode_bg@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/default_avatar_deep.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "default_avatar_deep@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/default_avatar_light.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "default_avatar_light@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/default_book_cover.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "default_book_cover@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "default_book_cover@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/delete_book_tip.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "delete_book_tip@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "delete_book_tip@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/dingshi.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "dingshi@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "dingshi@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/directory.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "directory@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "directory@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/directory_close.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "directory_close@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "directory_close@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/directory_close_pressed.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "directory_close_pressed@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "directory_close_pressed@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/directory_not_previewed.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "directory_not_previewed@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "directory_not_previewed@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/directory_previewed.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "directory_previewed@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "directory_previewed@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/directory_vip_chapter.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "directory_vip_chapter@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "directory_vip_chapter@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/discover_icon_back_24_24.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "discover_icon_back_24_24@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "discover_icon_back_24_24@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/discover_icon_bookshelf_24_24.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "discover_icon_bookshelf_24_24@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "discover_icon_bookshelf_24_24@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/discover_icon_bookshelf_24_24_p.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "discover_icon_bookshelf_24_24_p@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "discover_icon_bookshelf_24_24_p@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/discover_icon_more_24_24.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "discover_icon_more_24_24@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "discover_icon_more_24_24@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/discover_icon_next_36_36.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "discover_icon_next_36_36@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "discover_icon_next_36_36@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/discover_icon_palying_20_14.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "discover_icon_palying_20_14@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "discover_icon_palying_20_14@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/discover_icon_play_76_76.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "discover_icon_play_76_76@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "discover_icon_play_76_76@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/discover_icon_pouse_76_76.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "discover_icon_pouse_76_76@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "discover_icon_pouse_76_76@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/discover_icon_timing_24_24.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "discover_icon_timing_24_24@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "discover_icon_timing_24_24@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/discover_icon_up_36_36.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "discover_icon_up_36_36@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "discover_icon_up_36_36@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/draft_defaultImage.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "draft_defaultImage@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "draft_defaultImage@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/empty_failed.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "empty_failed.png", "scale" : "1x" }, { "idiom" : "universal", "filename" : "empty_failed@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/enter_icon.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "enter_icon@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/erciyuanv3.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "erciyuanv3@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "erciyuanv3@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/error.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "error.png", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/exchange_bg.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "exchange_bg@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "exchange_bg@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/exchange_center.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "exchange_center@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "exchange_center@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/exchange_duihuan.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "exchange_duihuan@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "exchange_duihuan@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/exchange_jiantou.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "exchange_jiantou@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "exchange_jiantou@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/exchange_jinru.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "exchange_jinru@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "exchange_jinru@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/exchange_jinru_h.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "exchange_jinru_h@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "exchange_jinru_h@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/exchange_kuang.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "exchange_kuang@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "exchange_kuang@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/exchange_tixian.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "exchange_tixian@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "exchange_tixian@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/exp_current.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "exp_current@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "exp_current@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/exp_more.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "exp_more@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "exp_more@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/exp_paiming.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "exp_paiming@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "exp_paiming@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/exp_progress.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "exp_progress@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "exp_progress@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/exp_publishBook.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "exp_publishBook@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "exp_publishBook@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/exp_publishTopic.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "exp_publishTopic@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "exp_publishTopic@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/exp_updateHeader.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "exp_updateHeader@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "exp_updateHeader@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/f_author_icon.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "f_author_icon@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "f_author_icon@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/f_commentator_icon.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "f_commentator_icon@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "f_commentator_icon@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/f_distillate.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "f_distillate@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "f_distillate@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/f_doyen_icon.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "f_doyen_icon@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "f_doyen_icon@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/f_game_center_icon.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "f_game_center_icon@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "f_game_center_icon@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/f_girl_icon.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "f_girl_icon@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "f_girl_icon@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/f_hot.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "f_hot@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "f_hot@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/f_invent_icon.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "f_invent_icon@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "f_invent_icon@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/f_like_comment.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "f_like_comment@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "f_like_comment@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/f_like_comment_selected.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "f_like_comment_selected@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "f_like_comment_selected@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/f_like_icon.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "f_like_icon@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "f_like_icon@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/f_like_icon_select.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "f_like_icon_select@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "f_like_icon_select@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/f_moderator_icon.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "f_moderator_icon@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "f_moderator_icon@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/f_official_icon.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "f_official_icon@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "f_official_icon@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/f_ramble_default_cover.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "f_ramble_default_cover@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "f_ramble_default_cover@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/f_ramble_icon.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "f_ramble_icon@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "f_ramble_icon@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/f_sort.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "f_sort@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "f_sort@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/f_today_topic.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "f_today_topic@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "f_today_topic@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/fanhui.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "fanhui@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "fanhui@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/fb_cancel.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "fb_cancel@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "fb_cancel@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/fb_cancel_selected.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "fb_cancel_selected@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "fb_cancel_selected@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/fb_gray_angle.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "fb_gray_angle@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "fb_gray_angle@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/fb_set.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "fb_set@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "fb_set@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/fb_set_selected.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "fb_set_selected@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "fb_set_selected@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/fb_white_angle.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "fb_white_angle@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "fb_white_angle@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/fd_tip.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "fd_tip@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "fd_tip@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/fd_tip_title.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "fd_tip_title@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "fd_tip_title@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/feedback.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "feedback@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "feedback@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/fenxiang.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "fenxiang@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "fenxiang@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/float_forum_icon.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "float_forum_icon@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "float_forum_icon@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/float_forum_icon_selected.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "float_forum_icon_selected@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "float_forum_icon_selected@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/font_decrease.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "font_decrease@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "font_decrease@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/font_decrease_selected.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "font_decrease_selected@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "font_decrease_selected@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/font_decrease_unable.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "font_decrease_unable@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "font_decrease_unable@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/font_increase.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "font_increase@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "font_increase@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/font_increase_selected.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "font_increase_selected@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "font_increase_selected@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/font_increase_unable.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "font_increase_unable@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "font_increase_unable@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/forum_avatar_small.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "forum_avatar_small@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/forum_book_entry.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "forum_book_entry@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "forum_book_entry@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/forum_collect_icon.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "forum_collect_icon@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "forum_collect_icon@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/forum_collect_icon_selected.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "forum_collect_icon_selected@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "forum_collect_icon_selected@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/forum_comment_cancel.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "forum_comment_cancel@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "forum_comment_cancel@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/forum_comment_cancel_selected.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "forum_comment_cancel_selected@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "forum_comment_cancel_selected@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/forum_comment_confirm.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "forum_comment_confirm@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "forum_comment_confirm@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/forum_comment_confirm_selected.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "forum_comment_confirm_selected@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "forum_comment_confirm_selected@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/forum_comment_confirm_unable.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "forum_comment_confirm_unable@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "forum_comment_confirm_unable@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/forum_comment_input_bg.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "forum_comment_input_bg@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/forum_comment_reply.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "forum_comment_reply@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "forum_comment_reply@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/forum_comment_reply_selected.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "forum_comment_reply_selected@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "forum_comment_reply_selected@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/forum_comment_start.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "forum_comment_start@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/forum_comment_start_selected.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "forum_comment_start_selected@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/forum_enter.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "forum_enter@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "forum_enter@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/forum_gray_star.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "forum_gray_star@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "forum_gray_star@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/forum_help_default_cover.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "forum_help_default_cover@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "forum_help_default_cover@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/forum_hint_arrow_right_up.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "forum_hint_arrow_right_up@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/forum_like_icon.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "forum_like_icon@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "forum_like_icon@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/forum_liked_icon.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "forum_liked_icon@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "forum_liked_icon@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/forum_more_icon.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "forum_more_icon@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "forum_more_icon@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/forum_more_icon_selected.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "forum_more_icon_selected@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "forum_more_icon_selected@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/forum_more_option.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "forum_more_option@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "forum_more_option@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/forum_more_option_selected.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "forum_more_option_selected@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "forum_more_option_selected@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/forum_not_voted.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "forum_not_voted@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "forum_not_voted@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/forum_post_comment_icon.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "forum_post_comment_icon@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "forum_post_comment_icon@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/forum_post_red.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "forum_post_red@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "forum_post_red@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/forum_post_red_selected.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "forum_post_red_selected@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "forum_post_red_selected@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/forum_post_shadow.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "forum_post_shadow@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/forum_public_help_icon.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "forum_public_help_icon@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "forum_public_help_icon@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/forum_public_review_icon.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "forum_public_review_icon@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "forum_public_review_icon@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/forum_red_star.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "forum_red_star@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "forum_red_star@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/forum_share_icon.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "forum_share_icon@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "forum_share_icon@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/forum_share_icon_selected.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "forum_share_icon_selected@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "forum_share_icon_selected@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/forum_topic_type_post.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "forum_topic_type_post@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "forum_topic_type_post@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/forum_topic_type_vote.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "forum_topic_type_vote@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/forum_vote_add.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "forum_vote_add@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "forum_vote_add@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/forum_vote_add_selected.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "forum_vote_add_selected@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "forum_vote_add_selected@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/forum_vote_delete.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "forum_vote_delete@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "forum_vote_delete@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/forum_voted.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "forum_voted@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "forum_voted@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/fuliv3.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "fuliv3@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "fuliv3@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/fullscreen.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "fullscreen.png", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/g_boy.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "g_boy@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/g_boy_avatar.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "g_boy_avatar@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/g_boy_selected.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "g_boy_selected@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/g_boy_tip.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "g_boy_tip@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/g_close.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "g_close@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/g_girl.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "g_girl@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/g_girl_avatar.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "g_girl_avatar@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/g_girl_selected.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "g_girl_selected@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/g_girl_tip.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "g_girl_tip@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/g_left_line.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "g_left_line@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/g_right_line.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "g_right_line@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/g_sex_tip.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "g_sex_tip@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/g_tip_overview.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "g_tip_overview@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/game_image.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "game_image@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "game_image@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/gou.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "gou@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "gou@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/green_mode_bg.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "green_mode_bg@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "green_mode_bg@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/guanbi.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "guanbi@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "guanbi@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/guanbiNotify.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "guanbiNotify@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "guanbiNotify@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/hsm_default_avatar.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "hsm_default_avatar@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "hsm_default_avatar@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/hsm_icon_1.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "hsm_icon_1@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "hsm_icon_1@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/hsm_icon_2.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "hsm_icon_2@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "hsm_icon_2@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/hsm_icon_3.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "hsm_icon_3@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "hsm_icon_3@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/hsm_new_topic_tip.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "hsm_new_topic_tip@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "hsm_new_topic_tip@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/interstitialIcon.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "interstitialIcon.png", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/interstitialIconclose.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "interstitialIconclose.png", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/jiantou.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "jiantou@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "jiantou@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/jingdian.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "jingdian@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "jingdian@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/jinjiv3.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "jinjiv3@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "jinjiv3@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/landscape.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "landscape@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "landscape@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/last_read_resource.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "last_read_resource@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "last_read_resource@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/liantu.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "liantu@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "liantu@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/liebiao.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "liebiao@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "liebiao@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/lishiv3.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "lishiv3@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "lishiv3@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/listbofang.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "listbofang@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "listbofang@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/listpaixu.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "listpaixu@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "listpaixu@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/listshengyin.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "listshengyin@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "listshengyin@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/listxiazai.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "listxiazai@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "listxiazai@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/listxuanji.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "listxuanji@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "listxuanji@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/login_background.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "login_background@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/login_country_background.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "login_country_background@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/login_country_background_highlighted.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "login_country_background_highlighted@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/logo.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "logo.png", "scale" : "1x" }, { "idiom" : "universal", "filename" : "logo@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/logo_recommend.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "logo_recommend.png", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/logo_recommend_ipad.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "logo_recommend_ipad.png", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/lsm_view_shadow.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "lsm_view_shadow@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/m_success_icon.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "m_success_icon@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "m_success_icon@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/mode_baidu.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "mode_baidu@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "mode_baidu@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/mode_cp.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "mode_cp@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "mode_cp@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/mode_easou.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "mode_easou@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "mode_easou@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/mode_juhe.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "mode_juhe@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "mode_juhe@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/mode_leidian.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "mode_leidian@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "mode_leidian@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/mode_shenma.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "mode_shenma@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "mode_shenma@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/mode_sogou.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "mode_sogou@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "mode_sogou@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/mode_soso.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "mode_soso@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "mode_soso@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/mode_tieba.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "mode_tieba@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "mode_tieba@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/mode_zhineng.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "mode_zhineng@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "mode_zhineng@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/month_jiaobiao.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "month_jiaobiao@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "month_jiaobiao@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/month_kuang.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "month_kuang@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "month_kuang@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/month_kuang1.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "month_kuang1@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "month_kuang1@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/month_touxiang.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "month_touxiang@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "month_touxiang@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/monthly_book_mark.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "monthly_book_mark@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "monthly_book_mark@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/monthly_mark.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "monthly_mark@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "monthly_mark@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/move-down.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "move-down@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "move-down@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/move-up.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "move-up@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "move-up@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/mp_arrow_down.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "mp_arrow_down@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "mp_arrow_down@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/mp_arrow_left.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "mp_arrow_left@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "mp_arrow_left@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/mp_arrow_right.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "mp_arrow_right@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "mp_arrow_right@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/mp_arrow_up.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "mp_arrow_up@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "mp_arrow_up@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/mp_refresh.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "mp_refresh@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "mp_refresh@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/mp_selected.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "mp_selected@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "mp_selected@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/mp_unselected.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "mp_unselected@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "mp_unselected@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/mv_ad_shadow.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "mv_ad_shadow@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/mv_arrow.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "mv_arrow@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "mv_arrow@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/mv_avatar_wrapper.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "mv_avatar_wrapper@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/mv_book_icon.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "mv_book_icon@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "mv_book_icon@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/mv_header_background.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "mv_header_background@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/mv_header_shadow.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "mv_header_shadow@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/mv_home_icon.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "mv_home_icon@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "mv_home_icon@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/mv_hot_vote.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "mv_hot_vote@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/mv_mani_icon.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "mv_mani_icon@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "mv_mani_icon@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/mv_post_icon.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "mv_post_icon@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/mv_reply_icon.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "mv_reply_icon@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/mv_separator.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "mv_separator@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/mv_title.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "mv_title@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "mv_title@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/mv_topic_icon.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "mv_topic_icon@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "mv_topic_icon@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/mv_vote_icon.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "mv_vote_icon@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/n_comment.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "n_comment@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "n_comment@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/n_icon.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "n_icon@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "n_icon@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/n_link.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "n_link@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "n_link@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/n_reply.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "n_reply@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "n_reply@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/n_vote.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "n_vote@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "n_vote@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/nav_add_book.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "nav_add_book@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "nav_add_book@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/nav_add_book_selected.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "nav_add_book_selected@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "nav_add_book_selected@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/nav_arrow_down.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "nav_arrow_down@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "nav_arrow_down@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/nav_arrow_up.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "nav_arrow_up@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "nav_arrow_up@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/nav_back_red.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "nav_back_red@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "nav_back_red@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/nav_back_red_selected.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "nav_back_red_selected@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "nav_back_red_selected@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/nav_back_white.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "nav_back_white@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "nav_back_white@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/nav_back_white_selected.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "nav_back_white_selected@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "nav_back_white_selected@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/nav_home_side_menu.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "nav_home_side_menu@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "nav_home_side_menu@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/nav_home_side_menu_selected.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "nav_home_side_menu_selected@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "nav_home_side_menu_selected@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/navigationbar_background.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "navigationbar_background.png", "scale" : "1x" }, { "idiom" : "universal", "filename" : "navigationbar_background@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/navigationbar_background_os7.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "navigationbar_background_os7.png", "scale" : "1x" }, { "idiom" : "universal", "filename" : "navigationbar_background_os7@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/network_cut.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "network_cut@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "network_cut@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/newUser_bg.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "newUser_bg.png", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/newUser_close.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "newUser_close.png", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/new_nav_hilighted.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "new_nav_hilighted@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "new_nav_hilighted@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/new_nav_night_hilighted.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "new_nav_night_hilighted@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "new_nav_night_hilighted@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/new_nav_night_normal.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "new_nav_night_normal@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "new_nav_night_normal@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/new_nav_night_selected.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "new_nav_night_selected@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "new_nav_night_selected@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/new_nav_normal.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "new_nav_normal@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "new_nav_normal@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/new_nav_selected.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "new_nav_selected@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "new_nav_selected@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/new_nav_vertical_separator.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "new_nav_vertical_separator@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/new_notification_icon.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "new_notification_icon@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/new_notification_tip.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "new_notification_tip@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/night_mode.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "night_mode@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "night_mode@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/nl_qq.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "nl_qq@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "nl_qq@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/nl_wechat.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "nl_wechat@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "nl_wechat@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/nl_weibo.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "nl_weibo@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "nl_weibo@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/nl_xiaomi.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "nl_xiaomi@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "nl_xiaomi@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/no_network.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "no_network@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "no_network@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/no_resource.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "no_resource@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "no_resource@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/notice_image.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "notice_image@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "notice_image@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/nvshengv3.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "nvshengv3@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "nvshengv3@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/official_icon.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "official_icon@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "official_icon@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/p_anywhere.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "p_anywhere@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "p_anywhere@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/p_anywhere_ipad.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "p_anywhere_ipad@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/p_anywhere_title.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "p_anywhere_title@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "p_anywhere_title@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/p_anywhere_title_ipad.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "p_anywhere_title_ipad@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/p_btn.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "resizing" : { "mode" : "9-part", "center" : { "mode" : "tile", "width" : 1, "height" : 1 }, "cap-insets" : { "bottom" : 41, "top" : 42, "right" : 225, "left" : 278 } }, "idiom" : "universal", "filename" : "p_btn@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "p_btn@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/p_btn_selected.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "p_btn_selected@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "p_btn_selected@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/p_fast.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "p_fast@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "p_fast@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/p_fast_ipad.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "p_fast_ipad@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/p_fast_title.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "p_fast_title@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "p_fast_title@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/p_fast_title_ipad.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "p_fast_title_ipad@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/p_guest_tip.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "p_guest_tip@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "p_guest_tip@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/p_guest_tip_ipad.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "p_guest_tip_ipad@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/p_logo.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "p_logo@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "p_logo@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/p_logo_ipad.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "p_logo_ipad@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/p_logo_small.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "p_logo_small@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "p_logo_small@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/p_logo_small_ipad.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "p_logo_small_ipad@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/p_qq_icon.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "p_qq_icon@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "p_qq_icon@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/p_sina_icon.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "p_sina_icon@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "p_sina_icon@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/p_synchronize.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "p_synchronize@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "p_synchronize@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/p_synchronize_ipad.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "p_synchronize_ipad@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/p_synchronize_title.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "p_synchronize_title@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "p_synchronize_title@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/p_synchronize_title_ipad.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "p_synchronize_title_ipad@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/pay_alipay.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "pay_alipay@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "pay_alipay@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/pay_apple.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "pay_apple@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "pay_apple@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/pay_record_option.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "pay_record_option@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "pay_record_option@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/pay_weixin.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "pay_weixin@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "pay_weixin@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/pf_header_bg.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "pf_header_bg@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/pf_new_notification.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "pf_new_notification@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "pf_new_notification@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/pf_ratebottom.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "pf_ratebottom@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "pf_ratebottom@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/pf_rateupper.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "pf_rateupper@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "pf_rateupper@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/pink_mode_bg.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "filename" : "pink_mode_bg@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/play_big_image.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "play_big_image.png", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/player_back.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "player_back.png", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/player_pause.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "player_pause.png", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/player_play.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "player_play.png", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/portrait.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "portrait@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "portrait@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/post_tip_icon.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "post_tip_icon@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "post_tip_icon@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/preview_btn.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "preview_btn@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "preview_btn@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/previewed_icon.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "previewed_icon@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "previewed_icon@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/progress_cancel.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "progress_cancel@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/progress_cancel_selected.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "progress_cancel_selected@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/progresshud_background.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "progresshud_background@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/promotion_image.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "promotion_image@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "promotion_image@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/qqicon.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "qqicon.png", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/ranking_arrow_down.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "ranking_arrow_down@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "ranking_arrow_down@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/ranking_other.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "ranking_other@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "ranking_other@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/rd_func_bg.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "rd_func_bg@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "rd_func_bg@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/rd_reading_add_icon.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "rd_reading_add_icon@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "rd_reading_add_icon@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/rd_reading_added_icon.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "rd_reading_added_icon@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "rd_reading_added_icon@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/rd_reading_change_icon.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "rd_reading_change_icon@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "rd_reading_change_icon@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/readAloud.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "readAloud@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "readAloud@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/readAloud_exit.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "readAloud_exit@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "readAloud_exit@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/readAloud_more.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "readAloud_more@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "readAloud_more@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/readAloud_more_highLighted.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "readAloud_more_highLighted@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "readAloud_more_highLighted@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/readAloud_pause.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "readAloud_pause@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "readAloud_pause@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/readAloud_play.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "readAloud_play@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "readAloud_play@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/reading_landscape.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "reading_landscape@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "reading_landscape@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/reading_more_setting.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "reading_more_setting@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "reading_more_setting@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/reading_more_setting_landscape.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "reading_more_setting_landscape@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "reading_more_setting_landscape@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/reading_record_icon.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "reading_record_icon@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "reading_record_icon@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/reading_setting.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "reading_setting@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "reading_setting@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/reading_tip_menu.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "reading_tip_menu@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "reading_tip_menu@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/reading_tip_next.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "reading_tip_next@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "reading_tip_next@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/reading_tip_pre.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "reading_tip_pre@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "reading_tip_pre@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/recommend_new.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "recommend_new@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "recommend_new@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/recommend_top.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "recommend_top@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "recommend_top@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/red_menu_icon.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "red_menu_icon@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/refresh_arrow.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "refresh_arrow@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/refresh_finish.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "refresh_finish@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/reward.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "reward@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "reward@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/reward_launch.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "reward_launch@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "reward_launch@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/rp_arrow.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "rp_arrow@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "rp_arrow@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/rp_badge_disabled.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "rp_badge_disabled@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "rp_badge_disabled@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/rp_badge_normal.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "rp_badge_normal@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "rp_badge_normal@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/rp_checkbox.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "rp_checkbox@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "rp_checkbox@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/rp_checkbox_unchecked.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "rp_checkbox_unchecked@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "rp_checkbox_unchecked@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/rp_disabled_bg.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "rp_disabled_bg@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "rp_disabled_bg@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/rp_info.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "rp_info@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "rp_info@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/rp_no_network.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "rp_no_network@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "rp_no_network@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/rp_normal_bg.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "rp_normal_bg@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "rp_normal_bg@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/rp_selected_bg.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "rp_selected_bg@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "rp_selected_bg@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/rsm_icon_0.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "rsm_icon_0@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "rsm_icon_0@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/rsm_icon_1.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "rsm_icon_1@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "rsm_icon_1@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/rsm_icon_10.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "rsm_icon_10@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "rsm_icon_10@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/rsm_icon_2.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "rsm_icon_2@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "rsm_icon_2@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/rsm_icon_3.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "rsm_icon_3@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "rsm_icon_3@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/rsm_icon_4.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "rsm_icon_4@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "rsm_icon_4@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/rsm_icon_5.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "rsm_icon_5@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "rsm_icon_5@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/rsm_icon_6.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "rsm_icon_6@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "rsm_icon_6@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/rsm_icon_7.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "rsm_icon_7@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "rsm_icon_7@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/rsm_icon_8.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "rsm_icon_8@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "rsm_icon_8@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/rsm_icon_9.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "rsm_icon_9@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "rsm_icon_9@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/rsm_icon_comic.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "rsm_icon_comic@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "rsm_icon_comic@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/rsm_icon_exclusive.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "rsm_icon_exclusive@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "rsm_icon_exclusive@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/rsm_icon_monthly.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "rsm_icon_monthly@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "rsm_icon_monthly@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/rsm_icon_recommended.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "rsm_icon_recommended@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "rsm_icon_recommended@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/rsm_icon_store.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "rsm_icon_store@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "rsm_icon_store@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/rsm_icon_wifi.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "rsm_icon_wifi@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "rsm_icon_wifi@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/rsm_view_shadow.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "rsm_view_shadow@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/s_wechat.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "s_wechat@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "s_wechat@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/sdk_weibo_logo.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "sdk_weibo_logo.png", "scale" : "1x" }, { "idiom" : "universal", "filename" : "sdk_weibo_logo@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "sdk_weibo_logo@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/search_bg.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "search_bg@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/search_cannot_delete.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "search_cannot_delete@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "search_cannot_delete@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/search_cannot_refresh.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "search_cannot_refresh@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "search_cannot_refresh@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/search_delete.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "search_delete@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "search_delete@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/search_history_mark.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "search_history_mark@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "search_history_mark@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/search_icon.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "search_icon@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "search_icon@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/search_refresh.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "search_refresh@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "search_refresh@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/setting_fontFanJian_jian.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "setting_fontFanJian_jian@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "setting_fontFanJian_jian@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/setting_font_bigger_nomal.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "setting_font_bigger_nomal@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "setting_font_bigger_nomal@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/setting_font_bigger_nomal_h.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "setting_font_bigger_nomal_h@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "setting_font_bigger_nomal_h@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/setting_font_bigger_uneable.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "setting_font_bigger_uneable@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "setting_font_bigger_uneable@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/setting_font_fan.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "setting_font_fan@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "setting_font_fan@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/setting_font_jian.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "setting_font_jian@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "setting_font_jian@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/setting_font_nomal.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "setting_font_nomal@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "setting_font_nomal@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/setting_font_selected.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "setting_font_selected@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "setting_font_selected@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/setting_font_snaller_normal.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "setting_font_snaller_normal@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "setting_font_snaller_normal@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/setting_font_snaller_normal_h.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "setting_font_snaller_normal_h.png", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "filename" : "setting_font_snaller_normal_h@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/setting_font_snaller_uneable.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "setting_font_snaller_uneable@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "setting_font_snaller_uneable@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/setting_orientation_hengpin.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "setting_orientation_hengpin@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "setting_orientation_hengpin@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/setting_orientation_shupin.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "setting_orientation_shupin@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "setting_orientation_shupin@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/setting_paly.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "setting_paly@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "setting_paly@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/setting_set.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "setting_set@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "setting_set@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/setting_space_big_nomal.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "setting_space_big_nomal@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "setting_space_big_nomal@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/setting_space_big_selected.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "setting_space_big_selected@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "setting_space_big_selected@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/setting_space_nomal.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "setting_space_nomal@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "setting_space_nomal@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/setting_space_selected.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "setting_space_selected@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "setting_space_selected@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/setting_space_small_nomal.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "setting_space_small_nomal@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "setting_space_small_nomal@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/setting_space_small_selected.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "setting_space_small_selected@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "setting_space_small_selected@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/setting_theme_selected.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "setting_theme_selected@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "setting_theme_selected@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/shang.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "shang@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "shang@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/sheepskin_mode_bg.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "filename" : "sheepskin_mode_bg@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/shengyin.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "shengyin@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "shengyin@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/show_right_sidemenu_icon.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "show_right_sidemenu_icon@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/sign_bg.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "sign_bg@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "sign_bg@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/sign_gotoSign.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "sign_gotoSign@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "sign_gotoSign@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/sign_signed.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "sign_signed@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "sign_signed@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/sign_success.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "sign_success.png", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/slider.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "slider.png", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/slider_black.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "slider_black@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "slider_black@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/slider_red.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "slider_red@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "slider_red@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/sm_arrow_down.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "sm_arrow_down@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/sm_bottom_background.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "sm_bottom_background@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/sm_bottom_shadow.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "sm_bottom_shadow@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/sm_exit.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "sm_exit@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "sm_exit@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/sm_exit_selected.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "sm_exit_selected@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "sm_exit_selected@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/sm_forum_bg.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "sm_forum_bg@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "sm_forum_bg@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/sm_forum_bg_selected.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "sm_forum_bg_selected@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "sm_forum_bg_selected@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/sm_topic_tip_bg.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "sm_topic_tip_bg@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "sm_topic_tip_bg@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/sm_web_back.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "sm_web_back@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "sm_web_back@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/sm_web_forward.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "sm_web_forward@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "sm_web_forward@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/sm_web_refresh.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "sm_web_refresh@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "sm_web_refresh@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/splash_bottom_icon.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "splash_bottom_icon@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "splash_bottom_icon@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/splash_bottom_logo.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "splash_bottom_logo@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "splash_bottom_logo@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/success.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "success.png", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/switch.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "switch@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "switch@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/task_lanch.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "task_lanch@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "task_lanch@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/task_person.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "task_person@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "task_person@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/task_praise.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "task_praise@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "task_praise@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/task_share.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "task_share@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "task_share@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/timeline_relationship_icon_addattention.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "timeline_relationship_icon_addattention.png", "scale" : "1x" }, { "idiom" : "universal", "filename" : "timeline_relationship_icon_addattention@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "timeline_relationship_icon_addattention@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/timeline_relationship_icon_attention.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "timeline_relationship_icon_attention.png", "scale" : "1x" }, { "idiom" : "universal", "filename" : "timeline_relationship_icon_attention@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "timeline_relationship_icon_attention@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/toolbar_baidu.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "toolbar_baidu.png", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/toolbar_baidu_active.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "toolbar_baidu_active.png", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/toolbar_bg.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "toolbar_bg.png", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/toolbar_bg1.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "toolbar_bg1.png", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/toolbar_close.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "toolbar_close.png", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/toolbar_fold.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "toolbar_fold.png", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/toolbar_message.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "toolbar_message.png", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/toolbar_message_active.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "toolbar_message_active.png", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/toolbar_navBack.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "toolbar_navBack.png", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/toolbar_navBack_active.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "toolbar_navBack_active.png", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/toolbar_navForward.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "toolbar_navForward.png", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/toolbar_navForward_active.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "toolbar_navForward_active.png", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/toolbar_unfold.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "toolbar_unfold.png", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/toolbar_weibo.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "toolbar_weibo.png", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/toolbar_weibo_active.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "toolbar_weibo_active.png", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/update_image.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "update_image@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "update_image@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/userCenter_account-1.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "userCenter_account@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "userCenter_account@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/userCenter_account.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "userCenter_account@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "userCenter_account@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/userCenter_bookList-1.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "userCenter_bookList@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "userCenter_bookList@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/userCenter_bookList.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "userCenter_bookList@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "userCenter_bookList@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/userCenter_comment.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "userCenter_comment@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "userCenter_comment@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/userCenter_exchange.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "userCenter_exchange@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "userCenter_exchange@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/userCenter_experience-1.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "userCenter_experience@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "userCenter_experience@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/userCenter_experience.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "userCenter_experience@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "userCenter_experience@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/userCenter_history.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "userCenter_history@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "userCenter_history@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/userCenter_msg-1.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "userCenter_msg@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "userCenter_msg@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/userCenter_msg.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "userCenter_msg@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "userCenter_msg@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/userCenter_rate-1.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "userCenter_rate@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "userCenter_rate@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/userCenter_rate.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "userCenter_rate@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "userCenter_rate@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/userCenter_setting-1.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "userCenter_setting@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "userCenter_setting@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/userCenter_setting.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "userCenter_setting@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "userCenter_setting@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/userCenter_setting2.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "userCenter_setting2@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "userCenter_setting2@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/userCenter_task.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "userCenter_task@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "userCenter_task@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/userCenter_topic-1.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "userCenter_topic@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "userCenter_topic@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/userCenter_topic.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "userCenter_topic@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "userCenter_topic@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/userCenter_wechat.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "userCenter_wechat@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "userCenter_wechat@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/verify_code_button.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "verify_code_button@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "verify_code_button@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/verify_code_button_highlighted.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "verify_code_button_highlighted@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "verify_code_button_highlighted@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/video_volumeoff.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "video_volumeoff.png", "scale" : "1x" }, { "idiom" : "universal", "filename" : "video_volumeoff@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/video_volumeon.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "video_volumeon.png", "scale" : "1x" }, { "idiom" : "universal", "filename" : "video_volumeon@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/violet_mode_bg.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "violet_mode_bg@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/voicefenxiang.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "voicefenxiang@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "voicefenxiang@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/volumeoff.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "volumeoff.png", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/volumeon.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "volumeon.png", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/wangwenv3.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "wangwenv3@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "wangwenv3@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/water_mode_bg.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "filename" : "water_mode_bg@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/wechat.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "wechat@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "wechat@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/wechatQR.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "wechatQR.png", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/weekGreen_mode_bg.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "weekGreen_mode_bg@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/weekPink_mode_bg.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "weekPink_mode_bg@2x.png", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/white_mode_bg.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "white_mode_bg@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "white_mode_bg@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/xia.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "xia@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "xia@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/yellow_mode_bg.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "yellow_mode_bg@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "yellow_mode_bg@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/yuan.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "yuan@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "yuan@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/yuanchuangv3.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "yuanchuangv3@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "yuanchuangv3@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/zanting.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "zanting@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "zanting@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/zantingNotify.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "zantingNotify@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "zantingNotify@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/zhonghev3.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "zhonghev3@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "zhonghev3@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/zhui.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "zhui@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "zhui@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/zssq_image.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "zssq_image@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "zssq_image@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/zuire.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "zuire@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "zuire@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/zuixin.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "zuixin@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "zuixin@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/方正兰亭黑.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "方正兰亭黑@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "方正兰亭黑@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/日文字体.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "日文字体@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "日文字体@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/楷体.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "楷体@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "楷体@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/翩翩体.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "翩翩体@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "翩翩体@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/隶变.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "隶变@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "隶变@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/雅痞.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "雅痞@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "雅痞@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/魏碑.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "魏碑@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "魏碑@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/黑体.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "黑体@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "黑体@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Assets.xcassets/zhuishushenqi/默认.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "scale" : "1x" }, { "idiom" : "universal", "filename" : "默认@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "默认@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: zhuishushenqi/Base/Controllers/BaseViewController.swift ================================================ // // BaseViewController.swift // zhuishushenqi // // Created by Nory Cao on 16/9/20. // Copyright © 2016年 QS. All rights reserved. // import UIKit typealias ZSBaseHandler = (_ result:Bool)->Void class BaseViewController: UIViewController, IndicatableView, UIGestureRecognizerDelegate { var navigationBarHiden:Bool = false override func viewDidLoad() { super.viewDidLoad() self.automaticallyAdjustsScrollViewInsets = false self.view.backgroundColor = UIColor.white self.view.backgroundColor = UIColor(red: 0.95, green: 0.95, blue: 0.95, alpha: 1.0) navigationController?.navigationBar.tintColor = UIColor.red navigationController?.navigationBar.barTintColor = UIColor.white let backItem = UIBarButtonItem(title: "返回", style: .plain, target: self, action: #selector(popAction)) self.navigationItem.backBarButtonItem = backItem navigationController?.interactivePopGestureRecognizer?.delegate = self } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) // navigationController?.setNavigationBarHidden(navigationBarHiden, animated: true) ZSFloatingManager.share.window?.rootViewController?.setNeedsStatusBarAppearanceUpdate() } override var preferredStatusBarStyle : UIStatusBarStyle { return .default } override var prefersStatusBarHidden : Bool { return false } override var supportedInterfaceOrientations: UIInterfaceOrientationMask { return .all } override var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation { return .portrait } override var shouldAutorotate: Bool { return true } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } @objc func popAction(){ self.navigationController?.popViewController(animated: true) } //MARK: - UIGestureRecognizerDelegate func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { return true } //MARK: - progress func showProgress() { self.indicatorView.center = self.view.center self.view.addSubview(self.indicatorView) self.view.bringSubviewToFront(self.indicatorView) } func hideProgress() { self.indicatorView.stopAnimating() self.indicatorView.removeFromSuperview() } func login(success:@escaping ZSBaseHandler) { if ZSLogin.share.hasLogin() { success(true) return } let loginVC = ZSLoginViewController() loginVC.loginResultHandler = { result in success(result) } present(loginVC, animated: true, completion: nil) } lazy var indicatorView:UIActivityIndicatorView = { let indicator = UIActivityIndicatorView(style: .gray) indicator.frame = CGRect(x: ScreenWidth/2 - 50 , y: ScreenHeight/2 - 50, width: 100, height: 100) indicator.startAnimating() return indicator }() } ================================================ FILE: zhuishushenqi/Base/Controllers/SideViewController.swift ================================================ // // SideViewController.swift // zhuishushenqi // // Created by yung on 16/9/30. // Copyright © 2016年 CNY. All rights reserved. // import UIKit private enum HorizonalXSideType{ case none case left case right } class SideViewController: UIViewController,UIGestureRecognizerDelegate { /** * 右侧视图控制器相对于容器视图的比例,1.0不进行缩放[暂不支持] */ var rightViewControllerScale:CGFloat = 0.75 /** * 左侧视图控制器相对于容器视图的比例,1.0不进行缩放[暂不支持] */ var leftViewControllerScale:CGFloat = 0.75 /** * 手势滑动时bounces */ var shouldStretchDrawer:Bool = true var maximumLeftOffsetWidth:CGFloat = 100 var maximumRightOffsetWidth:CGFloat = 200 /** * 侧滑菜单加载时间 */ var animateDuration = 0.25 /// 是否右侧边栏处于关闭状态 var isCloseRightSide:Bool = true /// 是否左侧边栏处于关闭状态 var isCloseLeftSide:Bool = true var isPanGestureEnable:Bool = true var leftViewController:UIViewController? { didSet { setSideViewController(side: .left) } } var rightViewController:UIViewController? { didSet { setSideViewController(side: .right) } } var contentViewController:UIViewController? { didSet { setContentViewController() } } fileprivate var contentView:UIView = UIView() fileprivate var containerView:UIView = UIView() fileprivate var maskView:UIView = UIView() fileprivate var shadowView:UIView = UIView() fileprivate var startingPanRect:CGRect = CGRect.zero var drawerOvershootPercentage:CGFloat = 0.1 var drawerOvershootLinearRangePercentage:CGFloat = 0.75 var panVelocityXAnimationThreshold:CGFloat = 200 /// 显示菜单方向 fileprivate var horizonalXSide:HorizonalXSideType = HorizonalXSideType.none /// 弹性空间,可以回弹 fileprivate var bounchesX:CGFloat = 20 /// 最小open宽度 fileprivate var minimumSwipeX:CGFloat = 20 fileprivate lazy var panGes:UIPanGestureRecognizer = { let pan:UIPanGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(self.panAction(_:))) pan.delegate = self return pan }() override func viewDidLoad() { super.viewDidLoad() initSubview() updateContentViewShadow() } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) self.navigationController?.isNavigationBarHidden = true } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) updateContentViewShadow() } override func willAnimateRotation(to toInterfaceOrientation: UIInterfaceOrientation, duration: TimeInterval) { guard let oldShadowPath = self.shadowView.layer.shadowPath else { return } self.shadowView.layer.shadowPath = nil updateContentViewShadow() self.shadowView.layer.add((({ let transition = CABasicAnimation(keyPath: "shadowPath") transition.fromValue = oldShadowPath transition.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut) transition.duration = duration return transition })()) ,forKey: "transition") } fileprivate func initSubview(){ var autoResizingMask = UIView.AutoresizingMask.flexibleWidth autoResizingMask = autoResizingMask.union(.flexibleHeight) containerView = UIView(frame: self.view.bounds) containerView.backgroundColor = UIColor.white containerView.autoresizingMask = autoResizingMask view.addSubview(containerView) var contentAutoResizingMask = UIView.AutoresizingMask.flexibleWidth contentAutoResizingMask = contentAutoResizingMask.union(.flexibleHeight) contentView = UIView(frame: self.view.bounds) contentView.backgroundColor = UIColor.white containerView.addSubview(contentView) contentView.autoresizingMask = contentAutoResizingMask contentView.addGestureRecognizer(panGes) maskView = UIView(frame: self.view.bounds) maskView.autoresizingMask = autoResizingMask maskView.backgroundColor = UIColor(white: 0.5, alpha: 0.2) let ges = UITapGestureRecognizer(target: self, action: #selector(maskAction(_:))) maskView.addGestureRecognizer(ges) contentView.addSubview(maskView) shadowView = UIView(frame: self.view.bounds) shadowView.isUserInteractionEnabled = true shadowView.autoresizingMask = autoResizingMask shadowView.backgroundColor = UIColor.clear contentView.addSubview(shadowView) } //MARK: - UIGestureRecognizerDelegate func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { if gestureRecognizer == panGes { let pan:UIPanGestureRecognizer = gestureRecognizer as! UIPanGestureRecognizer let velocity:CGPoint = pan.velocity(in: self.contentView) if pan.velocity(in: self.contentView).x < 3000 && abs(velocity.x)/abs(velocity.y) > 1 { return true } return false } return true } private func originXForDrawerOriginAndTargetOriginOffset(originX:CGFloat, targetOffset:CGFloat, maxOvershoot:CGFloat) ->CGFloat { let delta = abs(originX - targetOffset) let maxLinearPercentage = drawerOvershootLinearRangePercentage let nonLinearRange = maxOvershoot * maxLinearPercentage let nonLinearScalingDelta = delta - nonLinearRange let overshoot = nonLinearRange + nonLinearScalingDelta * nonLinearRange/sqrt(pow(nonLinearScalingDelta, 2.0) + 15000) if delta < nonLinearRange { return originX } else if targetOffset < 0 { return targetOffset - round(overshoot) } else { return targetOffset + round(overshoot) } } private func roundedOriginX(for drawerConstriants:CGFloat) ->CGFloat { if drawerConstriants < -self.maximumRightOffsetWidth { if self.shouldStretchDrawer && self.rightViewController != nil { let maxOvershoot = (self.contentView.frame.width - self.maximumRightOffsetWidth) * drawerOvershootPercentage return originXForDrawerOriginAndTargetOriginOffset(originX: drawerConstriants, targetOffset: -self.maximumRightOffsetWidth, maxOvershoot: maxOvershoot) } else { return -self.maximumRightOffsetWidth } } else if drawerConstriants > self.maximumRightOffsetWidth { if self.shouldStretchDrawer && self.leftViewController != nil { let maxOvershoot = (self.contentView.frame.width - self.maximumLeftOffsetWidth) * drawerOvershootPercentage return originXForDrawerOriginAndTargetOriginOffset(originX: drawerConstriants, targetOffset: self.maximumLeftOffsetWidth, maxOvershoot: maxOvershoot) } else { return self.maximumLeftOffsetWidth } } return drawerConstriants } private func childViewControllerForSide(drawerSide:HorizonalXSideType) ->UIViewController { var childViewController:UIViewController? switch drawerSide { case .none: childViewController = contentViewController break case .left: childViewController = leftViewController break case .right: childViewController = rightViewController break } return childViewController! } private func sideDrawerViewControllerForSide(drawerSide:HorizonalXSideType) ->UIViewController { var sideDrawerViewController:UIViewController? if drawerSide != .none { sideDrawerViewController = self.childViewControllerForSide(drawerSide: drawerSide) } return sideDrawerViewController! } @objc private func panAction(_ pan:UIPanGestureRecognizer) { switch pan.state { case .began: self.startingPanRect = self.contentView.frame break case .changed: self.view.isUserInteractionEnabled = false var newFrame = self.startingPanRect let translatedPoint = pan .translation(in: self.contentView) newFrame.origin.x = self.roundedOriginX(for: self.startingPanRect.minX + translatedPoint.x) newFrame = newFrame.integral self.contentView.center = CGPoint(x: newFrame.midX, y: newFrame.midY) newFrame = self.contentView.frame newFrame.origin.x = floor(newFrame.origin.x) newFrame.origin.y = floor(newFrame.origin.y) self.contentView.frame = newFrame let xOffset = newFrame.origin.x; var visibleSide = HorizonalXSideType.none if xOffset > 0 { visibleSide = .left self.leftViewController?.view.isHidden = false self.rightViewController?.view.isHidden = true } else { visibleSide = .right self.leftViewController?.view.isHidden = true self.rightViewController?.view.isHidden = false } if visibleSide != horizonalXSide { self.horizonalXSide = visibleSide } else if visibleSide == .none { self.horizonalXSide = .none } break case .ended,.cancelled: self.startingPanRect = CGRect.null let velocity = pan.velocity(in: self.containerView) finishAnimationForPanGestureWithXVelocity(xVelocity: velocity.x) { (finished) in } self.view.isUserInteractionEnabled = true break default: break } } private func finishAnimationForPanGestureWithXVelocity(xVelocity:CGFloat, completion:(_ finished:Bool)->Void) { var currentOriginX = self.contentView.frame.minX // let animationVelocity = max(abs(xVelocity), self.panVelocityXAnimationThreshold * 2) if horizonalXSide == .left { let midPoint = self.maximumLeftOffsetWidth / 2.0 if xVelocity > self.panVelocityXAnimationThreshold { self.showLeftViewController() } else if xVelocity < -self.panVelocityXAnimationThreshold { self.closeSideViewController() } else if currentOriginX < midPoint { self.closeSideViewController() } else { self.showLeftViewController() } } else if horizonalXSide == .right { currentOriginX = self.contentView.frame.maxX let midPoint = self.containerView.bounds.width - self.maximumRightOffsetWidth + self.maximumRightOffsetWidth / 2.0 if xVelocity > self.panVelocityXAnimationThreshold { self.closeSideViewController() } else if xVelocity < -self.panVelocityXAnimationThreshold { self.showRightViewController() } else if currentOriginX > midPoint { self.closeSideViewController() } else { self.showRightViewController() } } } @objc fileprivate func maskAction(_ tap:UITapGestureRecognizer){ if !isCloseLeftSide { closeSideViewController() }else if !isCloseRightSide{ closeSideViewController() } } private func setSideViewController(side:HorizonalXSideType) { var autoResizingMask = UIView.AutoresizingMask.init(rawValue: 0) autoResizingMask = autoResizingMask.union(UIView.AutoresizingMask.flexibleHeight) var viewController:UIViewController! if side == .left { viewController = leftViewController autoResizingMask = autoResizingMask.union(.flexibleRightMargin) } else { viewController = rightViewController autoResizingMask = autoResizingMask.union(.flexibleLeftMargin) } addChild(viewController) containerView.addSubview(viewController.view) containerView.sendSubviewToBack(viewController.view) viewController.view.isHidden = true viewController.didMove(toParent: self) viewController.view.autoresizingMask = autoResizingMask } private func setContentViewController() { if let viewController = contentViewController { let nav = ZSBaseNavigationViewController(rootViewController: viewController) addChild(nav) nav.view.frame = view.bounds nav.view.backgroundColor = UIColor.gray self.contentView.addSubview(nav.view) var autoResizingMask = UIView.AutoresizingMask.flexibleWidth autoResizingMask = autoResizingMask.union(UIView.AutoresizingMask.flexibleHeight) nav.view.autoresizingMask = autoResizingMask nav.didMove(toParent: self) } } /** 显示左侧滑菜单,若已处于显示状态,调用该方法将关闭左侧滑菜单 */ func showLeftViewController(){ var newFrame = CGRect.zero leftViewController?.view.isHidden = false rightViewController?.view.isHidden = true _ = self.contentView.frame; newFrame = self.contentView.frame newFrame.origin.x = maximumLeftOffsetWidth // var distance = abs(oldFrame.minX - newFrame.origin.x) self.contentView.bringSubviewToFront(self.maskView) UIView.animate(withDuration: 0.25, animations: { self.contentView.frame = newFrame self.maskView.isHidden = false }) { (finish) in self.isCloseLeftSide = false self.horizonalXSide = .left } // contentView.addSubview(self.maskView) } /** 显示右侧滑菜单,若已处于显示状态,调用该方法将关闭右侧滑菜单 */ func showRightViewController(){ var newFrame = CGRect.zero leftViewController?.view.isHidden = true rightViewController?.view.isHidden = false _ = self.contentView.frame; newFrame = self.contentView.frame newFrame.origin.x = 0 - maximumRightOffsetWidth // var distance = abs(oldFrame.minX - newFrame.origin.x) self.contentView.bringSubviewToFront(self.maskView) UIView.animate(withDuration: 0.25, animations: { self.contentView.frame = newFrame self.maskView.isHidden = false }) { (finish) in self.isCloseRightSide = false self.horizonalXSide = .right } // contentView.addSubview(self.maskView) } /** 关闭侧滑菜单 */ func closeSideViewController(){ UIView.animate(withDuration: animateDuration, animations: { self.contentView.frame = self.containerView.bounds self.maskView.isHidden = true }, completion: { (finished) in self.isCloseLeftSide = true self.isCloseRightSide = true self.leftViewController?.view.isHidden = false self.rightViewController?.view.isHidden = false }) } fileprivate func updateContentViewShadow(){ shadowView.layer.masksToBounds = false shadowView.layer.shadowRadius = 3 shadowView.layer.shadowOpacity = 1.0 shadowView.layer.shadowOffset = CGSize.zero shadowView.layer.shadowColor = UIColor.black.cgColor if shadowView.layer.shadowPath == nil { shadowView.layer.shadowPath = UIBezierPath(rect: self.contentView.bounds).cgPath } else { let currentPath = shadowView.layer.shadowPath?.boundingBoxOfPath if currentPath?.equalTo(shadowView.bounds) == false { shadowView.layer.shadowPath = UIBezierPath(rect: self.contentView.bounds).cgPath } } } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() } static let shared = SideViewController() override var preferredStatusBarStyle : UIStatusBarStyle { return .lightContent } } ================================================ FILE: zhuishushenqi/Base/Controllers/ZSBaseNavigationViewController.swift ================================================ // // ZSBaseNavigationViewController.swift // zhuishushenqi // // Created by yung on 2018/8/15. // Copyright © 2018年 QS. All rights reserved. // import UIKit class ZSBaseNavigationViewController: UINavigationController { override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. setupNavigationItem() } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } func setupNavigationItem(){ let leftBar:UIBarButtonItem = UIBarButtonItem(title: "返回", style: UIBarButtonItem.Style.plain, target: self, action: #selector(popAction)) self.navigationItem.leftBarButtonItem = leftBar } @objc func popAction(){ self.popViewController(animated: true) } override var shouldAutorotate: Bool { return self.topViewController?.shouldAutorotate ?? false } override var supportedInterfaceOrientations: UIInterfaceOrientationMask { return topViewController?.supportedInterfaceOrientations ?? .all } override var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation { return topViewController?.preferredInterfaceOrientationForPresentation ?? .portrait } /* // MARK: - Navigation // In a storyboard-based application, you will often want to do a little preparation before navigation override func prepare(for segue: UIStoryboardSegue, sender: Any?) { // Get the new view controller using segue.destinationViewController. // Pass the selected object to the new view controller. } */ } ================================================ FILE: zhuishushenqi/Base/Controllers/ZSBaseTableViewController.swift ================================================ // // ZSBaseTableViewController.swift // zhuishushenqi // // Created by caony on 2018/6/7. // Copyright © 2018年 QS. All rights reserved. // import UIKit class ZSBaseTableViewController: UITableViewController, IndicatableView { override func viewDidLoad() { super.viewDidLoad() self.automaticallyAdjustsScrollViewInsets = false self.view.backgroundColor = UIColor.white self.tableView.backgroundColor = UIColor(red: 0.95, green: 0.95, blue: 0.95, alpha: 1.0) navigationController?.navigationBar.tintColor = UIColor.red navigationController?.navigationBar.barTintColor = UIColor.white let backItem = UIBarButtonItem(title: "返回", style: .plain, target: self, action: #selector(popAction)) self.navigationItem.backBarButtonItem = backItem register() } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) navigationController?.isNavigationBarHidden = false } override var preferredStatusBarStyle : UIStatusBarStyle { return .default } override var prefersStatusBarHidden : Bool { return false } @objc func popAction(){ self.navigationController?.popViewController(animated: true) } private func register(){ let classes = registerCellClasses() for cls in classes { self.tableView.qs_registerCellClass(cls as! UITableViewCell.Type) } let nibClasses = registerCellNibs() for cls in nibClasses { self.tableView.qs_registerCellNib(cls as! UITableViewCell.Type) } let headerClasses = registerHeaderViewClasses() for cls in headerClasses { self.tableView.qs_registerHeaderFooterClass(cls as! UITableViewHeaderFooterView.Type) } let footerClasses = registerFooterViewClasses() for cls in footerClasses { self.tableView.qs_registerHeaderFooterClass(cls as! UITableViewHeaderFooterView.Type) } } func registerHeaderViewClasses() -> Array { return [] } func registerFooterViewClasses() -> Array { return [] } func registerCellClasses() -> Array { return [] } func registerCellNibs() -> Array { return [] } //MARK: - progress func showProgress() { self.view.addSubview(self.indicatorView) self.view.bringSubviewToFront(self.indicatorView) } func hideProgress() { self.indicatorView.stopAnimating() self.indicatorView.removeFromSuperview() } lazy var indicatorView:UIActivityIndicatorView = { let indicator = UIActivityIndicatorView(style: .gray) indicator.frame = CGRect(x: ScreenWidth/2 - 50 , y: ScreenHeight/2 - 50, width: 100, height: 100) indicator.startAnimating() return indicator }() } ================================================ FILE: zhuishushenqi/Base/Controllers/ZSSegmentViewController.swift ================================================ // // ZSSegmentViewController.swift // zhuishushenqi // // Created by yung on 2018/8/13. // Copyright © 2018年 QS. All rights reserved. // import UIKit protocol ZSSegmentProtocol:class { func segmentViewControllers() ->[UIViewController] } class ZSSegmentViewController: UIViewController ,UICollectionViewDelegate{ weak var delegate:ZSSegmentProtocol? private var collectionView:UICollectionView! var scrollViewDidEndDeceleratingHandler:ZSSegmentHandler? override func viewDidLoad() { super.viewDidLoad() } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) configureCollectionView() collectionView.snp.makeConstraints { (make) in make.left.top.right.bottom.equalToSuperview() } if #available(iOS 11.0, *) { collectionView.contentInsetAdjustmentBehavior = .never } else { // Fallback on earlier versions } } override func viewWillLayoutSubviews() { collectionView.snp.makeConstraints { (make) in make.left.top.right.bottom.equalToSuperview() } collectionView.reloadData() } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } func scrollToItem(at indexPath: IndexPath, at scrollPosition: UICollectionView.ScrollPosition, animated: Bool) { self.collectionView.scrollToItem(at: indexPath, at: scrollPosition, animated: animated) } func didSelect(index:Int) { let indexPath = IndexPath(item: index, section: 0) collectionView.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: true) } fileprivate func configureCollectionView() { let layout = UICollectionViewFlowLayout() layout.minimumLineSpacing = 0.01 layout.minimumInteritemSpacing = 0.01 layout.scrollDirection = .horizontal collectionView = UICollectionView(frame: CGRect.zero, collectionViewLayout: layout) collectionView.dataSource = self collectionView.delegate = self collectionView.isPagingEnabled = true collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "UICollectionViewCell") collectionView.showsHorizontalScrollIndicator = false collectionView.backgroundColor = UIColor.white view.addSubview(collectionView) } } extension ZSSegmentViewController:UICollectionViewDataSource, UICollectionViewDelegateFlowLayout { //MARK: - public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { if let viewControllers = self.delegate?.segmentViewControllers() { return viewControllers.count } return 0 } // The cell that is returned must be retrieved from a call to -dequeueReusableCellWithReuseIdentifier:forIndexPath: @available(iOS 6.0, *) public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "UICollectionViewCell", for: indexPath) cell.contentView.backgroundColor = UIColor.white if let viewControllers = self.delegate?.segmentViewControllers() { cell.contentView.removeAllSubviews() let viewController = viewControllers[indexPath.item] viewController.view.frame = cell.contentView.bounds if let _ = viewController.view.superview { viewController.view.removeFromSuperview() } if let _ = viewController.parent { viewController.removeFromParent() } addChild(viewController) cell.contentView.addSubview(viewController.view) } return cell } func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { return CGSize(width: self.view.bounds.width, height: self.view.bounds.height) } } extension ZSSegmentViewController:UIScrollViewDelegate { func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { let index:Int = Int(abs(scrollView.contentOffset.x/self.view.bounds.width)) scrollViewDidEndDeceleratingHandler?(index) } } ================================================ FILE: zhuishushenqi/Base/Controllers/ZSSegmenuViewController.swift ================================================ // // ZSSegmenuViewController.swift // zhuishushenqi // // Created by caony on 2019/3/23. // Copyright © 2019年 QS. All rights reserved. // import UIKit typealias ZSSegmentHandler = (_ index:Int)->Void protocol ZSSegmenuProtocol:class { func viewControllersForSegmenu(_ segmenu:ZSSegmenuViewController) ->[UIViewController] func segmenu(_ segmenu:ZSSegmenuViewController, didSelectSegAt index:Int) func segmenu(_ segmenu:ZSSegmenuViewController, didScrollToSegAt index:Int) } class ZSSegmenuViewController: UIViewController, ZSSegmentProtocol { fileprivate var segMenu:SegMenu! fileprivate var segmentViewController:ZSSegmentViewController = ZSSegmentViewController() weak var delegate:ZSSegmenuProtocol? override func viewDidLoad() { super.viewDidLoad() if let viewControllers = self.delegate?.viewControllersForSegmenu(self) { setupSubviews(viewControllers: viewControllers) } } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) layoutSubviews() } override func viewWillLayoutSubviews() { layoutSubviews() } private func layoutSubviews() { segMenu.snp.remakeConstraints { (make) in let statusHeight = UIApplication.shared.statusBarFrame.height let navHeight = self.navigationController?.navigationBar.height ?? 0 make.left.right.equalToSuperview() make.top.equalToSuperview().offset(statusHeight + navHeight) make.height.equalTo(kTootSegmentViewHeight) } segmentViewController.view.snp.remakeConstraints { (make) in make.left.bottom.right.equalToSuperview() make.top.equalTo(segMenu.snp.bottom) } } fileprivate func setupSubviews(viewControllers:[UIViewController]) { var titles:[String] = [] for controller in viewControllers { titles.append(controller.title ?? "") } segMenu = SegMenu(frame: CGRect.zero, WithTitles: titles) segMenu.menuDelegate = self self.view.addSubview(segMenu) segmentViewController.scrollViewDidEndDeceleratingHandler = { [weak self] index in guard let sSelf = self else { return } sSelf.segMenu.selectIndex(index) } addChild(segmentViewController) view.addSubview(segmentViewController.view) segmentViewController.delegate = self } func segmentViewControllers() -> [UIViewController] { if let viewControllers = self.delegate?.viewControllersForSegmenu(self) { return viewControllers } return [] } } extension ZSSegmenuViewController:SegMenuDelegate{ //MARK: - SegMenuDelegate func didSelectAtIndex(_ index:Int){ let indexPath = IndexPath(row: index, section: 0) self.segmentViewController.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: false) } } ================================================ FILE: zhuishushenqi/Base/Models/SwiftyJSON.swift ================================================ // SwiftyJSON.swift // // Copyright (c) 2014 - 2016 Ruoyu Fu, Pinglin Tang // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. import Foundation // MARK: - Error ///Error domain public let ErrorDomain: String = "SwiftyJSONErrorDomain" ///Error code public let ErrorUnsupportedType: Int = 999 public let ErrorIndexOutOfBounds: Int = 900 public let ErrorWrongType: Int = 901 public let ErrorNotExist: Int = 500 public let ErrorInvalidJSON: Int = 490 // MARK: - JSON Type /** JSON's type definitions. See http://www.json.org */ public enum Type :Int{ case number case string case bool case array case dictionary case null case unknown } // MARK: - JSON Base public struct JSON { /** Creates a JSON using the data. - parameter data: The NSData used to convert to json.Top level object in data is an NSArray or NSDictionary - parameter opt: The JSON serialization reading options. `.AllowFragments` by default. - parameter error: The NSErrorPointer used to return the error. `nil` by default. - returns: The created JSON */ public init(data:Data, options opt: JSONSerialization.ReadingOptions = .allowFragments, error: NSErrorPointer = nil) { do { let object: Any = try JSONSerialization.jsonObject(with: data, options: opt) self.init(object) } catch let aError as NSError { if error != nil { error?.pointee = aError } self.init(NSNull()) } } /** Creates a JSON from JSON string - parameter string: Normal json string like '{"a":"b"}' - returns: The created JSON */ public static func parse(_ string:String) -> JSON { return string.data(using: String.Encoding.utf8) .flatMap{ JSON(data: $0) } ?? JSON(NSNull()) } /** Creates a JSON using the object. - parameter object: The object must have the following properties: All objects are NSString/String, NSNumber/Int/Float/Double/Bool, NSArray/Array, NSDictionary/Dictionary, or NSNull; All dictionary keys are NSStrings/String; NSNumbers are not NaN or infinity. - returns: The created JSON */ public init(_ object: Any) { self.object = object } /** Creates a JSON from a [JSON] - parameter jsonArray: A Swift array of JSON objects - returns: The created JSON */ public init(_ jsonArray:[JSON]) { self.init(jsonArray.map { $0.object }) } /** Creates a JSON from a [String: JSON] - parameter jsonDictionary: A Swift dictionary of JSON objects - returns: The created JSON */ public init(_ jsonDictionary:[String: JSON]) { var dictionary = [String: Any](minimumCapacity: jsonDictionary.count) for (key, json) in jsonDictionary { dictionary[key] = json.object } self.init(dictionary) } /// Private object fileprivate var rawArray: [Any] = [] fileprivate var rawDictionary: [String : Any] = [:] fileprivate var rawString: String = "" fileprivate var rawNumber: NSNumber = 0 fileprivate var rawNull: NSNull = NSNull() fileprivate var rawBool: Bool = false /// Private type fileprivate var _type: Type = .null /// prviate error fileprivate var _error: NSError? = nil /// Object in JSON public var object: Any { get { switch self.type { case .array: return self.rawArray case .dictionary: return self.rawDictionary case .string: return self.rawString case .number: return self.rawNumber case .bool: return self.rawBool default: return self.rawNull } } set { _error = nil switch newValue { case let number as NSNumber: if number.isBool { _type = .bool self.rawBool = number.boolValue } else { _type = .number self.rawNumber = number } case let string as String: _type = .string self.rawString = string case _ as NSNull: _type = .null case let array as [JSON]: _type = .array self.rawArray = array.map { $0.object } case let array as [Any]: _type = .array self.rawArray = array case let dictionary as [String : Any]: _type = .dictionary self.rawDictionary = dictionary default: _type = .unknown _error = NSError(domain: ErrorDomain, code: ErrorUnsupportedType, userInfo: [NSLocalizedDescriptionKey: "It is a unsupported type"]) } } } /// JSON type public var type: Type { get { return _type } } /// Error in JSON public var error: NSError? { get { return self._error } } /// The static null JSON @available(*, unavailable, renamed:"null") public static var nullJSON: JSON { get { return null } } public static var null: JSON { get { return JSON(NSNull()) } } } public enum JSONIndex:Comparable { case array(Int) case dictionary(DictionaryIndex) case null static public func ==(lhs: JSONIndex, rhs: JSONIndex) -> Bool { switch (lhs, rhs) { case (.array(let left), .array(let right)): return left == right case (.dictionary(let left), .dictionary(let right)): return left == right case (.null, .null): return true default: return false } } static public func <(lhs: JSONIndex, rhs: JSONIndex) -> Bool { switch (lhs, rhs) { case (.array(let left), .array(let right)): return left < right case (.dictionary(let left), .dictionary(let right)): return left < right default: return false } } } public enum JSONRawIndex: Comparable { case array(Int) case dictionary(DictionaryIndex) case null static public func ==(lhs: JSONRawIndex, rhs: JSONRawIndex) -> Bool { switch (lhs, rhs) { case (.array(let left), .array(let right)): return left == right case (.dictionary(let left), .dictionary(let right)): return left == right case (.null, .null): return true default: return false } } static public func <(lhs: JSONRawIndex, rhs: JSONRawIndex) -> Bool { switch (lhs, rhs) { case (.array(let left), .array(let right)): return left < right case (.dictionary(let left), .dictionary(let right)): return left < right default: return false } } } extension JSON: Collection { public typealias Index = JSONRawIndex public var startIndex: Index { switch type { case .array: return .array(rawArray.startIndex) case .dictionary: return .dictionary(rawDictionary.startIndex) default: return .null } } public var endIndex: Index { switch type { case .array: return .array(rawArray.endIndex) case .dictionary: return .dictionary(rawDictionary.endIndex) default: return .null } } public func index(after i: Index) -> Index { switch i { case .array(let idx): return .array(rawArray.index(after: idx)) case .dictionary(let idx): return .dictionary(rawDictionary.index(after: idx)) default: return .null } } public subscript (position: Index) -> (String, JSON) { switch position { case .array(let idx): return (String(idx), JSON(self.rawArray[idx])) case .dictionary(let idx): let (key, value) = self.rawDictionary[idx] return (key, JSON(value)) default: return ("", JSON.null) } } } // MARK: - Subscript /** * To mark both String and Int can be used in subscript. */ public enum JSONKey { case index(Int) case key(String) } public protocol JSONSubscriptType { var jsonKey:JSONKey { get } } extension Int: JSONSubscriptType { public var jsonKey:JSONKey { return JSONKey.index(self) } } extension String: JSONSubscriptType { public var jsonKey:JSONKey { return JSONKey.key(self) } } extension JSON { /// If `type` is `.Array`, return json whose object is `array[index]`, otherwise return null json with error. fileprivate subscript(index index: Int) -> JSON { get { if self.type != .array { var r = JSON.null r._error = self._error ?? NSError(domain: ErrorDomain, code: ErrorWrongType, userInfo: [NSLocalizedDescriptionKey: "Array[\(index)] failure, It is not an array"]) return r } else if index >= 0 && index < self.rawArray.count { return JSON(self.rawArray[index]) } else { var r = JSON.null r._error = NSError(domain: ErrorDomain, code:ErrorIndexOutOfBounds , userInfo: [NSLocalizedDescriptionKey: "Array[\(index)] is out of bounds"]) return r } } set { if self.type == .array { if self.rawArray.count > index && newValue.error == nil { self.rawArray[index] = newValue.object } } } } /// If `type` is `.Dictionary`, return json whose object is `dictionary[key]` , otherwise return null json with error. fileprivate subscript(key key: String) -> JSON { get { var r = JSON.null if self.type == .dictionary { if let o = self.rawDictionary[key] { r = JSON(o) } else { r._error = NSError(domain: ErrorDomain, code: ErrorNotExist, userInfo: [NSLocalizedDescriptionKey: "Dictionary[\"\(key)\"] does not exist"]) } } else { r._error = self._error ?? NSError(domain: ErrorDomain, code: ErrorWrongType, userInfo: [NSLocalizedDescriptionKey: "Dictionary[\"\(key)\"] failure, It is not an dictionary"]) } return r } set { if self.type == .dictionary && newValue.error == nil { self.rawDictionary[key] = newValue.object } } } /// If `sub` is `Int`, return `subscript(index:)`; If `sub` is `String`, return `subscript(key:)`. fileprivate subscript(sub sub: JSONSubscriptType) -> JSON { get { switch sub.jsonKey { case .index(let index): return self[index: index] case .key(let key): return self[key: key] } } set { switch sub.jsonKey { case .index(let index): self[index: index] = newValue case .key(let key): self[key: key] = newValue } } } /** Find a json in the complex data structures by using array of Int and/or String as path. - parameter path: The target json's path. Example: let json = JSON[data] let path = [9,"list","person","name"] let name = json[path] The same as: let name = json[9]["list"]["person"]["name"] - returns: Return a json found by the path or a null json with error */ public subscript(path: [JSONSubscriptType]) -> JSON { get { return path.reduce(self) { $0[sub: $1] } } set { switch path.count { case 0: return case 1: self[sub:path[0]].object = newValue.object default: var aPath = path; aPath.remove(at: 0) var nextJSON = self[sub: path[0]] nextJSON[aPath] = newValue self[sub: path[0]] = nextJSON } } } /** Find a json in the complex data structures by using array of Int and/or String as path. - parameter path: The target json's path. Example: let name = json[9,"list","person","name"] The same as: let name = json[9]["list"]["person"]["name"] - returns: Return a json found by the path or a null json with error */ public subscript(path: JSONSubscriptType...) -> JSON { get { return self[path] } set { self[path] = newValue } } } // MARK: - LiteralConvertible extension JSON: Swift.ExpressibleByStringLiteral { public init(stringLiteral value: StringLiteralType) { self.init(value as Any) } public init(extendedGraphemeClusterLiteral value: StringLiteralType) { self.init(value as Any) } public init(unicodeScalarLiteral value: StringLiteralType) { self.init(value as Any) } } extension JSON: Swift.ExpressibleByIntegerLiteral { public init(integerLiteral value: IntegerLiteralType) { self.init(value as Any) } } extension JSON: Swift.ExpressibleByBooleanLiteral { public init(booleanLiteral value: BooleanLiteralType) { self.init(value as Any) } } extension JSON: Swift.ExpressibleByFloatLiteral { public init(floatLiteral value: FloatLiteralType) { self.init(value as Any) } } extension JSON: Swift.ExpressibleByDictionaryLiteral { public init(dictionaryLiteral elements: (String, Any)...) { let array = elements self.init(dictionaryLiteral: array) } public init(dictionaryLiteral elements: [(String, Any)]) { let jsonFromDictionaryLiteral: ([String : Any]) -> JSON = { dictionary in let initializeElement = Array(dictionary.keys).flatMap { key -> (String, Any)? in if let value = dictionary[key] { return (key, value) } return nil } return JSON(dictionaryLiteral: initializeElement) } var dict = [String : Any](minimumCapacity: elements.count) for element in elements { let elementToSet: Any if let json = element.1 as? JSON { elementToSet = json.object } else if let jsonArray = element.1 as? [JSON] { elementToSet = JSON(jsonArray).object } else if let dictionary = element.1 as? [String : Any] { elementToSet = jsonFromDictionaryLiteral(dictionary).object } else if let dictArray = element.1 as? [[String : Any]] { let jsonArray = dictArray.map { jsonFromDictionaryLiteral($0) } elementToSet = JSON(jsonArray).object } else { elementToSet = element.1 } dict[element.0] = elementToSet } self.init(dict) } } extension JSON: Swift.ExpressibleByArrayLiteral { public init(arrayLiteral elements: Any...) { self.init(elements as Any) } } extension JSON: Swift.ExpressibleByNilLiteral { @available(*, deprecated, message: "use JSON.null instead. Will be removed in future versions") public init(nilLiteral: ()) { self.init(NSNull() as Any) } } // MARK: - Raw extension JSON: Swift.RawRepresentable { public init?(rawValue: Any) { if JSON(rawValue).type == .unknown { return nil } else { self.init(rawValue) } } public var rawValue: Any { return self.object } public func rawData(options opt: JSONSerialization.WritingOptions = JSONSerialization.WritingOptions(rawValue: 0)) throws -> Data { guard JSONSerialization.isValidJSONObject(self.object) else { throw NSError(domain: ErrorDomain, code: ErrorInvalidJSON, userInfo: [NSLocalizedDescriptionKey: "JSON is invalid"]) } return try JSONSerialization.data(withJSONObject: self.object, options: opt) } public func rawString(_ encoding: String.Encoding = String.Encoding.utf8, options opt: JSONSerialization.WritingOptions = .prettyPrinted) -> String? { switch self.type { case .array, .dictionary: do { let data = try self.rawData(options: opt) return String(data: data, encoding: encoding) } catch _ { return nil } case .string: return self.rawString case .number: return self.rawNumber.stringValue case .bool: return self.rawBool.description case .null: return "null" default: return nil } } } // MARK: - Printable, DebugPrintable extension JSON: Swift.CustomStringConvertible, Swift.CustomDebugStringConvertible { public var description: String { if let string = self.rawString(options:.prettyPrinted) { return string } else { return "unknown" } } public var debugDescription: String { return description } } // MARK: - Array extension JSON { //Optional [JSON] public var array: [JSON]? { get { if self.type == .array { return self.rawArray.map{ JSON($0) } } else { return nil } } } //Non-optional [JSON] public var arrayValue: [JSON] { get { return self.array ?? [] } } //Optional [Any] public var arrayObject: [Any]? { get { switch self.type { case .array: return self.rawArray default: return nil } } set { if let array = newValue { self.object = array as Any } else { self.object = NSNull() } } } } // MARK: - Dictionary extension JSON { //Optional [String : JSON] public var dictionary: [String : JSON]? { if self.type == .dictionary { var d = [String : JSON](minimumCapacity: rawDictionary.count) for (key, value) in rawDictionary { d[key] = JSON(value) } return d } else { return nil } } //Non-optional [String : JSON] public var dictionaryValue: [String : JSON] { return self.dictionary ?? [:] } //Optional [String : Any] public var dictionaryObject: [String : Any]? { get { switch self.type { case .dictionary: return self.rawDictionary default: return nil } } set { if let v = newValue { self.object = v as Any } else { self.object = NSNull() } } } } // MARK: - Bool extension JSON { // : Swift.Bool //Optional bool public var bool: Bool? { get { switch self.type { case .bool: return self.rawBool default: return nil } } set { if let newValue = newValue { self.object = newValue as Bool } else { self.object = NSNull() } } } //Non-optional bool public var boolValue: Bool { get { switch self.type { case .bool: return self.rawBool case .number: return self.rawNumber.boolValue case .string: return ["true", "y", "t"].contains() { (truthyString) in return self.rawString.caseInsensitiveCompare(truthyString) == .orderedSame } default: return false } } set { self.object = newValue } } } // MARK: - String extension JSON { //Optional string public var string: String? { get { switch self.type { case .string: return self.object as? String default: return nil } } set { if let newValue = newValue { self.object = NSString(string:newValue) } else { self.object = NSNull() } } } //Non-optional string public var stringValue: String { get { switch self.type { case .string: return self.object as? String ?? "" case .number: return self.rawNumber.stringValue case .bool: return (self.object as? Bool).map { String($0) } ?? "" default: return "" } } set { self.object = NSString(string:newValue) } } } // MARK: - Number extension JSON { //Optional number public var number: NSNumber? { get { switch self.type { case .number: return self.rawNumber case .bool: return NSNumber(value: self.rawBool ? 1 : 0) default: return nil } } set { self.object = newValue ?? NSNull() } } //Non-optional number public var numberValue: NSNumber { get { switch self.type { case .string: let decimal = NSDecimalNumber(string: self.object as? String) if decimal == NSDecimalNumber.notANumber { // indicates parse error return NSDecimalNumber.zero } return decimal case .number: return self.object as? NSNumber ?? NSNumber(value: 0) case .bool: return NSNumber(value: self.rawBool ? 1 : 0) default: return NSNumber(value: 0.0) } } set { self.object = newValue } } } //MARK: - Null extension JSON { public var null: NSNull? { get { switch self.type { case .null: return self.rawNull default: return nil } } set { self.object = NSNull() } } public func exists() -> Bool{ if let errorValue = error, errorValue.code == ErrorNotExist || errorValue.code == ErrorIndexOutOfBounds || errorValue.code == ErrorWrongType { return false } return true } } //MARK: - URL extension JSON { //Optional URL public var URL: URL? { get { switch self.type { case .string: if let encodedString_ = self.rawString.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed) { // We have to use `Foundation.URL` otherwise it conflicts with the variable name. return Foundation.URL(string: encodedString_) } else { return nil } default: return nil } } set { self.object = newValue?.absoluteString ?? NSNull() } } } // MARK: - Int, Double, Float, Int8, Int16, Int32, Int64 extension JSON { public var double: Double? { get { return self.number?.doubleValue } set { if let newValue = newValue { self.object = NSNumber(value: newValue) } else { self.object = NSNull() } } } public var doubleValue: Double { get { return self.numberValue.doubleValue } set { self.object = NSNumber(value: newValue) } } public var float: Float? { get { return self.number?.floatValue } set { if let newValue = newValue { self.object = NSNumber(value: newValue) } else { self.object = NSNull() } } } public var floatValue: Float { get { return self.numberValue.floatValue } set { self.object = NSNumber(value: newValue) } } public var int: Int? { get { return self.number?.intValue } set { if let newValue = newValue { self.object = NSNumber(value: newValue) } else { self.object = NSNull() } } } public var intValue: Int { get { return self.numberValue.intValue } set { self.object = NSNumber(value: newValue) } } public var uInt: UInt? { get { return self.number?.uintValue } set { if let newValue = newValue { self.object = NSNumber(value: newValue) } else { self.object = NSNull() } } } public var uIntValue: UInt { get { return self.numberValue.uintValue } set { self.object = NSNumber(value: newValue) } } public var int8: Int8? { get { return self.number?.int8Value } set { if let newValue = newValue { self.object = NSNumber(value: newValue) } else { self.object = NSNull() } } } public var int8Value: Int8 { get { return self.numberValue.int8Value } set { self.object = NSNumber(value: newValue) } } public var uInt8: UInt8? { get { return self.number?.uint8Value } set { if let newValue = newValue { self.object = NSNumber(value: newValue) } else { self.object = NSNull() } } } public var uInt8Value: UInt8 { get { return self.numberValue.uint8Value } set { self.object = NSNumber(value: newValue) } } public var int16: Int16? { get { return self.number?.int16Value } set { if let newValue = newValue { self.object = NSNumber(value: newValue) } else { self.object = NSNull() } } } public var int16Value: Int16 { get { return self.numberValue.int16Value } set { self.object = NSNumber(value: newValue) } } public var uInt16: UInt16? { get { return self.number?.uint16Value } set { if let newValue = newValue { self.object = NSNumber(value: newValue) } else { self.object = NSNull() } } } public var uInt16Value: UInt16 { get { return self.numberValue.uint16Value } set { self.object = NSNumber(value: newValue) } } public var int32: Int32? { get { return self.number?.int32Value } set { if let newValue = newValue { self.object = NSNumber(value: newValue) } else { self.object = NSNull() } } } public var int32Value: Int32 { get { return self.numberValue.int32Value } set { self.object = NSNumber(value: newValue) } } public var uInt32: UInt32? { get { return self.number?.uint32Value } set { if let newValue = newValue { self.object = NSNumber(value: newValue) } else { self.object = NSNull() } } } public var uInt32Value: UInt32 { get { return self.numberValue.uint32Value } set { self.object = NSNumber(value: newValue) } } public var int64: Int64? { get { return self.number?.int64Value } set { if let newValue = newValue { self.object = NSNumber(value: newValue) } else { self.object = NSNull() } } } public var int64Value: Int64 { get { return self.numberValue.int64Value } set { self.object = NSNumber(value: newValue) } } public var uInt64: UInt64? { get { return self.number?.uint64Value } set { if let newValue = newValue { self.object = NSNumber(value: newValue) } else { self.object = NSNull() } } } public var uInt64Value: UInt64 { get { return self.numberValue.uint64Value } set { self.object = NSNumber(value: newValue) } } } //MARK: - Comparable extension JSON : Swift.Comparable {} public func ==(lhs: JSON, rhs: JSON) -> Bool { switch (lhs.type, rhs.type) { case (.number, .number): return lhs.rawNumber == rhs.rawNumber case (.string, .string): return lhs.rawString == rhs.rawString case (.bool, .bool): return lhs.rawBool == rhs.rawBool case (.array, .array): return lhs.rawArray as NSArray == rhs.rawArray as NSArray case (.dictionary, .dictionary): return lhs.rawDictionary as NSDictionary == rhs.rawDictionary as NSDictionary case (.null, .null): return true default: return false } } public func <=(lhs: JSON, rhs: JSON) -> Bool { switch (lhs.type, rhs.type) { case (.number, .number): return lhs.rawNumber <= rhs.rawNumber case (.string, .string): return lhs.rawString <= rhs.rawString case (.bool, .bool): return lhs.rawBool == rhs.rawBool case (.array, .array): return lhs.rawArray as NSArray == rhs.rawArray as NSArray case (.dictionary, .dictionary): return lhs.rawDictionary as NSDictionary == rhs.rawDictionary as NSDictionary case (.null, .null): return true default: return false } } public func >=(lhs: JSON, rhs: JSON) -> Bool { switch (lhs.type, rhs.type) { case (.number, .number): return lhs.rawNumber >= rhs.rawNumber case (.string, .string): return lhs.rawString >= rhs.rawString case (.bool, .bool): return lhs.rawBool == rhs.rawBool case (.array, .array): return lhs.rawArray as NSArray == rhs.rawArray as NSArray case (.dictionary, .dictionary): return lhs.rawDictionary as NSDictionary == rhs.rawDictionary as NSDictionary case (.null, .null): return true default: return false } } public func >(lhs: JSON, rhs: JSON) -> Bool { switch (lhs.type, rhs.type) { case (.number, .number): return lhs.rawNumber > rhs.rawNumber case (.string, .string): return lhs.rawString > rhs.rawString default: return false } } public func <(lhs: JSON, rhs: JSON) -> Bool { switch (lhs.type, rhs.type) { case (.number, .number): return lhs.rawNumber < rhs.rawNumber case (.string, .string): return lhs.rawString < rhs.rawString default: return false } } private let trueNumber = NSNumber(value: true) private let falseNumber = NSNumber(value: false) private let trueObjCType = String(cString: trueNumber.objCType) private let falseObjCType = String(cString: falseNumber.objCType) // MARK: - NSNumber: Comparable extension NSNumber { var isBool:Bool { get { let objCType = String(cString: self.objCType) if (self.compare(trueNumber) == .orderedSame && objCType == trueObjCType) || (self.compare(falseNumber) == .orderedSame && objCType == falseObjCType){ return true } else { return false } } } } func ==(lhs: NSNumber, rhs: NSNumber) -> Bool { switch (lhs.isBool, rhs.isBool) { case (false, true): return false case (true, false): return false default: return lhs.compare(rhs) == .orderedSame } } func !=(lhs: NSNumber, rhs: NSNumber) -> Bool { return !(lhs == rhs) } func <(lhs: NSNumber, rhs: NSNumber) -> Bool { switch (lhs.isBool, rhs.isBool) { case (false, true): return false case (true, false): return false default: return lhs.compare(rhs) == .orderedAscending } } func >(lhs: NSNumber, rhs: NSNumber) -> Bool { switch (lhs.isBool, rhs.isBool) { case (false, true): return false case (true, false): return false default: return lhs.compare(rhs) == ComparisonResult.orderedDescending } } func <=(lhs: NSNumber, rhs: NSNumber) -> Bool { switch (lhs.isBool, rhs.isBool) { case (false, true): return false case (true, false): return false default: return lhs.compare(rhs) != .orderedDescending } } func >=(lhs: NSNumber, rhs: NSNumber) -> Bool { switch (lhs.isBool, rhs.isBool) { case (false, true): return false case (true, false): return false default: return lhs.compare(rhs) != .orderedAscending } } ================================================ FILE: zhuishushenqi/Base/Models/XYCBaseModel.h ================================================ // // CSIIBaseModel.h // O2O_Clients // // Created by chaozhi on 16/3/9. // Copyright © 2016年 QS. All rights reserved. // #import @interface XYCBaseModel : NSObject +(NSArray *)modelWithModleClass:(Class )modelClass withJsArray:(NSArray *)JSONArray withError:(NSError **)error; @end ================================================ FILE: zhuishushenqi/Base/Models/XYCBaseModel.m ================================================ // // CSIIBaseModel.m // O2O_Clients // // Created by chaozhi on 16/3/9. // Copyright © 2016年 QS. All rights reserved. // #import "XYCBaseModel.h" #import "YYModel.h" @implementation XYCBaseModel +(NSArray *)modelWithModleClass:(Class )modelClass withJsArray:(NSArray *)JSONArray withError:(NSError **)error; { if (JSONArray == nil || ![JSONArray isKindOfClass:NSArray.class]) { if (error != NULL) { NSDictionary *userInfo = @{ NSLocalizedDescriptionKey: NSLocalizedString(@"Missing JSON array", @""), NSLocalizedFailureReasonErrorKey: [NSString stringWithFormat:NSLocalizedString(@"%@ could not be created because an invalid JSON array was provided: %@", @""), NSStringFromClass(modelClass), JSONArray.class], }; *error = [NSError errorWithDomain:@"modelWithModleClass" code:1 userInfo:userInfo]; } return nil; } NSMutableArray *models = [NSMutableArray arrayWithCapacity:JSONArray.count]; for (NSDictionary *dictionary in JSONArray ) { NSObject *model = [modelClass yy_modelWithDictionary:dictionary]; if (model == nil) { return nil; } [models addObject:model]; } return models; } @end ================================================ FILE: zhuishushenqi/Base/Models/ZSCacheHelper.swift ================================================ // // ZSCacheHelper.swift // zhuishushenqi // // Created by caony on 2019/1/18. // Copyright © 2019年 QS. All rights reserved. // import UIKit import Cache class ZSCacheHelper { static let bookshelfPath = "/ZSBookShelf" static let bookshelfBooksPath = "/ZSBookShelf/Books" static let bookshelfDownloadPath = "/ZSBookShelf/Download" static let historyPath = "/History" private let path:String = "/Documents" private let fileManager = FileManager.default var cachePath:String = "/ZSSQ/" var storeKey:String? static let shared = ZSCacheHelper() private init() { } // MARK: - get func cachedObj() ->Any? { if let key = storeKey { return cachedObj(for: key) } return nil } func cachedObj(for key:String) ->Any? { return cachedObj(for: key, cachePath: self.cachePath) } func cachedObj(for key:String, cachePath:String) ->Any? { // 去除首尾的斜线 var realPath = cachePath if realPath == "" { return nil } if !realPath.hasPrefix("/") { realPath = "/\(realPath)" } if !realPath.hasSuffix("/") { realPath = "\(realPath)/" } let filePath = NSHomeDirectory().appending(path).appending(realPath) let fullPath = filePath.appending("\(key.md5())") let url = URL(fileURLWithPath: fullPath) if let data = try? Data(contentsOf: url) { if let obj = NSKeyedUnarchiver.unarchiveObject(with: data) { return obj } } return nil } //MARK: - storage @discardableResult func storage(obj:Any, for key:String, cachePath:String) ->Bool { if key == "" { return false } var realPath = cachePath if realPath != "" { if !realPath.hasPrefix("/") { realPath = "/\(realPath)" } if !realPath.hasSuffix("/") { realPath = "\(realPath)/" } } else { realPath = self.cachePath } let filePath = NSHomeDirectory().appending(path).appending(realPath) let data = NSKeyedArchiver.archivedData(withRootObject: obj) if data.count == 0 { return false } try? fileManager.createDirectory(atPath: filePath, withIntermediateDirectories: true, attributes: nil) let fullPath = filePath.appending("\(key.md5())") let success = fileManager.createFile(atPath: fullPath, contents: data, attributes: nil) return success } @discardableResult func storage(obj:Any, for key:String) ->Bool { return storage(obj: obj, for: key, cachePath: self.cachePath) } @discardableResult func storage(obj:Any) ->Bool { guard let key = storeKey else { return false } let success = storage(obj: obj, for: key) return success } //MARK: - clear @discardableResult func clear(for key:String,cachePath:String) ->Bool { if key == "" { return false } var realPath = cachePath if realPath != "" { if !realPath.hasPrefix("/") { realPath = "/\(realPath)" } if !realPath.hasSuffix("/") { realPath = "\(realPath)/" } } else { realPath = self.cachePath } let filePath = NSHomeDirectory().appending(path).appending(realPath) let fullPath = filePath.appending("\(key.md5())") let exist = fileManager.fileExists(atPath: fullPath) if exist { try? fileManager.removeItem(atPath: fullPath) return true } return false } } ================================================ FILE: zhuishushenqi/Base/ViewManager/ZSAppInfo.swift ================================================ // // ZSAppInfo.swift // zhuishushenqi // // Created by caony on 2019/1/22. // Copyright © 2019年 QS. All rights reserved. // import UIKit class ZSAppInfo { static let infoDict = Bundle.main.infoDictionary /// App名称 static let appDisplayName:String = Bundle.main.infoDictionary?["CFBundleDisplayName"] as? String ?? "" /// bundle id static let bundleIdentifier:String = Bundle.main.bundleIdentifier ?? "" /// app 版本号 static let appVersion: String = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "" /// build 版本号 static let buildVersion: String = Bundle.main.infoDictionary?["CFBundleVersion"] as? String ?? "" /// ios 版本 static let iOSVersion: String = UIDevice.current.systemVersion /// 设备uuid static let identifierNumber:String = UIDevice.current.identifierForVendor?.uuidString ?? "" /// 设备名称 static let systemName: String = UIDevice.current.systemName /// 设备型号 static let model: String = UIDevice.current.model /// 设备区域化型号 static let localizedModel: String = UIDevice.current.localizedModel } ================================================ FILE: zhuishushenqi/Base/ViewManager/ZSBaseTableViewManger.swift ================================================ // // ZSBaseTableViewManger.swift // zhuishushenqi // // Created by caony on 2018/5/21. // Copyright © 2018年 QS. All rights reserved. // import UIKit /// 选中UITableViewCell的Block typealias didSelectTableCellBlock = (IndexPath, AnyObject) -> Void class ZSBaseTableViewManger: NSObject, UITableViewDataSource, UITableViewDelegate { lazy var myCellIdentifiers = [String]() lazy var zs_dataArrayList:[AnyObject] = [] var didSelectCellBlock: didSelectTableCellBlock? init(cellIdentifiers: [String], didSelectBlock: @escaping didSelectTableCellBlock) { super.init() self.myCellIdentifiers = cellIdentifiers self.didSelectCellBlock = didSelectBlock } /** 设置UITableView的Datasource和Delegate为self - parameter table: UITableView */ func handleTableViewDatasourceAndDelegate(table: UITableView) { table.dataSource = self table.delegate = self } /** 获取UITableView中Item所在的indexPath - parameter indexPath: indexPath - returns: return value description */ func itemAtIndexPath(indexPath: NSIndexPath) -> AnyObject { return zs_dataArrayList[indexPath.row] as AnyObject; } //MARK: - UITableViewDataSource func numberOfSections(in tableView: UITableView) -> Int { return 1 } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return zs_dataArrayList.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let item: AnyObject? = itemAtIndexPath(indexPath: indexPath as NSIndexPath) let cell = tableView.dequeueReusableCell(withIdentifier: myCellIdentifiers.first!) if cell == nil { UITableViewCell.registerTable(table: tableView, nibIdentifier: myCellIdentifiers.first!) } cell?.configure(cell: cell!, customObj: item!, indexPath: indexPath as NSIndexPath) return cell! } } ================================================ FILE: zhuishushenqi/Base/ViewManager/ZSBookDownloader.swift ================================================ // // ZSBookDownloader.swift // zhuishushenqi // // Created by caony on 2019/1/21. // Copyright © 2019年 QS. All rights reserved. // import UIKit class ZSBookDownloader { private var semaphore = DispatchSemaphore(value: 5) private var group = DispatchGroup() private var queue = DispatchQueue(label: "com.cnydev.zssq") /// key:chapterId or link, value:chapterContent private var chapterInfo:[String:String] = [:] static let shared = ZSBookDownloader() private init() { } @discardableResult func cacheContent(content:ZSChapterBody, for key:String) ->Bool { let cachePath = ZSCacheHelper.bookshelfBooksPath let success = ZSCacheHelper.shared.storage(obj: content, for: key, cachePath: cachePath) return success } func content(for key:String) ->ZSChapterBody? { let cachePath = ZSCacheHelper.bookshelfBooksPath if let obj = ZSCacheHelper.shared.cachedObj(for: key, cachePath: cachePath) as? ZSChapterBody { return obj } return nil } func isContentExist(_ key:String) ->Bool { let cachePath = ZSCacheHelper.bookshelfBooksPath if let _ = ZSCacheHelper.shared.cachedObj(for: key, cachePath: cachePath) { return true } return false } func download(book:BookDetail, start:Int, handler:@escaping ZSBaseCallback) { guard let chaptersInfo = book.chaptersInfo else { return } let totalChapterCount = chaptersInfo.count if start > totalChapterCount { return } queue.async(group: group) { for index in start..) { let link = (key as NSString).urlEncode() ?? key let api = QSAPI.chapter(key: link, type: BaseType.chapter) zs_get(api.path, parameters: api.parameters) { (json) in if let body = ZSChapterBody.deserialize(from: json?["chapter"] as? [String:Any]) { handler(body) } } } } ================================================ FILE: zhuishushenqi/Base/ViewManager/ZSCellAdapterProtocol.swift ================================================ // // ZSCellAdapterProtocol.swift // zhuishushenqi // // Created by caony on 2018/8/27. // Copyright © 2018年 QS. All rights reserved. // import Foundation protocol ZSCellAdapterProtocol:UITableViewDelegate,UITableViewDataSource { func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) } protocol ZSSectionAdapterProtocol:UITableViewDelegate { func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? // custom view for header. will be adjusted to default or specified header height func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? // custom view for footer. will be adjusted to default or specified footer height } protocol ZSListAdapterProtocol:ZSCellAdapterProtocol,ZSSectionAdapterProtocol { func numberOfSections(in tableView: UITableView) -> Int // Default is 1 if not implemented } ================================================ FILE: zhuishushenqi/Base/Views/CommunityView.swift ================================================ // // CommunityView.swift // zhuishushenqi // // Created by Nory Cao on 16/10/22. // Copyright © 2016年 QS. All rights reserved. // import UIKit protocol ComnunityDelegate { func didSelectCellAtIndex(_ index:Int) } class CommunityView: UIView,UITableViewDataSource,UITableViewDelegate { var delegate:ComnunityDelegate? var models:[BookDetail] = [] { didSet{ self.tableView.reloadData() } } fileprivate lazy var tableView:UITableView = { let tableView = UITableView(frame: CGRect(x: 0, y: 0, width: ScreenWidth, height: ScreenHeight - 104), style: .grouped) tableView.dataSource = self tableView.delegate = self tableView.separatorStyle = .singleLine return tableView }() var titles:NSArray = [["title":"动态","image":"d_icon"],["title":"综合讨论区","image":"f_ramble_icon"],["title":"书评区[找书必看]","image":"forum_public_review_icon"],["title":"书荒互助区","image":"forum_public_help_icon"],["title":"女生区","image":"f_girl_icon"],["title":"浏览记录","image":"f_invent_icon"]] override init(frame: CGRect) { super.init(frame: frame) tableView.dataSource = self tableView.delegate = self addSubview(tableView) backgroundColor = UIColor.white } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } func numberOfSections(in tableView: UITableView) -> Int { return 1 } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return titles.count + models.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let iden = "CellIden" var cell = tableView.dequeueReusableCell(withIdentifier: iden) if cell == nil { cell = UITableViewCell(style: .default, reuseIdentifier: iden) cell?.selectionStyle = .none } let name = indexPath.row < titles.count ? (titles[indexPath.row] as! NSDictionary).object(forKey: "image") as? String ?? "" : "" let title = indexPath.row < titles.count ? (titles[indexPath.row] as! NSDictionary).object(forKey: "title") as? String : "" cell?.textLabel?.text = title cell?.imageView?.image = UIImage(named:name) cell?.textLabel?.font = UIFont.systemFont(ofSize: 15) cell?.accessoryType = .disclosureIndicator if indexPath.row >= titles.count { cell?.imageView?.qs_setBookCoverWithURLString(urlString: models[indexPath.row - titles.count].cover) cell?.textLabel?.text = models[indexPath.row - titles.count].title } return cell! } func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { return 50 } func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { return 0.0001 } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { delegate?.didSelectCellAtIndex(indexPath.row) } fileprivate func initSubview(){ } } ================================================ FILE: zhuishushenqi/Base/Views/DarkStarView.swift ================================================ // // DarkStarView.swift // zhuishushenqi // // Created by Nory Cao on 2017/3/9. // Copyright © 2017年 QS. All rights reserved. // import UIKit class DarkStarView: UIImageView { // override init(frame: CGRect) { // super.init(frame: frame) // self.image = UIImage(named: "bd_star_empty") //// bd_star_empty // } init(frame: CGRect,image:UIImage?) { super.init(frame: frame) self.image = image } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } /* // Only override draw() if you perform custom drawing. // An empty implementation adversely affects performance during animation. override func draw(_ rect: CGRect) { // Drawing code } */ } ================================================ FILE: zhuishushenqi/Base/Views/DarkView.swift ================================================ // // DarkView.swift // zhuishushenqi // // Created by Nory Cao on 2017/3/9. // Copyright © 2017年 QS. All rights reserved. // import UIKit class DarkView: UIView { var darkStarViews:[DarkStarView] = [] init(frame: CGRect,image:UIImage?) { super.init(frame: frame) self.layer.masksToBounds = true makeLightView(image: image) } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } func makeLightView(image:UIImage?){ for index in 0..<5 { let width = self.bounds.width/5 - 10/5 let height = self.bounds.height let lightStarView = DarkStarView(frame: CGRect(x: CGFloat(1) + CGFloat(index)*width + CGFloat(2*index), y: 0, width: width, height: height), image: image) darkStarViews.append(lightStarView) addSubview(lightStarView) } } override func layoutSubviews() { super.layoutSubviews() for index in 0..<5 { let width = self.bounds.height let height = self.bounds.height let lightStarView = darkStarViews[index] lightStarView.frame = CGRect(x: CGFloat(1) + CGFloat(index)*width + CGFloat(2*index), y: 0, width: width, height: height) } } } ================================================ FILE: zhuishushenqi/Base/Views/EmptyView.swift ================================================ // // EmptyView.swift // zhuishushenqi // // Created by Nory Cao on 2017/3/17. // Copyright © 2017年 QS. All rights reserved. // import UIKit typealias ReloadAction = ()->Void protocol EmptyViewDelegate { func didClickReloadButton() } class EmptyView: UIView { @IBOutlet weak var emptyBtn: UIButton! @IBOutlet weak var title: UILabel! var delegate:EmptyViewDelegate? var reloadAction:ReloadAction? override func awakeFromNib() { super.awakeFromNib() } var tittle:String = "暂无数据,点击重新加载" { didSet{ self.title.text = tittle } } var image:UIImage? = UIImage(named: "sure_placeholder_error") { didSet{ emptyBtn.setImage(image, for: .normal) } } @IBAction func emptyAction(_ sender: Any) { delegate?.didClickReloadButton() if let action = self.reloadAction { action() } } } ================================================ FILE: zhuishushenqi/Base/Views/EmptyView.xib ================================================ ================================================ FILE: zhuishushenqi/Base/Views/LightStarView.swift ================================================ // // LightStarView.swift // zhuishushenqi // // Created by Nory Cao on 2017/3/9. // Copyright © 2017年 QS. All rights reserved. // import UIKit class LightStarView: UIImageView { init(frame: CGRect,image:UIImage?) { super.init(frame: frame) self.image = image } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } /* // Only override draw() if you perform custom drawing. // An empty implementation adversely affects performance during animation. override func draw(_ rect: CGRect) { // Drawing code } */ } ================================================ FILE: zhuishushenqi/Base/Views/LightView.swift ================================================ // // LightView.swift // zhuishushenqi // // Created by Nory Cao on 2017/3/9. // Copyright © 2017年 QS. All rights reserved. // import UIKit class LightView: UIView { var lightStarViews:[LightStarView] = [] init(frame: CGRect,image:UIImage?) { super.init(frame: frame) self.layer.masksToBounds = true makeLightView(image: image) } func makeLightView(image:UIImage?){ for index in 0..<5 { let width = self.bounds.width/5 - 10/5 let height = self.bounds.height let lightStarView = LightStarView(frame: CGRect(x: CGFloat(1) + CGFloat(index)*width + CGFloat(2*index), y: 0, width: width, height: height), image: image) lightStarViews.append(lightStarView) addSubview(lightStarView) } } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func layoutSubviews() { super.layoutSubviews() for index in 0..<5 { let width = self.bounds.height let height = self.bounds.height let lightStarView = lightStarViews[index] lightStarView.frame = CGRect(x: CGFloat(1) + CGFloat(index)*width + CGFloat(2*index), y: 0, width: width, height: height) } } } ================================================ FILE: zhuishushenqi/Base/Views/QSLoadingView.swift ================================================ // // QSLoadingView.swift // zhuishushenqi // // Created by Nory Cao on 2017/4/13. // Copyright © 2017年 QS. All rights reserved. // import UIKit typealias CloseAction = ()->Void class QSLoadingView: UIView { var tipStr:String? { didSet{ tip.text = tipStr } } var tip:UILabel! var closeClosure:CloseAction? override init(frame: CGRect) { super.init(frame: frame) setupSubviews() } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } func setupSubviews(){ self.backgroundColor = UIColor.clear let bgView = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100)) bgView.backgroundColor = UIColor(red: 1/255.0, green: 1/255.0, blue: 1/255.0, alpha: 0.8) bgView.center = self.center let acctivityView = UIActivityIndicatorView(style: .white) acctivityView.frame = CGRect(x: 0, y: 0, width: 30, height: 30) acctivityView.center = CGPoint(x: bgView.bounds.width/2, y: bgView.bounds.height/2 - 10) acctivityView.startAnimating() bgView.addSubview(acctivityView) // let loadView = UIImageView(frame: CGRect(x: 0, y: 0, width: 30, height: 30)) // loadView.center = CGPoint(x: bgView.bounds.width/2, y: bgView.bounds.height/2 - 10) // loadView.image = UIImage(named: "loadside") // bgView.addSubview(loadView) bgView.layer.cornerRadius = 10 addSubview(bgView) tip = UILabel(frame: CGRect(x: 0, y: bgView.bounds.height - 40, width: 100, height: 20)) tip.font = UIFont.systemFont(ofSize: 15) tip.textColor = UIColor.white tip.textAlignment = .center tip.text = "正在加载..." bgView.addSubview(tip) let close = UIButton(type: .custom) close.setImage(UIImage(named:"g_close"), for: .normal) close.frame = CGRect(x: bgView.bounds.width - 20, y: -10, width: 30, height: 30) close.addTarget(self, action: #selector(closeAction(btn:)), for: .touchUpInside) bgView.addSubview(close) // let animation = CABasicAnimation(keyPath: "transform.rotation.z") // animation.toValue = M_PI*2 // animation.duration = 1.0 // animation.repeatCount = MAXFLOAT // loadView.layer.add(animation, forKey: "rotate") } @objc func closeAction(btn:UIButton){ if let close = closeClosure{ close() } } } extension IndicatableView where Self:UIViewController{ func showActivityView(){ let loadView:QSLoadingView = QSLoadingView(frame: UIScreen.main.bounds) loadView.closeClosure = { self.hideActivityView() } KeyWindow?.insertSubview(loadView, at: KeyWindow?.subviews.count ?? 0) } func hideActivityView(){ if let subviews = KeyWindow?.subviews { for item in subviews { if item.isKind(of: QSLoadingView.self) { item.removeFromSuperview() } } } } func showLoadingPageView(){ } func hideLoadingPageView(){ } } ================================================ FILE: zhuishushenqi/Base/Views/RateView.swift ================================================ // // RateView.swift // zhuishushenqi // // Created by Nory Cao on 2017/3/14. // Copyright © 2017年 QS. All rights reserved. // import UIKit class RateView: UIView { //rate 0-5 var rate:Int = 0 { didSet{ setNeedsLayout() layoutIfNeeded() } } private var darkView:DarkView? private var lightView:LightView? init(frame: CGRect,darkImage:UIImage?,lightImage:UIImage?) { super.init(frame: frame) darkView = DarkView(frame: CGRect(x: 0, y: 0, width: self.bounds.width, height: self.bounds.height), image: darkImage) lightView = LightView(frame: CGRect(x: 0, y: 0, width: self.bounds.width, height: self.bounds.height), image: lightImage) addSubview(darkView!) addSubview(lightView!) } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func layoutSubviews() { super.layoutSubviews() darkView?.frame = CGRect(x: 0, y: 0, width: self.bounds.width, height: self.bounds.height) let width:CGFloat = (self.bounds.height + 2) * CGFloat(rate) lightView?.frame = CGRect(x: 0, y: 0, width: width + 1, height: self.bounds.height) } } ================================================ FILE: zhuishushenqi/Base/Views/UIScrollView+StateView.h ================================================ // // UIScrollView+StateView.h // zhuishushenqi // // Created by yung on 2017/8/23. // Copyright © 2017年 QS. All rights reserved. // #import typedef enum : NSUInteger { ZSTableViewStatusInitial, ZSTableViewStatusNoData, ZSTableViewStatusLoading, ZSTableViewStatusFailure, } ZSTableViewStatus; typedef void(^QSErrorHandler)(); @class QSErrorPageView,QSLoadingPageView; @interface UIScrollView (StateView) @property (nonatomic, copy)QSErrorHandler reloadAction; @property (nonatomic, strong)QSErrorPageView *errorPageView; @property (nonatomic, strong)QSLoadingPageView *loadingPageView; - (void)showErrorPage; - (void)showLoadingPage; @end @interface QSErrorPageView : UIView @property (nonatomic,copy)QSErrorHandler didClickReloadBlock; @end @interface QSLoadingPageView : UIView - (void)startAnimate; - (void)stopAnimate; @end @interface UITableView (StateView) @property (nonatomic, assign, getter=isAutoControlErrorView) BOOL autoControlErrorView; @property (nonatomic, assign) ZSTableViewStatus status; @end ================================================ FILE: zhuishushenqi/Base/Views/UIScrollView+StateView.m ================================================ // // UIScrollView+StateView.m // zhuishushenqi // // Created by yung on 2017/8/23. // Copyright © 2017年 QS. All rights reserved. // #import "UIScrollView+StateView.h" #import #import "zhuishushenqi-Swift.h" void qs_swizzle(Class cls,SEL old,SEL new){ Method oldMethod = class_getInstanceMethod(cls, old); Method newMethod = class_getInstanceMethod(cls, new); if (class_addMethod(cls, old, class_getMethodImplementation(cls, new), method_getTypeEncoding(newMethod))) { class_replaceMethod(cls, new, class_getMethodImplementation(cls, old), method_getTypeEncoding(oldMethod)); }else method_exchangeImplementations(oldMethod, newMethod); } @implementation UITableView (StateView) - (void)setAutoControlErrorView:(BOOL)autoControlErrorView { objc_setAssociatedObject(self, @selector(isAutoControlErrorView), @(autoControlErrorView), OBJC_ASSOCIATION_ASSIGN); } - (BOOL)isAutoControlErrorView { return [objc_getAssociatedObject(self, _cmd) boolValue]; } - (void)setStatus:(ZSTableViewStatus)status { objc_setAssociatedObject(self, @selector(status), @(status), OBJC_ASSOCIATION_ASSIGN); } - (ZSTableViewStatus)status { ZSTableViewStatus ss = [objc_getAssociatedObject(self, _cmd) integerValue]; return ss; } - (void)setReloadAction:(QSErrorHandler)reloadAction{ objc_setAssociatedObject(self, @selector(reloadAction), reloadAction, OBJC_ASSOCIATION_COPY); } - (QSErrorHandler)reloadAction{ return objc_getAssociatedObject(self, _cmd); } - (void)setErrorPageView:(QSErrorPageView *)errorPageView{ [self willChangeValueForKey:NSStringFromSelector(@selector(errorPageView))]; objc_setAssociatedObject(self, @selector(errorPageView), errorPageView, OBJC_ASSOCIATION_RETAIN_NONATOMIC); [self didChangeValueForKey:NSStringFromSelector(@selector(errorPageView))]; } - (QSErrorPageView *)errorPageView{ return objc_getAssociatedObject(self, _cmd); } - (void)setLoadingPageView:(QSLoadingPageView *)loadingPageView{ [self willChangeValueForKey:NSStringFromSelector(@selector(loadingPageView))]; objc_setAssociatedObject(self, @selector(loadingPageView), loadingPageView, OBJC_ASSOCIATION_RETAIN_NONATOMIC); [self didChangeValueForKey:NSStringFromSelector(@selector(loadingPageView))]; } - (QSLoadingPageView *)loadingPageView{ return objc_getAssociatedObject(self, _cmd); } + (void)load{ static dispatch_once_t predicate; dispatch_once(&predicate, ^{ Class cls = [self class]; if ([self instancesRespondToSelector:@selector(reloadData)]) { qs_swizzle(cls, @selector(reloadData), @selector(qs_reloadData)); } }); } - (BOOL)hasRowToDisplay{ NSInteger totalRows = 0; if ([self isKindOfClass:[UITableView class]]) { UITableView *tableView = (UITableView *)self; NSInteger numOfSections = [tableView numberOfSections]; for (NSInteger section = 0; section < numOfSections; section++) { NSInteger numOfRows = [tableView numberOfRowsInSection:section]; totalRows += numOfRows; } } return (totalRows != 0); } - (void)showLoadingPage{ if (!self.loadingPageView) { QSLoadingPageView *loadingPage = [[QSLoadingPageView alloc] initWithFrame:self.bounds]; self.loadingPageView = loadingPage; } self.status = ZSTableViewStatusLoading; self.loadingPageView.hidden = NO; self.loadingPageView.maskView.hidden = YES; if (self.subviews.count > 0) { [self insertSubview:self.loadingPageView atIndex:0]; }else{ [self addSubview:self.loadingPageView]; } } - (void)showErrorPage{ if (self.loadingPageView) { self.loadingPageView.hidden = YES; self.loadingPageView.maskView.hidden = NO; } if (!self.errorPageView) { QSErrorPageView *errorPage = [[QSErrorPageView alloc] initWithFrame:self.bounds]; if (self.reloadAction) { errorPage.didClickReloadBlock = self.reloadAction; } self.errorPageView = errorPage; } // 初始状态不显示错误页面 if (self.status == ZSTableViewStatusInitial) { return; } self.status = ZSTableViewStatusFailure; self.errorPageView.hidden = YES; if (self.subviews.count > 0) { [self insertSubview:self.errorPageView atIndex:0]; }else{ [self addSubview:self.errorPageView]; } if (![self hasRowToDisplay]) { self.errorPageView.hidden = NO; } } - (void)hideErrorPage{ self.errorPageView.hidden = YES; self.errorPageView.maskView.hidden = NO; } - (void)qs_reloadData{ [self qs_reloadData]; if (self.isAutoControlErrorView) { [self showErrorPage]; } } @end @interface QSErrorPageView () @property (nonatomic, weak)UIView *maskView; @property (nonatomic, weak)UIImageView *errorImageView; @property (nonatomic, weak)UILabel *errorTipLabel; @property (nonatomic, weak)UIButton *reloadButton; @end @implementation QSErrorPageView - (instancetype)init { self = [super initWithFrame:CGRectZero]; if (self) { } return self; } - (instancetype)initWithFrame:(CGRect)frame{ self = [super initWithFrame:frame]; if (self) { [self setupSubviews]; } return self; } - (void)setupSubviews{ UIImageView* errorImageView = [[UIImageView alloc]initWithImage:[UIImage imageNamed:@"ic_tip_fail"]]; errorImageView.frame = CGRectMake(0, 0, 120, 140); errorImageView.center = CGPointMake(self.center.x, self.center.y - 70); _errorImageView = errorImageView; [self addSubview:_errorImageView]; UILabel* errorTipLabel = [[UILabel alloc]initWithFrame:CGRectMake(0, 0, self.bounds.size.width, 50)]; errorTipLabel.center = CGPointMake(self.center.x, self.center.y + 20); errorTipLabel.numberOfLines = 1; errorTipLabel.font = [UIFont systemFontOfSize:15]; errorTipLabel.textAlignment = NSTextAlignmentCenter; errorTipLabel.textColor = [UIColor grayColor]; errorTipLabel.text = @"很抱歉,网络似乎出了点状况..."; _errorTipLabel = errorTipLabel; [self addSubview:_errorTipLabel]; UIButton* reloadButton = [UIButton buttonWithType:UIButtonTypeCustom]; reloadButton.frame = CGRectMake(0, 0, 120, 40); reloadButton.center = CGPointMake(self.center.x, self.center.y + 90); reloadButton.layer.masksToBounds = YES; reloadButton.layer.cornerRadius = 15; reloadButton.layer.borderColor = [UIColor grayColor].CGColor; reloadButton.layer.borderWidth = 1.0f; [reloadButton setBackgroundImage:[self createImageWithColor:[UIColor clearColor]] forState:UIControlStateNormal]; [reloadButton setBackgroundImage:[self createImageWithColor:[[UIColor grayColor] colorWithAlphaComponent:0.3]] forState:UIControlStateHighlighted]; [reloadButton setTitle:@"重新加载" forState:UIControlStateNormal]; [reloadButton setTitleColor:[UIColor grayColor] forState:UIControlStateNormal]; [reloadButton addTarget:self action:@selector(_clickReloadButton:) forControlEvents:UIControlEventTouchUpInside]; _reloadButton = reloadButton; [self addSubview:_reloadButton]; UIView *maskView = [[UIView alloc] initWithFrame:self.bounds]; if ([self.backgroundColor isEqual:UIColor.clearColor]) { maskView.backgroundColor = [UIColor whiteColor];; } else { maskView.backgroundColor = self.backgroundColor; } _maskView = maskView; [self addSubview:_maskView]; } - (void)_clickReloadButton:(UIButton *)sender{ if (_didClickReloadBlock) { _didClickReloadBlock(); } } - (UIImage*)createImageWithColor:(UIColor*) color { CGRect rect=CGRectMake(0,0, 1, 1); UIGraphicsBeginImageContext(rect.size); CGContextRef context = UIGraphicsGetCurrentContext(); CGContextSetFillColorWithColor(context, [color CGColor]); CGContextFillRect(context, rect); UIImage *theImage = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); return theImage; } @end @interface QSLoadingPageView() @property (nonatomic, weak)UIView *maskView; @property (nonatomic, weak)UIActivityIndicatorView *activityIndicatorView; @end @implementation QSLoadingPageView - (instancetype)init { self = [super initWithFrame:CGRectZero]; if (self) { } return self; } - (instancetype)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { } return self; } - (void)setupSubviews{ UIActivityIndicatorView *activityIndicatorView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray]; activityIndicatorView.center = self.center; _activityIndicatorView = activityIndicatorView; [self addSubview:_activityIndicatorView]; UILabel* errorTipLabel = [[UILabel alloc]initWithFrame:CGRectMake(0, 0, self.bounds.size.width, 50)]; errorTipLabel.center = CGPointMake(self.center.x, self.center.y + 30); errorTipLabel.numberOfLines = 1; errorTipLabel.font = [UIFont systemFontOfSize:15]; errorTipLabel.textAlignment = NSTextAlignmentCenter; errorTipLabel.textColor = [UIColor grayColor]; errorTipLabel.text = @"正在加载..."; [self addSubview:errorTipLabel]; UIView *maskView = [[UIView alloc] initWithFrame:self.bounds]; if ([self.backgroundColor isEqual:UIColor.clearColor]) { maskView.backgroundColor = [UIColor whiteColor];; } else { maskView.backgroundColor = self.backgroundColor; } _maskView = maskView; [self addSubview:_maskView]; } - (void)startAnimate{ [_activityIndicatorView startAnimating]; } - (void)stopAnimate{ [_activityIndicatorView stopAnimating]; } @end ================================================ FILE: zhuishushenqi/Base/Views/V2FPSLabel.swift ================================================ // // V2FPSLabel.swift // V2ex-Swift // // Created by huangfeng on 1/15/16. // Copyright © 2016 Fin. All rights reserved. // import UIKit //重写自 YYFPSLabel //https://github.com/ibireme/YYText/blob/master/Demo/YYTextDemo/YYFPSLabel.m class V2FPSLabel: UILabel { fileprivate var _link :CADisplayLink? fileprivate var _count:Int = 0 fileprivate var _lastTime:TimeInterval = 0 fileprivate let _defaultSize = CGSize(width: 55, height: 20); override init(frame: CGRect) { var targetFrame = frame if frame.size.width == 0 && frame.size.height == 0{ targetFrame.size = _defaultSize } super.init(frame: targetFrame) self.layer.cornerRadius = 5 self.clipsToBounds = true self.textAlignment = .center self.isUserInteractionEnabled = false self.textColor = UIColor.white self.backgroundColor = UIColor(white: 0, alpha: 0.7) self.font = UIFont(name: "Menlo", size: 14) weak var weakSelf = self _link = CADisplayLink(target: weakSelf!, selector:#selector(V2FPSLabel.tick(_:)) ); _link!.add(to: RunLoop.main, forMode:RunLoop.Mode.common) } required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) } @objc func tick(_ link:CADisplayLink) { if _lastTime == 0 { _lastTime = link.timestamp return } _count += 1 let delta = link.timestamp - _lastTime if delta < 1 { return } _lastTime = link.timestamp let fps = Double(_count) / delta _count = 0 let progress = fps / 60.0; self.textColor = UIColor(hue: CGFloat(0.27 * ( progress - 0.2 )) , saturation: 1, brightness: 0.9, alpha: 1) self.text = "\(Int(fps+0.5))FPS" } } ================================================ FILE: zhuishushenqi/Base/Views/XYCActionSheet.swift ================================================ // // XYCActionSheet.swift // zhuishushenqi // // Created by Nory Cao on 16/10/6. // Copyright © 2016年 QS. All rights reserved. // import UIKit protocol XYCActionSheetDelegate { func didSelectedAtIndex(_ index:Int, sheet:XYCActionSheet) } class XYCActionSheet: UIView { var delegate:XYCActionSheetDelegate? var titles:NSMutableArray = NSMutableArray() var backColor:NSArray = [UIColor.red,UIColor.green,UIColor.blue,UIColor.cyan,UIColor.orange,UIColor.yellow,UIColor.purple] fileprivate var colorArr:NSMutableArray = NSMutableArray(objects: ["0" as AnyObject,"1" as AnyObject,"2" as AnyObject,"3" as AnyObject,"4" as AnyObject,"5" as AnyObject,"6" as AnyObject], count: 7) init(frame: CGRect,titles:NSArray) { super.init(frame: frame) backgroundColor = UIColor(white: 0.00, alpha: 0.8) self.titles.addObjects(from: titles as [AnyObject]) self.titles.add("取消") initSuview() } fileprivate func initSuview(){ if titles.count == 0 { return } for index in 0..Bool{ if colorArray.count == 0 { return false } for indexxx in 0..Int{ var randomIndex = arc4random()%UInt32(backColor.count) while !colorArr.contains("\(randomIndex)") { randomIndex = arc4random()%UInt32(backColor.count) } colorArr.remove("\(randomIndex)") return Int(randomIndex) } @objc fileprivate func btnAction(_ btn:UIButton){ delegate?.didSelectedAtIndex(btn.tag,sheet: self) } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } } ================================================ FILE: zhuishushenqi/Base/Views/ZSBaseCellAdapter.swift ================================================ // // ZSBaseCellAdapter.swift // zhuishushenqi // // Created by caony on 2018/8/27. // Copyright © 2018年 QS. All rights reserved. // import Foundation class ZSBaseCellAdapter:NSObject ,ZSCellAdapterProtocol { var dataSource:[Any] = [] func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { var cell:UITableViewCell! if let cls = registerCellClasses().first { cell = tableView.dequeueReusableCell(withIdentifier: NSStringFromClass(cls)) } else { cell = tableView.dequeueReusableCell(withIdentifier: NSStringFromClass(UITableViewCell.self)) } return cell } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { } init(_ tableView:UITableView,dataSource:[Any]) { super.init() self.dataSource = dataSource for cls in registerCellClasses() { tableView.register(cls, forCellReuseIdentifier: NSStringFromClass(cls)) } } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return dataSource.count } func registerCellClasses() ->[AnyClass] { return [] } } ================================================ FILE: zhuishushenqi/Base/Views/ZSBaseSectionAdapter.swift ================================================ // // ZSBaseSectionAdapter.swift // zhuishushenqi // // Created by caony on 2018/8/27. // Copyright © 2018年 QS. All rights reserved. // import Foundation class ZSBaseSectionAdapter: ZSBaseCellAdapter,ZSSectionAdapterProtocol { var sectionTitle:String = "" var sectionHeight:CGFloat = 0 var cellAdapter:ZSCellAdapterProtocol? override init(_ tableView: UITableView, dataSource: [Any]) { super.init(tableView, dataSource: dataSource) } // init(_ sectionTitle:String,_ sectionHeight:CGFloat) { // super.init(<#UITableView#>, dataSource: <#[Any]#>) // self.sectionTitle = sectionTitle // self.sectionHeight = sectionHeight // } func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { return sectionHeight } func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { return nil }// custom view for header. will be adjusted to default or specified header height func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { return 0.01 } func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? { return nil }// custom view for footer. will be adjusted to default or specified footer height override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { var cell:UITableViewCell! if let cls = registerCellClasses().first { cell = tableView.dequeueReusableCell(withIdentifier: NSStringFromClass(cls)) } else { cell = tableView.dequeueReusableCell(withIdentifier: NSStringFromClass(UITableViewCell.self)) } return cell } override func responds(to aSelector: Selector!) -> Bool { var responds = super.responds(to: aSelector) if !responds { responds = (self.cellAdapter?.responds(to: aSelector))! } return responds } } ================================================ FILE: zhuishushenqi/Base.lproj/LaunchScreen.storyboard ================================================ ================================================ FILE: zhuishushenqi/Base.lproj/Main.storyboard ================================================ ================================================ FILE: zhuishushenqi/Client/QSAPI.swift ================================================ // // QSAPI.swift // zhuishushenqi // // Created by yung on 2017/6/29. // Copyright © 2017年 QS. All rights reserved. // import UIKit public protocol TargetType { var baseURLString:String { get } var path:String { get } var parameters:[String:Any]? { get } } enum BaseType { case normal case chapter } enum QSAPI { ///首次进入根据性别推荐书籍 case genderRecommend(gender:String) ///追书书架信息 case shelfMSG(_ shelf:Any) ///书架更新信息 case update(id:String) ///热门搜索 case hotwords(_ shelf:Any) ///联想搜索 case autoComplete(query:String) ///搜索书籍 case searchBook(id:String,start:String,limit:String) ///排行榜 case ranking(_ shelf:Any) ///榜单数据 case rankList(id:String) ///分类 case category(_ shelf:Any) ///分类详细 case categoryList(gender:String,type:String,major:String,minor:String,start:String,limit:String) ///tag过滤 case tagType(_ shelf:Any) ///主题书单 case themeTopic(sort:String,duration:String,start:String,gender:String,tag:String) ///主题书单详细 case themeDetail(key:String) ///热门评论 case hotComment(key:String) ///普通评论 case normalComment(key:String,start:String,limit:String) ///热门动态 case hotUser(key:String) ///都是热门,忘记干嘛的了 case hotPost(key:String,start:String,limit:String) ///评论详情 case commentDetail(key:String) ///社区 case community(key:String,start:String) ///社区评论 case communityComment(key:String,start:String) ///所有来源 case allResource(key:String) ///所有章节 case allChapters(key:String) ///某一章节 case chapter(key:String,type:BaseType) ///书籍信息 case book(key:String) ///详情页热门评论 case bookHot(key:String) ///详情页可能感兴趣 case interested(key:String) ///详情页推荐书单 case recommend(key:String) //随机看书 case mysteryBook(_ shelf:Any) ///登录 case login(idfa:String,platform_code:String,platform_token:String,platform_uid:String,version:String,tag:String) ///账户信息 case account(token:String) ///金币信息 case golden(token:String) ///账户详情 case userDetail(token:String) ///个人信息绑定账户 case userBind(token:String) ///退出登录 case logout(token:String) ///书架列表 case bookshelf(token:String) ///获取手机验证码 case SMSCode(mobile:String,Randstr:String,Ticket:String,captchaType:String,type:String) ///手机号登录 case mobileLogin(mobile:String,idfa:String,platform_code:String,smsCode:String,version:String) ///书架书籍删除 case booksheldDelete(books:String, token:String) ///书架书籍添加 case bookshelfAdd(books:String,token:String) ///用户昵称修改 case nicknameChange(nickname:String,token:String) /// case blessing_bag(token:String) ///查询活动 case judgeSignIn(token:String) ///签到领金币 case signIn(token:String,activityId:String,version:String,type:String) ///编写评论 case reviewPost(token:String,id:String,content:String) ///已购章节信息 case boughtChapters(id:String,token:String) ///书架信息diff case bookshelfdiff(books:String,token:String) ///追书券列表 case voucherList(token:String, type:String, start:Int, limit:Int) } extension QSAPI:TargetType{ var path: String { var pathComponent = "" switch self { case .genderRecommend(_): pathComponent = "/book/recommend" break case .shelfMSG(_): pathComponent = "/notification/shelfMessage" break case .update(_): pathComponent = "/book" break case .hotwords(_): pathComponent = "/book/hot-word" break case .autoComplete(_): pathComponent = "/book/auto-complete" break case .searchBook(_,_,_): pathComponent = "/book/fuzzy-search" break case .ranking(_): pathComponent = "/ranking/gender" break case let .rankList(id): pathComponent = "/ranking/\(id)" break case .category(_): pathComponent = "/cats/lv2/statistics" break case .categoryList(_,_,_,_,_,_): pathComponent = "/book/by-categories" break case .tagType(_): pathComponent = "/book-list/tagType" break case .themeTopic(_,_,_,_,_): pathComponent = "/book-list" break case let .themeDetail(key): pathComponent = "/book-list/\(key)" break case let .hotComment(key): pathComponent = "/post/\(key)/comment/best" break case let .normalComment(key,_,_): pathComponent = "/post/review/\(key)/comment" break case let .hotUser(key): pathComponent = "/user/twitter/\(key)/comments" break case let .hotPost(key,_,_): pathComponent = "/post/\(key)/comment" break case let .commentDetail(key): pathComponent = "/post/review/\(key)" break case let .community(key,start): pathComponent = "/post/by-book?book=\(key)&sort=updated&type=normal,vote&start=\(start)&limit=20" break case let .communityComment(key, start): pathComponent = "/post/review/by-book?book=\(key)&sort=updated&start=\(start)&limit=20" break case .allResource(_): pathComponent = "/toc" break case let .allChapters(key): pathComponent = "/mix-toc/\(key)" break case let .chapter(key,_): pathComponent = "/\(key)?k=22870c026d978c75&t=1489933049" break case let .book(key): pathComponent = "/book/\(key)" break case let .bookHot(key): pathComponent = "/post/review/best-by-book?book=\(key)" break case let .interested(key): pathComponent = "/book/\(key)/recommend" break case let .recommend(key): pathComponent = "/book-list/\(key)/recommend?limit=3" break case .mysteryBook(_): pathComponent = "/book/mystery-box" break case .login(_,_,_,_,_,_): pathComponent = "/user/login" break case .account(_): pathComponent = "/user/account" break case .golden(_): pathComponent = "/account" break case .userDetail(_): pathComponent = "/user/detail-info" break case .userBind(_): pathComponent = "/user/loginBind" break case .logout(_): pathComponent = "/user/logout" break case .bookshelf(_): pathComponent = "/v3/user/bookshelf" break case .SMSCode(_, _, _, _, _): pathComponent = "/v2/sms/sendSms" break case .mobileLogin(_, _, _, _, _): pathComponent = "/user/login" break case .booksheldDelete(_, _): pathComponent = "/v3/user/bookshelf" break case .bookshelfAdd(_, _): pathComponent = "/v3/user/bookshelf" break case .nicknameChange(_, _): pathComponent = "/user/change-nickname" break case let .blessing_bag(token): // https://goldcoin.zhuishushenqi.com/tasks/blessing-bag/detail?token=WupdmBZpkuzehCqdtDZF9IJR pathComponent = "/tasks/blessing-bag/detail?token=\(token)" break // https://api.zhuishushenqi.com/user/v2/judgeSignIn?token=Abrv3NbHCuKKJSVzeSglLXns case .judgeSignIn(_): pathComponent = "/user/v2/judgeSignIn" break // https://api.zhuishushenqi.com/user/signIn?token=Abrv3NbHCuKKJSVzeSglLXns&activityId=57eb9278b7b0f6fc1f2e1bc0&version=2&type=2 case .signIn(_, _, _, _): pathComponent = "/user/signIn" break case let .reviewPost(_,id,_): // https://api.zhuishushenqi.com/post/review/5be2ac16f6459891448e9b46/comment pathComponent = "/post/review/\(id)/comment" break case let .boughtChapters(id,_): // https://api.zhuishushenqi.com/v2/purchase/book/5b10fd1b5d144d1b68581805/chapters/bought?token=rPcCW1GGh1hFPnSRJDjkwjtS pathComponent = "/v2/purchase/book/\(id)/chapters/bought" break case .bookshelfdiff(_, _): // https://api.zhuishushenqi.com/v3/user/bookshelf/diff pathComponent = "/v3/user/bookshelf/diff" break case .voucherList(_, _, _, _): pathComponent = "/voucher" break default: break } return "\(baseURLString)\(pathComponent)" } var baseURLString: String{ var urlString = "http://api.zhuishushenqi.com" switch self { case let .chapter(_, type): switch type { case .chapter: urlString = "http://chapter2.zhuishushenqi.com/chapter" default: urlString = "http://api.zhuishushenqi.com" } case .golden(_): urlString = "http://goldcoin.zhuishushenqi.com" case .blessing_bag(_): urlString = "https://goldcoin.zhuishushenqi.com" default: urlString = "http://api.zhuishushenqi.com" } return urlString } var parameters: [String : Any]?{ switch self { case let .genderRecommend(gender): return ["gender":gender] case let .update(id): return ["view":"updated","id":id] case let .autoComplete(query): return ["query":query] case let .searchBook(id,start,limit): return ["query":id,"start":start,"limit":limit] case .shelfMSG(_): return ["platform":"ios"] case let .categoryList(gender,type,major,minor,start,limit): return ["gender":gender,"type":type,"major":major,"minor":minor,"start":start,"limit":limit] case let .themeTopic(sort,duration,start,gender,tag): return ["sort":sort,"duration":duration,"start":start,"gender":gender,"tag":tag] case let .normalComment(_,start,limit): return ["start":start,"limit":limit] case let .hotPost(_,start,limit): return ["start":start,"limit":limit] case let .allResource(key): return ["view":"summary","book":key] case .allChapters(_): return ["view":"chapters"] case let .login(idfa, platform_code, platform_token, platform_uid, version, tag): return ["idfa":idfa, "platform_code":platform_code, "platform_token":platform_token, "platform_uid":platform_uid, "version":version, "tag":tag] case let .account(token): return ["token":token] case let .golden(token): return ["token":token] case let .userDetail(token): return ["token":token] case let .userBind(token): return ["token":token] case let .logout(token): return ["token":token] case let .bookshelf(token): return ["token":token] case let .SMSCode(mobile,Randstr,Ticket,captchaType,type): return ["mobile":mobile, "Randstr":Randstr, "Ticket":Ticket, "captchaType":captchaType, "type":type] case let .mobileLogin(mobile, idfa, platform_code, smsCode, version): return ["mobile":mobile, "idfa":idfa, "platform_code":platform_code, "smsCode":smsCode, "version":version] case let .booksheldDelete(books, token): return ["books":books, "token":token] case let .bookshelfAdd(books, token): return ["books":books, "token":token] case let .nicknameChange(nickname,token): return ["nickname":nickname, "token":token] case let .judgeSignIn(token): return ["token":token] case let .signIn(token, activityId, version, type): return ["token": token, "activityId": activityId, "version": version, "type": type] case let .reviewPost(token, _, content): return ["token":token, "content":content] case let .boughtChapters(_, token): return ["token":token, ] case let .bookshelfdiff(books, token): return ["books":books, "token":token] case let .voucherList(token, type, start, limit): return ["token":token, "type":"\(type)", "start":"\(start)", "limit":"\(limit)"] default: return nil } } } //https://api.zhuishushenqi.com/v3/user/bookshelf?token=oRSd5bVUCpSunbwiKe5NOpOM //https://api.zhuishushenqi.com/v2/purchase/book/5b10fd1b5d144d1b68581805/chapters/bought?token=rPcCW1GGh1hFPnSRJDjkwjtS ================================================ FILE: zhuishushenqi/Client/QSNetworkManager.swift ================================================ // // QSNetworkManager.swift // zhuishushenqi // // Created by Nory Cao on 2017/3/20. // Copyright © 2017年 QS. All rights reserved. // import UIKit class QSNetworkManager { static let shared = QSNetworkManager() private init(){ } } ================================================ FILE: zhuishushenqi/Extension/Alamofire+ZSExtension.swift ================================================ // // Alamofire+ZSExtension.swift // zhuishushenqi // // Created by yung on 2018/7/31. // Copyright © 2018年 QS. All rights reserved. // import Foundation import Alamofire @discardableResult public func zs_get(_ urlStr: String,parameters: Parameters? = nil) -> DataRequest { return zs_get(urlStr, parameters: parameters, nil) } public func zs_post(_ urlStr: String,parameters: Parameters? = nil) -> DataRequest { return request(urlStr, method: .post, parameters: parameters, encoding: URLEncoding.default, headers: nil) } @discardableResult public func zs_post(_ urlStr: String,parameters: Parameters? = nil,_ handler:ZSBaseCallback<[String:Any]>?) -> DataRequest { var headers = SessionManager.defaultHTTPHeaders headers["User-Agent"] = YouShaQiUserAgent let req = request(urlStr, method: .post, parameters: parameters, encoding: URLEncoding.default, headers: headers).responseJSON { (response) in if let data = response.data { if let json = try? JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.allowFragments) as? [String:Any] { handler?(json) } else { handler?([:]) } } else { handler?([:]) } } return req } @discardableResult public func zs_put(_ urlStr: String,parameters: Parameters? = nil,_ handler:ZSBaseCallback<[String:Any]>?) -> DataRequest { var headers = SessionManager.defaultHTTPHeaders headers["User-Agent"] = YouShaQiUserAgent let req = request(urlStr, method: .put, parameters: parameters, encoding: URLEncoding.default, headers: headers).responseJSON { (response) in if let data = response.data { if let json = try? JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.allowFragments) as? [String:Any] { handler?(json) } else { handler?([:]) } } else { handler?([:]) } } return req } public let YouShaQiUserAgent = "YouShaQi/4.6.2 (iPhone; iOS 12.0; Scale/3.00)" @discardableResult public func zs_get(_ urlStr: String,parameters: Parameters? = nil,_ handler:ZSBaseCallback<[String:Any]>?) -> DataRequest { var headers = SessionManager.defaultHTTPHeaders headers["User-Agent"] = YouShaQiUserAgent let req = request(urlStr, method: .get, parameters: parameters, encoding: URLEncoding.default, headers: headers).responseJSON { (response) in if let data = response.data { if let json = try? JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.allowFragments) as? [String:Any] { handler?(json) } else { handler?([:]) } } else { handler?([:]) } } return req } @discardableResult public func zs_getObj(_ urlStr: String,parameters: Parameters? = nil,_ handler:ZSBaseCallback?) -> DataRequest { var headers = SessionManager.defaultHTTPHeaders headers["User-Agent"] = YouShaQiUserAgent let req = request(urlStr, method: .get, parameters: parameters, encoding: URLEncoding.default, headers: headers).responseJSON { (response) in if let data = response.data { if let json = try? JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.allowFragments) as? [String:Any] { handler?(json) } else if let jsonArray = try? JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.allowFragments) as? [[String:Any]] { handler?(jsonArray) } else { handler?([]) } } else { handler?([]) } } return req } @discardableResult public func zs_delete(_ urlStr: String,parameters: Parameters? = nil,_ handler:ZSBaseCallback<[String:Any]>?) -> DataRequest { var headers = SessionManager.defaultHTTPHeaders headers["User-Agent"] = YouShaQiUserAgent let req = request(urlStr, method: .delete, parameters: parameters, encoding: URLEncoding.default, headers: headers).responseJSON { (response) in if let data = response.data { if let json = try? JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.allowFragments) as? [String:Any] { handler?(json) } else { handler?([:]) } } else { handler?([:]) } } return req } public func zs_download(urlString:String, filePath:String, handler:ZSBaseCallback?) { let pathURL = URL(fileURLWithPath: filePath, isDirectory: true) do { try FileManager.default.createDirectory(at: pathURL, withIntermediateDirectories: true, attributes: nil) } catch { print(error) } let fileName = (urlString as NSString).lastPathComponent let fileURL = pathURL.appendingPathComponent(fileName) let destination: DownloadRequest.DownloadFileDestination = { temporaryURL, _ in return (fileURL, [.createIntermediateDirectories, .removePreviousFile]) } download(urlString, to: destination).response { (response) in if let error = response.error { handler?(error) } else { handler?(response.destinationURL) } } } @discardableResult public func zs_download(url:String, parameters: Parameters? = nil,_ handler:ZSBaseCallback<[String:Any]>?) -> DownloadRequest { let destination = DownloadRequest.suggestedDownloadDestination() let downloadRequest = download(url, method: .get, parameters: parameters, encoding: URLEncoding.default, headers: nil, to: destination).response { (response) in QSLog(response.destinationURL) let exist = FileManager.default.fileExists(atPath: response.destinationURL?.path ?? "") if exist { // 字体文件下载成功 handler?(["url":response.destinationURL?.path ?? ""]) } else { handler?(["error":response.error]) } QSLog(response.temporaryURL) QSLog(response.error) QSLog(response.response) } return downloadRequest } public func downloadFile(urlString:String, handler:NetworkHandler?) { let pathURL = URL(fileURLWithPath: filePath, isDirectory: true) do { try FileManager.default.createDirectory(at: pathURL, withIntermediateDirectories: true, attributes: nil) } catch { print(error) } let fileName = (urlString as NSString).lastPathComponent let fileURL = pathURL.appendingPathComponent(fileName) let destination: DownloadRequest.DownloadFileDestination = { temporaryURL, _ in return (fileURL, [.createIntermediateDirectories, .removePreviousFile]) } download(urlString, to: destination).response { (response) in if let error = response.error { handler?(error) } else { handler?(response.destinationURL) } } } ================================================ FILE: zhuishushenqi/Extension/ApplicationExtension.swift ================================================ // // ApplicationExtension.swift // zhuishushenqi // // Created by yung on 2020/6/18. // Copyright © 2020 QS. All rights reserved. // import Foundation import UIKit #if DEBUG extension UIApplication { @available(iOS 13.0, *) static let windowScene:UIWindowScene? = UIApplication.shared.connectedScenes.filter { $0.activationState == .foregroundActive }.first as? UIWindowScene var keywindow:UIWindow? { if #available(iOS 13.0, *) { return UIApplication.windowScene?.windows.first(where: { $0.isKeyWindow }) } else { return UIApplication.shared.windows.first(where: { $0.isKeyWindow }) } } private class ApplicationState { static let shared = ApplicationState() var current = UIApplication.State.inactive private init() { let center = NotificationCenter.default let mainQueue = OperationQueue.main center.addObserver(forName: UIApplication.didEnterBackgroundNotification, object: nil, queue: mainQueue) { (notification) in self.current = UIApplication.shared.applicationState } center.addObserver(forName: UIApplication.willEnterForegroundNotification, object: nil, queue: mainQueue) { (notification) in self.current = UIApplication.shared.applicationState } center.addObserver(forName: UIApplication.didFinishLaunchingNotification, object: nil, queue: mainQueue) { (notification) in self.current = UIApplication.shared.applicationState } center.addObserver(forName: UIApplication.didBecomeActiveNotification, object: nil, queue: mainQueue) { (notification) in self.current = UIApplication.shared.applicationState } center.addObserver(forName: UIApplication.willResignActiveNotification, object: nil, queue: mainQueue) { (notification) in self.current = UIApplication.shared.applicationState } } } @objc private var __applicationState: UIApplication.State { if Thread.isMainThread { return self.__applicationState } else { return ApplicationState.shared.current } } /// FIXME: -[UIApplication applicationState] called on a background thread. public static func mainThreadApplicationState() { if let originalMethod = class_getInstanceMethod(UIApplication.self, #selector(getter: applicationState)), let swizzledMethod = class_getInstanceMethod(UIApplication.self, #selector(getter: __applicationState)) { _ = ApplicationState.shared method_exchangeImplementations(originalMethod, swizzledMethod) } } } #endif ================================================ FILE: zhuishushenqi/Extension/Array+ZSExtension.swift ================================================ // // Array+ZSExtension.swift // zhuishushenqi // // Created by yung on 2018/7/4. // Copyright © 2018年 QS. All rights reserved. // import Foundation import HandyJSON extension Array { func find (array: [T], item : T) ->Int? { var index = 0 while(index < array.count) { if(item == array[index]) { return index } index = index + 1 } return nil } // 去重 func filterDuplicates(_ filter: (Element) -> E) -> [Element] { var result = [Element]() for value in self { let key = filter(value) if !result.map({filter($0)}).contains(key) { result.append(value) } } return result } subscript (safe index:Int) -> Element? { return (0..Element? { guard startIndex <= index && index < endIndex else { return nil } return self[index] } } extension Array where Element:NSCopying { var copy:[Element] { return self.map { $0.copy(with: nil) as! Element }; } } extension Array where Element:HandyJSON { func toJson() ->[[String:Any]] { var array:[[String:Any]] = [] for item in self { if let json = item.toJSON() { array.append(json) } } return array } } ================================================ FILE: zhuishushenqi/Extension/Date+Extension.swift ================================================ // // Date+Extension.swift // zhuishushenqi // // Created by Nory Cao on 2017/3/15. // Copyright © 2017年 QS. All rights reserved. // import Foundation extension Date{ func year()->Int{ let calendar = Calendar.current var dayComponents:DateComponents? if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_8_0 { dayComponents = calendar.dateComponents([.year], from: self) } return dayComponents?.year ?? 0 } func month()->Int { let calendar = Calendar.current var dayComponents:DateComponents? if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_8_0 { dayComponents = calendar.dateComponents([.month], from: self) } return dayComponents?.month ?? 0 } func day()->Int { let calendar = Calendar.current var dayComponents:DateComponents? if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_8_0 { dayComponents = calendar.dateComponents([.day], from: self) } return dayComponents?.day ?? 0 } func hour()->Int { let calendar = Calendar.current var dayComponents:DateComponents? if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_8_0 { dayComponents = calendar.dateComponents([.hour], from: self) } return dayComponents?.hour ?? 0 } func minute()->Int { let calendar = Calendar.current var dayComponents:DateComponents? if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_8_0 { dayComponents = calendar.dateComponents([.minute], from: self) } return dayComponents?.minute ?? 0 } func second()->Int { let calendar = Calendar.current var dayComponents:DateComponents? if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_8_0 { dayComponents = calendar.dateComponents([.second], from: self) } return dayComponents?.second ?? 0 } func days(month:Int)->Int{ var days = 0 switch month { case 1,3,5,7,8,10,12: days = 31 break case 2: days = isLeapYear() ? 29:28 break case 4,6,9,11: days = 30 break default: break } return days } func isLeapYear()->Bool{ let year = self.year() if (year % 4 == 0 && year % 100 != 0) || year % 400 == 0 { return true } return false } static func timeInterval(from formDate:Date?,to toDate:Date?)->TimeInterval{ if formDate == nil || toDate == nil { return 0 } let dateFormat = DateFormatter() dateFormat.dateFormat = "yyyy-MM-dd hh-mm-ss" let beginTime = formDate?.timeIntervalSince1970 let endTime = toDate?.timeIntervalSince1970 let resultTime = endTime! - beginTime! return resultTime } // + (NSUInteger)daysInMonth:(NSDate *)date month:(NSUInteger)month { // switch (month) { // case 1: case 3: case 5: case 7: case 8: case 10: case 12: // return 31; // case 2: // return [date isLeapYear] ? 29 : 28; // } // return 30; // } } ================================================ FILE: zhuishushenqi/Extension/DateIntervalFormatter+formatter.swift ================================================ // // DateIntervalFormatter+formatter.swift // zhuishushenqi // // Created by Nory Cao on 2017/3/15. // Copyright © 2017年 QS. All rights reserved. // import Foundation extension DateIntervalFormatter{ func formatter(begin:Date?,end:Date?)->TimeInterval{ if begin == nil || end == nil { return 0 } let dateFormat = DateFormatter() dateFormat.dateFormat = "yyyy-MM-dd hh-mm-ss" let beginTime = begin?.timeIntervalSince1970 let endTime = end?.timeIntervalSince1970 if let beginTimeInterval = beginTime,let endTimeInterval = endTime{ let minus = endTimeInterval - beginTimeInterval return minus } return 0 } //Just something func timeInfo(from:Date,to:Date)->String{ let year = to.year() - from.year() let month = to.month() - from.month() let day = to.day() - from.day() var retTime = 1.0 let timeInterval = formatter(begin: from, end: to) if timeInterval < 3600.0 { //小于一小时 retTime = timeInterval / 60.0 retTime = retTime <= 0.0 ? 1.0 : retTime return String(format: "%.0f分钟前",retTime) }else if timeInterval < 3600*24 { //小于一天,也就是今天 retTime = timeInterval / 3600.0 retTime = retTime <= 0.0 ? 1.0 : retTime return String(format: "%.0f小时前",retTime) }else if abs(year) == 0 && abs(month) <= 1 || abs(year) == 1 && to.month() == 1 && from.month() == 12{ // 第一个条件是同年,且相隔时间在一个月内 // 第二个条件是隔年,且相隔时间在一个月内,对于隔年,只能是去年12月与今年1月这种情况 var retDay = 0 if year == 0 {//同年 if month == 0 {//同月 retDay = day } } //跨月 if retDay <= 0{ // 获取发布日期中,该月有多少天 let totalDays = from.days(month: from.month()) // 当前天数 + (发布日期月中的总天数-发布日期月中发布日,即等于距离今天的天数) retDay = to.day() + totalDays - from.day() } return String(format: "%d天前",abs(retDay)) }else {//间隔时间大于一个月 if abs(year) <= 1 { if year == 0 {//同年 return String(format: "%d个月前", (month)) } else {//跨年计算月份 //跨年可能月份大于12个月,按一年来算 let monthCount = 12 - from.month() + to.month() if monthCount > 12 { return String(format: "%d年前",monthCount/12) } return String(format: "%d个月前",monthCount) } } if month < 0 { //未满一年不计算 return String(format: "%d年前",abs(year) - 1) } return String(format: "%d年前",abs(year)) } } } ================================================ FILE: zhuishushenqi/Extension/Dictionary+QSExtension.swift ================================================ // // Dictionary.swift // zhuishushenqi // // Created by yung on 2018/2/11. // Copyright © 2018年 QS. All rights reserved. // import Foundation extension Dictionary { public func valueForKey(key:String) ->Any?{ let dict = self as NSDictionary let value = dict[key] return value } public func key(at index:Int) ->String{ let dict = self as NSDictionary let keys = dict.allKeys let key = keys[index] as? String return key ?? "" } public func value(at index:Int) ->Any?{ let dict = self as NSDictionary let values = dict.allValues let value = values[index] as? BookDetail return value } public func allKeys()->[String]{ let dict = self as NSDictionary let allKeys = dict.allKeys as! [String] return allKeys } public func allValues()->[Any]{ let dict = self as NSDictionary let allValues = dict.allValues as! [Any] return allValues } //MARK: - transform var toJSON: String { if !JSONSerialization.isValidJSONObject(self) { return "" } if let data = try? JSONSerialization.data(withJSONObject: self, options: JSONSerialization.WritingOptions.init(rawValue: 0)) { if let jsonString = String(data: data, encoding: .utf8) { return jsonString } } return "" } } extension Dictionary where Key == String { subscript(bool key:String) ->Bool { if let value = self[key] as? Bool { return value } return false } subscript(string key:String) ->String { if let value = self[key] as? String { return value } return "" } } extension Dictionary where Value: Any { func decoding(with key: Key) -> T? { guard let any: Any = self[key] else { return nil } if let value: T = any as? T { return value } else { switch T.self { case is String.Type: switch any { case let someInt as Int: return String(someInt) as? T case let someDouble as Double: return String(someDouble) as? T case let someBool as Bool: return String(someBool) as? T default: return nil } case is Int.Type: if let someString: String = any as? String { return Int(someString) as? T } else if let someDouble: Double = any as? Double { return Int(someDouble) as? T } else { return nil } case is Double.Type: if let someString: String = any as? String { return Double(someString) as? T } else if let someInt: Int = any as? Int { return Double(someInt) as? T } else { return nil } case is Bool.Type: if let someString: String = any as? String { return Bool(someString) as? T } else { return nil } default: return nil } } } } ================================================ FILE: zhuishushenqi/Extension/DispatchTime+Extension.swift ================================================ // // DispatchTime+Extension.swift // zhuishushenqi // // Created by caony on 2019/6/20. // Copyright © 2019年 QS. All rights reserved. // import Foundation //extension DispatchTime: ExpressibleByIntegerLiteral { // public init(integerLiteral value: Int) { // self = DispatchTime.now() + .seconds(value) // } //} // //extension DispatchTime: ExpressibleByFloatLiteral { // public init(floatLiteral value: Double) { // self = DispatchTime.now() + .milliseconds(Int(value * 1000)) // } //} ================================================ FILE: zhuishushenqi/Extension/LocalizedUtils.swift ================================================ -e import Foundation extension String { var localized: String { return NSLocalizedString(self, comment: self) } /* Localizable.strings zhuishushenqi Created by yung on 2017/8/10. Copyright © 2017年 QS. All rights reserved. */ static var localized_login: String { return "Login".localized } static var localized_logout: String { return "Logout".localized } -e } ================================================ FILE: zhuishushenqi/Extension/NSDate+Extension.h ================================================ // // NSDate+Extension.h // iOS-Categories (https://github.com/shaojiankui/iOS-Categories) // // Created by Jakey on 15/4/25. // Copyright (c) 2015年 www.skyfox.org. All rights reserved. // #import @interface NSDate (Extension) /** * 获取日、月、年、小时、分钟、秒 */ - (NSUInteger)day; - (NSUInteger)month; - (NSUInteger)year; - (NSUInteger)hour; - (NSUInteger)minute; - (NSUInteger)second; + (NSUInteger)day:(NSDate *)date; + (NSUInteger)month:(NSDate *)date; + (NSUInteger)year:(NSDate *)date; + (NSUInteger)hour:(NSDate *)date; + (NSUInteger)minute:(NSDate *)date; + (NSUInteger)second:(NSDate *)date; /** * 获取一年中的总天数 */ - (NSUInteger)daysInYear; + (NSUInteger)daysInYear:(NSDate *)date; /** * 判断是否是润年 * @return YES表示润年,NO表示平年 */ - (BOOL)isLeapYear; + (BOOL)isLeapYear:(NSDate *)date; /** * 获取该日期是该年的第几周 */ - (NSUInteger)weekOfYear; + (NSUInteger)weekOfYear:(NSDate *)date; /** * 获取格式化为YYYY-MM-dd格式的日期字符串 */ - (NSString *)formatYMD; + (NSString *)formatYMD:(NSDate *)date; /** * 返回当前月一共有几周(可能为4,5,6) */ - (NSUInteger)weeksOfMonth; + (NSUInteger)weeksOfMonth:(NSDate *)date; /** * 获取该月的第一天的日期 */ - (NSDate *)begindayOfMonth; + (NSDate *)begindayOfMonth:(NSDate *)date; /** * 获取该月的最后一天的日期 */ - (NSDate *)lastdayOfMonth; + (NSDate *)lastdayOfMonth:(NSDate *)date; /** * 返回day天后的日期(若day为负数,则为|day|天前的日期) */ - (NSDate *)dateAfterDay:(NSUInteger)day; + (NSDate *)dateAfterDate:(NSDate *)date day:(NSInteger)day; /** * 返回day天后的日期(若day为负数,则为|day|天前的日期) */ - (NSDate *)dateAfterMonth:(NSUInteger)month; + (NSDate *)dateAfterDate:(NSDate *)date month:(NSInteger)month; /** * 返回numYears年后的日期 */ - (NSDate *)offsetYears:(int)numYears; + (NSDate *)offsetYears:(int)numYears fromDate:(NSDate *)fromDate; /** * 返回numMonths月后的日期 */ - (NSDate *)offsetMonths:(int)numMonths; + (NSDate *)offsetMonths:(int)numMonths fromDate:(NSDate *)fromDate; /** * 返回numDays天后的日期 */ - (NSDate *)offsetDays:(int)numDays; + (NSDate *)offsetDays:(int)numDays fromDate:(NSDate *)fromDate; /** * 返回numHours小时后的日期 */ - (NSDate *)offsetHours:(int)hours; + (NSDate *)offsetHours:(int)numHours fromDate:(NSDate *)fromDate; /** * 距离该日期前几天 */ - (NSUInteger)daysAgo; + (NSUInteger)daysAgo:(NSDate *)date; /** * 获取星期几 * * @return Return weekday number * [1 - Sunday] * [2 - Monday] * [3 - Tuerday] * [4 - Wednesday] * [5 - Thursday] * [6 - Friday] * [7 - Saturday] */ - (NSInteger)weekday; + (NSInteger)weekday:(NSDate *)date; /** * 获取星期几(名称) * * @return Return weekday as a localized string * [1 - Sunday] * [2 - Monday] * [3 - Tuerday] * [4 - Wednesday] * [5 - Thursday] * [6 - Friday] * [7 - Saturday] */ - (NSString *)dayFromWeekday; + (NSString *)dayFromWeekday:(NSDate *)date; /** * 日期是否相等 * * @param anotherDate The another date to compare as NSDate * @return Return YES if is same day, NO if not */ - (BOOL)isSameDay:(NSDate *)anotherDate; /** * 是否是今天 * * @return Return if self is today */ - (BOOL)isToday; /** * Add days to self * * @param days The number of days to add * @return Return self by adding the gived days number */ - (NSDate *)dateByAddingDays:(NSUInteger)days; /** * Get the month as a localized string from the given month number * * @param month The month to be converted in string * [1 - January] * [2 - February] * [3 - March] * [4 - April] * [5 - May] * [6 - June] * [7 - July] * [8 - August] * [9 - September] * [10 - October] * [11 - November] * [12 - December] * * @return Return the given month as a localized string */ + (NSString *)monthWithMonthNumber:(NSInteger)month; /** * 根据日期返回字符串 */ + (NSString *)stringWithDate:(NSDate *)date format:(NSString *)format; - (NSString *)stringWithFormat:(NSString *)format; + (NSDate *)dateWithString:(NSString *)string format:(NSString *)format; /** * 获取指定月份的天数 */ - (NSUInteger)daysInMonth:(NSUInteger)month; + (NSUInteger)daysInMonth:(NSDate *)date month:(NSUInteger)month; /** * 获取当前月份的天数 */ - (NSUInteger)daysInMonth; + (NSUInteger)daysInMonth:(NSDate *)date; /** * 返回x分钟前/x小时前/昨天/x天前/x个月前/x年前 */ - (NSString *)timeInfo; + (NSString *)timeInfoWithDate:(NSDate *)date; + (NSString *)timeInfoWithDateString:(NSString *)dateString; /** * 分别获取yyyy-MM-dd/HH:mm:ss/yyyy-MM-dd HH:mm:ss格式的字符串 */ - (NSString *)ymdFormat; - (NSString *)hmsFormat; - (NSString *)ymdHmsFormat; + (NSString *)ymdFormat; + (NSString *)hmsFormat; + (NSString *)ymdHmsFormat; + (NSDate*)getDateWithYear:(NSString *)year month:(NSString *)month day:(NSString *)day hour:(NSString *)hour mimute:(NSString *)minute second:(NSString*)second; @end ================================================ FILE: zhuishushenqi/Extension/NSDate+Extension.m ================================================ // // NSDate+Extension.m // iOS-Categories (https://github.com/shaojiankui/iOS-Categories) // // Created by Jakey on 15/4/25. // Copyright (c) 2015年 www.skyfox.org. All rights reserved. // https://github.com/632840804/HYBNSDateExtension #import "NSDate+Extension.h" @implementation NSDate (Extension) - (NSUInteger)day { return [NSDate day:self]; } - (NSUInteger)month { return [NSDate month:self]; } - (NSUInteger)year { return [NSDate year:self]; } - (NSUInteger)hour { return [NSDate hour:self]; } - (NSUInteger)minute { return [NSDate minute:self]; } - (NSUInteger)second { return [NSDate second:self]; } + (NSUInteger)day:(NSDate *)date { NSCalendar *calendar = [NSCalendar currentCalendar]; #if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_8_0 // NSDayCalendarUnit NSDateComponents *dayComponents = [calendar components:(NSCalendarUnitDay) fromDate:date]; #else NSDateComponents *dayComponents = [calendar components:(NSDayCalendarUnit) fromDate:date]; #endif return [dayComponents day]; } + (NSUInteger)month:(NSDate *)date { NSCalendar *calendar = [NSCalendar currentCalendar]; #if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_8_0 // NSDayCalendarUnit NSDateComponents *dayComponents = [calendar components:(NSCalendarUnitMonth) fromDate:date]; #else NSDateComponents *dayComponents = [calendar components:(NSMonthCalendarUnit) fromDate:date]; #endif return [dayComponents month]; } + (NSUInteger)year:(NSDate *)date { NSCalendar *calendar = [NSCalendar currentCalendar]; #if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_8_0 // NSDayCalendarUnit NSDateComponents *dayComponents = [calendar components:(NSCalendarUnitYear) fromDate:date]; #else NSDateComponents *dayComponents = [calendar components:(NSYearCalendarUnit) fromDate:date]; #endif return [dayComponents year]; } + (NSUInteger)hour:(NSDate *)date { NSCalendar *calendar = [NSCalendar currentCalendar]; #if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_8_0 // NSDayCalendarUnit NSDateComponents *dayComponents = [calendar components:(NSCalendarUnitHour) fromDate:date]; #else NSDateComponents *dayComponents = [calendar components:(NSHourCalendarUnit) fromDate:date]; #endif return [dayComponents hour]; } + (NSUInteger)minute:(NSDate *)date { NSCalendar *calendar = [NSCalendar currentCalendar]; #if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_8_0 // NSDayCalendarUnit NSDateComponents *dayComponents = [calendar components:(NSCalendarUnitMinute) fromDate:date]; #else NSDateComponents *dayComponents = [calendar components:(NSMinuteCalendarUnit) fromDate:date]; #endif return [dayComponents minute]; } + (NSUInteger)second:(NSDate *)date { NSCalendar *calendar = [NSCalendar currentCalendar]; #if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_8_0 // NSDayCalendarUnit NSDateComponents *dayComponents = [calendar components:(NSCalendarUnitSecond) fromDate:date]; #else NSDateComponents *dayComponents = [calendar components:(NSSecondCalendarUnit) fromDate:date]; #endif return [dayComponents second]; } - (NSUInteger)daysInYear { return [NSDate daysInYear:self]; } + (NSUInteger)daysInYear:(NSDate *)date { return [self isLeapYear:date] ? 366 : 365; } - (BOOL)isLeapYear { return [NSDate isLeapYear:self]; } + (BOOL)isLeapYear:(NSDate *)date { NSUInteger year = [date year]; if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0) { return YES; } return NO; } - (NSString *)formatYMD { return [NSDate formatYMD:self]; } + (NSString *)formatYMD:(NSDate *)date { return [NSString stringWithFormat:@"%lu-%02lu-%02lu",[date year],[date month], [date day]]; } - (NSUInteger)weeksOfMonth { return [NSDate weeksOfMonth:self]; } + (NSUInteger)weeksOfMonth:(NSDate *)date { return [[date lastdayOfMonth] weekOfYear] - [[date begindayOfMonth] weekOfYear] + 1; } - (NSUInteger)weekOfYear { return [NSDate weekOfYear:self]; } + (NSUInteger)weekOfYear:(NSDate *)date { NSUInteger i; NSUInteger year = [date year]; NSDate *lastdate = [date lastdayOfMonth]; for (i = 1;[[lastdate dateAfterDay:-7 * i] year] == year; i++) { } return i; } - (NSDate *)dateAfterDay:(NSUInteger)day { return [NSDate dateAfterDate:self day:day]; } + (NSDate *)dateAfterDate:(NSDate *)date day:(NSInteger)day { NSCalendar *calendar = [NSCalendar currentCalendar]; NSDateComponents *componentsToAdd = [[NSDateComponents alloc] init]; [componentsToAdd setDay:day]; NSDate *dateAfterDay = [calendar dateByAddingComponents:componentsToAdd toDate:date options:0]; return dateAfterDay; } - (NSDate *)dateAfterMonth:(NSUInteger)month { return [NSDate dateAfterDate:self month:month]; } + (NSDate *)dateAfterDate:(NSDate *)date month:(NSInteger)month { NSCalendar *calendar = [NSCalendar currentCalendar]; NSDateComponents *componentsToAdd = [[NSDateComponents alloc] init]; [componentsToAdd setMonth:month]; NSDate *dateAfterMonth = [calendar dateByAddingComponents:componentsToAdd toDate:date options:0]; return dateAfterMonth; } - (NSDate *)begindayOfMonth { return [NSDate begindayOfMonth:self]; } + (NSDate *)begindayOfMonth:(NSDate *)date { return [self dateAfterDate:date day:-[date day] + 1]; } - (NSDate *)lastdayOfMonth { return [NSDate lastdayOfMonth:self]; } + (NSDate *)lastdayOfMonth:(NSDate *)date { NSDate *lastDate = [self begindayOfMonth:date]; return [[lastDate dateAfterMonth:1] dateAfterDay:-1]; } - (NSUInteger)daysAgo { return [NSDate daysAgo:self]; } + (NSUInteger)daysAgo:(NSDate *)date { NSCalendar *calendar = [NSCalendar currentCalendar]; #if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_8_0 NSDateComponents *components = [calendar components:(NSCalendarUnitDay) fromDate:date toDate:[NSDate date] options:0]; #else NSDateComponents *components = [calendar components:(NSDayCalendarUnit) fromDate:date toDate:[NSDate date] options:0]; #endif return [components day]; } - (NSInteger)weekday { return [NSDate weekday:self]; } + (NSInteger)weekday:(NSDate *)date { NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian]; NSDateComponents *comps = [gregorian components:(NSCalendarUnitDay | NSCalendarUnitMonth | NSCalendarUnitYear | NSCalendarUnitWeekday) fromDate:date]; NSInteger weekday = [comps weekday]; return weekday; } - (NSString *)dayFromWeekday { return [NSDate dayFromWeekday:self]; } + (NSString *)dayFromWeekday:(NSDate *)date { switch([date weekday]) { case 1: return @"星期天"; break; case 2: return @"星期一"; break; case 3: return @"星期二"; break; case 4: return @"星期三"; break; case 5: return @"星期四"; break; case 6: return @"星期五"; break; case 7: return @"星期六"; break; default: break; } return @""; } - (BOOL)isSameDay:(NSDate *)anotherDate { NSCalendar *calendar = [NSCalendar currentCalendar]; NSDateComponents *components1 = [calendar components:(NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay) fromDate:self]; NSDateComponents *components2 = [calendar components:(NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay) fromDate:anotherDate]; return ([components1 year] == [components2 year] && [components1 month] == [components2 month] && [components1 day] == [components2 day]); } - (BOOL)isToday { return [self isSameDay:[NSDate date]]; } - (NSDate *)dateByAddingDays:(NSUInteger)days { NSDateComponents *c = [[NSDateComponents alloc] init]; c.day = days; return [[NSCalendar currentCalendar] dateByAddingComponents:c toDate:self options:0]; } /** * Get the month as a localized string from the given month number * * @param month The month to be converted in string * [1 - January] * [2 - February] * [3 - March] * [4 - April] * [5 - May] * [6 - June] * [7 - July] * [8 - August] * [9 - September] * [10 - October] * [11 - November] * [12 - December] * * @return Return the given month as a localized string */ + (NSString *)monthWithMonthNumber:(NSInteger)month { switch(month) { case 1: return @"January"; break; case 2: return @"February"; break; case 3: return @"March"; break; case 4: return @"April"; break; case 5: return @"May"; break; case 6: return @"June"; break; case 7: return @"July"; break; case 8: return @"August"; break; case 9: return @"September"; break; case 10: return @"October"; break; case 11: return @"November"; break; case 12: return @"December"; break; default: break; } return @""; } + (NSString *)stringWithDate:(NSDate *)date format:(NSString *)format { return [date stringWithFormat:format]; } - (NSString *)stringWithFormat:(NSString *)format { NSDateFormatter *outputFormatter = [[NSDateFormatter alloc] init]; [outputFormatter setDateFormat:format]; NSString *retStr = [outputFormatter stringFromDate:self]; return retStr; } + (NSDate *)dateWithString:(NSString *)string format:(NSString *)format { NSDateFormatter *inputFormatter = [[NSDateFormatter alloc] init]; [inputFormatter setDateFormat:format]; NSDate *date = [inputFormatter dateFromString:string]; return date; } - (NSUInteger)daysInMonth:(NSUInteger)month { return [NSDate daysInMonth:self month:month]; } + (NSUInteger)daysInMonth:(NSDate *)date month:(NSUInteger)month { switch (month) { case 1: case 3: case 5: case 7: case 8: case 10: case 12: return 31; case 2: return [date isLeapYear] ? 29 : 28; } return 30; } - (NSUInteger)daysInMonth { return [NSDate daysInMonth:self]; } + (NSUInteger)daysInMonth:(NSDate *)date { return [self daysInMonth:date month:[date month]]; } - (NSString *)timeInfo { return [NSDate timeInfoWithDate:self]; } + (NSString *)timeInfoWithDate:(NSDate *)date { return [self timeInfoWithDateString:[self stringWithDate:date format:[self ymdHmsFormat]]]; } + (NSString *)timeInfoWithDateString:(NSString *)dateString { NSDate *date = [self dateWithString:dateString format:[self ymdHmsFormat]]; NSDate *curDate = [NSDate date]; NSTimeInterval time = -[date timeIntervalSinceDate:curDate]; int month = (int)([curDate month] - [date month]); int year = (int)([curDate year] - [date year]); int day = (int)([curDate day] - [date day]); NSTimeInterval retTime = 1.0; if (time < 3600) { // 小于一小时 retTime = time / 60; retTime = retTime <= 0.0 ? 1.0 : retTime; return [NSString stringWithFormat:@"%.0f分钟前", retTime]; } else if (time < 3600 * 24) { // 小于一天,也就是今天 retTime = time / 3600; retTime = retTime <= 0.0 ? 1.0 : retTime; return [NSString stringWithFormat:@"%.0f小时前", retTime]; } else if (time < 3600 * 24 * 2) { return @"昨天"; } // 第一个条件是同年,且相隔时间在一个月内 // 第二个条件是隔年,对于隔年,只能是去年12月与今年1月这种情况 else if ((abs(year) == 0 && abs(month) <= 1) || (abs(year) == 1 && [curDate month] == 1 && [date month] == 12)) { int retDay = 0; if (year == 0) { // 同年 if (month == 0) { // 同月 retDay = day; } } if (retDay <= 0) { // 获取发布日期中,该月有多少天 int totalDays = (int)[self daysInMonth:date month:[date month]]; // 当前天数 + (发布日期月中的总天数-发布日期月中发布日,即等于距离今天的天数) retDay = (int)[curDate day] + (totalDays - (int)[date day]); } return [NSString stringWithFormat:@"%d天前", (abs)(retDay)]; } else { if (abs(year) <= 1) { if (year == 0) { // 同年 return [NSString stringWithFormat:@"%d个月前", abs(month)]; } // 隔年 int month = (int)[curDate month]; int preMonth = (int)[date month]; if (month == 12 && preMonth == 12) {// 隔年,但同月,就作为满一年来计算 return @"1年前"; } return [NSString stringWithFormat:@"%d个月前", (abs)(12 - preMonth + month)]; } return [NSString stringWithFormat:@"%d年前", abs(year)]; } return @"1小时前"; } - (NSString *)ymdFormat { return [NSDate ymdFormat]; } - (NSString *)hmsFormat { return [NSDate hmsFormat]; } - (NSString *)ymdHmsFormat { return [NSDate ymdHmsFormat]; } + (NSString *)ymdFormat { return @"yyyy-MM-dd"; } + (NSString *)hmsFormat { return @"HH:mm:ss"; } + (NSString *)ymdHmsFormat { return [NSString stringWithFormat:@"%@ %@", [self ymdFormat], [self hmsFormat]]; } - (NSDate *)offsetYears:(int)numYears { return [NSDate offsetYears:numYears fromDate:self]; } + (NSDate *)offsetYears:(int)numYears fromDate:(NSDate *)fromDate { if (fromDate == nil) { return nil; } #if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_8_0 // NSDayCalendarUnit NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian]; #else NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar]; #endif NSDateComponents *offsetComponents = [[NSDateComponents alloc] init]; [offsetComponents setYear:numYears]; return [gregorian dateByAddingComponents:offsetComponents toDate:fromDate options:0]; } - (NSDate *)offsetMonths:(int)numMonths { return [NSDate offsetMonths:numMonths fromDate:self]; } + (NSDate *)offsetMonths:(int)numMonths fromDate:(NSDate *)fromDate { if (fromDate == nil) { return nil; } #if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_8_0 // NSDayCalendarUnit NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian]; #else NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar]; #endif NSDateComponents *offsetComponents = [[NSDateComponents alloc] init]; [offsetComponents setMonth:numMonths]; return [gregorian dateByAddingComponents:offsetComponents toDate:fromDate options:0]; } - (NSDate *)offsetDays:(int)numDays { return [NSDate offsetDays:numDays fromDate:self]; } + (NSDate *)offsetDays:(int)numDays fromDate:(NSDate *)fromDate { if (fromDate == nil) { return nil; } #if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_8_0 // NSDayCalendarUnit NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian]; #else NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar]; #endif NSDateComponents *offsetComponents = [[NSDateComponents alloc] init]; [offsetComponents setDay:numDays]; return [gregorian dateByAddingComponents:offsetComponents toDate:fromDate options:0]; } - (NSDate *)offsetHours:(int)hours { return [NSDate offsetHours:hours fromDate:self]; } + (NSDate *)offsetHours:(int)numHours fromDate:(NSDate *)fromDate { if (fromDate == nil) { return nil; } #if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_8_0 // NSDayCalendarUnit NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian]; #else NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar]; #endif NSDateComponents *offsetComponents = [[NSDateComponents alloc] init]; [offsetComponents setHour:numHours]; return [gregorian dateByAddingComponents:offsetComponents toDate:fromDate options:0]; } + (NSDate*)getDateWithYear:(NSString *)year month:(NSString *)month day:(NSString *)day hour:(NSString *)hour mimute:(NSString *)minute second:(NSString*)second { NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian]; NSDateComponents *comps = [[NSDateComponents alloc] init]; [comps setYear:[year integerValue]]; [comps setMonth:[month integerValue]]; [comps setDay:[day integerValue]]; [comps setHour:[hour integerValue]]; [comps setMinute:[minute integerValue]]; [comps setSecond:[second integerValue]]; NSDate *date = [calendar dateFromComponents:comps]; NSTimeInterval timeInterval = [self offsetTimeIntervalFromCurrentTimeZoneForDate:date]; return [NSDate dateWithTimeInterval:timeInterval sinceDate:date]; } + (NSTimeInterval)offsetTimeIntervalFromCurrentTimeZoneForDate:(NSDate *)anyDate { //设置源日期时区 NSTimeZone* sourceTimeZone = [NSTimeZone timeZoneWithAbbreviation:@"UTC"];//或GMT //设置转换后的目标日期时区 NSTimeZone* destinationTimeZone = [NSTimeZone localTimeZone]; //得到源日期与世界标准时间的偏移量 NSInteger sourceGMTOffset = [sourceTimeZone secondsFromGMTForDate:anyDate]; //目标日期与本地时区的偏移量 NSInteger destinationGMTOffset = [destinationTimeZone secondsFromGMTForDate:anyDate]; //得到时间偏移量的差值 NSTimeInterval interval = destinationGMTOffset - sourceGMTOffset; return interval; } @end ================================================ FILE: zhuishushenqi/Extension/NSObject+Extension.swift ================================================ // // NSObject+Extension.swift // zhuishushenqi // // Created by yung on 2018/2/6. // Copyright © 2018年 QS. All rights reserved. // import Foundation extension NSObject { public func fetchProperties() ->[String:Any] { var outCount:UInt32 = 0 let properties = class_copyPropertyList(self.classForCoder, &outCount) var dict = [String:Any]() let count = Int(outCount) for idx in 0.. @interface NSString (Encode) - (NSString*)urlEncode; - (NSString *)urlDecode; - (NSString *)zs_urlDecode; + (NSStringEncoding)nh_stringEncodingForData:(NSData *)data encodingOptions:(NSDictionary *)opts convertedString:(NSString * _Nullable *)string usedLossyConversion:(BOOL *)usedLossyConversion; + (NSString *)fileEncoding:(NSString *)path; + (double)totalMemory; + (double)availableMemory; + (double)getMemoryUsage; @end ================================================ FILE: zhuishushenqi/Extension/NSString+Encode.m ================================================ // // NSString+Encode.m // zhuishushenqi // // Created by yung on 2017/4/26. // Copyright © 2017年 QS. All rights reserved. // #import "NSString+Encode.h" #import "uchardet.h" #import @implementation NSString (Encode) - (NSString*)urlEncode{ //different library use slightly different escaped and unescaped set. //below is copied from AFNetworking but still escaped [] as AF leave them for Rails array parameter which we don't use. //https://github.com/AFNetworking/AFNetworking/pull/555 NSString *result = (NSString *)CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (__bridge CFStringRef)self, CFSTR("."), CFSTR(":/?#[]@!$&'()*+,;="), kCFStringEncodingUTF8)); return result; } - (NSString *)urlDecode{ // NSString *result = (NSString *)CFBridgingRelease(CFURLCreateStringByReplacingPercentEscapesUsingEncoding(kCFAllocatorDefault,(__bridge CFStringRef)self,CFSTR(":/?#[]@!$&'()*+,;="),kCFStringEncodingUTF8)); NSString *result = (NSString *)CFBridgingRelease(CFURLCreateStringByReplacingPercentEscapes(kCFAllocatorDefault,(__bridge CFStringRef)self,CFSTR(":/?#[]@!$&'()*+,;="))); return result; } - (NSString *)zs_urlDecode{ NSString *result = [self stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; return result; } + (NSStringEncoding)nh_stringEncodingForData:(NSData *)data encodingOptions:(NSDictionary *)opts convertedString:(NSString * _Nullable *)string usedLossyConversion:(BOOL *)usedLossyConversion { return [NSString stringEncodingForData:data encodingOptions:opts convertedString:string usedLossyConversion:usedLossyConversion]; } + (NSString *)fileEncoding:(NSString *)path { FILE* file; char buf[2048]; size_t len; uchardet_t ud; /* 打开被检测文本文件,并读取一定数量的样本字符 */ file = fopen([path UTF8String], "rt"); if (file==NULL) { printf("文件打开失败!\n"); return @""; } len = fread(buf, sizeof(char), 2048, file); fclose(file); ud = uchardet_new(); if(uchardet_handle_data(ud, buf, len) != 0) { printf("分析编码失败!\n"); return @""; } uchardet_data_end(ud); const char * encode = uchardet_get_charset(ud); printf("文本的编码方式是%s。\n", encode); NSString * encodeString = [NSString stringWithFormat:@"%s", encode]; uchardet_delete(ud); return encodeString; } + (double)totalMemory { mach_port_t host_port; mach_msg_type_number_t host_size; vm_size_t pagesize; host_port = mach_host_self(); host_size = sizeof(vm_statistics_data_t) / sizeof(integer_t); host_page_size(host_port, &pagesize); vm_statistics_data_t vm_stat; if(host_statistics(host_port,HOST_VM_INFO, (host_info_t)&vm_stat, &host_size) !=KERN_SUCCESS) { return NSNotFound; } /* Stats in bytes */ uintptr_t mem_used = (vm_stat.active_count + vm_stat.inactive_count + vm_stat.wire_count) * pagesize; uintptr_t mem_free = vm_stat.free_count* pagesize; uintptr_t mem_total = mem_used + mem_free; return mem_total/1024.0/1024.0; } + (double)availableMemory { vm_statistics_data_t vmStats; mach_msg_type_number_t infoCount = HOST_VM_INFO_COUNT; kern_return_t kernReturn = host_statistics(mach_host_self(), HOST_VM_INFO, (host_info_t)&vmStats, &infoCount); if (kernReturn != KERN_SUCCESS) { return NSNotFound; } return ((vm_page_size * vmStats.free_count)/1024.0)/1024.0; } // 获取当前应用的内存占用情况,和Xcode数值相近 + (double)getMemoryUsage { task_vm_info_data_t vmInfo; mach_msg_type_number_t count = TASK_VM_INFO_COUNT; kern_return_t kernelReturn = task_info(mach_task_self(), TASK_VM_INFO, (task_info_t) &vmInfo, &count); if(kernelReturn == KERN_SUCCESS) { return (double)vmInfo.phys_footprint / (1024 * 1024); } else { return -1.0; } } @end ================================================ FILE: zhuishushenqi/Extension/NotificationCenter+QSExtension.swift ================================================ // // Notification+QSExtension.swift // zhuishushenqi // // Created by yung on 2018/2/6. // Copyright © 2018年 QS. All rights reserved. // import Foundation public typealias NotificationHandler = () ->Void extension NotificationCenter { static var observerHandler:NotificationHandler? public static func qs_addObserver(observer:Any,selector:Selector,name:String,object:Any?) -> Void { NotificationCenter.default.addObserver(observer, selector: selector, name: Notification.Name(rawValue: name), object: object) } public static func zs_addObserver(oberver:Any,name:String,_ handler:NotificationHandler?){ observerHandler = handler self.qs_addObserver(observer: self, selector: #selector(NotificationCenter.observerHandlerAction), name: name, object: nil) } public static func qs_postNotification(name:String,obj:Any?){ let noti = Notification(name: Notification.Name(rawValue: name), object: obj, userInfo: nil) NotificationCenter.default.post(noti) } public static func qs_removeObserver(observer:Any,name:String,object:Any?){ NotificationCenter.default.removeObserver(observer, name: Notification.Name(rawValue: name), object: object) } @objc public static func observerHandlerAction(){ NotificationCenter.observerHandler?() } } extension Notification.Name { static let ReachabilityStatusChanged = Notification.Name("ReachabilityStatusChanged") static let UIKeyboardWillShow = UIResponder.keyboardWillShowNotification static let DisplayThemeChanged = Notification.Name("DisplayThemeChanged") static let ReaderStyleChanged = Notification.Name("ReaderStyleChanged") static let ReaderFontSizeChanged = Notification.Name("ReaderFontSizeChanged") static let ReaderAnimationStyleChanged = Notification.Name("ReaderAnimationStyleChanged") static let LocalShelfChanged = Notification.Name("LocalShelfChanged") static let ShelfChanged = Notification.Name("ShelfChanged") } ================================================ FILE: zhuishushenqi/Extension/Reachability.swift ================================================ /* Copyright (c) 2014, Ashley Mills All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ import SystemConfiguration import Foundation import CoreTelephony public enum ReachabilityError: Error { case FailedToCreateWithAddress(sockaddr_in) case FailedToCreateWithHostname(String) case UnableToSetCallback case UnableToSetDispatchQueue } public enum QSNetworkType:Int{ case UnKnown case WWAN2G case WWAN3G case WWAN4G case WiFi case UnReachable } public let ReachabilityChangedNotification = NSNotification.Name("ReachabilityChangedNotification") func callback(reachability:SCNetworkReachability, flags: SCNetworkReachabilityFlags, info: UnsafeMutableRawPointer?) { guard let info = info else { return } let reachability = Unmanaged.fromOpaque(info).takeUnretainedValue() DispatchQueue.main.async { reachability.reachabilityChanged() } } public class Reachability { public typealias NetworkReachable = (Reachability) -> () public typealias NetworkUnreachable = (Reachability) -> () public enum NetworkStatus: CustomStringConvertible { case notReachable, reachableViaWiFi, reachableViaWWAN public var description: String { switch self { case .reachableViaWWAN: return "Cellular" case .reachableViaWiFi: return "WiFi" case .notReachable: return "No Connection" } } } public var whenReachable: NetworkReachable? public var whenUnreachable: NetworkUnreachable? public var reachableOnWWAN: Bool // The notification center on which "reachability changed" events are being posted public var notificationCenter: NotificationCenter = NotificationCenter.default public var currentReachabilityString: String { return "\(currentReachabilityStatus)" } public var currentReachabilityStatus: NetworkStatus { guard isReachable else { return .notReachable } if isReachableViaWiFi { return .reachableViaWiFi } if isRunningOnDevice { return .reachableViaWWAN } return .notReachable } fileprivate var previousFlags: SCNetworkReachabilityFlags? fileprivate var isRunningOnDevice: Bool = { #if (arch(i386) || arch(x86_64)) && os(iOS) return false #else return true #endif }() fileprivate var notifierRunning = false fileprivate var reachabilityRef: SCNetworkReachability? fileprivate let reachabilitySerialQueue = DispatchQueue(label: "uk.co.ashleymills.reachability") required public init(reachabilityRef: SCNetworkReachability) { reachableOnWWAN = true self.reachabilityRef = reachabilityRef } public convenience init?(hostname: String) { guard let ref = SCNetworkReachabilityCreateWithName(nil, hostname) else { return nil } self.init(reachabilityRef: ref) } public convenience init?() { var zeroAddress = sockaddr() zeroAddress.sa_len = UInt8(MemoryLayout.size) zeroAddress.sa_family = sa_family_t(AF_INET) guard let ref: SCNetworkReachability = withUnsafePointer(to: &zeroAddress, { SCNetworkReachabilityCreateWithAddress(nil, UnsafePointer($0)) }) else { return nil } self.init(reachabilityRef: ref) } deinit { stopNotifier() reachabilityRef = nil whenReachable = nil whenUnreachable = nil } } public extension Reachability { // MARK: - *** Notifier methods *** func startNotifier() throws { guard let reachabilityRef = reachabilityRef, !notifierRunning else { return } var context = SCNetworkReachabilityContext(version: 0, info: nil, retain: nil, release: nil, copyDescription: nil) context.info = UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque()) if !SCNetworkReachabilitySetCallback(reachabilityRef, callback, &context) { stopNotifier() throw ReachabilityError.UnableToSetCallback } if !SCNetworkReachabilitySetDispatchQueue(reachabilityRef, reachabilitySerialQueue) { stopNotifier() throw ReachabilityError.UnableToSetDispatchQueue } // Perform an intial check reachabilitySerialQueue.async { self.reachabilityChanged() } notifierRunning = true } func stopNotifier() { defer { notifierRunning = false } guard let reachabilityRef = reachabilityRef else { return } SCNetworkReachabilitySetCallback(reachabilityRef, nil, nil) SCNetworkReachabilitySetDispatchQueue(reachabilityRef, nil) } // MARK: - *** Connection test methods *** var isReachable: Bool { guard isReachableFlagSet else { return false } if isConnectionRequiredAndTransientFlagSet { return false } if isRunningOnDevice { if isOnWWANFlagSet && !reachableOnWWAN { // We don't want to connect when on 3G. return false } } return true } var isReachableViaWWAN: Bool { // Check we're not on the simulator, we're REACHABLE and check we're on WWAN return isRunningOnDevice && isReachableFlagSet && isOnWWANFlagSet } var networkType:QSNetworkType { if isReachableViaWWAN { let type2G = [CTRadioAccessTechnologyEdge, CTRadioAccessTechnologyGPRS, CTRadioAccessTechnologyCDMA1x] let type3G = [CTRadioAccessTechnologyHSDPA, CTRadioAccessTechnologyWCDMA, CTRadioAccessTechnologyHSUPA, CTRadioAccessTechnologyCDMAEVDORev0, CTRadioAccessTechnologyCDMAEVDORevA, CTRadioAccessTechnologyCDMAEVDORevB, CTRadioAccessTechnologyeHRPD] let type4G = [CTRadioAccessTechnologyLTE] let telephoneInfo = CTTelephonyNetworkInfo() let access = telephoneInfo.currentRadioAccessTechnology if type2G.contains(access ?? "") { return .WWAN2G }else if type3G.contains(access ?? ""){ return .WWAN3G }else if type4G.contains(access ?? ""){ return .WWAN4G }else { return .UnKnown } }else if isReachableViaWiFi { return .WiFi }else if isReachable{ return .UnKnown } return .UnReachable } var isReachableViaWiFi: Bool { // Check we're reachable guard isReachableFlagSet else { return false } // If reachable we're reachable, but not on an iOS device (i.e. simulator), we must be on WiFi guard isRunningOnDevice else { return true } // Check we're NOT on WWAN return !isOnWWANFlagSet } var description: String { let W = isRunningOnDevice ? (isOnWWANFlagSet ? "W" : "-") : "X" let R = isReachableFlagSet ? "R" : "-" let c = isConnectionRequiredFlagSet ? "c" : "-" let t = isTransientConnectionFlagSet ? "t" : "-" let i = isInterventionRequiredFlagSet ? "i" : "-" let C = isConnectionOnTrafficFlagSet ? "C" : "-" let D = isConnectionOnDemandFlagSet ? "D" : "-" let l = isLocalAddressFlagSet ? "l" : "-" let d = isDirectFlagSet ? "d" : "-" return "\(W)\(R) \(c)\(t)\(i)\(C)\(D)\(l)\(d)" } } fileprivate extension Reachability { func reachabilityChanged() { let flags = reachabilityFlags guard previousFlags != flags else { return } let block = isReachable ? whenReachable : whenUnreachable block?(self) self.notificationCenter.post(name: ReachabilityChangedNotification, object:self) previousFlags = flags } var isOnWWANFlagSet: Bool { #if os(iOS) return reachabilityFlags.contains(.isWWAN) #else return false #endif } var isReachableFlagSet: Bool { return reachabilityFlags.contains(.reachable) } var isConnectionRequiredFlagSet: Bool { return reachabilityFlags.contains(.connectionRequired) } var isInterventionRequiredFlagSet: Bool { return reachabilityFlags.contains(.interventionRequired) } var isConnectionOnTrafficFlagSet: Bool { return reachabilityFlags.contains(.connectionOnTraffic) } var isConnectionOnDemandFlagSet: Bool { return reachabilityFlags.contains(.connectionOnDemand) } var isConnectionOnTrafficOrDemandFlagSet: Bool { return !reachabilityFlags.intersection([.connectionOnTraffic, .connectionOnDemand]).isEmpty } var isTransientConnectionFlagSet: Bool { return reachabilityFlags.contains(.transientConnection) } var isLocalAddressFlagSet: Bool { return reachabilityFlags.contains(.isLocalAddress) } var isDirectFlagSet: Bool { return reachabilityFlags.contains(.isDirect) } var isConnectionRequiredAndTransientFlagSet: Bool { return reachabilityFlags.intersection([.connectionRequired, .transientConnection]) == [.connectionRequired, .transientConnection] } var reachabilityFlags: SCNetworkReachabilityFlags { guard let reachabilityRef = reachabilityRef else { return SCNetworkReachabilityFlags() } var flags = SCNetworkReachabilityFlags() let gotFlags = withUnsafeMutablePointer(to: &flags) { SCNetworkReachabilityGetFlags(reachabilityRef, UnsafeMutablePointer($0)) } if gotFlags { return flags } else { return SCNetworkReachabilityFlags() } } } ================================================ FILE: zhuishushenqi/Extension/SQLite+Extension.swift ================================================ // // SQLite+Extension.swift // zhuishushenqi // // Created by yung on 2018/8/11. // Copyright © 2018年 QS. All rights reserved. // import Foundation import SQLite protocol DBSaveProtocol { func db_save() } extension NSObject { public func db_connect(_ path:String?){ var db:Connection! if let dbPath = path { db = try? Connection(dbPath) } else { let dbPath = NSHomeDirectory() db = try? Connection("\(dbPath)/db.sqlite3") } } public func test(){ // let db = try? Connection("path/to/db.sqlite3") // // let users = Table("users") // let id = Expression("id") // let name = Expression("name") // let email = Expression("email") // // try? db?.run(users.create { t in // t.column(id, primaryKey: true) // t.column(name) // t.column(email, unique: true) // }) // // CREATE TABLE "users" ( // // "id" INTEGER PRIMARY KEY NOT NULL, // // "name" TEXT, // // "email" TEXT NOT NULL UNIQUE // // ) // // let insert = users.insert(name <- "Alice", email <- "alice@mac.com") // let rowid = try? db?.run(insert) // // INSERT INTO "users" ("name", "email") VALUES ('Alice', 'alice@mac.com') // // for user in try? db?.prepare(users) { // print("id: \(user[id]), name: \(user[name]), email: \(user[email])") // // id: 1, name: Optional("Alice"), email: alice@mac.com // } // // SELECT * FROM "users" // // let alice = users.filter(id == rowid) // // try? db.run(alice.update(email <- email.replace("mac.com", with: "me.com"))) // // UPDATE "users" SET "email" = replace("email", 'mac.com', 'me.com') // // WHERE ("id" = 1) // // try? db?.run(alice.delete()) // // DELETE FROM "users" WHERE ("id" = 1) // // try? db?.scalar(users.count) // 0 // // SELECT count(*) FROM "users" } } ================================================ FILE: zhuishushenqi/Extension/String+QSExtension.swift ================================================ // // String+crypto.swift // zhuishushenqi // // Created by Nory Cao on 2017/3/6. // Copyright © 2017年 QS. All rights reserved. // import Foundation import CommonCrypto extension String { //MARK: - crypto func md5() ->String{ let str = self.cString(using: String.Encoding.utf8) let strLen = CUnsignedInt(self.lengthOfBytes(using: String.Encoding.utf8)) let digestLen = Int(CC_MD5_DIGEST_LENGTH) let result = UnsafeMutablePointer.allocate(capacity: digestLen) CC_MD5(str!, strLen, result) let hash = NSMutableString() for i in 0 ..< digestLen { hash.appendFormat("%02x", result[i]) } result.deinitialize(count: digestLen) return String(format: hash as String) } //由于移动设备的内存有限,以下代码实现是将文件分块读出并且计算md5值的方法 func fileMD5()->String? { let handler = FileHandle(forReadingAtPath: self) if handler == nil { return nil } let ctx = UnsafeMutablePointer.allocate(capacity: MemoryLayout.size) CC_MD5_Init(ctx) var done = false while !done { let fileData = handler?.readData(ofLength: 256) fileData?.withUnsafeBytes({ (bytes) -> Void in CC_MD5_Update(ctx, bytes, CC_LONG(fileData!.count)) }) if fileData?.count == 0 { done = true } } let digestLen = Int(CC_MD5_DIGEST_LENGTH) let digest = UnsafeMutablePointer.allocate(capacity: digestLen) CC_MD5_Final(digest, ctx) var hash = "" for i in 0..String{ if self == "" { return self } var ends = end if self.count < ends { ends = self.count } let startIndex = self.index(self.startIndex, offsetBy: start) let endIndex = self.index(self.startIndex, offsetBy: ends) let range = startIndex..String{ if self == "" { return self } let startIndex = self.index(self.startIndex, offsetBy: start) let endIndex = self.index(self.startIndex, offsetBy: start + length) let range = startIndex..String{ if self == "" { return self } let startIndex = self.index(self.startIndex, offsetBy: from) let endIndex = self.endIndex let range = startIndex..String{ if self == "" { return self } let startIndex = self.startIndex let endIndex = self.index(self.startIndex, offsetBy: to) let range = startIndex..)->String{ if self == "" { return self } let startIndex = range.startIndex let endIndex = range.endIndex let sub = self.qs_subStr(start: startIndex, end: endIndex) return sub } func qs_subStr(range:NSRange)->String{ if self == "" { return self } let start = range.location let end = range.location + range.length return self.qs_subStr(start: start, end: end) } //MARK:- count func qs_width(_ font:UIFont,height:CGFloat) ->CGFloat { let dict = [NSAttributedString.Key.font:font] let sttt:NSString = self as NSString let rect:CGRect = sttt.boundingRect(with: CGSize(width: CGFloat(MAXFLOAT), height: CGFloat(height)), options: NSStringDrawingOptions.usesLineFragmentOrigin, attributes: dict, context: nil) return rect.size.width } func qs_height(_ font:UIFont,width:CGFloat) ->CGFloat { let dict = [NSAttributedString.Key.font:font] let sttt:NSString = self as NSString let rect:CGRect = sttt.boundingRect(with: CGSize(width: width, height: CGFloat(MAXFLOAT)), options: NSStringDrawingOptions.usesLineFragmentOrigin, attributes: dict, context: nil) return rect.size.height } func qs_width(_ fontSize:CGFloat,height:CGFloat) -> CGFloat{ let dict = [NSAttributedString.Key.font:UIFont.systemFont(ofSize: fontSize)] let sttt:NSString = self as NSString let rect:CGRect = sttt.boundingRect(with: CGSize(width: CGFloat(MAXFLOAT), height: CGFloat(height)), options: NSStringDrawingOptions.usesLineFragmentOrigin, attributes: dict, context: nil) return rect.size.width } func qs_height(_ fontSize:CGFloat,width:CGFloat) ->CGFloat { let dict = [NSAttributedString.Key.font:UIFont.systemFont(ofSize: fontSize)] let sttt:NSString = self as NSString let rect:CGRect = sttt.boundingRect(with: CGSize(width: width, height: CGFloat(MAXFLOAT)), options: NSStringDrawingOptions.usesLineFragmentOrigin, attributes: dict, context: nil) return rect.size.height } //获取子字符串 func substingInRange(_ r: Range) -> String? { if r.lowerBound < 0 || r.upperBound > self.count { return nil } let startIndex = self.index(self.startIndex, offsetBy:r.lowerBound) let endIndex = self.index(self.startIndex, offsetBy:r.upperBound) return String(self[startIndex..Bool { if self == string { return true } return false } //MARK: - string func asNSString() ->NSString { return (self as NSString) } var ocString:NSString { return (self as NSString) } var nsString:NSString { return (self as NSString) } func isEmpty() ->Bool { if self == "" || self.count == 0 { return true } return false } func schemeURLString(_ host:String = "") ->String { var urlString:String = self if !urlString.hasPrefix("http") { if urlString.hasPrefix("/") && host.hasSuffix("/") { let host = host.qs_subStr(start: 0, length: host.length - 1) urlString = "\(host)\(urlString)" } else { urlString = "\(host)\(urlString)" } } return urlString } func httpScheme()->String { var icon = self if icon.hasPrefix("//") { icon = icon.replacingOccurrences(of: "//", with: "http://") } return icon } func httpsScheme()->String { var icon = self if icon.hasPrefix("//") { icon = icon.replacingOccurrences(of: "//", with: "https://") } return icon } //MARK: - transform var length:Int { return self.lengthOfBytes(using: .utf8) } var oc_length:Int { return self.asNSString().length } var toArray:Array<[String:Any]>? { var json = self if json.contains("\\") { json = json.components(separatedBy: "\\").joined() } if let data = json.data(using: .utf8) { do { let arr = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.init(rawValue: 0)) as? Array<[String:Any]> return arr } catch let error { QSLog(error) } } return nil } var toDict:Dictionary? { var json = self if json.contains("\\") { json = json.components(separatedBy: "\\").joined() } if let data = json.data(using: .utf8) { do { let dict = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.init(rawValue: 0)) as? Dictionary guard let _ = dict?.first else { return nil } return dict } catch let error { QSLog(error) } } return nil } static func stringFromDataDetectingEncoding(data: Data, suggestedEncodings: [String.Encoding] = []) -> (string: String, encoding: String.Encoding, lossy: Bool)? { var outString: NSString? = nil var lossyConversion: ObjCBool = false var suggestedEncodingsValues:[NSNumber] = [] suggestedEncodingsValues = suggestedEncodings.map { (encode) -> NSNumber in return NSNumber.init(value: encode.rawValue) } let encoding = NSString.stringEncoding(for: data as Data, encodingOptions: [StringEncodingDetectionOptionsKey.suggestedEncodingsKey:suggestedEncodingsValues], convertedString: &outString, usedLossyConversion: &lossyConversion) guard let foundString = outString else { return nil } return (string: foundString as String, encoding: String.Encoding.init(rawValue: encoding), lossy: lossyConversion.boolValue) } } extension Optional { } extension String.Encoding { static var gbk:String.Encoding { let encode = CFStringConvertEncodingToNSStringEncoding(CFStringEncoding(CFStringEncodings.GB_18030_2000.rawValue)) return String.Encoding(rawValue: encode) } static func zs_encoding(str:String)->String.Encoding { switch str { case ".utf8": return .utf8 case ".gbk": return gbk default: return .utf8 } } } ================================================ FILE: zhuishushenqi/Extension/SwiftStdlib/FloatExtensions.swift ================================================ // // FloatExtensions.swift // zhuishushenqi // // Created by caony on 2019/1/22. // Copyright © 2019年 QS. All rights reserved. // import Foundation public extension Float { public var int:Int { return Int(self) } public var double:Double { return Double(self) } public var cgfloat:CGFloat { return CGFloat(self) } } ================================================ FILE: zhuishushenqi/Extension/SwiftStdlib/IntExtensions.swift ================================================ // // IntExtensions.swift // zhuishushenqi // // Created by caony on 2019/1/24. // Copyright © 2019年 QS. All rights reserved. // import Foundation extension Int { public var float:Float { return Float(self) } public var double:Double { return Double(self) } public var cgfloat:CGFloat { return CGFloat(self) } } ================================================ FILE: zhuishushenqi/Extension/UIButton+Extension.swift ================================================ // // UIButton+Extension.swift // zhuishushenqi // // Created by caony on 2019/7/2. // Copyright © 2019 QS. All rights reserved. // import UIKit extension UIButton { func qs_setBookCoverWithURLString(urlString:String){ setImage(UIImage(named: "default_book_cover"), for: .normal) // DispatchQueue.global().async { let noPercentStr:String = urlString.removingPercentEncoding ?? "" var urlStr = noPercentStr if urlStr.qs_subStr(to: 4) != "http"{ // urlStr = urlStr.qs_subStr(from: 7) urlStr = "\(IMAGE_BASEURL)\(urlStr)-coverl" } if urlStr.contains("http") == false { urlStr = "\(IMAGE_BASEURL)\(noPercentStr)" } let url = URL(string: urlStr) guard let imageURL = url else { QSLog("Invalid URL") return } let resource:QSResource = QSResource(url: imageURL) self.kf.setImage(with: resource, for: .normal, placeholder: UIImage(named: "default_book_cover")) } func qs_setAvatarWithURLString(urlString:String){ setImage(UIImage(named: "default_avatar_light"), for: .normal) let imageUrlString = "\(IMAGE_BASEURL)\(urlString)" let url:URL? = URL(string: imageUrlString) guard let imageURL = url else { QSLog("Invalid URL") return } let resource:QSResource = QSResource(url: imageURL) self.kf.setImage(with: resource, for: .normal, placeholder: UIImage(named: "default_avatar_light")) } } ================================================ FILE: zhuishushenqi/Extension/UICollectionView+QSExtension.swift ================================================ // // UICollectionView+QSExtension.swift // zhuishushenqi // // Created by yung on 2017/8/22. // Copyright © 2017年 QS. All rights reserved. // import Foundation import UIKit protocol ReusableView: class { static var reuseId: String {get} } extension ReusableView where Self: UIView { static var reuseId: String { return String(describing: self) } } extension UICollectionReusableView: ReusableView { } extension UICollectionView { func qs_registerCellNib(_ aClass:T.Type){ let name = String(describing: aClass) let nib = UINib(nibName: name, bundle: nil) self.register(nib, forCellWithReuseIdentifier: name) } func qs_registerCellClass(_ aClass:T.Type){ let name = String(describing:aClass) self.register(aClass, forCellWithReuseIdentifier: name) } func qs_dequeueReusableCell(_ aClass:T.Type, for indexPath:IndexPath)->T where T:ReusableView{ let name = String(describing:aClass) guard let cell = dequeueReusableCell(withReuseIdentifier: name, for: indexPath) as? T else { fatalError("\(name) is not registered") } return cell } // func qs_registerHeaderFooterNib(_ aClass:T.Type){ // let name = String(describing: aClass) // let nib = UINib(nibName: name, bundle: nil) // self.register(nib, forHeaderFooterViewReuseIdentifier: name) // } // // func qs_registerHeaderFooterClass(_ aClass:T.Type){ // let name = String(describing:aClass) // self.register(aClass, forHeaderFooterViewReuseIdentifier: name) // } // // func qs_dequeueReusableHeaderFooterView(_ aClass:T.Type)->T!{ // let name = String(describing:aClass) // guard let cell = dequeueReusableHeaderFooterView(withIdentifier: name) as? T else { // fatalError("\(name) is not registered") // } // return cell // } } ================================================ FILE: zhuishushenqi/Extension/UIColor+Theme.swift ================================================ // // UIColor+Theme.swift // zhuishushenqi // // Created by yung on 2017/4/28. // Copyright © 2017年 QS. All rights reserved. // import Foundation import UIKit extension UIColor{ func daylightTextColor()->UIColor{ return UIColor.black } func daylightToolBarColor()->UIColor{ return UIColor.black } func nightCellColor()->UIColor{ return UIColor.black } func nightSegColor()->UIColor{ return UIColor.darkGray } func nightNavColor()->UIColor{ return UIColor.darkGray } } ================================================ FILE: zhuishushenqi/Extension/UIFont+Extension.swift ================================================ // // UIFont+Extension.swift // zhuishushenqi // // Created by caony on 2018/9/14. // Copyright © 2018年 QS. All rights reserved. // import Foundation extension UIFont { } ================================================ FILE: zhuishushenqi/Extension/UIFont+ZSExtension.h ================================================ // // UIFont+ZSExtension.h // zhuishushenqi // // Created by caony on 2018/9/14. // Copyright © 2018年 QS. All rights reserved. // #import @interface UIFont (ZSExtension) /** * Create a font array from path while ttc is a colleciton of font. * * @param path Path of the font. * @param size Font size that you want. */ -(NSArray*)customFontArrayWithPath:(NSString*)path size:(CGFloat)size; /** * Create a font from path,TTF,OTF available. * * @param path Path of the font. * @param size Font size that you want. */ -(UIFont*)customFontWithPath:(NSString*)path size:(CGFloat)size; @end ================================================ FILE: zhuishushenqi/Extension/UIFont+ZSExtension.m ================================================ // // UIFont+ZSExtension.m // zhuishushenqi // // Created by caony on 2018/9/14. // Copyright © 2018年 QS. All rights reserved. // #import "UIFont+ZSExtension.h" #import @implementation UIFont (ZSExtension) -(NSArray*)customFontArrayWithPath:(NSString*)path size:(CGFloat)size { CFStringRef fontPath = CFStringCreateWithCString(NULL, [path UTF8String], kCFStringEncodingUTF8); CFURLRef fontUrl = CFURLCreateWithFileSystemPath(NULL, fontPath, kCFURLPOSIXPathStyle, 0); CFArrayRef fontArray = CTFontManagerCreateFontDescriptorsFromURL(fontUrl); CTFontManagerRegisterFontsForURL(fontUrl, kCTFontManagerScopeNone, NULL); NSMutableArray *customFontArray = [NSMutableArray array]; for (CFIndex i = 0 ; i < CFArrayGetCount(fontArray); i++){ CTFontDescriptorRef descriptor = CFArrayGetValueAtIndex(fontArray, i); CTFontRef fontRef = CTFontCreateWithFontDescriptor(descriptor, size, NULL); NSString *fontName = CFBridgingRelease(CTFontCopyName(fontRef, kCTFontPostScriptNameKey)); UIFont *font = [UIFont fontWithName:fontName size:size]; [customFontArray addObject:font]; } return customFontArray; } -(UIFont*)customFontWithPath:(NSString*)path size:(CGFloat)size { NSURL *fontUrl = [NSURL fileURLWithPath:path]; CGDataProviderRef fontDataProvider = CGDataProviderCreateWithURL((__bridge CFURLRef)fontUrl); CGFontRef fontRef = CGFontCreateWithDataProvider(fontDataProvider); CGDataProviderRelease(fontDataProvider); CTFontManagerRegisterGraphicsFont(fontRef, NULL); NSString *fontName = CFBridgingRelease(CGFontCopyPostScriptName(fontRef)); UIFont *font = [UIFont fontWithName:fontName size:size]; CGFontRelease(fontRef); return font; } @end ================================================ FILE: zhuishushenqi/Extension/UIImage+QSData.swift ================================================ // // UIImage+QSData.swift // zhuishushenqi // // Created by Nory Cao on 2017/3/22. // Copyright © 2017年 QS. All rights reserved. // import Foundation import UIKit extension UIImage { /// 从磁盘bundle中获取图片,不缓存,对象释放后内存即释放 /// - Parameters: /// - name: 图片名称,如果不包含后缀,则默认PNG /// - bundle: bundle名称,如果不包含后缀,则默认bundle class func image(for name:String, bundle:String) ->UIImage? { let bundleName = bundle.contains(".") ? bundle:"\(bundle).bundle" let stylePath = Bundle.main.path(forResource: bundleName, ofType: nil) ?? "" let styleBundle = Bundle(path: stylePath) let imageHasSuffix = name.contains(".") let imageNameWithSuffix = imageHasSuffix ? name:"\(name).png" let imagePath = "\(styleBundle?.resourcePath ?? "")/\(imageNameWithSuffix)" if let image = UIImage(contentsOfFile: imagePath) { return image } return nil } } extension UIImage{ static func image(with base64:String) -> UIImage? { let data:Data? = Data(base64Encoded: base64, options: .ignoreUnknownCharacters) if let decodeData = data{ return UIImage(data: decodeData) } return nil } static func image(from url:String)->UIImage?{ var image:UIImage? DispatchQueue.global().async { do{ let url:URL? = URL(string: url) if let imageUrl = url { let data:Data = try Data(contentsOf: imageUrl, options: .alwaysMapped) image = UIImage(data: data) } }catch{ QSLog(error) } } return image } func imageHasAlpha(image:UIImage)->Bool{ let alpha:CGImageAlphaInfo? = image.cgImage?.alphaInfo return (alpha == CGImageAlphaInfo.first || alpha == CGImageAlphaInfo.last || alpha == CGImageAlphaInfo.premultipliedLast || alpha == CGImageAlphaInfo.premultipliedFirst) } func base64() -> String? { var imageData:Data? var mimeType:String? if self.imageHasAlpha(image: self) { imageData = self.pngData() mimeType = "image/png" }else{ imageData = self.jpegData(compressionQuality: 1.0) mimeType = "image/jpeg" } QSLog(mimeType) return imageData?.base64EncodedString(options: .endLineWithCarriageReturn) } func qs_drawRectWithRoundedCorner(radius: CGFloat, _ sizetoFit: CGSize) -> UIImage { let rect = CGRect(origin: CGPoint(x: 0, y: 0), size: sizetoFit) UIGraphicsBeginImageContextWithOptions(rect.size, false, UIScreen.main.scale) UIGraphicsGetCurrentContext()?.addPath(UIBezierPath(roundedRect: rect, byRoundingCorners: UIRectCorner.allCorners, cornerRadii: CGSize(width: radius, height: radius)).cgPath) UIGraphicsGetCurrentContext()?.clip() self.draw(in: rect) UIGraphicsGetCurrentContext()?.drawPath(using: .fillStroke) let output = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); return output! } } ================================================ FILE: zhuishushenqi/Extension/UIImageView+zhuishu.swift ================================================ // // UIImageView+zhuishu.swift // zhuishushenqi // // Created by Nory Cao on 2017/3/16. // Copyright © 2017年 QS. All rights reserved. // import Foundation import UIKit import Kingfisher extension UIImageView{ func qs_setBookCoverWithURLString(urlString:String){ self.image = UIImage(named: "default_book_cover") // DispatchQueue.global().async { let noPercentStr:String = urlString.removingPercentEncoding ?? "" var urlStr = noPercentStr if urlStr.qs_subStr(to: 4) != "http"{ // urlStr = urlStr.qs_subStr(from: 7) urlStr = "\(IMAGE_BASEURL)\(urlStr)-coverl" } if urlStr.contains("http") == false { urlStr = "\(IMAGE_BASEURL)\(noPercentStr)" } let url = URL(string: urlStr) guard let imageURL = url else { QSLog("Invalid URL") return } let resource:QSResource = QSResource(url: imageURL) self.kf.setImage(with: resource, placeholder: UIImage(named: "default_book_cover")) // } } func qs_setAvatarWithURLString(urlString:String){ self.image = UIImage(named: "default_avatar_light") let imageUrlString = "\(IMAGE_BASEURL)\(urlString)" let url:URL? = URL(string: imageUrlString) guard let imageURL = url else { QSLog("Invalid URL") return } let resource:QSResource = QSResource(url: imageURL) self.kf.setImage(with: resource, placeholder: UIImage(named: "default_avatar_light")) } func qs_addCorner(radius: CGFloat) { //1.一种圆角添加方式,效率比直接CornerRadius高 self.image = self.image?.qs_drawRectWithRoundedCorner(radius: radius, self.bounds.size) } func qs_addCornerRadius(cornerRadius:CGFloat){ //2.高效添加圆角的方式 let maskPath = UIBezierPath(roundedRect: self.bounds, byRoundingCorners: .allCorners, cornerRadii: CGSize(width:cornerRadius, height: cornerRadius)) let maskLayer = CAShapeLayer() maskLayer.frame = self.bounds maskLayer.path = maskPath.cgPath self.layer.mask = maskLayer } } public struct ZSWrapper { public let base: Base public init(_ base: Base) { self.base = base } } public protocol ZSCompatible: AnyObject { } public protocol ZSCompatibleValue {} extension ZSCompatible { /// Gets a namespace holder for zs compatible types. public var zs: ZSWrapper { get { return ZSWrapper(self) } set { } } } extension ZSCompatibleValue { /// Gets a namespace holder for zs compatible types. public var zs: ZSWrapper { get { return ZSWrapper(self) } set { } } } extension ZSWrapper where Base: UIImageView { func setImage(imageUrl:String, placeHolder:String? = nil) { let url = URL(string: imageUrl) guard let imageURL = url else { QSLog("Invalid URL") return } let resource:QSResource = QSResource(url: imageURL) } } extension UIImageView { func setImage(imageUrl:String, placeHolder:String? = nil) { let url = URL(string: imageUrl) guard let imageURL = url else { QSLog("Invalid URL") return } let resource:QSResource = QSResource(url: imageURL) self.kf.setImage(with: resource, placeholder: UIImage(named: placeHolder ?? "")) } } extension UIButton{ func qs_setBookCoverWithUrlString(urlString:String){ let noPercentStr:String = urlString.removingPercentEncoding ?? "" self.setImage(UIImage(named: "default_book_cover"), for: .normal) var urlStr = noPercentStr if urlStr.qs_subStr(to: 4) != "http"{ urlStr = urlStr.qs_subStr(from: 7) } if urlStr.contains("http") == false { urlStr = "\(IMAGE_BASEURL)\(urlString)" } let url = URL(string: urlStr) guard let imageURL = url else { QSLog("Invalid URL") return } let resource:QSResource = QSResource(url: imageURL) self.kf.setImage(with:resource, for: .normal, placeholder: UIImage(named:"default_book_cover")) } } ================================================ FILE: zhuishushenqi/Extension/UILabel+zhuishu.swift ================================================ // // UILabel+zhuishu.swift // zhuishushenqi // // Created by Nory Cao on 2017/3/16. // Copyright © 2017年 QS. All rights reserved. // import Foundation import UIKit extension UILabel{ func qs_setCreateTime(createTime:String,append:String){ if createTime.lengthOfBytes(using: String.Encoding.utf8) > 18{ DispatchQueue.global().async { let year = createTime.qs_subStr(to: 4) let month = createTime.qs_subStr(start: 5, end: 7) let day = createTime.qs_subStr(start: 8, length: 2) let hour = createTime.qs_subStr(start: 11, length: 2) let mimute = createTime.qs_subStr(start: 14, length: 2) let second = createTime.qs_subStr(start: 17, length: 2) let beginDate = NSDate.getWithYear(year, month: month, day: day, hour: hour, mimute: mimute, second: second) let endDate = Date() let formatter = DateIntervalFormatter() let out = formatter.timeInfo(from: beginDate!, to: endDate) DispatchQueue.main.async { self.text = "\(out)\(append)" } } } } } ================================================ FILE: zhuishushenqi/Extension/UINavigationItem+BackItem.h ================================================ // // UINavigationItem+BackItem.h // zhuishushenqi // // Created by Nory Cao on 16/10/4. // Copyright © 2016年 QS. All rights reserved. // #import @interface UINavigationItem (BackItem) @end ================================================ FILE: zhuishushenqi/Extension/UINavigationItem+BackItem.m ================================================ // // UINavigationItem+BackItem.m // zhuishushenqi // // Created by Nory Cao on 16/10/4. // Copyright © 2016年 QS. All rights reserved. // #import "UINavigationItem+BackItem.h" #import @implementation UINavigationItem (BackItem) +(void)load{ static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ Method originalMethodImp = class_getInstanceMethod(self, @selector(backBarButtonItem)); Method destMethodImp = class_getInstanceMethod(self, @selector(myCustomBackButton)); method_exchangeImplementations(originalMethodImp, destMethodImp); }); } static char kCustomBackButtonKey; -(UIBarButtonItem *)myCustomBackButton{ UIBarButtonItem *item = [self myCustomBackButton]; if (item) { return item; } item = objc_getAssociatedObject(self, &kCustomBackButtonKey); if (!item) { item = [[UIBarButtonItem alloc] initWithTitle:@"返回" style:UIBarButtonItemStylePlain target:nil action:NULL]; objc_setAssociatedObject(self, &kCustomBackButtonKey, item, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } return item; } @end ================================================ FILE: zhuishushenqi/Extension/UIStoryboardExtension.swift ================================================ // // UIStoryboardExtension.swift // zhuishushenqi // // Created by yung on 2022/1/4. // Copyright © 2022 QS. All rights reserved. // import UIKit extension UIStoryboard { static let main:UIStoryboard = UIStoryboard(name: "Main", bundle: nil) } ================================================ FILE: zhuishushenqi/Extension/UITableView+FINAutomaticHeightCell.swift ================================================ // // UITableView+FINAutomaticHeightCell.swift // V2ex-Swift // // Created by huangfeng on 1/12/16. // Copyright © 2016 Fin. All rights reserved. // import UIKit extension UITableView { public func fin_heightForCellWithIdentifier(_ identifier: String, configuration: ((_ cell: T) -> Void)?) -> CGFloat { if identifier.count <= 0 { return 0 } let cell = self.fin_templateCellForReuseIdentifier(identifier) cell.prepareForReuse() if configuration != nil { configuration!(cell as! T) } // cell.setNeedsUpdateConstraints(); // cell.updateConstraintsIfNeeded(); // self.setNeedsLayout(); // self.layoutIfNeeded(); var fittingSize = cell.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize) if self.separatorStyle != .none { fittingSize.height += 1.0 / UIScreen.main.scale } return fittingSize.height } fileprivate func fin_templateCellForReuseIdentifier(_ identifier: String) -> UITableViewCell { assert(identifier.count > 0, "Expect a valid identifier - \(identifier)") if self.fin_templateCellsByIdentifiers == nil { self.fin_templateCellsByIdentifiers = [:] } var templateCell = self.fin_templateCellsByIdentifiers?[identifier] as? UITableViewCell if templateCell == nil { templateCell = self.dequeueReusableCell(withIdentifier: identifier) assert(templateCell != nil, "Cell must be registered to table view for identifier - \(identifier)") templateCell?.contentView.translatesAutoresizingMaskIntoConstraints = false self.fin_templateCellsByIdentifiers?[identifier] = templateCell } return templateCell! } public func fin_heightForCellWithIdentifier(_ identifier: T.Type, indexPath: IndexPath, configuration: ((_ cell: T) -> Void)?) -> CGFloat { let identifierStr = "\(identifier)"; if identifierStr.count == 0 { return 0 } // Hit cache if self.fin_hasCachedHeightAtIndexPath(indexPath) { let height: CGFloat = self.fin_indexPathHeightCache![indexPath.section][indexPath.row] // NSLog("hit cache by indexPath:[\(indexPath.section),\(indexPath.row)] -> \(height)"); return height } let height = self.fin_heightForCellWithIdentifier(identifierStr, configuration: configuration) self.fin_indexPathHeightCache![indexPath.section][indexPath.row] = height // NSLog("cached by indexPath:[\(indexPath.section),\(indexPath.row)] --> \(height)") return height } fileprivate struct AssociatedKey { static var CellsIdentifier = "me.fin.cellsIdentifier" static var HeightsCacheIdentifier = "me.fin.heightsCacheIdentifier" static var finHeightCacheAbsendValue = CGFloat(-1); } fileprivate func fin_hasCachedHeightAtIndexPath(_ indexPath:IndexPath) -> Bool { self.fin_buildHeightCachesAtIndexPathsIfNeeded([indexPath]); let height = self.fin_indexPathHeightCache![indexPath.section][indexPath.row]; return height >= 0; } fileprivate func fin_buildHeightCachesAtIndexPathsIfNeeded(_ indexPaths:Array) -> Void { if indexPaths.count <= 0 { return ; } if self.fin_indexPathHeightCache == nil { self.fin_indexPathHeightCache = []; } for indexPath in indexPaths { let cacheSectionCount = self.fin_indexPathHeightCache!.count; if indexPath.section >= cacheSectionCount { for i in cacheSectionCount...indexPath.section { if i >= self.fin_indexPathHeightCache!.count{ self.fin_indexPathHeightCache!.append([]) } } } let cacheCount = self.fin_indexPathHeightCache![indexPath.section].count; if indexPath.row >= cacheCount { for i in cacheCount...indexPath.row { if i >= self.fin_indexPathHeightCache![indexPath.section].count { self.fin_indexPathHeightCache![indexPath.section].append(AssociatedKey.finHeightCacheAbsendValue); } } } } } fileprivate var fin_templateCellsByIdentifiers: [String : AnyObject]? { get { return objc_getAssociatedObject(self, &AssociatedKey.CellsIdentifier) as? [String : AnyObject] } set { objc_setAssociatedObject(self, &AssociatedKey.CellsIdentifier, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } } fileprivate var fin_indexPathHeightCache: [ [CGFloat] ]? { get { return objc_getAssociatedObject(self, &AssociatedKey.HeightsCacheIdentifier) as? [[CGFloat]] } set { objc_setAssociatedObject(self, &AssociatedKey.HeightsCacheIdentifier, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } } public func fin_reloadData(){ self.fin_indexPathHeightCache = [[]]; self.reloadData(); } } ================================================ FILE: zhuishushenqi/Extension/UITableView+QSGeneric.swift ================================================ // // UITableView+QSCreate.swift // zhuishushenqi // // Created by Nory Cao on 2017/3/19. // Copyright © 2017年 QS. All rights reserved. // import Foundation import UIKit extension UITableView{ private struct AssociatedKey { static var viewExtension = "viewExtension" } var tableHander: ZSBaseTableViewManger { get { return objc_getAssociatedObject(self, &AssociatedKey.viewExtension) as! ZSBaseTableViewManger } set { newValue.handleTableViewDatasourceAndDelegate(table: self) objc_setAssociatedObject(self, &AssociatedKey.viewExtension, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) } } func qs_registerCellNib(_ aClass:T.Type){ let name = String(describing: aClass) let nib = UINib(nibName: name, bundle: nil) self.register(nib, forCellReuseIdentifier: name) } func qs_registerCellClass(_ aClass:T.Type){ let name = String(describing:aClass) self.register(aClass, forCellReuseIdentifier: name) } func qs_dequeueReusableCell(_ aClass:T.Type)->T!{ let name = String(describing:aClass) guard let cell = dequeueReusableCell(withIdentifier: name) as? T else { fatalError("\(name) is not registered") } return cell } func qs_registerHeaderFooterNib(_ aClass:T.Type){ let name = String(describing: aClass) let nib = UINib(nibName: name, bundle: nil) self.register(nib, forHeaderFooterViewReuseIdentifier: name) } func qs_registerHeaderFooterClass(_ aClass:T.Type){ let name = String(describing:aClass) self.register(aClass, forHeaderFooterViewReuseIdentifier: name) } func qs_dequeueReusableHeaderFooterView(_ aClass:T.Type)->T!{ let name = String(describing:aClass) guard let cell = dequeueReusableHeaderFooterView(withIdentifier: name) as? T else { fatalError("\(name) is not registered") } return cell } } ================================================ FILE: zhuishushenqi/Extension/UITableView+swizzling.swift ================================================ // // UITableView+swizzling.swift // zhuishushenqi // // Created by Nory Cao on 2017/3/17. // Copyright © 2017年 QS. All rights reserved. // import Foundation import UIKit extension UITableView{ func checkEmpty(){ checkEmpty(title: nil, image: nil) } func checkEmpty(title:String?,image:UIImage?){ guard let empDataSource = self.dataSource else { return } var sections = 0 var rows = 0 var isEmpty = true sections = empDataSource.numberOfSections!(in: self) for index in 0..EmptyView{ let emptyView:EmptyView? = UINib(nibName: "EmptyView", bundle: nil).instantiate(withOwner: nil, options: nil)[0] as? EmptyView emptyView?.tittle = title ?? "" emptyView?.image = image emptyView?.reloadAction = { self.reloadData() } return emptyView! } } ================================================ FILE: zhuishushenqi/Extension/UITableViewCell+ZSExtension.swift ================================================ // // UITableViewCell+ZSExtension.swift // zhuishushenqi // // Created by caony on 2018/5/21. // Copyright © 2018年 QS. All rights reserved. // import Foundation public extension UITableViewCell { class internal func nibWithIdentifier(identifier: String) -> UINib{ return UINib(nibName: identifier, bundle: nil) } /** 从nib文件中根据重用标识符注册UITableViewCell - parameter table: table description - parameter identifier: identifier description */ class func registerTable(table: UITableView, nibIdentifier identifier: String) { return table.register(self.nibWithIdentifier(identifier: identifier), forCellReuseIdentifier: identifier) } /** 配置UITableViewCell,设置UITableViewCell内容 - parameter cell: cell description - parameter obj: obj description - parameter indexPath: indexPath description */ func configure(cell: UITableViewCell, customObj obj: AnyObject, indexPath: NSIndexPath) { // Rewrite this func in SubClass ! } /** 获取自定义对象的cell高度 (已集成UITableView+FDTemplateLayoutCell,现在创建的cell自动计算高度) - parameter obj: obj description - parameter indexPath: indexPath description - returns: return value description */ class func getCellHeightWithCustomObj(obj: AnyObject?, indexPath: NSIndexPath) -> CGFloat { // Rewrite this func in SubClass if necessary return obj == nil ? 44.0 : 0.0 // default cell height 44.0 } } ================================================ FILE: zhuishushenqi/Extension/UIView+ScreenShot.swift ================================================ // // UIView+ScreenShot.swift // zhuishushenqi // // Created by Nory Cao on 2017/4/17. // Copyright © 2017年 QS. All rights reserved. // import Foundation import UIKit import PKHUD func RGBAColor(_ red:T, _ green:T, _ blue:T, _ alpha:CGFloat? = 1) -> UIColor { var redString = "\(red)" var greenString = "\(green)" var blueString = "\(blue)" if redString.hasPrefix("0x") || redString.hasPrefix("0X") { redString = redString.qs_subStr(from: 2) } if greenString.hasPrefix("0x") || greenString.hasPrefix("0X") { greenString = greenString.qs_subStr(from: 2) } if blueString.hasPrefix("0x") || blueString.hasPrefix("0X") { blueString = blueString.qs_subStr(from: 2) } // if T.self == CGFloat.self || T.self == Int.self || T.self == Double.self { // let redFloatValue:CGFloat = red as! CGFloat // let greenFloatValue:CGFloat = green as! CGFloat // let blueFloatValue:CGFloat = blue as! CGFloat // return UIColor(red: redFloatValue/255, green: greenFloatValue/255, blue: blueFloatValue/255, alpha: alpha ?? 1) // } return UIColor(hexString: "#\(redString)\(greenString)\(blueString)") ?? UIColor.black } extension UIView { func addCorners(corner:UIRectCorner, cornerRadii:CGSize) { let bezierPath = UIBezierPath(roundedRect: bounds, byRoundingCorners: corner, cornerRadii: cornerRadii) let shaperLayer = CAShapeLayer() shaperLayer.path = bezierPath.cgPath layer.mask = shaperLayer } func screenShot()->UIImage?{ let size = self.bounds.size let transform:CGAffineTransform = __CGAffineTransformMake(-1, 0, 0, 1, size.width, 0) UIGraphicsBeginImageContextWithOptions(size, self.isOpaque, 0.0) let context = UIGraphicsGetCurrentContext() context?.concatenate(transform) self.layer.render(in: context!) let image:UIImage? = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() return image } func nextController()->UIViewController?{ var nextResponder:UIResponder? = self.next while (nextResponder != nil) { if nextResponder?.isKind(of: UIViewController.self) == true { return nextResponder as? UIViewController } nextResponder = nextResponder?.next } return nil } func showTipView(tip:String) { HUD.flash(.label(tip), delay: 3) } func showProgress() { HUD.flash(.labeledProgress(title: "正在请求...", subtitle: nil)) } func hideProgress() { HUD.hide(animated: true) } func showTip(tip:String) { let tag = 10124 if let tipLabel = self.viewWithTag(tag) { tipLabel.removeFromSuperview() } let width = tip.qs_width(UIFont.systemFont(ofSize: 14), height: 30) let label = UILabel(frame: CGRect(x: ScreenWidth/2 - (width + 20)/2, y: self.bounds.height/2 - 15, width: width + 20, height: 30)) label.backgroundColor = UIColor.gray label.textAlignment = .center label.layer.cornerRadius = 15 label.clipsToBounds = true label.font = UIFont.systemFont(ofSize: 14) label.textColor = UIColor.white label.text = tip label.tag = tag self.addSubview(label) DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()+3) { label.removeFromSuperview() label.text = nil } } func addShadow(cornerRadius:CGFloat) { self.layer.shadowColor = UIColor.black.cgColor self.layer.shadowRadius = cornerRadius self.layer.shadowOffset = CGSize(width: 0.5, height: 0.5) self.layer.shadowOpacity = 0.3 } static let shadowLayerAccess = "shadowLayerAccess" static let backgroundViewAccess = "backgroundViewAccess" func addShadow(borderRadius:CGFloat) { if self.bounds.equalTo(CGRect.zero) { return } for layer in self.layer.sublayers ?? [] { if layer.accessibilityLabel == UIView.shadowLayerAccess { layer.removeFromSuperlayer() } } for view in self.subviews { if view.accessibilityLabel == UIView.backgroundViewAccess { view.removeFromSuperview() } } let backgroundView = UIView(frame: self.bounds) backgroundView.backgroundColor = UIColor.clear backgroundView.isUserInteractionEnabled = true addSubview(backgroundView) let layer = CAShapeLayer() layer.frame = self.bounds let path = UIBezierPath(roundedRect: layer.bounds, cornerRadius: borderRadius) layer.shadowPath = path.cgPath layer.shadowColor = RGBAColor(0, 0, 0, 0.5).cgColor layer.shadowRadius = borderRadius layer.masksToBounds = false self.layer.insertSublayer(layer, below: backgroundView.layer) } func removeSubviews() { for subview in self.subviews { subview.removeFromSuperview() } } } ================================================ FILE: zhuishushenqi/Extension/UIViewController+Alert.swift ================================================ // // UIAlertController+Alert.swift // zhuishushenqi // // Created by Nory Cao on 2017/4/18. // Copyright © 2017年 QS. All rights reserved. // import Foundation import UIKit import MBProgressHUD typealias AlertCallback = (_ action:UIAlertAction)->Void extension UIViewController{ func alert(with title:String?,message:String?,okTitle:String?){ self.alert(with: title, message: message, okTitle: okTitle, cancelTitle: nil, okAction: nil, cancelAction: nil) } func alert(with title:String?,message:String?,okTitle:String?,okAction:AlertCallback?){ self.alert(with: title, message: message, okTitle: okTitle, cancelTitle: nil, okAction: okAction, cancelAction: nil) } func alert(with title:String?,message:String?,okTitle:String?,cancelTitle:String?,okAction:AlertCallback?,cancelAction:AlertCallback?){ let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) if let title = okTitle { let ok = UIAlertAction(title: title, style: .default) { (alertAction) in if let action = okAction { action(alertAction) } } alert.addAction(ok) } if let title = cancelTitle { let cancel = UIAlertAction(title: title, style: .default) { (alertAction) in if let action = cancelAction { action(alertAction) } } alert.addAction(cancel) } self.present(alert, animated: true, completion: nil) } func hudAddTo(view:UIView,text:String,animated:Bool){ let hud = MBProgressHUD.showAdded(to: view, animated: animated) hud.mode = .text hud.label.text = text hud.offset.y = ScreenHeight/4 hud.hide(animated: animated, afterDelay: 3.0) } func hudAddedTo(view:UIView,text:String,timeInterVal:TimeInterval,animated:Bool){ let hud = MBProgressHUD.showAdded(to: view, animated: animated) hud.mode = MBProgressHUDMode.indeterminate hud.label.text = text hud.offset.y = ScreenHeight/4 hud.hide(animated:animated, afterDelay:timeInterVal) } func pregressHUDTo(view:UIView,animated:Bool) ->Void { let hud = MBProgressHUD.showAdded(to: view, animated: animated) hud.mode = MBProgressHUDMode.indeterminate } } ================================================ FILE: zhuishushenqi/Extension/UserDefaultsExtension.swift ================================================ // // UserDefaultsExtension.swift // zhuishushenqi // // Created by yung on 2022/1/4. // Copyright © 2022 QS. All rights reserved. // import Foundation extension UserDefaults { static let splashTimeKey = "splashTimeKey" static let firstRunKey = "firstRunKey" } ================================================ FILE: zhuishushenqi/Extension/Value.swift ================================================ // // Value.swift // zhuishushenqi // // Created by caony on 2018/5/21. // Copyright © 2018年 QS. All rights reserved. // import Foundation struct Value { let base: Base } protocol ValueCompatible { var value: Value { get } } extension ValueCompatible { var value: Value { return Value(base: self) } } ================================================ FILE: zhuishushenqi/Info.plist ================================================ NSUserTrackingUsageDescription 需要获取您设备的广告标识符,以为您提供更好的广告体验 GADApplicationIdentifier ca-app-pub-6271484308025079~2264869014 SKAdNetworkItems SKAdNetworkIdentifier cstr6suwn9.skadnetwork SKAdNetworkIdentifier 4fzdc2evr5.skadnetwork SKAdNetworkIdentifier 2fnua5tdw4.skadnetwork SKAdNetworkIdentifier ydx93a7ass.skadnetwork SKAdNetworkIdentifier 5a6flpkh64.skadnetwork SKAdNetworkIdentifier p78axxw29g.skadnetwork SKAdNetworkIdentifier v72qych5uu.skadnetwork SKAdNetworkIdentifier c6k4g5qg8m.skadnetwork SKAdNetworkIdentifier s39g8k73mm.skadnetwork SKAdNetworkIdentifier 3qy4746246.skadnetwork SKAdNetworkIdentifier 3sh42y64q3.skadnetwork SKAdNetworkIdentifier f38h382jlk.skadnetwork SKAdNetworkIdentifier hs6bdukanm.skadnetwork SKAdNetworkIdentifier prcb7njmu6.skadnetwork SKAdNetworkIdentifier v4nxqhlyqp.skadnetwork SKAdNetworkIdentifier wzmmz9fp6w.skadnetwork SKAdNetworkIdentifier yclnxrl5pm.skadnetwork SKAdNetworkIdentifier t38b2kh725.skadnetwork SKAdNetworkIdentifier 7ug5zh24hu.skadnetwork SKAdNetworkIdentifier 9rd848q2bz.skadnetwork SKAdNetworkIdentifier n6fk4nfna4.skadnetwork SKAdNetworkIdentifier kbd757ywx3.skadnetwork SKAdNetworkIdentifier 9t245vhmpl.skadnetwork SKAdNetworkIdentifier 4468km3ulz.skadnetwork SKAdNetworkIdentifier 2u9pt9hc89.skadnetwork SKAdNetworkIdentifier 8s468mfl3y.skadnetwork SKAdNetworkIdentifier av6w8kgt66.skadnetwork SKAdNetworkIdentifier klf5c3l5u5.skadnetwork SKAdNetworkIdentifier ppxm28t8ap.skadnetwork SKAdNetworkIdentifier 424m5254lk.skadnetwork SKAdNetworkIdentifier uw77j35x4d.skadnetwork SKAdNetworkIdentifier 578prtvx9j.skadnetwork SKAdNetworkIdentifier 4dzt52r2t5.skadnetwork SKAdNetworkIdentifier e5fvkxwrpn.skadnetwork SKAdNetworkIdentifier 8c4e2ghe7u.skadnetwork SKAdNetworkIdentifier zq492l623r.skadnetwork SKAdNetworkIdentifier 3qcr597p9d.skadnetwork CFBundleDevelopmentRegion en CFBundleDisplayName 追书神器 CFBundleDocumentTypes CFBundleTypeName TXT File CFBundleTypeRole Viewer LSItemContentTypes public.plain-text CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType APPL CFBundleShortVersionString 4.6.2 CFBundleSignature ???? CFBundleURLTypes CFBundleTypeRole Editor CFBundleURLName isp566551f4 CFBundleURLSchemes isp566551f4 CFBundleTypeRole Editor CFBundleURLName tencent100497199 CFBundleURLSchemes tencent100497199 CFBundleTypeRole Editor CFBundleURLName wechar CFBundleURLSchemes wxaf0fdeed6872dfcf CFBundleTypeRole Editor CFBundleURLName weibo CFBundleURLSchemes wb2023668704 CFBundleVersion 4.6.2.2 LSApplicationQueriesSchemes sinaweibohd sinaweibo weibosdk weibosdk2.5 weixin wechat IFlySpeechPlus mqq mqqapi mqqwpa mqqbrowser mttbrowser mqqOpensdkSSoLogin mqqopensdkapiV2 mqqopensdkapiV3 mqqopensdkapiV4 wtloginmqq2 mqzone mqzoneopensdk mqzoneopensdkapi mqzoneopensdkapi19 mqzoneopensdkapiV2 mqqapiwallet mqqopensdkfriend mqqopensdkdataline mqqgamebindinggroup mqqopensdkgrouptribeshare tencentapi.qq.reqContent tencentapi.qzone.reqContent LSRequiresIPhoneOS NSAppTransportSecurity NSAllowsArbitraryLoads NSMainStoryboardFile Main UIBackgroundModes fetch audio UIMainStoryboardFile Main UIRequiredDeviceCapabilities armv7 UIStatusBarHidden UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortraitUpsideDown UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIViewControllerBasedStatusBarAppearance ================================================ FILE: zhuishushenqi/IntroducePage/QSIntroduceCell.swift ================================================ // // QSIntroduceCell.swift // zhuishushenqi // // Created by yung on 2017/6/11. // Copyright © 2017年 QS. All rights reserved. // import UIKit class QSIntroduceCell: UITableViewCell { @IBOutlet weak var headerImageView: UIImageView! @IBOutlet weak var titleImageView: UIImageView! override func awakeFromNib() { super.awakeFromNib() // Initialization code headerImageView.contentMode = .scaleAspectFit titleImageView.contentMode = .scaleToFill } override func setSelected(_ selected: Bool, animated: Bool) { super.setSelected(selected, animated: animated) // Configure the view for the selected state } } ================================================ FILE: zhuishushenqi/IntroducePage/QSIntroduceCell.xib ================================================ ================================================ FILE: zhuishushenqi/IntroducePage/QSIntroduceReadCell.swift ================================================ // // QSIntroduceReadCell.swift // zhuishushenqi // // Created by yung on 2017/6/12. // Copyright © 2017年 QS. All rights reserved. // import UIKit class QSIntroduceReadCell: UITableViewCell { var startRead:Completion? override func awakeFromNib() { super.awakeFromNib() // Initialization code } @IBAction func startReading(_ sender: Any) { if let start = startRead { start() } } override func setSelected(_ selected: Bool, animated: Bool) { super.setSelected(selected, animated: animated) // Configure the view for the selected state } } ================================================ FILE: zhuishushenqi/IntroducePage/QSIntroduceReadCell.xib ================================================ ================================================ FILE: zhuishushenqi/IntroducePage/QSLastIntroduceCell.swift ================================================ // // QSLastIntroduceCell.swift // zhuishushenqi // // Created by yung on 2017/6/11. // Copyright © 2017年 QS. All rights reserved. // import UIKit typealias Completion = ()->Void class QSLastIntroduceCell: UITableViewCell { var sinaLogin:Completion? var qqLoginAction:Completion? var noAccountCompletion:Completion? @IBAction func weiboLogin(_ sender: Any) { if let sina = sinaLogin { sina() } } @IBAction func qqLogin(_ sender: Any) { if let qq = qqLoginAction { qq() } } var noAccount: UIButton? @IBOutlet weak var qqLoginBtn: UIButton! @IBOutlet weak var sinaLoginBtn: UIButton! @objc func noAccountAction(_ sender: Any) { if let noAcc = noAccountCompletion { noAcc() } } override func awakeFromNib() { super.awakeFromNib() // Initialization code // QSLog(noAccount.frame) // (102.0, 451.0, 118.0, 30.0) noAccount = UIButton(frame: CGRect(x: self.bounds.size.width/2 - 118/2, y: self.qqLoginBtn.frame.maxY + 44, width: 118, height: 30)) let dict = [NSAttributedString.Key.underlineStyle:1,NSAttributedString.Key.font:UIFont.systemFont(ofSize: 15)] as [NSAttributedString.Key : Any] let attr = NSMutableAttributedString(string: "没有帐号怎么办?", attributes: dict) noAccount?.setAttributedTitle(attr, for: .normal) noAccount?.titleLabel?.textAlignment = .center noAccount?.titleLabel?.textColor = UIColor.white noAccount?.addTarget(self, action: #selector(noAccountAction(_:)), for: .touchUpInside) contentView.addSubview(noAccount!) } override func setSelected(_ selected: Bool, animated: Bool) { super.setSelected(selected, animated: animated) // Configure the view for the selected state } override func layoutSubviews() { super.layoutSubviews() noAccount?.frame = CGRect(x: self.bounds.size.height/2 - 118/2, y: self.qqLoginBtn.frame.maxY + 44, width: 118, height: 30) } } class QSLoginButton: UIButton { override func layoutSubviews() { super.layoutSubviews() self.imageView?.frame = CGRect(x: 15, y: 11, width: 20, height: 20) // NSUnderlineStyle.styleSingle } } ================================================ FILE: zhuishushenqi/IntroducePage/QSLastIntroduceCell.xib ================================================ ================================================ FILE: zhuishushenqi/IntroducePage/ZSIntroducePage.swift ================================================ // // ZSIntroducePage.swift // zhuishushenqi // // Created by yung on 2017/6/9. // Copyright © 2017年 QS. All rights reserved. // import UIKit class ZSIntroducePage { static let shared = ZSIntroducePage() var splashWindow:UIWindow? var completion:Completion? private var introduceRootVC:QSIntroduceViewController? func show(completion:Completion?){ self.completion = completion detachRootViewController() splashWindow = UIWindow(frame: UIScreen.main.bounds) splashWindow?.windowLevel = .statusBar splashWindow?.frame = UIScreen.main.bounds splashWindow?.backgroundColor = UIColor.white introduceRootVC = QSIntroduceViewController() introduceRootVC?.completion = { self.hide() } splashWindow?.rootViewController = introduceRootVC splashWindow?.isHidden = false } func hide(){ attachRootViewController() splashWindow?.isHidden = true if let complete = completion { complete() } } } extension ZSIntroducePage { func detachRootViewController(){ if originalRootViewController == nil { let keyWindow = UIApplication.shared.windows.first(where: { $0.isKeyWindow }) originalRootViewController = keyWindow?.rootViewController } } func attachRootViewController(){ if originalRootViewController != nil { let keyWindow = UIApplication.shared.windows.first(where: { $0.isKeyWindow }) keyWindow?.rootViewController = originalRootViewController } } } typealias QSScrollViewDidScroll = (_ contentOffset:CGPoint)->Void class QSIntroduceViewController: UIViewController { var completion:Completion? var pageControl:UIPageControl! override func viewDidLoad() { super.viewDidLoad() let horizonalVC = QSHorizonalTableViewController(style: .grouped) horizonalVC.completion = completion horizonalVC.scrollViewDidScroll = { (contentOffset) in self.qs_scrollViewDidScroll(contentOffset: contentOffset) } horizonalVC.view.frame = CGRect(x: 0, y: 0, width: self.view.bounds.width, height: self.view.bounds.height) self.addChild(horizonalVC) self.view.addSubview(horizonalVC.view) pageControl = UIPageControl(frame: CGRect(x: 0, y: self.view.bounds.height - 60, width: self.view.bounds.width, height: 30)) pageControl.currentPage = 0 pageControl.numberOfPages = 4 pageControl.pageIndicatorTintColor = UIColor.white pageControl.currentPageIndicatorTintColor = UIColor.darkGray view.addSubview(pageControl) } func qs_scrollViewDidScroll(contentOffset:CGPoint){ var page:Int = Int(contentOffset.y/self.view.bounds.width) let residual = contentOffset.y.truncatingRemainder(dividingBy: self.view.bounds.width) if residual > 0 { page += 1 } pageControl.isHidden = false if page > 3 { pageControl.isHidden = true } pageControl.currentPage = page } } class QSHorizonalTableViewController: UITableViewController { var completion:Completion? var totalPages:Int = 5 var scrollViewDidScroll:QSScrollViewDidScroll? let backgroundColor:[UIColor] = [UIColor(red: 0.67, green: 0.83, blue: 0.24, alpha: 1.0), UIColor(red: 0.31, green: 0.75, blue: 1.0, alpha: 1.0), UIColor(red: 1.0, green: 0.85, blue: 0.16, alpha: 1.0), UIColor(red: 0.89, green: 0.20, blue: 0.13, alpha: 1.0), UIColor(red: 0.89, green: 0.20, blue: 0.13, alpha: 1.0)] private let imageNames = ["p_anywhere","p_synchronize","p_fast","",""] private let titleNames = ["p_anywhere_title","p_synchronize_title","p_fast_title","",""] private var scrollEnable:Bool = false override init(style: UITableView.Style) { super.init(style: style) } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) } override func viewDidLoad() { super.viewDidLoad() self.tableView = UITableView() self.tableView.transform = CGAffineTransform(rotationAngle: -CGFloat.pi/2) self.tableView.isPagingEnabled = true self.tableView.bounces = false self.tableView.scrollsToTop = false self.tableView.showsHorizontalScrollIndicator = false self.tableView.showsVerticalScrollIndicator = false self.tableView.separatorStyle = .none self.tableView.qs_registerCellNib(QSIntroduceCell.self) self.tableView.qs_registerCellNib(QSLastIntroduceCell.self) self.tableView.qs_registerCellNib(QSIntroduceReadCell.self) self.automaticallyAdjustsScrollViewInsets = false if #available(iOS 11,*) { self.tableView.contentInsetAdjustmentBehavior = .never } } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return totalPages } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { if indexPath.row == 3 { let cell:QSLastIntroduceCell? = tableView.qs_dequeueReusableCell(QSLastIntroduceCell.self) cell?.contentView.transform = CGAffineTransform(rotationAngle: CGFloat.pi/2) cell?.selectionStyle = .none cell?.contentView.backgroundColor = backgroundColor[indexPath.row] cell?.sinaLogin = { } cell?.qqLoginAction = { } cell?.noAccountCompletion = { self.scrollEnable = true self.tableView.setContentOffset(CGPoint(x: self.tableView.contentOffset.x, y: self.view.bounds.height*CGFloat(self.totalPages-1)), animated: true) self.tableView.isScrollEnabled = false } return cell! }else if indexPath.row == 4{ let cell:QSIntroduceReadCell? = tableView.qs_dequeueReusableCell(QSIntroduceReadCell.self) cell?.contentView.transform = CGAffineTransform(rotationAngle: CGFloat.pi/2) cell?.selectionStyle = .none cell?.contentView.backgroundColor = backgroundColor[indexPath.row] cell?.startRead = { if let complete = self.completion { complete() } } return cell! } let cell:QSIntroduceCell? = tableView.qs_dequeueReusableCell(QSIntroduceCell.self) cell?.contentView.transform = CGAffineTransform(rotationAngle: CGFloat.pi/2) cell?.contentView.backgroundColor = backgroundColor[indexPath.row] cell?.selectionStyle = .none cell?.headerImageView.image = UIImage(named: imageNames[indexPath.row]) cell?.titleImageView.image = UIImage(named: titleNames[indexPath.row]) return cell! } override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { return self.tableView.frame.width } override func scrollViewDidScroll(_ scrollView: UIScrollView) { QSLog("scrollViewDidScroll:\(scrollView.contentOffset)") let offsetY = scrollView.contentOffset.y if let scroll = scrollViewDidScroll { scroll(scrollView.contentOffset) } if !scrollEnable { if offsetY >= self.view.bounds.height*CGFloat(totalPages-2) { scrollView.setContentOffset( CGPoint(x: scrollView.contentOffset.x, y: self.view.bounds.height*CGFloat(totalPages-2)), animated: false) } } } override func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { let offsetY = scrollView.contentOffset.y if !scrollEnable { if offsetY >= self.view.bounds.height*CGFloat(totalPages-2) { scrollView.setContentOffset( CGPoint(x: scrollView.contentOffset.x, y: self.view.bounds.height*CGFloat(totalPages-2)), animated: false) } } } } ================================================ FILE: zhuishushenqi/NewVersion/BookShelf/NavigationBar.swift ================================================ // // HomeNavView.swift // ZSBookShelf // // Created by caony on 2019/6/20. // import UIKit protocol NavigationBarDelegate:class { func navView(navView:NavigationBar, didSelect at:Int) } class NavigationBar: UIView { private lazy var logoView:UIImageView = { let imageView = UIImageView(frame: CGRect.zero) imageView.image = UIImage(named: "bookshelf_bg_logo_80_19_80x19_") return imageView }() private var navButtons:[UIButton] = [] private var navImages:[ShelfNav] = [] weak var delegate:NavigationBarDelegate? convenience init(navImages:[ShelfNav], delegate:NavigationBarDelegate?) { self.init(frame: CGRect.zero) self.navImages = navImages self.delegate = delegate configureNavButtons() } override init(frame: CGRect) { super.init(frame: frame) addSubview(logoView) self.backgroundColor = UIColor.init(hexString: "#A70A0B") configureNavButtons() } private func configureNavButtons() { for image in navImages { let btn = UIButton(type: .custom) btn.setImage(image.image, for: .normal) btn.addTarget(self, action: #selector(navAction(btn:)), for: .touchUpInside) addSubview(btn) navButtons.append(btn) } } @objc private func navAction(btn:UIButton) { var index = 0 for button in navButtons { if button == btn { break } index += 1 } delegate?.navView(navView: self, didSelect: index) } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } public override func layoutSubviews() { super.layoutSubviews() logoView.frame = CGRect(x: 20, y: kNavgationBarHeight - 44 + 12.5, width: 80, height: 19) var index = navButtons.count - 1 while index >= 0 { let btn = navButtons[index] let originX = self.bounds.width - CGFloat(navButtons.count - index) * 42 - 13 btn.frame = CGRect(x: originX, y: kNavgationBarHeight - 44, width: 42, height: 42) index -= 1 } } } ================================================ FILE: zhuishushenqi/NewVersion/BookShelf/ZSBookLocalShelfViewController.swift ================================================ // // ZSBookLocalShelfViewController.swift // zhuishushenqi // // Created by yung on 2020/1/25. // Copyright © 2020 QS. All rights reserved. // import UIKit class ZSBookLocalShelfViewController: BaseViewController,UITableViewDataSource,UITableViewDelegate { lazy var tableView:UITableView = { let tableView = UITableView(frame: CGRect.zero, style: .grouped) tableView.qs_registerCellClass(ZSShelfTableViewCell.self) tableView.rowHeight = ZSBookLocalShelfViewController.kCellHeight tableView.estimatedRowHeight = ZSBookLocalShelfViewController.kCellHeight tableView.dataSource = self tableView.delegate = self return tableView }() static let kCellHeight:CGFloat = 60 override func viewDidLoad() { super.viewDidLoad() view.addSubview(tableView) tableView.snp.makeConstraints { (make) in make.left.right.bottom.equalToSuperview() make.top.equalToSuperview().offset(kNavgationBarHeight) } setupNavItem() let path = "\(NSHomeDirectory())/Documents/Inbox/" ZSShelfManager.share.scanPath(path: path) title = "本地书架" NotificationCenter.default.addObserver(self, selector: #selector(localChangeNoti(noti:)), name: NSNotification.Name.LocalShelfChanged, object: nil) } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) navigationController?.isNavigationBarHidden = false tableView.reloadData() } private func setupNavItem() { let addItem = UIBarButtonItem(title: "导入本地书籍", style: UIBarButtonItem.Style.done, target: self, action: #selector(addAction)) navigationItem.rightBarButtonItem = addItem } @objc private func addAction() { let importVC = ZSImportBookViewController() navigationController?.pushViewController(importVC, animated: true) } //MARK: - local handler @objc private func localChangeNoti(noti:Notification) { tableView.reloadData() } //MARK: - UITableViewDataSource func numberOfSections(in tableView: UITableView) -> Int { return 1 } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return ZSShelfManager.share.localBooks.count } func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { return 10 } func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { return 0.01 } func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat { return ZSLocalShelfViewController.kCellHeight } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.qs_dequeueReusableCell(ZSShelfTableViewCell.self) cell?.configure(model: ZSShelfManager.share.localBooks[indexPath.row]) return cell! } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) showActivityView() let shelf = ZSShelfManager.share.localBooks[indexPath.row] ZSShelfManager.share.getAikanModel(shelf) { [weak self] (aikan) in if let book = aikan { self?.jumpReader(book: book, indexPath: indexPath) } else if let book = QSReaderParse.parse(shelf: shelf) { ZSShelfManager.share.addAikan(book) self?.jumpReader(book: book, indexPath: indexPath) } } hideActivityView() } private func jumpReader(book:ZSAikanParserModel, indexPath:IndexPath) { let readerVC = ZSReaderController(chapter: nil, book, bookType: book.bookType) readerVC.hidesBottomBarWhenPushed = true navigationController?.pushViewController(readerVC, animated: true) move(from: indexPath, to: IndexPath(row: 0, section: 0)) } //MARK: - changeIndex private func move(from:IndexPath, to:IndexPath) { ZSShelfManager.share.change(from: from.row, to: to.row) tableView.reloadData() } } ================================================ FILE: zhuishushenqi/NewVersion/BookShelf/ZSBookShelfHeaderView.swift ================================================ // // ZSBookShelfHeaderView.swift // zhuishushenqi // // Created by yung on 2019/6/29. // Copyright © 2019 QS. All rights reserved. // import UIKit import SnapKit enum ZSBookShelfHeaderType { case none case tip case message case double } protocol ZSBookShelfHeaderDelegate:class { func headerView(headerView:ZSBookShelfHeaderView, didClickLoginButton:UIButton) func headerView(headerView:ZSBookShelfHeaderView, didClickMsgButton:UIButton) } class ZSBookShelfHeaderView: UITableViewHeaderFooterView { private lazy var imageView:UIImageView = { let imageView = UIImageView(frame: .zero) imageView.isUserInteractionEnabled = true imageView.image = UIImage(named: "bookshelf_top_bg_one_line") return imageView }() private lazy var tipButton:UIButton = { let button = UIButton(type: .custom) button.setTitleColor(UIColor.gray, for: .normal) button.titleLabel?.font = UIFont.systemFont(ofSize: 13) button.titleLabel?.textAlignment = .left button.setTitle("小说免费看? 海量福利", for: .normal) button.titleEdgeInsets = UIEdgeInsets(top: 0, left: 20, bottom: 0, right: 0) button.contentHorizontalAlignment = .left button.backgroundColor = UIColor.clear button.addTarget(self, action: #selector(loginAction), for: .touchUpInside) return button }() private lazy var msgButton:UIButton = { let button = UIButton(type: .custom) button.setTitleColor(UIColor.gray, for: .normal) button.titleLabel?.font = UIFont.systemFont(ofSize: 13) button.titleLabel?.textAlignment = .left button.contentHorizontalAlignment = .left button.titleEdgeInsets = UIEdgeInsets(top: 0, left: 20, bottom: 0, right: 0) button.backgroundColor = UIColor.clear button.addTarget(self, action: #selector(msgAction), for: .touchUpInside) return button }() private lazy var loginButton:UIButton = { let button = UIButton(type: .custom) button.setTitleColor(UIColor.gray, for: .normal) button.titleLabel?.font = UIFont.systemFont(ofSize: 13) button.setTitle("立即登录", for: .normal) button.setTitleColor(UIColor.white, for: .normal) button.backgroundColor = UIColor.init(hexString: "#EE4745") button.layer.cornerRadius = 5 button.layer.masksToBounds = true button.addTarget(self, action: #selector(loginAction), for: .touchUpInside) return button }() private lazy var arrowButton:UIButton = { let button = UIButton(type: .custom) button.setTitleColor(UIColor.gray, for: .normal) button.titleLabel?.font = UIFont.systemFont(ofSize: 13) button.setImage(UIImage(named: "bookshelf_icon_more_10_10_10x10_"), for: .normal) button.addTarget(self, action: #selector(msgAction), for: .touchUpInside) return button }() var type:ZSBookShelfHeaderType = .double { didSet { change(type: type) } } weak var delegate:ZSBookShelfHeaderDelegate? override init(reuseIdentifier: String?) { super.init(reuseIdentifier: reuseIdentifier) contentView.backgroundColor = UIColor.white layer.masksToBounds = true addSubview(imageView) imageView.addSubview(tipButton) imageView.addSubview(msgButton) tipButton.addSubview(loginButton) msgButton.addSubview(arrowButton) } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } func bind(msg:ZSShelfMessage?) { let title = msg?.postMessage() if let _ = title?.1 { self.type = ZSLogin.share.hasLogin() ? .message:.double } else { self.type = ZSLogin.share.hasLogin() ? .none:.tip } msgButton.setTitle(title?.1, for: .normal) msgButton.setTitleColor(title?.3, for: .normal) } private func change(type:ZSBookShelfHeaderType) { switch type { case .tip: imageView.image = UIImage(named: "bookshelf_top_bg_one_line") break case .message: imageView.image = UIImage(named: "bookshelf_top_bg_one_line") break case .double: imageView.image = UIImage(named: "bookshelf_top_bg_two_line") break default: break } setNeedsLayout() layoutIfNeeded() } @objc private func loginAction() { delegate?.headerView(headerView: self, didClickLoginButton: self.loginButton) } @objc private func msgAction() { delegate?.headerView(headerView: self, didClickMsgButton: self.msgButton) } override func layoutSubviews() { super.layoutSubviews() switch type { case .tip: imageView.frame = CGRect(x: 2, y: 0, width: bounds.width - 4, height: bounds.height) tipButton.frame = CGRect(x: 18, y: 20, width: bounds.width - 40, height: bounds.height - 40) break case .message: imageView.frame = CGRect(x: 2, y: 0, width: bounds.width - 4, height: bounds.height) msgButton.frame = CGRect(x: 18, y: 20, width: bounds.width - 40, height: bounds.height - 40) break case .double: imageView.frame = CGRect(x: 2, y: 0, width: bounds.width - 4, height: bounds.height) msgButton.frame = CGRect(x: 18, y: 20, width: bounds.width - 40, height: imageView.bounds.height/2 - 20) tipButton.frame = CGRect(x: 18, y: 0 + imageView.bounds.height/2, width: bounds.width - 40, height: imageView.bounds.height/2 - 20) break default: break } loginButton.frame = CGRect(x: tipButton.width - 80 - 10, y: tipButton.bounds.height/2 - 15, width: 80, height: 30) arrowButton.frame = CGRect(x: msgButton.width - 20 - 10, y: msgButton.bounds.height/2 - 10, width: 20, height: 20) msgButton.isHidden = (type != .message && type != .double) arrowButton.isHidden = (type != .message && type != .double) loginButton.isHidden = ZSLogin.share.hasLogin() tipButton.isHidden = ZSLogin.share.hasLogin() } } ================================================ FILE: zhuishushenqi/NewVersion/BookShelf/ZSBookShelfViewController.swift ================================================ // // ZSBookShelfViewController.swift // Alamofire // // Created by caony on 2019/6/17. // import UIKit import SnapKit import SafariServices enum ShelfNav:Int { case gift = 0 case search case history case more // community case mine case notification var image:UIImage? { switch self { case .gift: return UIImage(named: "bookshelf_icon_gift_34_34") case .search: return UIImage(named: "bookshelf_icon_seach_34_34") case .history: return UIImage(named: "bookshelf_icon_history_34_34") case .more: return UIImage(named: "bookshelf_icon_more_34_34") case .mine: return UIImage(named: "bbs_icon_personal_34_34_34x34_") case .notification: return UIImage(named: "bbs_icon_message_34_34_34x34_") } } var needLogin:Bool { switch self { case .gift: return true case .search: return false case .history: return false case .more: return false case .mine: return true case .notification: return true } } } class ZSBookShelfViewController: BaseViewController, NavigationBarDelegate, ZSBookShelfHeaderDelegate,ZSShelfTableViewCellDelegate,ZSShelfOperatingViewDelegate { lazy var navImages:[ShelfNav] = { var images:[ShelfNav] = [] let gifNav = ShelfNav(rawValue: 0) let searchNav = ShelfNav(rawValue: 1) let historyNav = ShelfNav(rawValue: 2) let moreNav = ShelfNav(rawValue: 3) if let _ = gifNav?.image { images.append(gifNav!) } if let _ = searchNav?.image { images.append(searchNav!) } if let _ = historyNav?.image { images.append(historyNav!) } if let _ = moreNav?.image { images.append(moreNav!) } return images }() lazy var navView:NavigationBar = { let navView = NavigationBar(navImages: self.navImages, delegate: self) return navView }() lazy var tableView:UITableView = { let tableView = UITableView(frame: .zero, style: .grouped) tableView.dataSource = self tableView.delegate = self tableView.sectionHeaderHeight = 0.01 tableView.sectionFooterHeight = 0.01 if #available(iOS 11, *) { tableView.contentInsetAdjustmentBehavior = .never } tableView.register(ZSShelfTableViewCell.self, forCellReuseIdentifier: "\(ZSShelfTableViewCell.self)") tableView.qs_registerHeaderFooterClass(ZSBookShelfHeaderView.self) let blurEffect = UIBlurEffect(style: .extraLight) let blurEffectView = UIVisualEffectView(effect: blurEffect) tableView.backgroundView = blurEffectView return tableView }() var shelfViewModel:ZSBookShelfViewModel? override func viewDidLoad() { super.viewDidLoad() observe() setupSubviews() } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) navigationController?.setNavigationBarHidden(true, animated: false) automaticallyAdjustsScrollViewInsets = false self.tableView.reloadData() ZSShelfManager.share.refresh() } override var preferredStatusBarStyle: UIStatusBarStyle { return UIStatusBarStyle.lightContent } override var prefersStatusBarHidden: Bool { return false } @objc func refreshAction() { shelfViewModel?.requestMsg() } func stopRefresh() { tableView.mj_header.endRefreshing() } func setupSubviews() { view.addSubview(navView) view.addSubview(tableView) navView.snp.makeConstraints { (make) in make.left.top.right.equalToSuperview() make.height.equalTo(kNavgationBarHeight) } tableView.snp.makeConstraints { (make) in make.left.right.equalToSuperview() make.top.equalTo(self.navView.snp.bottom) make.bottom.equalToSuperview().offset(-kTabbarBlankHeight - 49) } let mj_header = ZSRefreshTextHeader(refreshingTarget: self, refreshingAction: #selector(refreshAction)) mj_header?.endRefreshingCompletionBlock = { [weak mj_header] in mj_header?.changeText() } tableView.mj_header = mj_header mj_header?.beginRefreshing() } func observe() { shelfViewModel = ZSBookShelfViewModel() shelfViewModel?.reloadBlock = { [weak self] row in DispatchQueue.main.async { self?.stopRefresh() if row >= 0 { let indexPath = IndexPath(row: row, section: 0) self?.tableView.reloadRows(at: [indexPath], with: .none) } else { self?.tableView.reloadData() } } } NotificationCenter.default.addObserver(self, selector: #selector(localChangeNoti(noti:)), name: NSNotification.Name.ShelfChanged, object: nil) } func jumpCheck(type:ShelfNav, next:@escaping ()->Void) { if type.needLogin { if !ZSLogin.share.hasLogin() { login { (success) in success ? next():nil } } else { next() } } else { next() } } func jump(type:ShelfNav) { // https://h5.zhuishushenqi.com/v2/taskCenter.html?platform=ios&special=51&specialTasks=video&token=ob8lW49OQcetLaIyca394642ed0c48d0f87002693d0015cafeffd2ba2ea7e151bde405328e202a428ac3920683328908d400567735a62aab55750a2f65c58035e0e43ed270454a06fb54753b21b8434494c192e3d330af0c×tamp=1561889416.124459&gender=female&version=14&packageName=com.ifmoc.ZhuiShuShenQi switch type { case .gift: let timeInterval = Date().timeIntervalSince1970 let webVC = ZSWebViewController() webVC.url = "https://h5.zhuishushenqi.com/v2/taskCenter.html?platform=ios&special=51&specialTasks=video&token=\(ZSLogin.share.token)×tamp=\(timeInterval)&gender=female&version=\(14)&packageName=com.ifmoc.ZhuiShuShenQi" webVC.hidesBottomBarWhenPushed = true navigationController?.pushViewController(webVC, animated: true) break case .search: let searchVC = ZSSearchBookViewController() searchVC.hidesBottomBarWhenPushed = true navigationController?.pushViewController(searchVC, animated: true) break case .history: alert(with: "提示", message: "该功能待上线,请更新版本后再次尝试!", okTitle: "确定") break case .more: let localVC = ZSBookLocalShelfViewController() localVC.hidesBottomBarWhenPushed = true navigationController?.pushViewController(localVC, animated: true) break default: break } } func messageHandle() { // 存在三种可能,post,link,booklist if let message = shelfViewModel?.shelfMsg { let title = message.postMessage() let type = title.2 if type == .link { if let url = URL(string: title.0) { let safariVC = SFSafariViewController(url: url) self.present(safariVC, animated: true, completion: nil) } } else if type == .post { let id = title.0 let comment = BookComment() comment._id = id let commentVC = ZSBookCommentViewController(style: .grouped) commentVC.hidesBottomBarWhenPushed = true commentVC.viewModel.model = comment navigationController?.pushViewController(commentVC, animated: true) } else if type == .booklist { let topicVC = QSTopicDetailRouter.createModule(id: title.0) topicVC.hidesBottomBarWhenPushed = true navigationController?.pushViewController(topicVC, animated: true) } } } //MARK: - local handler @objc private func localChangeNoti(noti:Notification) { tableView.reloadData() } //MARK: - changeIndex private func move(from:IndexPath, to:IndexPath) { ZSShelfManager.share.change(from: from.row, to: to.row) tableView.reloadData() } //MARK: - NavigationBarDelegate func navView(navView: NavigationBar, didSelect at: Int) { if let nav = ShelfNav(rawValue: at) { jumpCheck(type: nav) { [weak self] in self?.jump(type: nav) } } } //MARK: - ZSBookShelfHeaderDelegate func headerView(headerView: ZSBookShelfHeaderView, didClickLoginButton: UIButton) { let loginVC = ZSLoginViewController() present(loginVC, animated: true, completion: nil) } func headerView(headerView: ZSBookShelfHeaderView, didClickMsgButton: UIButton) { messageHandle() } //MARK: - ZSShelfTableViewCellDelegate func shelfCell(cell: ZSShelfTableViewCell, clickMore: UIButton) { guard let indexPath = tableView.indexPath(for: cell) else { return } let bookPath = ZSShelfManager.share.books[indexPath.row] guard let book = ZSShelfManager.share.getShelfModel(bookPath: bookPath) else { return } ZSShelfManager.share.getAikanModel(book) { [weak self] (result) in guard let aikan = result else { Toast.show(tip: "找不到该书籍", ToastType.failure, 2) if book.bookType == .local { return } ZSShelfManager.share.remove(book) self?.tableView.reloadData() return } let opView = ZSShelfOperatingView(frame: UIScreen.main.bounds) opView.delegate = self opView.configure(book: aikan) opView.indexPath = indexPath opView.isLocalBook = aikan.bookType == .local opView.bookUrl = aikan.bookUrl opView.show(inView: self?.view.window ?? self!.view) } } //MARK: - ZSShelfOperatingViewDelegate func opView(opView: ZSShelfOperatingView, clickTop: UIButton) { guard let book = opView.book else { return } if book.bookType == .local { return } ZSBookCache.share.remove(book.bookName) ZSShelfManager.share.removeHistory(bookUrl: book.bookUrl) Toast.show(tip: "删除成功", .success, 1) } func opView(opView: ZSShelfOperatingView, clickDetail: UIButton) { let book = opView.book if book?.bookType == .local { return } let infoVC = ZSSearchInfoViewController() infoVC.model = book infoVC.hidesBottomBarWhenPushed = true self.navigationController?.pushViewController(infoVC, animated: true) } func opView(opView: ZSShelfOperatingView, clickDelete: UIButton) { if let book = opView.book { if ZSShelfManager.share.remove(book.bookUrl) { ZSShelfManager.share.remove(book.bookUrl) ZSShelfManager.share.removeAikan(bookUrl: book.bookUrl) ZSBookCache.share.remove(book.bookName) tableView.reloadData() Toast.show(tip: "删除成功", .success, 1) } } else if opView.isLocalBook { ZSShelfManager.share.removeLocalBook(bookUrl: opView.bookUrl) tableView.reloadData() } } func opView(opView: ZSShelfOperatingView, clickSource: UIButton) { let book = opView.book if book?.bookType == .local { return } if let source = ZSSourceManager.share.source(book?.host ?? "") { let addVC = ZSAddSourceViewController() addVC.source = source addVC.hidesBottomBarWhenPushed = true self.navigationController?.pushViewController(addVC, animated: true) } } func opView(opView: ZSShelfOperatingView, clickDownload: UIButton) { guard let book = opView.book else { return } guard let indexPath = opView.indexPath else { return } if book.bookType == .local { return } ZSReaderDownloader.share.download(book: book, start: 0) { [weak self] (index) in let cell = self?.tableView.cellForRow(at: indexPath) as? ZSShelfTableViewCell cell?.progress(value: CGFloat(index ?? 0), max: CGFloat(book.chaptersModel.count)) if index == book.chaptersModel.count - 1 { cell?.finish() Toast.show(tip: "下载完成", .success, 1) } } } } extension ZSBookShelfViewController: UITableViewDataSource, UITableViewDelegate { public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return ZSShelfManager.share.books.count } func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { if let _ = shelfViewModel?.shelfMsg?.postMessage().1 { return ZSLogin.share.hasLogin() ? 90:134 } return ZSLogin.share.hasLogin() ? 0.01:90 } func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { return 0.01 } func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { return 100 } func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { let headerView = tableView.qs_dequeueReusableHeaderFooterView(ZSBookShelfHeaderView.self) headerView?.delegate = self headerView?.bind(msg: shelfViewModel?.shelfMsg) return headerView } public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.qs_dequeueReusableCell(ZSShelfTableViewCell.self) cell?.selectionStyle = .none let book = ZSShelfManager.share.books[indexPath.row] if let model = ZSShelfManager.share.getShelfModel(bookPath: book) { cell?.configure(model: model) } cell?.delegate = self return cell! } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { let bookPath = ZSShelfManager.share.books[indexPath.row] guard let book = ZSShelfManager.share.getShelfModel(bookPath: bookPath) else { return } if book.bookType == .local { Toast.showProgress(tip: "加载中", onView: view.window!) ZSShelfManager.share.getAikanModel(book) { [weak self] (result) in if let aikan = result, aikan.chaptersModel.count != 1 { self?.jumpReader(book: aikan, indexPath: indexPath) } else if let shelf = QSReaderParse.parse(shelf: book) { ZSShelfManager.share.addAikan(shelf) self?.jumpReader(book: shelf, indexPath: indexPath) } Toast.hiden() } } else { ZSShelfManager.share.getAikanModel(book) { [weak self] (result) in guard let aikan = result else { return } if aikan.chaptersModel.count > 0 { let readerVC = ZSReaderController(chapter: nil, aikan) readerVC.hidesBottomBarWhenPushed = true self?.navigationController?.pushViewController(readerVC, animated: true) self?.move(from: indexPath, to: IndexPath(row: 0, section: 0)) aikan.update = false ZSShelfManager.share.modifyAikan(aikan) self?.tableView.reloadData() } else { self?.alert(with: "提示", message: "找不到该书籍", okTitle: "确定") } } } } private func jumpReader(book:ZSAikanParserModel, indexPath:IndexPath) { let readerVC = ZSReaderController(chapter: nil, book, bookType: book.bookType) readerVC.hidesBottomBarWhenPushed = true navigationController?.pushViewController(readerVC, animated: true) move(from: indexPath, to: IndexPath(row: 0, section: 0)) } } ================================================ FILE: zhuishushenqi/NewVersion/BookShelf/ZSBookShelfViewModel.swift ================================================ // // ZSBookShelfViewModel.swift // zhuishushenqi // // Created by yung on 2019/6/30. // Copyright © 2019 QS. All rights reserved. // import UIKit class ZSBookShelfViewModel { var viewDidLoad: ()->() = {} var reloadBlock: (_ row:Int)->() = { row in } var shelfMsg:ZSShelfMessage? var searchViewModel:ZSSearchBookViewModel = ZSSearchBookViewModel() fileprivate let shelvesWebService = ZSShelfWebService() init() { viewDidLoad = { [weak self] in self?.requestMsg(completion: { index in self?.reloadBlock(index) }) } } func requestMsg() { requestMsg { [weak self] index in self?.reloadBlock(index) } } func requestMsg(completion: @escaping(_ index:Int)->Void) { shelvesWebService.fetchShelfMsg { [weak self] (message) in self?.shelfMsg = message self?.update(completion: completion) } } func update(completion:@escaping(_ index:Int) ->Void) { func callback(index:Int) { DispatchQueue.main.async { completion(index) } } let books = ZSShelfManager.share.books if books.count == 0 { completion(-1) } for bookPath in books { guard let book = ZSShelfManager.share.getShelfModel(bookPath: bookPath) else { callback(index: -1) return } ZSShelfManager.share.getAikanModel(book) { [weak self] (result) in guard let src = result else { callback(index: -1) return } self?.searchViewModel.getBookInfo(src: src, bookUrl: book.bookUrl) { (chapters, info) in let lastChapter = chapters.last if chapters.count != src.chaptersModel.count { src.chaptersModel = chapters src.update = true } src.latestChapterName = lastChapter?.chapterName ?? "" DispatchQueue.global().sync { ZSShelfManager.share.modifyAikan(src) } let index = books.firstIndex(of: bookPath) ?? -1 if index == books.count - 1 { callback(index: -1) } } } } } } ================================================ FILE: zhuishushenqi/NewVersion/BookShelf/ZSRefreshTextHeader.swift ================================================ // // ZSRefreshTextHeader.swift // ZSBaseUI // // Created by caony on 2019/6/20. // import UIKit import MJRefresh import YYImage open class ZSRefreshTextHeader: MJRefreshHeader { open var tipBackgroundView:UIImageView! open var tipLabel:UILabel! open var gifView:YYAnimatedImageView! static var itemKey = 1 override init(frame: CGRect) { super.init(frame: frame) } required public init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } open func setText(text:String) { self.tipLabel.text = text } func changeText() { let headTitlePath = Bundle.main.path(forResource: "mjRefreshHeadTitle", ofType: "plist") ?? "" let headTitleDict = NSDictionary(contentsOfFile: headTitlePath) if let text = headTitleDict?["item_\(ZSRefreshTextHeader.itemKey)"] as? String { setText(text: text) } else { ZSRefreshTextHeader.itemKey = 0 } ZSRefreshTextHeader.itemKey += 1 } override open func prepare() { super.prepare() self.mj_h = 120 self.backgroundColor = UIColor.white // guard let info = Bundle.init(for: ZSRefreshTextHeader.self).infoDictionary else { return } // guard let executableName = info[kCFBundleExecutableKey as String] else { return } // var bundlePath = Bundle.init(for: ZSRefreshTextHeader.self).resourcePath ?? "" // bundlePath = bundlePath.appending("/\(executableName).bundle") // let imagePath = bundlePath.appending("/bookstore_bg") let bgImage = UIImage(named: "bookstore_bg") tipBackgroundView = UIImageView(image: bgImage) addSubview(tipBackgroundView) tipLabel = UILabel(frame: CGRect.zero) tipLabel.textColor = UIColor(red:0.51, green:0.33, blue:0.32, alpha:1.00) tipLabel.font = UIFont.systemFont(ofSize: 13) tipLabel.text = "来年若是凌风起,你自长哭我长笑" tipLabel.textAlignment = .center tipLabel.numberOfLines = 0 tipBackgroundView.addSubview(tipLabel) // let gifPath = bundlePath.appending("/mj_refresh") let gifPath = Bundle.main.path(forResource: "mj_refresh_loading@2x", ofType: "gif") ?? "" let image = YYImage(contentsOfFile: gifPath) gifView = YYAnimatedImageView(image: image) addSubview(gifView) gifView.startAnimating() } override open func placeSubviews() { super.placeSubviews() self.tipBackgroundView.frame = CGRect(x: 19, y: 10, width: self.bounds.width - 19*2, height: 59) self.tipLabel.frame = CGRect(x: 13, y: 12, width: tipBackgroundView.bounds.width - 26, height: 36) self.gifView.frame = CGRect(x: self.bounds.width/2 - 23/2, y: self.tipBackgroundView.frame.maxY + self.bounds.height/2 - self.tipBackgroundView.frame.maxY/2 - 16, width: 23, height: 32) } } ================================================ FILE: zhuishushenqi/NewVersion/BookShelf/ZSShelfManager.swift ================================================ // // ZSShelfManager.swift // zhuishushenqi // // Created by caony on 2020/1/8. // Copyright © 2020 QS. All rights reserved. // import UIKit import HandyJSON import YungCache class ZSShelfManager { private let shelfBooksPath = "bookshelf" private let shelfBooksPathKey = "shelfBooksPathKey" private let shelfBooksHistoryPath = "bookshelf/history" static let share = ZSShelfManager() let booksCache:Cache let shelfsCache:Cache // 每个book的localPath var books:[String] = [] { didSet { booksCache.setObjs(books, forKey: shelfBooksPath) } } var aikanBooks:[String:ZSAikanParserModel] = [:] var localBooks:[ZSShelfModel] = [] let helper = MonitorFileChangeHelp() var isScanning:Bool = false var queue:DispatchQueue = DispatchQueue(label: "com.shelf.update") private init(){ booksCache = Cache(path: shelfBooksPath) shelfsCache = Cache(path: "\(shelfBooksPath)/books") createPath() unpackBooksFile() local() } func unpackBooksFile() { if let books:[String] = booksCache.getObj(forKey: shelfBooksPath) { self.books = books.filterDuplicates({$0}) for bookPath in books { let fullPath = shelfModelPath(url: bookPath) let _:ZSShelfModel? = shelfsCache.getObj(forKey: fullPath) } } } func saveShelfModel(shelf:ZSShelfModel) { shelfsCache.setObj(shelf, forKey: "\(shelf.bookUrl)") } func getShelfModel(bookPath:String)->ZSShelfModel? { if let shelf:ZSShelfModel = shelfsCache.getObj(forKey: "\(bookPath)") { return shelf } return nil } func removeBook(bookPath:String) { let bookPath = shelfModelPath(url: bookPath) shelfsCache.removeObject(forKey: bookPath.asNSString()) } func refresh() { let path = ZSShelfConstant.inboxPath scanPath(path: path) } func local() { let path = ZSShelfConstant.inboxPath let localPath = ZSShelfConstant.localBooksPath let isDirectory:UnsafeMutablePointer? = UnsafeMutablePointer.allocate(capacity: 1) let localPathExist = FileManager.default.fileExists(atPath: localPath, isDirectory: isDirectory) if !localPathExist { try? FileManager.default.createDirectory(atPath: localPath, withIntermediateDirectories: true, attributes: nil) } scanPath(path: path) helper.watcher(forPath: path) { [weak self] (type) in self?.scanPath(path: path) } NotificationCenter.default.addObserver(self, selector: #selector(localChangeNoti(noti:)), name: NSNotification.Name.LocalShelfChanged, object: nil) } func scanPath(path:String){ if isScanning { return } isScanning = true if let items = try? FileManager.default.contentsOfDirectory(atPath: path) { for item in items { let filePath = path.appending("\(item)") let txtPathExtension = ".txt" if filePath.hasSuffix(txtPathExtension) { let fileFullName = filePath.nsString.lastPathComponent.replacingOccurrences(of: txtPathExtension, with: "") let bookUrl = "\(ZSShelfConstant.inboxPath)\(fileFullName)\(txtPathExtension)" let localBookUrl = "\(ZSShelfConstant.localBooksPath)\(fileFullName)\(txtPathExtension)" if !FileManager.default.fileExists(atPath: localBookUrl) { try? FileManager.default.copyItem(atPath: bookUrl, toPath: localBookUrl) } } } } localBooks.removeAll() let localPath = ZSShelfConstant.localBooksPath guard let localItems = try? FileManager.default.contentsOfDirectory(atPath: localPath) else { isScanning = false return } for item in localItems { let filePath = path.appending("\(item)") let txtPathExtension = ".txt" if filePath.hasSuffix(txtPathExtension) { let fileFullName = filePath.nsString.lastPathComponent.replacingOccurrences(of: txtPathExtension, with: "") let localBookUrl = "\(ZSShelfConstant.localBooks)\(fileFullName)\(txtPathExtension)" let shelf = ZSShelfModel() shelf.bookType = .local shelf.bookName = fileFullName shelf.bookUrl = localBookUrl if !localBooks.contains(shelf) { localBooks.append(shelf) } } } isScanning = false NotificationCenter.default.post(name: .ShelfChanged, object: nil) } //MARK: - local handler @objc private func localChangeNoti(noti:Notification) { refresh() } func removeLocalBook(bookUrl:String) { if exist(bookUrl) { books.removeAll { (model) -> Bool in return model == bookUrl } try? FileManager.default.removeItem(atPath:"\(NSHomeDirectory())\(bookUrl)") save() refresh() } } @discardableResult func add(_ book:ZSShelfModel) ->Bool { if !exist(book.bookUrl) { books.append(book.bookUrl) saveShelfModel(shelf: book) return true } return false } @discardableResult func remove(_ book:ZSShelfModel) ->Bool { let exitIndex = index(book.bookUrl) if exitIndex >= 0 && exitIndex < books.count { books.remove(at: exitIndex) removeBook(bookPath: book.bookUrl) return true } return false } @discardableResult func remove(_ bookUrl:String) ->Bool { var index = 0 for book in books { if book == bookUrl { books.remove(at: index) break } index += 1 } return true } @discardableResult func modify(_ book:ZSShelfModel) ->Bool { let exitIndex = index(book.bookUrl) if exitIndex >= 0 && exitIndex < books.count { books.remove(at: exitIndex) books.insert(book.bookUrl, at: exitIndex) return true } return false } func change(from:Int, to:Int) { if from >= books.count || from < 0 { return } if to >= books.count || to < 0 { return } if from == to { return } let book = books[from] if remove(book) { books.insert(book, at: to) } } func exist(_ bookUrl:String) ->Bool { var exist = false for bk in self.books { if bk == bookUrl { exist = true break } } return exist } func exist(_ bookUrl:String, at:[ZSShelfModel]) ->Bool { var exist = false for bk in at { if bk.bookUrl == bookUrl { exist = true break } } return exist } func exist(_ book:ZSShelfModel) -> Bool { var exist = false for bk in self.books { if bk == book.bookUrl { exist = true break } } return exist } func addAikan(_ book:ZSAikanParserModel) { let shelfModel = book.transformShelf() if add(shelfModel) { setAikan(model: book) { (result) in } } else if modify(shelfModel) { setAikan(model: book) { (result) in } } } func removeAikan(_ book:ZSAikanParserModel) { let shelfModel = book.transformShelf() if remove(shelfModel) { // save aikan let aikanFilePath = aikansPath(url: book.bookUrl) booksCache.removeObject(forKey: aikanFilePath.asNSString()) } } func removeAikan(bookUrl:String) { let aikanFilePath = aikansPath(url: bookUrl) booksCache.removeObject(forKey: aikanFilePath.asNSString()) } func modifyAikan(_ book:ZSAikanParserModel) { let bookPath = shelfModelPath(url: book.bookUrl) func updateAikan(shelf:ZSShelfModel, book:ZSAikanParserModel) { if modify(shelf) { setAikan(model: book) { (result) in } } } if let shelf:ZSShelfModel = shelfsCache.getObj(forKey: book.bookUrl) { let shelf = book.updateShelf(shelf: shelf) saveShelfModel(shelf: shelf) updateAikan(shelf: shelf, book: book) } else { let shelfModel = book.transformShelf() updateAikan(shelf: shelfModel, book: book) } } func getAikanModel(_ shelf:ZSShelfModel, block: @escaping (_ aikan:ZSAikanParserModel?)->Void) { let queue = DispatchQueue(label: "com.getaikanQueue", qos: DispatchQoS.default, attributes: DispatchQueue.Attributes.concurrent) queue.async { let aikan:ZSAikanParserModel? = self.booksCache.getObj(forKey: shelf.bookUrl) DispatchQueue.main.async { block(aikan) } } } func setAikan(model: ZSAikanParserModel, block: @escaping (_ aikan:Bool)->Void) { booksCache.setObj(model, forKey: model.bookUrl) DispatchQueue.main.async { block(true) } } func history(_ bookUrl:String, block:@escaping (_ history:ZSReadHistory?)->Void) { let aikanFilePath = historyStorePath(url: bookUrl) ZSShelfStorage.share.unarchive(path: aikanFilePath, block: { result in if let history = result as? ZSReadHistory { block(history) } else { block(nil) } }) } func addHistory(_ history:ZSReadHistory) { let aikanFilePath = historyStorePath(url: history.chapter.bookUrl) ZSShelfStorage.share.archive(obj: history, path: aikanFilePath) } func removeHistory(_ history:ZSReadHistory) { let aikanFilePath = historyStorePath(url: history.chapter.bookUrl) ZSShelfStorage.share.delete(path: aikanFilePath) } func removeHistory(bookUrl:String) { let aikanFilePath = historyStorePath(url: bookUrl) ZSShelfStorage.share.delete(path: aikanFilePath) } private func save() { let booksP = booksPath() let queue = DispatchQueue(label: "com.saveshelfQueue", qos: DispatchQoS.default, attributes: DispatchQueue.Attributes.concurrent) queue.async { self.booksCache.setObjs(self.books, forKey: booksP) } } private func index(_ bookPath:String) ->Int { var index = 0 var exitIndex = -1 for bk in self.books { if bk == bookPath { exitIndex = index break } index += 1 } return exitIndex } private func shelfModelPath(url:String) ->String { let documentPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first ?? "" let booksPath = documentPath.appending("/\(shelfBooksPath)/books/") let aikanFileName = url.md5() let aikanFilePath = booksPath.appending(aikanFileName) if !FileManager.default.fileExists(atPath: booksPath, isDirectory: nil) { try? FileManager.default.createDirectory(atPath: booksPath, withIntermediateDirectories: true, attributes: nil) } return aikanFilePath } private func aikansPath(url:String) ->String { let documentPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first ?? "" let booksPath = documentPath.appending("/\(shelfBooksPath)/") let aikanFileName = url.md5() let aikanFilePath = booksPath.appending(aikanFileName) return aikanFilePath } private func booksPath() ->String { let documentPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first ?? "" let booksPath = documentPath.appending("/\(shelfBooksPath)/\(shelfBooksPathKey.md5())") return booksPath } private func historyStorePath(url:String) ->String { let documentPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first ?? "" let booksHistoryPath = documentPath.appending("/\(shelfBooksHistoryPath)/") let aikanFileName = url.md5() let aikanFilePath = booksHistoryPath.appending(aikanFileName) return aikanFilePath } private func createPath() { let fileManager = FileManager.default let documentPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first ?? "" let booksPath = documentPath.appending("/\(shelfBooksPath)/") var pointer:UnsafeMutablePointer? if !fileManager.fileExists(atPath: booksPath, isDirectory: pointer) { try? fileManager.createDirectory(atPath: booksPath, withIntermediateDirectories: true, attributes: nil) } let historyPath = documentPath.appending("/\(shelfBooksHistoryPath)") if !fileManager.fileExists(atPath: historyPath, isDirectory: pointer) { try? fileManager.createDirectory(atPath: historyPath, withIntermediateDirectories: true, attributes: nil) } } } class ZSShelfModel: NSObject,NSCoding, HandyJSON { var icon:String = "" var bookName:String = "" var author:String = "" // 根据url查找对应的model var bookUrl:String = "" // 是否更新 var update:Bool = false // 最近更新章节 var latestChapterName:String = "" // 是否本地书籍 var bookType:ZSReaderBookStyle = .online required override init() { } func encode(with coder: NSCoder) { coder.encode(self.icon, forKey: "icon") coder.encode(self.bookName, forKey: "bookName") coder.encode(self.author, forKey: "author") coder.encode(self.bookUrl, forKey: "bookUrl") coder.encode(self.bookType.rawValue, forKey: "bookType") coder.encode(self.update, forKey: "update") coder.encode(self.latestChapterName, forKey: "latestChapterName") } required init?(coder: NSCoder) { self.icon = coder.decodeObject(forKey: "icon") as? String ?? "" self.bookName = coder.decodeObject(forKey: "bookName") as? String ?? "" self.author = coder.decodeObject(forKey: "author") as? String ?? "" self.bookUrl = coder.decodeObject(forKey: "bookUrl") as? String ?? "" self.bookType = ZSReaderBookStyle(rawValue: coder.decodeInteger(forKey: "bookType")) ?? .online self.update = coder.decodeBool(forKey: "update") self.latestChapterName = coder.decodeObject(forKey: "latestChapterName") as? String ?? "" } } class ZSShelfConstant { static let inboxPath = "\(NSHomeDirectory())/Documents/Inbox/" static let localBooksPath = "\(NSHomeDirectory())/Documents/LocalBooks/" static let localBooks = "/Documents/LocalBooks/" } extension Array where Element:ZSShelfModel { func contains(_ element: Element) -> Bool { if self.count == 0 { return false } var contain:Bool = false for item in self { if item.bookUrl == element.bookUrl { contain = true break } } return contain } } ================================================ FILE: zhuishushenqi/NewVersion/BookShelf/ZSShelfOperatingView.swift ================================================ // // ZSShelfOperatingView.swift // zhuishushenqi // // Created by caony on 2020/1/16. // Copyright © 2020 QS. All rights reserved. // import UIKit protocol ZSShelfOperatingViewDelegate:class { func opView(opView:ZSShelfOperatingView, clickDetail:UIButton) func opView(opView:ZSShelfOperatingView, clickDownload:UIButton) func opView(opView:ZSShelfOperatingView, clickTop:UIButton) func opView(opView:ZSShelfOperatingView, clickSource:UIButton) func opView(opView:ZSShelfOperatingView, clickDelete:UIButton) } class ZSShelfOperatingView: UIView { weak var delegate:ZSShelfOperatingViewDelegate? var isLocalBook:Bool = false var bookUrl:String = "" lazy var backgroundView:UIView = { let view = UIView(frame: .zero) view.backgroundColor = UIColor(white: 0.0, alpha: 0.4) return view }() lazy var contentView:UIView = { let view = UIView(frame: .zero) view.backgroundColor = UIColor(white: 1.0, alpha: 1.0) return view }() lazy var infoView:UIView = { let view = UIView(frame: .zero) view.backgroundColor = UIColor(white: 0.96, alpha: 1.0) return view }() lazy var bookIconView:UIImageView = { let view = UIImageView(frame: .zero) return view }() lazy var bookNameLB:UILabel = { let lb = UILabel(frame: .zero) lb.textColor = UIColor(red:0.51, green:0.33, blue:0.32, alpha:1.00) lb.font = UIFont.systemFont(ofSize: 13) lb.textAlignment = .left return lb }() lazy var authorLB:UILabel = { let lb = UILabel(frame: .zero) lb.textColor = UIColor(red:0.51, green:0.33, blue:0.32, alpha:1.00) lb.font = UIFont.systemFont(ofSize: 13) lb.textAlignment = .left return lb }() lazy var detailButton:UIButton = { let bt = UIButton(type: .custom) bt.setTitle("详情", for: .normal) bt.setTitleColor(UIColor.black, for: .normal) bt.titleLabel?.font = UIFont.systemFont(ofSize: 13) bt.layer.cornerRadius = 5 bt.layer.masksToBounds = true bt.addTarget(self, action: #selector(detailAction(bt:)), for: .touchUpInside) return bt }() lazy var downloadButton:UIButton = { let bt = UIButton(type: .custom) bt.setTitle("批量下载", for: .normal) bt.setTitleColor(UIColor.black, for: .normal) bt.setImage(UIImage(named: "icon_download_a_24x24_"), for: .normal) bt.titleLabel?.font = UIFont.systemFont(ofSize: 13) bt.layer.cornerRadius = 5 bt.layer.masksToBounds = true bt.addTarget(self, action: #selector(downloadAction(bt:)), for: .touchUpInside) return bt }() lazy var topButton:UIButton = { let bt = UIButton(type: .custom) bt.setTitle("删除缓存", for: .normal) bt.setTitleColor(UIColor.black, for: .normal) bt.setImage(UIImage(named: "icon_top_selected_24x24_"), for: .normal) bt.titleLabel?.font = UIFont.systemFont(ofSize: 13) bt.layer.cornerRadius = 5 bt.layer.masksToBounds = true bt.addTarget(self, action: #selector(topAction(bt:)), for: .touchUpInside) return bt }() lazy var groupButton:UIButton = { let bt = UIButton(type: .custom) bt.setTitle("查看来源", for: .normal) bt.setTitleColor(UIColor.black, for: .normal) bt.setImage(UIImage(named: "icon_download_a_24x24_"), for: .normal) bt.titleLabel?.font = UIFont.systemFont(ofSize: 13) bt.layer.cornerRadius = 5 bt.layer.masksToBounds = true bt.addTarget(self, action: #selector(sourceAction(bt:)), for: .touchUpInside) return bt }() lazy var deleteButton:UIButton = { let bt = UIButton(type: .custom) bt.setTitle("删除", for: .normal) bt.setTitleColor(UIColor.black, for: .normal) bt.setImage(UIImage(named: "icon_delete_1_24x24_"), for: .normal) bt.titleLabel?.font = UIFont.systemFont(ofSize: 13) bt.layer.cornerRadius = 5 bt.layer.masksToBounds = true bt.addTarget(self, action: #selector(deleteAction(bt:)), for: .touchUpInside) return bt }() var book:ZSAikanParserModel! var indexPath:IndexPath? override init(frame: CGRect) { super.init(frame: frame) addSubview(backgroundView) addSubview(contentView) contentView.addSubview(infoView) contentView.addSubview(downloadButton) contentView.addSubview(topButton) contentView.addSubview(groupButton) contentView.addSubview(deleteButton) infoView.addSubview(bookNameLB) infoView.addSubview(bookIconView) infoView.addSubview(authorLB) infoView.addSubview(detailButton) setupGesture() backgroundView.frame = bounds contentView.frame = CGRect(x: 0, y: bounds.height, width: bounds.width, height: 160) infoView.frame = CGRect(x: 0, y: 0, width: bounds.width, height: 80) bookIconView.frame = CGRect(x: 15, y: 10, width: 40, height: 60) bookNameLB.frame = CGRect(x: bookIconView.frame.maxX + 10, y: 10, width: 200, height: 20) authorLB.frame = CGRect(x: bookIconView.frame.maxX + 10, y: 40, width: 200, height: 20) detailButton.frame = CGRect(x: bounds.width - 75, y: 30, width: 60, height: 20) let bottomWidth:CGFloat = 80 let spaceX = (bounds.width/4 - bottomWidth)/2 downloadButton.frame = CGRect(x: spaceX, y: 80, width: bottomWidth, height: 60) topButton.frame = CGRect(x: spaceX * 3 + bottomWidth, y: 80, width: bottomWidth, height: 60) groupButton.frame = CGRect(x: spaceX * 5 + bottomWidth * 2, y: 80, width: bottomWidth, height: 60) deleteButton.frame = CGRect(x: spaceX * 7 + bottomWidth * 3, y: 80, width: bottomWidth, height: 60) } override func layoutSubviews() { super.layoutSubviews() } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } private func setupGesture() { let tap = UITapGestureRecognizer(target: self, action: #selector(tapAction(tap:))) backgroundView.addGestureRecognizer(tap) } @objc private func tapAction(tap:UITapGestureRecognizer) { hiden(true) } @objc private func detailAction(bt:UIButton) { delegate?.opView(opView: self, clickDetail: bt) hiden(false) } @objc private func downloadAction(bt:UIButton) { delegate?.opView(opView: self, clickDownload: bt) hiden(false) } @objc private func topAction(bt:UIButton) { delegate?.opView(opView: self, clickTop: bt) hiden(false) } @objc private func sourceAction(bt:UIButton) { delegate?.opView(opView: self, clickSource: bt) hiden(false) } @objc private func deleteAction(bt:UIButton) { delegate?.opView(opView: self, clickDelete: bt) hiden(false) } func configure(book:ZSAikanParserModel) { self.book = book let icon = book.bookIcon.length > 0 ? book.bookIcon:book.detailBookIcon let resource = QSResource(url: URL(string: icon) ?? URL(string: "https://www.baidu.com")!) bookIconView.kf.setImage(with: resource, placeholder: UIImage(named: "default_book_cover")) bookNameLB.text = book.bookName authorLB.text = book.bookAuthor } func show(inView:UIView,_ animated:Bool = true) { inView.addSubview(self) if animated { UIView.animate(withDuration: 0.5, animations: { self.contentView.frame.origin.y = self.bounds.height - 160 }) { (finished) in } } else { self.contentView.frame.origin.y = self.bounds.height - 160 } } func hiden(_ animated:Bool = true) { if animated { UIView.animate(withDuration: 0.5, animations: { self.contentView.frame.origin.y = self.bounds.height }) { (finished) in self.removeFromSuperview() } } else { self.contentView.frame.origin.y = self.bounds.height self.removeFromSuperview() } } } ================================================ FILE: zhuishushenqi/NewVersion/BookShelf/ZSShelfStorage.swift ================================================ // // ZSShelfStorage.swift // zhuishushenqi // // Created by caony on 2020/6/18. // Copyright © 2020 QS. All rights reserved. // import UIKit class ZSShelfStorage { private let syncStorage:ZSSyncStorage private var cacheDict:[String:Any] = [:] static var share = ZSShelfStorage() private init() { self.syncStorage = ZSSyncStorage(serialQueue: DispatchQueue(label: "ZSSQ.ZSSyncStorage.SerialQueue"), concurrentQueue:DispatchQueue(label: "ZSSQ.ZSSyncStorage.ConcurrentQueue", qos: .userInitiated, attributes: .concurrent)) NotificationCenter.default.addObserver(self, selector: #selector(didReceivememoryWarning), name: UIApplication.didReceiveMemoryWarningNotification, object: nil) } deinit { NotificationCenter.default.removeObserver(self) } @objc private func didReceivememoryWarning() { syncStorage.async_barrier { self.cacheDict.removeAll() } } private func getAndCacheFile(path:String) ->Any? { assert(path.length != 0) if path.length == 0 { return nil } var result:Any? = nil let manager = FileManager.default if cacheDict[path] != nil && manager.fileExists(atPath: path) { result = cacheDict[path] return result } if manager.fileExists(atPath: path) { if let data = manager.contents(atPath: path) { result = NSKeyedUnarchiver.unarchiveObject(with: data) } } if result != nil { cacheDict[path] = result! } return result } func object(for path:String) ->Any? { let start = CFAbsoluteTimeGetCurrent() assert(path.count != 0) if path.length == 0 { return nil } var obj:Any? = nil syncStorage.sync { obj = getAndCacheFile(path: path) } let end = CFAbsoluteTimeGetCurrent() print("get object for key:\(path), time:\(end - start)") return obj } @discardableResult func setObject(obj:Any, path:String) ->Bool { let start = CFAbsoluteTimeGetCurrent() assert(path.count != 0) if path.length == 0 { return false } syncStorage.sync { self.cacheDict[path] = obj } saveFile(path: path, obj: obj) let end = CFAbsoluteTimeGetCurrent() print("set object for key:\(path), time:\(end - start)") return true } @discardableResult func removeObject(path:String) ->Bool { let start = CFAbsoluteTimeGetCurrent() assert(path.count != 0) if path.length == 0 { return false } removeFile(path: path) let end = CFAbsoluteTimeGetCurrent() print("set object for key:\(path), time:\(end - start)") return true } private func saveFile(path:String, obj:Any){ syncStorage.async_serial { let data = NSKeyedArchiver.archivedData(withRootObject: obj) let url = URL(fileURLWithPath: path) try? data.write(to: url) } } private func removeFile(path:String) { syncStorage.async_serial { if FileManager.default.fileExists(atPath: path) { let url = URL(fileURLWithPath: path) try? FileManager.default.removeItem(at: url) } } } func asyncMain(block:@escaping()->Void) { DispatchQueue.main.async { block() } } func write(data:NSData?, path:String) { if data == nil || path.count == 0 { return } syncStorage.async { data?.write(toFile: path, atomically: true) } } func read(path:String, block:@escaping(_ data:NSData?) ->Void) { if path.count == 0 { asyncMain { block(nil) } return } syncStorage.async { [weak self] in let data:NSData? = FileManager.default.contents(atPath: path) as NSData? self?.asyncMain { block(data) } } } func delete(path:String) { if path.count == 0 { return } syncStorage.async { try? FileManager.default.removeItem(atPath: path) } } func archive(obj:Any?, path:String) { if obj == nil || path.count == 0 { return } syncStorage.async { let data = NSKeyedArchiver.archivedData(withRootObject: obj!) as NSData data.write(toFile: path, atomically: true) } } func unarchive(path:String, block:@escaping(_ obj:Any?) ->Void) { if path.count == 0 { asyncMain { block(nil) } return } syncStorage.async { [weak self] in if let data = FileManager.default.contents(atPath: path) { let obj:Any? = NSKeyedUnarchiver.unarchiveObject(with: data) self?.asyncMain { block(obj) } } else { self?.asyncMain { block(nil) } } } } } ================================================ FILE: zhuishushenqi/NewVersion/BookShelf/ZSShelfTableViewCell.swift ================================================ // // ZSShelfTableViewCell.swift // zhuishushenqi // // Created by caony on 2020/1/10. // Copyright © 2020 QS. All rights reserved. // import UIKit import UICircularProgressRing protocol ZSShelfTableViewCellDelegate:class { func shelfCell(cell:ZSShelfTableViewCell, clickMore:UIButton) } class ZSShelfTableViewCell: UITableViewCell { weak var delegate:ZSShelfTableViewCellDelegate? lazy var booknameLB:UILabel = { let lb = UILabel(frame: .zero) lb.textAlignment = .left lb.textColor = UIColor.black lb.font = UIFont.systemFont(ofSize: 15) return lb }() lazy var authorLB:UILabel = { let lb = UILabel(frame: .zero) lb.textAlignment = .left lb.textColor = UIColor.gray lb.font = UIFont.systemFont(ofSize: 15) return lb }() lazy var latestChapterLB:UILabel = { let lb = UILabel(frame: .zero) lb.textAlignment = .left lb.textColor = UIColor.red lb.font = UIFont.systemFont(ofSize: 13) return lb }() lazy var updateLB:UILabel = { let lb = UILabel(frame: .zero) lb.textAlignment = .left lb.textColor = UIColor.white lb.backgroundColor = UIColor.red lb.font = UIFont.systemFont(ofSize: 13) lb.layer.cornerRadius = 5 lb.layer.masksToBounds = true return lb }() lazy var moreBT:UIButton = { let bt = UIButton(type: .custom) bt.setImage(UIImage(named: "bbs_icon_more_big_26_26"), for: .normal) bt.addTarget(self, action: #selector(moreAction(bt:)), for: .touchUpInside) return bt }() lazy var darkRingBackground:UIView = { let view = UIView(frame: .zero) view.backgroundColor = UIColor(white: 0.0, alpha: 0.5) view.isHidden = true return view }() lazy var progressRing:UICircularProgressRing = { let ring = UICircularProgressRing() ring.style = .ontop ring.font = UIFont.systemFont(ofSize: 13) ring.isHidden = true return ring }() override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) contentView.addSubview(booknameLB) contentView.addSubview(authorLB) contentView.addSubview(moreBT) contentView.addSubview(updateLB) contentView.addSubview(latestChapterLB) imageView?.addSubview(darkRingBackground) imageView?.addSubview(progressRing) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func awakeFromNib() { super.awakeFromNib() // Initialization code } override func setSelected(_ selected: Bool, animated: Bool) { super.setSelected(selected, animated: animated) // Configure the view for the selected state } override func prepareForReuse() { super.prepareForReuse() finish() } @objc private func moreAction(bt:UIButton) { delegate?.shelfCell(cell: self, clickMore: bt) } func latestColor(update:Bool) -> UIColor { if update { return UIColor.red } return UIColor.gray } func configure(model:ZSShelfModel) { booknameLB.text = model.bookName authorLB.text = model.author latestChapterLB.text = "最近更新: \(model.latestChapterName)" updateLB.isHidden = (!model.update || model.bookType == .local) latestChapterLB.isHidden = model.bookType == .local latestChapterLB.textColor = latestColor(update: model.update) let icon = model.icon let resource = QSResource(url: URL(string: icon) ?? URL(string: "https://www.baidu.com")!) imageView?.kf.setImage(with: resource, placeholder: UIImage(named: "default_book_cover")) } func progress(value:CGFloat, max:CGFloat) { progressRing.isHidden = false darkRingBackground.isHidden = false progressRing.startProgress(to: value * 100 / max, duration: 0.3) } func finish() { progressRing.isHidden = true darkRingBackground.isHidden = true } override func layoutSubviews() { super.layoutSubviews() imageView?.frame = CGRect(x: 20, y: 10, width: 60, height: bounds.height - 20) darkRingBackground.frame = CGRect(x: 0, y: 0, width: 60, height: bounds.height - 20) booknameLB.frame = CGRect(x: (imageView?.frame.maxX ?? 0) + 10, y: 10, width: bounds.width - 60 - (imageView?.frame.maxX ?? 0) - 10, height: 20) authorLB.frame = CGRect(x: (imageView?.frame.maxX ?? 0) + 10, y: 40, width: 200, height: 20) latestChapterLB.frame = CGRect(x: (imageView?.frame.maxX ?? 0) + 10, y: bounds.height - 30, width: self.bounds.width - (imageView?.frame.maxX ?? 0) - 10, height: 20) let booknameSize = booknameLB.sizeThatFits(CGSize(width: CGFloat.greatestFiniteMagnitude, height: 20)) updateLB.frame = CGRect(x: (imageView?.frame.maxX ?? 0) + 20 + booknameSize.width, y: 15, width: 10, height: 10) moreBT.frame = CGRect(x: bounds.width - 60, y: 10, width: 40, height: 40) progressRing.frame = imageView?.bounds ?? CGRect(x: 0, y: 0, width: 60, height: bounds.height - 20) separatorInset = UIEdgeInsets(top: 0, left: 20, bottom: 0, right: -20) } } ================================================ FILE: zhuishushenqi/NewVersion/BookShelf/ZSSyncStorage.swift ================================================ // // ZSSyncStorage.swift // zhuishushenqi // // Created by caony on 2020/6/18. // Copyright © 2020 QS. All rights reserved. // import UIKit class ZSSyncStorage { public let serialQueue: DispatchQueue public let concurrentQueue: DispatchQueue public init(serialQueue: DispatchQueue, concurrentQueue:DispatchQueue) { self.serialQueue = serialQueue self.concurrentQueue = concurrentQueue } func sync(execute block: () -> Void) { serialQueue.sync { block() } } func async_barrier(execute block: @escaping() -> Void) { concurrentQueue.async(flags: .barrier) { block() } } func async( block : @escaping () -> Void) { concurrentQueue.async { block() } } func async_serial( block : @escaping () -> Void) { serialQueue.async { block() } } } ================================================ FILE: zhuishushenqi/NewVersion/BookShelf/ZSToast.swift ================================================ // // ZSToast.swift // zhuishushenqi // // Created by caony on 2020/1/17. // Copyright © 2020 QS. All rights reserved. // import UIKit import PKHUD enum ToastType { case success case failure case warning } class Toast: UIView { static func show(_ type:ToastType,_ delay:TimeInterval = 3) { switch type { case .success: HUD.flash(HUDContentType.success, delay: delay) break case .failure: HUD.flash(HUDContentType.error, delay: delay) break case .warning: HUD.flash(HUDContentType.progress, delay: delay) break default: break } } static func show(tip:String, _ type:ToastType = .success,_ delay:TimeInterval = 3) { switch type { case .success: HUD.flash(.labeledSuccess(title: tip, subtitle: nil), delay: delay) break case .failure: HUD.flash(.labeledError(title: tip, subtitle: nil), delay: delay) break case .warning: HUD.flash(.labeledProgress(title: tip, subtitle: nil), delay: delay) break default: break } } static func showProgress(tip:String, onView:UIView?, delay:TimeInterval = 1) { HUD.show(.labeledProgress(title: tip, subtitle: nil), onView: onView) } static func hiden(aniamted:Bool = true) { HUD.hide(animated: aniamted) } } ================================================ FILE: zhuishushenqi/NewVersion/BookStore/ZSBookStoreViewController.swift ================================================ // // ZSBookStoreViewController.swift // ZSBookStore // // Created by caony on 2019/6/18. // import UIKit class ZSBookStoreViewController: BaseViewController,ZSDiscoverNavigationBarDelegate, ZSWebViewControllerDelegate { lazy var navgationBar:ZSDiscoverNavigationBar = { let nav = ZSDiscoverNavigationBar(frame: .zero) nav.delegate = self nav.searchButton.setImage(UIImage(named:"bookstore_def_classify_20_20_20x20_"), for: .normal) nav.searchButton.setTitle("分类", for: .normal) nav.titleLabel.text = "书城" return nav }() var webViewController:ZSWebViewController = ZSWebViewController() override func viewDidLoad() { super.viewDidLoad() let timeInterval = Date().timeIntervalSince1970 webViewController.url = "https://h5.zhuishushenqi.com/v2/index3.html?id=e5fe6058afa449e4a8b9b3fb843c2bcd&posCode=B1×tamp=\(timeInterval)&gender=male&version=14&platform=ios&packageName=com.ifmoc.ZhuiShuShenQi" webViewController.delegate = self view.addSubview(self.navgationBar) view.addSubview(self.webViewController.view) // addChild(self.webViewController) navgationBar.snp.makeConstraints { (make) in make.left.top.right.equalToSuperview() make.height.equalTo(kNavgationBarHeight) } webViewController.view.snp.makeConstraints { (make) in make.left.right.equalToSuperview() make.top.equalTo(self.navgationBar.snp.bottom) make.height.equalTo(ScreenHeight - kNavgationBarHeight - FOOT_BAR_Height) } } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) navigationController?.isNavigationBarHidden = true } override var preferredStatusBarStyle: UIStatusBarStyle { return .lightContent } //MARK: - ZSDiscoverNavigationBarDelegate func nav(nav: ZSDiscoverNavigationBar, didClickSearch: UIButton) { let catelogVC = ZSCatelogViewController() catelogVC.hidesBottomBarWhenPushed = true navigationController?.pushViewController(catelogVC, animated: true) } } ================================================ FILE: zhuishushenqi/NewVersion/Comment/ZSForumComment.swift ================================================ // // ZSForumComment.swift // zhuishushenqi // // Created by yung on 2019/8/12. // Copyright © 2019 QS. All rights reserved. // import UIKit import HandyJSON struct ZSForumComment: HandyJSON { var id : String = "" var author : ZSForumAuthor = ZSForumAuthor() var content : String = "" var created : String = "" var floor : Int = 0 var likeCount : Int = 0 var replyTo : AnyObject? init() {} } struct ZSForumAuthor: HandyJSON { var id : String = "" var activityAvatar : String = "" var avatar : String = "" var gender : String = "" var lv : Int = 0 var nickname : String = "" var type : String = "" init() {} } ================================================ FILE: zhuishushenqi/NewVersion/Comment/ZSForumPageCell.swift ================================================ // // ZSForumPageCell.swift // zhuishushenqi // // Created by yung on 2019/8/13. // Copyright © 2019 QS. All rights reserved. // import UIKit protocol ZSForumPageCellDelegate:class { func forumCell(forumCell:ZSForumPageCell, clickIcon:UIButton) func forumCell(forumCell:ZSForumPageCell, clickLike:UIButton) func forumCell(forumCell:ZSForumPageCell, clickMore:UIButton) } class ZSForumPageCell: UITableViewCell { weak var delegate:ZSForumPageCellDelegate? private lazy var avatarView:UIButton = { let view = UIButton(type: .custom) view.layer.cornerRadius = 20 view.layer.masksToBounds = true view.addTarget(self, action: #selector(avatarAction(btn:)), for: .touchUpInside) return view }() private lazy var nicknameLabel:UILabel = { let label = UILabel(frame: .zero) label.font = UIFont.systemFont(ofSize: 13) label.textColor = UIColor.gray return label }() private lazy var levelLabel:UILabel = { let label = UILabel(frame: .zero) label.font = UIFont.systemFont(ofSize: 9) label.textAlignment = .center label.textColor = UIColor.gray label.layer.cornerRadius = 6.5 label.layer.masksToBounds = true label.layer.borderColor = UIColor.gray.cgColor label.layer.borderWidth = 1 return label }() private lazy var displayView:ZSDisplayView = { let view = ZSDisplayView(frame: .zero) return view }() private lazy var timeLabel:UILabel = { let label = UILabel(frame: .zero) label.font = UIFont.systemFont(ofSize: 13) label.textColor = UIColor.gray return label }() private lazy var floorLabel:UILabel = { let label = UILabel(frame: .zero) label.font = UIFont.systemFont(ofSize: 13) label.textColor = UIColor.gray return label }() private lazy var messageCountLabel:UILabel = { let label = UILabel(frame: .zero) label.font = UIFont.systemFont(ofSize: 13) label.textColor = UIColor.gray return label }() private lazy var likeButton:UIButton = { let button = UIButton(type: .custom) button.setImage(UIImage(named: "bbs_icon_like02_26_26"), for: .normal) button.addTarget(self, action: #selector(likeAction(btn:)), for: .touchUpInside) return button }() private lazy var moreButton:UIButton = { let button = UIButton(type: .custom) button.setImage(UIImage(named: "bbs_icon_more_big_26_26"), for: .normal) button.addTarget(self, action: #selector(moreAction(btn:)), for: .touchUpInside) return button }() override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) contentView.addSubview(avatarView) contentView.addSubview(nicknameLabel) contentView.addSubview(levelLabel) contentView.addSubview(displayView) contentView.addSubview(timeLabel) contentView.addSubview(floorLabel) contentView.addSubview(likeButton) contentView.addSubview(messageCountLabel) contentView.addSubview(moreButton) avatarView.snp.makeConstraints { (make) in make.top.left.equalTo(20) make.width.height.equalTo(40) } nicknameLabel.snp.makeConstraints { [unowned self](make) in make.left.equalTo(self.avatarView.snp_right).offset(20) make.top.equalTo(20) make.height.equalTo(14) make.width.equalTo(0) } levelLabel.snp.makeConstraints { [unowned self](make) in make.left.equalTo(self.nicknameLabel.snp_right).offset(5) make.top.equalTo(20) make.height.equalTo(14) make.width.equalTo(40) } displayView.snp.makeConstraints { [unowned self](make) in make.left.equalTo(self.nicknameLabel.snp_left) make.top.equalTo(self.nicknameLabel.snp_bottom).offset(10) make.right.equalToSuperview().offset(-20) make.height.equalTo(0) } timeLabel.snp.makeConstraints { [unowned self](make) in make.left.equalTo(self.displayView.snp_left) make.height.equalTo(14) make.width.equalTo(0) make.bottom.equalToSuperview().offset(-14) } floorLabel.snp.makeConstraints { [unowned self](make) in make.left.equalTo(self.timeLabel.snp_right).offset(10) make.height.equalTo(self.timeLabel.snp_height) make.width.equalTo(0) make.bottom.equalToSuperview().offset(-14) } moreButton.snp.makeConstraints { [unowned self](make) in make.right.equalToSuperview().offset(-20) make.top.equalTo(self.displayView.snp_bottom).offset(10) make.width.equalTo(26) make.height.equalTo(26) make.bottom.equalToSuperview().offset(-10) } messageCountLabel.snp.makeConstraints { [unowned self](make) in make.right.equalTo(self.moreButton.snp_left).offset(0) make.width.equalTo(30) make.height.equalTo(14) make.bottom.equalTo(self.moreButton.snp_bottom).offset(-2) } likeButton.snp.makeConstraints { [unowned self](make) in make.right.equalTo(self.messageCountLabel.snp_left).offset(0) make.width.height.equalTo(26) make.top.equalTo(self.moreButton.snp_top) } } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func awakeFromNib() { super.awakeFromNib() // Initialization code } override func setSelected(_ selected: Bool, animated: Bool) { super.setSelected(selected, animated: animated) // Configure the view for the selected state } func configure(model:ZSForumComment) { self.avatarView.qs_setAvatarWithURLString(urlString: model.author.avatar) self.nicknameLabel.text = model.author.nickname let size = self.nicknameLabel.sizeThatFits(CGSize(width: CGFloat.greatestFiniteMagnitude, height: 14)) nicknameLabel.snp.updateConstraints { (make) in make.width.equalTo(size.width) } self.levelLabel.text = "lv.\(model.author.lv)" let parser = MarkupParser() let settings = CTSettings() settings.pageRect = CGRect(x: settings.pageRect.origin.x, y: settings.pageRect.origin.y, width: self.displayView.bounds.width, height: settings.pageRect.height) parser.parseContent(model.content, settings: settings) displayView.snp.updateConstraints { (make) in make.height.equalTo(parser.coreData?.height ?? 0) } displayView.buildContent(attr: parser.attrString, andImages: parser.coreData?.images ?? [], settings: settings) timeLabel.qs_setCreateTime(createTime: model.created, append: "") let timeSize = timeLabel.sizeThatFits(CGSize(width: CGFloat.greatestFiniteMagnitude, height: 14)) timeLabel.snp.updateConstraints { (make) in make.width.equalTo(timeSize.width) } floorLabel.text = "\(model.floor)楼" let floorSize = floorLabel.sizeThatFits(CGSize(width: CGFloat.greatestFiniteMagnitude, height: 14)) floorLabel.snp.updateConstraints { (make) in make.width.equalTo(floorSize.width) } messageCountLabel.text = "\(model.likeCount)" } @objc private func avatarAction(btn:UIButton) { delegate?.forumCell(forumCell: self, clickIcon: btn) } @objc private func likeAction(btn:UIButton) { delegate?.forumCell(forumCell: self, clickLike: btn) } @objc private func moreAction(btn:UIButton) { delegate?.forumCell(forumCell: self, clickMore: btn) } } ================================================ FILE: zhuishushenqi/NewVersion/Comment/ZSForumPageFooterView.swift ================================================ // // ZSForumPageFooterView.swift // zhuishushenqi // // Created by yung on 2019/8/12. // Copyright © 2019 QS. All rights reserved. // import UIKit class ZSForumPageFooterView: UITableViewHeaderFooterView { private lazy var messageImageView:UIImageView = { let imageView = UIImageView(frame: .zero) imageView.image = UIImage(named: "bbs_icon_message_20_20") return imageView }() private lazy var messageLabel:UILabel = { let label = UILabel(frame: .zero) label.textColor = UIColor.darkText label.font = UIFont.systemFont(ofSize: 13) label.textAlignment = .left label.text = "0条评论" return label }() private lazy var sofaImageView:UIImageView = { let imageView = UIImageView(frame: .zero) imageView.image = UIImage(named: "community_noReview_sofa_icon") return imageView }() private lazy var sofaLabel:UILabel = { let label = UILabel(frame: .zero) label.textColor = UIColor.gray label.font = UIFont.systemFont(ofSize: 15) label.textAlignment = .center label.text = "赶紧来抢沙发啊~" return label }() override init(reuseIdentifier: String?) { super.init(reuseIdentifier: reuseIdentifier) contentView.addSubview(messageImageView) contentView.addSubview(messageLabel) contentView.addSubview(sofaImageView) contentView.addSubview(sofaLabel) messageImageView.snp.makeConstraints { (make) in make.left.top.equalToSuperview().offset(20) make.width.height.equalTo(20) } messageLabel.snp.makeConstraints { [unowned self](make) in make.left.equalTo(self.messageImageView.snp_right).offset(10) make.top.equalToSuperview().offset(20) make.width.equalTo(100) make.height.equalTo(20) } sofaImageView.snp.makeConstraints { (make) in make.width.equalTo(100) make.height.equalTo(56) make.top.equalToSuperview().offset(60) make.centerX.equalToSuperview() } sofaLabel.snp.makeConstraints { [unowned self](make) in make.left.equalToSuperview().offset(20) make.right.equalToSuperview().offset(-20) make.top.equalTo(self.sofaImageView.snp_bottom).offset(5) make.height.equalTo(20) make.bottom.equalToSuperview().offset(-100) } } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } } ================================================ FILE: zhuishushenqi/NewVersion/Comment/ZSForumPageHeaderView.swift ================================================ // // ZSForumPageHeaderView.swift // zhuishushenqi // // Created by yung on 2019/8/6. // Copyright © 2019 QS. All rights reserved. // import UIKit class ZSForumPageHeaderView: UITableViewHeaderFooterView, ZSForumToolBarDelegate { private lazy var iconView:UIImageView = { let view = UIImageView(frame: .zero) return view }() private lazy var nicknameLabel:UILabel = { let label = UILabel(frame: .zero) label.font = UIFont.systemFont(ofSize: 13) label.textColor = UIColor(red: 0.63, green: 0.53, blue: 0.48, alpha: 1.0) label.setContentHuggingPriority(UILayoutPriority.required, for: .horizontal) return label }() private lazy var tagView:UIImageView = { let imageView = UIImageView(image: UIImage(named: "")) return imageView }() private lazy var levelLabel:UILabel = { let label = UILabel(frame: .zero) label.font = UIFont.systemFont(ofSize: 9) label.textAlignment = .center label.textColor = UIColor(red: 0.63, green: 0.53, blue: 0.48, alpha: 1.0) label.layer.cornerRadius = 6.5 label.layer.masksToBounds = true label.layer.borderColor = UIColor(red: 0.63, green: 0.53, blue: 0.48, alpha: 1.0).cgColor label.layer.borderWidth = 1 return label }() private lazy var timeLabel:UILabel = { let label = UILabel(frame: .zero) label.font = UIFont.systemFont(ofSize: 9) label.textColor = UIColor.gray return label }() private lazy var titleLabel:UILabel = { let label = UILabel(frame: .zero) label.font = UIFont.systemFont(ofSize: 20) label.textColor = UIColor.black label.numberOfLines = 0 return label }() private lazy var displayView:ZSDisplayView = { let diplayView = ZSDisplayView(frame: .zero) return diplayView }() private lazy var toolBar:ZSForumToolBar = { let toolBar = ZSForumToolBar(frame: .zero) toolBar.delegate = self return toolBar }() override init(reuseIdentifier: String?) { super.init(reuseIdentifier: reuseIdentifier) contentView.backgroundColor = UIColor.white addSubview(iconView) addSubview(nicknameLabel) addSubview(tagView) addSubview(levelLabel) addSubview(timeLabel) addSubview(titleLabel) addSubview(displayView) addSubview(toolBar) iconView.snp.makeConstraints { (make) in make.left.equalToSuperview().offset(20) make.top.equalToSuperview().offset(20) make.width.equalTo(40) make.height.equalTo(40) } nicknameLabel.snp.makeConstraints { [unowned self] (make) in make.left.equalTo(self.iconView.snp_right).offset(10) make.top.equalTo(20) make.height.equalTo(20) } tagView.snp.makeConstraints { [unowned self](make) in make.left.equalTo(self.nicknameLabel.snp_right).offset(5) make.top.equalTo(24) make.width.equalTo(0) make.height.equalTo(12) } timeLabel.snp.makeConstraints { [unowned self](make) in make.left.equalTo(self.iconView.snp_right).offset(10) make.top.equalTo(self.nicknameLabel.snp_bottom) make.height.equalTo(20) } levelLabel.snp.makeConstraints { [unowned self](make) in make.left.equalTo(self.tagView.snp_right).offset(5) make.top.equalTo(23.5) make.width.equalTo(30) make.height.equalTo(13) } titleLabel.snp.makeConstraints { [unowned self](make) in make.left.equalTo(20) make.top.equalTo(self.iconView.snp_bottom).offset(10) make.right.equalToSuperview().offset(-20) } displayView.snp.makeConstraints { [unowned self](make) in make.left.equalToSuperview().offset(20) make.top.equalTo(self.titleLabel.snp_bottom).offset(10) make.right.equalToSuperview().offset(-20) make.height.equalTo(0) } toolBar.snp.makeConstraints { [unowned self](make) in make.width.equalTo(250) make.height.equalTo(52) make.top.equalTo(self.displayView.snp_bottom).offset(20) make.centerX.equalToSuperview() make.bottom.equalToSuperview().offset(-20) } } func configure(review:ZSPostReview?) { guard let postReview = review else { return } iconView.qs_setAvatarWithURLString(urlString: postReview.author.avatar) nicknameLabel.text = postReview.author.nickname levelLabel.text = "lv.\(postReview.author.lv)" timeLabel.qs_setCreateTime(createTime: postReview.created, append: "") titleLabel.text = postReview.title let parser = MarkupParser() parser.parseContent(postReview.content, settings: CTSettings.shared) displayView.snp.updateConstraints { (make) in make.height.equalTo(parser.coreData?.height ?? 0) } displayView.buildContent(attr: parser.attrString, andImages: parser.coreData?.images ?? [], settings: CTSettings.shared) tagView.image = postReview.author.type.image if let _ = tagView.image { tagView.snp.updateConstraints { (make) in make.width.equalTo(12) } } } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func layoutSubviews() { super.layoutSubviews() } //MARK: - ZSForumToolBarDelegate func toolBar(toolBar: ZSForumToolBar, clickLikeButton: UIButton) { } func toolBar(toolBar: ZSForumToolBar, clickShareButton: UIButton) { } func toolBar(toolBar: ZSForumToolBar, clickMoreButton: UIButton) { } } //let str = "(-@y@) [扶眼镜]转眼间,2019已过半,年初制定的旅行计划都如期进行了吗?如果你错过了上半年的春风与花海,那一定要抓住夏日的阳光明媚和碧海蓝天,可以去凉爽的地方避暑,或是去炎热的地方酣畅淋漓。那么,你心目中最理想的夏日旅行城市是哪里,一起来互相安利吧~\n{{type:image,url:http%3A%2F%2Fstatics.zhuishushenqi.com%2Fpost%2F156289728628679,size:200-200}}\n◇活动时间:\n即日起~7月14日(周日)23:59截止\n(活动奖励在活动结束后七个工作日内发放)\n\n◇参与方式①:\n❀活动期间,在『社区』-『综合讨论区』发贴:\n标题中需含有:#旅行#+______(你的自定义标题)\n帖子中内容需符合主题~\n\nA.一等奖:追书券5000点(2人)\n要求:综合排名+官方管理员判定\nB.二等奖:追书券2000点(10人)\n要求:帖子点赞数+评论数-最多的前10位\nC.原创参与奖1——追书券1200点\n要求:只要参加+帖子原创内容正文(去水)至少300字\nD.原创参与奖2——追书券800点\n要求:只要参加+帖子原创内容正文(去水)至少100字\n\n◆敲黑板:原创哦~非原创是木有奖励的哦~被举报了会记上小本本的哦~\n◆在某一个评判要求获得数相同情况下,会根据综合因素进行官方判定。\n◆内容健康积极向上,不发布恶意调侃、引战、偏激等内容。\n\n◇参与方式②:直接回复本帖完成填空\n#旅行#+_________(和本帖话题相关内容)\n◆活动结束后,随机抽选奖励666追书\n【本栏目介绍】#7日挑战#\n——每周三,会从【近期版权书单】/【近期搜索较热】中挑选男女频各8~10本小说(记得加入书架)\n以周二23:59为截止点,收集大家的挑战结果!\n@追书白小妹会在每周四公布优秀挑战结果者的书评!挑战奖励会在每周四18点前发,名单奖励见我们家小妹的帖子!(参与活动的小伙伴记得收藏本帖)\nㄟ( ▔, ▔ )ㄏ\n【挑战难度 】★★★☆☆\n男频12本,女频12本,共计24本\n【参与方式】\n《挑战结果可含》\n☑ 7天内读了哪几本小说\n☑ 【必须】 用读后感证明自己读了该小说(并说明阅读进度,比如读了第22章后就……)\n☑ 可尽情吐槽/打分觉得该本小说是毒草、粮草还是仙草~\n☑ 以上挑战结果可直接在『社区』-『综合讨论区』发布tag标题#7日挑战#的话题\n(评论不算哦)\n==========\n【男频】\n[[book:5cefa8ed56c75f15e0b9ca03 《阴媒》【连载】作者:秋刀]]\n[[book:5ae6f10def182f2278eb8ca3 《踏天神王》【连载】作者:圆脸猫]]\n[[book:5b9245105d31bf29abaeeb66 《贴身军医》【连载】作者:山不转]]\n[[book:5b92451170b03a2a67179db6 《龙武九天》【连载】作者:绿柠茶]]\n[[book:5abcba073ee22f2527a102bc 《我是神界监狱长》【连载】作者:玄武]]\n[[book:5b49a237169b24797649e187 《剑道之神》【连载】作者:一道惊鸿]]\n[[book:5b92450b68d76629b04b7f25 《我在异界当神壕》【连载】作者:芝士就是力量]]\n[[book:5b00da54ca29bda54d84872f 《霸道大帝》【连载】作者:王者荣耀]]\n[[book:5be29290ff992d28351a3f77 《我的高冷女总裁》【连载】作者:疯狂的豆芽]]\n[[book:5be29291f5215e27fb234f85 《我有一双透视神瞳》【连载】作者:霸王说唱]]\n[[book:54181ca6e8b62bce29f70457 《无限异能:超禁忌游戏》【完结】作者:宁航一]]\n[[book:598c25aa9b72d5774b20be30 《长安十二时辰全集》【完结】作者:马伯庸]]\n\n---------------\n【女频】\n[[book:56225938a27063ef471e4d6f 《何所冬暖,何所夏凉》【完结】作者:顾西爵]]\n[[book:521b20186adaee123f01d36d 《一路繁花相送》【完结】作者:青衫落拓]]\n[[book:5c3db9457235b95cac24ba93 《豪门嗜宠:霆少的小甜妻》【完结】作者:落眠]]\n[[book:5c3db9457235b95cac24ba94 《快穿之职业打脸玩家》【完结】作者:尧小台]]\n[[book:5c3db928baab695c3f744a7c 《狼性总裁不可以》【完结】作者:棠溪]]\n[[book:5be29291ff992d28351a3f83 《邪肆太子妃》【连载】作者:梅果子]]\n[[book:5be2928e9074f627c600dab5 《契约首席:沈少宠上瘾》【连载】作者:香蕉雪糕]]\n[[book:5be2928d34d3702800b1de45 《迷人娇妻》【连载】作者:十夜]]\n[[book:5b9245070d939f2a2d21cd25 《说散就散》【连载】作者:多士喵]]\n[[book:55f361ca08d07d3220e1dd5f 《盛世狂妃:傻女惊华》【连载】作者:安懒]]\n[[book:582d31f60e607e2973999768 《绝色兽妃:冷狂嫡女逆天下》【连载】作者:流间月]]\n[[book:5be29292f5215e27fb2351a8 《妖孽来袭:逆天小凰妻》【连载】作者:水鱼摇]]\n\n---------------\n【范围】\n仅限 以上红字书籍传送门的小说\n\n【奖励】\n01.符合发帖要求参与(去水)至少100字,获得1000追书券\n02.符合发帖要求参与,在每周四官方V@追书白小妹公布的优秀挑战结果者,获得3000追书券\n记得标题前缀加#7日挑战#\n03.发布在综合区的试读结果会根据试读总情况进行综合判定为1个结果\n04.试读内容同步书评可获得额外奖励,按书评内容质量获得随机额度的书券(单期可累积)\n(评论不算哦)\n\n=======\n《评论奖励》\n本帖回复#追书#+_____(书名)推荐一本人物角色名字好玩的的小说,谢谢~\n\n* 活动结束后随机抽选奖励100书券\n* 本帖互动问题会从新版书荒互助区摘选[[post:5aa887f2fbf8675f7b9f3329 【点击红字了解详情】]]\n\n---------------\n❤觉得不错的小说记得加入书架和评分哦!\n❤看到不错的书评可以点【有用】鼓励下哦!\n-----看完后欢迎留下亲你的书评(✪ω✪)\n-----爱试读喜欢写书评的小伙伴,请加QQ群《497136897》" //let parser = MarkupParser() //// parser.parseMarkup(text) //parser.parseContent(str) //ctView = ZSDisplayView(frame: CGRect(x: 0, y: 0, width: self.view.bounds.width, height: self.view.bounds.height)) //scrollView.addSubview(ctView) //ctView.buildContent(attr: parser.attrString, andImages: parser.coreData!.images) //scrollView.contentSize = CGSize(width: self.view.bounds.width, height: parser.coreData!.height) ================================================ FILE: zhuishushenqi/NewVersion/Comment/ZSForumPageTitleHeaderView.swift ================================================ // // ZSForumPageTitleHeaderView.swift // zhuishushenqi // // Created by yung on 2019/8/16. // Copyright © 2019 QS. All rights reserved. // import UIKit class ZSForumPageTitleHeaderView: UITableViewHeaderFooterView { lazy var titleLabel:UILabel = { let label = UILabel(frame: .zero) label.textAlignment = .left label.font = UIFont.systemFont(ofSize: 17) label.textColor = UIColor.black return label }() lazy var totalLabel:UILabel = { let label = UILabel(frame: .zero) label.textAlignment = .right label.font = UIFont.systemFont(ofSize: 13) label.textColor = UIColor.gray return label }() override init(reuseIdentifier: String?) { super.init(reuseIdentifier: reuseIdentifier) contentView.addSubview(titleLabel) contentView.addSubview(totalLabel) titleLabel.snp.makeConstraints { (make) in make.left.equalTo(20) make.right.equalTo(-20) make.top.bottom.equalToSuperview() } totalLabel.snp.makeConstraints { (make) in make.right.equalTo(self).offset(-20) make.top.bottom.equalToSuperview() } } override func prepareForReuse() { titleLabel.text = nil totalLabel.text = nil } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } } ================================================ FILE: zhuishushenqi/NewVersion/Comment/ZSForumPageViewController.swift ================================================ // // ZSForumViewController.swift // zhuishushenqi // // Created by yung on 2019/8/6. // Copyright © 2019 QS. All rights reserved. // import UIKit import MJRefresh class ZSForumPageViewController: BaseViewController { fileprivate lazy var tableView:UITableView = { let tableView = UITableView(frame: CGRect.zero, style: .grouped) tableView.dataSource = self tableView.delegate = self tableView.sectionHeaderHeight = UITableView.automaticDimension tableView.sectionFooterHeight = UITableView.automaticDimension tableView.estimatedSectionHeaderHeight = 100 tableView.estimatedSectionFooterHeight = 100 tableView.estimatedRowHeight = 180 tableView.rowHeight = UITableView.automaticDimension tableView.backgroundColor = UIColor.white tableView.qs_registerCellClass(ZSForumPageCell.self) tableView.qs_registerHeaderFooterClass(ZSForumPageHeaderView.self) tableView.qs_registerHeaderFooterClass(ZSForumPageFooterView.self) tableView.qs_registerHeaderFooterClass(ZSForumPageTitleHeaderView.self) return tableView }() private lazy var textView:ZSForumTextView = { let textView = ZSForumTextView(frame: .zero) return textView }() var viewModel:ZSForumViewModel = ZSForumViewModel() override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. observe() let mj_header = ZSRefreshTextHeader(refreshingTarget: self, refreshingAction: #selector(refreshAction)) mj_header?.endRefreshingCompletionBlock = { [weak mj_header] in mj_header?.changeText() } tableView.mj_header = mj_header mj_header?.beginRefreshing() let mj_footer = MJRefreshAutoStateFooter(refreshingTarget: self, refreshingAction: #selector(loadAction)) mj_footer?.isAutomaticallyRefresh = false tableView.mj_footer = mj_footer } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) view.addSubview(textView) view.addSubview(tableView) textView.snp.remakeConstraints { (make) in make.bottom.left.right.equalToSuperview() make.height.equalTo(52) } tableView.snp.remakeConstraints { [unowned self](make) in make.bottom.equalTo(self.textView.snp.top) make.left.right.top.equalToSuperview() } navigationController?.isNavigationBarHidden = false } private func observe() { self.viewModel.reloadBlock = { [weak self] in DispatchQueue.main.async { [unowned self] in self?.tableView.mj_header.endRefreshing() self?.tableView.reloadData() if self?.viewModel.noMoreData == true { self?.tableView.mj_footer.state = MJRefreshState.noMoreData } } } viewModel.request() } @objc func refreshAction() { viewModel.request() } @objc func loadAction() { viewModel.requestMore() } } extension ZSForumPageViewController:UITableViewDataSource , UITableViewDelegate { func numberOfSections(in tableView: UITableView) -> Int { return viewModel.numberOfSections() } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return viewModel.numberOfRowsInSection(section: section) } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.qs_dequeueReusableCell(ZSForumPageCell.self) cell?.selectionStyle = .none cell?.configure(model: viewModel.cellModel(for: indexPath)) return cell! } func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { if section == 0 { let headerView = tableView.qs_dequeueReusableHeaderFooterView(ZSForumPageHeaderView.self) headerView?.configure(review: viewModel.review) return headerView } else if section == 1 { if viewModel.haveBest() { let headerView = tableView.qs_dequeueReusableHeaderFooterView(ZSForumPageTitleHeaderView.self) headerView?.titleLabel.text = "仰望神评论" return headerView } else if viewModel.haveNormal() { let headerView = tableView.qs_dequeueReusableHeaderFooterView(ZSForumPageTitleHeaderView.self) headerView?.titleLabel.text = "最新评论" return headerView } } else if section == 2 { let headerView = tableView.qs_dequeueReusableHeaderFooterView(ZSForumPageTitleHeaderView.self) headerView?.titleLabel.text = "最新评论" headerView?.totalLabel.text = "共\(viewModel.review?.commentCount ?? 0)条评论" return headerView } return nil } func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? { if viewModel.haveFooter() { let footerView = tableView.qs_dequeueReusableHeaderFooterView(ZSForumPageFooterView.self) return footerView } return nil } func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { if section == 0 { return UITableView.automaticDimension } else if viewModel.haveBest() { return 50 } else if viewModel.haveNormal() { return 50 } return 0.01 } } ================================================ FILE: zhuishushenqi/NewVersion/Comment/ZSForumTextView.swift ================================================ // // ZSForumTextView.swift // zhuishushenqi // // Created by yung on 2019/8/19. // Copyright © 2019 QS. All rights reserved. // import UIKit class ZSForumTextView: UIView, UITextFieldDelegate { private lazy var topLine:UIView = { let view = UIView(frame: .zero) view.backgroundColor = UIColor(red: 0.72, green: 0.72, blue: 0.74, alpha: 1.0) return view }() private lazy var imageView:UIImageView = { let imageView = UIImageView(frame: .zero) imageView.image = UIImage(named: "chapter_review_send_tip_24x24_") imageView.contentMode = .scaleAspectFit return imageView }() private lazy var textView:UITextField = { let view = UITextField(frame: .zero) view.font = UIFont.systemFont(ofSize: 11) view.placeholder = "请填写您的评论" return view }() private lazy var sendButton:UIButton = { let view = UIButton(type: .custom) view.setTitle("发送", for: .normal) view.setTitleColor(UIColor.white, for: .normal) view.titleLabel?.font = UIFont.systemFont(ofSize: 13) view.backgroundColor = UIColor.red view.layer.cornerRadius = 15 view.layer.masksToBounds = true view.isEnabled = false return view }() override init(frame: CGRect) { super.init(frame: frame) backgroundColor = UIColor(red: 0.99, green: 0.99, blue: 1, alpha: 1) addSubview(topLine) addSubview(imageView) addSubview(textView) addSubview(sendButton) topLine.snp.makeConstraints { (make) in make.left.right.top.equalToSuperview() make.height.equalTo(0.5) } imageView.snp.makeConstraints { (make) in make.width.height.equalTo(26) make.left.top.equalTo(13) } sendButton.snp.makeConstraints { (make) in make.height.equalTo(28) make.width.equalTo(60) make.right.equalToSuperview().offset(-15) make.centerY.equalToSuperview() } textView.snp.makeConstraints { [unowned self](make) in make.left.equalTo(self.imageView.snp_right).offset(13) make.right.equalTo(self.sendButton.snp_left).offset(-13) make.top.equalToSuperview().offset(10) make.bottom.equalToSuperview().offset(-10) } } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } //MARK: - UITextFieldDelegate func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { return true } } ================================================ FILE: zhuishushenqi/NewVersion/Comment/ZSForumToolBar.swift ================================================ // // ZSForumToolBar.swift // zhuishushenqi // // Created by yung on 2019/8/12. // Copyright © 2019 QS. All rights reserved. // import UIKit protocol ZSForumToolBarDelegate:class { func toolBar(toolBar:ZSForumToolBar, clickLikeButton:UIButton) func toolBar(toolBar:ZSForumToolBar, clickShareButton:UIButton) func toolBar(toolBar:ZSForumToolBar, clickMoreButton:UIButton) } class ZSForumToolBar: UIView { weak var delegate:ZSForumToolBarDelegate? lazy var likeButton:UIButton = { let button = UIButton(type: .custom) button.setImage(UIImage(named: "bbs_icon_like02_26_26"), for: .normal) button.addTarget(self, action: #selector(likeAction(btn:)), for: .touchUpInside) return button }() lazy var shareButton:UIButton = { let button = UIButton(type: .custom) button.setImage(UIImage(named: "bbs_icon_share_26_26_26x26_"), for: .normal) button.addTarget(self, action: #selector(shareAction(btn:)), for: .touchUpInside) return button }() lazy var moreButton:UIButton = { let button = UIButton(type: .custom) button.setImage(UIImage(named: "bbs_icon_more_big_26_26"), for: .normal) button.addTarget(self, action: #selector(moreAction(btn:)), for: .touchUpInside) return button }() override init(frame: CGRect) { super.init(frame: frame) addSubview(likeButton) addSubview(shareButton) addSubview(moreButton) likeButton.snp.makeConstraints { (make) in make.left.top.bottom.equalToSuperview() make.width.equalTo(52) make.height.equalTo(52) } shareButton.snp.makeConstraints { (make) in make.top.bottom.equalToSuperview() make.width.equalTo(52) make.height.equalTo(52) make.centerX.equalToSuperview() } moreButton.snp.makeConstraints { (make) in make.right.top.bottom.equalToSuperview() make.width.equalTo(52) make.height.equalTo(52) } } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func layoutSubviews() { super.layoutSubviews() } @objc private func likeAction(btn:UIButton) { delegate?.toolBar(toolBar: self, clickLikeButton: btn) } @objc private func shareAction(btn:UIButton) { delegate?.toolBar(toolBar: self, clickShareButton: btn) } @objc private func moreAction(btn:UIButton) { delegate?.toolBar(toolBar: self, clickMoreButton: btn) } } ================================================ FILE: zhuishushenqi/NewVersion/Comment/ZSForumViewModel.swift ================================================ // // ZSForumViewModel.swift // zhuishushenqi // // Created by yung on 2019/8/6. // Copyright © 2019 QS. All rights reserved. // import UIKit import ZSAPI class ZSForumViewModel { var viewDidLoad: ()->() = {} var reloadBlock: ()->() = {} var id:String = "" var review:ZSPostReview? var bestComment:[ZSForumComment] = [] var comments:[ZSForumComment] = [] var start:Int = 0 var limit:Int = 0 var noMoreData:Bool = false init() { viewDidLoad = { [weak self] in self?.request() } } func haveFooter() ->Bool { return bestComment.count == 0 && comments.count == 0 } func haveBest() ->Bool { return bestComment.count != 0 } func haveNormal() ->Bool { return comments.count != 0 } func numberOfSections() ->Int { var sections = 1 if bestComment.count > 0 { sections += 1 } if comments.count > 0 { sections += 1 } return sections } func numberOfRowsInSection(section:Int) ->Int { var rows = 0 if section == 1 { if bestComment.count > 0 { rows = bestComment.count } else if comments.count > 0 { rows = comments.count } } else if section == 2 { rows = comments.count } return rows } func cellModel(for indexPath:IndexPath) -> ZSForumComment{ var model:ZSForumComment = ZSForumComment() if indexPath.section == 1 { if bestComment.count > 0 { model = bestComment[indexPath.row] } else if comments.count > 0 { model = comments[indexPath.row] } } else { if comments.count > 0 { model = comments[indexPath.row] } } return model } //MARK: - request func request() { requestDetail { [weak self] in self?.reloadBlock() } requestNormal { [weak self] in self?.reloadBlock() } requestBest { [weak self] in self?.reloadBlock() } } func requestMore() { requestMore { [weak self](haveMore) in self?.noMoreData = true self?.reloadBlock() } } private func requestDetail(completion:@escaping()->Void) { let api = ZSAPI.post(key: id) zs_get(api.path) { [weak self](json) in guard let review = json?["post"] as? [String:Any] else { completion() return } guard let postView = ZSPostReview.deserialize(from: review) else { completion() return } self?.review = postView completion() } } private func requestNormal(completion:@escaping()->Void) { start = 0 limit = 50 requestMore { _ in completion() } } private func requestMore(completion:@escaping(_ haveMore:Bool)->Void) { if comments.count % 50 != 0 { completion(false) return } start += 50 let api = ZSAPI.hotPost(key: id, start: "\(start)", limit: "\(limit)") zs_get(api.path, parameters: api.parameters) { [weak self](json) in guard let comments = json?["comments"] as? [[String:Any]] else { completion(true) return } guard let commentModels = [ZSForumComment].deserialize(from: comments) as? [ZSForumComment] else { completion(true) return } self?.comments = commentModels completion(true) } } private func requestBest(completion:@escaping()->Void) { let api = ZSAPI.hotComment(key: id) zs_get(api.path) { [weak self](json) in guard let comments = json?["comments"] as? [[String:Any]] else { completion() return } guard let commentModels = [ZSForumComment].deserialize(from: comments) as? [ZSForumComment] else { completion() return } self?.bestComment = commentModels completion() } } } ================================================ FILE: zhuishushenqi/NewVersion/Comment/ZSPostReview.swift ================================================ // // ZSPostReview.swift // zhuishushenqi // // Created by yung on 2019/8/7. // Copyright © 2019 QS. All rights reserved. // import UIKit import HandyJSON struct ZSPostReview:HandyJSON { var _id:String = "" var rating:Int = 0 var type:String = "" var book:ZSPostReviewBook = ZSPostReviewBook() var author:ZSPostReviewAuthor = ZSPostReviewAuthor() var helpful:ZSPostReviewHelpful = ZSPostReviewHelpful(no: 0, total: 0, yes: 0) var state:String = "" var updated:String = "" var created:String = "" var commentCount:Int = 0 var content:String = "" var title:String = "" var shareLink:String = "" var id:String = "" } //{ // "review": { // "_id": "5d47f6c109fdcf734ec03ec3", // "rating": 3, // "type": "review", // "book": { // "_id": "58c8b41a08eed7c070137872", // "author": "薄凉君子", // "title": "独步惊华,腹黑嫡女御天下", // "cover": "/agent/http%3A%2F%2Fimg.1391.com%2Fapi%2Fv1%2Fbookcenter%2Fcover%2F1%2F2066474%2F2066474_c1b2752e5649449c9a341556542dbfce.jpg%2F", // "safelevel": 5, // "allowFree": true, // "apptype": [0, 1, 2, 4, 6], // "id": "58c8b41a08eed7c070137872", // "latelyFollower": null, // "retentionRatio": null // }, // "author": { // "_id": "5cfa56b67a823add1cf3e377", // "avatar": "/avatar/cf/46/cf4682dccd69c381ce4ebf4abbb44f5f", // "nickname": "晨", // "activityAvatar": "", // "type": "normal", // "lv": 5, // "gender": "female", // "rank": null, // "created": "2019-06-07T12:21:10.000Z", // "id": "5cfa56b67a823add1cf3e377" // }, // "helpful": { // "total": -1, // "no": 2, // "yes": 1 // }, // "state": "normal", // "updated": "2019-08-06T13:46:47.643Z", // "created": "2019-08-05T09:28:33.410Z", // "commentCount": 0, // "content": "故事情节不错,作者用词要斟酌下了,看了一点就明显,秀色可餐的食物,秀色可餐是用来形容好看的人。男主看着女主,想让女主放下伪装。。。前几个词都不错,但醉生梦死。。这真的适合形容他俩吗? \n 😂", // "title": "加油加油", // "shareLink": "http://share.zhuishushenqi.com/post/5d47f6c109fdcf734ec03ec3", // "id": "5d47f6c109fdcf734ec03ec3" // }, // "ok": true //} ================================================ FILE: zhuishushenqi/NewVersion/Comment/ZSPostReviewAuthor.swift ================================================ // // Author.swift // // Create by 农运 on 7/8/2019 // Copyright © 2019. All rights reserved. // 模型生成器(小波汉化)JSONExport: https://github.com/Ahmed-Ali/JSONExport import Foundation import HandyJSON struct ZSPostReviewAuthor:HandyJSON{ var id : String = "" var activityAvatar : String = "" var avatar : String = "" var created : String = "" var gender : String = "" var lv : Int = 0 var nickname : String = "" var rank : AnyObject? var type : UserType = .none init() {} } ================================================ FILE: zhuishushenqi/NewVersion/Comment/ZSPostReviewBook.swift ================================================ // // Book.swift // // Create by 农运 on 7/8/2019 // Copyright © 2019. All rights reserved. // 模型生成器(小波汉化)JSONExport: https://github.com/Ahmed-Ali/JSONExport import Foundation import HandyJSON struct ZSPostReviewBook :HandyJSON{ var id : String = "" var allowFree : Bool = false var apptype : [Int] = [] var author : String = "" var cover : String = "" var latelyFollower : AnyObject? var retentionRatio : AnyObject? var safelevel : Int = 0 var title : String = "" } ================================================ FILE: zhuishushenqi/NewVersion/Comment/ZSPostReviewHelpful.swift ================================================ // // Helpful.swift // // Create by 农运 on 7/8/2019 // Copyright © 2019. All rights reserved. // 模型生成器(小波汉化)JSONExport: https://github.com/Ahmed-Ali/JSONExport import Foundation import HandyJSON struct ZSPostReviewHelpful:HandyJSON { var no : Int = 0 var total : Int = 0 var yes : Int = 0 } ================================================ FILE: zhuishushenqi/NewVersion/Community/ZSCommunityCell.swift ================================================ // // ZSCommunityCell.swift // zhuishushenqi // // Created by caony on 2019/7/2. // Copyright © 2019 QS. All rights reserved. // import UIKit import HandyJSON enum UserType: String,HandyJSONEnum { case none = "" case normal = "normal" case official = "official" case commentator = "commentator" case moderator = "moderator" case doyen = "doyen" case author = "author" var image:UIImage? { switch self { case .official: return UIImage(named: "f_official_icon_12x12_") case .commentator: return UIImage(named: "f_commentator_icon_14x14_") case .moderator: return UIImage(named: "f_moderator_icon_14x14_") case .author: return UIImage(named: "f_author_icon_14x14_") case .doyen: return UIImage(named: "f_doyen_icon_12x12_") default: return nil } } } protocol ZSCommunityCellDelegate:class { func community(cell:ZSCommunityCell, clickIcon:UIButton) func community(cell:ZSCommunityCell, clickFocus:UIButton) func community(cell:ZSCommunityCell, clickMsg:UIButton) func community(cell:ZSCommunityCell, clickShare:UIButton) } class ZSCommunityCell: UITableViewCell { lazy var iconButton:UIButton = { let button = UIButton(type: .custom) button.addTarget(self, action: #selector(iconAction(btn:)), for: .touchUpInside) return button }() lazy var nickNameLabel:UILabel = { let label = UILabel(frame: .zero) label.textColor = UIColor.init(red: 0.38, green: 0.38, blue: 0.4, alpha: 1) label.font = UIFont.systemFont(ofSize: 15) return label }() lazy var tagView:UIImageView = { let imageView = UIImageView(image: UIImage(named: "")) return imageView }() lazy var levelLabel:UILabel = { let label = UILabel(frame: .zero) label.textColor = UIColor.init(red: 0.64, green: 0.64, blue: 0.64, alpha: 1) label.font = UIFont.systemFont(ofSize: 9) label.textAlignment = .center label.layer.cornerRadius = 6.5 label.layer.borderColor = UIColor.darkGray.cgColor label.layer.borderWidth = 0.5 return label }() lazy var timeLabel:UILabel = { let label = UILabel(frame: .zero) label.textColor = UIColor.init(red: 0.54, green: 0.54, blue: 0.56, alpha: 1) label.font = UIFont.systemFont(ofSize: 12) return label }() lazy var forumImageView:UIImageView = { let imageView = UIImageView(frame: .zero) imageView.image = UIImage(named: "forum_image_15x13_") return imageView }() lazy var titleLabel:UILabel = { let label = UILabel(frame: .zero) label.textColor = UIColor.init(red: 0.2, green: 0.2, blue: 0.2, alpha: 1) label.font = UIFont.systemFont(ofSize: 17) return label }() lazy var contentLabel:UILabel = { let label = UILabel(frame: .zero) label.textColor = UIColor.init(red: 0.54, green: 0.54, blue: 0.56, alpha: 1) label.font = UIFont.systemFont(ofSize: 14) label.numberOfLines = 4 return label }() lazy var insertedScoreView:ZSInsertedBookScoreView = { let scoreView = ZSInsertedBookScoreView(frame: .zero) return scoreView }() lazy var msgButton:UIButton = { let button = UIButton(type: .custom) button.setImage(UIImage(named: "bbs_icon_topic_26_26_26x26_"), for: .normal) button.addTarget(self, action: #selector(commentAction(btn:)), for: .touchUpInside) return button }() lazy var shareButton:UIButton = { let button = UIButton(type: .custom) button.setImage(UIImage(named: "bbs_icon_share_26_26_26x26_"), for: .normal) button.addTarget(self, action: #selector(shareAction(btn:)), for: .touchUpInside) return button }() lazy var msgLabel:UILabel = { let button = UILabel(frame: .zero) button.textColor = UIColor.init(red: 0.72, green: 0.72, blue: 0.74, alpha: 1) button.font = UIFont.systemFont(ofSize: 12) return button }() lazy var shareLabel:UILabel = { let button = UILabel(frame: .zero) button.textColor = UIColor.init(red: 0.72, green: 0.72, blue: 0.74, alpha: 1) button.font = UIFont.systemFont(ofSize: 12) return button }() lazy var focusButton:UIButton = { let button = UIButton(type: .custom) button.layer.cornerRadius = 14 button.layer.masksToBounds = true button.backgroundColor = UIColor.init(red: 0.93, green: 0.28, blue: 0.27, alpha: 1) button.setTitle("关注", for: .normal) button.titleLabel?.font = UIFont.systemFont(ofSize: 15) button.addTarget(self, action: #selector(focusAction(btn:)), for: .touchUpInside) return button }() var model:QSHotModel? var tweet:ZSDynamicTweet? var focusState:Bool = false { didSet { focusState ? focusSelected():focusUnSelected() } } weak var delegate:ZSCommunityCellDelegate? override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) contentView.addSubview(iconButton) contentView.addSubview(nickNameLabel) contentView.addSubview(tagView) contentView.addSubview(levelLabel) contentView.addSubview(timeLabel) contentView.addSubview(forumImageView) contentView.addSubview(titleLabel) contentView.addSubview(contentLabel) contentView.addSubview(insertedScoreView) contentView.addSubview(msgButton) contentView.addSubview(shareButton) contentView.addSubview(msgLabel) contentView.addSubview(shareLabel) contentView.addSubview(focusButton) contentView.layer.masksToBounds = true } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func awakeFromNib() { super.awakeFromNib() // Initialization code } override func setSelected(_ selected: Bool, animated: Bool) { super.setSelected(selected, animated: animated) // Configure the view for the selected state } override func layoutSubviews() { super.layoutSubviews() iconButton.frame = CGRect(x: 15, y: 25, width: 35, height: 35) let nickNameWidth = nickNameLabel.sizeThatFits(CGSize(width: CGFloat.greatestFiniteMagnitude, height: 18)).width nickNameLabel.frame = CGRect(x: 58, y: 23, width: nickNameWidth, height: 18) var tagViewWidth:CGFloat = 0 if let _ = tagView.image { tagViewWidth = 12 } tagView.frame = CGRect(x: nickNameLabel.frame.maxX + 2, y: 26, width: tagViewWidth, height: 12) let levelWith = levelLabel.sizeThatFits(CGSize(width: CGFloat.greatestFiniteMagnitude, height: 13)).width levelLabel.frame = CGRect(x: tagView.frame.maxX + 2, y: 26, width: levelWith * 2, height: 13) timeLabel.frame = CGRect(x: 58, y: 45, width: 100, height: 13) forumImageView.frame = CGRect(x: 15, y: 84, width: 19, height: 16) titleLabel.frame = CGRect(x: 15, y: 80, width: bounds.width - 30, height: 25) contentLabel.frame = CGRect(x: 15, y: 118, width: bounds.width - 30, height: 79) insertedScoreView.frame = CGRect(x: 15, y: 205, width: bounds.width - 30, height: 125) insertedScoreView.height = (model?.tweet.book == nil || model?.tweet.book?.title.count == 0) ? 0:125 msgLabel.frame = CGRect(x: bounds.width/2 - 40 - 17.5, y: insertedScoreView.frame.maxY + 19, width: 40, height: 17) msgButton.frame = CGRect(x: msgLabel.frame.minX - 26 - 10, y: insertedScoreView.frame.maxY + 14, width: 26, height: 26) shareButton.frame = CGRect(x: bounds.width/2 + 17.5, y: insertedScoreView.frame.maxY + 14, width: 26, height: 26) shareLabel.frame = CGRect(x: shareButton.frame.maxX + 10, y: insertedScoreView.frame.maxY + 19, width: 40, height: 17) focusButton.frame = CGRect(x: bounds.width - 75, y: 30, width: 60, height: 28) self.iconButton.imageView?.qs_addCornerRadius(cornerRadius: 17.5) } func congfigure(model:QSHotModel) { self.model = model focusState = false for item in ZSLogin.share.followings ?? [] { if item._id == model.user._id { focusState = true } } self.iconButton.qs_setAvatarWithURLString(urlString: model.user.avatar) self.nickNameLabel.text = "\(model.user.nickname)" self.tagView.image = model.user.type.image self.levelLabel.text = "lv.\(model.user.lv)" self.timeLabel.qs_setCreateTime(createTime: "\(model.tweet.created)", append: "") self.forumImageView.isHidden = !model.tweet.haveImage let titlePrefixString = self.forumImageView.isHidden ? "":"\u{fff9} " self.titleLabel.text = "\(titlePrefixString)\(model.tweet.title)" self.contentLabel.text = "\(model.tweet.content)" self.insertedScoreView.configure(model: model.tweet.book, rate:model.tweet.score) self.msgLabel.text = "\(model.tweet.commented)" self.shareLabel.text = "\(model.tweet.retweeted)" setNeedsLayout() layoutIfNeeded() } func congfigure(mo:ZSDynamicTweet) { self.tweet = mo self.focusButton.isHidden = true self.iconButton.qs_setAvatarWithURLString(urlString: mo.user?.avatar ?? "") self.nickNameLabel.text = "\(mo.user?.nickname ?? "")" self.tagView.image = mo.user?.type.image self.levelLabel.text = "lv.\(mo.user?.lv ?? 0)" self.timeLabel.qs_setCreateTime(createTime: "\(mo.created)", append: "") self.forumImageView.isHidden = !mo.haveImage let titlePrefixString = self.forumImageView.isHidden ? "":"\u{fff9} " if mo.type == .retweet { self.titleLabel.text = "\(titlePrefixString)\(mo.refTweet?.title ?? "")" self.contentLabel.text = "\(mo.refTweet?.content ?? "")" } else { self.titleLabel.text = "\(titlePrefixString)\(mo.title)" self.contentLabel.text = "\(mo.content)" } self.insertedScoreView.configure(mo: mo.book, rate:mo.score) self.msgLabel.text = "\(mo.commented)" self.shareLabel.text = "\(mo.retweeted)" setNeedsLayout() layoutIfNeeded() } private func focusSelected() { self.focusButton.setTitle("已关注", for: .normal) self.focusButton.setTitleColor(UIColor(red: 0.94, green: 0.94, blue: 0.95, alpha: 1), for: .normal) self.focusButton.backgroundColor = UIColor(red: 0.75, green: 0.75, blue: 0.76, alpha: 1) } private func focusUnSelected() { self.focusButton.setTitle("关注", for: .normal) self.focusButton.setTitleColor(UIColor.white, for: .normal) self.focusButton.backgroundColor = UIColor.init(red: 0.93, green: 0.28, blue: 0.27, alpha: 1) } @objc private func iconAction(btn:UIButton) { delegate?.community(cell: self, clickIcon: btn) } @objc private func commentAction(btn:UIButton) { delegate?.community(cell: self, clickMsg: btn) } @objc private func focusAction(btn:UIButton) { delegate?.community(cell: self, clickFocus: btn) } @objc private func shareAction(btn:UIButton) { delegate?.community(cell: self, clickShare: btn) } } ================================================ FILE: zhuishushenqi/NewVersion/Community/ZSCommunityHot.swift ================================================ // // ZSCommunityHot.swift // zhuishushenqi // // Created by yung on 2022/1/6. // Copyright © 2022 QS. All rights reserved. // import Foundation import HandyJSON struct ZSCommunityHot : HandyJSON { var _id : String = "" var author : ZSCommunityAuthor! var authorId : String = "" var block : String = "" var book : ZSCommunityBook! var bookCount : Int = 0 var bookRecommend : Int = 0 var commentCount : Int! var content : String = "" var created : String = "" var dataId : String = "" var from : String = "" var isLike : Bool = false var likeCount : Int = 0 var readCount : Int = 0 var title : String = "" var updated : String = "" } struct ZSCommunityBook : HandyJSON { } struct ZSCommunityAuthor : HandyJSON { var _id : String = "" var avatar : String = "" var follower : Int = 0 var following : Int = 0 var gender : String = "" var isFollowing : Bool = false var lv : Int = 0 var mobile : Int = 0 var nickname : String = "" var type : String = "" } ================================================ FILE: zhuishushenqi/NewVersion/Community/ZSCommunityHotCell.swift ================================================ // // ZSCommunityHotCell.swift // zhuishushenqi // // Created by yung on 2022/1/6. // Copyright © 2022 QS. All rights reserved. // import UIKit protocol ZSCommunityHotCellDelegate:AnyObject { func community(cell:ZSCommunityHotCell, clickIcon:UIButton) func community(cell:ZSCommunityHotCell, clickFocus:UIButton) func community(cell:ZSCommunityHotCell, clickMsg:UIButton) func community(cell:ZSCommunityHotCell, clickShare:UIButton) } class ZSCommunityHotCell: UITableViewCell { lazy var iconButton:UIButton = { let button = UIButton(type: .custom) button.addTarget(self, action: #selector(iconAction(btn:)), for: .touchUpInside) return button }() lazy var nickNameLabel:UILabel = { let label = UILabel(frame: .zero) label.textColor = UIColor.init(red: 0.38, green: 0.38, blue: 0.4, alpha: 1) label.font = UIFont.systemFont(ofSize: 15) return label }() lazy var tagView:UIImageView = { let imageView = UIImageView(image: UIImage(named: "")) return imageView }() lazy var levelLabel:UILabel = { let label = UILabel(frame: .zero) label.textColor = UIColor.init(red: 0.64, green: 0.64, blue: 0.64, alpha: 1) label.font = UIFont.systemFont(ofSize: 9) label.textAlignment = .center label.layer.cornerRadius = 6.5 label.layer.borderColor = UIColor.darkGray.cgColor label.layer.borderWidth = 0.5 return label }() lazy var titleLabel:UILabel = { let label = UILabel(frame: .zero) label.textColor = UIColor.init(red: 0.2, green: 0.2, blue: 0.2, alpha: 1) label.font = UIFont.systemFont(ofSize: 17) return label }() lazy var contentLabel:ZSDisplayView = { let label = ZSDisplayView(frame: .zero) // label.textColor = UIColor.init(red: 0.54, green: 0.54, blue: 0.56, alpha: 1) // label.font = UIFont.systemFont(ofSize: 14) // label.numberOfLines = 4 return label }() lazy var timeLabel:UILabel = { let label = UILabel(frame: .zero) label.textColor = UIColor.init(red: 0.54, green: 0.54, blue: 0.56, alpha: 1) label.font = UIFont.systemFont(ofSize: 12) return label }() lazy var msgButton:UIButton = { let button = UIButton(type: .custom) button.setImage(UIImage(named: "bbs_icon_topic_26_26_26x26_"), for: .normal) button.addTarget(self, action: #selector(commentAction(btn:)), for: .touchUpInside) return button }() lazy var shareButton:UIButton = { let button = UIButton(type: .custom) button.setImage(UIImage(named: "bbs_icon_share_26_26_26x26_"), for: .normal) button.addTarget(self, action: #selector(shareAction(btn:)), for: .touchUpInside) return button }() lazy var msgLabel:UILabel = { let button = UILabel(frame: .zero) button.textColor = UIColor.init(red: 0.72, green: 0.72, blue: 0.74, alpha: 1) button.font = UIFont.systemFont(ofSize: 12) return button }() lazy var shareLabel:UILabel = { let button = UILabel(frame: .zero) button.textColor = UIColor.init(red: 0.72, green: 0.72, blue: 0.74, alpha: 1) button.font = UIFont.systemFont(ofSize: 12) return button }() lazy var focusButton:UIButton = { let button = UIButton(type: .custom) button.layer.cornerRadius = 14 button.layer.masksToBounds = true button.backgroundColor = UIColor.init(red: 0.93, green: 0.28, blue: 0.27, alpha: 1) button.setTitle("关注", for: .normal) button.titleLabel?.font = UIFont.systemFont(ofSize: 15) button.addTarget(self, action: #selector(focusAction(btn:)), for: .touchUpInside) return button }() weak var delegate:ZSCommunityHotCellDelegate? @objc private func iconAction(btn:UIButton) { delegate?.community(cell: self, clickIcon: btn) } @objc private func commentAction(btn:UIButton) { delegate?.community(cell: self, clickMsg: btn) } @objc private func focusAction(btn:UIButton) { delegate?.community(cell: self, clickFocus: btn) } @objc private func shareAction(btn:UIButton) { delegate?.community(cell: self, clickShare: btn) } func congfigure(model:ZSCommunityHot) { self.iconButton.qs_setAvatarWithURLString(urlString: model.author.avatar) self.nickNameLabel.text = "\(model.author.nickname)" // self.tagView.image = model.user.type.image self.levelLabel.text = "lv.\(model.author.lv)" self.timeLabel.qs_setCreateTime(createTime: "\(model.created)", append: "") self.titleLabel.text = "回答问题:\(model.title)" // self.contentLabel.text = "\(model.content)" let parser = MarkupParser() parser.parseContent(model.content, settings: CTSettings.shared) contentLabel.snp.updateConstraints { (make) in make.height.equalTo(parser.coreData?.height ?? 0) } contentLabel.buildContent(attr: parser.attrString, andImages: parser.coreData?.images ?? [], settings: CTSettings.shared) } override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) contentView.addSubview(iconButton) contentView.addSubview(nickNameLabel) contentView.addSubview(tagView) contentView.addSubview(levelLabel) contentView.addSubview(timeLabel) // contentView.addSubview(forumImageView) contentView.addSubview(titleLabel) contentView.addSubview(contentLabel) // contentView.addSubview(insertedScoreView) contentView.addSubview(msgButton) contentView.addSubview(shareButton) contentView.addSubview(msgLabel) contentView.addSubview(shareLabel) contentView.addSubview(focusButton) contentView.layer.masksToBounds = true iconButton.snp.makeConstraints { make in make.left.equalTo(20) make.top.equalTo(25) make.width.height.equalTo(20) } nickNameLabel.snp.makeConstraints { make in make.left.equalTo(self.iconButton.snp.right).offset(5) make.top.equalTo(25) make.height.equalTo(20) } tagView.snp.makeConstraints { make in make.left.equalTo(self.nickNameLabel.snp.right).offset(5) make.top.equalTo(30) make.height.equalTo(10) make.width.equalTo(25) } levelLabel.snp.makeConstraints { make in make.left.equalTo(self.tagView.snp.right).offset(5) make.top.equalTo(30) make.height.equalTo(10) make.width.equalTo(25) } titleLabel.snp.makeConstraints { make in make.left.equalTo(20) make.top.equalTo(self.iconButton.snp.bottom).offset(10) make.width.equalTo(self).offset(-40) make.height.equalTo(15) } contentLabel.snp.makeConstraints { make in make.left.equalTo(20) make.top.equalTo(self.titleLabel.snp.bottom).offset(5) make.width.equalTo(self).offset(-40) make.height.equalTo(30) } timeLabel.snp.makeConstraints { make in make.left.equalTo(20) make.top.equalTo(self.contentLabel.snp.bottom).offset(5) make.height.equalTo(12) } } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func awakeFromNib() { super.awakeFromNib() // Initialization code } override func setSelected(_ selected: Bool, animated: Bool) { super.setSelected(selected, animated: animated) // Configure the view for the selected state } } ================================================ FILE: zhuishushenqi/NewVersion/Community/ZSCommunityNavigationBar.swift ================================================ // // ZSCommunityNavigationBar.swift // zhuishushenqi // // Created by caony on 2019/7/2. // Copyright © 2019 QS. All rights reserved. // import UIKit protocol ZSCommunityNavigationBarDelegate:class { func navView(navView:ZSCommunityNavigationBar, didSelectRight at:Int) func navView(navView:ZSCommunityNavigationBar, didSelectLeft at:Int) } class ZSCommunityNavigationBar: UIView, ZSVoiceSegmentProtocol { lazy var titleView:ZSVoiceSegmentView = { let titleView = ZSVoiceSegmentView(frame: .zero) titleView.delegate = self titleView.backgroundColor = UIColor.clear titleView.collectionView.backgroundColor = UIColor.clear titleView.type = .bigselect return titleView }() var titles:[String] = ["热门","关注","论坛"] private var navButtons:[UIButton] = [] private var navImages:[ShelfNav] = [] weak var delegate:ZSCommunityNavigationBarDelegate? convenience init(navImages:[ShelfNav], delegate:ZSCommunityNavigationBarDelegate?) { self.init(frame: CGRect.zero) self.navImages = navImages self.delegate = delegate configureNavButtons() } override init(frame: CGRect) { super.init(frame: frame) addSubview(titleView) self.backgroundColor = UIColor.init(hexString: "#A70A0B") isUserInteractionEnabled = true } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func layoutSubviews() { super.layoutSubviews() titleView.frame = CGRect(x: 20, y: kNavgationBarHeight - 44, width: 240, height: 44) var index = navButtons.count - 1 while index >= 0 { let btn = navButtons[index] let originX = self.bounds.width - CGFloat(navButtons.count - index) * 42 - 13 btn.frame = CGRect(x: originX, y: kNavgationBarHeight - 44, width: 42, height: 42) index -= 1 } } private func configureNavButtons() { for image in navImages { let btn = UIButton(type: .custom) btn.setImage(image.image, for: .normal) btn.addTarget(self, action: #selector(navAction(btn:)), for: .touchUpInside) addSubview(btn) navButtons.append(btn) } } @objc private func navAction(btn:UIButton) { var index = 0 for button in navButtons { if button == btn { break } index += 1 } delegate?.navView(navView: self, didSelectRight: index) } //MARK: - ZSVoiceSegmentProtocol func titlesForSegment(segmentView: ZSVoiceSegmentView) -> [String] { return titles } func didSelect(segment: ZSVoiceSegmentView, at index: Int) { delegate?.navView(navView: self, didSelectLeft: index) } } ================================================ FILE: zhuishushenqi/NewVersion/Community/ZSCommunityViewController.swift ================================================ // // ZSCommunityViewController.swift // ZSCommunity // // Created by caony on 2019/6/18. // import UIKit import MJRefresh class ZSCommunityViewController: BaseViewController, ZSCommunityNavigationBarDelegate, ZSCommunityHotCellDelegate { lazy var navImages:[ShelfNav] = { var images:[ShelfNav] = [] let mine = ShelfNav(rawValue: 4) let notification = ShelfNav(rawValue: 5) if let _ = mine?.image { images.append(mine!) } if let _ = notification?.image { images.append(notification!) } return images }() lazy var navigationBar:ZSCommunityNavigationBar = { let nav = ZSCommunityNavigationBar(navImages: self.navImages, delegate: self) return nav }() lazy var tableView:UITableView = { let tableView = UITableView(frame: .zero, style: .grouped) tableView.dataSource = self tableView.delegate = self tableView.sectionHeaderHeight = 0.01 tableView.sectionFooterHeight = 0.01 if #available(iOS 11, *) { tableView.contentInsetAdjustmentBehavior = .never } tableView.qs_registerCellClass(ZSCommunityHotCell.self) let blurEffect = UIBlurEffect(style: .extraLight) let blurEffectView = UIVisualEffectView(effect: blurEffect) tableView.backgroundView = blurEffectView return tableView }() var viewModel:ZSCommunityViewModel = ZSCommunityViewModel() override func viewDidLoad() { super.viewDidLoad() observe() setupSubviews() } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) navigationController?.isNavigationBarHidden = true } override var preferredStatusBarStyle: UIStatusBarStyle { return .lightContent } func setupSubviews() { view.addSubview(navigationBar) view.addSubview(tableView) navigationBar.snp.remakeConstraints { (make) in make.left.right.top.equalToSuperview() make.height.equalTo(kNavgationBarHeight) } tableView.snp.remakeConstraints { (make) in make.left.right.equalToSuperview() make.top.equalTo(self.navigationBar.snp_bottom) make.height.equalTo(ScreenHeight - kNavgationBarHeight - FOOT_BAR_Height) } let mj_header = ZSRefreshTextHeader(refreshingTarget: self, refreshingAction: #selector(refreshAction)) mj_header?.endRefreshingCompletionBlock = { [weak mj_header] in mj_header?.changeText() } tableView.mj_header = mj_header mj_header?.beginRefreshing() let mj_footer = MJRefreshAutoStateFooter(refreshingTarget: self, refreshingAction: #selector(loadAction)) mj_footer?.isAutomaticallyRefresh = false tableView.mj_footer = mj_footer } @objc private func refreshAction() { self.viewModel.requestCommunity() self.viewModel.getCommunity { } } @objc private func loadAction() { self.viewModel.requestMore() } private func observe() { self.viewModel.reloadBlock = { DispatchQueue.main.async { self.tableView.mj_header.endRefreshing() self.tableView.mj_footer.endRefreshing() self.tableView.reloadData() } } } //MARK: - ZSCommunityNavigationBarDelegate func navView(navView: ZSCommunityNavigationBar, didSelectRight at: Int) { if at == 0 { let dynamicVC = ZSUserDynamicViewController() dynamicVC.id = ZSLogin.share.userInfo()?.user?._id ?? "" dynamicVC.type = .mine dynamicVC.hidesBottomBarWhenPushed = true navigationController?.pushViewController(dynamicVC, animated: true) } else if at == 1 { let notiVC = ZSNotificationViewController() notiVC.hidesBottomBarWhenPushed = true notiVC.type = .message navigationController?.pushViewController(notiVC, animated: true) } } func navView(navView: ZSCommunityNavigationBar, didSelectLeft at: Int) { } //MARK: - ZSCommunityCellDelegate // func community(cell: ZSCommunityCell, clickIcon: UIButton) { // guard let indexPath = tableView.indexPath(for: cell) else { // return // } // let dynamicVC = ZSUserDynamicViewController() // dynamicVC.id = viewModel.twitters[indexPath.row].user._id // dynamicVC.hidesBottomBarWhenPushed = true // navigationController?.pushViewController(dynamicVC, animated: true) // } // // func community(cell: ZSCommunityCell, clickFocus: UIButton) { // guard let indexPath = tableView.indexPath(for: cell) else { // return // } // let id = self.viewModel.twitters[indexPath.row].user._id // if cell.focusState == false { // self.viewModel.focus(id: id) { [weak cell] (result) in // cell?.focusState = result ? !cell!.focusState:cell!.focusState // } // } else { // self.viewModel.unFocus(id: id) { [weak cell] (result) in // cell?.focusState = result ? !cell!.focusState:cell!.focusState // } // } // } // // func community(cell: ZSCommunityCell, clickMsg: UIButton) { // // } // // func community(cell: ZSCommunityCell, clickShare: UIButton) { // // } func community(cell: ZSCommunityHotCell, clickIcon: UIButton) { guard let indexPath = tableView.indexPath(for: cell) else { return } let dynamicVC = ZSUserDynamicViewController() dynamicVC.id = viewModel.hots[indexPath.row].author._id dynamicVC.hidesBottomBarWhenPushed = true navigationController?.pushViewController(dynamicVC, animated: true) } func community(cell: ZSCommunityHotCell, clickFocus: UIButton) { guard let indexPath = tableView.indexPath(for: cell) else { return } let id = self.viewModel.hots[indexPath.row].author._id } func community(cell: ZSCommunityHotCell, clickMsg: UIButton) { } func community(cell: ZSCommunityHotCell, clickShare: UIButton) { } } extension ZSCommunityViewController:UITableViewDataSource, UITableViewDelegate { func numberOfSections(in tableView: UITableView) -> Int { return 1 } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return self.viewModel.hots.count } func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { let model = self.viewModel.hots[indexPath.row] return model.title.count == 0 ? (382 - 125):382 // return 382 - 125 } func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { return 0.01 } func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { return 0.01 } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.qs_dequeueReusableCell(ZSCommunityHotCell.self) cell?.selectionStyle = .none cell?.congfigure(model: self.viewModel.hots[indexPath.row]) cell?.delegate = self return cell! } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { let model = self.viewModel.hots[indexPath.row] let forumVC = ZSForumPageViewController() forumVC.hidesBottomBarWhenPushed = true forumVC.viewModel.id = model._id self.navigationController?.pushViewController(forumVC, animated: true) } } ================================================ FILE: zhuishushenqi/NewVersion/Community/ZSCommunityViewModel.swift ================================================ // // ZSCommunityViewModel.swift // zhuishushenqi // // Created by caony on 2019/7/2. // Copyright © 2019 QS. All rights reserved. // import UIKit import ZSAPI class ZSCommunityViewModel { var viewDidLoad: ()->() = {} var reloadBlock: ()->() = {} var twitters:[QSHotModel] = [] var hots:[ZSCommunityHot] = [] private var start:Int = 0 private var limit:Int = 20 init() { viewDidLoad = { [weak self] in self?.requestCommunity(completion: { [weak self] in self?.reloadBlock() }) } } func requestCommunity() { requestCommunity { [weak self] in self?.reloadBlock() } } func requestMore() { guard let model = twitters.last else { self.reloadBlock() return } requestMore(model: model) { [weak self] in self?.reloadBlock() } } func requestMore(model:QSHotModel,completion:@escaping()->Void) { let api = ZSAPI.userTwitter("\(model.tweet._id)") zs_get(api.path, parameters: api.parameters) { [weak self] (json) in guard let tweets = json?["tweets"] as? [Any] else { completion() return } if let twitters = [QSHotModel].deserialize(from: tweets) as? [QSHotModel] { self?.twitters.append(contentsOf: twitters) completion() } else { completion() } } } func requestCommunity(completion:@escaping()->Void) { let api = ZSAPI.userTwitter("") zs_get(api.path) { [weak self] (json) in guard let tweets = json?["tweets"] as? [Any] else { completion() return } if let twitters = [QSHotModel].deserialize(from: tweets) as? [QSHotModel] { self?.twitters = twitters completion() } else { completion() } } } func focus(id:String, completion:@escaping(_ success:Bool)->Void) { let api = ZSAPI.focus(token: ZSLogin.share.token, followeeId: id) zs_post(api.path, parameters: api.parameters) { (json) in if let result = json?["ok"] as? Bool { ZSLogin.share.fetchFollowings() completion(result) } else { completion(false) } } } func unFocus(id:String, completion:@escaping(_ success:Bool)->Void) { let api = ZSAPI.unFocus(token: ZSLogin.share.token, followeeId: id) zs_post(api.path, parameters: api.parameters) { (json) in if let result = json?["ok"] as? Bool { ZSLogin.share.fetchFollowings() completion(result) } else { completion(false) } } } //MARK: - 社区 func getCommunity(completion:@escaping()->Void) { let api = ZSAPI.communityHot(start: start, limit: limit) ZSNet.getJSON(api.path, parameters: api.parameters) { (json) in guard let feeds = json?["feeds"] as? [[String:Any]] else { completion(); return } if let hots = [ZSCommunityHot].deserialize(from: feeds) as? [ZSCommunityHot] { self.hots = hots } completion() } } func getAidAnswer(key:String, completion:@escaping()->Void) { let api = ZSAPI.bookAidAnswer(key: key) ZSNet.getJSON(api.path, parameters: api.parameters) { (json) in completion() } } func getAidBestComment(key:String, completion:@escaping()->Void) { let api = ZSAPI.bookAidBestComment(key: key) ZSNet.getJSON(api.path, parameters: api.parameters) { (json) in completion() } } func getBookAidComments(key:String, completion:@escaping()->Void) { let api = ZSAPI.bookAidComments(key: key) ZSNet.getJSON(api.path, parameters: api.parameters) { (json) in completion() } } func getBookAidQuestion(key:String, completion:@escaping()->Void) { let api = ZSAPI.bookAidQuestion(key: key) ZSNet.getJSON(api.path, parameters: api.parameters) { (json) in completion() } } func getForumBook(key:String, completion:@escaping()->Void) { let api = ZSAPI.forumBook(key: key) ZSNet.getJSON(api.path, parameters: api.parameters) { (json) in completion() } } func getAccurateSearch(author:String, key:String, userId:String, completion:@escaping()->Void) { let api = ZSAPI.accurateSearch(author: author, key: key, userId: userId) ZSNet.getJSON(api.path, parameters: api.parameters) { (json) in completion() } } func getBookRecommend(key:String, completion:@escaping()->Void) { let api = ZSAPI.bookRecommend(key: key, position: "detail", ts: 0) ZSNet.getJSON(api.path, parameters: api.parameters) { (json) in completion() } } func getChapterContent(token:String, key:String, thirdToken:String, completion:@escaping()->Void) { let api = ZSAPI.chapterContent(key: key, token: token, thirdToken: thirdToken) ZSNet.getJSON(api.path, parameters: api.parameters) { (json) in completion() } } } ================================================ FILE: zhuishushenqi/NewVersion/Community/ZSDynamicHeaderView.swift ================================================ // // ZSDynamicHeaderView.swift // zhuishushenqi // // Created by yung on 2019/7/6. // Copyright © 2019 QS. All rights reserved. // import UIKit protocol ZSDynamicHeaderViewDelegate:class { func headerView(headerView:ZSDynamicHeaderView, clickIcon:UIButton) func headerView(headerView:ZSDynamicHeaderView, clickFocus:UIButton) func headerView(headerView:ZSDynamicHeaderView, clickFocusCount:UIButton) func headerView(headerView:ZSDynamicHeaderView, clickFans:UIButton) } class ZSDynamicHeaderView: UITableViewHeaderFooterView { lazy var iconView:UIButton = { let imageView = UIButton(type: .custom) imageView.addTarget(self, action: #selector(iconAction(btn:)), for: .touchUpInside) imageView.layer.cornerRadius = 32 imageView.layer.masksToBounds = true return imageView }() lazy var focusButton:UIButton = { let button = UIButton(type: .custom) button.layer.cornerRadius = 14 button.layer.masksToBounds = true button.backgroundColor = UIColor.init(red: 0.93, green: 0.28, blue: 0.27, alpha: 1) button.setTitle("关注", for: .normal) button.titleLabel?.font = UIFont.systemFont(ofSize: 15) button.addTarget(self, action: #selector(focusAction(btn:)), for: .touchUpInside) return button }() lazy var nickNameLabe:UILabel = { let label = UILabel(frame: .zero) label.textColor = UIColor(red: 0.2, green: 0.2, blue: 0.2, alpha: 1) label.font = UIFont.systemFont(ofSize: 16) return label }() lazy var levelLabel:UILabel = { let label = UILabel(frame: .zero) label.textColor = UIColor.init(red: 0.64, green: 0.64, blue: 0.64, alpha: 1) label.font = UIFont.systemFont(ofSize: 9) label.textAlignment = .center label.layer.cornerRadius = 6.5 label.layer.borderColor = UIColor.darkGray.cgColor label.layer.borderWidth = 0.5 return label }() lazy var focusLabel:UILabel = { let label = UILabel(frame: .zero) label.textColor = UIColor.init(red: 0.6, green: 0.6, blue: 0.6, alpha: 1) label.font = UIFont.systemFont(ofSize: 10) label.text = "关注" return label }() lazy var focusCountLabel:UILabel = { let label = UILabel(frame: .zero) label.textColor = UIColor.init(red: 0.2, green: 0.2, blue: 0.2, alpha: 1) label.font = UIFont.systemFont(ofSize: 14) return label }() lazy var focusCountButton:UIButton = { let button = UIButton(type: .custom) button.titleLabel?.font = UIFont.systemFont(ofSize: 15) button.addTarget(self, action: #selector(focusCountAction(btn:)), for: .touchUpInside) return button }() lazy var fansLabel:UILabel = { let label = UILabel(frame: .zero) label.textColor = UIColor.init(red: 0.6, green: 0.6, blue: 0.6, alpha: 1) label.font = UIFont.systemFont(ofSize: 10) label.text = "粉丝" return label }() lazy var fansCountLabel:UILabel = { let label = UILabel(frame: .zero) label.textColor = UIColor.init(red: 0.2, green: 0.2, blue: 0.2, alpha: 1) label.font = UIFont.systemFont(ofSize: 14) return label }() lazy var fansCountButton:UIButton = { let button = UIButton(type: .custom) button.titleLabel?.font = UIFont.systemFont(ofSize: 15) button.addTarget(self, action: #selector(fansAction(btn:)), for: .touchUpInside) return button }() weak var delegate:ZSDynamicHeaderViewDelegate? var type:UserDynamicType = .dynamic { didSet { type == .dynamic ? focusButton.setTitle("关注", for: .normal) : focusButton.setTitle("编辑", for: .normal) } } var focusState:Bool = false { didSet { if type == .dynamic { focusState ? focusSelected():focusUnSelected() } } } override init(reuseIdentifier: String?) { super.init(reuseIdentifier: reuseIdentifier) backgroundColor = UIColor.white contentView.backgroundColor = UIColor.white contentView.addSubview(iconView) contentView.addSubview(focusButton) contentView.addSubview(nickNameLabe) contentView.addSubview(levelLabel) contentView.addSubview(focusLabel) contentView.addSubview(focusCountLabel) contentView.addSubview(focusCountButton) contentView.addSubview(fansLabel) contentView.addSubview(fansCountLabel) contentView.addSubview(fansCountButton) } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func layoutSubviews() { super.layoutSubviews() iconView.frame = CGRect(x: 15, y: 34, width: 64, height: 64) focusButton.frame = CGRect(x: bounds.width - 60 - 16, y: 30, width: 60, height: 28) let nickNameWidth = nickNameLabe.sizeThatFits(CGSize(width: CGFloat.greatestFiniteMagnitude, height: 20)).width nickNameLabe.frame = CGRect(x: iconView.frame.maxX + 20, y: 32, width: nickNameWidth, height: 20) let levelWidth = levelLabel.sizeThatFits(CGSize(width: CGFloat.greatestFiniteMagnitude, height: 13)).width + 10 levelLabel.frame = CGRect(x: nickNameLabe.frame.maxX + 3, y: 35.5, width: levelWidth, height: 13) let focusWidth = focusCountLabel.sizeThatFits(CGSize(width: CGFloat.greatestFiniteMagnitude, height: 20)).width focusCountLabel.frame = CGRect(x: iconView.frame.maxX + 20, y: nickNameLabe.frame.maxY + 20, width: focusWidth, height: 20) focusLabel.frame = CGRect(x: iconView.frame.maxX + 20, y: focusCountLabel.frame.maxY, width: 40, height: 10) let focusCountWidth = max(focusCountLabel.frame.maxX - focusCountLabel.frame.minX, focusLabel.frame.maxX - focusLabel.frame.minX) focusCountButton.frame = CGRect(x: focusCountLabel.frame.minX, y: focusCountLabel.frame.minY, width: focusCountWidth, height: 30) let fansCountWidth = fansCountLabel.sizeThatFits(CGSize(width: CGFloat.greatestFiniteMagnitude, height: 20)).width fansCountLabel.frame = CGRect(x: focusCountLabel.frame.maxX + 19, y: nickNameLabe.frame.maxY + 20, width: fansCountWidth, height: 20) fansLabel.frame = CGRect(x: fansCountLabel.frame.minX, y: fansCountLabel.frame.maxY, width: 40, height: 10) let fansCountButtonWidth = max(fansCountLabel.frame.maxX - fansCountLabel.frame.minX, fansLabel.frame.maxX - fansLabel.frame.minX) fansCountButton.frame = CGRect(x: fansCountLabel.frame.minX, y: fansCountLabel.frame.minY, width: fansCountButtonWidth, height: 30) } func configure(user:ZSDynaicUser?) { if let userInfo = user { iconView.qs_setAvatarWithURLString(urlString: userInfo.avatar) nickNameLabe.text = userInfo.nickname levelLabel.text = "lv.\(userInfo.lv)" focusCountLabel.text = "\(userInfo.followings)" fansCountLabel.text = "\(userInfo.followers)" } } private func focusSelected() { self.focusButton.setTitle("已关注", for: .normal) self.focusButton.setTitleColor(UIColor(red: 0.94, green: 0.94, blue: 0.95, alpha: 1), for: .normal) self.focusButton.backgroundColor = UIColor(red: 0.75, green: 0.75, blue: 0.76, alpha: 1) } private func focusUnSelected() { self.focusButton.setTitle("关注", for: .normal) self.focusButton.setTitleColor(UIColor.white, for: .normal) self.focusButton.backgroundColor = UIColor.init(red: 0.93, green: 0.28, blue: 0.27, alpha: 1) } @objc func iconAction(btn:UIButton) { delegate?.headerView(headerView: self, clickIcon: btn) } @objc func focusAction(btn:UIButton) { delegate?.headerView(headerView: self, clickFocus: btn) } @objc func fansAction(btn:UIButton) { delegate?.headerView(headerView: self, clickFans: btn) } @objc func focusCountAction(btn:UIButton) { delegate?.headerView(headerView: self, clickFocusCount: btn) } } ================================================ FILE: zhuishushenqi/NewVersion/Community/ZSDynamicViewModel.swift ================================================ // // ZSDynamicViewModel.swift // zhuishushenqi // // Created by yung on 2019/7/6. // Copyright © 2019 QS. All rights reserved. // import UIKit import ZSAPI class ZSDynamicViewModel { var viewDidLoad: ()->() = {} var reloadBlock: ()->() = {} var dynamic:ZSDynamic? var followings:[ZSFollowings] = [] init() { } func requestFollowings(id:String) { requestFollowings(id: id) { [weak self] in self?.reloadBlock() } } func requestFollowings(id:String,completion:@escaping()->Void) { let api = ZSAPI.userFollowings("\(id)") zs_get(api.path) { [weak self] (json) in guard let followings = json?["followings"] as? [Any] else { completion() return } if let follows = [ZSFollowings].deserialize(from: followings) as? [ZSFollowings] { self?.followings = follows completion() } else { completion() } } } func requestCommunity(id:String) { requestCommunity(id:id) { [weak self] in self?.reloadBlock() } } func requestCommunity(id:String, completion:@escaping()->Void) { let api = ZSAPI.userTwitters("\(id)", last: "") zs_get(api.path) { [weak self] (json) in if let dynamic = ZSDynamic.deserialize(from: json) { self?.dynamic = dynamic completion() } else { completion() } } } func requestMore(id:String) { requestMore(id:id) { [weak self] in self?.reloadBlock() } } func requestMore(id:String, completion:@escaping()->Void) { guard let last = dynamic?.tweets.last?._id else { completion() return } let api = ZSAPI.userTwitters("\(id)", last: "\(last)") zs_get(api.path) { [weak self] (json) in if let dynamic = ZSDynamic.deserialize(from: json) { self?.dynamic = dynamic completion() } else { completion() } } } func focus(id:String, completion:@escaping(_ success:Bool)->Void) { let api = ZSAPI.focus(token: ZSLogin.share.token, followeeId: id) zs_post(api.path, parameters: api.parameters) { (json) in if let result = json?["ok"] as? Bool { ZSLogin.share.fetchFollowings() completion(result) } else { completion(false) } } } func unFocus(id:String, completion:@escaping(_ success:Bool)->Void) { let api = ZSAPI.unFocus(token: ZSLogin.share.token, followeeId: id) zs_post(api.path, parameters: api.parameters) { (json) in if let result = json?["ok"] as? Bool { ZSLogin.share.fetchFollowings() completion(result) } else { completion(false) } } } } ================================================ FILE: zhuishushenqi/NewVersion/Community/ZSFollowings.swift ================================================ // // ZSFollowings.swift // zhuishushenqi // // Created by yung on 2019/7/6. // Copyright © 2019 QS. All rights reserved. // import UIKit import HandyJSON enum ZSDynamicType: String,HandyJSONEnum { case none = "" case retweet = "RETWEET" case article = "ARTICLE" } struct ZSDynamicTweetFrom:HandyJSON { var _id:String = "" var avatar:String = "" var nickname:String = "" var activityAvatar:String = "" var type:UserType = .none var lv:Int = 0 var gender:String = "" } struct ZSDynamicTweetReTweet:HandyJSON { var _id:String = "" var title:String = "" var content:String = "" var __v:Int = 0 var hotAt:String = "" var post:[String:Any] = [:] var votes:[Any] = [] var deleted:Bool = false var isHot:Bool = false var score:Int = 0 var commented:Int = 0 var retweeted:Int = 0 var type:String = "" var created:String = "" var haveImage:Bool = false } struct ZSDynamicTweetBook:HandyJSON { var _id:String = "" var author:String = "" var title:String = "" var cover:String = "" var allowFree:Bool = false var apptype:[Int] = [] var allowMonthly:Bool = false var wordCount:Int = 0 } struct ZSDynamicTweet:HandyJSON { var _id:String = "" var title:String = "" var content:String = "" var user:ZSDynaicUser? var from:ZSDynamicTweetFrom? var refTweet:ZSDynamicTweetReTweet? var book:ZSDynamicTweetBook? var _v:Int = 0 var post:Any? var votes:[Any] = [] var deleted:Bool = false var isHot:Bool = false var score:Int = 0 var commented:Int = 0 var retweeted:Int = 0 var type:ZSDynamicType = .none var created:String = "" var haveImage:Bool = false } struct ZSDynamic:HandyJSON { var user:ZSDynaicUser? var tweets:[ZSDynamicTweet] = [] } struct ZSFollowings:HandyJSON { var _id: String = "" var avatar:String = "" var nickname:String = "" var mobile:String = "" var followers:Int = 0 var followings:Int = 0 var tweets:Int = 0 var type:UserType = .none var lv:Int = 0 } struct ZSDynaicUser:HandyJSON { var _id:String = "" var nickname:String = "" var avatar:String = "" var exp:Int = 0 var lv:Int = 0 var type:UserType = .none var gender:String = "" var genderChanged:Bool = false var tweets:Int = 0 var followings:Int = 0 var followers:Int = 0 var rank:CGFloat = 0 var bindMobile:Bool = false var grouping:ZSDynamicUserGrouping? } struct ZSDynamicUserGrouping:HandyJSON { var level2_group_id:Int = 0 var level3_group_id:Int = 0 var level4_group_id:Int = 0 var level5_group_id:Int = 0 } // user": { // "_id": "56e903c1febd4661455a0692", // "nickname": "追书家的眼镜娘", // "avatar": "/avatar/65/be/65be796b6f8d0e0a8179737752a991c9", // "exp": 10270, // "lv": 10, // "type": "official", // "gender": "female", // "genderChanged": true, // "tweets": 1202, // "followings": 44, // "followers": 57166, // "rank": 0.5946728430804864, // "bindMobile": true, // "grouping": { // "level2_group_id": 51, // "level3_group_id": 166, // "level4_group_id": 1012, // "level5_group_id": 2004 // } ================================================ FILE: zhuishushenqi/NewVersion/Community/ZSInsertedBookScoreView.swift ================================================ // // ZSInsertedBookScoreView.swift // zhuishushenqi // // Created by caony on 2019/7/2. // Copyright © 2019 QS. All rights reserved. // import UIKit typealias ZSInsertedBookScoreViewHandler = ()->Void class ZSInsertedBookScoreView: UIView { lazy var backgroundView:UIView = { let view = UIView(frame: .zero) view.backgroundColor = UIColor.init(red: 0.94, green: 0.94, blue: 0.96, alpha: 1) view.layer.cornerRadius = 5 view.layer.masksToBounds = true return view }() lazy var bookIconView:UIImageView = { let view = UIImageView(frame: .zero) return view }() lazy var scoreView:UIImageView = { let view = UIImageView(frame: .zero) return view }() lazy var bookNameLabel:UILabel = { let view = UILabel(frame: .zero) view.textColor = UIColor.black view.font = UIFont.systemFont(ofSize: 17) return view }() lazy var scoreLabel:UILabel = { let view = UILabel(frame: .zero) view.textColor = UIColor.init(red: 0.68, green: 0.68, blue: 0.68, alpha: 1) view.font = UIFont.systemFont(ofSize: 14) return view }() lazy var rateView:RateView = { let rateView = RateView(frame: .zero, darkImage: UIImage(named: "details_icon_star_n_32_32_32x32_"), lightImage: UIImage(named: "community_star_p_32x32_")) return rateView }() lazy var touchableView:UIControl = { let control = UIControl(frame: .zero) control.addTarget(self, action: #selector(touchAction(control:)), for: .touchUpInside) return control }() var touchHandler:ZSInsertedBookScoreViewHandler? override init(frame: CGRect) { super.init(frame: frame) addSubview(backgroundView) addSubview(bookIconView) addSubview(bookNameLabel) addSubview(scoreLabel) addSubview(scoreView) addSubview(rateView) addSubview(touchableView) isUserInteractionEnabled = true self.layer.masksToBounds = true } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func layoutSubviews() { super.layoutSubviews() backgroundView.frame = self.bounds bookIconView.frame = CGRect(x: 20, y: 14, width: 68, height: 93) bookNameLabel.frame = CGRect(x: 102, y: 14, width: bounds.width - 102 - 10, height: 24) scoreLabel.frame = CGRect(x: 102, y: 53, width: bounds.width - 102 - 10, height: 20) // scoreView.frame = CGRect(x: 102, y: 81.5, width: 150, height: 25) rateView.frame = CGRect(x: 102, y: 81.5, width: 150, height: 25) touchableView.frame = self.bounds } func configure(model:ZSHotBook?, rate:Int = 0) { if let book = model { self.bookIconView.qs_setBookCoverWithURLString(urlString: book.cover) self.bookNameLabel.text = "\(book.title)" self.scoreLabel.text = "楼主打分" self.scoreView.image = UIImage(named: "") self.rateView.rate = rate } } func configure(mo:ZSDynamicTweetBook?, rate:Int = 0) { if let book = mo { self.bookIconView.qs_setBookCoverWithURLString(urlString: book.cover) self.bookNameLabel.text = "\(book.title)" self.scoreLabel.text = "楼主打分" self.scoreView.image = UIImage(named: "") self.rateView.rate = rate } } @objc private func touchAction(control:UIControl) { touchHandler?() } } ================================================ FILE: zhuishushenqi/NewVersion/Community/ZSNoNetworkView.swift ================================================ // // ZSNoNetworkView.swift // zhuishushenqi // // Created by yung on 2019/7/6. // Copyright © 2019 QS. All rights reserved. // import UIKit typealias ZSNoNetworkHandler = ()->Void class ZSNoNetworkView: UIView { lazy var imageView:UIImageView = { let imageView = UIImageView(frame: .zero) imageView.image = UIImage(named: "no_network_140x128_") return imageView }() lazy var loginButton:UIButton = { let button = UIButton(type: .custom) button.setTitle("登录", for: .normal) button.setTitleColor(UIColor.white, for: .normal) button.layer.cornerRadius = 4 button.layer.masksToBounds = true button.backgroundColor = UIColor(red: 0.41, green: 0.64, blue: 0.29, alpha: 1) button.addTarget(self, action: #selector(loginAction(btn:)), for: .touchUpInside) return button }() var loginHandler:ZSNoNetworkHandler? override init(frame: CGRect) { super.init(frame: frame) addSubview(imageView) addSubview(loginButton) } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func layoutSubviews() { super.layoutSubviews() imageView.frame = CGRect(x: bounds.width/2 - 70, y: 200, width: 140, height: 128) loginButton.frame = CGRect(x: bounds.width/2 - 100, y: imageView.frame.maxY + 200, width: 200, height: 40) } @objc func loginAction(btn:UIButton) { loginHandler?() } } ================================================ FILE: zhuishushenqi/NewVersion/Community/ZSNotification.swift ================================================ // // ZSNotification.swift // zhuishushenqi // // Created by yung on 2019/7/7. // Copyright © 2019 QS. All rights reserved. // import UIKit import HandyJSON enum MsgType:String,HandyJSONEnum { case none = "" case postPush = "post_push" case commentReply = "comment_reply" case commentLike = "comment_like" var image:UIImage? { switch self { case .postPush: return UIImage(named: "personal_icon_topic_14_14_14x14_") case .commentReply: return UIImage(named: "personal_icon_reply_14_14_14x14_") case .commentLike: return UIImage(named: "personal_icon_reply_14_14_14x14_") default: return nil } } } struct ZSNotification:HandyJSON { var _id:String = "" var type:MsgType = .postPush var post:ZSNotificationPost? var title:String = "" var trigger:ZSNotificationTrigger? var comment:ZSNotificationComment? var myComment:ZSNotificationMyComment? var deleted:Bool = false var created:String = "" } struct ZSNotificationPost:HandyJSON { var _id:String = "" var type:UserType = .normal var title:String = "" } struct ZSNotificationTrigger:HandyJSON { var _id:String = "" var avatar:String = "" var nickname:String = "" var type:UserType = .normal var lv:Int = 0 var gender:String = "" } struct ZSNotificationComment:HandyJSON { var _id:String = "" var content:String = "" var floor:Int = 0 } struct ZSNotificationMyComment:HandyJSON { var _id:String = "" var content:String = "" } //{ // "notifications": [{ // "_id": "5c9df5ba113ca53246a60349", // "type": "post_push", // "post": { // "_id": "5c9df54461465a4c4605170a", // "type": "normal", // "title": "【好消息】追书喜提晋江,各路大神文应有尽有,万本精品同步更新中!" // }, // "title": "【好消息】晋江掌阅好书来了!各路大神文应有尽有,万本精品同步更新中!", // "trigger": null, // "comment": null, // "myComment": null, // "deleted": false, // "created": "2019-03-29T10:38:50.298Z" // }, { // "_id": "5c4151669cb008ec1e0a730d", // "type": "comment_reply", // "post": { // "_id": "5c3fffd1b250154e3737ad54", // "type": "normal", // "title": "〔推书〕:读书人不得不看的三本优质网文!😉(含书评)" // }, // "user": "57ac9879c12b61e826bd7221", // "myComment": { // "_id": "5c4130cc18dd9c1c65086261", // "content": "可以\\n不错哟" // }, // "comment": { // "_id": "5c4151669cb008ec1e0a730c", // "content": "谢谢💓", // "floor": 26 // }, // "trigger": { // "_id": "57d9fa98d466c43c346612bf", // "avatar": "/avatar/5b/4c/5b4c8db957f7db769e251fff6681f68c", // "nickname": "ઇGodfatherଓ", // "type": "normal", // "lv": 10, // "gender": "female" // }, // "deleted": false, // "created": "2019-01-18T04:09:10.000Z" // }, { // "_id": "5be71c0b2b6cdd1000ef8bcc", // "type": "comment_reply", // "post": { // "_id": "5be29607474e10bd576ec1e4", // "type": "normal", // "title": "随便画画" // }, // "user": "57ac9879c12b61e826bd7221", // "myComment": { // "_id": "5be70b63d0074f1000171921", // "content": "测试下评论,没有做超链接的展示" // }, // "comment": { // "_id": "5be71c0b2b6cdd1000ef8bcb", // "content": "什么鬼◑▂◑◐▂◐", // "floor": 24 // }, // "trigger": { // "_id": "5addba11c127e26e7ef59051", // "avatar": "/avatar/d0/ee/d0ee266d66821762d950a8096aaa8548", // "nickname": "七月殇", // "type": "normal", // "lv": 7, // "gender": "male" // }, // "deleted": false, // "created": "2018-11-10T17:57:31.000Z" // }, { // "_id": "5be4237d7ccde0c779bbbc6d", // "type": "comment_reply", // "post": { // "_id": "5be29607474e10bd576ec1e4", // "type": "normal", // "title": "随便画画" // }, // "user": "57ac9879c12b61e826bd7221", // "myComment": { // "_id": "5be41fa3acc7d9350a427903", // "content": "试试" // }, // "comment": { // "_id": "5be4237d7ccde0c779bbbc6c", // "content": "什么", // "floor": 16 // }, // "trigger": { // "_id": "5addba11c127e26e7ef59051", // "avatar": "/avatar/d0/ee/d0ee266d66821762d950a8096aaa8548", // "nickname": "七月殇", // "type": "normal", // "lv": 7, // "gender": "male" // }, // "deleted": false, // "created": "2018-11-08T11:52:29.000Z" // }, { // "_id": "5bb1b0100e7abeaa7362fd86", // "type": "post_push", // "post": { // "_id": "5baf14726f660bbe4fe5dc36", // "type": "vote", // "title": "【活动】🇨🇳国庆七天乐:红歌大比拼,更多活动豪礼砸不停~【楼层奖励名单已出】" // }, // "title": "[有人@你]🇨🇳喜迎国庆🇨🇳追书七天福利送!最强攻略在这里!", // "trigger": null, // "comment": null, // "myComment": null, // "deleted": false, // "created": "2018-10-01T05:26:40.131Z" // }], // "ok": true //} ================================================ FILE: zhuishushenqi/NewVersion/Community/ZSNotificationCell.swift ================================================ // // ZSNotificationCell.swift // zhuishushenqi // // Created by yung on 2019/7/7. // Copyright © 2019 QS. All rights reserved. // import UIKit typealias ZSNotificationHandler = ()->Void class ZSNotificationCell: UITableViewCell { lazy var iconView:UIButton = { let button = UIButton(type: .custom) button.layer.cornerRadius = 18 button.layer.masksToBounds = true button.addTarget(self, action: #selector(iconAction(btn:)), for: .touchUpInside) return button }() lazy var msgLabel:UILabel = { let label = UILabel(frame: .zero) label.textColor = UIColor(red: 0.2, green: 0.2, blue: 0.2, alpha: 1.0) label.font = UIFont.systemFont(ofSize: 17) label.numberOfLines = 0 return label }() lazy var nickNameLabel:UILabel = { let label = UILabel(frame: .zero) label.textColor = UIColor(red: 0.38, green: 0.38, blue: 0.4, alpha: 1.0) label.font = UIFont.systemFont(ofSize: 17) return label }() lazy var levelLabel:UILabel = { let label = UILabel(frame: .zero) label.textColor = UIColor.init(red: 0.64, green: 0.64, blue: 0.64, alpha: 1) label.font = UIFont.systemFont(ofSize: 9) label.textAlignment = .center label.layer.cornerRadius = 6.5 label.layer.borderColor = UIColor.darkGray.cgColor label.layer.borderWidth = 0.5 return label }() lazy var commentBackgroundView:UIView = { let view = UIView(frame: .zero) view.backgroundColor = UIColor(red: 0.96, green: 0.96, blue: 0.96, alpha: 1) view.layer.cornerRadius = 4 view.layer.masksToBounds = true return view }() lazy var commentLabel:UILabel = { let label = UILabel(frame: .zero) label.textColor = UIColor(red: 0.47, green: 0.47, blue: 0.5, alpha: 1.0) label.font = UIFont.systemFont(ofSize: 13) label.numberOfLines = 0 return label }() lazy var msgTypeView:UIImageView = { let view = UIImageView(frame: .zero) return view }() var iconHandler:ZSNotificationHandler? override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) contentView.addSubview(iconView) contentView.addSubview(nickNameLabel) contentView.addSubview(levelLabel) contentView.addSubview(msgLabel) contentView.addSubview(commentBackgroundView) commentBackgroundView.addSubview(commentLabel) commentBackgroundView.addSubview(msgTypeView) } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func awakeFromNib() { super.awakeFromNib() // Initialization code } override func setSelected(_ selected: Bool, animated: Bool) { super.setSelected(selected, animated: animated) // Configure the view for the selected state } override func layoutSubviews() { super.layoutSubviews() iconView.frame = CGRect(x: 15, y: 24, width: 36, height: 36) let nickNameWidth = nickNameLabel.sizeThatFits(CGSize(width: CGFloat.greatestFiniteMagnitude, height: 19.3)).width nickNameLabel.frame = CGRect(x: iconView.frame.maxX + 9, y: 32.3, width: nickNameWidth, height: 19.3) let levelWith = levelLabel.sizeThatFits(CGSize(width: CGFloat.greatestFiniteMagnitude, height: 13)).width levelLabel.frame = CGRect(x: nickNameLabel.frame.maxX + 2, y: 35.7, width: levelWith * 2, height: 13) let msgLabelHeight = msgLabel.sizeThatFits(CGSize(width: bounds.width - 30 , height: CGFloat.greatestFiniteMagnitude)).height msgLabel.frame = CGRect(x: 15, y: iconView.frame.maxY + 14, width: bounds.width - 30, height: msgLabelHeight) let commentHeight = commentLabel.sizeThatFits(CGSize(width: bounds.width - 30 - 28, height: CGFloat.greatestFiniteMagnitude)).height commentLabel.frame = CGRect(x: 14, y: 14, width: bounds.width - 30 - 28, height: commentHeight) commentBackgroundView.frame = CGRect(x: 15, y: msgLabel.frame.maxY + 10, width: bounds.width - 30, height: commentHeight + 28) msgTypeView.frame = CGRect(x: 14, y: 16, width: 13, height: 13) } func configure(model:ZSNotification) { if model.type == .postPush { iconView.setImage(UIImage(named: "official_icon_30x30_"), for: .normal) } else { iconView.qs_setAvatarWithURLString(urlString: model.trigger?.avatar ?? "") } nickNameLabel.text = "\(model.trigger?.nickname ?? "")" levelLabel.text = "lv.\(model.trigger?.lv ?? 0)" levelLabel.isHidden = model.type == .postPush msgLabel.text = model.type == .postPush ? "\(model.title)":"\(model.comment?.content ?? "")" let prefixComment = model.type == .commentReply ? "\u{fff9} ":"\u{fff9} " commentLabel.text = model.type == .postPush ? "\u{fff9} \(model.post?.title ?? "")":"\(prefixComment)\(model.myComment?.content ?? "")" msgTypeView.image = model.type.image setNeedsLayout() layoutIfNeeded() } func heightFor(model:ZSNotification) ->CGFloat { configure(model: model) let height = self.commentBackgroundView.frame.maxY + 27 return height } @objc private func iconAction(btn:UIButton) { iconHandler?() } } ================================================ FILE: zhuishushenqi/NewVersion/Community/ZSNotificationViewController.swift ================================================ // // ZSNotificationViewController.swift // zhuishushenqi // // Created by yung on 2019/7/7. // Copyright © 2019 QS. All rights reserved. // import UIKit enum NotificationType { case message case notification } class ZSNotificationViewController: BaseViewController,UITableViewDataSource, UITableViewDelegate { lazy var tableView:UITableView = { let tableView = UITableView(frame: .zero, style: .grouped) tableView.dataSource = self tableView.delegate = self tableView.sectionHeaderHeight = 0.01 tableView.sectionFooterHeight = 0.01 if #available(iOS 11, *) { tableView.contentInsetAdjustmentBehavior = .never } tableView.separatorStyle = .none tableView.qs_registerCellClass(ZSNotificationCell.self) tableView.qs_registerCellClass(UITableViewCell.self) tableView.qs_registerHeaderFooterClass(ZSNotificationHeaderView.self) let blurEffect = UIBlurEffect(style: .extraLight) let blurEffectView = UIVisualEffectView(effect: blurEffect) tableView.backgroundView = blurEffectView return tableView }() private lazy var cell:ZSNotificationCell = { let cell = ZSNotificationCell(style: .default, reuseIdentifier: "\(ZSNotificationCell.self)") return cell }() var viewModel:ZSNotificationViewModel = ZSNotificationViewModel() var type:NotificationType = .message override func viewDidLoad() { super.viewDidLoad() title = type == .message ? "我的消息":"通知" observe() view.addSubview(tableView) tableView.snp.remakeConstraints { (make) in make.top.equalTo(kNavgationBarHeight) make.left.right.equalToSuperview() make.height.equalTo(ScreenHeight - kNavgationBarHeight) } if type == .message { self.viewModel.requestImportant() self.viewModel.postRead { (result) in } } else { self.viewModel.requestUnimportant() } } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) navigationController?.isNavigationBarHidden = false } private func observe() { self.viewModel.reloadBlock = { [weak self] in DispatchQueue.main.async { self?.tableView.reloadData() } } } func jumpToDynamic(indexPath:IndexPath) { if let trigger = self.viewModel.notifications[indexPath.row].trigger { let dynamicVC = ZSUserDynamicViewController() dynamicVC.id = trigger._id dynamicVC.hidesBottomBarWhenPushed = true navigationController?.pushViewController(dynamicVC, animated: true) } } //MARK: - UITableViewDataSource, UITableViewDelegate func numberOfSections(in tableView: UITableView) -> Int { return type == .message ? 2:1 } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { if type == .message { if section == 0 { return 1 } } return self.viewModel.notifications.count } func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { if type == .message { if indexPath.section == 0 { return 50 } } let noti = self.viewModel.notifications[indexPath.row] return cell.heightFor(model: noti) } func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { if type == .message { if section == 0 { return 0.01 } } return 50 } func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { return 0.01 } func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { if type == .message { if section == 0{ return nil } } let headerView = tableView.qs_dequeueReusableHeaderFooterView(ZSNotificationHeaderView.self) headerView?.titlelabel.text = "已读" headerView?.contentView.backgroundColor = UIColor.white return headerView } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { if type == .message { if indexPath.section == 0 { let cell = tableView.qs_dequeueReusableCell(UITableViewCell.self) cell?.selectionStyle = .none cell?.textLabel?.text = "通知" cell?.accessoryType = .disclosureIndicator cell?.imageView?.image = UIImage(named: "notification_personal_icon_message_24_24_24x24_") return cell! } } let cell = tableView.qs_dequeueReusableCell(ZSNotificationCell.self) cell?.selectionStyle = .none cell?.configure(model: self.viewModel.notifications[indexPath.row]) cell?.iconHandler = { [weak self] in self?.jumpToDynamic(indexPath: indexPath) } return cell! } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { if type == .message { if indexPath.section == 0 { let notiVC = ZSNotificationViewController() notiVC.type = .notification navigationController?.pushViewController(notiVC, animated: true) } } } } class ZSNotificationHeaderView: UITableViewHeaderFooterView { lazy var titlelabel:UILabel = { let label = UILabel(frame: .zero) label.font = UIFont.systemFont(ofSize: 20) label.textColor = UIColor.black return label }() override init(reuseIdentifier: String?) { super.init(reuseIdentifier: reuseIdentifier) contentView.addSubview(titlelabel) } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func layoutSubviews() { super.layoutSubviews() titlelabel.frame = CGRect(x: 15, y: 0, width: bounds.width - 30, height: bounds.height) } } ================================================ FILE: zhuishushenqi/NewVersion/Community/ZSNotificationViewModel.swift ================================================ // // ZSNotificationViewModel.swift // zhuishushenqi // // Created by yung on 2019/7/7. // Copyright © 2019 QS. All rights reserved. // import UIKit import ZSAPI class ZSNotificationViewModel { var viewDidLoad: ()->() = {} var reloadBlock: ()->() = {} var notifications:[ZSNotification] = [] var type:NotificationType = .message init() { viewDidLoad = { [weak self] in self?.requestImportant() self?.requestUnimportant() } } func requestImportant() { requestImportant { [weak self] in self?.reloadBlock() } } func requestImportant(completion:@escaping()->Void) { let api = ZSAPI.important(token: ZSLogin.share.token) zs_get(api.path, parameters: api.parameters) { [weak self] (json) in guard let notifications = json?["notifications"] as? [Any] else { completion() return } if let noti = [ZSNotification].deserialize(from: notifications) as? [ZSNotification] { self?.notifications = noti completion() } else { completion() } } } func requestUnimportant() { requestUnimportant { [weak self] in self?.reloadBlock() } } func requestUnimportant(completion:@escaping()->Void) { let api = ZSAPI.unimportant(token: ZSLogin.share.token) zs_get(api.path, parameters: api.parameters) { [weak self] (json) in guard let notifications = json?["notifications"] as? [Any] else { completion() return } if let noti = [ZSNotification].deserialize(from: notifications) as? [ZSNotification] { self?.notifications = noti completion() } else { completion() } } } func postRead(completion:@escaping(_ result:Bool)->Void) { let api = ZSAPI.readImportant(token: ZSLogin.share.token) zs_post(api.path, parameters: api.parameters) { (json) in if let result = json?["ok"] as? Bool { completion(result) } else { completion(false) } } } } ================================================ FILE: zhuishushenqi/NewVersion/Community/ZSRefreshFooter.swift ================================================ // // ZSRefreshFooter.swift // zhuishushenqi // // Created by caony on 2019/7/5. // Copyright © 2019 QS. All rights reserved. // import UIKit import MJRefresh class ZSRefreshFooter: MJRefreshFooter { lazy var indicatorView:UIActivityIndicatorView = { let view = UIActivityIndicatorView(frame: .zero) view.style = UIActivityIndicatorView.Style.white return view }() override func placeSubviews() { super.placeSubviews() } override func prepare() { super.prepare() } } ================================================ FILE: zhuishushenqi/NewVersion/Community/ZSUserDynamicViewController.swift ================================================ // // ZSUserDynamicViewController.swift // zhuishushenqi // // Created by yung on 2019/7/6. // Copyright © 2019 QS. All rights reserved. // import UIKit import MJRefresh enum UserDynamicType { case dynamic case mine } class ZSUserDynamicViewController: BaseViewController, ZSCommunityCellDelegate,ZSDynamicHeaderViewDelegate { lazy var errView:ZSNoNetworkView = { let errView = ZSNoNetworkView(frame: .zero) errView.loginHandler = { [weak self] in self?.loginAction() } return errView }() lazy var tableView:UITableView = { let tableView = UITableView(frame: .zero, style: .grouped) tableView.dataSource = self tableView.delegate = self tableView.sectionHeaderHeight = 0.01 tableView.sectionFooterHeight = 0.01 if #available(iOS 11, *) { tableView.contentInsetAdjustmentBehavior = .never } tableView.qs_registerCellClass(ZSCommunityCell.self) tableView.qs_registerHeaderFooterClass(ZSDynamicHeaderView.self) let blurEffect = UIBlurEffect(style: .extraLight) let blurEffectView = UIVisualEffectView(effect: blurEffect) tableView.backgroundView = blurEffectView return tableView }() var viewModel:ZSDynamicViewModel = ZSDynamicViewModel() var id:String = "" var type:UserDynamicType = .dynamic override func viewDidLoad() { super.viewDidLoad() title = type == .dynamic ? "动态" :"我的" observe() view.addSubview(tableView) view.addSubview(errView) errView.snp.remakeConstraints { (make) in make.edges.equalToSuperview() } tableView.snp.remakeConstraints { (make) in make.top.equalTo(kNavgationBarHeight) make.left.right.equalToSuperview() make.height.equalTo(ScreenHeight - kNavgationBarHeight) } let mj_header = ZSRefreshTextHeader(refreshingTarget: self, refreshingAction: #selector(refreshAction)) mj_header?.endRefreshingCompletionBlock = { [weak mj_header] in mj_header?.changeText() } tableView.mj_header = mj_header if ZSLogin.share.hasLogin() { mj_header?.beginRefreshing() } let mj_footer = MJRefreshAutoStateFooter(refreshingTarget: self, refreshingAction: #selector(loadAction)) mj_footer?.isAutomaticallyRefresh = false tableView.mj_footer = mj_footer } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) errView.isHidden = ZSLogin.share.hasLogin() tableView.isHidden = !ZSLogin.share.hasLogin() navigationController?.isNavigationBarHidden = false } private func observe() { self.viewModel.reloadBlock = { DispatchQueue.main.async { self.tableView.mj_header.endRefreshing() self.tableView.mj_footer.endRefreshing() self.tableView.reloadData() } } } func loginAction() { login { (finished) in } } @objc func refreshAction() { // self.viewModel.requestFollowings(id: id) self.viewModel.requestCommunity(id: id) } @objc func loadAction() { self.viewModel.requestMore(id: id) } //MARK: - ZSDynamicHeaderViewDelegate func headerView(headerView: ZSDynamicHeaderView, clickIcon: UIButton) { } func headerView(headerView: ZSDynamicHeaderView, clickFocus: UIButton) { if headerView.type == .mine { let userInfoVC = ZSUserInfoViewController() userInfoVC.hidesBottomBarWhenPushed = true navigationController?.pushViewController(userInfoVC, animated: true) } else { if headerView.focusState == false { self.viewModel.focus(id: id) { [weak headerView] (result) in headerView?.focusState = result ? !headerView!.focusState:headerView!.focusState } } else { self.viewModel.unFocus(id: id) { [weak headerView] (result) in headerView?.focusState = result ? !headerView!.focusState:headerView!.focusState } } } } func headerView(headerView: ZSDynamicHeaderView, clickFocusCount: UIButton) { } func headerView(headerView: ZSDynamicHeaderView, clickFans: UIButton) { } //MARK: - ZSCommunityCellDelegate func community(cell: ZSCommunityCell, clickIcon: UIButton) { guard let indexPath = tableView.indexPath(for: cell) else { return } let dynamicVC = ZSUserDynamicViewController() dynamicVC.id = self.viewModel.dynamic?.tweets[indexPath.row].user?._id ?? "" dynamicVC.hidesBottomBarWhenPushed = true navigationController?.pushViewController(dynamicVC, animated: true) } func community(cell: ZSCommunityCell, clickFocus: UIButton) { guard let indexPath = tableView.indexPath(for: cell) else { return } let id = self.viewModel.dynamic?.tweets[indexPath.row].user?._id ?? "" if cell.focusState == false { self.viewModel.focus(id: id) { [weak cell] (result) in cell?.focusState = result ? !cell!.focusState:cell!.focusState } } else { self.viewModel.unFocus(id: id) { [weak cell] (result) in cell?.focusState = result ? !cell!.focusState:cell!.focusState } } } func community(cell: ZSCommunityCell, clickMsg: UIButton) { } func community(cell: ZSCommunityCell, clickShare: UIButton) { } } extension ZSUserDynamicViewController:UITableViewDataSource, UITableViewDelegate { func numberOfSections(in tableView: UITableView) -> Int { return 1 } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return self.viewModel.dynamic?.tweets.count ?? 0 } func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { let model = self.viewModel.dynamic?.tweets[indexPath.row] if let book = model?.book { return book.title.count == 0 ? (382 - 125):382 } return 382 - 125 } func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { return 130 } func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { return 0.01 } func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { let headerView = tableView.qs_dequeueReusableHeaderFooterView(ZSDynamicHeaderView.self) headerView?.delegate = self headerView?.configure(user: self.viewModel.dynamic?.user) headerView?.type = type return headerView } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.qs_dequeueReusableCell(ZSCommunityCell.self) cell?.selectionStyle = .none if let dynamic = self.viewModel.dynamic { cell?.congfigure(mo: dynamic.tweets[indexPath.row]) } cell?.delegate = self return cell! } } ================================================ FILE: zhuishushenqi/NewVersion/CoreText/CTSettings.swift ================================================ // // CTSettings.swift // CoreTextDemo // // Created by caony on 2019/7/11. // Copyright © 2019 cj. All rights reserved. // import UIKit import HandyJSON //{{type:book,id:5ec6a64273f1fa0f00ca4122,title:我的徒弟都是大反派,author:谋生任转蓬,cover:/agent/http%3A%2F%2Fimg.13***%2Fapi%2Fv1%2Fbookcenter%2Fcover%2F1%2F3458246%2F3458246_c43a9d1461c04a6ea6eb8425ecc6e980.jpg%2F,allowFree:true,latelyFollower:23815,wordCount:4417679,allowMonthly:true,contentType:txt,superscript:,isSerial:false,retentionRatio:79.81,majorCateV2:,minorCateV2:东方玄幻,onShelve:true}} struct ZSImageData { var parse:ZSImageParse? var position:Int = 0 // coretext frame var imagePosition:CGRect = .zero var image:UIImage? } struct ZSLinkData { var title:String = "" var url:String = "" // post或者booklist var linkTo:String = "" var key:String = "" var type:ZSParseType = .link } struct ZSBookData:HandyJSON { var content:String = "" var type:ZSParseType = .book var id:String = "" var title:String = "" var author:String = "" var cover:String = "" var allowFree:Bool = true var latelyFollower:Int = 0 var wordCount:Int = 0 var allowMonthly:Bool = false var contentType:String = "txt" var superscript:String = "" var isSerial:Bool = false var retentionRatio:Double = 0 var majorCateV2:String = "" var minorCateV2:String = "" var onShelve:Bool = false } struct ZSTextData { var ctFrame:CTFrame? var height:CGFloat = 0 var images:[ZSImageData] = [] var links:[ZSLinkData] = [] var books:[ZSBookData] = [] var content:NSAttributedString? } class CTSettings { //1 // MARK: - Properties var margin: CGFloat = 20 { didSet { setMargin(margin: margin) } } var columnsPerPage: CGFloat! var pageRect: CGRect! var columnRect: CGRect! static let shared = CTSettings() // MARK: - Initializers init() { //2 columnsPerPage = UIDevice.current.userInterfaceIdiom == .phone ? 1 : 2 //3 pageRect = UIScreen.main.bounds.insetBy(dx: margin, dy: margin) //4 columnRect = CGRect(x: 0, y: 0, width: pageRect.width / columnsPerPage, height: pageRect.height).insetBy(dx: margin, dy: margin) } func setMargin(margin:CGFloat) { pageRect = UIScreen.main.bounds.insetBy(dx: margin, dy: margin) columnRect = CGRect(x: 0, y: 0, width: pageRect.width / columnsPerPage, height: pageRect.height).insetBy(dx: margin, dy: margin) } } ================================================ FILE: zhuishushenqi/NewVersion/CoreText/MarkupParser.swift ================================================ // // MarkupParser.swift // CoreTextDemo // // Created by caony on 2019/7/11. // Copyright © 2019 cj. All rights reserved. // import UIKit import CoreText import HandyJSON enum ZSParseType: String, Codable { case none = "" case image = "image" case link = "link" case book = "book" case booklist = "booklist" case post = "post" } struct ZSImageParse:HandyJSON { var type:ZSParseType = .image var url:String = "" var size:String = "" { didSet { let sizeArray = size.components(separatedBy: "-") if sizeArray.count == 2 { imageSize = CGSize(width: Double(sizeArray.first!) ?? 0, height: Double(sizeArray.last!) ?? 0) } } } var imageSize:CGSize = .zero } class MarkupParser: NSObject { // MARK: - Properties var color: UIColor = .gray var fontName: String = "Arial" var fontSize: CGFloat = 13 var font: UIFont = UIFont.systemFont(ofSize: 13) var lineSpace: CGFloat = 0 var paragraphSpace: CGFloat = 0 var attrString: NSMutableAttributedString! var images: [[String: Any]] = [] var coreData:ZSTextData? // MARK: - Initializers override init() { super.init() } let kBookNamePattern = "(《.*?》)" let kTextLinkPattern = "(\\[\\[.*?\\]\\])" let kBookJumpPattern = "(\\{\\{.*?\\}\\})" // //《❤温馨小贴士》 // static NSString *const kImageViewPattern = @"(《.*?》)"; // // //static NSString *const kImageViewPattern = @"(?《.*?》)"; // // //[[post:5b50550d6f788aef59667822 【传送门】告别燥热?这样玩转书单还有惊喜大礼!]] // static NSString *const kTextLinkPattern = @"(\\[\\[.*?\\]\\])"; // // //{{type:image,url:http%3A%2F%2Fstatics.zhuishushenqi.com%2Fpost%2F151678369762541,size:420-422}} // static NSString *const kBookPattern = @"(\\{\\{.*?\\}\\})"; // "content": "\n{{type:book,id:5ec6a64273f1fa0f00ca4122,title:我的徒弟都是大反派,author:谋生任转蓬,cover:/agent/http%3A%2F%2Fimg.13***%2Fapi%2Fv1%2Fbookcenter%2Fcover%2F1%2F3458246%2F3458246_c43a9d1461c04a6ea6eb8425ecc6e980.jpg%2F,allowFree:true,latelyFollower:23815,wordCount:4417679,allowMonthly:true,contentType:txt,superscript:,isSerial:false,retentionRatio:79.81,majorCateV2:,minorCateV2:东方玄幻,onShelve:true}}\n\n睁眼醒来发现自己身处异世,武功全无,身边还都是一个个想要逼死师父的大反派,怎一个惨字了得?!看猪脚是如何通过系统的帮助吊打众人,重返巅峰,纵横天下!此书格局很大,一环扣一环,整体架构严谨,但文笔还需继续加强,不过作为新人来讲已经很不错了,强行安利一波!男主的人设是个糟老头子,一开始也就没什么颜值了,刚穿越过去的时候想要低调但实力不允许啊!", // 根据配置获取富文本格式 func attributes() ->[NSAttributedString.Key:Any] { let fontRef = CTFontCreateWithName("ArialMT" as CFString, fontSize, nil) let settingCount = 3 let settings:[CTParagraphStyleSetting] = [ CTParagraphStyleSetting(spec: CTParagraphStyleSpecifier.lineSpacingAdjustment, valueSize: MemoryLayout.size, value: &lineSpace), CTParagraphStyleSetting(spec: CTParagraphStyleSpecifier.maximumLineSpacing, valueSize: MemoryLayout.size, value: &lineSpace), CTParagraphStyleSetting(spec: CTParagraphStyleSpecifier.minimumLineSpacing, valueSize: MemoryLayout.size, value: &lineSpace), ] let paragraph = CTParagraphStyleCreate(settings, settingCount) let textColor = color let attr:[NSAttributedString.Key:Any] = [ NSAttributedString.Key.foregroundColor:textColor.cgColor, NSAttributedString.Key.font:fontRef, NSAttributedString.Key.paragraphStyle:paragraph ] return attr } func attribute() ->[NSAttributedString.Key:Any] { let defaultFont: UIFont = .systemFont(ofSize: UIScreen.main.bounds.size.height / 40) let font = UIFont(name: fontName, size: UIScreen.main.bounds.size.height / 40) ?? defaultFont let style = NSMutableParagraphStyle() style.alignment = NSTextAlignment.left let attrs = [NSAttributedString.Key.foregroundColor: color, NSAttributedString.Key.font: font,NSAttributedString.Key.paragraphStyle:style] as [NSAttributedString.Key : Any] return attrs } func bookAttribute() ->[NSAttributedString.Key:Any] { let defaultFont: UIFont = .systemFont(ofSize: 13) let font = UIFont(name: fontName, size: 13) ?? defaultFont let style = NSMutableParagraphStyle() style.alignment = NSTextAlignment.left let attrs = [NSAttributedString.Key.foregroundColor: UIColor.orange, NSAttributedString.Key.font: font,NSAttributedString.Key.paragraphStyle:style] as [NSAttributedString.Key : Any] return attrs } func linkAttribute() ->[NSAttributedString.Key:Any] { let defaultFont: UIFont = .systemFont(ofSize: UIScreen.main.bounds.size.height / 40) let font = UIFont(name: fontName, size: UIScreen.main.bounds.size.height / 40) ?? defaultFont let linkColor = UIColor.orange let style = NSMutableParagraphStyle() style.alignment = NSTextAlignment.left let attrs = [NSAttributedString.Key.foregroundColor: linkColor, NSAttributedString.Key.font: font,NSAttributedString.Key.paragraphStyle:style] as [NSAttributedString.Key : Any] return attrs } // MARK: - zssq parse func parseContent(_ content: String, settings:CTSettings) { attrString = NSMutableAttributedString(string: "") let pattern = "\(kBookNamePattern)|\(kBookJumpPattern)|\(kTextLinkPattern)" do { let regex = try NSRegularExpression(pattern: pattern, options: [.caseInsensitive,.dotMatchesLineSeparators]) let chunks = regex.matches(in: content, options: NSRegularExpression.MatchingOptions(rawValue: 0), range: NSRange(location: 0, length: content.count)) var images:[ZSImageData] = [] var links:[ZSLinkData] = [] var books:[ZSBookData] = [] var lastRange:NSRange = NSMakeRange(0, 0) for chunk in chunks { guard let markupRange = content.range(from: chunk.range) else { lastRange = chunk.range continue } let chunkSubStr = content[markupRange] let chunkStr = String(chunkSubStr) if let preTextRange = content.range(from: NSMakeRange(lastRange.location + lastRange.length, chunk.range.location - lastRange.location - lastRange.length)) { let preText = String(content[preTextRange]) let text = NSMutableAttributedString(string: preText, attributes: attributes()) attrString.append(text) } // image or book if chunkStr.contains("{{") { let bookAndImageDict = getDict(bookAndImage: chunkStr) let type = bookAndImageDict["type"] as? String ?? "" if type == ZSParseType.book.rawValue { let book = parseBook(dict: bookAndImageDict) let text = NSMutableAttributedString(string: "《\(book.title)》", attributes: bookAttribute()) books.append(book) attrString.append(text) } else if type == ZSParseType.image.rawValue { let imageModel = parse(imageDict: bookAndImageDict) if imageModel.type == .image { var imageData = ZSImageData() imageData.position = attrString.length imageData.parse = imageModel images.append(imageData) var width: CGFloat = imageData.parse?.imageSize.width ?? 0 var height: CGFloat = imageData.parse?.imageSize.height ?? 0 if width > settings.pageRect.width { height = height/width * settings.pageRect.width width = settings.pageRect.width } let attr = configImage(width: width, height: height) attrString.append(attr) } } } else if chunkStr.contains("[[") { // link(post/booklist) if let linkData = linkDara(str: chunkStr) { links.append(linkData) let text = NSMutableAttributedString(string: linkData.title, attributes: linkAttribute()) attrString.append(text) } } // else if chunkStr.contains("《") { // let text = NSMutableAttributedString(string: chunkStr, attributes: linkAttribute()) // let book = parse(book: chunkStr) // books.append(book) // attrString.append(text) // } lastRange = chunk.range } if lastRange.location + lastRange.length < content.count { if let remainContentRange = content.range(from: NSMakeRange(lastRange.location + lastRange.length, content.count - lastRange.location - lastRange.length)) { let remainContent = String(content[remainContentRange]) let text = NSMutableAttributedString(string: remainContent, attributes: attributes()) attrString.append(text) } } let framesetter = CTFramesetterCreateWithAttributedString(attrString as CFAttributedString) let textSize = CTFramesetterSuggestFrameSizeWithConstraints(framesetter, CFRangeMake(0, 0), nil, CGSize(width: settings.pageRect.width, height: CGFloat.greatestFiniteMagnitude), nil) var coreData = ZSTextData() coreData.books = books coreData.images = images coreData.links = links coreData.height = textSize.height self.coreData = coreData } catch _ { } } func getDict(bookAndImage:String) ->[String:Any] { var chunkStr = bookAndImage.replacingOccurrences(of: "{{", with: "") chunkStr = chunkStr.replacingOccurrences(of: "}}", with: "") var bookAndImageDic:[String:Any] = [:] let keyValueString = chunkStr.components(separatedBy: ",") for item in keyValueString { let keyValues = item.components(separatedBy: ":") if keyValues.count != 2 { continue } let key = keyValues.first! let value = keyValues.last! bookAndImageDic[key] = value } return bookAndImageDic } func parseBook(dict:[String:Any]) ->ZSBookData { let bookModel = ZSBookData() if let bookModel = ZSBookData.deserialize(from: dict, designatedPath: nil) { return bookModel } return bookModel } func parse(imageDict:[String:Any]) ->ZSImageParse { let imageParse = ZSImageParse() if let imageParse = ZSImageParse.deserialize(from: imageDict, designatedPath: nil) { return imageParse } return imageParse } func linkDara(str:String) ->ZSLinkData? { var linkStr = str.replacingOccurrences(of: "[[", with: "") linkStr = linkStr.replacingOccurrences(of: "]]", with: "") let links = linkStr.components(separatedBy: ":") if links.count == 2 { // 这里有三种情况,post/link/booklist // 先取key 判断类型 let key = links.first! guard let type = ZSParseType.init(rawValue: key) else { return nil } switch type{ case .post, .booklist, .link, .book: let postValue = links.last! // 第一个空格进行分割 guard let range = postValue.range(of: " ") else { return nil } let startPos = String.Index.init(utf16Offset: 0, in: postValue) let link = String(postValue[startPos..ZSBookData? { return nil } // MARK: - Internal func parseMarkup(_ markup: String) { //1 attrString = NSMutableAttributedString(string: "") //2 do { let regex = try NSRegularExpression(pattern: "(.*?)(<[^>]+>|\\Z)", options: [.caseInsensitive,.dotMatchesLineSeparators]) //3 let chunks = regex.matches(in: markup, options: NSRegularExpression.MatchingOptions(rawValue: 0), range: NSRange(location: 0, length: markup.count)) let defaultFont: UIFont = .systemFont(ofSize: UIScreen.main.bounds.size.height / 40) //1 for chunk in chunks { //2 guard let markupRange = markup.range(from: chunk.range) else { continue } //3 let parts = markup[markupRange].components(separatedBy: "<") //4 let font = UIFont(name: fontName, size: UIScreen.main.bounds.size.height / 40) ?? defaultFont //5 let attrs = [NSAttributedString.Key.foregroundColor: color, NSAttributedString.Key.font: font] as [NSAttributedString.Key : Any] let text = NSMutableAttributedString(string: parts[0], attributes: attrs) attrString.append(text) // 1 if parts.count <= 1 { continue } let tag = parts[1] //2 if tag.hasPrefix("font") { let colorRegex = try NSRegularExpression(pattern: "(?<=color=\")\\w+", options: NSRegularExpression.Options(rawValue: 0)) colorRegex.enumerateMatches(in: tag, options: NSRegularExpression.MatchingOptions(rawValue: 0), range: NSMakeRange(0, tag.count)) { (match, _, _) in //3 if let match = match, let range = tag.range(from: match.range) { let colorSel = NSSelectorFromString(tag[range]+"Color") color = UIColor.perform(colorSel).takeRetainedValue() as? UIColor ?? .black } } //5 let faceRegex = try NSRegularExpression(pattern: "(?<=face=\")[^\"]+", options: NSRegularExpression.Options(rawValue: 0)) faceRegex.enumerateMatches(in: tag, options: NSRegularExpression.MatchingOptions(rawValue: 0), range: NSMakeRange(0, tag.count)) { (match, _, _) in if let match = match, let range = tag.range(from: match.range) { fontName = String(tag[range]) } } } //1 else if tag.hasPrefix("img") { var filename:String = "" let imageRegex = try NSRegularExpression(pattern: "(?<=src=\")[^\"]+", options: NSRegularExpression.Options(rawValue: 0)) imageRegex.enumerateMatches(in: tag, options: NSRegularExpression.MatchingOptions(rawValue: 0), range: NSMakeRange(0, tag.count)) { (match, _, _) in if let match = match, let range = tag.range(from: match.range) { filename = String(tag[range]) } } //2 let settings = CTSettings() var width: CGFloat = settings.columnRect.width var height: CGFloat = 0 if let image = UIImage(named: filename) { height = width * (image.size.height / image.size.width) // 3 if height > settings.columnRect.height - font.lineHeight { height = settings.columnRect.height - font.lineHeight width = height * (image.size.width / image.size.height) } } //1 images += [["width": NSNumber(value: Float(width)), "height": NSNumber(value: Float(height)), "filename": filename, "location": NSNumber(value: attrString.length)]] let attr = configImage(width: width, height: height) attrString.append(attr) } } } catch _ { } } func configImage(width:CGFloat, height:CGFloat) ->NSAttributedString { struct RunStruct { let ascent: CGFloat let descent: CGFloat let width: CGFloat } let extentBuffer = UnsafeMutablePointer.allocate(capacity: 1) extentBuffer.initialize(to: RunStruct(ascent: height, descent: 0, width: width)) //3 var callbacks = CTRunDelegateCallbacks(version: kCTRunDelegateVersion1, dealloc: { (pointer) in }, getAscent: { (pointer) -> CGFloat in let d = pointer.assumingMemoryBound(to: RunStruct.self) return d.pointee.ascent }, getDescent: { (pointer) -> CGFloat in let d = pointer.assumingMemoryBound(to: RunStruct.self) return d.pointee.descent }, getWidth: { (pointer) -> CGFloat in let d = pointer.assumingMemoryBound(to: RunStruct.self) return d.pointee.width }) //4 let delegate = CTRunDelegateCreate(&callbacks, extentBuffer) //5 let attrDictionaryDelegate = [(kCTRunDelegateAttributeName as NSAttributedString.Key): (delegate as Any)] var objectReplacementChar:unichar = 0xFFFC let content = NSString(characters: &objectReplacementChar, length: 1) let attrStr = NSMutableAttributedString(string: String(content), attributes: attrDictionaryDelegate) attrStr.addAttributes(attribute(), range: NSMakeRange(0, attrStr.length)) return attrStr } } extension String { func range(from range: NSRange) -> Range? { guard let from16 = utf16.index(utf16.startIndex, offsetBy: range.location, limitedBy: utf16.endIndex), let to16 = utf16.index(from16, offsetBy: range.length, limitedBy: utf16.endIndex), let from = String.Index(from16, within: self), let to = String.Index(to16, within: self) else { return nil } return from ..< to } } ================================================ FILE: zhuishushenqi/NewVersion/CoreText/ZSDisplayView.swift ================================================ // // ZSDisplayView.swift // CoreTextDemo // // Created by caony on 2019/7/12. // Copyright © 2019 cj. All rights reserved. // import UIKit import Kingfisher typealias ZSDisplayHandler = ()->Void class ZSDisplayView: UIView { // MARK: - Properties var ctFrame: CTFrame? var coreHeight: CGFloat = 0 fileprivate var images: [(image: UIImage, frame: CGRect)] = [] fileprivate var imageModels:[ZSImageData] = [] // MARK: - Properties fileprivate var imageIndex: Int! fileprivate var longPress:UILongPressGestureRecognizer! fileprivate var originRange = NSRange(location: 0, length: 0) fileprivate var selectedRange = NSRange(location: 0, length: 0) fileprivate var attributeString:NSMutableAttributedString = NSMutableAttributedString(string: "") fileprivate var rects:[CGRect] = [] fileprivate var leftCursor:ZSTouchAnchorView! fileprivate var rightCursor:ZSTouchAnchorView! // 移动光标 var isTouchCursor = false // 是否编辑状态 var isEditing:Bool { return rects.count > 0 } // 段落划分 var paragraphes:[String] { return attributeString.string.components(separatedBy: "\n") } // 编辑回调 var startEditingHandler:ZSDisplayHandler? var endEditingHandler:ZSDisplayHandler? private var touchRightCursor = false private var touchOriginRange = NSRange(location: 0, length: 0) override init(frame: CGRect) { super.init(frame: frame) backgroundColor = UIColor.clear isUserInteractionEnabled = true if longPress == nil { longPress = UILongPressGestureRecognizer(target: self, action: #selector(longPressAction(gesture:))) addGestureRecognizer(longPress) } _ = UITapGestureRecognizer(target: self, action: #selector(tapAction(tap:))) // addGestureRecognizer(tap) } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } func clearEditing() { rects = [] hideCursor() setNeedsDisplay() } func range(for string:String) ->NSRange { attributeString.string.ocString.range(of: string) } func startEdit(for string:String) { let selectedRange = range(for: string) rects = rangeRects(range: selectedRange, ctframe: ctFrame) setNeedsDisplay() } func nextParagraph(string:String) -> String { var paragraghString:String = getNextUnEmptyParagarph(index: -1) if string.count == 0 { return paragraghString } for paragraph in paragraphes { if paragraph.contains(string) { if let index = paragraphes.firstIndex(of: paragraph) { paragraghString = getNextUnEmptyParagarph(index: index) break } } } return paragraghString } fileprivate func getNextUnEmptyParagarph(index:Int) ->String { if (index + 1) >= paragraphes.count { return "" } let string = paragraphes[index + 1] if string.count > 0 { return string } return getNextUnEmptyParagarph(index: index + 1) } @objc private func tapAction(tap:UITapGestureRecognizer) { originRange = NSRange(location: 0, length: 0) selectedRange = NSRange(location: 0, length: 0) rects.removeAll() hideCursor() setNeedsDisplay() } @objc private func longPressAction(gesture:UILongPressGestureRecognizer) { var originPoint = CGPoint.zero switch gesture.state { case .began: originPoint = gesture.location(in: self) originRange = touchLocation(point: originPoint, str: self.attributeString.string) selectedRange = originRange rects = rangeRects(range: selectedRange, ctframe: ctFrame) showCursor() setNeedsDisplay() break case .changed: let finalRange = touchLocation(point: gesture.location(in: self), str: attributeString.string) if finalRange.location == 0 || finalRange.location == NSNotFound { return } var range = NSRange(location: 0, length: 0) range.location = min(finalRange.location, originRange.location) if finalRange.location > originRange.location { range.length = finalRange.location - originRange.location + finalRange.length } else { range.length = originRange.location - finalRange.location + originRange.length } selectedRange = range rects = rangeRects(range: selectedRange, ctframe: ctFrame) showCursor() startEditingHandler?() setNeedsDisplay() break case .ended: break case .cancelled: endEditingHandler?() break default: break } } func showCursor() { guard rects.count > 0 else { return } showMenuController() let leftRect = rects.first! let rightRect = rects.last! if leftCursor == nil { let rect = CGRect(x: leftRect.minX - 5, y: leftRect.origin.y - 10, width: 10, height: leftRect.height + 6) leftCursor = ZSTouchAnchorView(frame: rect) addSubview(leftCursor) } else { let rect = CGRect(x: leftRect.minX - 5, y: leftRect.origin.y - 10, width: 10, height: leftRect.height + 6) leftCursor.frame = rect } if rightCursor == nil { let rect = CGRect(x: rightRect.maxX - 2, y: rightRect.origin.y - 10, width: 10, height: rightRect.height + 6) rightCursor = ZSTouchAnchorView(frame: rect) addSubview(rightCursor) } else { rightCursor.frame = CGRect(x: rightRect.maxX - 4, y: rightRect.origin.y - 10, width: 10, height: rightRect.height + 6) } } func hideCursor() { hideMenuController() if leftCursor != nil { leftCursor.removeFromSuperview() leftCursor = nil } if rightCursor != nil { rightCursor.removeFromSuperview() rightCursor = nil } } override func touchesBegan(_ touches: Set, with event: UIEvent?) { guard let point = touches.first?.location(in: self) else { return } guard leftCursor != nil && rightCursor != nil else { return } if rightCursor.frame.insetBy(dx: -30, dy: -30).contains(point) { touchRightCursor = true isTouchCursor = true } else if leftCursor.frame.insetBy(dx: -30, dy: -30).contains(point) { touchRightCursor = false isTouchCursor = true } touchOriginRange = selectedRange } override func touchesMoved(_ touches: Set, with event: UIEvent?) { guard let point = touches.first?.location(in: self) else { return } guard leftCursor != nil && rightCursor != nil else { return } if isTouchCursor { let finalRange = touchLocation(point: point, str: attributeString.string) if (finalRange.location == 0 && finalRange.length == 0) || finalRange.location == NSNotFound { return } var range = NSRange(location: 0, length: 0) if touchRightCursor { // 移动右边光标 if finalRange.location >= touchOriginRange.location { range.location = touchOriginRange.location range.length = finalRange.location - touchOriginRange.location + 1 } else { range.location = finalRange.location range.length = touchOriginRange.location - range.location } } else { // 移动左边光标 if finalRange.location <= touchOriginRange.location { range.location = finalRange.location range.length = touchOriginRange.location - finalRange.location + touchOriginRange.length } else if finalRange.location > touchOriginRange.location { if finalRange.location <= touchOriginRange.location + touchOriginRange.length - 1 { range.location = finalRange.location range.length = touchOriginRange.location + touchOriginRange.length - finalRange.location } else { range.location = touchOriginRange.location + touchOriginRange.length range.length = finalRange.location - range.location } } } selectedRange = range rects = rangeRects(range: selectedRange, ctframe: ctFrame) // 显示光标 showCursor() setNeedsDisplay() } } override func touchesEnded(_ touches: Set, with event: UIEvent?) { isTouchCursor = false touchOriginRange = selectedRange } func rangeRects(range:NSRange, ctframe:CTFrame?) ->[CGRect] { var rects:[CGRect] = [] guard let ctframe = ctframe else { return rects } guard range.location != NSNotFound else { return rects } let lines = CTFrameGetLines(ctframe) as Array var origins = [CGPoint](repeating: CGPoint.zero, count: lines.count) CTFrameGetLineOrigins(ctframe, CFRange(location: 0, length: 0), &origins) for index in 0.. range.location && lineRange.location < (range.location + range.length) { var ascent: CGFloat = 0 var descent: CGFloat = 0 var startX: CGFloat = 0 var contentRange = NSRange(location: range.location, length: 0) let end = min(lineRange.location + lineRange.length, range.location + range.length) contentRange.length = end - contentRange.location CTLineGetTypographicBounds(line, &ascent, &descent, nil) let y = coreHeight - origin.y - ascent startX = CTLineGetOffsetForStringIndex(line, contentRange.location, nil) let endX = CTLineGetOffsetForStringIndex(line, contentRange.location + contentRange.length, nil) let rect = CGRect(x: origin.x + startX, y: y, width: endX - startX, height: ascent + descent) rects.append(rect) } } } return rects } func touchLocation(point:CGPoint, str:String = "") ->NSRange { var touchRange = NSMakeRange(0, 0) guard let ctFrame = self.ctFrame else { return touchRange } let lines = CTFrameGetLines(ctFrame) as Array var origins = [CGPoint](repeating: CGPoint.zero, count: lines.count) CTFrameGetLineOrigins(ctFrame, CFRange(location: 0, length: 0), &origins) for index in 0.. imgLocation || runRange.location + runRange.length <= imgLocation { continue } //2 var imgBounds: CGRect = .zero var ascent: CGFloat = 0 imgBounds.size.width = CGFloat(CTRunGetTypographicBounds(run, CFRangeMake(0, 0), &ascent, nil, nil)) imgBounds.size.height = ascent //3 let xOffset = CTLineGetOffsetForStringIndex(line, CTRunGetStringRange(run).location, nil) imgBounds.origin.x = origins[lineIndex].x + xOffset imgBounds.origin.y = origins[lineIndex].y //4 self.images += [(image: img, frame: imgBounds)] //5 imageIndex! += 1 if imageIndex < images.count { nextImage = images[imageIndex] imgLocation = (nextImage["location"] as AnyObject).intValue } } } } } // MARK: - Life Cycle override func draw(_ rect: CGRect) { guard let context = UIGraphicsGetCurrentContext() else { return } context.textMatrix = .identity context.translateBy(x: 0, y: coreHeight) context.scaleBy(x: 1.0, y: -1.0) if rects.count > 0 { let lineRects = rects.map { rect -> CGRect in return CGRect(x: rect.origin.x - 2, y: coreHeight - rect.origin.y - rect.height, width: rect.width + 4, height: rect.height) } let fillPath = CGMutablePath() UIColor(red:0.92, green:0.5, blue:0.5, alpha:1.00).withAlphaComponent(0.5).setFill() fillPath.addRects(lineRects) context.addPath(fillPath) context.fillPath() } CTFrameDraw(ctFrame!, context) for imageData in images { if let image = imageData.image.cgImage { let imgBounds = imageData.frame context.draw(image, in: imgBounds) } } for imageModel in imageModels { if let image = imageModel.image { context.draw(image.cgImage!, in: imageModel.imagePosition) continue } if let url = URL(string: imageModel.parse?.url ?? "" ) { ImageDownloader.default.downloadImage(with: url, options: nil, progressBlock: nil) { [weak self] (result) in switch result { case .success(let value): print(value.image) self!.imageModels[self?.indexOfModel(model: imageModel, models: self!.imageModels) ?? 0].image = value.image self?.setNeedsDisplay() case .failure(let error): print(error) } } } } } func indexOfModel(model:ZSImageData, models:[ZSImageData]) ->Int { var index = 0 for data in models { if data.parse?.url == model.parse?.url { break } index += 1 } return index } } extension ZSDisplayView { override var canBecomeFirstResponder: Bool { return true } @objc func copyItemFunc() { UIPasteboard.general.string = self.attributeString.string.qs_subStr(range: selectedRange) clearEditing() } func showMenuController() { if self.becomeFirstResponder() { let selectionRectTuple = rectForMenuController() let menu = UIMenuController.shared menu.arrowDirection = selectionRectTuple.1 if menu.isMenuVisible { menu.setTargetRect(selectionRectTuple.0, in: self) menu.update() } else { let copyItem = UIMenuItem(title: "Copy", action: #selector(copyItemFunc)) menu.menuItems = [copyItem] menu.setTargetRect(selectionRectTuple.0, in: self) menu.setMenuVisible(true, animated: true) } } } func hideMenuController() { if self.resignFirstResponder() { let menu = UIMenuController.shared menu.setMenuVisible(false, animated: true) } } func rectForMenuController()->(CGRect, UIMenuController.ArrowDirection) { guard let firstRect = rects.first else { return (CGRect.zero, .default) } guard let lastRect = rects.last else { return (CGRect.zero, .default) } var menuRect:CGRect = CGRect.zero var direction:UIMenuController.ArrowDirection = .default // 尝试在第一行的下方展示 if firstRect.origin.y < 50 { menuRect = CGRect(x: lastRect.origin.x + (lastRect.width - lastRect.origin.x)/2, y: lastRect.origin.y, width: 100, height: 30) direction = .up } else { menuRect = CGRect(x: firstRect.origin.x + (firstRect.width - firstRect.origin.x)/2, y: firstRect.origin.y - 20, width: 100, height: 30) } return (menuRect, direction) } } ================================================ FILE: zhuishushenqi/NewVersion/CoreText/ZSTouchAnchorView.swift ================================================ // // ZSTouchAnchorView.swift // CoreTextDemo // // Created by caony on 2019/7/14. // Copyright © 2019 cj. All rights reserved. // import UIKit class ZSTouchAnchorView: UIView { lazy var dotView:UIView = { let view = UIView(frame: .zero) view.backgroundColor = UIColor(red:0.92, green:0.25, blue:0.29, alpha:1.00) view.layer.cornerRadius = 5 view.layer.masksToBounds = true return view }() lazy var lineView:UIView = { let view = UIView(frame: .zero) view.backgroundColor = UIColor(red:0.92, green:0.25, blue:0.29, alpha:1.00) return view }() override init(frame: CGRect) { super.init(frame: frame) addSubview(dotView) addSubview(lineView) } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func layoutSubviews() { super.layoutSubviews() dotView.frame = CGRect(x: 0, y: 0, width: 10, height: 10) lineView.frame = CGRect(x: bounds.width/2 - 1, y: 9, width: 2, height: bounds.height - 9) } } ================================================ FILE: zhuishushenqi/NewVersion/Device/ZSFloatingManager.swift ================================================ // // ZSFloatingManager.swift // zhuishushenqi // // Created by daye on 2021/6/9. // Copyright © 2021 QS. All rights reserved. // import UIKit class ZSFloatingManager { static let share = ZSFloatingManager() var window:ZSFloatingWindow? private init() { } } ================================================ FILE: zhuishushenqi/NewVersion/Device/ZSFloatingView.swift ================================================ // // ZSFloatingView.swift // zhuishushenqi // // Created by daye on 2021/6/9. // Copyright © 2021 QS. All rights reserved. // import UIKit class ZSFloatingView: UIView { private var start:CGPoint = .zero private var original:CGPoint = .zero override func touchesBegan(_ touches: Set, with event: UIEvent?) { if let touch = touches.first { start = touch.location(in: self) original = center } } override func touchesMoved(_ touches: Set, with event: UIEvent?) { if let touch = touches.first { let move = touch.location(in: self) let deltaX = move.x - start.x let deltaY = move.y - start.y center = CGPoint(x: center.x + deltaX, y: center.y + deltaY) } } override func touchesEnded(_ touches: Set, with event: UIEvent?) { var centerX:CGFloat = 0 if center.x < ScreenWidth/2 { centerX = bounds.width/2 + 10 } else { centerX = ScreenWidth - bounds.width/2 - 10 } var centerY:CGFloat = center.y var topMargin:CGFloat = 10 var bottomMargin:CGFloat = 10 if IPHONEX { topMargin = 44 bottomMargin = 34 } if center.y < bounds.height/2 + topMargin { centerY = bounds.height/2 + topMargin } else if center.y > ScreenHeight - bounds.height/2 - bottomMargin { centerY = ScreenHeight - bounds.height/2 - bottomMargin } else { centerY = center.y } UIView.animate(withDuration: 0.1) { self.center = CGPoint(x: centerX, y: centerY) } } } ================================================ FILE: zhuishushenqi/NewVersion/Device/ZSFloatingViewController.swift ================================================ // // ZSFloatingViewController.swift // zhuishushenqi // // Created by daye on 2021/6/9. // Copyright © 2021 QS. All rights reserved. // import UIKit class ZSFloatingViewController: BaseViewController { override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = UIColor.clear } override var prefersStatusBarHidden: Bool { return KeyWindow?.rootViewController?.prefersStatusBarHidden ?? true } override var preferredStatusBarStyle: UIStatusBarStyle { return KeyWindow?.rootViewController?.preferredStatusBarStyle ?? .lightContent } } ================================================ FILE: zhuishushenqi/NewVersion/Device/ZSFloatingWindow.swift ================================================ // // ZSFloatingWindow.swift // zhuishushenqi // // Created by daye on 2021/6/9. // Copyright © 2021 QS. All rights reserved. // import UIKit class ZSFloatingWindow: UIWindow { override init(frame: CGRect) { super.init(frame: frame) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { if let rootVC = rootViewController { for subview in rootVC.view.subviews { if subview.frame.contains(point) { return subview } } } let hitView = super.hitTest(point, with: event) return hitView } } ================================================ FILE: zhuishushenqi/NewVersion/Device/ZSMemoryFloatingView.swift ================================================ // // ZSMemoryFloatingView.swift // zhuishushenqi // // Created by daye on 2021/6/9. // Copyright © 2021 QS. All rights reserved. // import UIKit enum MemoryState { case low case normal case high case full var memoryBgColor:UIColor { switch self { case .low: return UIColor(hexString: "#00BB00") ?? UIColor.green case .normal: return UIColor(hexString: "#00BB00") ?? UIColor.green case .high: return UIColor(hexString: "#FF5809") ?? UIColor.orange case .full: return UIColor(hexString: "#AE0000") ?? UIColor.red default: return UIColor.green } } func get(percent:CGFloat) ->MemoryState { if percent > 0 && percent <= 0.3 { return .low } else if percent > 0.3 && percent <= 0.5 { return .normal } else if percent > 0.5 && percent <= 0.99 { return .high } else { return .full } } } class ZSMemoryFloatingView: ZSFloatingView { lazy var memoryUsageLabel: UILabel = { let view = UILabel(frame: .zero) view.textColor = UIColor.white view.font = UIFont.systemFont(ofSize: 11) view.textAlignment = .center view.isUserInteractionEnabled = false return view }() lazy var memoryTotalLabel: UILabel = { let view = UILabel(frame: .zero) view.textColor = UIColor.white view.font = UIFont.systemFont(ofSize: 11) view.textAlignment = .center return view }() lazy var memoryCornerView: UIView = { let view = UIView(frame: .zero) view.isUserInteractionEnabled = false return view }() lazy var memoryContentView: UIView = { let view = UIView(frame: .zero) view.isUserInteractionEnabled = false return view }() lazy var memoryBgView: UIView = { let view = UIView(frame: .zero) view.isUserInteractionEnabled = false return view }() let memoryNormalWidth:CGFloat = 60 let memoryNormalHeight:CGFloat = 60 var memoryState:MemoryState = .low { didSet { update() } } var percent:CGFloat = 0 private var timer:Timer? override init(frame: CGRect) { super.init(frame: frame) isUserInteractionEnabled = true addSubview(memoryContentView) memoryContentView.addSubview(memoryCornerView) memoryCornerView.addSubview(memoryBgView) addSubview(memoryUsageLabel) backgroundColor = UIColor.white memoryContentView.backgroundColor = UIColor(hexString: "#3A8863") layer.cornerRadius = memoryNormalWidth/2 layer.masksToBounds = true memoryContentView.layer.cornerRadius = (memoryNormalWidth - 4)/2 memoryContentView.layer.masksToBounds = true memoryCornerView.layer.cornerRadius = (memoryNormalWidth - 8)/2 memoryCornerView.layer.masksToBounds = true update() } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func layoutSubviews() { super.layoutSubviews() memoryContentView.frame = CGRect(x: 2, y: 2, width: memoryNormalWidth - 4, height: memoryNormalHeight - 4) memoryCornerView.frame = CGRect(x: 2, y: 2, width: memoryContentView.bounds.width - 4, height: memoryContentView.bounds.height - 4) let memoryBgHeight = memoryCornerView.bounds.height * percent memoryBgView.frame = CGRect(x: 0, y: memoryCornerView.bounds.height - memoryBgHeight, width: memoryCornerView.bounds.width, height: memoryBgHeight) memoryUsageLabel.frame = bounds } func update() { memoryBgView.backgroundColor = memoryState.memoryBgColor } static func show() { let memoryFloatingView = ZSMemoryFloatingView(frame: .zero) memoryFloatingView.show() } func show() { let screenBounds = UIScreen.main.bounds frame = CGRect(x: screenBounds.width - memoryNormalWidth - 10.0, y: screenBounds.height - memoryNormalHeight - FOOT_BAR_Height, width: memoryNormalWidth, height: memoryNormalHeight) let floatingWindow = ZSFloatingWindow(frame: screenBounds) let floatingVC = ZSFloatingViewController() floatingWindow.rootViewController = floatingVC floatingWindow.isHidden = false floatingWindow.isUserInteractionEnabled = false floatingWindow.windowLevel = UIWindow.Level(rawValue: 999.0) floatingVC.view.addSubview(self) ZSFloatingManager.share.window = floatingWindow start() } func dismiss() { timer?.invalidate() timer = nil removeFromSuperview() for window in UIApplication.shared.windows { if window.isKind(of: ZSFloatingWindow.self) { window.isHidden = true } } } func start() { timer = Timer(timeInterval: 1, target: self, selector: #selector(timerAction(timer:)), userInfo: nil, repeats: true) RunLoop.main.add(timer!, forMode: RunLoop.Mode.common) } @objc private func timerAction(timer:Timer) { let totalMemoryMBytes = NSString.totalMemory() let availableMemoryMBytes = NSString.availableMemory() let memoryUsageMBytes = NSString.getMemoryUsage() let usageMemoryPercent = (totalMemoryMBytes - availableMemoryMBytes)/totalMemoryMBytes memoryState = memoryState.get(percent: CGFloat(usageMemoryPercent)) // memoryUsageLabel.text = String(format: "%.2f%%", usageMemoryPercent * 100) memoryUsageLabel.text = String(format: "%.1fMB", memoryUsageMBytes) percent = CGFloat(usageMemoryPercent) setNeedsLayout() layoutIfNeeded() } } ================================================ FILE: zhuishushenqi/NewVersion/Discover/ZSDiscoverHeaderView.swift ================================================ // // ZSDiscoverHeaderView.swift // zhuishushenqi // // Created by yung on 2019/7/7. // Copyright © 2019 QS. All rights reserved. // import UIKit typealias ZSDiscoverHandler = (_ index:Int)->Void class ZSDiscoverHeaderView: UITableViewHeaderFooterView { var items:[UIButton] = [] var handler:ZSDiscoverHandler? override init(reuseIdentifier: String?) { super.init(reuseIdentifier: reuseIdentifier) setupSubviews() } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func layoutSubviews() { super.layoutSubviews() for item in 0.. Int { return 1 } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return menus.count } func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { return 70 } func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { return 100 } func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { let headerView = tableView.qs_dequeueReusableHeaderFooterView(ZSDiscoverHeaderView.self) headerView?.handler = { [weak self] index in self?.jumpTo(index: index) } return headerView } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.qs_dequeueReusableCell(UITableViewCell.self) let menuItem = menus[indexPath.row] cell?.imageView?.image = UIImage(named: "\(menuItem.icon ?? "")") cell?.textLabel?.text = "\(menuItem.title ?? "")" cell?.accessoryType = .disclosureIndicator return cell! } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { let menuItem = menus[indexPath.row] switch menuItem.type { case .vip: let timeInterval = Date().timeIntervalSince1970 let url = ZSDiscover.vip(time: timeInterval, token: ZSLogin.share.token, packageName: "com.ifmoc.ZhuiShuShenQi").url jump(url: url, title: menuItem.title ?? "") break case .free: let timeInterval = Date().timeIntervalSince1970 let url = ZSDiscover.free(time: timeInterval, token: ZSLogin.share.token, packageName: "com.ifmoc.ZhuiShuShenQi").url jump(url: url, title: menuItem.title ?? "") break case .manhua: let timeInterval = Date().timeIntervalSince1970 let url = ZSDiscover.cartoon(time: timeInterval, token: ZSLogin.share.token, userId: ZSLogin.share.userInfo()?.user?._id ?? "", packageName: "com.ifmoc.ZhuiShuShenQi").url jump(url: url, title: menuItem.title ?? "") break case .personal: let timeInterval = Date().timeIntervalSince1970 let url = ZSDiscover.exclusiveList(time: timeInterval, token: ZSLogin.share.token, packageName: "com.ifmoc.ZhuiShuShenQi").url jump(url: url, title: menuItem.title ?? "") break default: break } } } ================================================ FILE: zhuishushenqi/NewVersion/Discover/ZSSearchViewController.swift ================================================ // // ZSSearchViewController.swift // zhuishushenqi // // Created by yung on 2019/7/7. // Copyright © 2019 QS. All rights reserved. // import UIKit class ZSNewSearchViewController: BaseViewController { override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) } } ================================================ FILE: zhuishushenqi/NewVersion/File/EncrtptorText ================================================ Yg5fsKPOvpii0qAH2lpmJhjFmRhfe4dshhjY5Oim2Y/T6CMmsuWy72yLxVhY9R6qPmFj/emfQqDl3uH5M6ZHAYr2q+IU6rIl2d2GWnEio/UvN9Kd/tYTiNtAQQK5p+LKRQLobggeH/USnQcwjtkrLrTt+o7brRD/bpw1M2Zz9Du0z0dYtiQcdc/gvYG/oO1HtqyTG20xy20tT0tZ910+wNc3ApT1Dr9EY/VhAU/N0IUaot/MVqoGAIb9PmuJ+S9qAK3bZITZLtXc3UN4XAROExCHv5BY/+SljE2b4EQ6266YXHbEB8IuWArFjTZ1hocpiy31/jC33QRtxsPNi85j7/RxoI7dwvRV+4cuj3k+rrVzlzVicmAcbpU6FhqBCeZHKVDbxQFp/5+o6LILqCMaYtp7Te9gbzkbvk6VLtqNydyYYBd4ojuF6OKmkuKVrh1aLhueejMzRvyYwq8i8Hd1URGPZQEdEhGcxoo3/Zh2rn4tDn21c/a06RdsdOoZhBY8CsYz2E1yEtWdSlrbE0U9mRHstXUuNN2zStRDTQSKjD9513xba5p7Yx0mma44kmVgNMbKg+eH9AYoNQ2vqL2DEonNyvgDuo/ieFey+sHUDNVW3jXgZa5x++/2Ra9LZoJ7tMaj74rVoi9hyJ16PXIc7ay6KcZkf9mI9nsKA94NDXr1ADqJXE6lCwTXkqMQldbxBm/Sr9cEJiWcLnEj7x8qiucOeYAlAhAVk956Vlw1siscIc5j+drwaFo5EYfoGB5pFr8IUtxR1YnxsP5LtjGScuC3L0LaRoAGIi6RgnDFGZW6UBlcPIwh6H2dy6yUdLwisExBhf07w2KiogVZxXF9RU4UK5/2KpIMd9F7daD6X/l8RINx7z2Tk3loO6odbCL5oeHAL4R1UVCdgRLuA6Wg5SU6qykO950v/XFx311Cfc5SFzyQE4qnEWrgUg5RyJyiPwJenciA1itKJqLnsnxrmBkKVxOe2zEJnW6esj2DpfYuS8VjKAf8WNXUFKTUuFva059qbAzy2HahzgHBLOk9mNRE+owav8v0/VtawsF3yJ5jFQuUNDnqHUYZ2oFmKs1TFiuNkZiR3G3Aqcth8wH/ImxW43kNkedCZXjhsBlD0bn/qI+09TF77Q3qnkivQVYBb5aaOMGfA8rNG2Mh1cJ9vjYpBWA0MzE0hjanCfqtUfyn5EDeMQbTsvqiWtfTMS3NJLzFAX36sdxPLVN/nJ/rh5Ef3lDE9pT9la9DzpH1MTiqqw/a/kFOCQTc4EkUIGwmR7+pzq9CmY0QlENt6fy+heIQQjYkGEMox4F81fiSu/iOnhnlhFXIywYG8zdJPDoP+XFECjEubMxv5SjWG8ScS1O31gWITPUbJ3ZJTTJApvyWHF0OwIu0yICRw+rMM98YPMbbBD/bT0cQWMgAkARqWDtzWptJIynixCZjnaJ1f6xyaXJT9T4vexUVc+BbBkgp/B09L3oCYtjV2WY0Qn8lsFJdhMfxCqvpxNqgGAgMh0fcsFCl9nhu5C+dtNcFT29tDunc4+xg9dsiqn4XygdP1nVVxVmQq7Nbr62L23ZvKBaTUFndvgS0M59mQGWhtGODiDu1d+r1JYyASmXb9hvS3XZQgRaRGt3dFRh1Q6JVW4QAC8VipVjuYLpkkbhj34Qtply/yrcahfGqToiPacYTdVxROkJNBLoUrxy7wCFPZMSfORnbgoNlWp1zkjDJEClP/ANhnzgErHbMry4hNu0Edt9EQSxrmTbc/C08LQePAyH7j9i142pYx7hfQsfgkytqtOpgasHt/k1jNoUj1fiIWtlqOvjBnNj/UwdY3VlmlNQ9IRgS1J2wtSS9DtlvDmOh7ydJkKoXgjjg/QeWkMDYI7k5T9bNisBMxFI2reT1GIz/Oid7GGy+teWybxfJYsgHEALdHevzKYc1j889bAUKk0rpHNz3Kh3FZfDlPhpVYISYsVQsLgH/Vg8FAOfmyZwPuBErGfGzgyduOR4G2jtngF/2+9qH70WgxAVu+KFVy+i453LxmHwmMYujUC9XObQ6bbQ+WT2qjx0kH0CImielDjmvqShPiCjEH3LEsI9sWFBXOWka0+K88IxayhaaTWHvLY2jeWjKY7Wtlt/MhhNAU9FgJczmgVsn424vs5Sf8qSv7SxhD2RrPk9rdwwIrPfdkSUf7lxf4fPKKIaNL7nVKZmrAn2FvGQOcIdxWpdGO6RD1GW2c8c64bWjeXvcYG9xXYOmuifuynZsSWICyCqNbFnbgTKXr5jbf28fb0oE1T4V4KBiZutYxVn0u0LnzWna6CFruyd9IpWievHsfSGeqYZwiEo52MwzhH27x0SoWN3bqjg9HYy++gNTM7XvmernG/AFO8R8Jfgbs/pijXldEB16if7LZAvyQWPDT+ZlgVKm3zQRDKK8u8U8Og/MjSpdlCeIxiv9opndDcY45E5TEoicPhsd6ViSGsfWNerUMOauNeAb3JtlK0BoDUagN38y5FsP826UijWLBCF5baAY97c3QmCHvJ+LYX56Z8dI4vqtpA6uCILpFmgFOzZCB8DLSd5k5ou2TV8kwvxH+oRWIiHjSTGPDyhvqKqgTCDyLywPYTEOfZLHeJFkfIjNkWTS24E76FxA0LwPY8uRPziklC50UxuTL8GqiAs7Vijm+u7y/IMLSeVj5Fu4bspY0xF11iNpgroq9t8qrkpB4V9YcBBqCk2X9/RsCW33mBZY25JOo/8ImPTATUVlXxNL4oWMATTkwZZBN+vbyDLiEaQMPINwXg1vVpQT3fQLvw3uqAkYLZDQipPrFBrWjfFeqt1qlSNKULtyvPdwsm5PdgMfU4dk1bupdu3rX0IftK4Gl0/PqJytRMh2ImAxyj+k9loIyagpY0PrA/qlnuyQ/kqRnW1eVXV1XGoOlzfcFsk2I8ZGLb1xZcrkP3bCZPwLGO4iP4QX9x4FO+rv+teFOQlQwGrqeRrjBRQFFPvkowPVo49m9sPeKU8aN+NjFiIQZ9wjgvFrLu+bU0FtX8gUn0wyv9wTrspSsn/GaCp+HZvCvLykYi3Xn1G2TDj852CRIJx8Br0jsw7tEZ8VXMJu9/vAaMJOdjibv2L7XjKdWQDBX5YSamPjlmtROd5VuLfTXOkmKatgviueCPSvrnpCppZCOvrylUsQ+jGHHvOHF/UiGkso56cqOX4etglAjpuuXmz+p6qQdHajeYZ8j/+ibyKAiAOnEkpb4mrLrggx/YtYShW8DUR4iuYgM7YHINQRFWW6hJ6DXIJf9G08e2Zb8+381KleiMri7uF6k8Kl00xHpMG0hsRMhJ2W8K0xKyjfquZrquaDfbQTar3MU6Z0knRJsIoZ5cqvG1hqjTJp5kRvo8ud+EVltDAwMUuYpveNgJ5spS1n0i24CPz7W6ATHWVTuz+F9X3Kr0bE+QU3PqW9RNnNzO0Hb9A22lgOmIJ+s9Rq6q8fIVyYkr2fiHWto0BQQvSb7KyCsL8m6IQ3Fcdn2CtcDwBrhSJ3H6mMMEXvwlZDoG8+4RqJRBLG+ZQLu7JPe1kZHaBY8TcunjYLYVI+rz/wxSYfHngIEoDeTeFKbdxyYiIR0s69tPN8MLyEaj7kaGfVIjLRRX+w2YlahT+4ZFtLGd3EzpqaMyeacSEBgQXpMbf3WjlG7E/1d7DEBaHa/RFahDE1QWfxPG2I93oBVcDFfrn0JD2x+terW9pUWvV7FlS9dSvJFI2/sAEkuReshquY/q+DQSS9w0+QMzZ3QxM8zJFQRXq5NFSpsfnu/ANfwuFC6SRfKkCy8uMBu0iBADBaKEXrAtUNRtdn9QjRzftflqtFFHvYu54h3pH1eScd4w6k/TwOjjV7r7CyR9G0JypIQD9LSnp0JGKXwKY4OGfxi6YOVJFB3DzS7idz6cPg4lLf4q3Kou9QL//tfn0OMKOS0RIRrT7ZZwFkqQ9Gv9b+Kb8WJyHeb+Nw/E9bFHPdZlAnJRsqYGzzxIkRBCPRLmSHjUtgB9MMyX8nZH8flqvpVGsxsqh72Xbjd2sJQWHh2bZxpgEyeyOx6hpZ1KvGc+9TM+7TqPHW66S5Z5gI4yeiMTqwv9yjk/Ypf8YVc3TeEIhCwJxIWqgPpI6G7POAzYKOoVjp/xZjnWW0LWIbQkvcUGwMHEN/h0K5l+xVIRnyMXwjbB0aka2qwO3B5BbnXPu5cnWJMGH4MNXlUVWw0A7q3ods1+4IlO8Bj/uovtRQopx68pndOOGCpwveHOGlf7XdM4AeFpgRR4psFFF3PL+VKolPQDgv3pGCzZSGQj+zpUyFzx0VmuYcitu2bn4FtW1Aij09s0PBPLBI7q3NeaSGjPDEaWPaf7kBrfw8ySwYmu1KUl5cmmj37cHiOBOdXf2MfYxzTS9LcMrnbvbJSlTnn+UKjmfKBdGxz5R3sWPxcjkwYc8wszbiyVEekQZo0sYhXGBpnMbFyXWm9Rp+HwZJ7AUFdqbxgFVuGZKdo4gp795QClWtRGJOQll8n8Vr5bxFAeXoHWkLoXImzDQXxTGe+2MzHEd80kEenFIORcNztTm7FBQDZR+qdm+J6Tnc7k5PMzJWWBFvc49VV2OUOz3jkHwSoeP9Ol2gMhdSQPGGipVyi2ONf1MBgAnz99Iz8dLNDry38rWwTsHFNsNxIScNtSt80pLoXA2fLz3ihwyuPv79VB878GuJ9Cj6mShS5hiafhQeD4o93KE4wo40+bNjRJrN4iWHKNB4dco1Wc6qG44S/2hbJ/KBDfIcleXWNgxwHdPC3ErUU02P8WYfm+CD4+SDT6tjbt7+Kuvp/Id5NvDbWb7Pt0MZgnw28sP2hDGlazEQDnLOz7yFKetegzYDQ1RF5i0LXhDGNuGMGALkAZsDzBpDid7RwSpiyhQ9Vp98voXmxV7fxqidbqKV4iuZkHVm134llvMrfI8HVR1DwnYnsdkJHtpqpdG8CmVeMMw3scBciJMZrgJvxGu7Wd3JZkSOW9yttqY99ttNtub7hD3WylOAYNDoZqhGGAhpZD6c0YePA6BlEtvpJ1b80wUAn85pqAIe3skoMEe2+8Pi/NkR1wP+0I/EF3t4NvHR5y/fee6c7SntNuU1bMlEya6FH2dI1+mTptlBOzrHbUuU0cdHkSvlI7XZq4of/1Y25Z8T1JE6UZKplCuDVNVj+XDpRcYQyjygpg4hGsWPsnr7pRZ/nf6/yUFgzdFltBLpWl2x4gRccluFpGOXyggnv+MqJgwhukAlK6Er2dYW0upOlLUsgCzS05VSne/ZOegFnp2SBI2VcCrWdBWN+CLgKUgKIYd4FjnF8y9wrLAVRsh6uBWKpjjcuMsWk3Ri8I5kmN8tz4TaU8Rc+sL3NC6/yFL1POc+W0EWO/GRccYHMLXQk8n2imJyaxji0hWly8bt/hi4fTH1o3j/p+1hZapnCG7zfgPxZYTueQ148bb9qxyvNYnZEwoxoDEwAKRE86/oRRxeJXuok3KHNNeywHPgGuvlmE3Ptv9Oi5VhfeTKvesjFICUbig6+WALU8qCfM+mACBHmEI3QJXWcvL9m1ssNIS/IQl1xvOe3N5rSzy0UcQOxe3ZRt2WKn8Lze4XyeAV5byh4Epb5sxxkmhZPUVyM0vyNwy9+9XbGYhLKfVtauMLmz7B7pRghB1Ko7iNnm9ZDf+CdpiTwzI6DKTBYEWh9mSxJvyYKOIG4jnRzHCbZf7B522lXLhAq0z8moSB4Z8quIuMak0axu1QyA0tZxg/9vl3yxflqblwmJoaBPPeUeaOSU2OK4bjmn4H0u2E56iwgqJoUMIw8ZoLkJxc+LkckbUqu/9/HWOwDMowpwBb/6b0NM5onHRSkjpgmYe4BBSnvBuRqkaMZEF2OEJn7zCEoiHveGi6Ytl0lW1urbOBbBrWouQCwitn80+OdH4pcucdAlArrfMvVJ4cWUbW4c0WTgcYnxEy9JoNROAvLTR9nOuxxb5SvYd6mU+aYc+e5O+bMYeirJXUNeLTSRBUVhn+8iv0MIf7yOoou68ak9ozvSCO1CRVwIEjrqGUznfpuD80lJ+dceBeQZqGQCBzJWPIgw1obLG+Y7ReKvpS7gSgNeHP86uLvvYqkSqyal2mjwdl8OqSwGVp06l8EzKiEdrPyNmjmzkegURBhGGMDUZCGHE+0B5cy4IwI2b6hws9ONGsC3H/3BOiTdaK19HPiFaPGI3KmwO0pUqolV2sGy/DJadlvQ44q1rzOi+pfQp+Q5321+iABvnK8javFKI05fON0bMEec3ltxg1egPjAS51SYeLncVjGzAQzfvaeantiFusw8XGd6VOSWFF7SpBJEZeQURYm/SclOnWwQjb5SVT72HOSjzEWvgdQYsoyEx8qNWqywoD+7bmMiOB2sg64qQU0cMY8QNAWku3J5xw2tFw5969ISHTY7IoQ8r4kl2VBnQQosCZI6ynCZTFKAwSpBmT6gGEgbAHe0RiLkz97dArTaFHFBupFwXFdpMLzF+Qw4goYumEuhA8ybbKKeFLecAyWWMwUAwl+QAesiQR2N5bxWtqoQ57rUL0lSZbzc74C2Lzd+bHZi5OBNVK9tKqFzrSKnX3ePEnP+VTG4/q+xvdyuJUVLBKISIRnPYDvX75hxYt1QpAbcceqWchoVJ5xpD0qQBMlxAaMc992XZ/4lCWjTPVXybyGTtYnT6VZx5jPSzaeTetw6OuavtEB4AzrnXapDGgDEBRbh4t58gR+gGzaSnYR+o2CXFbcIgDCaKT/YViZ09Z3SftZbkc92yXaSBm7iH1jYnk2VFacL6MkpbPmTyd7SV4rB+Qami1sGc4LgB59e/4z2RTATD3+Ls+PjD1XlxziNKvXdbc5kuRGw6FuQGgEXX+cJe8w/eWBFYcwsvwNzlyPP/xDjEnyDrKXc2YPt77zmYgsyIOMX38FqaAwAYsLrxzApDcPPrsnYOxkzR2820l3Tkr1+oVQiQZnmSRlYqNbd9c2hUFzp6I/d70DoT1zcgdUY7JBdzOXpxO4mqJdY3HraLXnETZ5gho71wfuVhyMGQmtPy/irHD21LP8kEmZJ9VSolyrTMgndK0j3efLyh92Swqh28nR0u3MH/F9Ipj2vKZO7BBi13QnmJ88gDIzSGSZKxHKPatg2xLYGBl/SmxbhWC31oacz9hZXcEOgNJWqTGNnUUU8vPMqNwz3nbLwc5D/fqfGAGTfVLMSRk8F/1R+Z/UyMhe9zKY78te56KNQuG3BGtQms2IGs6czBC3WKTGwXpr5OF1rFlIR68HqD60z79vKifNL5xRG9cc97FDRKnlv8/a9A2aNxIY1K70xs+1rqMaqM4/l9hn6wPA3j1y80Xdkp9a3Op/XLOXbPYgbWcqxp8q5dx66HegXg5wYwL2awO2s8aEjV/nuB8IhK+I1Q3sZ+GoB2XWr3woObSOhZMZhUIPoOad1Db0AbiudjJeUXQHCiW3bUg71oDq+QbrBi13lqOXW8NzvJRNhL5yPuj3PzOk5SG0DvZXw7mdI79Xqbhmh5KqMFX6X8/PM3FQdmcg3mGSkI4vcPEh5BdOVsI1xbm3ZInhfOVwlnyEi5QG+TxyUw/O96Ed/JOpgx8wsRvO7Qn3HuvOFehISqjhywf0g9SQKnJRwRhUVUAQPUZuu36hlRLvofJzqQLYAgFxlknSZdJZvyZLs+Xcmfco7/ksLDilrxlWxuYtLttjS5h9zPStkPV6rTPWIRxXbXuvzdoPcaqjmCDGx8jOhuFSyM3ZTTKHpCptcnJLyfA2aVCC+ag/qocJ/zyboQiBZn2ZRk770VfKxvZweG8U1RzWl9VJeYT32adCcFaPpsU6zXwRPZeGmqFHiC+PsrKvf8xO0YXULdkc46sHznTn3Ufpb4Bc5TBKEDgOK+9TIylv2L3ndmIujOF3OlOvcFSw2LyeyctDFCivFTdTe6oIgIWsowTSbC5d6NFixSeUwN2dlTH/Y4wb9Mly3wFSI6mEJNqlCwZbwN+/LSKJWVHGWplgNYL3zYF0BAlrCQblZZ6lPT9m/LwaIw4rcr/DxQJKya9Gwr2VO7cxMlMRqLQtXgonFw4C44S9l8Dm8a7SbvGUDTpOOTfi7HQlqctAVsv5WkEYW9iiYT0QWrHsqOt0ToTX0Sn9/aSxBE2KlmniXYVG5qrO+sudwXa3rQLCXGNQcfB14lOxapDip/Nf/a0RqMdSd4yMDbdzXrKc4PwlsdCuHfoqAehPhF/wrjj655fsQMW6s04XCn+coFO3ioOgv8E63j+8rvsPI33iwFJGQF7mRVFJMdhHNePlanjh9FFia6Qsxy2RF27YxzCBweNfs22MdD0tuUIUlM7czx2BE5GqH0vv6Jn3OHhgiaVagj79r18EkNKN+Bd8FjNdX0bWmP8DgKK+nz5ES7Rwlqg+HEv1m3aR7Z2lBNvGUoj2B3EFZyEgTIjQP8otpQkP9kqckzB2KdbcvoFTDTXr/JEPIUUvImIwi+1T9WwG0MwbfFbxQpYY43VCCxYMbg5DjJG0cd94Z40M9C98gYtHqSrZmvQ21hUbvZA/nvtqg+adyvfrjWgMzTmsPtHeU6GDqdgn2ffmomxmxmUBvo5y7E997FVi6ICNIlvMPyD1lxm2DUvF/L+uGDppn0JqxmJK4Z2pHy6VGxoHEb8mtK7cW93BFa8Aku7mXThBAHYUaCEwSbUJQFPbgq7ED+i1L1q/Xh9LgeMZiRVX39kJSOwCYwix49vgXi6KYkHTy4bXG2fH/W8uEg9nrbyze7168B0+T5kTchHVZInMEvj9zbdQV1132u+kHg9Ye0uT0PlAgsrHBzbpvoNS5yzsx26jge0xYg6lplQfVtdEVLyLEWrPmfNmlk8OZYKXCm2xOmw0Rt0Aua8ZKcEp68694XG59Q3XjJejZKUOoRwHZkUvsYCf/9tIvdYiOXr9H2KsOpsmQb9Mqk5jUfRA0qkiAmhjHqEXyXkReLTncb1aL8sFOH6h3gMawyB4rP9KsBj2wcl9dLvlBE8NtKvY1rOtMwqu+X6hSuUcNG7AGHT4Oql1CKFpP4YGfabkfgl6i2jcSFJQsQ+DU75j5N2qWK502XJTydHeqDCPbPbM2gq2VND/FuhxQTAkONpzkXGuzJeXgHdkil1oAvy+PDQjG799WTQrNyP9zPyE4UDQlUcrSNdCP521V6IXE/74AHj11QRoKS6jVSgdZKTlWgj+vIzpA55GAXv9gpyXG7Ml+9uSSSmknKUONNCd6YnJGeRyx9fPrgyl+znETLhd2VmLwB1h0Ub4xWsZcZijp/nHaqf6AnbVSLUxiA0D+lBBTefDAF2K+0RPHki55RX5Y0q7Oxq3Dm936Hv15/Axo86/lVM9aFQo4BRgwlXn+uYr+elWHdDtu1VAvlQhtpUwbH8vz+X6eXvlsHQAVms7FDhMYTbZq3WaWDB7fmDV3eEO0eg4QbyrosxH0kCs/kWStwhtLJ2lXwZAkMZndvXv8fB57BQWnYy0cyZSA+uA2UorhL5yqUxngowQHZ2fdWf13gsEZ+9T4dgs55uAQKWulvrN2YNDcDfZjK/ZkL7L+bmigUYDn6icus8Fq8g2lzZLUCg53318hLaPq0/EjiPcmexPM9CVg6G4vYKl5+YXEm8WGITd5oixGCOQyysG4vzKWbkbyiFDKuh4X7o3iew2F/mQHKBZYNALRwzNyYWn6KZ+8WsOkvNADbmcg/0V3rm40qs4AlBXeX6SfJQ1dUADFPA0hVIqPtDfzP+WRpKGu0xL7np/siLsp6UZrQRRDegRylmGTaYhY0bOrO8lEbL4Ef5mFTyRMWRLhU76TMlzEAiWm2MKGz05TT1V4GObmIIxY++QyQg2UMoJhBkYE2gyakLlrtbdHETtZ9TO+x+Tbnrz1QD2vF+SExMyUcGbCGedoBR+aJWdqOAt3dqZHUDln8+PsSg5RrCOvH7Dpg0CCCY7QIyjIjSMfGy/R0jgXU3XqopsypBw1Va+RzK8jG1nOCowXhPQuF1ziaLJg+lpRnOIcvzOV5WCYcOTpYFxo9/clLbpK8mKV5ZCIW71L1AcREHUlskD9gbYKGEhWX/cLG85TehEVnNrWXYlc2CUl94RKrXAiN4ClzcMTsTz9ptHLIQAnlAgVkShqdppXU5Yrrs9n1XKJaCAYEDVYcLYASQqhykm7+uZslKzfgMC628pV6O1TcFQzsucGxpSkMDrLZ/aLn0vAPZLLSD1U2XWx73ZKhIei727TxSBZEL7A+4WO9Kd52HNJrPNaG4ZuGWQi12mIoR5wshNbEk0fypHTtJN+8qyGDvqYdYAHN/uow5WSOS4378u0oEJUnhkD9xYOODMRNseZVCPxbcvRqz/C3IIpKXTDVLmktAbZtuZSu9Qw2sb+Fvt9eY916ln3KTmCIHTfOudWQFWMmJqsFcnnDIYBUw5ReCTas96M629zuYGIcRaarzte7MCR3CN3pDZZeWXB63ZWjrAaUS/PmX4GeksPnSv1Jt2Js5w3rpwLVs/fLOzlPZnvoXaaSzoeIX319qnvH19bWu72cF6fiys8gZvrgW8et4zn2A8F11Ql0VYyqyRn/CECLQjE0bM7mmuUNggjWK6pV1gOpk/rDzqnYTq1Rh/xj4KCHdQBNZUNhSRTw7xjZ8a13NkJWpAt1vMJGhQl/xFujnQO4ZLqTkiL8W4PQuFi17mm7ouZIPKJShzlmoJGZ0WEQo9WSsVA1O4+GAQFWpEpsmyCkrooRD/wtxfdeXveB5vSqKdgUvB37Agwh/b0oaEv5WvQjuEK0nBJTJmxVEq76G652Zdww6F23ncUOcZufqxOINGgTVaA7NJg6Ft9w6n8ztFoppk9OcvsUBRXz2f4syGcFPYOkGgEEutQIaNf+BimfdPGYUwbyFNlfgeT8fkXHarncJMJB9WAGAvABpoZui1g8+vODpxpRpAtWv2aPTWa9/O5mcPxyeAHnMQJa7UbScuDf6OkjDxrkav4X+BUemAc87seEgORJYXzlAdYf1aeEVU70eNJRVgNpyeBQo8OyyAQJSWunOLKwr2dpkI01Be/QnOZblhssKV8Pr+FmGl6pt/z0Z5KrU/zjZE6PtKSqNdvEFWLQZX6PUDme4sIsIGA5+pxShjVQrKYPpBWh8qdWyDSnBWqPDq/vp7xFYAgAsHQXVlXDMc4t1MENW+10GEEUrWLzdzhzL//2/XH6695HR0P+KG29mGT2ZvpQVjN16Qu4UPJuKV+GddXh6d/hnLeP8A7AqmNvJAhcsD4CpklP7Eqn/6IYoY8MRYAP7UTfAMNZIjCEK7duyb3UtHcHZn0TtxpHyYwWPLZrxukx8qDAlztfeJBUG5Kzl+A+v5t9BZkqUxvZh/xUHiyn+Mees4HKf2q7+uvY/xIQ9TFl1v4ZxwB8kKxQaz5++2doFSsqFijRmlsPMld2rr1894sVpqZbGsL9cgNOvFmnX63JcQW7kqzuHjV5c05nippgfa2Y4gOUZ6zgFt4L8k4ILWFW3Vw71DsMWy6BuvQvHmATED36DbrkGuYFcCB+aJjSuQEiVgr5ZfDk6Ny6N7Tl1aAWc6MTSzy5Cpm8jfqgFGbXGlB54euebLKE5z3RE5WzRuVhvhLmG7MUtOlPa51x3tvl2F5kSA2sPhM53Li5Hx3G9HHLD9syvtRL762iGE5bBc1B6ZTZa6/Wo/BVzv78WH8o4mp1XkyOlLT1kqSKcUfkiO7ljw8WCCjTSH/0G2QSr3pWeos+p8eWJ94e7ht7a6R+jw827lexhQ2WlU83pEMrZssS1Cg/UzLoXZxTlY8xEqmTYWBoF+1TaFGia9a7tZpKm7m2ltSnXDQiFnlvLcIfvGZKde+R9d+sxbpELNC4w32dlbKgGwM/rxkj9PhvU6ajZpP3wUpE7aQsG1qxbKKfJSbtt2DhErcgdCfecIX6Dih9Eglj174ruaDHtufTmQdbVdjpVjy43JJM3Kj2/8yYpJWNWLjeoXPFn7ZoOAevG3b3D7IQhdEuovNKSitCeRokXG3f6JPIjted7LGnPI6C1e1Llc02tT7dn0BctN6YOMnXrKqAlJFPIN/VQ2VAAjOnegWHVa8Om0xhCdvLT7J09i3QXZzyzJ8iTWbwl4bkpaGMlxLin/ZuULy2JNIBWFulTnXwI8fOsIkxt5B+RGV2OscOhJkUJTzNLjGfZdEtE7Z1fE61IMnctYbJ5xgZ6nqBmShriup79OMQj4SyITzdRMCxUmo1WLQxIkbj7xUV1zx1RE600gwC55H+x84eHlcQhL0vojFBA370M4sNIc4RGBVgdlor2y9nL1MEL/ddiwOno5Eax0YA9w+2Wqvd6pcmhDKkETJeU0WEGl2dA2IG8DucdmHxxvsHOTmq17nYjtDTWi193vQqhqPaLu6jTN+SShlruIkJMkzBwXRfT6fkjAqAzdQKzXE1gtZQ= ================================================ FILE: zhuishushenqi/NewVersion/Mine/ZSAddSourceTextField.h ================================================ // // ZSAddSourceTextField.h // zhuishushenqi // // Created by daye on 2020/12/25. // Copyright © 2020 QS. All rights reserved. // #import NS_ASSUME_NONNULL_BEGIN @interface ZSAddSourceTextField : UITextField @end NS_ASSUME_NONNULL_END ================================================ FILE: zhuishushenqi/NewVersion/Mine/ZSAddSourceTextField.m ================================================ // // ZSAddSourceTextField.m // zhuishushenqi // // Created by daye on 2020/12/25. // Copyright © 2020 QS. All rights reserved. // #import "ZSAddSourceTextField.h" @implementation ZSAddSourceTextField /* // Only override drawRect: if you perform custom drawing. // An empty implementation adversely affects performance during animation. - (void)drawRect:(CGRect)rect { // Drawing code } */ -(void)removeFromSuperview { [self resignFirstResponder]; NSLog(@"------- %s retainCount:%zd",__func__,CFGetRetainCount((__bridge CFTypeRef)(self))); if (self) { CFRelease((__bridge CFTypeRef)(self)); } } @end ================================================ FILE: zhuishushenqi/NewVersion/Mine/ZSAddSourceViewController.swift ================================================ // // ZSAddSourceViewController.swift // zhuishushenqi // // Created by caony on 2019/11/11. // Copyright © 2019 QS. All rights reserved. // import UIKit import SnapKit class ZSAddSourceViewController: BaseViewController { var source:ZSAikanParserModel? { didSet { reloadData() } } lazy var booksTF:ZSAddLineView = { let tf = ZSAddLineView(frame: .zero) tf.placeholder = "请输入" tf.title = "books" tf.delegate = self return tf }() lazy var searchUrlTF:ZSAddLineView = { let tf = ZSAddLineView(frame: .zero) tf.placeholder = "请输入" tf.delegate = self tf.title = "searchUrl" return tf }() lazy var nameTF:ZSAddLineView = { let tf = ZSAddLineView(frame: .zero) tf.placeholder = "请输入" tf.title = "name" tf.delegate = self return tf }() lazy var hostTF:ZSAddLineView = { let tf = ZSAddLineView(frame: .zero) tf.placeholder = "请输入" tf.title = "host" tf.delegate = self return tf }() lazy var contentTF:ZSAddLineView = { let tf = ZSAddLineView(frame: .zero) tf.placeholder = "请输入" tf.title = "content" tf.delegate = self return tf }() lazy var chapterUrlTF:ZSAddLineView = { let tf = ZSAddLineView(frame: .zero) tf.placeholder = "请输入" tf.title = "chapterUrl" tf.delegate = self return tf }() lazy var chapterNameTF:ZSAddLineView = { let tf = ZSAddLineView(frame: .zero) tf.placeholder = "请输入" tf.title = "chapterName" tf.delegate = self return tf }() lazy var chaptersTF:ZSAddLineView = { let tf = ZSAddLineView(frame: .zero) tf.placeholder = "请输入" tf.title = "chapters" tf.delegate = self return tf }() lazy var detailBookIconTF:ZSAddLineView = { let tf = ZSAddLineView(frame: .zero) tf.placeholder = "请输入" tf.title = "detailBookIcon" tf.delegate = self return tf }() lazy var detailChaptersUrlTF:ZSAddLineView = { let tf = ZSAddLineView(frame: .zero) tf.placeholder = "请输入" tf.title = "detailChaptersUrl" tf.delegate = self return tf }() lazy var bookLastChapterNameTF:ZSAddLineView = { let tf = ZSAddLineView(frame: .zero) tf.placeholder = "请输入" tf.title = "bookLastChapterName" tf.delegate = self return tf }() lazy var bookUpdateTimeTF:ZSAddLineView = { let tf = ZSAddLineView(frame: .zero) tf.placeholder = "请输入" tf.title = "bookUpdateTime" tf.delegate = self return tf }() lazy var bookUrlTF:ZSAddLineView = { let tf = ZSAddLineView(frame: .zero) tf.placeholder = "请输入" tf.title = "bookUrl" tf.delegate = self return tf }() lazy var bookIconTF:ZSAddLineView = { let tf = ZSAddLineView(frame: .zero) tf.placeholder = "请输入" tf.title = "bookIcon" tf.delegate = self return tf }() lazy var bookDescTF:ZSAddLineView = { let tf = ZSAddLineView(frame: .zero) tf.placeholder = "请输入" tf.title = "bookDesc" tf.delegate = self return tf }() lazy var bookCategoryTF:ZSAddLineView = { let tf = ZSAddLineView(frame: .zero) tf.placeholder = "请输入" tf.title = "bookCategory" tf.delegate = self return tf }() lazy var bookAuthorTF:ZSAddLineView = { let tf = ZSAddLineView(frame: .zero) tf.placeholder = "请输入" tf.title = "bookAuthor" tf.delegate = self return tf }() lazy var bookNameTF:ZSAddLineView = { let tf = ZSAddLineView(frame: .zero) tf.placeholder = "请输入" tf.title = "bookName" tf.delegate = self return tf }() lazy var detailBookDescTF:ZSAddLineView = { let tf = ZSAddLineView(frame: .zero) tf.placeholder = "请输入" tf.title = "detailBookDesc" tf.delegate = self return tf }() lazy var contentRemoveTF:ZSAddLineView = { let tf = ZSAddLineView(frame: .zero) tf.placeholder = "请输入" tf.title = "contentRemove" tf.delegate = self return tf }() lazy var contentReplaceTF:ZSAddLineView = { let tf = ZSAddLineView(frame: .zero) tf.placeholder = "请输入" tf.title = "contentReplace" tf.delegate = self return tf }() lazy var contentTagRemoveTF:ZSAddLineView = { let tf = ZSAddLineView(frame: .zero) tf.placeholder = "请输入" tf.title = "contentTagRemove" tf.delegate = self return tf }() lazy var searchEncodingTF:ZSAddLineView = { let tf = ZSAddLineView(frame: .zero) tf.placeholder = "请输入" tf.title = "searchEncoding" tf.delegate = self return tf }() lazy var scrollView:UIScrollView = { let view = UIScrollView(frame: .zero) view.backgroundColor = UIColor.white return view }() override func viewDidLoad() { super.viewDidLoad() self.title = (source?.host.length ?? 0) > 0 ? "修改来源": "添加来源" setupSubviews() setupNavItem() } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) navigationController?.isNavigationBarHidden = false } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) scrollView.contentSize = CGSize(width: self.view.bounds.width, height: 22 * 50 + 19*10 + kNavgationBarHeight + 20) } private func setupSubviews() { view.addSubview(scrollView) scrollView.snp.makeConstraints { (make) in make.edges.equalToSuperview() } scrollView.addSubview(self.booksTF) booksTF.snp.makeConstraints { (make) in make.left.equalTo(self.view.snp.left).offset(20) make.top.equalTo(20) make.right.equalTo(self.view.snp.right).offset(-20) make.height.equalTo(50) } scrollView.addSubview(bookNameTF) bookNameTF.snp.makeConstraints { (make) in make.left.equalTo(self.view.snp.left).offset(20) make.top.equalTo(self.booksTF.snp.bottom).offset(10) make.right.equalTo(self.view.snp.right).offset(-20) make.height.equalTo(50) } scrollView.addSubview(bookAuthorTF) bookAuthorTF.snp.makeConstraints { (make) in make.left.equalTo(self.view.snp.left).offset(20) make.top.equalTo(self.bookNameTF.snp.bottom).offset(10) make.right.equalTo(self.view.snp.right).offset(-20) make.height.equalTo(50) } scrollView.addSubview(bookCategoryTF) bookCategoryTF.snp.makeConstraints { (make) in make.left.equalTo(self.view.snp.left).offset(20) make.top.equalTo(self.bookAuthorTF.snp.bottom).offset(10) make.right.equalTo(self.view.snp.right).offset(-20) make.height.equalTo(50) } scrollView.addSubview(bookDescTF) bookDescTF.snp.makeConstraints { (make) in make.left.equalTo(self.view.snp.left).offset(20) make.top.equalTo(self.bookCategoryTF.snp.bottom).offset(10) make.right.equalTo(self.view.snp.right).offset(-20) make.height.equalTo(50) } scrollView.addSubview(bookIconTF) bookIconTF.snp.makeConstraints { (make) in make.left.equalTo(self.view.snp.left).offset(20) make.top.equalTo(self.bookDescTF.snp.bottom).offset(10) make.right.equalTo(self.view.snp.right).offset(-20) make.height.equalTo(50) } scrollView.addSubview(bookUrlTF) bookUrlTF.snp.makeConstraints { (make) in make.left.equalTo(self.view.snp.left).offset(20) make.top.equalTo(self.bookIconTF.snp.bottom).offset(10) make.right.equalTo(self.view.snp.right).offset(-20) make.height.equalTo(50) } scrollView.addSubview(bookUpdateTimeTF) bookUpdateTimeTF.snp.makeConstraints { (make) in make.left.equalTo(self.view.snp.left).offset(20) make.top.equalTo(self.bookUrlTF.snp.bottom).offset(10) make.right.equalTo(self.view.snp.right).offset(-20) make.height.equalTo(50) } scrollView.addSubview(bookLastChapterNameTF) bookLastChapterNameTF.snp.makeConstraints { (make) in make.left.equalTo(self.view.snp.left).offset(20) make.top.equalTo(self.bookUpdateTimeTF.snp.bottom).offset(10) make.right.equalTo(self.view.snp.right).offset(-20) make.height.equalTo(50) } scrollView.addSubview(detailChaptersUrlTF) detailChaptersUrlTF.snp.makeConstraints { (make) in make.left.equalTo(self.view.snp.left).offset(20) make.top.equalTo(self.bookLastChapterNameTF.snp.bottom).offset(10) make.right.equalTo(self.view.snp.right).offset(-20) make.height.equalTo(50) } scrollView.addSubview(detailBookIconTF) detailBookIconTF.snp.makeConstraints { (make) in make.left.equalTo(self.view.snp.left).offset(20) make.top.equalTo(self.detailChaptersUrlTF.snp.bottom).offset(10) make.right.equalTo(self.view.snp.right).offset(-20) make.height.equalTo(50) } scrollView.addSubview(chaptersTF) chaptersTF.snp.makeConstraints { (make) in make.left.equalTo(self.view.snp.left).offset(20) make.top.equalTo(self.detailBookIconTF.snp.bottom).offset(10) make.right.equalTo(self.view.snp.right).offset(-20) make.height.equalTo(50) } scrollView.addSubview(chapterNameTF) chapterNameTF.snp.makeConstraints { (make) in make.left.equalTo(self.view.snp.left).offset(20) make.top.equalTo(self.chaptersTF.snp.bottom).offset(10) make.right.equalTo(self.view.snp.right).offset(-20) make.height.equalTo(50) } scrollView.addSubview(chapterUrlTF) chapterUrlTF.snp.makeConstraints { (make) in make.left.equalTo(self.view.snp.left).offset(20) make.top.equalTo(self.chapterNameTF.snp.bottom).offset(10) make.right.equalTo(self.view.snp.right).offset(-20) make.height.equalTo(50) } scrollView.addSubview(contentTF) contentTF.snp.makeConstraints { (make) in make.left.equalTo(self.view.snp.left).offset(20) make.top.equalTo(self.chapterUrlTF.snp.bottom).offset(10) make.right.equalTo(self.view.snp.right).offset(-20) make.height.equalTo(50) } scrollView.addSubview(hostTF) hostTF.snp.makeConstraints { (make) in make.left.equalTo(self.view.snp.left).offset(20) make.top.equalTo(self.contentTF.snp.bottom).offset(10) make.right.equalTo(self.view.snp.right).offset(-20) make.height.equalTo(50) } scrollView.addSubview(nameTF) nameTF.snp.makeConstraints { (make) in make.left.equalTo(self.view.snp.left).offset(20) make.top.equalTo(self.hostTF.snp.bottom).offset(10) make.right.equalTo(self.view.snp.right).offset(-20) make.height.equalTo(50) } scrollView.addSubview(searchUrlTF) searchUrlTF.snp.makeConstraints { (make) in make.left.equalTo(self.view.snp.left).offset(20) make.top.equalTo(self.nameTF.snp.bottom).offset(10) make.right.equalTo(self.view.snp.right).offset(-20) make.height.equalTo(50) } scrollView.addSubview(detailBookDescTF) detailBookDescTF.snp.makeConstraints { (make) in make.left.equalTo(self.view.snp.left).offset(20) make.top.equalTo(self.searchUrlTF.snp.bottom).offset(10) make.right.equalTo(self.view.snp.right).offset(-20) make.height.equalTo(50) } scrollView.addSubview(contentRemoveTF) contentRemoveTF.snp.makeConstraints { (make) in make.left.equalTo(self.view.snp.left).offset(20) make.top.equalTo(self.detailBookDescTF.snp.bottom).offset(10) make.right.equalTo(self.view.snp.right).offset(-20) make.height.equalTo(50) } scrollView.addSubview(contentReplaceTF) contentReplaceTF.snp.makeConstraints { (make) in make.left.equalTo(self.view.snp.left).offset(20) make.top.equalTo(self.contentRemoveTF.snp.bottom).offset(10) make.right.equalTo(self.view.snp.right).offset(-20) make.height.equalTo(50) } scrollView.addSubview(contentTagRemoveTF) contentTagRemoveTF.snp.makeConstraints { (make) in make.left.equalTo(self.view.snp.left).offset(20) make.top.equalTo(self.contentReplaceTF.snp.bottom).offset(10) make.right.equalTo(self.view.snp.right).offset(-20) make.height.equalTo(50) } scrollView.addSubview(searchEncodingTF) searchEncodingTF.snp.makeConstraints { (make) in make.left.equalTo(self.view.snp.left).offset(20) make.top.equalTo(self.contentTagRemoveTF.snp.bottom).offset(10) make.right.equalTo(self.view.snp.right).offset(-20) make.height.equalTo(50) } } private func setupNavItem() { let addItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(addAction)) self.navigationItem.rightBarButtonItem = addItem } private func reloadData() { guard let model = source else { return } booksTF.text = model.books bookCategoryTF.text = model.bookCategory bookNameTF.text = model.bookName bookAuthorTF.text = model.bookAuthor bookDescTF.text = model.bookDesc bookIconTF.text = model.bookIcon bookUrlTF.text = model.bookUrl bookUpdateTimeTF.text = model.bookUpdateTime bookLastChapterNameTF.text = model.bookLastChapterName detailChaptersUrlTF.text = model.detailChaptersUrl detailBookIconTF.text = model.detailBookIcon chaptersTF.text = model.chapters chapterNameTF.text = model.chapterName chapterUrlTF.text = model.chapterUrl contentTF.text = model.content hostTF.text = model.host nameTF.text = model.name searchUrlTF.text = model.searchUrl detailBookDescTF.text = model.detailBookDesc searchEncodingTF.text = model.searchEncoding contentRemoveTF.text = model.contentRemove contentReplaceTF.text = model.contentReplace contentTagRemoveTF.text = model.contentTagReplace } @objc private func addAction() { if booksTF.text.length == 0 { alert(with: "提醒", message: "books不能为空", okTitle: "确定") return } let model:ZSAikanParserModel = source == nil ? ZSAikanParserModel():source! model.books = booksTF.textField.text ?? "" model.bookName = bookNameTF.textField.text ?? "" model.bookAuthor = bookAuthorTF.textField.text ?? "" model.bookCategory = bookCategoryTF.textField.text ?? "" model.bookDesc = bookDescTF.textField.text ?? "" model.bookIcon = bookIconTF.textField.text ?? "" model.bookUrl = bookUrlTF.textField.text ?? "" model.bookUpdateTime = bookUpdateTimeTF.textField.text ?? "" model.bookLastChapterName = bookLastChapterNameTF.textField.text ?? "" model.detailChaptersUrl = detailChaptersUrlTF.textField.text ?? "" model.detailBookIcon = detailBookIconTF.textField.text ?? "" model.chapters = chaptersTF.textField.text ?? "" model.chapterName = chapterNameTF.textField.text ?? "" model.chapterUrl = chapterUrlTF.textField.text ?? "" model.content = contentTF.textField.text ?? "" model.host = hostTF.textField.text ?? "" model.name = nameTF.textField.text ?? "" model.searchUrl = searchUrlTF.textField.text ?? "" model.detailBookDesc = detailBookDescTF.textField.text ?? "" model.searchEncoding = searchEncodingTF.textField.text ?? "" model.contentReplace = contentReplaceTF.textField.text ?? "" model.contentRemove = contentRemoveTF.textField.text ?? "" model.contentTagReplace = contentTagRemoveTF.textField.text ?? "" ZSSourceManager.share.add(source: model) navigationController?.popViewController(animated: true) } } extension ZSAddSourceViewController:ZSAddLineViewDelegate { } protocol ZSAddLineViewDelegate:UITextFieldDelegate { } class ZSAddLineView: UIView { var placeholder:String = "" { didSet { textField.placeholder = placeholder } } var title:String { set{ titleLabel.text = newValue } get{ return titleLabel.text ?? "" } } var text:String { set { textField.text = newValue } get { return textField.text ?? "" } } weak var delegate:ZSAddLineViewDelegate? { didSet { } } lazy var titleLabel:UILabel = { let tf = UILabel(frame: .zero) tf.font = UIFont.systemFont(ofSize: 11) tf.text = "" return tf }() lazy var textField:UITextField = { let tf = UITextField(frame: .zero) tf.layer.borderColor = UIColor.gray.cgColor tf.layer.borderWidth = 0.3 tf.layer.cornerRadius = 5 tf.layer.masksToBounds = true tf.font = UIFont.systemFont(ofSize: 13) tf.borderStyle = .roundedRect tf.placeholder = "请输入" return tf }() override init(frame: CGRect) { super.init(frame: frame) self.addSubview(titleLabel) self.addSubview(textField) // self.titleLabel.snp.makeConstraints { (make) in // make.left.equalTo(self.view.snp.left).offset(20) // make.width.equalTo(100) // make.top.height.equalToSuperview() // } // self.textField.snp.makeConstraints { (make) in // make.left.equalTo(self.titleLabel.snp_right).offset(10) // make.top.height.equalToSuperview() // make.right.equalTo(self.view.snp.right).offset(-20) // } } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func layoutSubviews() { super.layoutSubviews() self.titleLabel.frame = CGRect(x: 0, y: 0, width: 100, height: self.bounds.height) self.textField.frame = CGRect(x: 120, y: 5, width: self.bounds.size.width - 140, height: self.bounds.height - 10) } deinit { print(titleLabel.text) } } ================================================ FILE: zhuishushenqi/NewVersion/Mine/ZSDetailButtonCell.swift ================================================ // // ZSDetailButtonCell.swift // zhuishushenqi // // Created by caony on 2019/7/1. // Copyright © 2019 QS. All rights reserved. // import UIKit typealias ZSDetailButtonCellHandler = ()->Void typealias ZSDetailButtonCellValueHandler = (_ result:Bool)->Void enum ZSDetailButtonCellType { case none case indicator case text case swtch case roundedIndicator case textIndicator } class ZSDetailButtonCell: UITableViewCell { lazy var detailButton:UIButton = { let button = UIButton(type: .custom) button.layer.cornerRadius = 15 button.layer.masksToBounds = true button.layer.borderWidth = 1 button.layer.borderColor = UIColor.init(hexString: "#A70A0B")?.cgColor button.setTitleColor(UIColor.init(hexString: "#A70A0B"), for: .normal) button.setTitle("开通", for: .normal) button.titleLabel?.font = UIFont.systemFont(ofSize: 13) button.addTarget(self, action: #selector(detailButtonAction(btn:)), for: .touchUpInside) return button }() lazy var detailTextButton:UIButton = { let button = UIButton(type: .custom) button.layer.masksToBounds = true button.setTitleColor(UIColor.gray, for: .normal) button.setTitle("开通", for: .normal) button.titleLabel?.font = UIFont.systemFont(ofSize: 13) button.addTarget(self, action: #selector(detailTextButtonAction(btn:)), for: .touchUpInside) return button }() lazy var detailSwitch:UISwitch = { let detailSwitch = UISwitch(frame: .zero) detailSwitch.isOn = false detailSwitch.onTintColor = UIColor.init(hexString: "#A70A0B") detailSwitch.addTarget(self, action: #selector(detailSwitchAction(swtch:)), for: .valueChanged) return detailSwitch }() var detailHandler:ZSDetailButtonCellHandler? var detailTextHandler:ZSDetailButtonCellHandler? var switchHandler:ZSDetailButtonCellValueHandler? var type:ZSDetailButtonCellType = .none { didSet { change(type: type) } } override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: .subtitle, reuseIdentifier: reuseIdentifier) self.accessoryType = .disclosureIndicator self.selectionStyle = .none detailTextLabel?.textColor = UIColor.gray contentView.addSubview(detailButton) contentView.addSubview(detailTextButton) contentView.addSubview(detailSwitch) } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func awakeFromNib() { super.awakeFromNib() // Initialization code } override func setSelected(_ selected: Bool, animated: Bool) { super.setSelected(selected, animated: animated) // Configure the view for the selected state } override func layoutSubviews() { super.layoutSubviews() let accessoryViewMarginX:CGFloat = accessoryType == .disclosureIndicator ? bounds.width - 20:bounds.width let detailButtonWidth:CGFloat = detailButton.sizeThatFits(CGSize(width: CGFloat.greatestFiniteMagnitude, height: 30)).width detailButton.frame = CGRect(x: accessoryViewMarginX - detailButtonWidth - 30 - 20, y: bounds.height/2 - 15, width: detailButtonWidth + 30, height: 30) let detailTextWidth:CGFloat = detailTextButton.sizeThatFits(CGSize(width: CGFloat.greatestFiniteMagnitude, height: 30)).width var detailTextFrame = CGRect.zero switch type { case .text: detailTextFrame = CGRect(x: bounds.width - detailTextWidth - 20, y: bounds.height/2 - 15, width: detailTextWidth, height: 30) break case .textIndicator: detailTextFrame = CGRect(x: bounds.width - detailTextWidth - 30, y: bounds.height/2 - 15, width: detailTextWidth, height: 30) default: break } detailTextButton.frame = detailTextFrame detailSwitch.frame = CGRect(x: bounds.width - 55 - 20, y: bounds.height/2 - 20, width: 51, height: 31) } func configure(title:String, icon:String, detail:String?, detailButtonText:String?) { textLabel?.text = title imageView?.image = UIImage(named: icon) detailTextLabel?.text = detail detailButton.setTitle(detailButtonText, for: .normal) detailTextButton.setTitle(detailButtonText, for: .normal) setNeedsLayout() layoutIfNeeded() } private func change(type:ZSDetailButtonCellType) { detailTextButton.isHidden = (type != .text && type != .textIndicator) detailButton.isHidden = (type != .roundedIndicator) detailSwitch.isHidden = (type != .swtch) switch type { case .text: accessoryType = .none break case .textIndicator: accessoryType = .disclosureIndicator break case .roundedIndicator: accessoryType = .disclosureIndicator break case .swtch: accessoryType = .none break case .indicator: accessoryType = .disclosureIndicator break default: break } } @objc private func detailButtonAction(btn:UIButton) { detailHandler?() } @objc private func detailTextButtonAction(btn:UIButton) { detailTextHandler?() } @objc private func detailSwitchAction(swtch:UISwitch) { switchHandler?(swtch.isOn) } } ================================================ FILE: zhuishushenqi/NewVersion/Mine/ZSMineHeaderView.swift ================================================ // // ZSMineHeaderView.swift // zhuishushenqi // // Created by yung on 2019/7/7. // Copyright © 2019 QS. All rights reserved. // import UIKit typealias ZSMineHeaderHandler = ()->Void class ZSMineHeaderView: UITableViewHeaderFooterView { lazy var coinLabel:UILabel = { let label = UILabel(frame: .zero) label.textColor = UIColor.init(red: 0.6, green: 0.6, blue: 0.6, alpha: 1) label.font = UIFont.systemFont(ofSize: 10) label.text = "书币" return label }() lazy var coinCountLabel:UILabel = { let label = UILabel(frame: .zero) label.textColor = UIColor.init(red: 0.2, green: 0.2, blue: 0.2, alpha: 1) label.font = UIFont.systemFont(ofSize: 14) return label }() lazy var coinCountButton:UIButton = { let button = UIButton(type: .custom) button.titleLabel?.font = UIFont.systemFont(ofSize: 15) button.addTarget(self, action: #selector(coinCountAction(btn:)), for: .touchUpInside) return button }() lazy var quanLabel:UILabel = { let label = UILabel(frame: .zero) label.textColor = UIColor.init(red: 0.6, green: 0.6, blue: 0.6, alpha: 1) label.font = UIFont.systemFont(ofSize: 10) label.text = "书券" return label }() lazy var quanCountLabel:UILabel = { let label = UILabel(frame: .zero) label.textColor = UIColor.init(red: 0.2, green: 0.2, blue: 0.2, alpha: 1) label.font = UIFont.systemFont(ofSize: 14) return label }() lazy var quanCountButton:UIButton = { let button = UIButton(type: .custom) button.titleLabel?.font = UIFont.systemFont(ofSize: 15) button.addTarget(self, action: #selector(quanAction(btn:)), for: .touchUpInside) return button }() var coinHandler:ZSMineHeaderHandler? var quanHandler:ZSMineHeaderHandler? override init(reuseIdentifier: String?) { super.init(reuseIdentifier: reuseIdentifier) contentView.backgroundColor = UIColor.white contentView.addSubview(coinLabel) contentView.addSubview(coinCountLabel) contentView.addSubview(coinCountButton) contentView.addSubview(quanLabel) contentView.addSubview(quanCountLabel) contentView.addSubview(quanCountButton) } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } @objc private func coinCountAction(btn:UIButton) { coinHandler?() } @objc private func quanAction(btn:UIButton) { quanHandler?() } func configure(account:ZSAccount?) { if let acc = account { self.coinCountLabel.text = "\(acc.balance)" self.quanCountLabel.text = "\(acc.voucherSum)" } } override func layoutSubviews() { super.layoutSubviews() self.coinCountLabel.frame = CGRect(x: 15, y: 15, width: 120, height: 15) self.coinLabel.frame = CGRect(x: 15, y: self.coinCountLabel.frame.maxY, width: 120, height: 15) self.quanCountLabel.frame = CGRect(x: self.coinCountLabel.frame.maxX, y: 15, width: 120, height: 15) self.quanLabel.frame = CGRect(x: self.quanCountLabel.frame.minX, y: self.quanCountLabel.frame.maxY, width: 120, height: 15) self.coinCountButton.frame = CGRect(x: 15, y: 15, width: 60, height: 30) self.quanCountButton.frame = CGRect(x: self.quanLabel.frame.minX, y: 15, width: 60, height: 30) } } ================================================ FILE: zhuishushenqi/NewVersion/Mine/ZSMineMenuItem.swift ================================================ // // ZSMineMenuItem.swift // zhuishushenqi // // Created by caony on 2019/7/1. // Copyright © 2019 QS. All rights reserved. // import UIKit enum ZSMineMenuItemType { case account case vip case id case level case message case history case booklist case topic case question case comment case feedback case darkmode case setting } enum ZSMineMenuItemDisclosureType { case none case controller case controllerWithDisclosureText case controllerWithTitle case externalLink case swtch case titleButton } class ZSMineMenuItem { var type:ZSMineMenuItemType = .account var title:String? var icon:String? var detailTitle:String? var disclosureText:String? var disclosureType:ZSMineMenuItemDisclosureType = .none var isSwitchOn:Bool = false var cellType:ZSDetailButtonCellType = .none init(type:ZSMineMenuItemType, disclosureType:ZSMineMenuItemDisclosureType, cellType:ZSDetailButtonCellType, isSwitchOn:Bool, title:String?, icon:String?, detailTitle:String?, disclosureText:String?) { self.type = type self.disclosureType = disclosureType self.cellType = cellType self.isSwitchOn = isSwitchOn self.title = title self.icon = icon self.detailTitle = detailTitle self.disclosureText = disclosureText } } ================================================ FILE: zhuishushenqi/NewVersion/Mine/ZSMineNavigationBar.swift ================================================ // // ZSMineNavigationBar.swift // zhuishushenqi // // Created by caony on 2019/7/1. // Copyright © 2019 QS. All rights reserved. // import UIKit enum MineNavigationBarType { case logout case login } protocol ZSMineNavigationBarDelegate:class { func navigationBar(navigationBar:ZSMineNavigationBar, didClickLogin:UIButton) func navigationBar(navigationBar:ZSMineNavigationBar, didClickIcon:UIButton) } class ZSMineNavigationBar: UIView { private lazy var titleLabel:UILabel = { let label = UILabel(frame: .zero) label.textColor = UIColor.white label.font = UIFont.systemFont(ofSize: 17) label.text = "未登录" return label }() private lazy var loginButton:UIButton = { let button = UIButton(type: .custom) button.setTitle("点击登录", for: .normal) button.setTitleColor(UIColor.init(hexString: "#A70A0B"), for: .normal) button.backgroundColor = UIColor.white button.titleLabel?.font = UIFont.systemFont(ofSize: 13) button.addTarget(self, action: #selector(loginAction(btn:)), for: .touchUpInside) button.layer.masksToBounds = true button.layer.cornerRadius = 15 return button }() private lazy var iconButton:UIButton = { let button = UIButton(type: .custom) button.backgroundColor = UIColor.clear button.addTarget(self, action: #selector(userAction(btn:)), for: .touchUpInside) button.layer.masksToBounds = true button.layer.cornerRadius = 20 return button }() var type:MineNavigationBarType = .logout { didSet { changeType(type: type) } } weak var delegate:ZSMineNavigationBarDelegate? override init(frame: CGRect) { super.init(frame: frame) addSubview(titleLabel) addSubview(loginButton) addSubview(iconButton) self.backgroundColor = UIColor.init(hexString: "#A70A0B") isUserInteractionEnabled = true } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func layoutSubviews() { super.layoutSubviews() let statusHeight = UIApplication.shared.statusBarFrame.height titleLabel.frame = CGRect(x: 20, y: statusHeight, width: 100, height: 44) loginButton.frame = CGRect(x: bounds.width - 120, y: statusHeight + 7, width: 100, height: 30) iconButton.frame = CGRect(x: bounds.width - 60, y: statusHeight + 2, width: 40, height: 40) } private func changeType(type:MineNavigationBarType) { switch type { case .logout: titleLabel.text = "未登录" break default: break } loginButton.isHidden = type == .login iconButton.isHidden = type == .logout setNeedsLayout() layoutIfNeeded() } func loginState(state:MineNavigationBarType, title:String?, icon:String?) { type = state if state == .login { titleLabel.text = title let resource = QSResource(url: URL(string: "\(IMAGE_BASEURL)\(icon ?? "")") ?? URL(string: "https://www.baidu.com")!) iconButton.kf.setImage(with: resource, for: .normal) } } @objc private func loginAction(btn:UIButton) { delegate?.navigationBar(navigationBar: self, didClickLogin: btn) } @objc private func userAction(btn:UIButton) { delegate?.navigationBar(navigationBar: self, didClickIcon: btn) } } ================================================ FILE: zhuishushenqi/NewVersion/Mine/ZSMineViewController.swift ================================================ // // ZSMineViewController.swift // ZSMine // // Created by caony on 2019/6/18. // import UIKit import MJRefresh class ZSMineViewController: BaseViewController, ZSMineNavigationBarDelegate { lazy var navigationBar:ZSMineNavigationBar = { let navigationBar = ZSMineNavigationBar(frame: .zero) navigationBar.loginState(state: .logout, title: nil, icon: nil) navigationBar.delegate = self return navigationBar }() lazy var tableView:UITableView = { let tableView = UITableView(frame: .zero, style: .grouped) tableView.dataSource = self tableView.delegate = self tableView.sectionHeaderHeight = 0.01 tableView.sectionFooterHeight = 0.01 if #available(iOS 11, *) { tableView.contentInsetAdjustmentBehavior = .never } tableView.qs_registerCellClass(ZSDetailButtonCell.self) tableView.qs_registerHeaderFooterClass(ZSMineHeaderView.self) let blurEffect = UIBlurEffect(style: .extraLight) let blurEffectView = UIVisualEffectView(effect: blurEffect) tableView.backgroundView = blurEffectView return tableView }() var menus:[ZSMineMenuItem] = [] var viewModel:ZSMineViewModel = ZSMineViewModel() override func viewDidLoad() { super.viewDidLoad() view.addSubview(navigationBar) view.addSubview(tableView) navigationBar.snp.remakeConstraints { (make) in make.left.top.right.equalToSuperview() make.height.equalTo(kNavgationBarHeight) } tableView.snp.remakeConstraints { (make) in make.left.right.equalToSuperview() make.top.equalTo(kNavgationBarHeight) make.height.equalTo(ScreenHeight - kNavgationBarHeight - FOOT_BAR_Height) } observe() setupMenu() } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) navigationController?.isNavigationBarHidden = true navigationBar.loginState(state: ZSLogin.share.hasLogin() ? .login:.logout, title: ZSThirdLogin.share.userInfo?.user?.nickname, icon: ZSThirdLogin.share.userInfo?.user?.avatar) tableView.reloadData() } override var preferredStatusBarStyle : UIStatusBarStyle { return .lightContent } //MARK: - custom private func setupMenu() { let account = ZSMineMenuItem(type: .account, disclosureType: .controllerWithTitle,cellType: .roundedIndicator, isSwitchOn: false, title: "我的账户", icon: "personal_icon_account_24_24_24x24_", detailTitle: nil, disclosureText: "充值") let vip = ZSMineMenuItem(type: .vip, disclosureType: .controllerWithTitle,cellType: .roundedIndicator, isSwitchOn: false, title: "我的VIP", icon: "personal_icon_vip_24_24_24x24_", detailTitle: nil, disclosureText: "开通") let id = ZSMineMenuItem(type: .id, disclosureType: .controllerWithTitle,cellType: .text, isSwitchOn: false, title: "我的ID", icon: "personal_icon_id_24_24_24x24_", detailTitle: "开通VIP,免广告阅读", disclosureText: "点击复制") let level = ZSMineMenuItem(type: .level, disclosureType: .controllerWithTitle,cellType: .textIndicator, isSwitchOn: false, title: "经验等级", icon: "personal_icon_level _24_24_24x24_", detailTitle: nil, disclosureText: "\(ZSThirdLogin.share.userInfo?.user?.lv ?? 0)级") let message = ZSMineMenuItem(type: .message, disclosureType: .controllerWithTitle,cellType: .indicator, isSwitchOn: false, title: "我的消息", icon: "profile_personal_icon_message_24_24_24x24_", detailTitle: nil, disclosureText: nil) let history = ZSMineMenuItem(type: .history, disclosureType: .controllerWithTitle,cellType: .indicator, isSwitchOn: false, title: "阅读历史", icon: "personal_icon_history_24_24_24x24_", detailTitle: nil, disclosureText: nil) let booklist = ZSMineMenuItem(type: .booklist, disclosureType: .controllerWithTitle,cellType: .indicator, isSwitchOn: false, title: "书单", icon: "personal_icon_booklist_24_24_24x24_", detailTitle: nil, disclosureText: nil) let topic = ZSMineMenuItem(type: .topic, disclosureType: .controllerWithTitle,cellType: .indicator, isSwitchOn: false, title: "话题", icon: "personal_icon_level3 _24_24_24x24_", detailTitle: nil, disclosureText: nil) let question = ZSMineMenuItem(type: .question, disclosureType: .controllerWithTitle,cellType: .indicator, isSwitchOn: false, title: "我的书荒提问", icon: "personal_icon_huntbook_24_24_24x24_", detailTitle: nil, disclosureText: nil) let comment = ZSMineMenuItem(type: .comment, disclosureType: .controllerWithTitle,cellType: .indicator, isSwitchOn: false, title: "给追书神器好评", icon: "personal_icon_zslike_24_24_24x24_", detailTitle: nil, disclosureText: nil) let feedback = ZSMineMenuItem(type: .feedback, disclosureType: .controllerWithTitle,cellType: .indicator, isSwitchOn: false, title: "意见反馈", icon: "personal_icon_opinion_24_24_24x24_", detailTitle: nil, disclosureText: nil) let darkmode = ZSMineMenuItem(type: .darkmode, disclosureType: .controllerWithTitle,cellType: .swtch, isSwitchOn: false, title: "夜间模式", icon: "personal_icon_darkmode_24_24_23x23_", detailTitle: nil, disclosureText: nil) let setting = ZSMineMenuItem(type: .setting, disclosureType: .controllerWithTitle,cellType: .indicator, isSwitchOn: false, title: "设置", icon: "personal_icon_setting_24_24_24x24_", detailTitle: nil, disclosureText: nil) menus.append(account) menus.append(vip) menus.append(id) menus.append(level) menus.append(message) menus.append(history) menus.append(booklist) menus.append(topic) menus.append(question) menus.append(comment) menus.append(feedback) menus.append(darkmode) menus.append(setting) } private func observe() { self.viewModel.reloadBlock = { [weak self] in DispatchQueue.main.async { self?.tableView.reloadData() } } viewModel.requestAccount() } //MARK: - ZSMineNavigationBarDelegate func navigationBar(navigationBar: ZSMineNavigationBar, didClickLogin: UIButton) { login { (result) in } } func navigationBar(navigationBar: ZSMineNavigationBar, didClickIcon: UIButton) { login { [weak self] (result) in if result { let userInfoVC = ZSUserInfoViewController() userInfoVC.hidesBottomBarWhenPushed = true self?.navigationController?.pushViewController(userInfoVC, animated: true) } } } } extension ZSMineViewController:UITableViewDataSource, UITableViewDelegate { func numberOfSections(in tableView: UITableView) -> Int { return 1 } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return menus.count } func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { return 60 } func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { if ZSLogin.share.hasLogin() { return 55 } return 0.01 } func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { return 0.01 } func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { if ZSLogin.share.hasLogin() { let headerView = tableView.qs_dequeueReusableHeaderFooterView(ZSMineHeaderView.self) headerView?.configure(account: viewModel.account) return headerView } return nil } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.qs_dequeueReusableCell(ZSDetailButtonCell.self) let item = menus[indexPath.row] cell?.type = item.cellType cell?.configure(title: item.title ?? "", icon: item.icon ?? "", detail: item.detailTitle, detailButtonText: item.disclosureText) cell?.detailHandler = { } cell?.detailTextHandler = { } cell?.switchHandler = { isOn in } return cell! } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { let item = menus[indexPath.row] switch item.type { case .account: break case .setting: let settingVC = ZSSettingViewController() settingVC.hidesBottomBarWhenPushed = true navigationController?.pushViewController(settingVC, animated: true) break default: break } } } ================================================ FILE: zhuishushenqi/NewVersion/Mine/ZSMineViewModel.swift ================================================ // // ZSMineViewModel.swift // zhuishushenqi // // Created by yung on 2019/7/7. // Copyright © 2019 QS. All rights reserved. // import UIKit class ZSMineViewModel { var viewDidLoad: ()->() = {} var reloadBlock: ()->() = {} var service:ZSMyService = ZSMyService() var account:ZSAccount? init() { viewDidLoad = { [weak self] in self?.requestAccount() } } func requestAccount() { requestAccount { [weak self] in self?.reloadBlock() } } func requestAccount(completion:@escaping()->Void) { service.fetchAccount(token: ZSLogin.share.token) { (account) in self.account = account completion() } } } ================================================ FILE: zhuishushenqi/NewVersion/Mine/ZSRegularVerifyViewController.swift ================================================ // // ZSRegularVerifyViewController.swift // zhuishushenqi // // Created by yung on 2020/12/18. // Copyright © 2020 QS. All rights reserved. // import UIKit import SnapKit class ZSRegularVerifyViewController: BaseViewController { private let htmlTextPlaceHolder = "请输入html文本" private let resultTextPlaceHolder = "测试结果:" override func viewDidLoad() { super.viewDidLoad() title = "规则验证" view.addSubview(typeSelect) view.addSubview(htmlTextTV) view.addSubview(regularTF) view.addSubview(parseBT) view.addSubview(resultLabel) typeSelect.snp.makeConstraints { (make) in make.left.equalToSuperview().offset(20) make.right.equalToSuperview().offset(-20) make.height.equalTo(40) make.top.equalToSuperview().offset(kNavgationBarHeight + 20) } htmlTextTV.snp.makeConstraints { [unowned self] (make) in make.top.equalTo(self.typeSelect.snp_bottomMargin).offset(20) make.left.equalToSuperview().offset(20) make.right.equalToSuperview().offset(-20) make.height.equalTo(200) } regularTF.snp.makeConstraints { [unowned self] (make) in make.top.equalTo(self.htmlTextTV.snp_bottomMargin).offset(20) make.left.equalToSuperview().offset(20) make.right.equalToSuperview().offset(-20) make.height.equalTo(44) } parseBT.snp.makeConstraints { [unowned self](make) in make.top.equalTo(self.regularTF.snp_bottomMargin).offset(20) make.left.equalToSuperview().offset(20) make.right.equalToSuperview().offset(-20) make.height.equalTo(44) } resultLabel.snp.makeConstraints { [unowned self](make) in make.top.equalTo(self.parseBT.snp_bottomMargin).offset(20) make.left.equalToSuperview().offset(20) make.right.equalToSuperview().offset(-20) make.height.equalTo(200) } htmlTextTV.text = htmlTextPlaceHolder resultLabel.loadHTMLString(resultTextPlaceHolder, baseURL: nil) } @objc private func parseAction(btn:UIButton) { let htmlText = htmlTextTV.text ?? "" let regularText = regularTF.text ?? "" let typeSelectedIndex = typeSelect.selectedSegmentIndex if let document = OCGumboDocument(htmlString: htmlText) { let parse = AikanHtmlParser() switch typeSelectedIndex { case 0: let elements = parse.elementArray(with: document, withRegexString: regularText) var resultText = "" elements.enumerateObjects { (node, idx, stop) in guard let element = node as? OCGumboNode else { return } resultText.append("\(element.html() ?? "")
") } // let htmlData = NSString(string: resultText).data(using: String.Encoding.unicode.rawValue) // let options = [NSAttributedString.DocumentReadingOptionKey.documentType: // NSAttributedString.DocumentType.html] // let attributedString = try? NSMutableAttributedString(data: htmlData ?? Data(), // options: options, // documentAttributes: nil) resultLabel.loadHTMLString(resultText, baseURL: nil) break case 1: let objs = parse.string(withGumboNode: document, withAikanString: regularText, withText: true) resultLabel.loadHTMLString("\(objs)", baseURL: nil) break case 2: let attr = ZSReaderDownloader.share.contentReplace(string: htmlText, reg: regularText) resultLabel.loadHTMLString("\(attr)", baseURL: nil) break default: break } } } // MARK: - lazy lazy var typeSelect: UISegmentedControl = { let seg = UISegmentedControl(items: ["array", "string", "contentReplace"]) seg.tintColor = UIColor.red if #available(iOS 13.0, *) { seg.selectedSegmentTintColor = UIColor.white } else { // Fallback on earlier versions } seg.selectedSegmentIndex = 1 return seg }() lazy var htmlTextTV: UITextView = { let tv = UITextView() tv.font = UIFont.systemFont(ofSize: 13) tv.textColor = UIColor.gray tv.layer.cornerRadius = 5 tv.layer.masksToBounds = true tv.delegate = self return tv }() lazy var regularTF: UITextField = { let tf = UITextField() tf.placeholder = "请输入规则" tf.backgroundColor = UIColor.white tf.layer.cornerRadius = 5 tf.layer.masksToBounds = true return tf }() lazy var parseBT: UIButton = { let bt = UIButton(type: .custom) bt.setTitle("开始测试", for: .normal) bt.setTitleColor(UIColor.white, for: .normal) bt.backgroundColor = UIColor.red bt.layer.cornerRadius = 5 bt.layer.masksToBounds = true bt.addTarget(self, action: #selector(parseAction(btn:)), for: .touchUpInside) return bt }() lazy var resultLabel: UIWebView = { let lb = UIWebView() lb.layer.cornerRadius = 5 lb.layer.masksToBounds = true return lb }() } extension ZSRegularVerifyViewController:UITextViewDelegate { func textViewShouldEndEditing(_ textView: UITextView) -> Bool { if textView.text.count == 0 { htmlTextTV.text = htmlTextPlaceHolder } return true } func textViewDidBeginEditing(_ textView: UITextView) { if textView.text == htmlTextPlaceHolder { textView.text = "" } } } ================================================ FILE: zhuishushenqi/NewVersion/Mine/ZSSetting.swift ================================================ // // ZSSetting.swift // zhuishushenqi // // Created by caony on 2019/11/11. // Copyright © 2019 QS. All rights reserved. // import UIKit @objc protocol SettingsDelegate: AnyObject { func settingsOpenURLInNewTab(_ url: URL) } // A setting in the sections panel. Contains a sublist of Settings class SettingSection: ZSSetting { fileprivate let children: [ZSSetting] init(title: NSAttributedString? = nil, footerTitle: NSAttributedString? = nil, cellHeight: CGFloat? = nil, children: [ZSSetting]) { self.children = children super.init(title: title, footerTitle: footerTitle, cellHeight: cellHeight) } var count: Int { var count = 0 for setting in children where !setting.hidden { count += 1 } return count } subscript(val: Int) -> ZSSetting? { var i = 0 for setting in children where !setting.hidden { if i == val { return setting } i += 1 } return nil } } // A base setting class that shows a title. You probably want to subclass this, not use it directly. class ZSSetting: NSObject { fileprivate var _title: NSAttributedString? fileprivate var _footerTitle: NSAttributedString? fileprivate var _cellHeight: CGFloat? fileprivate var _image: UIImage? weak var delegate: SettingsDelegate? // The url the SettingsContentViewController will show, e.g. Licenses and Privacy Policy. var url: URL? { return nil } // The title shown on the pref. var title: NSAttributedString? { return _title } var footerTitle: NSAttributedString? { return _footerTitle } var cellHeight: CGFloat? { return _cellHeight} fileprivate(set) var accessibilityIdentifier: String? // An optional second line of text shown on the pref. var status: NSAttributedString? { return nil } // Whether or not to show this pref. var hidden: Bool { return false } var style: UITableViewCell.CellStyle { return .subtitle } var accessoryType: UITableViewCell.AccessoryType { return .none } var accessoryView: UIImageView? { return nil } var textAlignment: NSTextAlignment { return .natural } var image: UIImage? { return _image } var enabled: Bool = true func accessoryButtonTapped() { onAccessoryButtonTapped?() } var onAccessoryButtonTapped: (() -> Void)? // Called when the cell is setup. Call if you need the default behaviour. func onConfigureCell(_ cell: UITableViewCell) { cell.detailTextLabel?.assign(attributed: status) cell.detailTextLabel?.attributedText = status cell.detailTextLabel?.numberOfLines = 0 cell.textLabel?.assign(attributed: title) cell.textLabel?.textAlignment = textAlignment cell.textLabel?.numberOfLines = 1 cell.textLabel?.lineBreakMode = .byTruncatingTail cell.accessoryType = accessoryType cell.accessoryView = accessoryView cell.selectionStyle = enabled ? .default : .none cell.accessibilityIdentifier = accessibilityIdentifier cell.imageView?.image = _image if let title = title?.string { if let detailText = cell.detailTextLabel?.text { cell.accessibilityLabel = "\(title), \(detailText)" } else if let status = status?.string { cell.accessibilityLabel = "\(title), \(status)" } else { cell.accessibilityLabel = title } } cell.accessibilityTraits = UIAccessibilityTraits.button cell.indentationWidth = 0 cell.layoutMargins = .zero let backgroundView = UIView() backgroundView.backgroundColor = UIColor(rgb: 0xd1d1d6) backgroundView.bounds = cell.bounds cell.selectedBackgroundView = backgroundView // So that the separator line goes all the way to the left edge. cell.separatorInset = .zero // if let cell = cell as? ThemedTableViewCell { // cell.applyTheme() // } } // Called when the pref is tapped. func onClick(_ navigationController: UINavigationController?) { return } // Called when the pref is long-pressed. func onLongPress(_ navigationController: UINavigationController?) { return } // Helper method to set up and push a SettingsContentViewController func setUpAndPushSettingsContentViewController(_ navigationController: UINavigationController?) { if let url = self.url { // let viewController = SettingsContentViewController() // viewController.settingsTitle = self.title // viewController.url = url // navigationController?.pushViewController(viewController, animated: true) } } init(title: NSAttributedString? = nil, footerTitle: NSAttributedString? = nil, cellHeight: CGFloat? = nil, delegate: SettingsDelegate? = nil, enabled: Bool? = nil) { self._title = title self._footerTitle = footerTitle self._cellHeight = cellHeight self.delegate = delegate self.enabled = enabled ?? true } } extension UILabel { // iOS bug: NSAttributed string color is ignored without setting font/color to nil func assign(attributed: NSAttributedString?) { guard let attributed = attributed else { return } let attribs = attributed.attributes(at: 0, effectiveRange: nil) if attribs[NSAttributedString.Key.foregroundColor] == nil { // If the text color attribute isn't set, use the table view row text color. // textColor = UIColor.theme.tableView.rowText textColor = UIColor.black } else { textColor = nil } attributedText = attributed } } ================================================ FILE: zhuishushenqi/NewVersion/Mine/ZSSourceCell.swift ================================================ // // ZSSourceCell.swift // zhuishushenqi // // Created by caony on 2019/11/11. // Copyright © 2019 QS. All rights reserved. // import UIKit protocol ZSSourceCellDelegate:class { func cellDidClickCheck(cell:ZSSourceCell, checked:Bool) func cellDidClickDelete(cell:ZSSourceCell) } class ZSSourceCell: UITableViewCell { weak var delegate:ZSSourceCellDelegate? lazy var checkButton:UIButton = { let bt = UIButton(type: .custom) bt.setImage(UIImage(named: "source_checkbox_normal"), for: .normal) bt.setImage(UIImage(named: "source_checkbox_selected"), for: .selected) bt.imageView?.contentMode = .scaleToFill bt.addTarget(self, action: #selector(checkAction(btn:)), for: .touchUpInside) return bt }() lazy var deleteButton:UIButton = { let bt = UIButton(type: .custom) bt.setImage(UIImage(named: "source_delete"), for: .normal) bt.addTarget(self, action: #selector(deleteAction(btn:)), for: .touchUpInside) return bt }() var source:ZSAikanParserModel? override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) contentView.addSubview(checkButton) contentView.addSubview(deleteButton) } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func awakeFromNib() { super.awakeFromNib() // Initialization code } func configure(source:ZSAikanParserModel) { self.source = source self.textLabel?.text = source.name self.checkButton.isSelected = source.checked } override func setSelected(_ selected: Bool, animated: Bool) { super.setSelected(selected, animated: animated) // Configure the view for the selected state } override func layoutSubviews() { super.layoutSubviews() self.checkButton.frame = CGRect(x: 20, y: self.contentView.size.height/2 - 20, width: 40, height: 40) self.deleteButton.frame = CGRect(x: self.contentView.width - 20 - 20, y: self.contentView.size.height/2 - 10, width: 20, height: 20) self.textLabel?.frame = CGRect(x: 60, y: 0, width: 200, height: self.contentView.bounds.height) } @objc private func checkAction(btn:UIButton) { btn.isSelected = !btn.isSelected self.source?.checked = btn.isSelected self.delegate?.cellDidClickCheck(cell: self, checked: btn.isSelected) } @objc private func deleteAction(btn:UIButton) { self.delegate?.cellDidClickDelete(cell: self) } } ================================================ FILE: zhuishushenqi/NewVersion/Mine/ZSSourcesViewController.swift ================================================ // // ZSSourcesViewController.swift // zhuishushenqi // // Created by caony on 2019/11/11. // Copyright © 2019 QS. All rights reserved. // import UIKit class ZSSourcesViewController: ZSBaseTableViewController { var sources:[ZSAikanParserModel] { return ZSSourceManager.share.sources } override func viewDidLoad() { super.viewDidLoad() setupNavBar() } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) self.tableView.reloadData() } private func setupNavBar() { let addItem = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(addAction)) self.navigationItem.rightBarButtonItem = addItem } @objc private func addAction() { let addVC = ZSAddSourceViewController() self.navigationController?.pushViewController(addVC, animated: true) } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return sources.count } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.qs_dequeueReusableCell(ZSSourceCell.self) cell?.delegate = self let source = sources[indexPath.row] cell?.configure(source: source) return cell! } override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) let source = sources[indexPath.row] let addVC = ZSAddSourceViewController() addVC.source = source self.navigationController?.pushViewController(addVC, animated: true) } override func registerCellClasses() -> Array { return [ZSSourceCell.self] } } extension ZSSourcesViewController:ZSSourceCellDelegate { func cellDidClickCheck(cell:ZSSourceCell, checked:Bool) { if checked { ZSSourceManager.share.select(source: cell.source!) } else { ZSSourceManager.share.unselect(source: cell.source!) } } func cellDidClickDelete(cell:ZSSourceCell) { alert(with: "提醒", message: "确认删除吗,删除后不可恢复?", okTitle: "确认", cancelTitle: "取消", okAction: { [weak self] (action) in ZSSourceManager.share.delete(source: cell.source!) self?.tableView.reloadData() }, cancelAction: nil) } } ================================================ FILE: zhuishushenqi/NewVersion/Network/ZSNetwork.swift ================================================ // // ZSNetwork.swift // zhuishushenqi // // Created by yung on 2022/1/6. // Copyright © 2022 QS. All rights reserved. // import Foundation import Alamofire class ZSNet { @discardableResult public class func GET(_ urlStr: String,parameters: Parameters? = nil,_ handler:@escaping ZSBaseCallback) -> DataRequest { var headers = SessionManager.defaultHTTPHeaders headers["User-Agent"] = YouShaQiUserAgent let req = request(urlStr, method: .get, parameters: parameters, encoding: URLEncoding.default, headers: headers).responseJSON { (response) in if let data = response.data { if let json = try? JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.allowFragments) { handler(json) } else { handler(nil) } } else { handler(nil) } } return req } @discardableResult public class func getJSON(_ urlStr: String,parameters: Parameters? = nil,_ handler:@escaping ZSBaseCallback<[String:Any]>) -> DataRequest { let req = GET(urlStr, parameters: parameters) { (data) in if let json = data as? [String:Any] { handler(json) } else { handler([:]) } } return req } @discardableResult public class func getJSONArray(_ urlStr: String,parameters: Parameters? = nil,_ handler:@escaping ZSBaseCallback<[[String:Any]]>) -> DataRequest { let req = GET(urlStr, parameters: parameters) { (data) in if let json = data as? [[String:Any]] { handler(json) } else { handler([[:]]) } } return req } @discardableResult public class func POST(_ urlStr: String,parameters: Parameters? = nil,_ handler:@escaping ZSBaseCallback) -> DataRequest { var headers = SessionManager.defaultHTTPHeaders headers["User-Agent"] = YouShaQiUserAgent let req = request(urlStr, method: .post, parameters: parameters, encoding: URLEncoding.default, headers: headers).responseJSON { (response) in if let data = response.data { if let json = try? JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.allowFragments) { handler(json) } else { handler(nil) } } else { handler(nil) } } return req } } ================================================ FILE: zhuishushenqi/NewVersion/Reader/Color.swift ================================================ // // Color.swift // dlxk // // Created by caony on 2019/11/30. // Copyright © 2019 dlxk. All rights reserved. // import Foundation import UIKit class TableViewColor { var backgroundColor:UIColor { return UIColor.Noah.TableNormalBackground } var rowBackgroundColor:UIColor { return UIColor.Noah.NormalBackground } var rowText:UIColor { return UIColor.Noah.NormalText } var detailText:UIColor { return UIColor.Noah.NormalDetailText } var headerBackground:UIColor { return UIColor.Noah.TableNormalBackground } var separator: UIColor { return UIColor.Noah.NormalGray } } class DarkTableViewColor:TableViewColor { override var backgroundColor:UIColor { return UIColor.Noah.TableGrayBackground } override var rowBackgroundColor:UIColor { return UIColor.Noah.NormalGrayBackground } override var rowText:UIColor { return UIColor.Noah.WhiteText } override var detailText:UIColor { return UIColor.Noah.DarkDetailText } override var headerBackground:UIColor { return UIColor.Noah.TableGrayBackground } override var separator: UIColor { return UIColor.Noah.WhiteText } } class TabBarColor { var backgroundColor:UIColor { return UIColor.Noah.NormalBackground } var titleTextColor:UIColor { return UIColor.Noah.TabBarTitle } var titleTextSelectedColor:UIColor { return UIColor.Noah.TabBarTitleSelected } } class DarkTabBarColor:TabBarColor { override var backgroundColor:UIColor { return UIColor.Noah.NormalGrayBackground } override var titleTextColor:UIColor { return UIColor.Noah.TabBarTitleDark } override var titleTextSelectedColor:UIColor { return UIColor.Noah.TabBarTitleDarkSelected } } class NavitaionBarColor { var backgroundColor:UIColor { return UIColor.Noah.NormalNavBackground } var barTintColor:UIColor { return UIColor.Noah.WhiteText } var tintColor:UIColor { return UIColor.Noah.OnTintRed } } class DarkNavitaionBarColor:NavitaionBarColor { override var backgroundColor:UIColor { return UIColor.Noah.NormalNavBackground } override var barTintColor:UIColor { return UIColor.Noah.NormalGrayBackground } override var tintColor:UIColor { return UIColor.Noah.WhiteText } } class GeneralColor { var faviconBackground: UIColor { return UIColor.clear } var passcodeDot: UIColor { return UIColor.Noah.NormalGray } var highlightBlue: UIColor { return UIColor.Noah.HighlightBlue } var destructiveRed: UIColor { return UIColor.Noah.NormalRed } var separator: UIColor { return UIColor.Noah.NormalGray } var settingsTextPlaceholder: UIColor? { return nil } var controlTint: UIColor { return UIColor.Noah.OnTintRed } } extension UIColor { struct Noah { static let NormalBackground = UIColor.white static let DarkCellBackground = UIColor.black static let NormalGray = UIColor(red:0.22, green:0.22, blue:0.24, alpha:1.00) static let NormalGrayBackground = UIColor(red:0.22, green:0.22, blue:0.24, alpha:1.00) static let TableNormalBackground = UIColor(red:0.95, green:0.95, blue:0.95, alpha:1.00) static let TableGrayBackground = UIColor(red:0.16, green:0.16, blue:0.18, alpha:1.00) static let NormalNavBackground = UIColor.red static let WhiteText = UIColor.white static let NormalText = UIColor.black static let NormalDetailText = UIColor.gray static let DarkDetailText = UIColor(white: 0.6, alpha: 1.0) static let HighlightBlue = UIColor.blue static let OnTintRed = UIColor(red:1.00, green:0.44, blue:0.45, alpha:1.00) static let NormalRed = UIColor.red static let TabBarTitle = UIColor(red: 160/255.0, green: 160/255.0, blue: 160/255.0, alpha: 1.0) static let TabBarTitleSelected = UIColor(red: 34/255.0, green: 34/255.0, blue: 34/255.0, alpha: 1.0) static let TabBarTitleDark = UIColor(red: 160/255.0, green: 160/255.0, blue: 160/255.0, alpha: 1.0) static let TabBarTitleDarkSelected = UIColor.white } } ================================================ FILE: zhuishushenqi/NewVersion/Reader/ReaderBar.swift ================================================ // // ReaderBar.swift // zhuishushenqi // // Created by caony on 2020/1/13. // Copyright © 2020 QS. All rights reserved. // import Foundation import UIKit class ReaderBar { var backgroundImage:UIImage? { return nil } var textColor:UIColor { return UIColor.black } } ================================================ FILE: zhuishushenqi/NewVersion/Reader/ReaderNavigationBar.swift ================================================ // // ReaderNavigationBar.swift // zhuishushenqi // // Created by caony on 2020/1/13. // Copyright © 2020 QS. All rights reserved. // import Foundation import UIKit class ReaderNavigationBar { var tintColor:UIColor { return UIColor.white } var barTintColor:UIColor { return UIColor.white } var backgroundColor:UIColor { return UIColor.black } } class WhiteReaderNavigationBar:ReaderNavigationBar { } class BlackReaderNavigationBar:ReaderNavigationBar { } ================================================ FILE: zhuishushenqi/NewVersion/Reader/ThemeManager.swift ================================================ // // ThemeManager.swift // dlxk // // Created by caony on 2019/11/30. // Copyright © 2019 dlxk. All rights reserved. // import Foundation import UIKit enum ThemeManagerPrefs: String { case systemThemeIsOn = "prefKeySystemThemeSwitchOnOff" case automaticSwitchIsOn = "prefKeyAutomaticSwitchOnOff" case automaticSliderValue = "prefKeyAutomaticSliderValue" case themeName = "prefKeyThemeName" } protocol Themeable:class { func applyTheme() } protocol ReaderTheme { var name:String { get } var navigationBar:ReaderNavigationBar { get } var bottomBar:ReaderBottomBar { get } var bigBottomBar:ReaderBigBottomBar { get } var readerBar:ReaderBar { get } } class WhiteTheme:ReaderTheme { var navigationBar: ReaderNavigationBar { return WhiteReaderNavigationBar() } var bottomBar: ReaderBottomBar { return ReaderBottomBar() } var bigBottomBar: ReaderBigBottomBar { return ReaderBigBottomBar() } var readerBar: ReaderBar { return ReaderBar() } var name: String { return ThemeName.white.rawValue } } class DarkTheme:WhiteTheme { override var name: String { return ThemeName.dark.rawValue } } class ReaderBottomBar { } class ReaderBigBottomBar { } enum ThemeName:String { case white case dark } fileprivate func themeFrom(name: String?) -> ReaderTheme { guard let name = name, let theme = ThemeName(rawValue: name) else { return WhiteTheme() } switch theme { case .dark: return DarkTheme() default: return WhiteTheme() } } ================================================ FILE: zhuishushenqi/NewVersion/Reader/ZSBookCache.swift ================================================ // // ZSBookCache.swift // zhuishushenqi // // Created by yung on 2020/1/5. // Copyright © 2020 QS. All rights reserved. // import UIKit class ZSBookCache { static let share = ZSBookCache() private init() { } @discardableResult func cacheContent(content:ZSBookChapter, for book:String) ->Bool { ZSBookMemoryCache.share.cacheContent(content: content, for: book) ZSBookDiskCache.share.cacheContent(content: content, for: book) return true } func content(for key:String, book:String) ->ZSBookChapter? { if let obj = ZSBookMemoryCache.share.content(for: key) { return obj } else if let obj = ZSBookDiskCache.share.content(for: key, book: book) { // 内存缓存不存在,磁盘缓存存在,则将磁盘缓存加载到内存中 ZSBookMemoryCache.share.cacheContent(content: obj, for: key) return obj } return nil } func isContentExist(_ key:String, book:String) ->Bool { if ZSBookMemoryCache.share.isContentExist(key) { return true } else if ZSBookDiskCache.share.isContentExist(key, book: book) { return true } return false } func remove(_ key:String, book:String) { ZSBookMemoryCache.share.remove(key) ZSBookDiskCache.share.remove(key, book: book) } func remove(_ book:String) { ZSBookMemoryCache.share.removeAllCache() ZSBookDiskCache.share.remove(book) } } ================================================ FILE: zhuishushenqi/NewVersion/Reader/ZSBookChapter.swift ================================================ // // ZSBookChapter.swift // zhuishushenqi // // Created by yung on 2020/1/5. // Copyright © 2020 QS. All rights reserved. // import UIKit import HandyJSON class ZSBookChapter:NSObject, NSCoding, HandyJSON { static let defaultContent = "正在获数据,请稍候..." // 存储用 var bookUrl:String = "" var chapterUrl:String = "" var chapterName:String = "" var chapterContent:String = "" { didSet { // 本地书籍获取章节z时再计算 if bookType == .online { calPages() } } } // 当前数组中的序号,与书籍中对应的序号可能有出入 var chapterIndex:Int = 0 // 每页的范围 var ranges:[NSRange] = [] var pages:[ZSBookPage] = [] var bookType:ZSReaderBookStyle = .online required override init() { } // 不存在则不是当前chapter func getNextPage(page:ZSBookPage) ->ZSBookPage? { if (page.pageIndex + 1) < self.pages.count { return self.pages[page.pageIndex + 1] } return nil } func getLastPage(page:ZSBookPage) ->ZSBookPage? { if page.pageIndex - 1 >= 0 { return self.pages[page.pageIndex - 1] } return nil } func contentNil() ->Bool { return pages.count == 0 || chapterContent.length == 0 || chapterContent == ZSBookChapter.defaultContent } func calPages() { var content = chapterContent if content.length == 0 { content = ZSBookChapter.defaultContent } var size = ZSReader.share.contentFrame var top:CGFloat = 0 let bottom:CGFloat = 30 var orientation:UIInterfaceOrientation = .portrait DispatchQueue.main.async { orientation = UIApplication.shared.statusBarOrientation } if orientation.isPortrait && IPHONEX { top = 30 } top += 30 size = CGRect(x: size.origin.x, y: top, width: size.width, height: UIScreen.main.bounds.height - top - bottom) let attributes = ZSReader.share.attributes() self.ranges = QSReaderParse.pageWithAttributes(attrubutes: attributes, constrainedToFrame: size, string: self.chapterContent) var pages:[ZSBookPage] = [] for item in 0..Bool { let cachePath = ZSCacheHelper.bookshelfDownloadPath.appending("/\(book)") let success = ZSCacheHelper.shared.storage(obj: content, for: content.chapterUrl, cachePath: cachePath) return success } func content(for key:String, book:String) ->ZSBookChapter? { let cachePath = ZSCacheHelper.bookshelfDownloadPath.appending("/\(book)") if let obj = ZSCacheHelper.shared.cachedObj(for: key, cachePath: cachePath) as? ZSBookChapter { return obj } return nil } /// 缓存是否存在 /// - Parameters: /// - key: chapterUrl /// - book: bookName func isContentExist(_ key:String, book:String) ->Bool { let cachePath = ZSCacheHelper.bookshelfDownloadPath.appending("/\(book)") if let _ = ZSCacheHelper.shared.cachedObj(for: key, cachePath: cachePath) { return true } return false } func remove(_ key:String, book:String) { let cachePath = ZSCacheHelper.bookshelfDownloadPath.appending("/\(book)") ZSCacheHelper.shared.clear(for: key, cachePath: cachePath) } func remove(_ book:String) { let cachePath = ZSCacheHelper.bookshelfDownloadPath.appending("/\(book)") let fullPath = NSHomeDirectory().appending("/Documents").appending(cachePath) do { try FileManager.default.removeItem(atPath: fullPath) } catch let error { print(error) } } } ================================================ FILE: zhuishushenqi/NewVersion/Reader/ZSBookMemoryCache.swift ================================================ // // ZSBookMemoryCache.swift // zhuishushenqi // // Created by yung on 2020/1/5. // Copyright © 2020 QS. All rights reserved. // import UIKit /// key:url,value:text class ZSBookMemoryCache { /// key:chapterId or link, value:chapterContent private var chapterInfo:[String:ZSBookChapter] = [:] static let share = ZSBookMemoryCache() private init() { } @discardableResult func cacheContent(content:ZSBookChapter, for key:String) ->Bool { chapterInfo[content.chapterUrl] = content return true } func content(for key:String) ->ZSBookChapter? { if let obj = chapterInfo[key] { return obj } return nil } func isContentExist(_ key:String) ->Bool { let content = chapterInfo[key] return content != nil } func removeAllCache() { chapterInfo.removeAll() } func remove(_ key:String) { chapterInfo.removeValue(forKey: key) } } ================================================ FILE: zhuishushenqi/NewVersion/Reader/ZSHorizonalViewController.swift ================================================ // // ZSHorizonalViewController.swift // zhuishushenqi // // Created by caony on 2019/7/9. // Copyright © 2019 QS. All rights reserved. // import UIKit class ZSHorizonalViewController: BaseViewController, ZSReaderVCProtocol { fileprivate var pageVC:PageViewController = PageViewController() weak var toolBar:ZSReaderToolbar? weak var dataSource:UIPageViewControllerDataSource? weak var delegate:UIPageViewControllerDelegate? var nextPageHandler: ZSReaderPageHandler? var lastPageHandler: ZSReaderPageHandler? lazy var horizonalController:UIPageViewController = { var transitionStyle:UIPageViewController.TransitionStyle = .scroll let controller = UIPageViewController(transitionStyle: transitionStyle, navigationOrientation: UIPageViewController.NavigationOrientation.horizontal, options: nil) controller.dataSource = self controller.delegate = self controller.isDoubleSided = false controller.setViewControllers([pageVC], direction: .forward, animated: true, completion: nil) return controller }() override func viewDidLoad() { super.viewDidLoad() } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) if self.horizonalController.view.superview != self.view { self.horizonalController.view.removeFromSuperview() self.addChild(self.horizonalController) self.view.addSubview(self.horizonalController.view) self.horizonalController.didMove(toParent: self) } } func nextParagraph(string:String) -> String { return pageVC.nextParagraph(string: string) } func startEdit(for string:String) { pageVC.startEdit(for: string) } func bind(toolBar: ZSReaderToolbar) { self.toolBar = toolBar } func destroy() { if let controllers = horizonalController.viewControllers as? [PageViewController] { for controller in controllers { controller.destroy() } } pageVC.destroy() } func changeBg(style: ZSReaderStyle) { pageVC.bgView.image = style.backgroundImage } func jumpPage(page: ZSBookPage,_ animated:Bool = false,_ direction:UIPageViewController.NavigationDirection = .forward) { if horizonalController.viewControllers?.count == 0 { return } if animated { if direction == .forward { if let vc = pageViewController(horizonalController, viewControllerAfter: horizonalController.viewControllers![0]) as? PageViewController { vc.newPage = page horizonalController.setViewControllers([vc], direction: direction, animated: animated) { [weak self] (finished) in self?.pageVC = vc } } } else { if let vc = pageViewController(horizonalController, viewControllerBefore: horizonalController.viewControllers![0]) as? PageViewController { vc.newPage = page horizonalController.setViewControllers([vc], direction: direction, animated: animated) { [weak self] (finished) in self?.pageVC = vc } } } } else { pageVC = horizonalController.viewControllers![0] as! PageViewController pageVC.newPage = page } } } extension ZSHorizonalViewController:UIPageViewControllerDataSource, UIPageViewControllerDelegate { func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? { return dataSource?.pageViewController(pageViewController, viewControllerBefore: viewController) } func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? { return dataSource?.pageViewController(pageViewController, viewControllerAfter: viewController) } func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool){ delegate?.pageViewController?(pageViewController, didFinishAnimating: finished, previousViewControllers: previousViewControllers, transitionCompleted: completed) if !completed { guard let pageVC = previousViewControllers.first as? PageViewController else { return } self.pageVC = pageVC } else { } } } ================================================ FILE: zhuishushenqi/NewVersion/Reader/ZSNormalViewController.swift ================================================ // // ZSNormalViewController.swift // zhuishushenqi // // Created by caony on 2019/7/9. // Copyright © 2019 QS. All rights reserved. // import UIKit class ZSNormalViewController: BaseViewController, ZSReaderVCProtocol { var nextPageHandler: ZSReaderPageHandler? var lastPageHandler: ZSReaderPageHandler? override func viewDidLoad() { super.viewDidLoad() } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) if self.pageViewController.view.superview != self.view { self.pageViewController.view.removeFromSuperview() self.addChild(self.pageViewController) self.view.addSubview(self.pageViewController.view) self.pageViewController.didMove(toParent: self) self.setupGesture() } } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) } weak var toolBar:ZSReaderToolbar? fileprivate var changedPage = false fileprivate var pageViewController:PageViewController = PageViewController() //MARK: - ZSReaderVCProtocol func jumpPage(page: ZSBookPage,_ animated:Bool=false, _ direction:UIPageViewController.NavigationDirection = .forward) { pageViewController.newPage = page } func nextParagraph(string:String) -> String { return pageViewController.nextParagraph(string: string) } func startEdit(for string:String) { pageViewController.startEdit(for: string) } func bind(toolBar: ZSReaderToolbar) { self.toolBar = toolBar } func changeBg(style: ZSReaderStyle) { pageViewController.bgView.image = style.backgroundImage } func setupGesture(){ let pan = UIPanGestureRecognizer(target: self, action: #selector(panAction)) view.addGestureRecognizer(pan) } override func touchesEnded(_ touches: Set, with event: UIEvent?) { toolBar?.show(inView: self.view, true) } @objc func panAction(pan:UIPanGestureRecognizer){ // x方向滑动超过20就翻页 let translation:CGPoint = pan.translation(in: self.view) if pan.state == .changed && !changedPage { let offsetX = translation.x if offsetX < -20 { // 在本次手势结束前都不再响应 changedPage = true nextPage() } else if (offsetX > 20) { // 在本次手势结束前都不再响应 changedPage = true lastPage() } } else if pan.state == .ended { changedPage = false } } func nextPage() { nextPageHandler?() } func lastPage() { lastPageHandler?() } func destroy() { pageViewController.destroy() } deinit { } } ================================================ FILE: zhuishushenqi/NewVersion/Reader/ZSPageTableViewCell.swift ================================================ // // ZSPageTableViewCell.swift // zhuishushenqi // // Created by caony on 2020/1/15. // Copyright © 2020 QS. All rights reserved. // import UIKit class ZSPageTableViewCell: UITableViewCell { override func awakeFromNib() { super.awakeFromNib() // Initialization code } override func setSelected(_ selected: Bool, animated: Bool) { super.setSelected(selected, animated: animated) // Configure the view for the selected state } } ================================================ FILE: zhuishushenqi/NewVersion/Reader/ZSPageViewController.swift ================================================ // // ZSPageViewController.swift // zhuishushenqi // // Created by caony on 2019/7/9. // Copyright © 2019 QS. All rights reserved. // import UIKit class ZSPageViewController: BaseViewController, ZSReaderVCProtocol { var nextPageHandler: ZSReaderPageHandler? var lastPageHandler: ZSReaderPageHandler? var startEditingHandler:ZSDisplayHandler? var endEditingHandler:ZSDisplayHandler? fileprivate var pageVC:PageViewController = PageViewController() { didSet { pageVC.startEditingHandler = { [weak self] in self?.startEditingHandler?() } pageVC.endEditingHandler = { [weak self] in self?.endEditingHandler?() } } } weak var toolBar:ZSReaderToolbar? weak var dataSource:UIPageViewControllerDataSource? weak var delegate:UIPageViewControllerDelegate? var sideNum = 1 lazy var horizonalController:UIPageViewController = { var transitionStyle:UIPageViewController.TransitionStyle = .pageCurl let controller = UIPageViewController(transitionStyle: transitionStyle, navigationOrientation: UIPageViewController.NavigationOrientation.horizontal, options: nil) controller.dataSource = self controller.delegate = self controller.isDoubleSided = true controller.setViewControllers([pageVC], direction: .forward, animated: true, completion: nil) return controller }() override func viewDidLoad() { super.viewDidLoad() navigationBarHiden = true } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) if self.horizonalController.view.superview != self.view { self.horizonalController.view.removeFromSuperview() self.addChild(self.horizonalController) self.view.addSubview(self.horizonalController.view) self.horizonalController.didMove(toParent: self) } } func nextParagraph(string:String) -> String { return pageVC.nextParagraph(string: string) } func startEdit(for string:String) { pageVC.startEdit(for: string) } func bind(toolBar: ZSReaderToolbar) { self.toolBar = toolBar } func destroy() { if let controllers = horizonalController.viewControllers as? [PageViewController] { for controller in controllers { controller.destroy() } } pageVC.destroy() } func changeBg(style: ZSReaderStyle) { pageVC.bgView.image = style.backgroundImage } func jumpPage(page: ZSBookPage,_ animated:Bool = false,_ direction:UIPageViewController.NavigationDirection = .forward) { pageVC.newPage = page var viewControllers:[UIViewController] = [pageVC] if animated { let backgroundVC = QSReaderBackgroundViewController() backgroundVC.setBackground(viewController: pageVC) viewControllers = [pageVC, backgroundVC] } horizonalController.setViewControllers(viewControllers, direction: direction, animated: animated, completion: nil) } @objc private func tapAction(tap:UITapGestureRecognizer) { toolBar?.show(inView: view, true) } deinit { QSLog("释放了") } override func touchesBegan(_ touches: Set, with event: UIEvent?) { super.touchesBegan(touches, with: event) } override func touchesMoved(_ touches: Set, with event: UIEvent?) { super.touchesMoved(touches, with: event) } override func touchesEnded(_ touches: Set, with event: UIEvent?) { super.touchesEnded(touches, with: event) // delegate?.touchArea(touches: touches, with: event) } } extension ZSPageViewController:UIPageViewControllerDataSource, UIPageViewControllerDelegate { func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? { sideNum -= 1 if abs(sideNum)%2 == 0 { //背面 let backgroundVC = QSReaderBackgroundViewController() backgroundVC.setBackground(viewController: self.pageVC) return backgroundVC } if let vc = dataSource?.pageViewController(pageViewController, viewControllerBefore: viewController) as? PageViewController { pageVC = vc return vc } return nil } func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? { sideNum += 1 if abs(sideNum)%2 == 0 { //背面 let backgroundVC = QSReaderBackgroundViewController() backgroundVC.setBackground(viewController: self.pageVC) return backgroundVC } else if let vc = dataSource?.pageViewController(pageViewController, viewControllerAfter: viewController) as? PageViewController { pageVC = vc return vc } return nil } func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool){ delegate?.pageViewController?(pageViewController, didFinishAnimating: finished, previousViewControllers: previousViewControllers, transitionCompleted: completed) if !completed { guard let pageVC = previousViewControllers.first as? PageViewController else { return } self.pageVC = pageVC } else { } } } ================================================ FILE: zhuishushenqi/NewVersion/Reader/ZSReadHistory.swift ================================================ // // ZSReadHistory.swift // zhuishushenqi // // Created by yung on 2020/1/6. // Copyright © 2020 QS. All rights reserved. // import UIKit import HandyJSON class ZSReadHistory:NSObject, NSCoding, HandyJSON { // 记录当前章节 var chapter:ZSBookChapter! // 记录当前页码 var page:ZSBookPage! { didSet { } } required override init() { } required init?(coder: NSCoder) { self.chapter = coder.decodeObject(forKey: "chapter") as? ZSBookChapter self.page = coder.decodeObject(forKey: "page") as? ZSBookPage } func encode(with coder: NSCoder) { coder.encode(self.chapter, forKey: "chapter") coder.encode(self.page, forKey: "page") } } ================================================ FILE: zhuishushenqi/NewVersion/Reader/ZSReader.swift ================================================ // // ZSReader.swift // zhuishushenqi // // Created by caony on 2019/7/9. // Copyright © 2019 QS. All rights reserved. // import UIKit import HandyJSON func cache(value:Int ,for key:String) { UserDefaults.standard.set(value, forKey: key) UserDefaults.standard.synchronize() } func cache(value:CGFloat ,for key:String) { UserDefaults.standard.set(value, forKey: key) UserDefaults.standard.synchronize() } func cache(value:CGRect,for key:String) { UserDefaults.standard.set(value, forKey: key) UserDefaults.standard.synchronize() } func getValue(for key:String) ->Int { if let value = UserDefaults.standard.value(forKey: key) as? Int { return value } return -1 } func getValue(for key:String) ->CGFloat { if let value = UserDefaults.standard.value(forKey: key) as? CGFloat { return value } return -1 } func getRectValue(for key:String) ->CGRect { if let value = UserDefaults.standard.value(forKey: key) as? CGRect { return value } return CGRect.zero } class ZSReader { static let share = ZSReader() var pageStyle:ZSReaderPageStyle = .pageCurl { didSet { cache(value: pageStyle.rawValue, for: "\(ZSReader.self).\(ZSReaderPageStyle.self)") self.didChangePageStyle(pageStyle) } } var contentFrame = CGRect(x: 10, y: STATEBARHEIGHT, width: UIScreen.main.bounds.width - 20, height: UIScreen.main.bounds.height - STATEBARHEIGHT - 30) { didSet { cache(value: contentFrame, for: "\(ZSReader.self).contentFrame") self.didChangeContentFrame(contentFrame) } } var theme:ZSReaderTheme = ZSReaderTheme() { didSet { self.didChangeTheme(theme) } } var bookStyle:ZSReaderBookStyle = .online { didSet { cache(value: bookStyle.rawValue, for: "\(ZSReader.self).\(ZSReaderBookStyle.self)") self.didChangeBookStyle(bookStyle) } } var didChangePageStyle: (ZSReaderPageStyle) ->Void = { _ in } var didChangeContentFrame: (CGRect) ->Void = { _ in } var didChangeBookStyle: (ZSReaderBookStyle) ->Void = { _ in } var didChangeTheme: (ZSReaderTheme) ->Void = { _ in } private init() { // pageStyle = ZSReaderPageStyle.init(rawValue: getValue(for: "\(ZSReader.self).\(ZSReaderPageStyle.self)")) ?? .pageCurl // contentFrame = getRectValue(for: "\(ZSReader.self).contentFrame").equalTo(CGRect.zero) ? UIScreen.main.bounds:getRectValue(for: "\(ZSReader.self).contentFrame") bookStyle = ZSReaderBookStyle.init(rawValue: getValue(for: "\(ZSReader.self).\(ZSReaderBookStyle.self)")) ?? .online let pageStyleCacheValue:Int = getValue(for: "\(ZSReader.self).\(ZSReaderPageStyle.self)") if pageStyleCacheValue >= 0 { pageStyle = ZSReaderPageStyle.init(rawValue: pageStyleCacheValue) ?? .normal } } public func attributes() -> [NSAttributedString.Key:Any]{ let paragraphStyle = NSMutableParagraphStyle() paragraphStyle.lineSpacing = theme.lineSpace paragraphStyle.paragraphSpacing = theme.paragraphSpace let font = UIFont(name: "ArialMT", size: theme.fontSize.size) ?? UIFont.systemFont(ofSize: theme.fontSize.size) let attributes = [NSAttributedString.Key.font:font, NSAttributedString.Key.paragraphStyle:paragraphStyle] as [NSAttributedString.Key : Any] return attributes } } class ZSReaderTheme { var readerStyle:ZSReaderStyle = .white { didSet { cache(value: readerStyle.rawValue, for: "\(ZSReaderTheme.self).\(ZSReaderStyle.self)") self.didChangeReaderStyle(readerStyle) } } var fontSize:ZSReaderFontSize = ZSReaderFontSize() { didSet { cache(value: fontSize.size, for: "\(ZSReaderTheme.self).\(ZSReaderFontSize.self)") self.didChangeFontSize(fontSize) } } var fontStyle:ZSReaderFont = .system { didSet { cache(value: fontStyle.rawValue, for: "\(ZSReaderTheme.self).\(ZSReaderFont.self)") self.didChangeFontStyle(fontStyle) } } var lineSpace:CGFloat = 10 { didSet { cache(value: lineSpace, for: "\(ZSReaderTheme.self).lineSpace") self.didChangeLineSpace(lineSpace) } } var paragraphSpace:CGFloat = 5 { didSet { cache(value: paragraphSpace, for: "\(ZSReaderTheme.self).paragraphSpace") self.didChangeParagraphSpace(paragraphSpace) } } var chinese:ZSReaderChinese = .simple { didSet { cache(value: chinese.rawValue, for: "\(ZSReaderTheme.self).\(ZSReaderChinese.self)") self.didChangeChine(chinese) } } var brightness:ZSReaderBrightness = ZSReaderBrightness() { didSet { cache(value: CGFloat(brightness.brightness), for: "\(ZSReaderTheme.self).\(ZSReaderBrightness.self)") self.didChangeBrightness(brightness) } } init() { fontSize = ZSReaderFontSize() } var didChangeReaderStyle: (ZSReaderStyle) ->Void = { _ in } var didChangeFontSize: (ZSReaderFontSize) ->Void = { _ in } var didChangeFontStyle: (ZSReaderFont) ->Void = { _ in } var didChangeLineSpace: (CGFloat) ->Void = { _ in } var didChangeParagraphSpace: (CGFloat) ->Void = { _ in } var didChangeChine: (ZSReaderChinese) ->Void = { _ in } var didChangeBrightness: (ZSReaderBrightness) ->Void = { _ in } } enum ZSReaderBookStyle:Int, HandyJSONEnum { case online case local } struct ZSReaderFontSize { var size:CGFloat = 15 var enableBigger:Bool = true var enableSmaller:Bool = false init() { let pageStyleCacheValue:CGFloat = getValue(for: "\(ZSReaderTheme.self).\(ZSReaderFontSize.self)") if pageStyleCacheValue >= 15 { size = pageStyleCacheValue } enableSmaller = size > 15 ? true:false enableBigger = size < 30 ? true:false } mutating func bigger() { if !enableBigger { return } size += 1 enableSmaller = size > 15 ? true:false enableBigger = size < 30 ? true:false } mutating func smaller() { if !enableSmaller { return } size -= 1 enableSmaller = size > 15 ? true:false enableBigger = size < 30 ? true:false } } struct ZSReaderBrightness { var brightness:Float = 1.0 var enableBigger:Bool = false var enableSmaller:Bool = true var minValue:Float = 0.0 var maxValue:Float = 0.8 init() { enableSmaller = brightness > minValue ? true:false enableBigger = brightness < maxValue ? true:false } mutating func bigger() { if !enableBigger { return } brightness += 0.1 enableSmaller = brightness > minValue ? true:false enableBigger = brightness < maxValue ? true:false } mutating func smaller() { if !enableSmaller { return } brightness -= 0.1 enableSmaller = brightness > minValue ? true:false enableBigger = brightness < maxValue ? true:false } } enum ZSReaderChinese:Int { case simple case traditional } enum ZSReaderStyle:Int { case white case yellow case green // case blackgreen case pink case sheepskin case violet case water case weekGreen case weekPink case coffee case night var isNightMode:Bool { return self == .night } static let count: Int = { var max: Int = 0 while let _ = ZSReaderStyle(rawValue: max) { max += 1 } return max }() var backgroundImage:UIImage { switch self { case .yellow: if let image = ZSImage(for: "yellow_mode_bg") { return image } return #imageLiteral(resourceName: "yellow_mode_bg") case .white: if let image = ZSImage(for: "white_mode_bg") { return image } return #imageLiteral(resourceName: "white_mode_bg") case .green: if let image = ZSImage(for: "green_mode_bg") { return image } return #imageLiteral(resourceName: "green_mode_bg") // case .blackgreen: // return #imageLiteral(resourceName: "new_nav_night_normal") case .pink: if let image = ZSImage(for: "violet_mode_bg") { return image } return #imageLiteral(resourceName: "violet_mode_bg") case .sheepskin: if let image = ZSImage(for: "beijing") { return image } return #imageLiteral(resourceName: "beijing") case .violet: if let image = ZSImage(for: "violet_mode_bg") { return image } return #imageLiteral(resourceName: "violet_mode_bg") case .water: if let image = ZSImage(for: "sheepskin_mode_bg") { return image } return #imageLiteral(resourceName: "sheepskin_mode_bg") case .weekPink: if let image = ZSImage(for: "violet_mode_bg") { return image } return #imageLiteral(resourceName: "violet_mode_bg") case .weekGreen: if let image = ZSImage(for: "violet_mode_bg") { return image } return #imageLiteral(resourceName: "violet_mode_bg") case .coffee: if let image = ZSImage(for: "pf_header_bg") { return image } return #imageLiteral(resourceName: "pf_header_bg") case .night: if let image = ZSImage(for: "blackGreen_mode_bg") { return image } return #imageLiteral(resourceName: "blackGreen_mode_bg") default: return #imageLiteral(resourceName: "violet_mode_bg") } } var textColor:UIColor { switch self { case .yellow,.white,.green: return UIColor.black case .coffee: return UIColor.white default: return UIColor.black } } var batteryColor:UIColor { switch self { case .yellow,.white,.green: return UIColor.darkGray case .coffee: return UIColor.white default: return UIColor.darkGray } } var borderColor:UIColor { switch self { case .white: return UIColor.black default: return UIColor.black } } var current:ReaderTheme { switch self { case .white: return WhiteTheme() default: return WhiteTheme() } } } enum ZSReaderFont:Int { case system case lantingBalck case kaiti case weibei case yapi case pianpianti case lishu case riwen var fontPath:String? { switch self { case .system: return nil default: return nil } return nil } } enum ZSReaderPageStyle:Int { case normal case pageCurl case vertical case horizonal var styleName:String { switch self { case .normal: return "无" case .pageCurl: return "仿真翻页" case .vertical: return "上下滚屏" case .horizonal: return "左右平移" default: return "无" } } static let count: Int = { var max: Int = 0 while let _ = ZSReaderPageStyle(rawValue: max) { max += 1 } return max }() var pageViewControler:BaseViewController? { switch self { case .pageCurl: break default: return nil } return nil } } func ZSImage(for name:String, bundle:String = "style") ->UIImage? { return UIImage.image(for: name, bundle: bundle) } ================================================ FILE: zhuishushenqi/NewVersion/Reader/ZSReaderBaseViewModel.swift ================================================ // // ZSReaderBaseViewModel.swift // zhuishushenqi // // Created by caony on 2019/9/20. // Copyright © 2019 QS. All rights reserved. // import UIKit enum ZSChapterType { case online case memory case disk } class ZSReaderBaseViewModel { var model:ZSAikanParserModel? { didSet { bookReload({}) }} // 设置首次进入时指定的章节,如从详情页的目录进入 var originalChapter:ZSBookChapter? var preRequestChapterCount = 5 var reader = ZSReader.share // 阅读历史 var readHistory:ZSReadHistory? var chapters:[ZSBookChapter] { get { return model?.chaptersModel ?? [] } } var page:ZSBookPage? fileprivate var webService = ZSReaderWebService() init() { reader.didChangeContentFrame = { frame in } reader.theme.didChangeFontSize = { fontSize in } } func bookReload(_ block: @escaping ()->Void) { // 阅读历史记录保存nil guard let book = model else { block() return } ZSShelfManager.share.history(book.bookUrl) { [weak self] (result) in if let history = result { self?.readHistory = history block() } } } func saveHistory() { guard let history = readHistory else { return } ZSShelfManager.share.addHistory(history) } func request(callback:ZSReaderBaseCallback?) { if let chapter = originalChapter, chapter.chapterContent.length == 0 || chapter.chapterContent == ZSBookChapter.defaultContent { ZSReaderDownloader.share.download(chapter: chapter, book: model!, reg: model?.content ?? "") { (chapter) in callback?(chapter) } preRequest(chapter: chapter) } else if let chapters = model?.chaptersModel, chapters.count > 0 { ZSReaderDownloader.share.download(chapter: chapters.first!, book: model!, reg: model?.content ?? "") { (chapter) in callback?(chapter) } preRequest(chapter: chapters.first!) } } // 预加载章节, 前后章节各 preRequestChapterCount func preRequest(chapter:ZSBookChapter) { let chapterIndex = chapter.chapterIndex var index = chapterIndex - preRequestChapterCount // 预下载前面 while index < chapterIndex { if index > 0 && index < chapters.count { guard let book = model else { return } let cp = chapters[index] let exist = ZSBookCache.share.isContentExist(cp.chapterUrl, book: book.bookName) if !exist { ZSReaderDownloader.share.download(chapter: cp, book: model!, reg: model?.content ?? "") { (chapter) in } } } index += 1 } var behindIndex = chapterIndex + 1 // 预下载后面 while behindIndex <= chapterIndex + preRequestChapterCount { if behindIndex > 0 && behindIndex < chapters.count { guard let book = model else { return } let cp = chapters[behindIndex] let exist = ZSBookCache.share.isContentExist(cp.chapterUrl, book: book.bookName) if !exist { ZSReaderDownloader.share.download(chapter: cp, book: model!, reg: model?.content ?? "") { (chapter) in } } } behindIndex += 1 } } func request(chapter:ZSBookChapter, callback:@escaping ZSReaderTypeCallback) { // 是否存在缓存了 guard let book = model else { return } let exist = ZSBookCache.share.isContentExist(chapter.chapterUrl, book: book.bookName) if exist && !chapter.contentNil() { if let chapter = ZSBookCache.share.content(for: chapter.chapterUrl, book: book.bookName) { callback(chapter, .memory) self.preRequest(chapter: chapter) return } } ZSReaderDownloader.share.download(chapter: chapter, book: model!, reg: model?.content ?? "") { (cp) in DispatchQueue.main.async { callback(cp, .online) } self.preRequest(chapter: chapter) } } } ================================================ FILE: zhuishushenqi/NewVersion/Reader/ZSReaderBottomBar.swift ================================================ // // ZSReaderBottomBar.swift // zhuishushenqi // // Created by caony on 2020/1/8. // Copyright © 2020 QS. All rights reserved. // import UIKit protocol ZSReaderBottomBarDelegate:class { func bottomBar(bottomBar:ZSReaderBottomBar, clickLast:UIButton) func bottomBar(bottomBar:ZSReaderBottomBar, clickNext:UIButton) func bottomBar(bottomBar:ZSReaderBottomBar, clickCatalog:UIButton) func bottomBar(bottomBar:ZSReaderBottomBar, clickDark:UIButton) func bottomBar(bottomBar:ZSReaderBottomBar, clickSetting:UIButton) func bottomBar(bottomBar:ZSReaderBottomBar, progress:Float) } class ZSReaderBottomBar: UIView { weak var delegate:ZSReaderBottomBarDelegate? lazy var progressBar:UISlider = { let bar = UISlider(frame: UIScreen.main.bounds) bar.minimumTrackTintColor = UIColor.white bar.maximumTrackTintColor = UIColor.gray bar.addTarget(self, action: #selector(sliderAction(slider:)), for: .touchUpInside) bar.addTarget(self, action: #selector(sliderAction(slider:)), for: .touchUpOutside) bar.addTarget(self, action: #selector(sliderAction(slider:)), for: .touchCancel) return bar }() lazy var catalogButton:ZSReaderMenuButton = { let bt = ZSReaderMenuButton(type: .custom) bt.setTitle("目录", for: .normal) bt.setTitleColor(UIColor.white, for: .normal) bt.titleLabel?.font = UIFont.systemFont(ofSize: 11) bt.setImage(UIImage(named: "icon_tools_menu_30x30_"), for: .normal) bt.addTarget(self, action: #selector(catalogAction(bt:)), for: .touchUpInside) return bt }() lazy var darkButton:ZSReaderMenuButton = { let bt = ZSReaderMenuButton(type: .custom) bt.setTitle("夜间", for: .normal) bt.setTitle("白天", for: .selected) bt.titleLabel?.font = UIFont.systemFont(ofSize: 11) bt.setImage(UIImage(named: "pic_day_46x46_"), for: .selected) bt.setImage(UIImage(named: "pic_night_46x46_"), for: .normal) bt.setTitleColor(UIColor.white, for: .normal) bt.addTarget(self, action: #selector(darkAction(bt:)), for: .touchUpInside) return bt }() lazy var settingButton:ZSReaderMenuButton = { let bt = ZSReaderMenuButton(type: .custom) bt.setTitle("设置", for: .normal) bt.setTitleColor(UIColor.white, for: .normal) bt.titleLabel?.font = UIFont.systemFont(ofSize: 11) bt.setImage(UIImage(named: "icon_tools_setting_30x30_"), for: .normal) bt.addTarget(self, action: #selector(settingAction(bt:)), for: .touchUpInside) return bt }() lazy var lastButton:UIButton = { let bt = UIButton(type: .custom) bt.setTitle("上一章", for: .normal) bt.setTitleColor(UIColor.white, for: .normal) bt.addTarget(self, action: #selector(lastAction(bt:)), for: .touchUpInside) return bt }() lazy var nextButton:UIButton = { let bt = UIButton(type: .custom) bt.setTitle("下一章", for: .normal) bt.setTitleColor(UIColor.white, for: .normal) bt.addTarget(self, action: #selector(nextAction(bt:)), for: .touchUpInside) return bt }() private init() { super.init(frame: .zero) } override init(frame: CGRect) { super.init(frame: frame) backgroundColor = UIColor.black addSubview(lastButton) addSubview(progressBar) addSubview(nextButton) addSubview(catalogButton) addSubview(darkButton) addSubview(settingButton) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func layoutSubviews() { super.layoutSubviews() lastButton.frame = CGRect(origin: CGPoint(x: 20, y: 20), size: CGSize(width: 60, height: 30)) nextButton.frame = CGRect(x: self.bounds.width - 80, y: 20, width: 60, height: 30) progressBar.frame = CGRect(x: lastButton.frame.maxX + 10, y: 20, width: self.bounds.width - lastButton.frame.maxX * 2 - 20, height: 30) catalogButton.frame = CGRect(x: 20, y: lastButton.frame.maxY + 10, width: 30, height: 60) darkButton.frame = CGRect(x: self.bounds.width/2 - 20, y: lastButton.frame.maxY + 10, width: 30, height: 60) settingButton.frame = CGRect(x: self.bounds.width - 60, y: lastButton.frame.maxY + 10, width: 30, height: 60) } @objc private func lastAction(bt:UIButton) { delegate?.bottomBar(bottomBar: self, clickLast: bt) } @objc private func nextAction(bt:UIButton) { delegate?.bottomBar(bottomBar: self, clickNext: bt) } @objc private func catalogAction(bt:UIButton) { delegate?.bottomBar(bottomBar: self, clickCatalog: bt) } @objc private func darkAction(bt:UIButton) { delegate?.bottomBar(bottomBar: self, clickDark: bt) } @objc private func settingAction(bt:UIButton) { delegate?.bottomBar(bottomBar: self, clickSetting: bt) } @objc private func sliderAction(slider:UISlider) { delegate?.bottomBar(bottomBar: self, progress: slider.value) } } class ZSReaderMenuButton:UIButton { override func layoutSubviews() { super.layoutSubviews() imageView?.frame = CGRect(x: self.bounds.width/2 - 15, y: 0, width: 30, height: 30) titleLabel?.frame = CGRect(x: 0, y: (imageView?.frame.maxY ?? 30), width: self.bounds.width, height: 20) titleLabel?.textAlignment = .center } } ================================================ FILE: zhuishushenqi/NewVersion/Reader/ZSReaderBottomBigBar.swift ================================================ // // ZSReaderBottomBigBar.swift // zhuishushenqi // // Created by caony on 2020/1/11. // Copyright © 2020 QS. All rights reserved. // import UIKit protocol ZSReaderBottomBigBarDelegate:class { func bigBar(bigBar:ZSReaderBottomBigBar, progress:Float) func bigBar(bigBar:ZSReaderBottomBigBar, readerStyle:ZSReaderStyle) func bigBar(bigBar:ZSReaderBottomBigBar, fontAdd:UIButton) func bigBar(bigBar:ZSReaderBottomBigBar, fontPlus:UIButton) func bigBar(bigBar:ZSReaderBottomBigBar, animationStyle:UIButton) func bigBar(bigBar:ZSReaderBottomBigBar, moreSetting:UIButton) } class ZSReaderBottomBigBar: UIView, ZSReaderThemeSelectionViewDelegate { weak var delegate:ZSReaderBottomBigBarDelegate? lazy var progressBar:UISlider = { let bar = UISlider(frame: UIScreen.main.bounds) bar.minimumTrackTintColor = UIColor.white bar.maximumTrackTintColor = UIColor.gray bar.minimumValue = 0.0 bar.maximumValue = 1.0 bar.value = 1.0 bar.addTarget(self, action: #selector(sliderAction(slider:)), for: .valueChanged) return bar }() lazy var animationButton:ZSReaderMenuButton = { let bt = ZSReaderMenuButton(type: .custom) bt.setTitle("翻页动画", for: .normal) bt.setTitleColor(UIColor.white, for: .normal) bt.titleLabel?.font = UIFont.systemFont(ofSize: 11) bt.setImage(UIImage(named: "icon_tools_menu_30x30_"), for: .normal) bt.addTarget(self, action: #selector(catalogAction(bt:)), for: .touchUpInside) return bt }() lazy var eyeButton:ZSReaderMenuButton = { let bt = ZSReaderMenuButton(type: .custom) bt.setTitle("护眼模式", for: .normal) bt.titleLabel?.font = UIFont.systemFont(ofSize: 11) bt.setImage(UIImage(named: "pic_day_46x46_"), for: .selected) bt.setImage(UIImage(named: "pic_night_46x46_"), for: .normal) bt.setTitleColor(UIColor.white, for: .normal) bt.addTarget(self, action: #selector(darkAction(bt:)), for: .touchUpInside) return bt }() lazy var autoButton:ZSReaderMenuButton = { let bt = ZSReaderMenuButton(type: .custom) bt.setTitle("自动阅读", for: .normal) bt.setTitleColor(UIColor.white, for: .normal) bt.titleLabel?.font = UIFont.systemFont(ofSize: 11) bt.setImage(UIImage(named: "icon_tools_setting_30x30_"), for: .normal) bt.addTarget(self, action: #selector(settingAction(bt:)), for: .touchUpInside) return bt }() lazy var moreButton:ZSReaderMenuButton = { let bt = ZSReaderMenuButton(type: .custom) bt.setTitle("更多", for: .normal) bt.setTitleColor(UIColor.white, for: .normal) bt.titleLabel?.font = UIFont.systemFont(ofSize: 11) bt.setImage(UIImage(named: "icon_tools_setting_30x30_"), for: .normal) bt.addTarget(self, action: #selector(moreAction(bt:)), for: .touchUpInside) return bt }() lazy var lightPlusButton:UIButton = { let bt = UIButton(type: .custom) bt.setImage(UIImage(named: "QDReaderSetting_brightnessDown_24x24_"), for: .normal) bt.setTitleColor(UIColor.white, for: .normal) bt.addTarget(self, action: #selector(lastAction(bt:)), for: .touchUpInside) return bt }() lazy var lightAddButton:UIButton = { let bt = UIButton(type: .custom) bt.setImage(UIImage(named: "QDReaderSetting_brightnessUp_24x24_"), for: .normal) bt.setTitleColor(UIColor.white, for: .normal) bt.addTarget(self, action: #selector(nextAction(bt:)), for: .touchUpInside) return bt }() lazy var fontAddButton:UIButton = { let bt = UIButton(type: .custom) // bt.setImage(UIImage(named: ""), for: .normal) bt.setTitle("A+", for: .normal) bt.titleLabel?.font = UIFont.systemFont(ofSize: 22) bt.setTitleColor(UIColor.white, for: .normal) bt.layer.cornerRadius = 20 bt.layer.masksToBounds = true bt.layer.borderColor = UIColor.white.cgColor bt.layer.borderWidth = 0.5 bt.addTarget(self, action: #selector(fontAddAction(bt:)), for: .touchUpInside) return bt }() lazy var fontPlusButton:UIButton = { // 72.5 let bt = UIButton(type: .custom) // bt.setImage(UIImage(named: ""), for: .normal) bt.setTitle("A-", for: .normal) bt.titleLabel?.font = UIFont.systemFont(ofSize: 22) bt.setTitleColor(UIColor.white, for: .normal) bt.layer.cornerRadius = 20 bt.layer.masksToBounds = true bt.layer.borderColor = UIColor.white.cgColor bt.layer.borderWidth = 0.5 bt.addTarget(self, action: #selector(fontPlusAction(bt:)), for: .touchUpInside) return bt }() lazy var fontButton:UIButton = { let bt = UIButton(type: .custom) bt.setImage(UIImage(named: ""), for: .normal) bt.setTitle("系统字体", for: .normal) bt.titleLabel?.font = UIFont.systemFont(ofSize: 15) bt.setTitleColor(UIColor.white, for: .normal) bt.layer.cornerRadius = 20 bt.layer.masksToBounds = true bt.layer.borderColor = UIColor.white.cgColor bt.layer.borderWidth = 0.5 bt.addTarget(self, action: #selector(nextAction(bt:)), for: .touchUpInside) return bt }() lazy var fontSizeLabel:UILabel = { let lb = UILabel(frame: .zero) lb.textColor = UIColor.white lb.textAlignment = .center lb.font = UIFont.systemFont(ofSize: 13) lb.text = "\(ZSReader.share.theme.fontSize.size)" return lb }() lazy var selectionView:ZSReaderThemeSelectionView = { let view = ZSReaderThemeSelectionView(frame: CGRect(x: 0, y: 0, width: self.bounds.width, height: 40)) view.delegate = self return view }() override init(frame: CGRect) { super.init(frame: frame) addSubview(lightPlusButton) addSubview(progressBar) addSubview(lightAddButton) addSubview(fontPlusButton) addSubview(fontSizeLabel) addSubview(fontAddButton) addSubview(fontButton) addSubview(selectionView) addSubview(animationButton) addSubview(eyeButton) addSubview(autoButton) addSubview(moreButton) backgroundColor = UIColor.black } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func layoutSubviews() { super.layoutSubviews() lightPlusButton.frame = CGRect(origin: CGPoint(x: 20, y: 20), size: CGSize(width: 24, height: 30)) lightAddButton.frame = CGRect(x: self.bounds.width - 44, y: 20, width: 24, height: 30) progressBar.frame = CGRect(x: lightPlusButton.frame.maxX + 10, y: 20, width: self.bounds.width - lightPlusButton.frame.maxX * 2 - 20, height: 30) fontPlusButton.frame = CGRect(x: 20, y: lightPlusButton.frame.maxY + 20, width: 82.5, height: 40) fontSizeLabel.frame = CGRect(x: fontPlusButton.frame.maxX , y: lightPlusButton.frame.maxY + 20, width: 40, height: 40) fontAddButton.frame = CGRect(x: fontSizeLabel.frame.maxX, y: lightPlusButton.frame.maxY + 20, width: 82.5, height: 40) fontButton.frame = CGRect(x: self.bounds.width - 82.5 - 20, y: lightPlusButton.frame.maxY + 20, width: 82.5, height: 40) selectionView.frame = CGRect(x: 0, y: fontPlusButton.frame.maxY + 20, width: self.bounds.width, height: 40) let animationWidth:CGFloat = 50 let spaceX:CGFloat = (self.bounds.width - 40 - animationWidth * 4)/3.0 animationButton.frame = CGRect(x: 20, y: selectionView.frame.maxY + 20, width: animationWidth, height: 60) eyeButton.frame = CGRect(x: 20 + animationWidth + spaceX, y: selectionView.frame.maxY + 20, width: animationWidth, height: 60) autoButton.frame = CGRect(x: 20 + animationWidth * 2 + spaceX * 2, y: selectionView.frame.maxY + 20, width: animationWidth, height: 60) moreButton.frame = CGRect(x: 20 + animationWidth * 3 + spaceX * 3, y: selectionView.frame.maxY + 20, width: animationWidth, height: 60) } @objc private func fontAddAction(bt:UIButton) { delegate?.bigBar(bigBar: self, fontAdd: bt) } @objc private func fontPlusAction(bt:UIButton) { delegate?.bigBar(bigBar: self, fontPlus: bt) } @objc private func nextAction(bt:UIButton) { } @objc private func lastAction(bt:UIButton) { } @objc private func settingAction(bt:UIButton) { } @objc private func moreAction(bt:UIButton) { delegate?.bigBar(bigBar: self, moreSetting: bt) } @objc private func darkAction(bt:UIButton) { } @objc private func catalogAction(bt:UIButton) { delegate?.bigBar(bigBar: self, animationStyle: bt) } @objc private func sliderAction(slider:UISlider) { let brightness = ZSReader.share.theme.brightness let scale = 1/(brightness.maxValue - brightness.minValue) let progress = brightness.maxValue - ((slider.value - slider.minimumValue) / scale + brightness.minValue) delegate?.bigBar(bigBar: self, progress: progress) } //MARK: - ZSReaderThemeSelectionViewDelegate func selectionView(selectionView: ZSReaderThemeSelectionView, select: ZSReaderStyle) { delegate?.bigBar(bigBar: self, readerStyle: select) } deinit { QSLog("释放了") } } ================================================ FILE: zhuishushenqi/NewVersion/Reader/ZSReaderCache.swift ================================================ // // ZSReaderCache.swift // zhuishushenqi // // Created by caony on 2019/9/20. // Copyright © 2019 QS. All rights reserved. // import UIKit class ZSReaderCache { fileprivate var webService = ZSReaderWebService() fileprivate var book:BookDetail! fileprivate var sourceIndex = 1 fileprivate var cachedChapter:[String:QSChapter] = [:] static let shared = ZSReaderCache() private init() { } func request(book:BookDetail,_ callback:ZSBaseCallback?) { self.book = book // 1. 请求所有的章节信息 fetchResources { [unowned self](resources) in guard let source = self.actualSource() else { return } // 2.请求当前源所有的章节 self.fetchChapters(source: source, { [unowned self] (chapters) in self.book.chaptersInfo = chapters // 3. 请求第一章信息【如果有阅读记录,请求阅读记录的章节信息】 guard let chapter = self.initialChapter(chapters: self.book.chaptersInfo) else { return } // 如果缓存中已经存在,则取缓存 if let _ = self.cachedChapter[chapter.link] { callback?(nil) } else { // 不存在缓存,开始下载 self.webService.fetchChapter(key: chapter.link, { (body) in guard let chapterBody = body else { return } self.storeChapter(body: chapterBody, index: self.initialChapterIndex(chapters: self.book.chaptersInfo)) callback?(nil) }) } }) } } func load() ->QSPage { // 章节数与页数默认-1 let page = QSPage() let chapterIndex = initialChapterIndex(chapters: self.book.chaptersInfo) if chapterIndex < (self.book.chaptersInfo?.count ?? 0){ if let chapterInfo = self.book.chaptersInfo?[chapterIndex] { if let chapter = cachedChapter[chapterInfo.link] { let pageIndex = initialPageIndex(chapters: self.book.chaptersInfo) if pageIndex < chapter.pages.count { return chapter.pages[pageIndex] } } } } return page } private func fetch(chapter:ZSChapterInfo) { } private func initialChapter(chapters:[ZSChapterInfo]?) ->ZSChapterInfo? { if let record = book.record { if record.chapter < (chapters?.count ?? 0) { return chapters?[record.chapter] } } return chapters?.first } private func initialChapterIndex(chapters:[ZSChapterInfo]?) ->Int { if let record = book.record { if record.chapter < (chapters?.count ?? 0) { return record.chapter } } return 0 } private func initialPageIndex(chapters:[ZSChapterInfo]?) ->Int { if let record = book.record { if record.chapter < (chapters?.count ?? 0) { return record.page } } return 0 } private func fetchResources(_ callback:ZSBaseCallback<[ResourceModel]>?){ let key = book._id webService.fetchAllResource(key: key) { (resources) in self.book.resources = resources callback?(resources) } } private func fetchChapters(source:ResourceModel, _ callback:ZSBaseCallback<[ZSChapterInfo]>?){ webService.fetchAllChapters(key: source._id) { (chapters) in callback?(chapters) } } private func changeSource(index:Int) { if index < (self.book.resources?.count ?? 0) { self.book.record?.source = self.book.resources?[safe: index] } } private func actualSource() ->ResourceModel? { // 1. 如果阅读记录中存在source,则直接采用 if let source = self.book.record?.source { return source } // 2. 不存在则采用已经请求的源【0默认为追书官方源,因此从1开始】 let sourceCount = self.book.resources?.count ?? 0 if sourceIndex < sourceCount { return self.book.resources?[safe: sourceIndex] } return nil } private func fetchSourceIndex() ->Int? { guard let source = self.book.record?.source else { return nil } guard let resources = self.book.resources else { return nil } var index = 0 for model in resources { if model._id.isEqual(to: source._id) { break } index += 1 } return index } // 将新获取的章节信息存入chapterDict中 @discardableResult private func storeChapter(body:ZSChapterBody,index:Int)->QSChapter? { let chapterModel = self.book?.chaptersInfo?[safe: index] let qsChapter = QSChapter() if let link = chapterModel?.link { qsChapter.link = link // 如果使用追书正版书源,取的字段应该是cpContent,需要根据当前选择的源进行判断 qsChapter.isVip = chapterModel?.isVip ?? false qsChapter.order = chapterModel?.order ?? 0 qsChapter.currency = chapterModel?.currency ?? 0 qsChapter.cpContent = body.cpContent qsChapter.id = body.id qsChapter.content = body.body if let title = chapterModel?.title { qsChapter.title = title } qsChapter.curChapter = index qsChapter.getPages() // 直接计算page cachedChapter[link] = qsChapter return qsChapter } return nil } } ================================================ FILE: zhuishushenqi/NewVersion/Reader/ZSReaderCatalogViewController.swift ================================================ // // ZSReaderCatalogViewController.swift // zhuishushenqi // // Created by caony on 2020/1/9. // Copyright © 2020 QS. All rights reserved. // import UIKit protocol ZSReaderCatalogViewControllerDelegate:class { func catalog(catalog:ZSReaderCatalogViewController, clickChapter:ZSBookChapter) } class ZSReaderCatalogViewController: BaseViewController , ZSSearchInfoTableViewCellDelegate{ var model:ZSAikanParserModel? { didSet { } } var chapter:ZSBookChapter? weak var delegate:ZSReaderCatalogViewControllerDelegate? lazy var tableView:UITableView = { let tableView = UITableView(frame: .zero, style: .grouped) tableView.dataSource = self tableView.delegate = self tableView.sectionHeaderHeight = 0.01 tableView.sectionFooterHeight = 0.01 if #available(iOS 11, *) { tableView.contentInsetAdjustmentBehavior = .never } tableView.qs_registerCellClass(ZSSearchInfoTableViewCell.self) let blurEffect = UIBlurEffect(style: .extraLight) let blurEffectView = UIVisualEffectView(effect: blurEffect) tableView.backgroundView = blurEffectView return tableView }() fileprivate var reverse:Bool = false override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. setupSubview() setupNavItem() } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) navigationController?.isNavigationBarHidden = false } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) scrollToCurrent() } private func setupSubview() { title = model?.bookName view.addSubview(tableView) tableView.snp.remakeConstraints { (make) in make.left.right.equalToSuperview() make.top.equalTo(kNavgationBarHeight) make.height.equalTo(ScreenHeight - kNavgationBarHeight - kTabbarBlankHeight) } } private func setupNavItem() { let addItem = UIBarButtonItem(title: "正序/倒序", style: UIBarButtonItem.Style.done, target: self, action: #selector(sortReverse)) self.navigationItem.rightBarButtonItem = addItem } private func scrollToCurrent() { guard let book = model else { return } guard let cp = chapter else { return } if cp.chapterIndex < book.chaptersModel.count { if reverse { let indexPath = IndexPath(row: book.chaptersModel.count - cp.chapterIndex - 1, section: 0) self.tableView.scrollToRow(at: indexPath, at: .middle, animated: false) } else { let indexPath = IndexPath(row: cp.chapterIndex, section: 0) self.tableView.scrollToRow(at: indexPath, at: .middle, animated: false) } } } @objc private func sortReverse() { reverse = !reverse tableView.reloadData() } //MARK: - ZSSearchInfoTableViewCellDelegate func infoCell(cell: ZSSearchInfoTableViewCell, click download: UIButton) { guard let indexPath = tableView.indexPath(for: cell) else { return } if let chapter = self.model?.chaptersModel[indexPath.row] { ZSReaderDownloader.share.download(chapter: chapter,book:model!, reg: model!.content) { [weak self] (chapter) in if chapter.contentNil() { Toast.show(tip: "下载失败", .failure, 1) } else { self?.tableView.reloadData() } } } } } extension ZSReaderCatalogViewController:UITableViewDataSource, UITableViewDelegate { func numberOfSections(in tableView: UITableView) -> Int { return 1 } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return self.model?.chaptersModel.count ?? 0 } func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { return 40 } func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { return 0.01 } func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { return 0.01 } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.qs_dequeueReusableCell(ZSSearchInfoTableViewCell.self) cell?.delegate = self cell?.selectionStyle = .none cell?.accessoryType = .disclosureIndicator var chapter:ZSBookChapter! if reverse { chapter = self.model!.chaptersModel[self.model!.chaptersModel.count - indexPath.row - 1] } else { chapter = self.model!.chaptersModel[indexPath.row] } cell?.textLabel?.text = chapter.chapterName if let cp = ZSBookMemoryCache.share.content(for: chapter.chapterUrl) { if cp.chapterContent.length > 0 { cell?.downloadFinish() } } if let cp = self.chapter { if cp.chapterIndex == chapter.chapterIndex { cell?.textLabel?.textColor = UIColor.red } else { cell?.textLabel?.textColor = UIColor.black } } return cell! } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { guard let chapters = model?.chaptersModel else { return } if reverse { delegate?.catalog(catalog: self, clickChapter: chapters[chapters.count - 1 - indexPath.row]) } else { delegate?.catalog(catalog: self, clickChapter: chapters[indexPath.row]) } navigationController?.popViewController(animated: true) } } ================================================ FILE: zhuishushenqi/NewVersion/Reader/ZSReaderController.swift ================================================ // // ZSReaderViewController.swift // zhuishushenqi // // Created by caony on 2019/7/9. // Copyright © 2019 QS. All rights reserved. // import UIKit struct ZSReaderPref { static let leftFrame = CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width/3, height: UIScreen.main.bounds.height) static let centerFrame = CGRect(x: UIScreen.main.bounds.width/3, y: 0, width: UIScreen.main.bounds.width/3, height: UIScreen.main.bounds.height) static let rightFrame = CGRect(x: UIScreen.main.bounds.width*2.0/3, y: 0, width: UIScreen.main.bounds.width/3, height: UIScreen.main.bounds.height) init() { switch type { case .normal: readerVC = ZSNormalViewController() case .vertical: break case .horizonal: readerVC = ZSHorizonalViewController() break case .pageCurl: readerVC = ZSPageViewController() break } } var type:ZSReaderPageStyle { get { return ZSReader.share.pageStyle } } var readerVC:ZSReaderVCProtocol? var bookType:ZSReaderBookStyle = .online mutating func change() { switch type { case .normal: readerVC = ZSNormalViewController() case .vertical: break case .horizonal: readerVC = ZSHorizonalViewController() break case .pageCurl: readerVC = ZSPageViewController() break } } } class ZSReaderController: BaseViewController, ZSReaderToolbarDelegate,ZSReaderCatalogViewControllerDelegate,ZSReaderTouchAreaDelegate { var pref:ZSReaderPref = ZSReaderPref() var viewModel:ZSReaderBaseViewModel = ZSReaderBaseViewModel() var reader = ZSReader.share var toolBar:ZSReaderToolbar = ZSReaderToolbar(frame: UIScreen.main.bounds) var statusBarStyle:UIStatusBarStyle = .lightContent var statusBarHiden:Bool = true var touchArea:ZSReaderTouchArea = ZSReaderTouchArea(frame: UIScreen.main.bounds) lazy var speechView:ZSSpeechView = { let speechView = ZSSpeechView(frame: CGRect(x: 0, y: 0, width: self.view.bounds.size.width, height: self.view.bounds.size.height)) return speechView }() var voiceBook:VoiceBook = VoiceBook() convenience init(chapter:ZSBookChapter?,_ model:ZSAikanParserModel, bookType:ZSReaderBookStyle = .online) { self.init() viewModel.originalChapter = chapter viewModel.model = model toolBar.progress(minValue: 0, maxValue: Float(model.chaptersModel.count)) toolBar.delegate = self toolBar.bind(title: model.bookName) touchArea.delegate = self pref.bookType = bookType load() } convenience init(chapter:ZSBookChapter?,_ model:ZSAikanParserModel) { self.init() viewModel.originalChapter = chapter viewModel.model = model toolBar.progress(minValue: 0, maxValue: Float(model.chaptersModel.count)) toolBar.delegate = self toolBar.bind(title: model.bookName) touchArea.delegate = self load() } override func viewDidLoad() { super.viewDidLoad() let tapGesture:UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(tapAction(tap:))) view.addGestureRecognizer(tapGesture) changeReaderType() updateHistory() setupSpeech() } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) navigationController?.setNavigationBarHidden(true, animated: false) if let vc = pref.readerVC { vc.view.bounds = view.bounds } if let horVC = pref.readerVC as? ZSHorizonalViewController { horVC.dataSource = self horVC.delegate = self } if let horVC = pref.readerVC as? ZSPageViewController { horVC.dataSource = self horVC.delegate = self } pref.readerVC?.bind(toolBar: toolBar) } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) ZSBookMemoryCache.share.removeAllCache() } override func popAction() { pref.readerVC?.destroy() removeReaderVC() ZSBookMemoryCache.share.removeAllCache() super.popAction() } private func setupSpeech() { speechView.startHandler = { [weak self] selected in guard let strongSelf = self else { return } if selected! { strongSelf.startSpeech() } else { strongSelf.pauseSpeech() } } speechView.stopHandler = { [weak self] _ in self?.stopSpeech() self?.speechView.hiden() } voiceBook.completeHandler = { [weak self] error in QSLog(error) if error?.errorCode == 0 { // 读完了,自动翻下一页 self?.continueSpeech() } } } private func request() { // 请求当前章节 viewModel.request { (_) in } } func removeReaderVC() { if let vc = pref.readerVC { vc.willMove(toParent: self) vc.view.removeFromSuperview() vc.removeFromParent() } } private func load() { // 首次进入加载 if let oriChapter = viewModel.originalChapter { oriChapter.calPages() initialHistory(chapter: oriChapter) update(history: viewModel.readHistory!, chapter: oriChapter, page: oriChapter.pages.first!) viewModel.page = oriChapter.pages.first! pref.readerVC?.jumpPage(page: oriChapter.pages.first!, false, .forward) if oriChapter.contentNil() { viewModel.request(chapter: oriChapter) { [weak self] (cp,type) in // 比较当前章节与阅读记录中如果不同,说明请求完之前已经翻页了,不再跳转 cp.calPages() if cp.chapterUrl == oriChapter.chapterUrl { self?.update(history: self?.viewModel.readHistory, chapter: cp, page: cp.pages.first!) self?.viewModel.page = cp.pages.first! self?.pref.readerVC?.jumpPage(page: cp.pages.first!,false, .forward) } } } } else if let history = viewModel.readHistory { if history.chapter.contentNil() { history.chapter.calPages() } viewModel.page = history.page pref.readerVC?.jumpPage(page: history.page, false, .forward) } else { guard let chapter = viewModel.model?.chaptersModel.first else { return } if chapter.contentNil() { chapter.calPages() } initialHistory(chapter: chapter) update(history: viewModel.readHistory!, chapter: chapter, page: chapter.pages.first!) viewModel.page = chapter.pages.first! pref.readerVC?.jumpPage(page: chapter.pages.first!, false, .forward) viewModel.request(chapter: chapter) { [weak self] (cp,type) in // 比较当前章节与阅读记录中如果不同,说明请求完之前已经翻页了,不再跳转 cp.calPages() if cp.chapterUrl == chapter.chapterUrl { self?.update(history: self?.viewModel.readHistory, chapter: cp, page: cp.pages.first!) self?.viewModel.page = cp.pages.first! self?.pref.readerVC?.jumpPage(page: cp.pages.first!, false, .forward) } } } } private func changeReaderType(_ change:Bool=false) { if change { pref.change() if let history = viewModel.readHistory { viewModel.page = history.page pref.readerVC?.jumpPage(page: history.page, false, .forward) } if let horVC = pref.readerVC as? ZSHorizonalViewController { horVC.dataSource = self horVC.delegate = self } if let horVC = pref.readerVC as? ZSPageViewController { horVC.dataSource = self horVC.delegate = self } } if let vc = pref.readerVC { if let _ = vc.view.superview { removeReaderVC() } addChild(vc) view.addSubview(vc.view) vc.didMove(toParent: self) touchArea.removeFromSuperview() // view.addSubview(touchArea) // view.bringSubviewToFront(touchArea) vc.view.bounds = view.bounds } bind() } private func updateHistory() { viewModel.bookReload { [weak self] in if let history = self?.viewModel.readHistory { self?.viewModel.page = history.page self?.pref.readerVC?.jumpPage(page: history.page, false, .forward) } } } @objc private func tapAction(tap:UITapGestureRecognizer) { var isEditing:Bool = false if let horVC = pref.readerVC as? ZSPageViewController { if let pageVC = horVC.horizonalController.viewControllers?.first as? PageViewController { isEditing = pageVC.isPageEditing pageVC.clearEditing() } } if isEditing { return } let point = tap.location(in: view) let windowPoint = view.convert(point, to: view.window!) if ZSReaderPref.leftFrame.contains(windowPoint) { showLastPage() } else if ZSReaderPref.rightFrame.contains(windowPoint) { showNextPage() } else { toolBar.show(inView: view, true) } } //MARK: - page manager func bind() { pref.readerVC?.nextPageHandler = { [weak self] in self?.requestNextPage() } pref.readerVC?.lastPageHandler = { [weak self] in self?.requestLastPage() } } func request(chapter:ZSBookChapter, callback:ZSReaderTypeCallback?) { viewModel.request(chapter: chapter) { (cp, type) in if cp.pages.count > 0 { callback?(cp, type) } } } func requestLastPage() { guard let history = viewModel.readHistory else { return } guard let page = history.page else { return } let chapter = zs_currentChapter() if let lastP = chapter.getLastPage(page: page) { show(chapter: chapter, lastP) return } if let lastChapter = zs_lastChapter() { //新章节 show(chapter: lastChapter, nil, false) } } func requestNextPage() { guard let history = viewModel.readHistory else { return } guard let page = history.page else { return } let chapter = zs_currentChapter() if let nextP = chapter.getNextPage(page: page) { show(chapter: chapter, nextP) return } if let nextChapter = zs_nextChapter() { //新章节 show(chapter: nextChapter, nil) } } //MARK: - history manager func initialHistory(chapter:ZSBookChapter) { chapter.calPages() if let _ = viewModel.readHistory { } else { let history = ZSReadHistory() history.chapter = chapter history.page = chapter.pages.first viewModel.readHistory = history } } func update(history:ZSReadHistory?, chapter:ZSBookChapter, page:ZSBookPage) { history?.chapter = chapter history?.page = page } //MARK: - chapter manage private func zs_currentChapter() ->ZSBookChapter { guard let chapters = viewModel.model?.chaptersModel, chapters.count > 0 else { return ZSBookChapter() } guard let history = viewModel.readHistory else { let chapter = chapters.first! if chapter.pages.count == 0 { chapter.calPages() } let history = ZSReadHistory() history.chapter = chapter history.page = chapter.pages.first viewModel.readHistory = history return chapters.first! } // 可能存在修改字体大小等因素,因此重新计算 history.chapter.calPages() return history.chapter } private func zs_lastChapter() ->ZSBookChapter? { guard let book = viewModel.model else { return nil } let chapters = book.chaptersModel let currentC = zs_currentChapter() let chapterIndex = currentC.chapterIndex if (chapterIndex - 1) < chapters.count && (chapterIndex - 1 >= 0) { return chapters[chapterIndex - 1] } return nil } private func zs_nextChapter() -> ZSBookChapter? { guard let book = viewModel.model else { return nil } let chapters = book.chaptersModel let currentC = zs_currentChapter() let chapterIndex = currentC.chapterIndex if chapterIndex + 1 < chapters.count { return chapters[chapterIndex + 1] } return nil } func fontChange() { guard let history = viewModel.readHistory else { return } guard let chapter = history.chapter else { return } chapter.calPages() show(chapter: chapter, nil) } func showLastPage() { guard let history = viewModel.readHistory else { return } let chapter = zs_currentChapter() if let lastPage = chapter.getLastPage(page: history.page) { if let _ = pref.readerVC as? ZSPageViewController { viewModel.page = lastPage pref.readerVC?.jumpPage(page: lastPage, false, .reverse) } else if let _ = pref.readerVC as? ZSHorizonalViewController { viewModel.page = lastPage pref.readerVC?.jumpPage(page: lastPage, true, .reverse) } update(history: history, chapter: chapter, page: lastPage) } else if let lastChapter = zs_lastChapter() { // 新章节 show(chapter: lastChapter, nil, false, true, .reverse) } } func showNextPage() { guard let history = viewModel.readHistory else { return } let chapter = zs_currentChapter() if let nextPage = chapter.getNextPage(page: history.page) { update(history: history, chapter: chapter, page: nextPage) if let _ = pref.readerVC as? ZSPageViewController { viewModel.page = nextPage pref.readerVC?.jumpPage(page: nextPage, false, .forward) } else if let _ = pref.readerVC as? ZSHorizonalViewController { viewModel.page = nextPage pref.readerVC?.jumpPage(page: nextPage, true, .forward) } } else if let nextChapter = zs_nextChapter() { // 新章节 show(chapter: nextChapter, nil, true, true, .forward) } } func show(chapter:ZSBookChapter,_ direction:UIPageViewController.NavigationDirection = .forward,_ first:Bool = true) { guard let history = viewModel.readHistory else { return } if chapter.pages.count > 0 { let page = first ? chapter.pages.first!:chapter.pages.last! update(history: history, chapter: chapter, page: page) viewModel.page = page pref.readerVC?.jumpPage(page: page, false, direction) } } func show(chapter:ZSBookChapter,_ page:ZSBookPage? = nil,_ next:Bool = true,_ animated:Bool = false,_ direction:UIPageViewController.NavigationDirection = .forward) { guard let history = viewModel.readHistory else { return } if let p = page { viewModel.page = p pref.readerVC?.jumpPage(page: p, animated, direction) update(history: history, chapter: chapter, page: p) } else if !chapter.contentNil(){ let page = next ? chapter.pages.first!:chapter.pages.last! viewModel.page = page pref.readerVC?.jumpPage(page: page, animated, direction) update(history: history, chapter: chapter, page: page) } else { // 计算 chapter.calPages() // 更新历史记录 let page = chapter.pages.first! viewModel.page = page pref.readerVC?.jumpPage(page: page, animated, direction) update(history: history, chapter: chapter, page: page) request(chapter: chapter) { [weak self] (cp,type) in let first = next ? true:(type == .online ? true:false) self?.show(chapter: cp, direction, first) } } } //MARK: - ZSReaderTouchAreaDelegate func touchAreaTapCenter(touchAres: ZSReaderTouchArea) { if toolBar.isToolBarShow { toolBar.hiden(true) } else { toolBar.show(inView: view, true) } } //MARK: - ZSReaderToolbarDelegate func toolBar(toolBar: ZSReaderToolbar, clickBack: UIButton) { popAction() viewModel.saveHistory() } func toolBar(toolBar: ZSReaderToolbar, clickListen: UIButton) { toolBar.hiden(true) speechView.show() } func toolBarWillShow(toolBar: ZSReaderToolbar) { statusBarHiden = false UIView.animate(withDuration: 0.35, animations: { self.setNeedsStatusBarAppearanceUpdate() ZSFloatingManager.share.window?.rootViewController?.setNeedsStatusBarAppearanceUpdate() }, completion: nil) } func toolBarWillHiden(toolBar: ZSReaderToolbar) { statusBarHiden = true UIView.animate(withDuration: 0.35, animations: { self.setNeedsStatusBarAppearanceUpdate() ZSFloatingManager.share.window?.rootViewController?.setNeedsStatusBarAppearanceUpdate() }, completion: nil) } func toolBarDidShow(toolBar: ZSReaderToolbar) { } func toolBarDidHiden(toolBar: ZSReaderToolbar) { } func toolBar(toolBar:ZSReaderToolbar, clickLast:UIButton) { if let chapter = zs_lastChapter() { show(chapter: chapter, nil) } } func toolBar(toolBar:ZSReaderToolbar, clickNext:UIButton) { if let chapter = zs_nextChapter() { show(chapter: chapter, nil) } } func toolBar(toolBar:ZSReaderToolbar, clickCatalog:UIButton) { toolBar.hiden(false) let catalogVC = ZSReaderCatalogViewController() catalogVC.model = viewModel.model catalogVC.chapter = zs_currentChapter() catalogVC.delegate = self navigationController?.pushViewController(catalogVC, animated: true) } func toolBar(toolBar:ZSReaderToolbar, clickDark:UIButton) { } func toolBar(toolBar:ZSReaderToolbar, clickSetting:UIButton) { } func toolBar(toolBar: ZSReaderToolbar, clickMore: UIButton) { toolBar.hiden(false) let moreVC = QSMoreSettingController() let nav = UINavigationController(rootViewController: moreVC) present(nav, animated: true, completion: nil) } func toolBar(toolBar:ZSReaderToolbar, progress:Float) { guard let chapterModels = viewModel.model?.chaptersModel else { return } let chapterIndex = Int(progress) if chapterIndex >= 0 && chapterIndex < chapterModels.count { let chapter = chapterModels[chapterIndex] show(chapter: chapter, nil) } } func toolBar(toolBar:ZSReaderToolbar, lightProgress:Float) { touchArea.alpah = CGFloat(lightProgress) } func toolBar(toolBar: ZSReaderToolbar, readerStyle: ZSReaderStyle) { ZSReader.share.theme.readerStyle = readerStyle pref.readerVC?.changeBg(style: readerStyle) } func toolBar(toolBar: ZSReaderToolbar, fontAdd: UIButton) { if ZSReader.share.theme.fontSize.enableBigger { ZSReader.share.theme.fontSize.bigger() fontChange() } let enableFontAdd = ZSReader.share.theme.fontSize.enableBigger let enableFontPlus = ZSReader.share.theme.fontSize.enableSmaller toolBar.enablFontAdd(enableFontAdd) toolBar.enableFontPlus(enableFontPlus) } func toolBar(toolBar: ZSReaderToolbar, fontPlus: UIButton) { if ZSReader.share.theme.fontSize.enableSmaller { ZSReader.share.theme.fontSize.smaller() fontChange() } let enableFontAdd = ZSReader.share.theme.fontSize.enableBigger let enableFontPlus = ZSReader.share.theme.fontSize.enableSmaller toolBar.enablFontAdd(enableFontAdd) toolBar.enableFontPlus(enableFontPlus) } func toolBar(toolBar: ZSReaderToolbar, animationStyle: UIButton) { } func toolBar(toolBar:ZSReaderToolbar, select style:ZSReaderPageStyle) { toolBar.hiden(true) removeReaderVC() ZSReader.share.pageStyle = style changeReaderType(true) } func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { return nil } //MARK: - ZSReaderCatalogViewControllerDelegate func catalog(catalog: ZSReaderCatalogViewController, clickChapter: ZSBookChapter) { show(chapter: clickChapter, nil) } //MARK: - system override var preferredStatusBarStyle: UIStatusBarStyle { return statusBarStyle } override var prefersStatusBarHidden: Bool { return statusBarHiden } override var preferredStatusBarUpdateAnimation: UIStatusBarAnimation{ return .slide } deinit { QSLog("释放了") } func showPage(chapter:ZSBookChapter, _ page:ZSBookPage? = nil,_ first:Bool = true, handler:ZSReaderBaseCallback?) { if let p = page { handler?(p) } else if !chapter.contentNil(){ // 修改字体大小后需重新计算 chapter.calPages() let page = first ? chapter.pages.first!:chapter.pages.last! handler?(page) } else { // 计算 chapter.calPages() // 更新历史记录 let page = chapter.pages.first! handler?(page) request(chapter: chapter) { (cp,type) in cp.calPages() let p = first ? cp.pages.first!:cp.pages.last! handler?(p) } } } //MARK: - speech func startSpeech() { if voiceBook.isSpeaking() { stopSpeech() } let speaker = speechView.speaker if speaker.engineType == .local { let speakerPath = "\(filePath)\(speaker.name).jet" voiceBook.config.speakerPath = speakerPath voiceBook.config.voiceID = "\(speaker.speakerId)" voiceBook.engineLocal() continueSpeech() } else { let appid = "5ba0b197" // let xfyj = "5445f87d" // let xfyj2 = "591a4d99" // let zssq = "566551f4" let initString = "appid=\(appid)" IFlySpeechUtility.createUtility(initString) voiceBook.setVcn(name: speaker.name) voiceBook.engineCloud() continueSpeech() } } func pauseSpeech() { voiceBook.pause() } func resumeSpeech() { voiceBook.resume() } func stopSpeech() { voiceBook.stop() } /// 一段播完了,开始下一段 func continueSpeech() { var paragraph: String = viewModel.page?.content ?? "" paragraph = pref.readerVC?.nextParagraph(string: voiceBook.speakText) ?? "" if paragraph.count > 0 { pref.readerVC?.startEdit(for: paragraph) voiceBook.start(sentence: paragraph) } else { // 翻页 showNextPage() paragraph = pref.readerVC?.nextParagraph(string: "") ?? "" pref.readerVC?.startEdit(for: paragraph) voiceBook.start(sentence: paragraph) } } //MARK: - UIGestureRecognizer override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { return false } } extension ZSReaderController:UIPageViewControllerDataSource, UIPageViewControllerDelegate { func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? { if let currentPage = pageViewController.viewControllers?.first as? PageViewController { currentPage.clearEditing() } let pageVC = PageViewController() guard let history = viewModel.readHistory else { return nil } guard let page = history.page else { return nil } let chapter = zs_currentChapter() if let lastP = chapter.getLastPage(page: page) { showPage(chapter: chapter, lastP, false) { (p) in pageVC.newPage = p } } else if let lastChapter = zs_lastChapter() { //新章节 showPage(chapter: lastChapter, nil, false) { (p) in pageVC.newPage = p } } else { return nil } return pageVC } func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? { if let currentPage = pageViewController.viewControllers?.first as? PageViewController { currentPage.clearEditing() } let pageVC = PageViewController() guard let history = viewModel.readHistory else { return nil } guard let page = history.page else { return nil } let chapter = zs_currentChapter() if let lastP = chapter.getNextPage(page: page) { showPage(chapter: chapter, lastP, true) { (p) in pageVC.newPage = p } } else if let lastChapter = zs_nextChapter() { //新章节 showPage(chapter: lastChapter, nil, true) { (p) in pageVC.newPage = p } } else { return nil } return pageVC } func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool){ if completed { // 动画完成需要更新阅读记录 if let pageVC = pageViewController.viewControllers?.first as? PageViewController { if let page = pageVC.newPage { QSLog("\(page.chapterName),page: \(page.pageIndex)") guard let history = viewModel.readHistory else { return } guard let book = viewModel.model else { return } // 获取当前章节 let chapterIndex = page.chapterIndex if chapterIndex >= 0 && chapterIndex < book.chaptersModel.count { let chapter = book.chaptersModel[chapterIndex] // chapter存在会立即返回,r如果不存在,则只有一页,直接进入下一章 update(history: history, chapter: chapter, page: page) request(chapter: chapter) { [weak self] (cp,type) in self?.update(history: history, chapter: cp, page: page) } } } } } } } ================================================ FILE: zhuishushenqi/NewVersion/Reader/ZSReaderDownloader.swift ================================================ // // ZSBookDownloader.swift // zhuishushenqi // // Created by yung on 2020/1/5. // Copyright © 2020 QS. All rights reserved. // import UIKit import Alamofire import YungNetworkTool // 下载完成后放入内存缓存中 class ZSReaderDownloader { private var semaphore = DispatchSemaphore(value: 1) private var group = DispatchGroup() private var queue = DispatchQueue(label: "com.cnydev.zssq", qos: .default, attributes: .concurrent, autoreleaseFrequency: .inherit) static let share = ZSReaderDownloader() let defaultReplaces:[String:String] = ["
\n":"", "

":"", "

":"", "“":"", "”":"", "…":"", "":"", " ":" ", "
":"", "
":"\n" ] private var cancel = false private init() { } func cancelDownload() { cancel = true } func requestData(url:String, handler:@escaping ZSBaseCallback) { let task = DataTask(url: url) task.resultHandler = { (data, error) in handler(data) } task.resume() } func requestString(url:String, encoding:String.Encoding, contentReg:String, contentReplaceReg:String, handler:@escaping ZSBaseCallback) { func callback(handler:@escaping ZSBaseCallback, content:String) { DispatchQueue.main.async { handler(content) } } requestData(url: url) { [weak self] (data) in guard let strongSelf = self else { return } if let responseData = data { let originContent = strongSelf.getContent(htmlData: responseData, reg: contentReg, encoding: encoding) let targetContent = strongSelf.contentTrim(content: originContent, reg: contentReplaceReg) callback(handler: handler, content: targetContent) } else { callback(handler: handler, content: "") } } } func download(book:ZSAikanParserModel, start:Int, handler:@escaping ZSBaseCallback) { let chaptersInfo = book.chaptersModel let totalChapterCount = chaptersInfo.count if start > totalChapterCount { return } queue.async(group: group) { for index in start..) { let key = chapter.chapterUrl let encoding = String.Encoding.zs_encoding(str: book.searchEncoding) requestString(url: key, encoding: encoding, contentReg: book.content, contentReplaceReg: book.contentReplace) { [weak chapter](resultString) in guard let strongChapter = chapter else { return } if let string = resultString { strongChapter.chapterContent = string ZSBookCache.share.cacheContent(content: strongChapter, for: book.bookName) handler(strongChapter) } else { handler(strongChapter) } } } func getContent(htmlData:Data, reg:String, encoding:String.Encoding) ->String { let htmlString = String(data: htmlData, encoding: encoding) ?? "" guard let document = OCGumboDocument(htmlString: htmlString) else { return "" } let parse = AikanHtmlParser() let contentString = parse.string(withGumboNode: document, withAikanString: reg, withText: false) return contentString } func contentTrim(content:String, reg:String) -> String { let contentReplaceString = contentReplace(string: content, reg: reg) let brContent = contentReplaceString.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) let noBrContent = defaultContentReplace(string: brContent) return noBrContent } func contentReplace(string:String, reg:String) ->String { var resultString = string guard let data = reg.data(using: .utf8) else { return resultString } guard let json = try? JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.fragmentsAllowed) as? [[String:Any]] else { return resultString } for dict in json { let first = dict["first"] as? String ?? "" let noRegular:Bool = dict[bool:"noRegular"] if !noRegular { resultString = regularString(string: resultString, first: first) } else { resultString = resultString.replacingOccurrences(of: first, with: "") } } return resultString } private func regularString(string:String, first:String) ->String { var resultString = string let regStr = try? NSRegularExpression(pattern: first, options: NSRegularExpression.Options.caseInsensitive) if let results = regStr?.matches(in: resultString, options: NSRegularExpression.MatchingOptions.reportCompletion, range: NSMakeRange(0, resultString.oc_length)) { var removeLength = 0 for result in results { var range = result.range range.location = range.location - removeLength let subString = resultString.ocString.substring(with: range) resultString = resultString.ocString.replacingOccurrences(of: subString, with: "") removeLength = removeLength + subString.oc_length } } return resultString } private func defaultContentReplace(string:String)->String { var replaceString = string for (key,value) in defaultReplaces { replaceString = replaceString.replacingOccurrences(of: key, with: value) } return replaceString } } ================================================ FILE: zhuishushenqi/NewVersion/Reader/ZSReaderStyleSelectionView.swift ================================================ // // ZSReaderStyleSelectionView.swift // zhuishushenqi // // Created by yung on 2020/2/1. // Copyright © 2020 QS. All rights reserved. // import UIKit protocol ZSReaderStyleSelectionViewDelegate:class { func style(selectionView:ZSReaderStyleSelectionView, select style:ZSReaderPageStyle) } class ZSReaderStyleSelectionView: UIButton, UITableViewDataSource, UITableViewDelegate { weak var delegate:ZSReaderStyleSelectionViewDelegate? lazy var tableView:UITableView = { let tableView = UITableView(frame: .zero, style: .grouped) tableView.dataSource = self tableView.delegate = self tableView.sectionHeaderHeight = 0.01 tableView.sectionFooterHeight = 0.01 tableView.backgroundColor = UIColor.black if #available(iOS 11, *) { tableView.contentInsetAdjustmentBehavior = .never } tableView.qs_registerCellClass(ZSReaderStyleSelectionCell.self) // let blurEffect = UIBlurEffect(style: .extraLight) // let blurEffectView = UIVisualEffectView(effect: blurEffect) // tableView.backgroundView = blurEffectView return tableView }() var style:ZSReaderPageStyle! override init(frame: CGRect) { super.init(frame: frame) isUserInteractionEnabled = true backgroundColor = UIColor.black addSubview(tableView) addTarget(self, action: #selector(touchAction(bt:)), for: .touchUpInside) style = ZSReader.share.pageStyle } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func layoutSubviews() { super.layoutSubviews() tableView.frame = bounds } @objc private func touchAction(bt:UIButton) { } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return ZSReaderPageStyle.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.qs_dequeueReusableCell(ZSReaderStyleSelectionCell.self) let pageStyle = ZSReaderPageStyle(rawValue: indexPath.row) cell?.contentView.backgroundColor = UIColor.black cell?.textLabel?.textColor = UIColor.white cell?.textLabel?.text = pageStyle?.styleName cell?.isStyleSelected = pageStyle == style cell?.selectionStyle = .none return cell! } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { let pageStyle:ZSReaderPageStyle = ZSReaderPageStyle(rawValue: indexPath.row) ?? style style = pageStyle tableView.reloadData() delegate?.style(selectionView: self, select: pageStyle) } func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool { return true } } class ZSReaderStyleSelectionCell:UITableViewCell { var isStyleSelected:Bool = false { didSet { selectionView.isHidden = !isStyleSelected } } lazy var selectionView:UIImageView = { let view = UIImageView(image: UIImage(named: "peach_reader_def_xuance2_32_32_32x32_")) view.frame = CGRect(x: 0, y: 0, width: 32, height: 32) view.isHidden = true return view }() override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) contentView.addSubview(selectionView) accessoryType = .none } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func layoutSubviews() { super.layoutSubviews() selectionView.frame = CGRect(x: bounds.width - 32 - 15, y: bounds.height/2 - 16, width: 32, height: 32) } } ================================================ FILE: zhuishushenqi/NewVersion/Reader/ZSReaderThemeSelectionView.swift ================================================ // // ZSReaderThemeSelectionView.swift // zhuishushenqi // // Created by caony on 2020/1/11. // Copyright © 2020 QS. All rights reserved. // import UIKit protocol ZSReaderThemeSelectionViewDelegate:class { func selectionView(selectionView:ZSReaderThemeSelectionView, select:ZSReaderStyle) } class ZSReaderThemeSelectionView: UIView { weak var delegate:ZSReaderThemeSelectionViewDelegate? lazy var scrollView:UIScrollView = { let view = UIScrollView(frame: self.bounds) if #available(iOS 13.0, *) { view.automaticallyAdjustsScrollIndicatorInsets = false } else { // Fallback on earlier versions } view.showsHorizontalScrollIndicator = false return view }() override init(frame: CGRect) { super.init(frame: frame) addSubview(scrollView) setupSubviews() } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } private func setupSubviews() { let marginX:CGFloat = 20 let spaceX:CGFloat = 15 let width:CGFloat = (UIScreen.main.bounds.width - marginX * 2 - spaceX * 3)/4.0 let height:CGFloat = 40 for index in 0..UIButton { let bt = UIButton(type: .custom) bt.frame = CGRect(x: 0, y: 0, width: 82.5, height: 40) bt.layer.cornerRadius = 20 bt.layer.masksToBounds = true bt.tag = style.rawValue bt.layer.borderColor = style.borderColor.cgColor bt.layer.borderWidth = 0.5 bt.setImage(style.backgroundImage, for: .normal) return bt } } ================================================ FILE: zhuishushenqi/NewVersion/Reader/ZSReaderToolbar.swift ================================================ // // ZSReaderToolbar.swift // zhuishushenqi // // Created by caony on 2020/1/8. // Copyright © 2020 QS. All rights reserved. // import UIKit protocol ZSReaderToolbarDelegate:class { func toolBar(toolBar:ZSReaderToolbar, clickBack:UIButton) func toolBar(toolBar:ZSReaderToolbar, clickListen:UIButton) func toolBar(toolBar:ZSReaderToolbar, clickLast:UIButton) func toolBar(toolBar:ZSReaderToolbar, clickNext:UIButton) func toolBar(toolBar:ZSReaderToolbar, clickCatalog:UIButton) func toolBar(toolBar:ZSReaderToolbar, clickDark:UIButton) func toolBar(toolBar:ZSReaderToolbar, clickSetting:UIButton) func toolBar(toolBar:ZSReaderToolbar, clickMore:UIButton) func toolBar(toolBar:ZSReaderToolbar, progress:Float) func toolBar(toolBar:ZSReaderToolbar, lightProgress:Float) func toolBar(toolBar:ZSReaderToolbar, readerStyle:ZSReaderStyle) func toolBar(toolBar:ZSReaderToolbar, fontAdd:UIButton) func toolBar(toolBar:ZSReaderToolbar, fontPlus:UIButton) func toolBar(toolBar:ZSReaderToolbar, animationStyle:UIButton) func toolBar(toolBar:ZSReaderToolbar, select style:ZSReaderPageStyle) func toolBarWillShow(toolBar:ZSReaderToolbar) func toolBarDidShow(toolBar:ZSReaderToolbar) func toolBarWillHiden(toolBar:ZSReaderToolbar) func toolBarDidHiden(toolBar:ZSReaderToolbar) func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? } class ZSReaderToolbar: UIView, ZSReaderTopbarDelegate, ZSReaderBottomBarDelegate, ZSReaderBottomBigBarDelegate,ZSReaderStyleSelectionViewDelegate { weak var delegate:ZSReaderToolbarDelegate? private let bottomBarHeight:CGFloat = 140 private let bottomBarBigHeight:CGFloat = 250 var isToolBarShow:Bool { return topBar.frame.origin.y >= 0 } var superTapGesture:UITapGestureRecognizer? lazy var bgView:UIView = { let view = UIView(frame: UIScreen.main.bounds) view.backgroundColor = UIColor(white: 0.0, alpha: 0.0) return view }() lazy var topBar:ZSReaderTopbar = { let top = ZSReaderTopbar(frame: CGRect(x: 0, y: -kNavgationBarHeight, width: self.bounds.width, height: kNavgationBarHeight)) top.delegate = self return top }() lazy var bottomBar:ZSReaderBottomBar = { let bar = ZSReaderBottomBar(frame: CGRect(x: 0, y: self.bounds.height, width: self.bounds.width, height: bottomBarHeight)) bar.delegate = self return bar }() lazy var bottomBigBar:ZSReaderBottomBigBar = { let bar = ZSReaderBottomBigBar(frame: CGRect(x: 0, y: self.bounds.height - bottomBarBigHeight - kTabbarBlankHeight, width: self.bounds.width, height: bottomBarBigHeight + kTabbarBlankHeight)) bar.delegate = self bar.isHidden = true return bar }() lazy var readerStyleView:ZSReaderStyleSelectionView = { let view = ZSReaderStyleSelectionView(frame: CGRect(x: 0, y: self.bounds.height, width: self.bounds.width, height: 375)) view.delegate = self return view }() override init(frame: CGRect) { super.init(frame: frame) addSubview(bgView) addSubview(topBar) addSubview(bottomBar) addSubview(bottomBigBar) addSubview(readerStyleView) isUserInteractionEnabled = true let tap = UITapGestureRecognizer(target: self, action: #selector(tapAction)) bgView.addGestureRecognizer(tap) // let pan = UIPanGestureRecognizer { (_) in // // } // addGestureRecognizer(pan) bgView.frame = CGRect(x: 0, y: topBar.height, width: self.bounds.width, height: self.bounds.height - topBar.height - bottomBar.height) } func progress(minValue:Float,maxValue:Float) { bottomBar.progressBar.minimumValue = minValue bottomBar.progressBar.maximumValue = maxValue } func gesture(for view:UIView) ->UITapGestureRecognizer? { let gestures = view.gestureRecognizers ?? [] var gesture:UITapGestureRecognizer? for ges in gestures { if ges.isKind(of: UITapGestureRecognizer.self) { gesture = ges as? UITapGestureRecognizer break } } return gesture } func show(inView:UIView,_ animated:Bool) { if let gesture = gesture(for: inView) { superTapGesture = gesture inView.removeGestureRecognizer(gesture) } bottomBar.isHidden = false bottomBigBar.isHidden = true readerStyleView.isHidden = true if animated { self.delegate?.toolBarWillShow(toolBar: self) UIView.animate(withDuration: 0.5, animations: { self.topBar.frame.origin.y = 0 self.bottomBar.frame.origin.y = self.bounds.height - self.bottomBarHeight self.bottomBigBar.frame.origin.y = self.bounds.height - self.bottomBarBigHeight - kTabbarBlankHeight self.delegate?.toolBarDidShow(toolBar: self) }) { (finish) in } inView.addSubview(self) } else { self.delegate?.toolBarWillShow(toolBar: self) self.topBar.frame.origin.y = 0 self.bottomBar.frame.origin.y = self.bounds.height - self.bottomBarHeight self.bottomBigBar.frame.origin.y = self.bounds.height - self.bottomBarBigHeight - kTabbarBlankHeight inView.addSubview(self) self.delegate?.toolBarDidShow(toolBar: self) } } func hiden(_ animated:Bool) { if let gesture = superTapGesture { if let superView = self.superview { superView.addGestureRecognizer(gesture) } } if animated { self.delegate?.toolBarWillHiden(toolBar: self) UIView.animate(withDuration: 0.5, animations: { self.topBar.frame.origin.y = -kNavgationBarHeight self.bottomBar.frame.origin.y = self.bounds.height self.bottomBigBar.frame.origin.y = self.bounds.height self.readerStyleView.frame = CGRect(x: 0, y: self.bounds.height, width: self.bounds.width, height: 375) }) { (finish) in self.delegate?.toolBarDidHiden(toolBar: self) self.removeFromSuperview() } } else { self.delegate?.toolBarWillHiden(toolBar: self) self.topBar.frame.origin.y = -kNavgationBarHeight self.bottomBar.frame.origin.y = self.bounds.height self.bottomBigBar.frame.origin.y = self.bounds.height self.readerStyleView.frame = CGRect(x: 0, y: self.bounds.height, width: self.bounds.width, height: 375) removeFromSuperview() self.delegate?.toolBarDidHiden(toolBar: self) } } func bind(title:String) { topBar.titleLabel.text = title } func enableFontPlus(_ enable:Bool) { self.bottomBigBar.fontPlusButton.isEnabled = enable self.bottomBigBar.fontSizeLabel.text = "\(ZSReader.share.theme.fontSize.size)" } func enablFontAdd(_ enable:Bool) { self.bottomBigBar.fontAddButton.isEnabled = enable } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } @objc private func tapAction() { hiden(true) } deinit { QSLog("释放了") } override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { let view = super.hitTest(point, with: event) // let readerPoint = self.convert(point, to: readerStyleView) // if readerStyleView.frame.contains(point) { // return readerStyleView // } if let other = delegate?.hitTest(point, with: event) { return other } return view } //MARK: - ZSReaderTopbarDelegate func topBar(topBar: ZSReaderTopbar, clickBack: UIButton) { removeFromSuperview() delegate?.toolBar(toolBar: self, clickBack: clickBack) } func topBar(topBar: ZSReaderTopbar, clickListen: UIButton) { delegate?.toolBar(toolBar: self, clickListen: clickListen) } //MARK: - ZSReaderBottomBarDelegate func bottomBar(bottomBar:ZSReaderBottomBar, clickLast:UIButton) { delegate?.toolBar(toolBar: self, clickLast: clickLast) } func bottomBar(bottomBar:ZSReaderBottomBar, clickNext:UIButton) { delegate?.toolBar(toolBar: self, clickNext: clickNext) } func bottomBar(bottomBar:ZSReaderBottomBar, clickCatalog:UIButton) { delegate?.toolBar(toolBar: self, clickCatalog: clickCatalog) } func bottomBar(bottomBar:ZSReaderBottomBar, clickDark:UIButton) { delegate?.toolBar(toolBar: self, clickDark: clickDark) } func bottomBar(bottomBar:ZSReaderBottomBar, clickSetting:UIButton) { delegate?.toolBar(toolBar: self, clickSetting: clickSetting) bottomBar.isHidden = true bottomBigBar.isHidden = false bgView.frame = CGRect(x: 0, y: topBar.height, width: self.bounds.width, height: self.bounds.height - topBar.height - bottomBigBar.height) } func bottomBar(bottomBar:ZSReaderBottomBar, progress:Float) { delegate?.toolBar(toolBar: self, progress: progress) } //MARK: - ZSReaderBottomBigBarDelegate func bigBar(bigBar: ZSReaderBottomBigBar, progress: Float) { delegate?.toolBar(toolBar: self, lightProgress: progress) } func bigBar(bigBar: ZSReaderBottomBigBar, readerStyle: ZSReaderStyle) { delegate?.toolBar(toolBar: self, readerStyle: readerStyle) } func bigBar(bigBar: ZSReaderBottomBigBar, fontAdd: UIButton) { delegate?.toolBar(toolBar: self, fontAdd: fontAdd) } func bigBar(bigBar: ZSReaderBottomBigBar, fontPlus: UIButton) { delegate?.toolBar(toolBar: self, fontPlus: fontPlus) } func bigBar(bigBar: ZSReaderBottomBigBar, animationStyle: UIButton) { delegate?.toolBar(toolBar: self, animationStyle: animationStyle) readerStyleView.isHidden = false bottomBar.isHidden = true bigBar.isHidden = true readerStyleView.frame = CGRect(x: 0, y: self.bounds.height - 375, width: self.bounds.width, height: 375) readerStyleView.tableView.reloadData() } func bigBar(bigBar: ZSReaderBottomBigBar, moreSetting: UIButton) { delegate?.toolBar(toolBar: self, clickMore: moreSetting) } //MARK: - ZSReaderStyleSelectionViewDelegate func style(selectionView:ZSReaderStyleSelectionView, select style:ZSReaderPageStyle) { delegate?.toolBar(toolBar: self, select: style) } } ================================================ FILE: zhuishushenqi/NewVersion/Reader/ZSReaderTopbar.swift ================================================ // // ZSReaderTopbar.swift // zhuishushenqi // // Created by caony on 2020/1/8. // Copyright © 2020 QS. All rights reserved. // import UIKit protocol ZSReaderTopbarDelegate:class { func topBar(topBar:ZSReaderTopbar, clickBack:UIButton) func topBar(topBar:ZSReaderTopbar, clickListen:UIButton) } class ZSReaderTopbar: UIView { weak var delegate:ZSReaderTopbarDelegate? lazy var backButton:UIButton = { let bt = UIButton(type: .custom) bt.setTitle("返回", for: .normal) bt.setTitleColor(UIColor.white, for: .normal) bt.titleLabel?.font = UIFont.systemFont(ofSize: 17) bt.setImage(UIImage(named: "bg_back_white"), for: .normal) bt.addTarget(self, action: #selector(backAction(bt:)), for: .touchUpInside) return bt }() lazy var titleLabel:UILabel = { let view = UILabel() view.textAlignment = .center view.textColor = UIColor.white view.font = UIFont.systemFont(ofSize: 17) return view }() lazy var listenButton:UIButton = { let bt = UIButton(type: .custom) bt.setImage(UIImage(named: "readAloud"), for: .normal) bt.addTarget(self, action: #selector(listenAction(bt:)), for: .touchUpInside) return bt }() override init(frame: CGRect) { super.init(frame: frame) addSubview(backButton) addSubview(titleLabel) addSubview(listenButton) backgroundColor = UIColor.black } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func layoutSubviews() { super.layoutSubviews() let statusHeight = UIApplication.shared.statusBarFrame.height backButton.frame = CGRect(x: 10, y: statusHeight + 5, width: 60, height: 30) titleLabel.frame = CGRect(origin: CGPoint(x: 70, y: statusHeight + 5), size: CGSize(width: ScreenWidth - 140, height: 30)) listenButton.frame = CGRect(origin: CGPoint(x: self.bounds.size.width - 50, y: statusHeight), size: CGSize(width: 49, height: 49)) } @objc private func backAction(bt:UIButton) { delegate?.topBar(topBar: self, clickBack: bt) } @objc private func listenAction(bt:UIButton) { delegate?.topBar(topBar: self, clickListen: bt) } } ================================================ FILE: zhuishushenqi/NewVersion/Reader/ZSReaderTouchArea.swift ================================================ // // ZSReaderTouchArea.swift // zhuishushenqi // // Created by caony on 2020/1/9. // Copyright © 2020 QS. All rights reserved. // import UIKit protocol ZSReaderTouchAreaDelegate:class { func touchAreaTapCenter(touchAres:ZSReaderTouchArea) } class ZSReaderTouchManager { var touchWindow:ZSReaderTouchWindow! static let share = ZSReaderTouchManager() private init() { touchWindow = ZSReaderTouchWindow() touchWindow.frame = UIScreen.main.bounds touchWindow.windowLevel = .normal touchWindow.backgroundColor = UIColor.clear } func show(view:UIView) { touchWindow.addSubview(view) touchWindow.isHidden = false } func hiden(view:UIView) { view.removeFromSuperview() touchWindow.isHidden = true } func hiden() { touchWindow.isHidden = true for subview in touchWindow.subviews { subview.removeFromSuperview() } } } class ZSReaderTouchWindow: UIWindow { } class ZSReaderTouchArea: UIView, UIGestureRecognizerDelegate { private lazy var backgroundView:UIView = { let view = UIView(frame: self.bounds) view.backgroundColor = UIColor.black view.alpha = alpah view.isUserInteractionEnabled = false return view }() var alpah:CGFloat = 0.0 { didSet { backgroundView.alpha = alpah } } weak var delegate:ZSReaderTouchAreaDelegate? override init(frame: CGRect) { super.init(frame: frame) addSubview(backgroundView) isUserInteractionEnabled = true let tap = UITapGestureRecognizer(target: self, action: #selector(tapAction(tap:))) addGestureRecognizer(tap) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func layoutSubviews() { super.layoutSubviews() } @objc private func tapAction(tap:UITapGestureRecognizer) { delegate?.touchAreaTapCenter(touchAres: self) } override func touchesEnded(_ touches: Set, with event: UIEvent?) { } } ================================================ FILE: zhuishushenqi/NewVersion/Reader/ZSReaderVCProtocol.swift ================================================ // // ZSReaderProtocol.swift // zhuishushenqi // // Created by caony on 2019/9/19. // Copyright © 2019 QS. All rights reserved. // import Foundation typealias ZSReaderPageHandler = ()->Void protocol ZSReaderVCProtocol:UIViewController { // 遵循协议的子页面向reader索取page var nextPageHandler:ZSReaderPageHandler? { get set } var lastPageHandler:ZSReaderPageHandler? { get set } func nextParagraph(string:String) ->String func startEdit(for string:String) func bind(toolBar:ZSReaderToolbar) func destroy() func changeBg(style:ZSReaderStyle) // reader调用子页面更新page func jumpPage(page:ZSBookPage,_ animated:Bool,_ direction:UIPageViewController.NavigationDirection) } ================================================ FILE: zhuishushenqi/NewVersion/Reader/ZSVerticalViewController.swift ================================================ // // ZSVerticalViewController.swift // zhuishushenqi // // Created by caony on 2019/7/9. // Copyright © 2019 QS. All rights reserved. // import UIKit class ZSVerticalViewController: BaseViewController, ZSReaderVCProtocol { fileprivate var pageVC:PageViewController = PageViewController() weak var toolBar:ZSReaderToolbar? weak var dataSource:UIPageViewControllerDataSource? weak var delegate:UIPageViewControllerDelegate? var nextPageHandler: ZSReaderPageHandler? var lastPageHandler: ZSReaderPageHandler? lazy var tableView:UITableView = { let tableView = UITableView(frame: .zero, style: .grouped) tableView.dataSource = self tableView.delegate = self tableView.sectionHeaderHeight = 0.01 tableView.sectionFooterHeight = 0.01 if #available(iOS 11, *) { tableView.contentInsetAdjustmentBehavior = .never } tableView.register(ZSShelfTableViewCell.self, forCellReuseIdentifier: "\(ZSShelfTableViewCell.self)") tableView.qs_registerHeaderFooterClass(ZSBookShelfHeaderView.self) let blurEffect = UIBlurEffect(style: .extraLight) let blurEffectView = UIVisualEffectView(effect: blurEffect) tableView.backgroundView = blurEffectView return tableView }() override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. } func nextParagraph(string:String) -> String { return pageVC.nextParagraph(string: string) } func startEdit(for string:String) { pageVC.startEdit(for: string) } func bind(toolBar: ZSReaderToolbar) { self.toolBar = toolBar } func destroy() { pageVC.destroy() } func changeBg(style: ZSReaderStyle) { pageVC.bgView.image = style.backgroundImage } func jumpPage(page: ZSBookPage,_ animated:Bool, _ direction:UIPageViewController.NavigationDirection = .forward) { pageVC.newPage = page tableView.beginUpdates() tableView.reloadSection(UInt(page.chapterIndex), with: UITableView.RowAnimation.automatic) tableView.endUpdates() } private func setupGesture() { let tap = UITapGestureRecognizer(target: self, action: #selector(tapAction(tap:))) tap.numberOfTouchesRequired = 1 tap.numberOfTapsRequired = 1 view.addGestureRecognizer(tap) } @objc private func tapAction(tap:UITapGestureRecognizer) { toolBar?.show(inView: view, true) } } extension ZSVerticalViewController:UITableViewDataSource, UITableViewDelegate { func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return 0 } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { return UITableViewCell() } } ================================================ FILE: zhuishushenqi/NewVersion/Search/AikanHtmlParser.h ================================================ // // AikanHtmlParser.h // CJDemo // // Created by yung on 2019/10/15. // Copyright © 2019 cj. All rights reserved. // #import #import "AikanParserModel.h" #import "OCGumbo.h" #import "OCGumbo+Query.h" NS_ASSUME_NONNULL_BEGIN @interface AikanHtmlParser : NSObject - (void)parseSearchListWithModel:(AikanParserModel *)arg1 html:(NSString *)arg2 toArray:(NSMutableArray *)arg3; - (OCQueryObject *)elementArrayWithGumboNode:(OCGumboNode *)arg1 withRegexString:(NSString *)arg2; - (NSString *)stringWithGumboNode:(id)arg1 withAikanString:(id)arg2 withText:(_Bool)arg3; - (id)getMatchString:(id)arg1 regString:(id)arg2; @end NS_ASSUME_NONNULL_END ================================================ FILE: zhuishushenqi/NewVersion/Search/AikanHtmlParser.m ================================================ // // AikanHtmlParser.m // CJDemo // // Created by yung on 2019/10/15. // Copyright © 2019 cj. All rights reserved. // #import "AikanHtmlParser.h" @implementation AikanHtmlParser - (NSString *)stringWithGumboNode:(OCGumboNode *)arg1 withAikanString:(NSString *)arg2 withText:(_Bool)arg3 { if (arg2 && arg1) { NSArray * regs = [arg2 componentsSeparatedByString:@"@"]; if (regs.count != 4) { return @""; } NSString * reg1 = [regs objectAtIndex:1]; if (reg1.length) { OCQueryObject * obj = [self elementArrayWithGumboNode:arg1 withRegexString:reg1]; if (obj.count) { NSString * reg2 = [regs objectAtIndex:2]; NSInteger reg2IntValue = [reg2 integerValue]; BOOL full; OCGumboNode * node; if (obj.count > reg2IntValue) { full = YES; node = [obj objectAtIndex:reg2IntValue]; } else { full = NO; } if (full) { NSString * reg3 = [regs objectAtIndex:3]; if ([reg3 isEqualToString:@"own:"]) { NSArray * text = node.Query(@""); OCGumboNode * node = text.lastObject; return node.text(); } else if (reg3.length >= 5) { NSString * sub3 = [reg3 substringToIndex:4]; if ([sub3 isEqualToString:@"abs:"]) { NSString * reg3Last = [reg3 substringFromIndex:4]; NSString * attr = node.attr(reg3Last); if (attr) { NSString * attrRe = [NSString stringWithFormat:@"%@",attr]; if ([attrRe isEqualToString:@""]) { return @""; } else { return attrRe; } } else { return @""; } } } else if (reg3.length > 0) { NSString * sub3 = [reg3 substringToIndex:1]; if ([sub3 isEqualToString:@":"]) { NSString * text = node.text(); NSString * reg3Last = [reg3 substringFromIndex:1]; NSString * matchString = [self getMatchString:text regString:reg3Last]; return matchString; } else { NSString * attr = node.attr(sub3); if (attr) { NSString * attrRe = [NSString stringWithFormat:@"%@",attr]; if ([attrRe isEqualToString:@""]) { return @""; } else { return attrRe; } } else { return @""; } } } if (arg3) { NSString * text = node.text(); if (!text) { return @""; } NSString * attrRe = [NSString stringWithFormat:@"%@",text]; if ([attrRe isEqualToString:@""]) { return @""; } else { return attrRe; } } else { NSString * html = node.html(); if (!html) { return @""; } NSString * attrRe = [NSString stringWithFormat:@"%@",html]; if ([attrRe isEqualToString:@""]) { return @""; } else { return attrRe; } } } } } else { NSString * reg3 = [regs objectAtIndex:3]; if ([reg3 isEqualToString:@"own:"]) { NSArray * text = arg1.Query(@""); OCGumboNode * node = text.lastObject; return node.text(); } else if (reg3.length >= 5) { NSString * sub3 = [reg3 substringToIndex:4]; if ([sub3 isEqualToString:@"abs:"]) { NSString * reg3Last = [reg3 substringFromIndex:4]; NSString * attr = arg1.attr(reg3Last); if (attr) { NSString * attrRe = [NSString stringWithFormat:@"%@",attr]; if ([attrRe isEqualToString:@""]) { return @""; } else { return attrRe; } } else { return @""; } } } else if (reg3.length > 0) { NSString * sub3 = [reg3 substringToIndex:1]; if ([sub3 isEqualToString:@":"]) { NSString * text = arg1.text(); NSString * reg3Last = [reg3 substringFromIndex:1]; NSString * matchString = [self getMatchString:text regString:reg3Last]; return matchString; } else { NSString * attr = arg1.attr(sub3); if (attr) { NSString * attrRe = [NSString stringWithFormat:@"%@",attr]; if ([attrRe isEqualToString:@""]) { return @""; } else { return attrRe; } } else { return @""; } } } if (arg3) { NSString * text = arg1.text(); if (!text) { return @""; } NSString * attrRe = [NSString stringWithFormat:@"%@",text]; if ([attrRe isEqualToString:@""]) { return @""; } else { return attrRe; } } else { NSString * html = arg1.html(); if (!html) { return @""; } NSString * attrRe = [NSString stringWithFormat:@"%@",html]; if ([attrRe isEqualToString:@""]) { return @""; } else { return attrRe; } } } } return @""; } - (NSString *)getMatchString:(NSString *)arg1 regString:(NSString *)arg2 { if (arg1.length) { NSMutableString * string = [NSMutableString stringWithCapacity:0]; NSRegularExpression * reg = [NSRegularExpression regularExpressionWithPattern:arg2 options:(NSRegularExpressionCaseInsensitive) error:nil]; NSArray * results = [reg matchesInString:arg1 options:(NSMatchingReportProgress) range:NSMakeRange(0, arg1.length)]; NSString * range1String; if (results.count) { for (NSInteger index = 0; index < results.count; index++) { NSTextCheckingResult * result = [results objectAtIndex:index]; NSRange range = [result range]; NSString * subString = [arg1 substringWithRange:range]; [string appendString:subString]; if ([result numberOfRanges] >= 2) { NSRange range1 = [result rangeAtIndex:1]; range1String = [arg1 substringWithRange:range1]; } } } else { return @""; } if (results.count) { [string appendString:[arg1 substringFromIndex:0]]; } return range1String; } else { return @""; } return nil; } - (void)parseSearchListWithModel:(AikanParserModel *)arg1 html:(NSString *)arg2 toArray:(NSMutableArray *)arg3 { OCGumboDocument * document = [[OCGumboDocument alloc] initWithHTMLString:arg2]; NSString * booksReg = arg1.books; NSArray * elements = [self elementArrayWithGumboNode:document withRegexString:booksReg]; } - (OCQueryObject *)elementArrayWithGumboNode:(OCGumboNode *)arg1 withRegexString:(NSString *)arg2 { NSArray * regs = [arg2 componentsSeparatedByString:@" "]; OCQueryObject *next; if (regs.count > 0) { for (NSInteger index = 0; index < regs.count; index++) { NSString * reg = regs[index]; reg = [reg stringByReplacingOccurrencesOfString:@"=" withString:@" "]; if (index) { next = next.find(reg); } else { next = arg1.Query(reg); } } } return next; } @end // //学霸的黑科技系统TXT下载_学霸的黑科技系统晨星LL - 笔趣阁 // // // // // // // // // // // // // // // // // // // // // // // // // // // //
// //
// //
//
//
// //
//
//

学霸的黑科技系统简介

//
//

“系统,积分能兑钱吗?”“不能。”“干,那我要你何用!”“本系统能让你当上学爸,全人类爸爸的爸,你还要钱有什么用?”

// //
// //
//
//

学霸的黑科技系统书友评论

//
// //
// // //
//
//
//
//
//
// //
// //

请所有作者发布作品时务必遵守国家互联网信息管理办法规定,我们拒绝任何色情小说,一经发现,即作删除
//本站所收录作品、社区话题、书库评论及本站所做之广告均属其个人行为,与本站立场无关
//Copyright©2015-2016 www.boquge.com All rights Reserved  版权所有 
//

//
// //
// // ================================================ FILE: zhuishushenqi/NewVersion/Search/AikanParserModel.h ================================================ // // AikanParserModel.h // ZSSouShu // // Created by yung on 2019/10/8. // Copyright © 2019 CJ. All rights reserved. // #import NS_ASSUME_NONNULL_BEGIN @interface AikanParserModel : NSObject @property(retain, nonatomic) NSDate *errDate; // @synthesize errDate=_errDate; @property(copy, nonatomic) NSString *searchUrl; // @synthesize searchUrl=_searchUrl; @property(copy, nonatomic) NSString *name; // @synthesize name=_name; @property(nonatomic) unsigned long long type; // @synthesize type=_type; @property(nonatomic) _Bool enabled; // @synthesize enabled=_enabled; @property(nonatomic) _Bool checked; // @synthesize checked=_checked; @property(copy, nonatomic) NSString *searchEncoding; // @synthesize searchEncoding=_searchEncoding; @property(copy, nonatomic) NSString *host; // @synthesize host=_host; @property(copy, nonatomic) NSString *contentReplace; // @synthesize contentReplace=_contentReplace; @property(copy, nonatomic) NSString *contentRemove; // @synthesize contentRemove=_contentRemove; @property(copy, nonatomic) NSString *content; // @synthesize content=_content; @property(nonatomic) _Bool chaptersReverse; // @synthesize chaptersReverse=_chaptersReverse; @property(copy, nonatomic) NSString *chapterUrl; // @synthesize chapterUrl=_chapterUrl; @property(copy, nonatomic) NSString *chapterName; // @synthesize chapterName=_chapterName; @property(copy, nonatomic) NSString *chapters; // @synthesize chapters=_chapters; @property(copy, nonatomic) NSArray *chaptersModel; // @synthesize chapters=_chapters; @property(copy, nonatomic) NSString *detailBookIcon; // @synthesize detailBookIcon=_detailBookIcon; @property(copy, nonatomic) NSString *detailChaptersUrl; // @synthesize detailChaptersUrl=_detailChaptersUrl; @property(copy, nonatomic) NSString *detailBookDesc; // @synthesize bookDesc=_bookDesc; @property(copy, nonatomic) NSString *bookLastChapterName; // @synthesize bookLastChapterName=_bookLastChapterName; @property(copy, nonatomic) NSString *bookUpdateTime; // @synthesize bookUpdateTime=_bookUpdateTime; @property(copy, nonatomic) NSString *bookUrl; // @synthesize bookUrl=_bookUrl; @property(copy, nonatomic) NSString *bookIcon; // @synthesize bookIcon=_bookIcon; @property(copy, nonatomic) NSString *bookDesc; // @synthesize bookDesc=_bookDesc; @property(copy, nonatomic) NSString *bookCategory; // @synthesize bookCategory=_bookCategory; @property(copy, nonatomic) NSString *bookAuthor; // @synthesize bookAuthor=_bookAuthor; @property(copy, nonatomic) NSString *bookName; // @synthesize bookName=_bookName; @property(copy, nonatomic) NSString *books; // @synthesize books=_books; @end NS_ASSUME_NONNULL_END ================================================ FILE: zhuishushenqi/NewVersion/Search/AikanParserModel.m ================================================ // // AikanParserModel.m // ZSSouShu // // Created by yung on 2019/10/8. // Copyright © 2019 CJ. All rights reserved. // #import "AikanParserModel.h" @implementation AikanParserModel - (instancetype)initWithCoder:(NSCoder *)coder { self = [super init]; if (self) { self.errDate = [coder decodeObjectForKey:@"errDate"]; self.books = [coder decodeObjectForKey:@"books"]; self.searchUrl = [coder decodeObjectForKey:@"searchUrl"]; self.name = [coder decodeObjectForKey:@"name"]; self.type = [coder decodeIntegerForKey:@"type"]; self.enabled = [coder decodeBoolForKey:@"enabled"]; self.checked = [coder decodeBoolForKey:@"checked"]; self.searchEncoding = [coder decodeObjectForKey:@"searchEncoding"]; self.host = [coder decodeObjectForKey:@"host"]; self.contentReplace = [coder decodeObjectForKey:@"contentReplace"]; self.contentRemove = [coder decodeObjectForKey:@"contentRemove"]; self.content = [coder decodeObjectForKey:@"content"]; self.chaptersReverse = [coder decodeBoolForKey:@"chaptersReverse"]; self.chapterUrl = [coder decodeObjectForKey:@"chapterUrl"]; self.chapterName = [coder decodeObjectForKey:@"chapterName"]; self.chapters = [coder decodeObjectForKey:@"chapters"]; self.detailBookIcon = [coder decodeObjectForKey:@"detailBookIcon"]; self.detailBookDesc = [coder decodeObjectForKey:@"detailBookDesc"]; self.detailChaptersUrl = [coder decodeObjectForKey:@"detailChaptersUrl"]; self.bookLastChapterName = [coder decodeObjectForKey:@"bookLastChapterName"]; self.bookUpdateTime = [coder decodeObjectForKey:@"bookUpdateTime"]; self.bookUrl = [coder decodeObjectForKey:@"bookUrl"]; self.bookIcon = [coder decodeObjectForKey:@"bookIcon"]; self.bookDesc = [coder decodeObjectForKey:@"bookDesc"]; self.bookCategory = [coder decodeObjectForKey:@"bookCategory"]; self.bookAuthor = [coder decodeObjectForKey:@"bookAuthor"]; self.bookName = [coder decodeObjectForKey:@"bookName"]; } return self; } - (void)encodeWithCoder:(NSCoder *)coder { [coder encodeObject:self.books forKey:@"books"]; [coder encodeObject:self.errDate forKey:@"errDate"]; [coder encodeObject:self.name forKey:@"name"]; [coder encodeInteger:self.type forKey:@"type"]; [coder encodeBool:self.enabled forKey:@"enabled"]; [coder encodeBool:self.checked forKey:@"checked"]; [coder encodeObject:self.searchUrl forKey:@"searchUrl"]; [coder encodeObject:self.searchEncoding forKey:@"searchEncoding"]; [coder encodeObject:self.host forKey:@"host"]; [coder encodeObject:self.contentReplace forKey:@"contentReplace"]; [coder encodeObject:self.contentRemove forKey:@"contentRemove"]; [coder encodeObject:self.content forKey:@"content"]; [coder encodeBool:self.chaptersReverse forKey:@"chaptersReverse"]; [coder encodeObject:self.chapterUrl forKey:@"chapterUrl"]; [coder encodeObject:self.chapterName forKey:@"chapterName"]; [coder encodeObject:self.chapters forKey:@"chapters"]; [coder encodeObject:self.detailBookIcon forKey:@"detailBookIcon"]; [coder encodeObject:self.detailBookDesc forKey:@"detailBookDesc"]; [coder encodeObject:self.detailChaptersUrl forKey:@"detailChaptersUrl"]; [coder encodeObject:self.bookLastChapterName forKey:@"bookLastChapterName"]; [coder encodeObject:self.bookUpdateTime forKey:@"bookUpdateTime"]; [coder encodeObject:self.bookUrl forKey:@"bookUrl"]; [coder encodeObject:self.bookIcon forKey:@"bookIcon"]; [coder encodeObject:self.bookDesc forKey:@"bookDesc"]; [coder encodeObject:self.bookCategory forKey:@"bookCategory"]; [coder encodeObject:self.bookAuthor forKey:@"bookAuthor"]; [coder encodeObject:self.bookName forKey:@"bookName"]; } - (id)copyWithZone:(NSZone *)zone { AikanParserModel * model = [[AikanParserModel alloc] init]; model.books = self.books; model.errDate = self.errDate; model.name = self.name; model.type = self.type; model.enabled = self.enabled; model.checked = self.checked; model.searchUrl = self.searchUrl; model.searchEncoding = self.searchEncoding; model.host = self.host; model.contentReplace = self.contentReplace; model.contentRemove = self.contentRemove; model.content = self.content; model.chaptersReverse = self.chaptersReverse; model.chapterUrl = self.chapterUrl; model.chapterName = self.chapterName; model.chapters = self.chapters; model.detailBookIcon = self.detailBookIcon; model.detailChaptersUrl = self.detailChaptersUrl; model.bookLastChapterName = self.bookLastChapterName; model.bookUpdateTime = self.bookUpdateTime; model.bookUrl = self.bookUrl; model.bookIcon = self.bookIcon; model.bookDesc = self.bookDesc; model.bookCategory = self.bookCategory; model.bookAuthor = self.bookAuthor; model.bookName = self.bookName; return model; } @end ================================================ FILE: zhuishushenqi/NewVersion/Search/AikanParserModel.swift ================================================ // // AikanParserModel.swift // CJReader // // Created by yung on 2019/10/9. // import UIKit import HandyJSON class ZSAikanParserModel: NSObject, NSCoding, NSCopying, HandyJSON { var errDate:Date? var searchUrl:String = "" var name:String = "" var type:Int64 = 0 var enabled:Bool = false var checked:Bool = false var searchEncoding:String = "" var host:String = "" var contentReplace:String = "" var contentRemove:String = "" var contentTagReplace:String = "" var content:String = "" var chapterUrl:String = "" var chapterName:String = "" var chapters:String = "" var chaptersModel:[ZSBookChapter] = [] var detailBookIcon:String = "" var detailChaptersUrl:String = "" var bookLastChapterName:String = "" var bookUpdateTime:String = "" var bookUrl:String = "" var bookIcon:String = "" var bookDesc:String = "" var bookCategory:String = "" var bookAuthor:String = "" var bookName:String = "" var books:String = "" var chaptersReverse:Bool = false var detailBookDesc:String = "" var bookType:ZSReaderBookStyle = .online var update:Bool = false var latestChapterName:String = "" required override init() { } required init?(coder aDecoder: NSCoder) { self.errDate = aDecoder.decodeObject(forKey: "errDate") as? Date self.searchUrl = aDecoder.decodeObject(forKey: "searchUrl") as! String self.name = aDecoder.decodeObject(forKey: "name") as! String self.type = aDecoder.decodeInt64(forKey: "type") self.enabled = aDecoder.decodeBool(forKey: "enabled") self.checked = aDecoder.decodeBool(forKey: "checked") self.searchEncoding = aDecoder.decodeObject(forKey: "searchEncoding") as! String self.host = aDecoder.decodeObject(forKey: "host") as! String self.contentReplace = aDecoder.decodeObject(forKey: "contentReplace") as! String self.contentRemove = aDecoder.decodeObject(forKey: "contentRemove") as! String self.contentTagReplace = aDecoder.decodeObject(forKey: "contentTagReplace") as? String ?? "" self.content = aDecoder.decodeObject(forKey: "content") as! String self.chapterUrl = aDecoder.decodeObject(forKey: "chapterUrl") as! String self.chapterName = aDecoder.decodeObject(forKey: "chapterName") as! String self.chapters = aDecoder.decodeObject(forKey: "chapters") as! String self.detailBookIcon = aDecoder.decodeObject(forKey: "detailBookIcon") as! String self.detailChaptersUrl = aDecoder.decodeObject(forKey: "detailChaptersUrl") as! String self.bookLastChapterName = aDecoder.decodeObject(forKey: "bookLastChapterName") as! String self.detailBookDesc = aDecoder.decodeObject(forKey: "detailBookDesc") as! String self.bookUpdateTime = aDecoder.decodeObject(forKey: "bookUpdateTime") as! String self.bookUrl = aDecoder.decodeObject(forKey: "bookUrl") as! String self.bookIcon = aDecoder.decodeObject(forKey: "bookIcon") as! String self.bookDesc = aDecoder.decodeObject(forKey: "bookDesc") as! String self.bookCategory = aDecoder.decodeObject(forKey: "bookCategory") as! String self.bookAuthor = aDecoder.decodeObject(forKey: "bookAuthor") as! String self.bookName = aDecoder.decodeObject(forKey: "bookName") as! String self.books = aDecoder.decodeObject(forKey: "books") as! String self.chaptersReverse = aDecoder.decodeBool(forKey: "chaptersReverse") self.chaptersModel = aDecoder.decodeObject(forKey: "chaptersModel") as? [ZSBookChapter] ?? [] self.bookType = ZSReaderBookStyle(rawValue: aDecoder.decodeInteger(forKey: "bookType")) ?? ZSReaderBookStyle.online self.update = aDecoder.decodeBool(forKey: "update") self.latestChapterName = aDecoder.decodeObject(forKey: "latestChapterName") as? String ?? "" } func encode(with aCoder: NSCoder) { aCoder.encode(self.errDate, forKey: "errDate") aCoder.encode(self.searchUrl, forKey: "searchUrl") aCoder.encode(self.name, forKey: "name") aCoder.encode(self.type, forKey: "type") aCoder.encode(self.enabled, forKey: "enabled") aCoder.encode(self.checked, forKey: "checked") aCoder.encode(self.searchEncoding, forKey: "searchEncoding") aCoder.encode(self.host, forKey: "host") aCoder.encode(self.contentReplace, forKey: "contentReplace") aCoder.encode(self.contentRemove, forKey: "contentRemove") aCoder.encode(self.contentTagReplace, forKey: "contentTagReplace") aCoder.encode(self.content, forKey: "content") aCoder.encode(self.chapterUrl, forKey: "chapterUrl") aCoder.encode(self.chapterName, forKey: "chapterName") aCoder.encode(self.chapters, forKey: "chapters") aCoder.encode(self.detailBookIcon, forKey: "detailBookIcon") aCoder.encode(self.detailChaptersUrl, forKey: "detailChaptersUrl") aCoder.encode(self.bookLastChapterName, forKey: "bookLastChapterName") aCoder.encode(self.bookUpdateTime, forKey: "bookUpdateTime") aCoder.encode(self.bookUrl, forKey: "bookUrl") aCoder.encode(self.bookIcon, forKey: "bookIcon") aCoder.encode(self.bookDesc, forKey: "bookDesc") aCoder.encode(self.bookCategory, forKey: "bookCategory") aCoder.encode(self.bookAuthor, forKey: "bookAuthor") aCoder.encode(self.bookName, forKey: "bookName") aCoder.encode(self.books, forKey: "books") aCoder.encode(self.chaptersReverse, forKey: "chaptersReverse") aCoder.encode(self.chaptersModel, forKey: "chaptersModel") aCoder.encode(self.detailBookDesc, forKey: "detailBookDesc") aCoder.encode(self.bookType.rawValue, forKey: "bookType") aCoder.encode(self.update, forKey: "update") aCoder.encode(self.latestChapterName, forKey: "latestChapterName") } func copySelf() ->ZSAikanParserModel { let copyModel = ZSAikanParserModel() copyModel.errDate = self.errDate copyModel.searchUrl = self.searchUrl copyModel.name = self.name copyModel.type = self.type copyModel.enabled = self.enabled copyModel.checked = self.checked copyModel.searchEncoding = self.searchEncoding copyModel.host = self.host copyModel.contentReplace = self.contentReplace copyModel.contentRemove = self.contentRemove copyModel.contentTagReplace = self.contentTagReplace copyModel.content = self.content copyModel.chapterUrl = self.chapterUrl copyModel.chapterName = self.chapterName copyModel.chapters = self.chapters copyModel.detailBookIcon = self.detailBookIcon copyModel.detailChaptersUrl = self.detailChaptersUrl copyModel.bookLastChapterName = self.bookLastChapterName copyModel.bookUpdateTime = self.bookUpdateTime copyModel.bookUrl = self.bookUrl copyModel.bookIcon = self.bookIcon copyModel.bookDesc = self.bookDesc copyModel.bookCategory = self.bookCategory copyModel.bookAuthor = self.bookAuthor copyModel.bookName = self.bookName copyModel.books = self.books copyModel.chaptersReverse = self.chaptersReverse copyModel.chaptersModel = self.chaptersModel copyModel.detailBookDesc = self.detailBookDesc copyModel.bookType = self.bookType copyModel.update = self.update copyModel.latestChapterName = self.latestChapterName return copyModel } func copy(with zone: NSZone? = nil) -> Any { let copyModel = ZSAikanParserModel() copyModel.errDate = self.errDate copyModel.searchUrl = self.searchUrl copyModel.name = self.name copyModel.type = self.type copyModel.enabled = self.enabled copyModel.checked = self.checked copyModel.searchEncoding = self.searchEncoding copyModel.host = self.host copyModel.contentReplace = self.contentReplace copyModel.contentRemove = self.contentRemove copyModel.contentTagReplace = self.contentTagReplace copyModel.content = self.content copyModel.chapterUrl = self.chapterUrl copyModel.chapterName = self.chapterName copyModel.chapters = self.chapters copyModel.detailBookIcon = self.detailBookIcon copyModel.detailChaptersUrl = self.detailChaptersUrl copyModel.bookLastChapterName = self.bookLastChapterName copyModel.bookUpdateTime = self.bookUpdateTime copyModel.bookUrl = self.bookUrl copyModel.bookIcon = self.bookIcon copyModel.bookDesc = self.bookDesc copyModel.bookCategory = self.bookCategory copyModel.bookAuthor = self.bookAuthor copyModel.bookName = self.bookName copyModel.books = self.books copyModel.chaptersReverse = self.chaptersReverse copyModel.chaptersModel = self.chaptersModel copyModel.detailBookDesc = self.detailBookDesc copyModel.bookType = self.bookType copyModel.update = self.update copyModel.latestChapterName = self.latestChapterName return copyModel } func transformShelf() ->ZSShelfModel { let shelfModel = ZSShelfModel() shelfModel.icon = self.bookIcon.length > 0 ? self.bookIcon:self.detailBookIcon shelfModel.bookName = self.bookName shelfModel.author = self.bookAuthor shelfModel.bookUrl = self.bookUrl shelfModel.bookType = self.bookType shelfModel.update = self.update shelfModel.latestChapterName = self.latestChapterName return shelfModel } func updateShelf(shelf:ZSShelfModel) ->ZSShelfModel { // shelf.icon = self.bookIcon.length > 0 ? self.bookIcon:self.detailBookIcon // shelf.bookName = self.bookName // shelf.author = self.bookAuthor // shelf.bookUrl = self.bookUrl // shelf.bookType = self.bookType shelf.update = self.update shelf.latestChapterName = self.latestChapterName return shelf } /// 是否是可用的model func available() ->Bool { if bookAuthor.length == 0 && bookName.length == 0 || bookUrl.length == 0 { return false } return true } } ================================================ FILE: zhuishushenqi/NewVersion/Search/ZSAikanHtmlParser.swift ================================================ // // ZSAikanHtmlParser.swift // zhuishushenqi // // Created by caony on 2020/1/8. // Copyright © 2020 QS. All rights reserved. // import UIKit class ZSAikanHtmlParser { static func string(node:OCGumboNode, aikanString:String, text:Bool) ->String { let regs = aikanString.components(separatedBy: "@") if regs.count != 4 { return "" } let reg1 = regs[1] if reg1.length > 0 { let obj = self.elementArray(node: node, regexString: reg1) if obj.count > 0 { let reg2 = regs[2] let reg2Value = Int(reg2) ?? 0 var regNode:OCGumboNode? if obj.count > reg2Value { regNode = obj[reg2Value] as? OCGumboNode let reg3 = regs[3] if reg3 == "own:" { let text = regNode?.query(""); let resultNode = text?.lastObject as? OCGumboNode return resultNode?.text() ?? ""; } else if reg3.length >= 5 { let sub3 = reg3.nsString.substring(to: 4) if sub3 == "abs:" { let reg3Last = reg3.nsString.substring(from: 4) let attr = regNode?.attr(reg3Last) ?? ""; return attr } } else if reg3.length > 0 { let sub3 = reg3.nsString.substring(to: 1) if sub3 == ":" { let text = regNode?.text() ?? "" let reg3Last = reg3.nsString.substring(from: 1) let matchString = self.matchString(string: text, regString: reg3Last) return matchString; } else { let attr = regNode?.attr(sub3) ?? "" return attr } } if text { let text = regNode?.text() ?? "" return text } else { let html = regNode?.html() ?? "" return html } } } } else { let reg3 = regs[3] if reg3 == "own:" { let text = node.query("") let resultNode = text?.lastObject as? OCGumboNode return resultNode?.text() ?? "" } else if reg3.length >= 5 { let sub3 = reg3.nsString.substring(to: 4) if sub3 == "abs:" { let reg3Last = reg3.nsString.substring(from: 4) let attr = node.attr(reg3Last) ?? "" return attr } } else if reg3.length > 0 { let sub3 = reg3.nsString.substring(to: 1) if sub3 == ":" { let text = node.text() ?? "" let reg3Last = reg3.nsString.substring(from: 1) let matchString = self.matchString(string: text, regString: reg3Last) return matchString; } else { let attr = node.attr(sub3) ?? "" return attr } } if text { let text = node.text() ?? "" return text } else { let html = node.html() ?? "" return html } } return "" } static func matchString(string:String, regString:String) ->String { if string.length > 0 { let reg = try! NSRegularExpression(pattern: regString, options: NSRegularExpression.Options.caseInsensitive) let results = reg.matches(in: string, options: NSRegularExpression.MatchingOptions.reportProgress, range: NSMakeRange(0, string.length)) var resultString = "" var range1String = "" if results.count > 0 { for index in 0..= 2 { let range1 = result.range(at: 1) range1String = string.nsString.substring(with: range1) } } } if results.count > 0 { resultString.append(string.nsString.substring(from: 0)) } return range1String } return "" } static func elementArray(node:OCGumboNode, regexString:String) ->OCQueryObject { let regs = regexString.components(separatedBy: " ") var next:OCQueryObject = [] if regs.count > 0 { for index in 0.. 0 { next = next.find(reg) ?? []; } else { next = node.query(reg) ?? []; } } } return next } } ================================================ FILE: zhuishushenqi/NewVersion/Search/ZSBookInfoHeaderView.swift ================================================ // // ZSBookInfoHeaderView.swift // zhuishushenqi // // Created by yung on 2019/10/28. // Copyright © 2019 QS. All rights reserved. // import UIKit typealias ZSBookInfoHeaderHandler = ()->Void class ZSBookInfoHeaderView: UITableViewHeaderFooterView { var lastTapHandler:ZSBookInfoHeaderHandler? lazy var authorLabel:UILabel = { let lb = UILabel(frame: .zero) lb.textColor = UIColor.red lb.numberOfLines = 0 lb.font = UIFont.systemFont(ofSize: 15) return lb }() lazy var sourceLabel:UILabel = { let lb = UILabel(frame: .zero) lb.textColor = UIColor.red lb.numberOfLines = 1 lb.font = UIFont.systemFont(ofSize: 15) return lb }() lazy var contentLabel:UILabel = { let lb = UILabel(frame: .zero) lb.textColor = UIColor.gray lb.numberOfLines = 0 lb.font = UIFont.systemFont(ofSize: 13) return lb }() lazy var iconView:UIImageView = { let lb = UIImageView(frame: .zero) return lb }() lazy var lastUpdateTimeBT:UIButton = { let lb = UIButton(type: .custom) lb.setTitleColor(UIColor.red, for: .normal) lb.titleLabel?.font = UIFont.systemFont(ofSize: 15) lb.addTarget(self, action: #selector(lastButtonAction(bt:)), for: .touchUpInside) return lb }() override init(reuseIdentifier: String?) { super.init(reuseIdentifier: reuseIdentifier) contentView.addSubview(authorLabel) contentView.addSubview(sourceLabel) contentView.addSubview(contentLabel) contentView.addSubview(iconView) contentView.addSubview(lastUpdateTimeBT) contentView.backgroundColor = UIColor.white } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func layoutSubviews() { super.layoutSubviews() self.iconView.frame = CGRect(x: 15, y: 15, width: 70, height: 100) self.authorLabel.frame = CGRect(x: self.iconView.frame.maxX + 15, y: 15, width: self.contentView.bounds.width - self.iconView.frame.maxX - 30, height: 40) self.sourceLabel.frame = CGRect(x: self.iconView.frame.maxX + 15, y: self.authorLabel.frame.maxY, width: self.contentView.bounds.width - self.iconView.frame.maxX - 30, height: 20) if lastUpdateTimeBT.titleLabel?.text?.length ?? 0 > 0 { self.lastUpdateTimeBT.frame = CGRect(x: 15, y: self.iconView.frame.maxY + 10, width: self.contentView.bounds.width - 30, height: 40) } else { self.lastUpdateTimeBT.frame = CGRect(x: 15, y: self.iconView.frame.maxY + 10, width: self.contentView.bounds.width - 30, height: 0) } let contentSize = self.contentLabel.sizeThatFits(CGSize(width: self.contentView.bounds.width - 30, height: CGFloat.greatestFiniteMagnitude)) self.contentLabel.frame = CGRect(x: 15, y: self.lastUpdateTimeBT.frame.maxY + 10, width: self.contentView.bounds.width - 30, height:contentSize.height) } static func height(for model:ZSAikanParserModel) ->CGFloat { let headerView = ZSBookInfoHeaderView(reuseIdentifier: "\(ZSBookInfoHeaderView.self)") headerView.configure(model: model) let contentSize = headerView.contentLabel.sizeThatFits(CGSize(width: ScreenWidth - 30, height: CGFloat.greatestFiniteMagnitude)) if headerView.lastUpdateTimeBT.titleLabel?.text?.length ?? 0 > 0 { return 190 + contentSize.height } else { return 150 + contentSize.height } } @objc private func lastButtonAction(bt:UIButton) { if lastTapHandler != nil { lastTapHandler?() } } func configure(model:ZSAikanParserModel) { let icon = model.bookIcon.length > 0 ? model.bookIcon:model.detailBookIcon let resource = QSResource(url: URL(string: icon) ?? URL(string: "https://www.baidu.com")!) self.iconView.kf.setImage(with: resource,placeholder: UIImage(named: "default_book_cover")) self.authorLabel.text = model.bookAuthor self.sourceLabel.text = "来源:\(model.name) \(model.host)" self.contentLabel.text = model.detailBookDesc.length != 0 ? model.detailBookDesc:model.bookDesc self.lastUpdateTimeBT.setTitle("\(model.bookLastChapterName) \(model.bookUpdateTime)" , for: .normal) } } ================================================ FILE: zhuishushenqi/NewVersion/Search/ZSHeaderSearch.swift ================================================ // // ZSHeaderSearch.swift // zhuishushenqi // // Created by caony on 2019/10/23. // Copyright © 2019 QS. All rights reserved. // import UIKit struct ZSHeaderSearch { var type:ZSHeaderSearchType = .hot var items:[Any] = [] var height:CGFloat = 0 var headerTitle:String = "" var headerDetail:String = "" var headerIcon:String = "" } ================================================ FILE: zhuishushenqi/NewVersion/Search/ZSHeaderSearchCell.swift ================================================ // // ZSHeaderSearchCell.swift // zhuishushenqi // // Created by caony on 2019/10/22. // Copyright © 2019 QS. All rights reserved. // import UIKit enum ZSHeaderSearchType { case hot case recommend case history } class ZSHeaderSearchCell: UITableViewCell { var clickHandler:ZSSearchClickHandler? private var type:ZSHeaderSearchType = .hot { didSet { reloadData() } } private var model:ZSHeaderSearch? private var hotView:ZSSearchHotView? private var recView:ZSSearchRecommendView? override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func awakeFromNib() { super.awakeFromNib() // Initialization code } override func setSelected(_ selected: Bool, animated: Bool) { super.setSelected(selected, animated: animated) // Configure the view for the selected state } override func layoutSubviews() { super.layoutSubviews() hotView?.frame = CGRect(x: 0, y: 0, width: self.contentView.bounds.width, height: self.contentView.bounds.height) recView?.frame = CGRect(x: 0, y: 0, width: self.contentView.bounds.width, height: self.contentView.bounds.height) } func configure(model:ZSHeaderSearch) { self.model = model self.type = model.type } private func reloadData() { removeOtherSubview() switch self.type { case .hot: showHotView() case .recommend: showRecommendView() default: break } } private func showHotView() { let hotView = ZSSearchHotView(frame: CGRect(x: 0, y: 0, width: self.contentView.bounds.width, height: (model?.height ?? 0))) hotView.cellsFrame = model?.items as! [ZSSearchHotwords] hotView.clickHandler = { [unowned self] model in self.clickHandler?(model) } self.hotView = hotView self.contentView.addSubview(hotView) } private func showRecommendView() { let hotView = ZSSearchRecommendView(frame: CGRect(x: 0, y: 0, width: self.contentView.bounds.width, height: (model?.height ?? 0))) hotView.cellsFrame = model?.items as! [ZSHotWord] hotView.clickHandler = { [unowned self] word in self.clickHandler?(word) } self.recView = hotView self.contentView.addSubview(hotView) } private func removeOtherSubview() { for subview in self.contentView.subviews { if !subview.isKind(of: ZSHeaderSearchTopView.self) { subview.removeFromSuperview() } } } } class ZSHeaderSearchTopView:UITableViewHeaderFooterView { lazy var titleLabel:UILabel = { let lb = UILabel(frame: .zero) lb.textColor = UIColor.black lb.font = UIFont.systemFont(ofSize: 17) return lb }() lazy var detailButton:UIButton = { let bt = UIButton(type: .custom) bt.setTitleColor(UIColor.gray, for: .normal) bt.titleLabel?.font = UIFont.systemFont(ofSize: 13) return bt }() override init(reuseIdentifier: String?) { super.init(reuseIdentifier: reuseIdentifier) contentView.addSubview(self.titleLabel) contentView.addSubview(self.detailButton) backgroundColor = UIColor.white contentView.backgroundColor = UIColor.white } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func layoutSubviews() { super.layoutSubviews() self.titleLabel.frame = CGRect(x: 20, y: 20, width: 200, height: 21) let width = self.detailButton.sizeThatFits(CGSize(width: CGFloat.greatestFiniteMagnitude, height: 55)).width self.detailButton.frame = CGRect(x: self.bounds.width - width - 20, y: 0, width: width, height: 55) } } ================================================ FILE: zhuishushenqi/NewVersion/Search/ZSHotWord.swift ================================================ // // ZSHotWord.swift // zhuishushenqi // // Created by caony on 2019/10/22. // Copyright © 2019 QS. All rights reserved. // import UIKit import HandyJSON struct ZSHotWord:HandyJSON,Equatable { var word:String = "" var book:String = "" var frame:CGRect = CGRect.zero init() { } } ================================================ FILE: zhuishushenqi/NewVersion/Search/ZSSearchBookView.swift ================================================ // // ZSSearchBookView.swift // zhuishushenqi // // Created by caony on 2019/10/22. // Copyright © 2019 QS. All rights reserved. // import UIKit import MJRefresh class ZSSearchBookView: UIView { var viewModel:ZSSearchBookViewModel? { didSet { reloadData() } } var clickHandler:ZSSearchClickHandler? lazy var tableView:UITableView = { let tableView = UITableView(frame: .zero, style: .grouped) tableView.dataSource = self tableView.delegate = self tableView.sectionHeaderHeight = 0.01 tableView.sectionFooterHeight = 0.01 if #available(iOS 11, *) { tableView.contentInsetAdjustmentBehavior = .never } tableView.qs_registerCellClass(ZSHeaderSearchCell.self) tableView.qs_registerCellClass(UITableViewCell.self) tableView.qs_registerHeaderFooterClass(ZSHeaderSearchTopView.self) let blurEffect = UIBlurEffect(style: .extraLight) let blurEffectView = UIVisualEffectView(effect: blurEffect) tableView.backgroundView = blurEffectView return tableView }() override init(frame: CGRect) { super.init(frame: frame) addSubview(tableView) let mj_header = ZSRefreshTextHeader(refreshingTarget: self, refreshingAction: #selector(refreshAction)) mj_header?.endRefreshingCompletionBlock = { [weak mj_header] in mj_header?.changeText() } tableView.mj_header = mj_header mj_header?.beginRefreshing() } @objc private func refreshAction() { viewModel?.request() } @objc private func reloadData() { observe() refreshAction() self.tableView.reloadData() } private func observe() { self.viewModel?.reloadBlock = { [weak self] in DispatchQueue.main.async { self?.tableView.mj_header.endRefreshing() self?.tableView.reloadData() } } } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func layoutSubviews() { super.layoutSubviews() tableView.frame = self.bounds } } extension ZSSearchBookView:UITableViewDataSource, UITableViewDelegate { func numberOfSections(in tableView: UITableView) -> Int { return viewModel?.numberOfSections() ?? 2 } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { if let model = viewModel?.model(for: section) { if model.type == .history { return model.items.count } } return 1 } func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { return viewModel?.height(for: indexPath.section) ?? 0 } func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { return 55 } func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { return 0.01 } func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { let headerView = tableView.qs_dequeueReusableHeaderFooterView(ZSHeaderSearchTopView.self) if let model = viewModel?.model(for: section) { headerView?.titleLabel.text = model.headerTitle headerView?.detailButton.setTitle(model.headerDetail, for: .normal) } return headerView } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.qs_dequeueReusableCell(UITableViewCell.self) if let model = viewModel?.model(for: indexPath.section) { if model.type == .hot || model.type == .recommend { let cell = tableView.qs_dequeueReusableCell(ZSHeaderSearchCell.self) cell?.selectionStyle = .none cell?.configure(model: model) cell?.clickHandler = { [weak self] word in self?.viewModel?.wordClick(word: word) self?.tableView.reloadData() self?.clickHandler?(word) } return cell! } else { if indexPath.row < model.items.count { if let history = model.items[indexPath.row] as? ZSSearchHistory { cell?.textLabel?.text = history.word cell?.textLabel?.textColor = UIColor.gray } } } } return cell! } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { if let model = viewModel?.model(for: indexPath.section) { if model.type == .history { if let history = model.items[indexPath.row] as? ZSSearchHistory { viewModel?.wordClick(word: history.word) tableView.reloadData() clickHandler?(history.word) } } } } } ================================================ FILE: zhuishushenqi/NewVersion/Search/ZSSearchBookViewController.swift ================================================ // // ZSSearchBookViewController.swift // zhuishushenqi // // Created by caony on 2019/10/22. // Copyright © 2019 QS. All rights reserved. // import UIKit class ZSSearchBookViewController: BaseViewController,ZSTopSearchBarProtocol { var viewModel = ZSSearchBookViewModel() lazy var topBar:ZSTopSearchBar = { let bar = ZSTopSearchBar(frame: .zero) bar.delegate = self return bar }() lazy var bookView:ZSSearchBookView = { let view = ZSSearchBookView(frame: .zero) view.viewModel = viewModel view.clickHandler = { [weak self] word in self?.searchHotClick(word: word) } return view }() lazy var resultView:ZSSearchResultView = { let view = ZSSearchResultView(frame: .zero) view.resultHandler = { [weak self] model in self?.resultClick(model: model) } return view }() override func viewDidLoad() { super.viewDidLoad() setupSubviews() } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) navigationController?.setNavigationBarHidden(true, animated: false) } func setupSubviews() { view.addSubview(topBar) view.addSubview(bookView) view.addSubview(resultView) topBar.snp.remakeConstraints { (make) in make.left.right.top.equalToSuperview() make.height.equalTo(kNavgationBarHeight + 22) } bookView.snp.remakeConstraints { (make) in make.left.right.equalToSuperview() make.top.equalTo(topBar.snp.bottom) make.height.equalTo(ScreenHeight - kNavgationBarHeight - 22) } resultView.snp.remakeConstraints { (make) in make.left.right.equalToSuperview() make.top.equalTo(topBar.snp.bottom) make.height.equalTo(ScreenHeight - kNavgationBarHeight - 22) } resultView.isHidden = true } func searchHotClick(word:String) { topBar.textfield.text = word request(text: word) } func resultClick(model:ZSAikanParserModel) { let infoVC = ZSSearchInfoViewController() infoVC.model = model navigationController?.pushViewController(infoVC, animated: true) } func request(text:String) { viewModel.wordClick(word: text) bookView.tableView.reloadData() viewModel.startRequestBooks() viewModel.request(text: text) { [weak self] (book) in self?.resultView.isHidden = false self?.resultView.addBook(book: book) } } //MARK: - ZSTopSearchBarProtocol func zsTopSearchBar(topSearchBar: ZSTopSearchBar, searchTextFieldShouldBeginEditing text: String) { } func zsTopSearchBar(topSearchBar: ZSTopSearchBar, searchTextFieldEditChanged text: String) { if text.length == 0 { resultView.isHidden = true viewModel.stopRequestBooks() resultView.clearBooks() view.endEditing(true) } } func zsTopSearchBar(topSearchBar: ZSTopSearchBar, searchTextFieldReturn text: String) { if text.length > 0 { view.endEditing(true) request(text: text) } } func zsTopSearchBarCancelButtonClick(topSearchBar: ZSTopSearchBar) { navigationController?.popViewController(animated: false) } } ================================================ FILE: zhuishushenqi/NewVersion/Search/ZSSearchBookViewModel.swift ================================================ // // ZSSearchBookViewModel.swift // zhuishushenqi // // Created by caony on 2019/10/22. // Copyright © 2019 QS. All rights reserved. // import UIKit import ZSAPI import Alamofire let NET = ZSReaderDownloader.share class ZSSearchBookViewModel { var viewDidLoad: ()->() = {} var reloadBlock: ()->() = {} var searchHotwords:[ZSSearchHotwords] = [] var hotword:[ZSHotWord] = [] var searchHotHeader:ZSHeaderSearch = ZSHeaderSearch() var searchRecHeader:ZSHeaderSearch = ZSHeaderSearch() var historyHeader:ZSHeaderSearch = ZSHeaderSearch() var source:[ZSAikanParserModel] = [] private var stopBooks:Bool = false private let detailBookDesc = "detailBookDesc" private let detailBookIcon = "detailBookIcon" private let bookLastChapterName = "bookLastChapterName" private let bookUpdateTime = "bookUpdateTime" init() { viewDidLoad = { [weak self] in self?.request() } self.source = ZSSourceManager.share.sources.filter({ (model) -> Bool in return model.checked }) historyHeader.type = .history historyHeader.items = ZSHistoryManager.share.historyList historyHeader.headerTitle = "搜索历史" historyHeader.headerDetail = "删除历史" historyHeader.height = 44 historyHeader.headerIcon = "" } func stopRequestBooks() { stopBooks = true } func startRequestBooks() { stopBooks = false } private func calHotSearch(hot:[ZSSearchHotwords]) { var hotSearchs = hot let words = hotSearchs.filter { (word) -> Bool in if let index = hotSearchs.firstIndex(of: word) { return index < 8 } return true } hotSearchs = words let marginX:CGFloat = 20 let marginY:CGFloat = 10 let cellHeight:CGFloat = 28 let spaceX:CGFloat = 15 let spaceY:CGFloat = 20 var cellX = marginX var cellY = marginY var index = 0 for hotword in words { var cell = hotword let cellWidth = hotword.word.qs_width(13, height: cellHeight/2) + 20 if (cellX + cellWidth + marginX + spaceX) > UIScreen.main.bounds.width { cellY += (cellHeight + spaceY) cellX = marginX } else if index != 0 { cellX += spaceX } cell.frame = CGRect(x: cellX, y: cellY, width: cellWidth, height: cellHeight) cellX += cellWidth hotSearchs[index] = cell index += 1 } let hotHeight = cellY + cellHeight + 10 searchHotHeader.type = .hot searchHotHeader.items = hotSearchs searchHotHeader.headerTitle = "搜索热词" searchHotHeader.headerDetail = "查看更多" searchHotHeader.height = hotHeight searchHotHeader.headerIcon = "" self.searchHotwords = hotSearchs } private func calRecSearch(rec:[ZSHotWord]) { var recs:[ZSHotWord] = rec let words = recs.filter { (word) -> Bool in if let index = recs.firstIndex(of: word) { return index < 8 } return true } recs = words let marginX:CGFloat = 20 let marginY:CGFloat = 10 let cellHeight:CGFloat = 28 let spaceX:CGFloat = 0 let spaceY:CGFloat = 10 var cellX = marginX var cellY = marginY var index = 0 for hot in words { var cell = hot let cellWidth = (UIScreen.main.bounds.width - 40)/2 cellY = CGFloat(index/2) * (cellHeight + spaceY) + marginY cell.frame = CGRect(x: cellX, y: cellY, width: cellWidth, height: cellHeight) cellX += spaceX cellX += cellWidth if cellX + cellWidth + spaceX + marginX > UIScreen.main.bounds.width { cellX = marginX } recs[index] = cell index += 1 } let recHeight = cellY + cellHeight + 10 searchRecHeader.type = .recommend searchRecHeader.items = recs searchRecHeader.headerTitle = "热门推荐" searchRecHeader.headerDetail = "换一批" searchRecHeader.height = recHeight searchRecHeader.headerIcon = "" } func numberOfSections() ->Int { if historyHeader.items.count > 0 { return 3 } return 2 } func model(for row:Int) ->ZSHeaderSearch { if row == 0 { return searchHotHeader } else if row == 1 { return searchRecHeader } historyHeader.items = ZSHistoryManager.share.historyList return historyHeader } func height(for row:Int) ->CGFloat { let header = model(for: row) return header.height } func wordClick(word:String) { ZSHistoryManager.share.add(word: word) } func delete(word:String) { ZSHistoryManager.share.remove(word: word) } func request() { requestSearchHotwords { [weak self] in self?.reloadBlock() } requestHotWord { [weak self] in self?.reloadBlock() } } func requestString(url:String, encoding:String.Encoding, handler:@escaping ZSBaseCallback) { NET.requestData(url: url) { (data) in guard let responseData = data else { return } let htmlString = String(data: responseData, encoding: encoding) handler(htmlString) } } func request(text:String,completion:@escaping(_ book:ZSAikanParserModel)->Void) { for (_, src) in source.enumerated() { var searchUrl = src.searchUrl.replacingOccurrences(of: "%@", with: text) let character = CharacterSet.urlQueryAllowed searchUrl = searchUrl.addingPercentEncoding(withAllowedCharacters: character) ?? "" let encoding = String.Encoding.zs_encoding(str: src.searchEncoding ) requestString(url: searchUrl, encoding: encoding) { [weak self, unowned src](string) in guard let sSelf = self else { return } guard let htmlString = string else { return } sSelf.getBook(src: src, htmlString: htmlString) { (book) in completion(book) } } } } private func getBook(src:ZSAikanParserModel, htmlString:String, completion:@escaping(_ book:ZSAikanParserModel)->Void) { guard let document = OCGumboDocument(htmlString: htmlString) else { return } let reg = src.books let parse = AikanHtmlParser() let obj = parse.elementArray(with: document, withRegexString: reg) for node in obj { let book = src.copySelf() book.name = src.name book.bookAuthor = parse.string(withGumboNode: node, withAikanString: src.bookAuthor, withText: true).trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) book.bookIcon = parse.string(withGumboNode: node, withAikanString: src.bookIcon, withText: false).httpScheme() book.bookName = parse.string(withGumboNode: node, withAikanString: src.bookName, withText: true).trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) book.bookDesc = parse.string(withGumboNode: node, withAikanString: src.bookDesc, withText: true).trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) book.bookUrl = parse.string(withGumboNode: node, withAikanString: src.bookUrl, withText: false).schemeURLString(src.host) book.bookUpdateTime = parse.string(withGumboNode: node, withAikanString: src.bookUpdateTime, withText: true) book.bookLastChapterName = parse.string(withGumboNode: node, withAikanString: src.bookLastChapterName, withText: true) if !book.available() { continue } getBookInfo(src: src, bookUrl: book.bookUrl) { [weak self] (chapters, bookDetailInfo) in guard let sself = self else { return } book.chaptersModel = chapters book.detailBookDesc = bookDetailInfo[string:sself.detailBookDesc] book.detailBookIcon = bookDetailInfo[string:sself.detailBookIcon] book.bookLastChapterName = book.bookLastChapterName.length > 0 ? book.bookLastChapterName: bookDetailInfo[string:sself.bookLastChapterName] book.bookUpdateTime = book.bookUpdateTime.length > 0 ? book.bookUpdateTime:bookDetailInfo[string:sself.bookUpdateTime] DispatchQueue.main.async { completion(book) } } } } func getBookInfo(src:ZSAikanParserModel, bookUrl:String, completion:@escaping(_ chapters:[ZSBookChapter], _ bookDetailInfo:[String:String])->Void) { NET.requestData(url: bookUrl) { [weak self](data) in guard let sself = self else { completion([],[:]); return } guard let responseData = data else { completion([],[:]); return } let htmlString = String(data: responseData, encoding: String.Encoding.zs_encoding(str: src.searchEncoding)) ?? "" let noTagString = sself.contentTagRemove(string: htmlString, reg: src.contentTagReplace, encoding: src.searchEncoding) guard let document = OCGumboDocument(htmlString: noTagString) else { completion([],[:]); return } let parse = AikanHtmlParser() var bookDetailInfo:[String:String] = [:] bookDetailInfo[sself.detailBookDesc] = parse.string(withGumboNode: document, withAikanString: src.detailBookDesc, withText: false) bookDetailInfo[sself.detailBookIcon] = parse.string(withGumboNode: document, withAikanString: src.detailBookIcon, withText: false) bookDetailInfo[sself.bookLastChapterName] = parse.string(withGumboNode: document, withAikanString: src.bookLastChapterName, withText: true) bookDetailInfo[sself.bookUpdateTime] = parse.string(withGumboNode: document, withAikanString: src.bookUpdateTime, withText: true) // 如果detailChaptersUrl不存在,则直接取chapters字段,无需重新请求 var reg = src.detailChaptersUrl if reg.length > 0 { var chapterDir = parse.string(withGumboNode: document, withAikanString: reg, withText: false) chapterDir = chapterDir.schemeURLString(src.host) sself.getDetailChapters(chaptersUrl: chapterDir, bookUrl: bookUrl, src: src) { (chapters) in guard let chapterModels = chapters else { completion([],[:]); return } completion(chapterModels, bookDetailInfo) } } else { reg = src.chapters let obj = parse.elementArray(with: document, withRegexString: reg) var chaptersArr:[ZSBookChapter] = [] for node in obj { autoreleasepool { let index = obj.index(of: node) let chapter = sself.getChapter(node: node, index: index, src: src) chapter.bookUrl = bookUrl chaptersArr.append(chapter) } } if !sself.stopBooks { completion(chaptersArr,bookDetailInfo) } } } } func getChapter(node:Any, index:Int, src:ZSAikanParserModel)->ZSBookChapter { let parse = AikanHtmlParser() var chapterUrl = parse.string(withGumboNode: node, withAikanString: src.chapterUrl, withText: false) chapterUrl = chapterUrl.schemeURLString(src.host) let chapterName = parse.string(withGumboNode: node, withAikanString: src.chapterName, withText: true) let info:ZSBookChapter = ZSBookChapter() info.chapterUrl = chapterUrl info.chapterName = chapterName info.chapterIndex = index return info } func getDetailChapters(chaptersUrl:String, bookUrl:String, src:ZSAikanParserModel, handler:@escaping ZSBaseCallback<[ZSBookChapter]>) { NET.requestData(url: chaptersUrl) { [weak self](data) in guard let strongSelf = self else { return } guard let responseData = data else { return } let htmlString = String(data: responseData, encoding: String.Encoding.zs_encoding(str: src.searchEncoding)) ?? "" guard let document = OCGumboDocument(htmlString: htmlString) else { return } let parse = AikanHtmlParser() let chalters = parse.elementArray(with: document, withRegexString: src.chapters) var chaptersArr:[ZSBookChapter] = [] for node in chalters { autoreleasepool { let index = chalters.index(of: node) var chapterUrl = parse.string(withGumboNode: node, withAikanString: src.chapterUrl, withText: false) let chapterTitle = parse.string(withGumboNode: node, withAikanString: src.chapterName, withText: true) chapterUrl = chapterUrl.schemeURLString(src.host) let info:ZSBookChapter = ZSBookChapter() info.chapterUrl = chapterUrl info.chapterName = chapterTitle info.chapterIndex = index info.bookUrl = bookUrl chaptersArr.append(info) } } if !strongSelf.stopBooks { handler(chaptersArr) } } } private func contentTagRemove(string:String, reg:String, encoding:String) ->String { var resultString = string guard let data = reg.data(using: .utf8) else { return resultString } guard let json = try? JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.mutableContainers) as? [[String:Any]] else { return resultString } for regInfo in json { let noRegular = regInfo[bool:"noRegular"] let regString = regInfo[string:"reg"] if noRegular { resultString = resultString.replacingOccurrences(of: regString, with: "") } else { let reg = try? NSRegularExpression(pattern: regString, options: NSRegularExpression.Options.allowCommentsAndWhitespace) let subString = resultString.nsString.substring(with: NSMakeRange(0, resultString.nsString.length)) print(subString) if let results = reg?.matches(in: resultString, options: NSRegularExpression.MatchingOptions.reportCompletion, range: NSMakeRange(0, resultString.nsString.length)) { for result in results { if let subString = resultString.substingInRange(result.range.location..<(result.range.location + result.range.length)) { resultString = resultString.replacingOccurrences(of: subString, with: "") } } } } } return resultString } private func requestSearchHotwords(completion:@escaping()->Void) { let api = ZSAPI.searchHotwords("" as AnyObject) zs_get(api.path) { [weak self] (json) in guard let searchHotwordsDict = json?["searchHotWords"] as? [[String:Any]] else { completion() return } guard let searchModels = [ZSSearchHotwords].deserialize(from: searchHotwordsDict) as? [ZSSearchHotwords] else { completion() return } self?.searchHotwords = searchModels self?.calHotSearch(hot: searchModels) completion() } } private func requestHotWord(completion:@escaping()->Void) { let api = ZSAPI.hotwords("" as AnyObject) zs_get(api.path) { [weak self] (json) in guard let searchHotwordsDict = json?["newHotWords"] as? [[String:Any]] else { completion() return } guard let searchModels = [ZSHotWord].deserialize(from: searchHotwordsDict) as? [ZSHotWord] else { completion() return } self?.hotword = searchModels self?.calRecSearch(rec: searchModels) completion() } } } ================================================ FILE: zhuishushenqi/NewVersion/Search/ZSSearchHistory.swift ================================================ // // ZSSearchHistory.swift // zhuishushenqi // // Created by caony on 2020/1/3. // Copyright © 2020 QS. All rights reserved. // import UIKit class ZSSearchHistory:NSObject , NSCoding{ var word:String = "" var timeInterval:TimeInterval = 0 func encode(with coder: NSCoder) { coder.encode(word, forKey: "word") coder.encode(timeInterval, forKey: "timeInterval") } override init() { } required init?(coder: NSCoder) { word = coder.decodeObject(forKey: "word") as? String ?? "" timeInterval = coder.decodeDouble(forKey: "timeInterval") } static func == (lhs: ZSSearchHistory, rhs: ZSSearchHistory) -> Bool { return lhs.word == rhs.word && lhs.timeInterval == rhs.timeInterval } } class ZSHistoryManager { static let share = ZSHistoryManager() var historyList:[ZSSearchHistory] = [] private let saveFileName = "history" private init() { unpack() } func add(word:String) { if word.length == 0 { return } let date = Date().timeIntervalSince1970 let history = ZSSearchHistory() history.word = word history.timeInterval = date if historyList.contains(where: { (model) -> Bool in return model.word == history.word }) { remove(word: word) } historyList.insert(history, at: 0) DispatchQueue.global().async { self.pack() } } func remove(word:String) { let date = Date().timeIntervalSince1970 let history = ZSSearchHistory() history.word = word history.timeInterval = date let i = index(of: history) historyList.remove(at: i) DispatchQueue.global().async { self.pack() } } private func index(of: ZSSearchHistory)->Int { var index = 0 for item in historyList { if item.word == of.word { break } index += 1 } return index } private func unpack() { let savePath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first!.appending("/history/") let saveFilePath = savePath.appending(saveFileName.md5()) if let historyList = NSKeyedUnarchiver.unarchiveObject(withFile: saveFilePath) as? [ZSSearchHistory] { self.historyList = historyList } } private func pack() { let savePath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first!.appending("/history/") let saveFilePath = savePath.appending(saveFileName.md5()) if !FileManager.default.fileExists(atPath: savePath, isDirectory: nil) { try? FileManager.default.createDirectory(atPath: savePath, withIntermediateDirectories: true, attributes: nil) } NSKeyedArchiver.archiveRootObject(historyList, toFile: saveFilePath) } } ================================================ FILE: zhuishushenqi/NewVersion/Search/ZSSearchHotView.swift ================================================ // // ZSSearchHotView.swift // zhuishushenqi // // Created by caony on 2019/10/22. // Copyright © 2019 QS. All rights reserved. // import UIKit typealias ZSSearchClickHandler = (_ hotword:String)->Void class ZSSearchHotView: UIView { var cellsFrame:[ZSSearchHotwords] = [] { didSet { reloadData() } } var clickHandler:ZSSearchClickHandler? override init(frame: CGRect) { super.init(frame: frame) } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func layoutSubviews() { super.layoutSubviews() } func reloadData() { removeAllSubviews() for cellModel in cellsFrame { let cell = ZSSearchHotCell(title: cellModel.word, maxSize: cellModel.frame.size) let tap = UITapGestureRecognizer(target: self, action: #selector(tapAction(tap:))) cell.addGestureRecognizer(tap) cell.frame = cellModel.frame addSubview(cell) } } @objc private func tapAction(tap:UITapGestureRecognizer) { for cellModel in cellsFrame { if cellModel.frame.contains(tap.location(in: self)) { clickHandler?(cellModel.word) break } } } } class ZSSearchHotCell: UIView { lazy var titleLabel:UILabel = { let lb = UILabel(frame: .zero) lb.textColor = UIColor.gray lb.font = UIFont.systemFont(ofSize: 13) lb.textAlignment = .center lb.layer.borderWidth = 0.3 lb.layer.borderColor = UIColor.gray.cgColor lb.layer.cornerRadius = 14 return lb }() convenience init(title:String, maxSize:CGSize) { self.init() addSubview(self.titleLabel) self.titleLabel.text = title self.titleLabel.frame = CGRect(x: 0, y: 0, width: maxSize.width, height: maxSize.height) } override func layoutSubviews() { super.layoutSubviews() } } ================================================ FILE: zhuishushenqi/NewVersion/Search/ZSSearchHotwords.swift ================================================ // // ZSSearchHotwords.swift // zhuishushenqi // // Created by caony on 2019/10/22. // Copyright © 2019 QS. All rights reserved. // import UIKit import HandyJSON struct ZSSearchHotwords:HandyJSON, Equatable { var word:String = "" var times:Int = 0 var isNew:Bool = false var soaring:Int = 0 var frame:CGRect = CGRect.zero init() { } } ================================================ FILE: zhuishushenqi/NewVersion/Search/ZSSearchInfoBottomView.swift ================================================ // // ZSSearchInfoBottomView.swift // zhuishushenqi // // Created by caony on 2020/1/8. // Copyright © 2020 QS. All rights reserved. // import UIKit protocol ZSSearchInfoBottomViewDelegate:class { func bottomView(bottomView:ZSSearchInfoBottomView, clickAdd:UIButton) func bottomView(bottomView:ZSSearchInfoBottomView, clickRead:UIButton) } class ZSSearchInfoBottomView: UIView { weak var delegate:ZSSearchInfoBottomViewDelegate? lazy var topLine:UIView = { let line = UIView(frame: .zero) line.backgroundColor = UIColor.gray return line }() lazy var addToShelfButton:UIButton = { let bt = UIButton(type: .custom) bt.setTitle("添加书架", for: .normal) bt.setTitleColor(UIColor.black, for: .normal) bt.setTitle("已在书架", for: .selected) bt.layer.cornerRadius = 20 bt.layer.masksToBounds = true bt.layer.borderWidth = 0.3 bt.layer.borderColor = UIColor.gray.cgColor bt.addTarget(self, action: #selector(addAction(bt:)), for: .touchUpInside) bt.setTitleColor(UIColor.gray, for: .selected) return bt }() lazy var startReadButton:UIButton = { let bt = UIButton(type: .custom) bt.setTitle("开始阅读", for: .normal) bt.layer.cornerRadius = 20 bt.layer.masksToBounds = true bt.backgroundColor = UIColor.red bt.addTarget(self, action: #selector(readAction(bt:)), for: .touchUpInside) bt.setTitleColor(UIColor.white, for: .normal) return bt }() @objc private func addAction(bt:UIButton) { bt.isSelected = !bt.isSelected delegate?.bottomView(bottomView: self, clickAdd: bt) } @objc private func readAction(bt:UIButton) { delegate?.bottomView(bottomView: self, clickRead: bt) } override init(frame: CGRect) { super.init(frame: frame) backgroundColor = UIColor.white addSubview(topLine) addSubview(addToShelfButton) addSubview(startReadButton) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } func configure(book:ZSAikanParserModel) { self.addToShelfButton.isSelected = ZSShelfManager.share.exist(book.bookUrl) } override func layoutSubviews() { super.layoutSubviews() topLine.frame = CGRect(x: 0, y: 0, width: self.bounds.width, height: 0.3) addToShelfButton.frame = CGRect(x: 20, y: 10, width: 170, height: 40) startReadButton.frame = CGRect(x: self.bounds.width - 190, y: 10, width: 170, height: 40) } } ================================================ FILE: zhuishushenqi/NewVersion/Search/ZSSearchInfoTableViewCell.swift ================================================ // // ZSSearchInfoTableViewCell.swift // zhuishushenqi // // Created by yung on 2020/1/5. // Copyright © 2020 QS. All rights reserved. // import UIKit protocol ZSSearchInfoTableViewCellDelegate:class { func infoCell(cell:ZSSearchInfoTableViewCell,click download:UIButton) } class ZSSearchInfoTableViewCell: UITableViewCell { weak var delegate:ZSSearchInfoTableViewCellDelegate? private lazy var downloadBtn:UIButton = { let bt = UIButton(type: .custom) bt.setTitle("缓存", for: .normal) bt.setTitle("已缓存", for: .selected) bt.setTitleColor(UIColor.red, for: .normal) bt.setTitleColor(UIColor.gray, for: .selected) bt.frame = CGRect(x: 0, y: 0, width: 60, height: 30) bt.addTarget(self, action: #selector(downloadAction(bt:)), for: .touchUpInside) return bt }() override func awakeFromNib() { super.awakeFromNib() // Initialization code } override func setSelected(_ selected: Bool, animated: Bool) { super.setSelected(selected, animated: animated) // Configure the view for the selected state } override func layoutSubviews() { super.layoutSubviews() accessoryView = self.downloadBtn } func downloadFinish() { self.downloadBtn.isSelected = true } override func prepareForReuse() { self.downloadBtn.isSelected = false } @objc private func downloadAction(bt:UIButton) { if bt.isSelected == false { delegate?.infoCell(cell: self, click: bt) } } } ================================================ FILE: zhuishushenqi/NewVersion/Search/ZSSearchInfoViewController.swift ================================================ // // ZSSearchInfoViewController.swift // zhuishushenqi // // Created by yung on 2019/10/28. // Copyright © 2019 QS. All rights reserved. // import UIKit import SnapKit class ZSSearchInfoViewController: BaseViewController, ZSSearchInfoTableViewCellDelegate, ZSSearchInfoBottomViewDelegate { var model:ZSAikanParserModel? { didSet { } } lazy var tableView:UITableView = { let tableView = UITableView(frame: .zero, style: .grouped) tableView.dataSource = self tableView.delegate = self tableView.sectionHeaderHeight = 0.01 tableView.sectionFooterHeight = 0.01 if #available(iOS 11, *) { tableView.contentInsetAdjustmentBehavior = .never } tableView.qs_registerCellClass(ZSSearchInfoTableViewCell.self) tableView.qs_registerHeaderFooterClass(ZSBookInfoHeaderView.self) let blurEffect = UIBlurEffect(style: .extraLight) let blurEffectView = UIVisualEffectView(effect: blurEffect) tableView.backgroundView = blurEffectView return tableView }() lazy var bottomView:ZSSearchInfoBottomView = { let view = ZSSearchInfoBottomView(frame: .zero) view.delegate = self return view }() override func viewDidLoad() { super.viewDidLoad() setupSubview() setupNavItem() } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) navigationController?.isNavigationBarHidden = false } override func popAction() { ZSReaderDownloader.share.cancelDownload() super.popAction() } private func setupSubview() { title = model?.bookName view.addSubview(tableView) view.addSubview(bottomView) tableView.snp.remakeConstraints { (make) in make.left.right.equalToSuperview() make.top.equalTo(kNavgationBarHeight) make.height.equalTo(ScreenHeight - kNavgationBarHeight - kTabbarBlankHeight - 60) } bottomView.snp.makeConstraints { (make) in let height = kTabbarBlankHeight + 60 make.left.right.bottom.equalToSuperview() make.height.equalTo(height) } bottomView.configure(book: model!) } private func setupNavItem() { let addItem = UIBarButtonItem(title: "缓存全本", style: UIBarButtonItem.Style.done, target: self, action: #selector(downloadAll)) self.navigationItem.rightBarButtonItem = addItem } @objc private func downloadAll() { guard let book = model else { return } ZSReaderDownloader.share.download(book: book, start: 0) { [weak self] (finished) in self?.tableView.reloadData() } } func infoCell(cell:ZSSearchInfoTableViewCell,click download:UIButton) { guard let indexPath = tableView.indexPath(for: cell) else { return } if let chapter = self.model?.chaptersModel[indexPath.row] { ZSReaderDownloader.share.download(chapter: chapter,book:model!, reg: model!.content) { [weak self] (chapter) in DispatchQueue.main.async { self?.tableView.reloadData() } } } } //MARK: - ZSSearchInfoBottomViewDelegate func bottomView(bottomView: ZSSearchInfoBottomView, clickAdd: UIButton) { let selected = clickAdd.isSelected if selected { if let book = self.model { ZSShelfManager.share.addAikan(book) } } else { if let book = self.model { ZSShelfManager.share.removeAikan(book) } } } func bottomView(bottomView: ZSSearchInfoBottomView, clickRead: UIButton) { guard let book = self.model else { return } if book.chaptersModel.count > 0 { let readerVC = ZSReaderController(chapter: nil, book) readerVC.hidesBottomBarWhenPushed = true self.navigationController?.pushViewController(readerVC, animated: true) } else { alert(with: "提示", message: "找不到该书籍", okTitle: "确定") } } deinit { } } extension ZSSearchInfoViewController:UITableViewDataSource, UITableViewDelegate { func numberOfSections(in tableView: UITableView) -> Int { return 1 } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return self.model?.chaptersModel.count ?? 0 } func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { return 40 } func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { if let book = model { return ZSBookInfoHeaderView.height(for: book) } return 200 } func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { return 0.01 } func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { let headerView = tableView.qs_dequeueReusableHeaderFooterView(ZSBookInfoHeaderView.self) if let book = model { headerView?.configure(model: book) } return headerView } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.qs_dequeueReusableCell(ZSSearchInfoTableViewCell.self) cell?.delegate = self cell?.selectionStyle = .none cell?.accessoryType = .disclosureIndicator if let dict = self.model?.chaptersModel[indexPath.row] { cell?.textLabel?.text = dict.chapterName if let chapter = ZSBookMemoryCache.share.content(for: dict.chapterUrl) { if chapter.chapterContent.length > 0 { cell?.downloadFinish() } } } return cell! } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { guard let chapters = model?.chaptersModel else { return } let pageVC = ZSReaderController(chapter: chapters[indexPath.row], model!) self.navigationController?.pushViewController(pageVC, animated: true) } } ================================================ FILE: zhuishushenqi/NewVersion/Search/ZSSearchRecommendView.swift ================================================ // // ZSSearchRecommendView.swift // zhuishushenqi // // Created by caony on 2019/10/23. // Copyright © 2019 QS. All rights reserved. // import UIKit typealias ZSSearchRecHandler = (_ hotword:ZSHotWord)->Void typealias ZSSearchHistoryHandler = (_ history:ZSSearchHistory)->Void class ZSSearchRecommendView: UIView { var cellsFrame:[ZSHotWord] = [] { didSet { reloadData() } } var clickHandler:ZSSearchClickHandler? override init(frame: CGRect) { super.init(frame: frame) } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func layoutSubviews() { super.layoutSubviews() } func reloadData() { removeAllSubviews() for cellModel in cellsFrame { let cell = ZSSearchRecommendCell(title: cellModel.word, maxSize: cellModel.frame.size) let tap = UITapGestureRecognizer(target: self, action: #selector(tapAction(tap:))) cell.addGestureRecognizer(tap) cell.frame = cellModel.frame addSubview(cell) } } @objc func tapAction(tap:UITapGestureRecognizer) { for cellModel in cellsFrame { if cellModel.frame.contains(tap.location(in: self)) { clickHandler?(cellModel.word) break } } } } class ZSSearchRecommendCell: UIView { lazy var titleLabel:UILabel = { let lb = UILabel(frame: .zero) lb.textColor = UIColor.gray lb.font = UIFont.systemFont(ofSize: 13) lb.textAlignment = .left return lb }() lazy var iconView:UIImageView = { let icon = UIImageView(frame: .zero) icon.image = UIImage(named: "") icon.backgroundColor = UIColor.gray return icon }() convenience init(title:String, maxSize:CGSize) { self.init() addSubview(self.titleLabel) addSubview(self.iconView) self.titleLabel.text = title } override func layoutSubviews() { super.layoutSubviews() self.iconView.frame = CGRect(x: 0, y: 4, width: 14, height: 14) self.titleLabel.frame = CGRect(x: 19, y: 0, width: self.bounds.width - 19, height: 21) } } ================================================ FILE: zhuishushenqi/NewVersion/Search/ZSSearchResultCell.swift ================================================ // // ZSSearchResultCell.swift // zhuishushenqi // // Created by yung on 2019/10/27. // Copyright © 2019 QS. All rights reserved. // import UIKit import Kingfisher class ZSSearchResultCell: UITableViewCell { lazy var bookIconView:UIImageView = { let icon = UIImageView(frame: .zero) return icon }() lazy var bookNameLB:UILabel = { let icon = UILabel(frame: .zero) icon.textColor = UIColor.black icon.font = UIFont.systemFont(ofSize: 15) return icon }() lazy var authorLB:UILabel = { let icon = UILabel(frame: .zero) icon.textColor = UIColor.black icon.font = UIFont.systemFont(ofSize: 13) return icon }() lazy var typeLB:UILabel = { let icon = UILabel(frame: .zero) icon.textColor = UIColor.gray icon.font = UIFont.systemFont(ofSize: 15) return icon }() lazy var sourceLB:UILabel = { let icon = UILabel(frame: .zero) icon.textColor = UIColor.gray icon.font = UIFont.systemFont(ofSize: 15) icon.textAlignment = .right return icon }() lazy var bookDescLB:UILabel = { let icon = UILabel(frame: .zero) icon.textColor = UIColor.gray icon.font = UIFont.systemFont(ofSize: 13) icon.numberOfLines = 0 return icon }() override func layoutSubviews() { super.layoutSubviews() self.bookIconView.frame = CGRect(x: 15, y: 15, width: 80, height: 100) self.bookNameLB.frame = CGRect(x: self.bookIconView.frame.maxX + 20, y: 15, width: 200, height: 15) self.sourceLB.frame = CGRect(x: self.contentView.bounds.width - 100 - 15, y: 20, width: 100, height: 15) self.authorLB.frame = CGRect(x: self.bookIconView.frame.maxX + 20, y: self.bookNameLB.frame.maxY + 5, width: 200, height: 15) self.bookDescLB.frame = CGRect(x: self.bookIconView.frame.maxX + 20, y: self.authorLB.frame.maxY + 5, width: self.contentView.bounds.width - self.bookIconView.frame.maxX - 20 - 20, height: 45) } func configure(model:ZSAikanParserModel) { let icon = model.bookIcon.length != 0 ? model.bookIcon:model.detailBookIcon let resource = QSResource(url: URL(string: "\(icon)") ?? URL(string: "https://www.baidu.com")!) self.bookIconView.kf.setImage(with: resource,placeholder: UIImage(named: "default_book_cover")) self.bookDescLB.text = model.bookDesc.length != 0 ? model.bookDesc:model.detailBookDesc self.sourceLB.text = model.name self.authorLB.text = model.bookAuthor self.bookNameLB.text = model.bookName } override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) contentView.addSubview(self.bookIconView) contentView.addSubview(self.bookNameLB) contentView.addSubview(self.authorLB) contentView.addSubview(self.typeLB) contentView.addSubview(self.sourceLB) contentView.addSubview(self.bookDescLB) } override func prepareForReuse() { let resource = QSResource(url: URL(string: "") ?? URL(string: "https://www.baidu.com")!) self.bookIconView.kf.setImage(with: resource) self.bookDescLB.text = "" self.sourceLB.text = "" self.authorLB.text = "" self.bookNameLB.text = "" } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func awakeFromNib() { super.awakeFromNib() // Initialization code } override func setSelected(_ selected: Bool, animated: Bool) { super.setSelected(selected, animated: animated) // Configure the view for the selected state } } ================================================ FILE: zhuishushenqi/NewVersion/Search/ZSSearchResultView.swift ================================================ // // ZSSearchResultView.swift // zhuishushenqi // // Created by caony on 2019/10/22. // Copyright © 2019 QS. All rights reserved. // import UIKit typealias ZSSearchResultHander = (_ model:ZSAikanParserModel)->Void class ZSSearchResultView: UIView { var books:[ZSAikanParserModel] = [] var resultHandler:ZSSearchResultHander? lazy var tableView:UITableView = { let tableView = UITableView(frame: .zero, style: .grouped) tableView.dataSource = self tableView.delegate = self tableView.sectionHeaderHeight = 0.01 tableView.sectionFooterHeight = 0.01 if #available(iOS 11, *) { tableView.contentInsetAdjustmentBehavior = .never } tableView.qs_registerCellClass(ZSSearchResultCell.self) let blurEffect = UIBlurEffect(style: .extraLight) let blurEffectView = UIVisualEffectView(effect: blurEffect) tableView.backgroundView = blurEffectView return tableView }() override init(frame: CGRect) { super.init(frame: frame) self.backgroundColor = UIColor.white addSubview(tableView) } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } func addBook(book:ZSAikanParserModel) { DispatchQueue.main.async { if self.books.count != 0 { self.books.append(book) let indexPath:IndexPath = IndexPath(row: self.books.count - 1, section: 0) self.tableView.insertRows(at: [indexPath], with: .bottom) self.tableView.endUpdates() } else { self.books.append(book) self.reloadData() } } } func clearBooks() { self.books.removeAll() self.reloadData() } private func reloadData() { self.tableView.reloadData() } override func layoutSubviews() { super.layoutSubviews() self.tableView.frame = self.bounds } } extension ZSSearchResultView:UITableViewDataSource, UITableViewDelegate { func numberOfSections(in tableView: UITableView) -> Int { return 1 } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return self.books.count } func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { return 130 } func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { return 0.01 } func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { return 0.01 } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.qs_dequeueReusableCell(ZSSearchResultCell.self) cell?.selectionStyle = .none cell?.configure(model: self.books[indexPath.row]) return cell! } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { if resultHandler != nil { resultHandler?(books[indexPath.row]) } } } ================================================ FILE: zhuishushenqi/NewVersion/Search/ZSSourceManager.swift ================================================ // // ZSSourceManagerr.swift // zhuishushenqi // // Created by caony on 2019/11/11. // Copyright © 2019 QS. All rights reserved. // // 书籍来源管理类 import UIKit class ZSSourceManager { var sources:[ZSAikanParserModel] = [] static let selectedSourceKey = "selectedSourceKey" static let share = ZSSourceManager() private init() { unpack() } private func unpack() { let modifyfilePath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first!.appending("/source/HtmlParserModelData_edit.dat") let originialfilePath_edit = Bundle(for: ZSSourceManager.self).path(forResource: "HtmlParserModelData_edit.dat", ofType: nil) ?? "" let originialfilePath = Bundle(for: ZSSourceManager.self).path(forResource: "HtmlParserModelData.dat", ofType: nil) ?? "" if let objs = NSKeyedUnarchiver.unarchiveObject(withFile: modifyfilePath) as? [ZSAikanParserModel] { self.sources = objs } else if let objs = NSKeyedUnarchiver.unarchiveObject(withFile: modifyfilePath) as? [AikanParserModel] { self.sources = self.transform(models: objs) } else if let objs = NSKeyedUnarchiver.unarchiveObject(withFile: originialfilePath_edit) as? [ZSAikanParserModel] { self.sources = objs save() } else if let objs = NSKeyedUnarchiver.unarchiveObject(withFile: originialfilePath_edit) as? [AikanParserModel] { self.sources = self.transform(models: objs) save() } else if let objs = NSKeyedUnarchiver.unarchiveObject(withFile: originialfilePath) as? [ZSAikanParserModel] { self.sources = objs save() } else if let objs = NSKeyedUnarchiver.unarchiveObject(withFile: originialfilePath) as? [AikanParserModel] { self.sources = self.transform(models: objs) save() } } private func transform(models:[AikanParserModel]) ->[ZSAikanParserModel] { var aikans:[ZSAikanParserModel] = [] for model in models { let aikan = ZSAikanParserModel() // aikan.errDate = model.errDate aikan.searchUrl = model.searchUrl aikan.name = model.name aikan.type = Int64(model.type) aikan.enabled = model.enabled aikan.checked = model.checked aikan.searchEncoding = model.searchEncoding aikan.host = model.host aikan.contentReplace = model.contentReplace aikan.contentRemove = model.contentRemove aikan.content = model.content aikan.chapterUrl = model.chapterUrl aikan.chapterName = model.chapterName aikan.chapters = model.chapters aikan.chaptersModel = model.chaptersModel as? [ZSBookChapter] ?? [] aikan.detailBookIcon = model.detailBookIcon aikan.detailChaptersUrl = model.detailChaptersUrl aikan.bookLastChapterName = model.bookLastChapterName aikan.bookUpdateTime = model.bookUpdateTime aikan.bookUrl = model.bookUrl aikan.bookIcon = model.bookIcon aikan.bookDesc = model.bookDesc aikan.bookCategory = model.bookCategory aikan.bookAuthor = model.bookAuthor aikan.bookName = model.bookName aikan.books = model.books aikan.chaptersReverse = model.chaptersReverse aikan.detailBookDesc = model.detailBookDesc aikans.append(aikan) } return aikans } private func save() { let path = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first!.appending("/source/") let filePath = path.appending("HtmlParserModelData_edit.dat") let isDirectory:UnsafeMutablePointer? = UnsafeMutablePointer.allocate(capacity: 1) if !FileManager.default.fileExists(atPath: path, isDirectory: isDirectory) { try? FileManager.default.createDirectory(atPath: path, withIntermediateDirectories: true, attributes: nil) } NSKeyedArchiver.archiveRootObject(self.sources, toFile: filePath) } func add(source:ZSAikanParserModel) { if sources.contains(source) { modify(source: source) } else { sources.append(source) DispatchQueue.global().async { self.save() } } } func modify(source:ZSAikanParserModel) { var index = 0 let ts = sources for ss in ts { if ss.host == source.host { sources[index] = source break } index += 1 } DispatchQueue.global().async { self.save() } } func delete(source:ZSAikanParserModel) { var index = 0 let ts = sources for ss in ts { if ss.host == source.host { sources.remove(at: index) break } index += 1 } DispatchQueue.global().async { self.save() } } func select(source:ZSAikanParserModel) { source.checked = true replace(source: source) DispatchQueue.global().async { self.save() } } func unselect(source:ZSAikanParserModel) { source.checked = false replace(source: source) DispatchQueue.global().async { self.save() } } func source(_ host:String) ->ZSAikanParserModel? { var resultSource:ZSAikanParserModel? for source in sources { if host == source.host { resultSource = source break } } return resultSource } private func replace(source:ZSAikanParserModel) { var index = 0 let ts = sources for ss in ts { if ss.host == source.host { sources[index] = source break } index += 1 } } } ================================================ FILE: zhuishushenqi/NewVersion/Search/ZSTopSearchBar.swift ================================================ // // ZSTopSearchBar.swift // zhuishushenqi // // Created by caony on 2019/10/22. // Copyright © 2019 QS. All rights reserved. // import UIKit protocol ZSTopSearchBarProtocol:class { func zsTopSearchBar(topSearchBar:ZSTopSearchBar, searchTextFieldShouldBeginEditing text:String) func zsTopSearchBar(topSearchBar:ZSTopSearchBar, searchTextFieldEditChanged text:String) func zsTopSearchBar(topSearchBar:ZSTopSearchBar, searchTextFieldReturn text:String) func zsTopSearchBarCancelButtonClick(topSearchBar:ZSTopSearchBar) } class ZSTopSearchBar: UIView, UITextFieldDelegate { lazy var textfield:ZSTopSearchTextField = { let tf = ZSTopSearchTextField(frame: CGRect.zero) tf.placeholder = "请输入书名、作者或者分类" tf.font = UIFont.systemFont(ofSize: 13) tf.textColor = UIColor.gray tf.backgroundColor = UIColor(red:0.92, green:0.92, blue:0.93, alpha:1.00) tf.layer.cornerRadius = 10 tf.layer.masksToBounds = true tf.delegate = self tf.clearButtonMode = .always return tf }() lazy var cancelButton:UIButton = { let bt = UIButton(type: .custom) bt.setTitle("取消", for: UIControl.State.normal) bt.setTitleColor(UIColor(red:0.93, green:0.28, blue:0.27, alpha:1.00), for: .normal) bt.titleLabel?.font = UIFont.systemFont(ofSize: 17) bt.addTarget(self, action: #selector(cancelAction(bt:)), for: .touchUpInside) return bt }() weak var delegate:ZSTopSearchBarProtocol? override init(frame: CGRect) { super.init(frame: frame) backgroundColor = UIColor.white addSubview(self.textfield) addSubview(self.cancelButton) } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func layoutSubviews() { super.layoutSubviews() let statusFrame = UIApplication.shared.statusBarFrame let screenFrame = UIScreen.main.bounds textfield.frame = CGRect(x: 15, y: 16 + statusFrame.height, width: screenFrame.width - 75 - 15, height: 36) cancelButton.frame = CGRect(x: screenFrame.width - 75, y: statusFrame.height, width: 75, height: 68) } @objc private func cancelAction(bt:UIButton) { delegate?.zsTopSearchBarCancelButtonClick(topSearchBar: self) } func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool { delegate?.zsTopSearchBar(topSearchBar: self, searchTextFieldShouldBeginEditing: textField.text ?? "") return true } func textFieldShouldReturn(_ textField: UITextField) -> Bool { delegate?.zsTopSearchBar(topSearchBar: self, searchTextFieldReturn: textField.text ?? "") return true } func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { return true } func textFieldShouldClear(_ textField: UITextField) -> Bool { delegate?.zsTopSearchBar(topSearchBar: self, searchTextFieldEditChanged: "") return true } } class ZSTopSearchTextField:UITextField { override func textRect(forBounds bounds: CGRect) -> CGRect { let rect = super.textRect(forBounds: bounds) return CGRect(x: rect.origin.x + 10, y: rect.origin.y, width: rect.width, height: rect.height) } override func editingRect(forBounds bounds: CGRect) -> CGRect { let rect = super.editingRect(forBounds: bounds) return CGRect(x: rect.origin.x + 10, y: rect.origin.y, width: rect.width, height: rect.height) } } ================================================ FILE: zhuishushenqi/NewVersion/Tabbar/ZSTabBarController.swift ================================================ // // ZSTabBarController.swift // zhuishushenqi // // Created by caony on 2019/6/18. // Copyright © 2019年 QS. All rights reserved. // import UIKit class ZSTabBarController: UITabBarController,UITabBarControllerDelegate { var lastSelectedIndex:Int = 0 override func viewDidLoad() { super.viewDidLoad() delegate = self setupSubviews() } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.3) { ZSMemoryFloatingView.show() } } override var prefersStatusBarHidden: Bool { guard let currentVC = viewControllers?[selectedIndex] as? UINavigationController else { return false } guard let topVC = currentVC.topViewController else { return currentVC.prefersStatusBarHidden } guard let presentedVC = topVC.presentedViewController else { return topVC.prefersStatusBarHidden } return presentedVC.prefersStatusBarHidden } override var preferredStatusBarStyle: UIStatusBarStyle { guard let currentVC = viewControllers?[selectedIndex] as? UINavigationController else { return .lightContent } guard let topVC = currentVC.topViewController else { return currentVC.preferredStatusBarStyle } guard let presentedVC = topVC.presentedViewController else { return topVC.preferredStatusBarStyle } return presentedVC.preferredStatusBarStyle } private func setupSubviews() { let homeItem = UITabBarItem(title: "书架", image: UIImage(named: "tab_bookshelf")?.withRenderingMode(.alwaysOriginal), selectedImage: UIImage(named: "tab_bookshelf_sel")?.withRenderingMode(.alwaysOriginal)) let channelItem = UITabBarItem(title: "书城", image: UIImage(named: "tab_bookstore")?.withRenderingMode(.alwaysOriginal), selectedImage: UIImage(named: "tab_bookstore_sel")?.withRenderingMode(.alwaysOriginal)) let dynamicItem = UITabBarItem(title: "社区", image: UIImage(named: "tab_bbs")?.withRenderingMode(.alwaysOriginal), selectedImage: UIImage(named: "tab_bbs_sel")?.withRenderingMode(.alwaysOriginal)) let vipItem = UITabBarItem(title: "发现", image: UIImage(named: "tab_discover")?.withRenderingMode(.alwaysOriginal), selectedImage: UIImage(named: "tab_discover_sel")?.withRenderingMode(.alwaysOriginal)) let mineItem = UITabBarItem(title: "我的", image: UIImage(named: "tab_profile")?.withRenderingMode(.alwaysOriginal), selectedImage: UIImage(named: "tab_profile_sel")?.withRenderingMode(.alwaysOriginal)) let homeVC = ZSBookShelfViewController() let homeNav = UINavigationController(rootViewController: homeVC) homeNav.tabBarItem = homeItem let channelVC = ZSBookStoreViewController() let channelNav = UINavigationController(rootViewController: channelVC) channelNav.tabBarItem = channelItem let dynamicVC = ZSCommunityViewController() let dynamicNav = UINavigationController(rootViewController: dynamicVC) dynamicNav.tabBarItem = dynamicItem let vipVC = ZSDiscoverViewController() let vipNav = UINavigationController(rootViewController: vipVC) vipNav.tabBarItem = vipItem let mineVC = ZSMineViewController() let mineNav = UINavigationController(rootViewController: mineVC) mineNav.tabBarItem = mineItem viewControllers = [homeNav, channelNav, dynamicNav, vipNav, mineNav] let normalAttributes = [NSAttributedString.Key.foregroundColor:UIColor(hexString: "7A7A7A")] let selectedAttributes = [NSAttributedString.Key.foregroundColor:UIColor(hexString: "#A70B0B")] UITabBarItem.appearance().setTitleTextAttributes(normalAttributes as [NSAttributedString.Key : Any], for: .normal) UITabBarItem.appearance().setTitleTextAttributes(selectedAttributes as [NSAttributedString.Key : Any], for: .selected) } func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) { guard let baseNav = viewController as? UINavigationController else { return } guard let _ = baseNav.topViewController as? BaseViewController else { return } } override func tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem) { lastSelectedIndex = selectedIndex if let index = tabBar.items?.firstIndex(of: item) { guard let navController = viewControllers?[index] as? UINavigationController else { return } guard let _ = navController.topViewController as? BaseViewController else { return } } } } ================================================ FILE: zhuishushenqi/NewVersion/ZSAPI/LICENSE ================================================ Copyright (c) 2019 2252055382@qq.com Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: zhuishushenqi/NewVersion/ZSAPI/ZSAPI/Classes/ZSAPI.swift ================================================ // // QSAPI.swift // zhuishushenqi // // Created by yung on 2017/6/29. // Copyright © 2017年 QS. All rights reserved. // import UIKit public protocol ZSTargetType { var baseURLString:String { get } var path:String { get } var parameters:[String:Any]? { get } } public enum ZSBaseType { case normal case chapter } public enum ZSAPI { ///首次进入根据性别推荐书籍 case genderRecommend(gender:String) ///追书书架信息 case shelfMSG(_ placeHolder:AnyObject) ///书架更新信息 case update(id:String) ///热门搜索 case hotwords(_ placeHolder:AnyObject) ///搜索热词 case searchHotwords(_ placeHolder:AnyObject) ///联想搜索 case autoComplete(query:String) ///搜索书籍 case searchBook(id:String,start:String,limit:String) ///排行榜 case ranking(_ placeHolder:AnyObject) ///榜单数据 case rankList(id:String) ///分类 case category(_ placeHolder:AnyObject) ///分类详细 case categoryList(gender:String,type:String,major:String,minor:String,start:String,limit:String) ///tag过滤 case tagType(_ placeHolder:AnyObject) ///主题书单 case themeTopic(sort:String,duration:String,start:String,gender:String,tag:String) ///主题书单详细 case themeDetail(key:String) ///热门评论 case hotComment(key:String) ///普通评论 case normalComment(key:String,start:String,limit:String) ///热门动态 case hotUser(key:String) ///都是热门,忘记干嘛的了 case hotPost(key:String,start:String,limit:String) ///评论详情 case commentDetail(key:String) ///社区 case community(key:String,start:String) ///社区评论 case communityComment(key:String,start:String) ///所有来源 case allResource(key:String) ///所有章节 case allChapters(key:String) ///某一章节 case chapter(key:String,type:ZSBaseType) ///书籍信息 case book(key:String) ///详情页热门评论 case bookHot(key:String) ///详情页可能感兴趣 case interested(key:String) ///详情页推荐书单 case recommend(key:String) //随机看书 case mysteryBook(_ placeHolder:AnyObject) ///登录 case login(idfa:String,platform_code:String,platform_token:String,platform_uid:String,version:String,tag:String) ///账户信息 case account(token:String) ///金币信息 case golden(token:String) ///账户详情 case userDetail(token:String) ///个人信息绑定账户 case userBind(token:String) ///退出登录 case logout(token:String) ///书架列表 case bookshelf(token:String) ///获取手机验证码 case SMSCode(mobile:String,Randstr:String,Ticket:String,captchaType:String,type:String) ///手机号登录 case mobileLogin(mobile:String,idfa:String,platform_code:String,smsCode:String,version:String) ///书架书籍删除 case booksheldDelete(books:String, token:String) ///书架书籍添加 case bookshelfAdd(books:String,token:String) ///用户昵称修改 case nicknameChange(nickname:String,token:String) /// case blessing_bag(token:String) ///查询活动 case judgeSignIn(token:String) ///签到领金币 case signIn(token:String,activityId:String,version:String,type:String) ///编写评论 case reviewPost(token:String,id:String,content:String) ///已购章节信息 case boughtChapters(id:String,token:String) ///书架信息diff case bookshelfdiff(books:String,token:String) ///追书券列表 case voucherList(token:String, type:String, start:Int, limit:Int) ///有声书分类列表 // case voiceCategory() // https://apidian2.lnk.la/system/rule?type=test&version=not_found&juhe=1 // 第三方书籍来源接口 case thirdPartSource(type:String, version:String,juhe:Int) // 社区 case userTwitter(_ last:String) // 关注 case userFollowings(_ id:String) // 粉丝 case userFollowers(_ id:String) // 社区用户动态 case userTwitters(_ id:String, last:String) // 关注用户 case focus(token:String, followeeId:String) // 取消关注 case unFocus(token:String, followeeId:String) // 通知阅读 case readImportant(token:String) // 重要通知 case important(token:String) // 不重要通知阅读 case readUnimportant(token:String) // 不重要通知 case unimportant(token:String) // 社区tweet详情 case post(key:String) // 新版社区 case communityHot(start:Int, limit:Int) // 社区回复 case bookAidAnswer(key:String) // 热评 case bookAidBestComment(key:String) // 一般评价 case bookAidComments(key:String) // 社区提问 case bookAidQuestion(key:String) // 社区书籍 case forumBook(key:String) // 精确搜索 case accurateSearch(author:String, key:String, userId:String) // 详情推荐 case bookRecommend(key:String, position:String, ts:Double) // 章节内容 case chapterContent(key:String, token:String, thirdToken:String) //http://bookapi01.zhuishushenqi.com/book/crypto/chapterContent/5ec241a4f4fdb43320c34521?token=&third-token=0e05f2d6a4e2080EB811f1DA087eE362%3A75616d677637696a616a62627045d0b944fd1fa0ab281b3ce8f71eb65ae1ac96a6fbfe28829107b72e07c7f46375b7 } //https://api.ximalaya.com/openapi-gateway-app/v2/albums/list?access_token=906bbf257eddd7ba84ceec277bdef5b5&app_key=e31646fa4555ea3472d4114921ee192e&client_os_type=1&device=iPhone&device_id=F6F542A1-1676-4D28-96C2-CF9D2F52F5FD&pack_id=com.ifmoc.ZhuiShuShenQi&sdk_version=5.4.7&calc_dimension=1&category_id=3&count=20&page=1&tag_name=%E8%A8%80%E6%83%85&type=0&sig=8db40bb73de647a3baba9afb4635eb8e& extension ZSAPI:ZSTargetType{ public var path: String { var pathComponent = "" switch self { case .genderRecommend(_): pathComponent = "/book/recommend" break case .shelfMSG(_): pathComponent = "/notification/shelfMessage" break case .update(_): pathComponent = "/book" break case .hotwords(_): pathComponent = "/book/hot-word" break case .searchHotwords(_): pathComponent = "/book/search-hotwords" break case .autoComplete(_): pathComponent = "/book/auto-complete" break case .searchBook(_,_,_): pathComponent = "/book/fuzzy-search" break case .ranking(_): pathComponent = "/ranking/gender" break case let .rankList(id): pathComponent = "/ranking/\(id)" break case .category(_): pathComponent = "/cats/lv2/statistics" break case .categoryList(_,_,_,_,_,_): pathComponent = "/book/by-categories" break case .tagType(_): pathComponent = "/book-list/tagType" break case .themeTopic(_,_,_,_,_): pathComponent = "/book-list" break case let .themeDetail(key): pathComponent = "/book-list/\(key)" break case let .hotComment(key): pathComponent = "/post/\(key)/comment/best" break case let .normalComment(key,_,_): pathComponent = "/post/review/\(key)/comment" break case let .hotUser(key): pathComponent = "/user/twitter/\(key)/comments" break case let .hotPost(key,_,_): pathComponent = "/post/\(key)/comment" break case let .commentDetail(key): pathComponent = "/post/review/\(key)" break case let .community(key,start): pathComponent = "/post/by-book?book=\(key)&sort=updated&type=normal,vote&start=\(start)&limit=20" break case let .communityComment(key, start): pathComponent = "/post/review/by-book?book=\(key)&sort=updated&start=\(start)&limit=20" break case .allResource(_): pathComponent = "/toc" break case let .allChapters(key): pathComponent = "/toc/\(key)" break case let .chapter(key,_): pathComponent = "/\(key)?k=22870c026d978c75&t=1489933049" break case let .book(key): pathComponent = "/book/\(key)" break case let .bookHot(key): pathComponent = "/post/review/best-by-book?book=\(key)" break case let .interested(key): pathComponent = "/book/\(key)/recommend" break case let .recommend(key): pathComponent = "/book-list/\(key)/recommend?limit=3" break case .mysteryBook(_): pathComponent = "/book/mystery-box" break case .login(_,_,_,_,_,_): pathComponent = "/user/login" break case .account(_): pathComponent = "/user/account" break case .golden(_): pathComponent = "/account" break case .userDetail(_): pathComponent = "/user/detail-info" break case .userBind(_): pathComponent = "/user/loginBind" break case .logout(_): pathComponent = "/user/logout" break case .bookshelf(_): pathComponent = "/v3/user/bookshelf" break case .SMSCode(_, _, _, _, _): pathComponent = "/v2/sms/sendSms" break case .mobileLogin(_, _, _, _, _): pathComponent = "/user/login" break case .booksheldDelete(_, _): pathComponent = "/v3/user/bookshelf" break case .bookshelfAdd(_, _): pathComponent = "/v3/user/bookshelf" break case .nicknameChange(_, _): pathComponent = "/user/change-nickname" break case let .blessing_bag(token): // https://goldcoin.zhuishushenqi.com/tasks/blessing-bag/detail?token=WupdmBZpkuzehCqdtDZF9IJR pathComponent = "/tasks/blessing-bag/detail?token=\(token)" break // https://api.zhuishushenqi.com/user/v2/judgeSignIn?token=Abrv3NbHCuKKJSVzeSglLXns case .judgeSignIn(_): pathComponent = "/user/v2/judgeSignIn" break // https://api.zhuishushenqi.com/user/signIn?token=Abrv3NbHCuKKJSVzeSglLXns&activityId=57eb9278b7b0f6fc1f2e1bc0&version=2&type=2 case .signIn(_, _, _, _): pathComponent = "/user/signIn" break case let .reviewPost(_,id,_): // https://api.zhuishushenqi.com/post/review/5be2ac16f6459891448e9b46/comment pathComponent = "/post/review/\(id)/comment" break case let .boughtChapters(id,_): // https://api.zhuishushenqi.com/v2/purchase/book/5b10fd1b5d144d1b68581805/chapters/bought?token=rPcCW1GGh1hFPnSRJDjkwjtS pathComponent = "/v2/purchase/book/\(id)/chapters/bought" break case .bookshelfdiff(_, _): // https://api.zhuishushenqi.com/v3/user/bookshelf/diff pathComponent = "/v3/user/bookshelf/diff" break case .voucherList(_, _, _, _): pathComponent = "/voucher" break case .thirdPartSource(_, _, _): pathComponent = "/system/rule" break case .userTwitter(_): pathComponent = "/user/twitter/hottweets" break case let .userFollowings(id): pathComponent = "/user/followings/\(id)" break case let .userFollowers(id): pathComponent = "/user/followers/\(id)" break case let .userTwitters(id, _): pathComponent = "/user/\(id)/twitter" break case .focus(_, _): pathComponent = "/user/follow" break case .unFocus(_, _): pathComponent = "/user/unfollow" break case .readImportant(_): pathComponent = "/user/notification/read-important" break case .important(_): pathComponent = "/user/notification/important" break case .readImportant(_): pathComponent = "/user/notification/read-unimportant" break case .unimportant(_): pathComponent = "/user/notification/unimportant" break case let .post(key): pathComponent = "/post/\(key)?keepImage=1" break case let .communityHot(_, _): //http://community.zhuishushenqi.com/community/hots?start=20&limit=20&group=-1 pathComponent = "/community/hots" break case let .bookAidAnswer(key): // https://community.zhuishushenqi.com/bookAid/answer/61d51a802c4c7a0001fb6530 pathComponent = "/bookAid/answer/\(key)" break case let .bookAidBestComment(key): // https://community.zhuishushenqi.com/bookAid/answer/61d51a802c4c7a0001fb6530/bestComments pathComponent = "/bookAid/answer/\(key)/bestComments" break case let .bookAidComments(key): // https://community.zhuishushenqi.com/bookAid/answer/61af751d2c4c7a0001fb52ca/comments pathComponent = "/bookAid/answer/\(key)/comments" break case let .bookAidQuestion(key): // https://community.zhuishushenqi.com/bookAid/question/61d3c9e82c4c7a0001fb6496?token=&packageName=com.ifmoc.ZhuiShuShenQi pathComponent = "/bookAid/question/\(key)?token=&packageName=com.ifmoc.ZhuiShuShenQi" break case let .forumBook(key): // http://community.zhuishushenqi.com/forum/book/5ec241a4f4fdb43320c34521/hot?block=all_review pathComponent = "/forum/book/\(key)/hot?block=all_review" break case let.accurateSearch(author, key, userId): // http://b.zhuishushenqi.com/books/accurate-search-author?author=%E5%86%99%E7%A6%BB%E5%A3%B0&packageName=com.ifmoc.ZhuiShuShenQi&bookId=5ec241a4f4fdb43320c34521&userid=yk_ff4ef77e9f4b19f3281bb pathComponent = "/books/accurate-search-author" break case let .bookRecommend(key, position, ts): // http://b.zhuishushenqi.com/book/5ec241a4f4fdb43320c34521/recommend?packageName=com.ifmoc.ZhuiShuShenQi&position=detail&ts=1641396502 pathComponent = "/book/\(key)/recommend" break case let .chapterContent(key, token, thirdToken): //http://bookapi01.zhuishushenqi.com/book/crypto/chapterContent/5ec241a4f4fdb43320c34521?token=&third-token=0e05f2d6a4e2080EB811f1DA087eE362%3A75616d677637696a616a62627045d0b944fd1fa0ab281b3ce8f71eb65ae1ac96a6fbfe28829107b72e07c7f46375b7 pathComponent = "/book/crypto/chapterContent/\(key)" break default: pathComponent = "" break } return "\(baseURLString)\(pathComponent)" } public var baseURLString: String{ var urlString = "http://api.zhuishushenqi.com" switch self { case let .chapter(_, type): switch type { case .chapter: urlString = "http://chapter2.zhuishushenqi.com/chapter" default: urlString = "http://api.zhuishushenqi.com" } case .golden(_): urlString = "http://goldcoin.zhuishushenqi.com" case .blessing_bag(_): urlString = "https://goldcoin.zhuishushenqi.com" case .thirdPartSource(_, _, _): urlString = "https://apidian2.lnk.la" case .readImportant(_): urlString = "https://api.zhuishushenqi.com" case .important(_): urlString = "https://api.zhuishushenqi.com" case .communityHot(_, _): urlString = "http://community.zhuishushenqi.com" case .bookAidAnswer(_): urlString = "https://community.zhuishushenqi.com" case .bookAidBestComment(_): urlString = "https://community.zhuishushenqi.com" case .bookAidComments(_): urlString = "https://community.zhuishushenqi.com" case .bookAidQuestion(_): urlString = "https://community.zhuishushenqi.com" case .forumBook(_): urlString = "http://community.zhuishushenqi.com" case .accurateSearch(_, _, _): urlString = "http://b.zhuishushenqi.com" case .bookRecommend(_, _, _): urlString = "http://b.zhuishushenqi.com" case .chapterContent(_, _, _): urlString = "http://bookapi.zhuishushenqi.com" default: urlString = "http://api.zhuishushenqi.com" } return urlString } public var parameters: [String : Any]?{ switch self { case let .genderRecommend(gender): return ["gender":gender] case let .update(id): return ["view":"updated","id":id] case let .autoComplete(query): return ["query":query] case let .searchBook(id,start,limit): return ["query":id,"start":start,"limit":limit] case .shelfMSG(_): return ["platform":"ios"] case let .categoryList(gender,type,major,minor,start,limit): return ["gender":gender,"type":type,"major":major,"minor":minor,"start":start,"limit":limit] case let .themeTopic(sort,duration,start,gender,tag): return ["sort":sort,"duration":duration,"start":start,"gender":gender,"tag":tag] case let .normalComment(_,start,limit): return ["start":start,"limit":limit] case let .hotPost(_,start,limit): return ["start":start,"limit":limit] case let .allResource(key): return ["view":"summary","book":key] case .allChapters(_): return ["view":"chapters"] case let .login(idfa, platform_code, platform_token, platform_uid, version, tag): return ["idfa":idfa, "platform_code":platform_code, "platform_token":platform_token, "platform_uid":platform_uid, "version":version, "tag":tag] case let .account(token): return ["token":token] case let .golden(token): return ["token":token] case let .userDetail(token): return ["token":token] case let .userBind(token): return ["token":token] case let .logout(token): return ["token":token] case let .bookshelf(token): return ["token":token] case let .SMSCode(mobile,Randstr,Ticket,captchaType,type): return ["mobile":mobile, "Randstr":Randstr, "Ticket":Ticket, "captchaType":captchaType, "type":type] case let .mobileLogin(mobile, idfa, platform_code, smsCode, version): return ["mobile":mobile, "idfa":idfa, "platform_code":platform_code, "smsCode":smsCode, "version":version] case let .booksheldDelete(books, token): return ["books":books, "token":token] case let .bookshelfAdd(books, token): return ["books":books, "token":token] case let .nicknameChange(nickname,token): return ["nickname":nickname, "token":token] case let .judgeSignIn(token): return ["token":token] case let .signIn(token, activityId, version, type): return ["token": token, "activityId": activityId, "version": version, "type": type] case let .reviewPost(token, _, content): return ["token":token, "content":content] case let .boughtChapters(_, token): return ["token":token, ] case let .bookshelfdiff(books, token): return ["books":books, "token":token] case let .voucherList(token, type, start, limit): return ["token":token, "type":"\(type)", "start":"\(start)", "limit":"\(limit)"] case let .thirdPartSource(type, version, juhe): return ["type":type, "version":version, "juhe":"\(juhe)"] case let .userTwitter(last): if last.count == 0 { return nil } return ["last":last] case let .userTwitters(_, last): if last.count == 0 { return nil } return ["last":last] case let .focus(token, followeeId): return ["token":"\(token)", "followeeId":"\(followeeId)"] case let .unFocus(token, followeeId): return ["token":"\(token)", "followeeId":"\(followeeId)"] case let .readImportant(token): return ["token":"\(token)"] case let .important(token): return ["token":"\(token)"] case let .readUnimportant(token): return ["token":"\(token)"] case let .unimportant(token): return ["token":"\(token)"] case let .communityHot(start, limit): return ["start":start, "limit":limit, "group":-1] case .bookAidAnswer(_): return nil case .bookAidBestComment(_): return nil case .bookAidComments(_): return nil case .bookAidQuestion(_): return nil case let .accurateSearch(author, key, userId): return [ "author":"\(author)", "bookId":"\(key)", "userid":"\(userId)", "packageName":"com.ifmoc.ZhuiShuShenQi" ] case let .bookRecommend(key, position, ts): return [ "ts":"\(ts)", "position":"\(position)", "packageName":"com.ifmoc.ZhuiShuShenQi" ] case let .chapterContent(key, token, thirdToken): return [ "token":"\(token)", "third-token":"\(thirdToken)" ] default: return nil } } } //https://api.zhuishushenqi.com/v3/user/bookshelf?token=oRSd5bVUCpSunbwiKe5NOpOM //https://api.zhuishushenqi.com/v2/purchase/book/5b10fd1b5d144d1b68581805/chapters/bought?token=rPcCW1GGh1hFPnSRJDjkwjtS ================================================ FILE: zhuishushenqi/NewVersion/ZSAPI/ZSAPI.podspec ================================================ # # Be sure to run `pod lib lint ZSAPI.podspec' to ensure this is a # valid spec before submitting. # # Any lines starting with a # are optional, but their use is encouraged # To learn more about a Podspec see https://guides.cocoapods.org/syntax/podspec.html # Pod::Spec.new do |s| s.name = 'ZSAPI' s.version = '1.0.15' s.summary = 'ZSAPI.' # This description is used to generate tags and improve search results. # * Think: What does it do? Why did you write it? What is the focus? # * Try to keep it short, snappy and to the point. # * Write the description between the DESC delimiters below. # * Finally, don't worry about the indent, CocoaPods strips it! s.description = <<-DESC zhuishushenqi baseui module. DESC s.homepage = 'https://github.com/zssq/ZSAPI' # s.screenshots = 'www.example.com/screenshots_1', 'www.example.com/screenshots_2' s.license = { :type => 'MIT', :file => 'LICENSE' } s.author = { '2252055382@qq.com' => 'norycao' } s.source = { :git => 'https://github.com/zssq/ZSAPI.git', :tag => s.version.to_s } # s.social_media_url = 'https://twitter.com/norycao' s.ios.deployment_target = '8.0' s.swift_version = '5.0' s.source_files = 'ZSAPI/Classes/**/*' s.resource_bundles = { 'ZSAPI' => ['ZSAPI/Assets/*.png'] } #s.frameworks = 'UIKit', 'SystemConfiguration', 'Security', 'CoreTelephony', 'CFNetwork', 'CoreGraphics', 'CoreText', 'QuartzCore', 'ImageIO', 'Photos' #s.libraries = 'z', 'sqlite3.0', 'c++' #s.public_header_files = 'Pod/Classes/**/*.h' # s.frameworks = 'UIKit', 'MapKit' end ================================================ FILE: zhuishushenqi/NewVersion/ZSAppConfig/LICENSE ================================================ Copyright (c) 2019 2252055382@qq.com Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: zhuishushenqi/NewVersion/ZSAppConfig/ZSAppConfig/Classes/AppStyle.swift ================================================ // // AppStyle.swift // zhuishushenqi // // Created by yung on 2017/8/8. // Copyright © 2017年 QS. All rights reserved. // import Foundation let nightKey = "light.key" let fontSizeKey = "fontSize.key" let animationStyleKey = "animationStyle.key" public struct AppStyle { public static var shared = AppStyle() public var readFontSize:Int { set { UserDefaults.standard.set(newValue, forKey: fontSizeKey) } get { let size = UserDefaults.standard.integer(forKey: fontSizeKey) if size == 0 { return 20; } return size } } public var reader:Reader = AppStyle.getReader() { didSet{ AppStyle.setReader(reader) } } public var theme:AppTheme = UserDefaults.standard.bool(forKey: nightKey) ? .night : .day { didSet{ UserDefaults.standard.set(theme == .night, forKey: nightKey) } } private init(){} public static func getReader()->Reader{ let value = UserDefaults.standard.integer(forKey: readerKey) switch value { case 1: return .yellow case 2: return .green default: return .white } } public static func setReader(_ reader:Reader){ var value = 0 switch reader { case .yellow: value = 1 case .green: value = 2 default: value = 0 } UserDefaults.standard.set(value, forKey: readerKey) } } ================================================ FILE: zhuishushenqi/NewVersion/ZSAppConfig/ZSAppConfig/Classes/Config.swift ================================================ // // Config.swift // zhuishushenqi // // Created by Nory Cao on 16/9/17. // Copyright © 2016年 QS. All rights reserved. // import Foundation import UIKit // FIXME: comparison operators with optionals were removed from the Swift Standard Libary. // Consider refactoring the code to use the non-optional operators. fileprivate func < (lhs: T?, rhs: T?) -> Bool { switch (lhs, rhs) { case let (l?, r?): return l < r case (nil, _?): return true default: return false } } // FIXME: comparison operators with optionals were removed from the Swift Standard Libary. // Consider refactoring the code to use the non-optional operators. fileprivate func >= (lhs: T?, rhs: T?) -> Bool { switch (lhs, rhs) { case let (l?, r?): return l >= r default: return !(lhs < rhs) } } //MARK:- API public let BASEURL = "http://api.zhuishushenqi.com" public let IMAGE_BASEURL = "http://statics.zhuishushenqi.com" public let CHAPTERURL = "http://chapter2.zhuishushenqi.com/chapter" public let BOOKSHELF = "user/bookshelf" public let RANKING = "ranking/gender" // db public let searchHistory = "searchHistory" public let dbName = "QS.zhuishushenqi.searchHistory" //MARK: - 常用frame public let BOUNDS = UIScreen.main.bounds public let ScreenWidth = UIScreen.main.bounds.size.width public let ScreenHeight = UIScreen.main.bounds.size.height public let SCALE = (ScreenWidth / 320.0) public let TOP_BAR_Height = 64 public let FOOT_BAR_Height = 49 public let STATEBARHEIGHT = UIApplication.shared.statusBarFrame.height public let kNavgationBarHeight:CGFloat = (IPHONEX ? 88:64) public let kTabbarBlankHeight = (IPHONEX ? 34:0) public let kQSReaderTopMargin = (IPHONEX ? 30:0) //区分屏幕 public let IPHONE4 = UIScreen.instancesRespond(to: #selector(getter: RunLoop.currentMode)) ? CGSize(width: 640, height: 960).equalTo((UIScreen.main.currentMode?.size)!) : false public let IPHONE5 = UIScreen.instancesRespond(to: #selector(getter: RunLoop.currentMode)) ? CGSize(width: 640, height: 1136).equalTo((UIScreen.main.currentMode?.size)!) : false public let IPHONE6 = UIScreen.instancesRespond(to: #selector(getter: RunLoop.currentMode)) ? CGSize(width: 750, height: 1334).equalTo((UIScreen.main.currentMode?.size)!) : false public let IPHONE6Plus = UIScreen.instancesRespond(to: #selector(getter: RunLoop.currentMode)) ? CGSize(width: 1242, height: 2208).equalTo((UIScreen.main.currentMode?.size)!) : false public let IPHONEX = UIScreen.instancesRespond(to: #selector(getter: RunLoop.currentMode)) ? CGSize(width: 1125, height: 2436).height <= (UIScreen.main.currentMode?.size.height)! : false //根据系统判断 获取iPad的屏幕尺寸 public let IOS9_OR_LATER = (Float(UIDevice.current.systemVersion) >= 9.0) public let IOS8_OR_LATER = (Float(UIDevice.current.systemVersion) >= 8.0) public let IOS7_OR_LATER = (Float(UIDevice.current.systemVersion) >= 7.0) public let USER_DEFAULTS = UserDefaults.standard public let KeyWindow = UIApplication.shared.keyWindow public let ReaderBg = "ReaderBg" public let FontSize = "FontSize" public let OriginalBrightness = "OriginalBrightness" public let Brightness = "Brightness" public let ReadingProgress = "ReadingProgress" public let PostLink = "PostLink" // notification public let SHOW_RECOMMEND = "ShowRecomend" public let BOOKSHELF_REFRESH = "BookShelfRefresh" public let BOOKSHELF_ADD = "BOOKSHELF_ADD" public let BOOKSHELF_DELETE = "BOOKSHELF_DELETE" public let RootDisappearNotificationName = "RootDisappearNotificationName" public func getAttributes(with lineSpave:CGFloat,font:UIFont)->NSDictionary{ let paraStyle = NSMutableParagraphStyle() paraStyle.lineBreakMode = .byCharWrapping paraStyle.alignment = .left paraStyle.lineSpacing = lineSpave paraStyle.hyphenationFactor = 1.0 paraStyle.firstLineHeadIndent = 0.0 paraStyle.paragraphSpacingBefore = 0.0 paraStyle.headIndent = 0 paraStyle.tailIndent = 0 let dict = [NSAttributedString.Key.font:font,NSAttributedString.Key.kern:1.5,NSAttributedString.Key.paragraphStyle:paraStyle] as [NSAttributedString.Key : Any] return dict as NSDictionary } public func attributeText(with lineSpace:CGFloat,text:String,font:UIFont)->NSAttributedString{ let paraStyle = NSMutableParagraphStyle() paraStyle.lineBreakMode = .byCharWrapping paraStyle.alignment = .left paraStyle.hyphenationFactor = 1.0 paraStyle.firstLineHeadIndent = 0.0 paraStyle.paragraphSpacingBefore = 0.0 paraStyle.headIndent = 0 paraStyle.tailIndent = 0 let dict = [NSAttributedString.Key.font:font,NSAttributedString.Key.kern:1.5,NSAttributedString.Key.paragraphStyle:paraStyle] as [NSAttributedString.Key : Any] let attributeStr = NSAttributedString(string: text, attributes: dict) return attributeStr } public func QSLog(_ message:T,fileName:String = #file,lineName:Int = #line,funcName:String = #function){ #if DEBUG print("QSLog:\((fileName as NSString).lastPathComponent)[\(lineName)]\(funcName):\n\(message)\n") #endif } ================================================ FILE: zhuishushenqi/NewVersion/ZSAppConfig/ZSAppConfig/Classes/Reader.swift ================================================ // // ReaderStyle.swift // zhuishushenqi // // Created by yung on 2017/8/9. // Copyright © 2017年 QS. All rights reserved. // import Foundation public enum Reader:Int { case white case yellow case green case blackgreen case pink case sheepskin case violet case water case weekGreen case weekPink case coffee } extension Reader { public var backgroundImage:UIImage { switch self { case .yellow: return #imageLiteral(resourceName: "yellow_mode_bg") case .white: return #imageLiteral(resourceName: "white_mode_bg") case .green: return #imageLiteral(resourceName: "green_mode_bg") case .blackgreen: return #imageLiteral(resourceName: "new_nav_night_normal") case .pink: return #imageLiteral(resourceName: "violet_mode_bg") case .sheepskin: return #imageLiteral(resourceName: "beijing") case .violet: return #imageLiteral(resourceName: "violet_mode_bg") case .water: return #imageLiteral(resourceName: "sheepskin_mode_bg") case .weekPink: return #imageLiteral(resourceName: "violet_mode_bg") case .weekGreen: return #imageLiteral(resourceName: "violet_mode_bg") case .coffee: return #imageLiteral(resourceName: "pf_header_bg") } } public var textColor:UIColor { switch self { case .yellow,.white,.green: return UIColor.black case .blackgreen,.coffee: return UIColor.white default: return UIColor.black } } public var batteryColor:UIColor { switch self { case .yellow,.white,.green: return UIColor.darkGray case .blackgreen,.coffee: return UIColor.white default: return UIColor.darkGray } } } ================================================ FILE: zhuishushenqi/NewVersion/ZSAppConfig/ZSAppConfig/Classes/Theme.swift ================================================ // // Theme.swift // zhuishushenqi // // Created by yung on 2017/8/9. // Copyright © 2017年 QS. All rights reserved. // import Foundation let readerKey = "reader.key" public enum AppTheme { case day case night } extension AppTheme { } ================================================ FILE: zhuishushenqi/NewVersion/ZSAppConfig/ZSAppConfig.podspec ================================================ # # Be sure to run `pod lib lint ZSAppConfig.podspec' to ensure this is a # valid spec before submitting. # # Any lines starting with a # are optional, but their use is encouraged # To learn more about a Podspec see https://guides.cocoapods.org/syntax/podspec.html # Pod::Spec.new do |s| s.name = 'ZSAppConfig' s.version = '1.0.15' s.summary = 'ZSAppConfig.' # This description is used to generate tags and improve search results. # * Think: What does it do? Why did you write it? What is the focus? # * Try to keep it short, snappy and to the point. # * Write the description between the DESC delimiters below. # * Finally, don't worry about the indent, CocoaPods strips it! s.description = <<-DESC zhuishushenqi baseui module. DESC s.homepage = 'https://github.com/zssq/ZSAppConfig' # s.screenshots = 'www.example.com/screenshots_1', 'www.example.com/screenshots_2' s.license = { :type => 'MIT', :file => 'LICENSE' } s.author = { '2252055382@qq.com' => 'norycao' } s.source = { :git => 'https://github.com/zssq/ZSAppConfig.git', :tag => s.version.to_s } # s.social_media_url = 'https://twitter.com/norycao' s.ios.deployment_target = '8.0' s.swift_version = '5.0' s.source_files = 'ZSAppConfig/Classes/**/*' s.resource_bundles = { 'ZSAppConfig' => ['ZSAppConfig/Assets/*.png'] } #s.frameworks = 'UIKit', 'SystemConfiguration', 'Security', 'CoreTelephony', 'CFNetwork', 'CoreGraphics', 'CoreText', 'QuartzCore', 'ImageIO', 'Photos' #s.libraries = 'z', 'sqlite3.0', 'c++' #s.public_header_files = 'Pod/Classes/**/*.h' # s.frameworks = 'UIKit', 'MapKit' end ================================================ FILE: zhuishushenqi/NewVersion/ZSExtension/LICENSE ================================================ Copyright (c) 2019 2252055382@qq.com Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: zhuishushenqi/NewVersion/ZSExtension/ZSExtension/Classes/Alamofire+ZSExtension.swift ================================================ // // Alamofire+ZSExtension.swift // zhuishushenqi // // Created by yung on 2018/7/31. // Copyright © 2018年 QS. All rights reserved. // import Foundation import Alamofire import ZSAppConfig @discardableResult public func zs_get(_ urlStr: String,parameters: Parameters? = nil) -> DataRequest { return zs_get(urlStr, parameters: parameters, nil) } public func zs_post(_ urlStr: String,parameters: Parameters? = nil) -> DataRequest { return request(urlStr, method: .post, parameters: parameters, encoding: URLEncoding.default, headers: nil) } @discardableResult public func zs_post(_ urlStr: String,parameters: Parameters? = nil,_ handler:ZSBaseCallback<[String:Any]>?) -> DataRequest { var headers = SessionManager.defaultHTTPHeaders headers["User-Agent"] = YouShaQiUserAgent let req = request(urlStr, method: .post, parameters: parameters, encoding: URLEncoding.default, headers: headers).responseJSON { (response) in if let data = response.data { if let json = try? JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.allowFragments) as? [String:Any] { handler?(json) } } else { handler?([:]) } } return req } @discardableResult public func zs_put(_ urlStr: String,parameters: Parameters? = nil,_ handler:ZSBaseCallback<[String:Any]>?) -> DataRequest { var headers = SessionManager.defaultHTTPHeaders headers["User-Agent"] = YouShaQiUserAgent let req = request(urlStr, method: .put, parameters: parameters, encoding: URLEncoding.default, headers: headers).responseJSON { (response) in if let data = response.data { if let json = try? JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.allowFragments) as? [String:Any] { handler?(json) } } else { handler?([:]) } } return req } public let YouShaQiUserAgent = "YouShaQi/4.4.4 (iPhone; iOS 12.0; Scale/3.00)" @discardableResult public func zs_get(_ urlStr: String,parameters: Parameters? = nil,_ handler:ZSBaseCallback<[String:Any]>?) -> DataRequest { var headers = SessionManager.defaultHTTPHeaders headers["User-Agent"] = YouShaQiUserAgent let req = request(urlStr, method: .get, parameters: parameters, encoding: URLEncoding.default, headers: headers).responseJSON { (response) in if let data = response.data { if let json = try? JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.allowFragments) as? [String:Any] { handler?(json) } } else { handler?([:]) } } return req } @discardableResult public func zs_delete(_ urlStr: String,parameters: Parameters? = nil,_ handler:ZSBaseCallback<[String:Any]>?) -> DataRequest { var headers = SessionManager.defaultHTTPHeaders headers["User-Agent"] = YouShaQiUserAgent let req = request(urlStr, method: .delete, parameters: parameters, encoding: URLEncoding.default, headers: headers).responseJSON { (response) in if let data = response.data { if let json = try? JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.allowFragments) as? [String:Any] { handler?(json) } } else { handler?([:]) } } return req } public func zs_download(urlString:String, filePath:String, handler:ZSBaseCallback?) { let pathURL = URL(fileURLWithPath: filePath, isDirectory: true) do { try FileManager.default.createDirectory(at: pathURL, withIntermediateDirectories: true, attributes: nil) } catch { print(error) } let fileName = (urlString as NSString).lastPathComponent let fileURL = pathURL.appendingPathComponent(fileName) let destination: DownloadRequest.DownloadFileDestination = { temporaryURL, _ in return (fileURL, [.createIntermediateDirectories, .removePreviousFile]) } download(urlString, to: destination).response { (response) in if let error = response.error { handler?(error) } else { handler?(response.destinationURL) } } } @discardableResult public func zs_download(url:String, parameters: Parameters? = nil,_ handler:ZSBaseCallback<[String:Any]>?) -> DownloadRequest { let destination = DownloadRequest.suggestedDownloadDestination() let downloadRequest = download(url, method: .get, parameters: parameters, encoding: URLEncoding.default, headers: nil, to: destination).response { (response) in QSLog(response.destinationURL) let exist = FileManager.default.fileExists(atPath: response.destinationURL?.path ?? "") if exist { // 字体文件下载成功 handler?(["url":response.destinationURL?.path ?? ""]) } else { handler?(["error":response.error]) } QSLog(response.temporaryURL) QSLog(response.error) QSLog(response.response) } return downloadRequest } public func downloadFile(urlString:String, handler:NetworkHandler?) { let pathURL = URL(fileURLWithPath: "", isDirectory: true) do { try FileManager.default.createDirectory(at: pathURL, withIntermediateDirectories: true, attributes: nil) } catch { print(error) } let fileName = (urlString as NSString).lastPathComponent let fileURL = pathURL.appendingPathComponent(fileName) let destination: DownloadRequest.DownloadFileDestination = { temporaryURL, _ in return (fileURL, [.createIntermediateDirectories, .removePreviousFile]) } download(urlString, to: destination).response { (response) in if let error = response.error { handler?(error) } else { handler?(response.destinationURL) } } } ================================================ FILE: zhuishushenqi/NewVersion/ZSExtension/ZSExtension/Classes/Array+ZSExtension.swift ================================================ // // Array+ZSExtension.swift // zhuishushenqi // // Created by yung on 2018/7/4. // Copyright © 2018年 QS. All rights reserved. // import Foundation extension Array { func find (array: [T], item : T) ->Int? { var index = 0 while(index < array.count) { if(item == array[index]) { return index } index = index + 1 } return nil } // 去重 func filterDuplicates(_ filter: (Element) -> E) -> [Element] { var result = [Element]() for value in self { let key = filter(value) if !result.map({filter($0)}).contains(key) { result.append(value) } } return result } subscript (safe index:Int) -> Element? { return (0..Element? { guard startIndex <= index && index < endIndex else { return nil } return self[index] } } ================================================ FILE: zhuishushenqi/NewVersion/ZSExtension/ZSExtension/Classes/Date+Extension.swift ================================================ // // Date+Extension.swift // zhuishushenqi // // Created by Nory Cao on 2017/3/15. // Copyright © 2017年 QS. All rights reserved. // import Foundation extension Date{ func year()->Int{ let calendar = Calendar.current var dayComponents:DateComponents? if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_8_0 { dayComponents = calendar.dateComponents([.year], from: self) } return dayComponents?.year ?? 0 } func month()->Int { let calendar = Calendar.current var dayComponents:DateComponents? if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_8_0 { dayComponents = calendar.dateComponents([.month], from: self) } return dayComponents?.month ?? 0 } func day()->Int { let calendar = Calendar.current var dayComponents:DateComponents? if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_8_0 { dayComponents = calendar.dateComponents([.day], from: self) } return dayComponents?.day ?? 0 } func hour()->Int { let calendar = Calendar.current var dayComponents:DateComponents? if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_8_0 { dayComponents = calendar.dateComponents([.hour], from: self) } return dayComponents?.hour ?? 0 } func minute()->Int { let calendar = Calendar.current var dayComponents:DateComponents? if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_8_0 { dayComponents = calendar.dateComponents([.minute], from: self) } return dayComponents?.minute ?? 0 } func second()->Int { let calendar = Calendar.current var dayComponents:DateComponents? if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_8_0 { dayComponents = calendar.dateComponents([.second], from: self) } return dayComponents?.second ?? 0 } func days(month:Int)->Int{ var days = 0 switch month { case 1,3,5,7,8,10,12: days = 31 break case 2: days = isLeapYear() ? 29:28 break case 4,6,9,11: days = 30 break default: break } return days } func isLeapYear()->Bool{ let year = self.year() if (year % 4 == 0 && year % 100 != 0) || year % 400 == 0 { return true } return false } static func timeInterval(from formDate:Date?,to toDate:Date?)->TimeInterval{ if formDate == nil || toDate == nil { return 0 } let dateFormat = DateFormatter() dateFormat.dateFormat = "yyyy-MM-dd hh-mm-ss" let beginTime = formDate?.timeIntervalSince1970 let endTime = toDate?.timeIntervalSince1970 let resultTime = endTime! - beginTime! return resultTime } // + (NSUInteger)daysInMonth:(NSDate *)date month:(NSUInteger)month { // switch (month) { // case 1: case 3: case 5: case 7: case 8: case 10: case 12: // return 31; // case 2: // return [date isLeapYear] ? 29 : 28; // } // return 30; // } } ================================================ FILE: zhuishushenqi/NewVersion/ZSExtension/ZSExtension/Classes/DateIntervalFormatter+formatter.swift ================================================ // // DateIntervalFormatter+formatter.swift // zhuishushenqi // // Created by Nory Cao on 2017/3/15. // Copyright © 2017年 QS. All rights reserved. // import Foundation extension DateIntervalFormatter{ func formatter(begin:Date?,end:Date?)->TimeInterval{ if begin == nil || end == nil { return 0 } let dateFormat = DateFormatter() dateFormat.dateFormat = "yyyy-MM-dd hh-mm-ss" let beginTime = begin?.timeIntervalSince1970 let endTime = end?.timeIntervalSince1970 if let beginTimeInterval = beginTime,let endTimeInterval = endTime{ let minus = endTimeInterval - beginTimeInterval return minus } return 0 } //Just something func timeInfo(from:Date,to:Date)->String{ let year = to.year() - from.year() let month = to.month() - from.month() let day = to.day() - from.day() var retTime = 1.0 let timeInterval = formatter(begin: from, end: to) if timeInterval < 3600.0 { //小于一小时 retTime = timeInterval / 60.0 retTime = retTime <= 0.0 ? 1.0 : retTime return String(format: "%.0f分钟前",retTime) }else if timeInterval < 3600*24 { //小于一天,也就是今天 retTime = timeInterval / 3600.0 retTime = retTime <= 0.0 ? 1.0 : retTime return String(format: "%.0f小时前",retTime) }else if abs(year) == 0 && abs(month) <= 1 || abs(year) == 1 && to.month() == 1 && from.month() == 12{ // 第一个条件是同年,且相隔时间在一个月内 // 第二个条件是隔年,且相隔时间在一个月内,对于隔年,只能是去年12月与今年1月这种情况 var retDay = 0 if year == 0 {//同年 if month == 0 {//同月 retDay = day } } //跨月 if retDay <= 0{ // 获取发布日期中,该月有多少天 let totalDays = from.days(month: from.month()) // 当前天数 + (发布日期月中的总天数-发布日期月中发布日,即等于距离今天的天数) retDay = to.day() + totalDays - from.day() } return String(format: "%d天前",abs(retDay)) }else {//间隔时间大于一个月 if abs(year) <= 1 { if year == 0 {//同年 return String(format: "%d个月前", (month)) } else {//跨年计算月份 //跨年可能月份大于12个月,按一年来算 let monthCount = 12 - from.month() + to.month() if monthCount > 12 { return String(format: "%d年前",monthCount/12) } return String(format: "%d个月前",monthCount) } } if month < 0 { //未满一年不计算 return String(format: "%d年前",abs(year) - 1) } return String(format: "%d年前",abs(year)) } } } ================================================ FILE: zhuishushenqi/NewVersion/ZSExtension/ZSExtension/Classes/Dictionary+QSExtension.swift ================================================ // // Dictionary.swift // zhuishushenqi // // Created by yung on 2018/2/11. // Copyright © 2018年 QS. All rights reserved. // import Foundation extension Dictionary { public func valueForKey(key:String) ->Any?{ let dict = self as NSDictionary let value = dict[key] return value } public func key(at index:Int) ->String{ let dict = self as NSDictionary let keys = dict.allKeys let key = keys[index] as? String return key ?? "" } public func value(at index:Int) ->Any?{ let dict = self as NSDictionary let values = dict.allValues let value = values[index] return value } public func allKeys()->[String]{ let dict = self as NSDictionary let allKeys = dict.allKeys as! [String] return allKeys } public func allValues()->[Any]{ let dict = self as NSDictionary let allValues = dict.allValues as! [Any] return allValues } //MARK: - transform var toJSON: String { if !JSONSerialization.isValidJSONObject(self) { return "" } if let data = try? JSONSerialization.data(withJSONObject: self, options: JSONSerialization.WritingOptions.init(rawValue: 0)) { if let jsonString = String(data: data, encoding: .utf8) { return jsonString } } return "" } } extension Dictionary where Value: Any { func decoding(with key: Key) -> T? { guard let any: Any = self[key] else { return nil } if let value: T = any as? T { return value } else { switch T.self { case is String.Type: switch any { case let someInt as Int: return String(someInt) as? T case let someDouble as Double: return String(someDouble) as? T case let someBool as Bool: return String(someBool) as? T default: return nil } case is Int.Type: if let someString: String = any as? String { return Int(someString) as? T } else if let someDouble: Double = any as? Double { return Int(someDouble) as? T } else { return nil } case is Double.Type: if let someString: String = any as? String { return Double(someString) as? T } else if let someInt: Int = any as? Int { return Double(someInt) as? T } else { return nil } case is Bool.Type: if let someString: String = any as? String { return Bool(someString) as? T } else { return nil } default: return nil } } } } ================================================ FILE: zhuishushenqi/NewVersion/ZSExtension/ZSExtension/Classes/DispatchTime+Extension.swift ================================================ // // DispatchTime+Extension.swift // zhuishushenqi // // Created by caony on 2019/6/20. // Copyright © 2019年 QS. All rights reserved. // import Foundation //extension DispatchTime: ExpressibleByIntegerLiteral { // public init(integerLiteral value: Int) { // self = DispatchTime.now() + .seconds(value) // } //} // //extension DispatchTime: ExpressibleByFloatLiteral { // public init(floatLiteral value: Double) { // self = DispatchTime.now() + .milliseconds(Int(value * 1000)) // } //} ================================================ FILE: zhuishushenqi/NewVersion/ZSExtension/ZSExtension/Classes/LocalizedUtils.swift ================================================ import Foundation extension String { var localized: String { return NSLocalizedString(self, comment: self) } /* Localizable.strings zhuishushenqi Created by yung on 2017/8/10. Copyright © 2017年 QS. All rights reserved. */ static var localized_login: String { return "Login".localized } static var localized_logout: String { return "Logout".localized } } ================================================ FILE: zhuishushenqi/NewVersion/ZSExtension/ZSExtension/Classes/NSData+Base64.h ================================================ // // NSData+Base64.h // zhuishushenqi // // Created by yung on 2018/11/9. // Copyright © 2018年 QS. All rights reserved. // #import void *NewBase64Decode( const char *inputBuffer, size_t length, size_t *outputLength); char *NewBase64Encode( const void *inputBuffer, size_t length, bool separateLines, size_t *outputLength); @interface NSData (Base64) + (NSData *)dataFromBase64String:(NSString *)aString; - (NSString *)base64EncodedString; // added by Hiroshi Hashiguchi - (NSString *)base64EncodedStringWithSeparateLines:(BOOL)separateLines; @end ================================================ FILE: zhuishushenqi/NewVersion/ZSExtension/ZSExtension/Classes/NSData+Base64.m ================================================ // // NSData+Base64.m // zhuishushenqi // // Created by yung on 2018/11/9. // Copyright © 2018年 QS. All rights reserved. // #import "NSData+Base64.h" // // Mapping from 6 bit pattern to ASCII character. // static unsigned char base64EncodeLookup[65] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; // // Definition for "masked-out" areas of the base64DecodeLookup mapping // #define xx 65 // // Mapping from ASCII character to 6 bit pattern. // static unsigned char base64DecodeLookup[256] = { xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, 62, xx, xx, xx, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, xx, xx, xx, xx, xx, xx, xx, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, xx, xx, xx, xx, xx, xx, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, }; // // Fundamental sizes of the binary and base64 encode/decode units in bytes // #define BINARY_UNIT_SIZE 3 #define BASE64_UNIT_SIZE 4 // // NewBase64Decode // // Decodes the base64 ASCII string in the inputBuffer to a newly malloced // output buffer. // // inputBuffer - the source ASCII string for the decode // length - the length of the string or -1 (to specify strlen should be used) // outputLength - if not-NULL, on output will contain the decoded length // // returns the decoded buffer. Must be free'd by caller. Length is given by // outputLength. // void *NewBase64Decode( const char *inputBuffer, size_t length, size_t *outputLength) { if (length == -1) { length = strlen(inputBuffer); } size_t outputBufferSize = ((length+BASE64_UNIT_SIZE-1) / BASE64_UNIT_SIZE) * BINARY_UNIT_SIZE; unsigned char *outputBuffer = (unsigned char *)malloc(outputBufferSize); size_t i = 0; size_t j = 0; while (i < length) { // // Accumulate 4 valid characters (ignore everything else) // unsigned char accumulated[BASE64_UNIT_SIZE]; size_t accumulateIndex = 0; while (i < length) { unsigned char decode = base64DecodeLookup[inputBuffer[i++]]; if (decode != xx) { accumulated[accumulateIndex] = decode; accumulateIndex++; if (accumulateIndex == BASE64_UNIT_SIZE) { break; } } } // // Store the 6 bits from each of the 4 characters as 3 bytes // // (Uses improved bounds checking suggested by Alexandre Colucci) // if(accumulateIndex >= 2) outputBuffer[j] = (accumulated[0] << 2) | (accumulated[1] >> 4); if(accumulateIndex >= 3) outputBuffer[j + 1] = (accumulated[1] << 4) | (accumulated[2] >> 2); if(accumulateIndex >= 4) outputBuffer[j + 2] = (accumulated[2] << 6) | accumulated[3]; j += accumulateIndex - 1; } if (outputLength) { *outputLength = j; } return outputBuffer; } // // NewBase64Encode // // Encodes the arbitrary data in the inputBuffer as base64 into a newly malloced // output buffer. // // inputBuffer - the source data for the encode // length - the length of the input in bytes // separateLines - if zero, no CR/LF characters will be added. Otherwise // a CR/LF pair will be added every 64 encoded chars. // outputLength - if not-NULL, on output will contain the encoded length // (not including terminating 0 char) // // returns the encoded buffer. Must be free'd by caller. Length is given by // outputLength. // char *NewBase64Encode( const void *buffer, size_t length, bool separateLines, size_t *outputLength) { const unsigned char *inputBuffer = (const unsigned char *)buffer; #define MAX_NUM_PADDING_CHARS 2 #define OUTPUT_LINE_LENGTH 64 #define INPUT_LINE_LENGTH ((OUTPUT_LINE_LENGTH / BASE64_UNIT_SIZE) * BINARY_UNIT_SIZE) #define CR_LF_SIZE 2 // // Byte accurate calculation of final buffer size // size_t outputBufferSize = ((length / BINARY_UNIT_SIZE) + ((length % BINARY_UNIT_SIZE) ? 1 : 0)) * BASE64_UNIT_SIZE; if (separateLines) { outputBufferSize += (outputBufferSize / OUTPUT_LINE_LENGTH) * CR_LF_SIZE; } // // Include space for a terminating zero // outputBufferSize += 1; // // Allocate the output buffer // char *outputBuffer = (char *)malloc(outputBufferSize); if (!outputBuffer) { return NULL; } size_t i = 0; size_t j = 0; const size_t lineLength = separateLines ? INPUT_LINE_LENGTH : length; size_t lineEnd = lineLength; while (true) { if (lineEnd > length) { lineEnd = length; } for (; i + BINARY_UNIT_SIZE - 1 < lineEnd; i += BINARY_UNIT_SIZE) { // // Inner loop: turn 48 bytes into 64 base64 characters // outputBuffer[j++] = base64EncodeLookup[(inputBuffer[i] & 0xFC) >> 2]; outputBuffer[j++] = base64EncodeLookup[((inputBuffer[i] & 0x03) << 4) | ((inputBuffer[i + 1] & 0xF0) >> 4)]; outputBuffer[j++] = base64EncodeLookup[((inputBuffer[i + 1] & 0x0F) << 2) | ((inputBuffer[i + 2] & 0xC0) >> 6)]; outputBuffer[j++] = base64EncodeLookup[inputBuffer[i + 2] & 0x3F]; } if (lineEnd == length) { break; } // // Add the newline // // outputBuffer[j++] = '\r'; // outputBuffer[j++] = '\n'; lineEnd += lineLength; } if (i + 1 < length) { // // Handle the single '=' case // outputBuffer[j++] = base64EncodeLookup[(inputBuffer[i] & 0xFC) >> 2]; outputBuffer[j++] = base64EncodeLookup[((inputBuffer[i] & 0x03) << 4) | ((inputBuffer[i + 1] & 0xF0) >> 4)]; outputBuffer[j++] = base64EncodeLookup[(inputBuffer[i + 1] & 0x0F) << 2]; outputBuffer[j++] = '='; } else if (i < length) { // // Handle the double '=' case // outputBuffer[j++] = base64EncodeLookup[(inputBuffer[i] & 0xFC) >> 2]; outputBuffer[j++] = base64EncodeLookup[(inputBuffer[i] & 0x03) << 4]; outputBuffer[j++] = '='; outputBuffer[j++] = '='; } outputBuffer[j] = 0; // // Set the output length and return the buffer // if (outputLength) { *outputLength = j; } return outputBuffer; } @implementation NSData (Base64) // // dataFromBase64String: // // Creates an NSData object containing the base64 decoded representation of // the base64 string 'aString' // // Parameters: // aString - the base64 string to decode // // returns the autoreleased NSData representation of the base64 string // + (NSData *)dataFromBase64String:(NSString *)aString { NSData *data = [aString dataUsingEncoding:NSASCIIStringEncoding]; size_t outputLength; void *outputBuffer = NewBase64Decode([data bytes], [data length], &outputLength); NSData *result = [NSData dataWithBytes:outputBuffer length:outputLength]; free(outputBuffer); return result; } // // base64EncodedString // // Creates an NSString object that contains the base 64 encoding of the // receiver's data. Lines are broken at 64 characters long. // // returns an autoreleased NSString being the base 64 representation of the // receiver. // - (NSString *)base64EncodedString { size_t outputLength; char *outputBuffer = NewBase64Encode([self bytes], [self length], true, &outputLength); NSString *result = [[NSString alloc] initWithBytes:outputBuffer length:outputLength encoding:NSASCIIStringEncoding]; free(outputBuffer); return result; } // added by Hiroshi Hashiguchi - (NSString *)base64EncodedStringWithSeparateLines:(BOOL)separateLines { size_t outputLength; char *outputBuffer = NewBase64Encode([self bytes], [self length], separateLines, &outputLength); NSString *result = [[NSString alloc] initWithBytes:outputBuffer length:outputLength encoding:NSASCIIStringEncoding]; free(outputBuffer); return result; } @end ================================================ FILE: zhuishushenqi/NewVersion/ZSExtension/ZSExtension/Classes/NSDate+Extension.h ================================================ // // NSDate+Extension.h // iOS-Categories (https://github.com/shaojiankui/iOS-Categories) // // Created by Jakey on 15/4/25. // Copyright (c) 2015年 www.skyfox.org. All rights reserved. // #import @interface NSDate (Extension) /** * 获取日、月、年、小时、分钟、秒 */ - (NSUInteger)day; - (NSUInteger)month; - (NSUInteger)year; - (NSUInteger)hour; - (NSUInteger)minute; - (NSUInteger)second; + (NSUInteger)day:(NSDate *)date; + (NSUInteger)month:(NSDate *)date; + (NSUInteger)year:(NSDate *)date; + (NSUInteger)hour:(NSDate *)date; + (NSUInteger)minute:(NSDate *)date; + (NSUInteger)second:(NSDate *)date; /** * 获取一年中的总天数 */ - (NSUInteger)daysInYear; + (NSUInteger)daysInYear:(NSDate *)date; /** * 判断是否是润年 * @return YES表示润年,NO表示平年 */ - (BOOL)isLeapYear; + (BOOL)isLeapYear:(NSDate *)date; /** * 获取该日期是该年的第几周 */ - (NSUInteger)weekOfYear; + (NSUInteger)weekOfYear:(NSDate *)date; /** * 获取格式化为YYYY-MM-dd格式的日期字符串 */ - (NSString *)formatYMD; + (NSString *)formatYMD:(NSDate *)date; /** * 返回当前月一共有几周(可能为4,5,6) */ - (NSUInteger)weeksOfMonth; + (NSUInteger)weeksOfMonth:(NSDate *)date; /** * 获取该月的第一天的日期 */ - (NSDate *)begindayOfMonth; + (NSDate *)begindayOfMonth:(NSDate *)date; /** * 获取该月的最后一天的日期 */ - (NSDate *)lastdayOfMonth; + (NSDate *)lastdayOfMonth:(NSDate *)date; /** * 返回day天后的日期(若day为负数,则为|day|天前的日期) */ - (NSDate *)dateAfterDay:(NSUInteger)day; + (NSDate *)dateAfterDate:(NSDate *)date day:(NSInteger)day; /** * 返回day天后的日期(若day为负数,则为|day|天前的日期) */ - (NSDate *)dateAfterMonth:(NSUInteger)month; + (NSDate *)dateAfterDate:(NSDate *)date month:(NSInteger)month; /** * 返回numYears年后的日期 */ - (NSDate *)offsetYears:(int)numYears; + (NSDate *)offsetYears:(int)numYears fromDate:(NSDate *)fromDate; /** * 返回numMonths月后的日期 */ - (NSDate *)offsetMonths:(int)numMonths; + (NSDate *)offsetMonths:(int)numMonths fromDate:(NSDate *)fromDate; /** * 返回numDays天后的日期 */ - (NSDate *)offsetDays:(int)numDays; + (NSDate *)offsetDays:(int)numDays fromDate:(NSDate *)fromDate; /** * 返回numHours小时后的日期 */ - (NSDate *)offsetHours:(int)hours; + (NSDate *)offsetHours:(int)numHours fromDate:(NSDate *)fromDate; /** * 距离该日期前几天 */ - (NSUInteger)daysAgo; + (NSUInteger)daysAgo:(NSDate *)date; /** * 获取星期几 * * @return Return weekday number * [1 - Sunday] * [2 - Monday] * [3 - Tuerday] * [4 - Wednesday] * [5 - Thursday] * [6 - Friday] * [7 - Saturday] */ - (NSInteger)weekday; + (NSInteger)weekday:(NSDate *)date; /** * 获取星期几(名称) * * @return Return weekday as a localized string * [1 - Sunday] * [2 - Monday] * [3 - Tuerday] * [4 - Wednesday] * [5 - Thursday] * [6 - Friday] * [7 - Saturday] */ - (NSString *)dayFromWeekday; + (NSString *)dayFromWeekday:(NSDate *)date; /** * 日期是否相等 * * @param anotherDate The another date to compare as NSDate * @return Return YES if is same day, NO if not */ - (BOOL)isSameDay:(NSDate *)anotherDate; /** * 是否是今天 * * @return Return if self is today */ - (BOOL)isToday; /** * Add days to self * * @param days The number of days to add * @return Return self by adding the gived days number */ - (NSDate *)dateByAddingDays:(NSUInteger)days; /** * Get the month as a localized string from the given month number * * @param month The month to be converted in string * [1 - January] * [2 - February] * [3 - March] * [4 - April] * [5 - May] * [6 - June] * [7 - July] * [8 - August] * [9 - September] * [10 - October] * [11 - November] * [12 - December] * * @return Return the given month as a localized string */ + (NSString *)monthWithMonthNumber:(NSInteger)month; /** * 根据日期返回字符串 */ + (NSString *)stringWithDate:(NSDate *)date format:(NSString *)format; - (NSString *)stringWithFormat:(NSString *)format; + (NSDate *)dateWithString:(NSString *)string format:(NSString *)format; /** * 获取指定月份的天数 */ - (NSUInteger)daysInMonth:(NSUInteger)month; + (NSUInteger)daysInMonth:(NSDate *)date month:(NSUInteger)month; /** * 获取当前月份的天数 */ - (NSUInteger)daysInMonth; + (NSUInteger)daysInMonth:(NSDate *)date; /** * 返回x分钟前/x小时前/昨天/x天前/x个月前/x年前 */ - (NSString *)timeInfo; + (NSString *)timeInfoWithDate:(NSDate *)date; + (NSString *)timeInfoWithDateString:(NSString *)dateString; /** * 分别获取yyyy-MM-dd/HH:mm:ss/yyyy-MM-dd HH:mm:ss格式的字符串 */ - (NSString *)ymdFormat; - (NSString *)hmsFormat; - (NSString *)ymdHmsFormat; + (NSString *)ymdFormat; + (NSString *)hmsFormat; + (NSString *)ymdHmsFormat; + (NSDate*)getDateWithYear:(NSString *)year month:(NSString *)month day:(NSString *)day hour:(NSString *)hour mimute:(NSString *)minute second:(NSString*)second; @end ================================================ FILE: zhuishushenqi/NewVersion/ZSExtension/ZSExtension/Classes/NSDate+Extension.m ================================================ // // NSDate+Extension.m // iOS-Categories (https://github.com/shaojiankui/iOS-Categories) // // Created by Jakey on 15/4/25. // Copyright (c) 2015年 www.skyfox.org. All rights reserved. // https://github.com/632840804/HYBNSDateExtension #import "NSDate+Extension.h" @implementation NSDate (Extension) - (NSUInteger)day { return [NSDate day:self]; } - (NSUInteger)month { return [NSDate month:self]; } - (NSUInteger)year { return [NSDate year:self]; } - (NSUInteger)hour { return [NSDate hour:self]; } - (NSUInteger)minute { return [NSDate minute:self]; } - (NSUInteger)second { return [NSDate second:self]; } + (NSUInteger)day:(NSDate *)date { NSCalendar *calendar = [NSCalendar currentCalendar]; #if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_8_0 // NSDayCalendarUnit NSDateComponents *dayComponents = [calendar components:(NSCalendarUnitDay) fromDate:date]; #else NSDateComponents *dayComponents = [calendar components:(NSDayCalendarUnit) fromDate:date]; #endif return [dayComponents day]; } + (NSUInteger)month:(NSDate *)date { NSCalendar *calendar = [NSCalendar currentCalendar]; #if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_8_0 // NSDayCalendarUnit NSDateComponents *dayComponents = [calendar components:(NSCalendarUnitMonth) fromDate:date]; #else NSDateComponents *dayComponents = [calendar components:(NSMonthCalendarUnit) fromDate:date]; #endif return [dayComponents month]; } + (NSUInteger)year:(NSDate *)date { NSCalendar *calendar = [NSCalendar currentCalendar]; #if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_8_0 // NSDayCalendarUnit NSDateComponents *dayComponents = [calendar components:(NSCalendarUnitYear) fromDate:date]; #else NSDateComponents *dayComponents = [calendar components:(NSYearCalendarUnit) fromDate:date]; #endif return [dayComponents year]; } + (NSUInteger)hour:(NSDate *)date { NSCalendar *calendar = [NSCalendar currentCalendar]; #if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_8_0 // NSDayCalendarUnit NSDateComponents *dayComponents = [calendar components:(NSCalendarUnitHour) fromDate:date]; #else NSDateComponents *dayComponents = [calendar components:(NSHourCalendarUnit) fromDate:date]; #endif return [dayComponents hour]; } + (NSUInteger)minute:(NSDate *)date { NSCalendar *calendar = [NSCalendar currentCalendar]; #if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_8_0 // NSDayCalendarUnit NSDateComponents *dayComponents = [calendar components:(NSCalendarUnitMinute) fromDate:date]; #else NSDateComponents *dayComponents = [calendar components:(NSMinuteCalendarUnit) fromDate:date]; #endif return [dayComponents minute]; } + (NSUInteger)second:(NSDate *)date { NSCalendar *calendar = [NSCalendar currentCalendar]; #if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_8_0 // NSDayCalendarUnit NSDateComponents *dayComponents = [calendar components:(NSCalendarUnitSecond) fromDate:date]; #else NSDateComponents *dayComponents = [calendar components:(NSSecondCalendarUnit) fromDate:date]; #endif return [dayComponents second]; } - (NSUInteger)daysInYear { return [NSDate daysInYear:self]; } + (NSUInteger)daysInYear:(NSDate *)date { return [self isLeapYear:date] ? 366 : 365; } - (BOOL)isLeapYear { return [NSDate isLeapYear:self]; } + (BOOL)isLeapYear:(NSDate *)date { NSUInteger year = [date year]; if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0) { return YES; } return NO; } - (NSString *)formatYMD { return [NSDate formatYMD:self]; } + (NSString *)formatYMD:(NSDate *)date { return [NSString stringWithFormat:@"%lu-%02lu-%02lu",[date year],[date month], [date day]]; } - (NSUInteger)weeksOfMonth { return [NSDate weeksOfMonth:self]; } + (NSUInteger)weeksOfMonth:(NSDate *)date { return [[date lastdayOfMonth] weekOfYear] - [[date begindayOfMonth] weekOfYear] + 1; } - (NSUInteger)weekOfYear { return [NSDate weekOfYear:self]; } + (NSUInteger)weekOfYear:(NSDate *)date { NSUInteger i; NSUInteger year = [date year]; NSDate *lastdate = [date lastdayOfMonth]; for (i = 1;[[lastdate dateAfterDay:-7 * i] year] == year; i++) { } return i; } - (NSDate *)dateAfterDay:(NSUInteger)day { return [NSDate dateAfterDate:self day:day]; } + (NSDate *)dateAfterDate:(NSDate *)date day:(NSInteger)day { NSCalendar *calendar = [NSCalendar currentCalendar]; NSDateComponents *componentsToAdd = [[NSDateComponents alloc] init]; [componentsToAdd setDay:day]; NSDate *dateAfterDay = [calendar dateByAddingComponents:componentsToAdd toDate:date options:0]; return dateAfterDay; } - (NSDate *)dateAfterMonth:(NSUInteger)month { return [NSDate dateAfterDate:self month:month]; } + (NSDate *)dateAfterDate:(NSDate *)date month:(NSInteger)month { NSCalendar *calendar = [NSCalendar currentCalendar]; NSDateComponents *componentsToAdd = [[NSDateComponents alloc] init]; [componentsToAdd setMonth:month]; NSDate *dateAfterMonth = [calendar dateByAddingComponents:componentsToAdd toDate:date options:0]; return dateAfterMonth; } - (NSDate *)begindayOfMonth { return [NSDate begindayOfMonth:self]; } + (NSDate *)begindayOfMonth:(NSDate *)date { return [self dateAfterDate:date day:-[date day] + 1]; } - (NSDate *)lastdayOfMonth { return [NSDate lastdayOfMonth:self]; } + (NSDate *)lastdayOfMonth:(NSDate *)date { NSDate *lastDate = [self begindayOfMonth:date]; return [[lastDate dateAfterMonth:1] dateAfterDay:-1]; } - (NSUInteger)daysAgo { return [NSDate daysAgo:self]; } + (NSUInteger)daysAgo:(NSDate *)date { NSCalendar *calendar = [NSCalendar currentCalendar]; #if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_8_0 NSDateComponents *components = [calendar components:(NSCalendarUnitDay) fromDate:date toDate:[NSDate date] options:0]; #else NSDateComponents *components = [calendar components:(NSDayCalendarUnit) fromDate:date toDate:[NSDate date] options:0]; #endif return [components day]; } - (NSInteger)weekday { return [NSDate weekday:self]; } + (NSInteger)weekday:(NSDate *)date { NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian]; NSDateComponents *comps = [gregorian components:(NSCalendarUnitDay | NSCalendarUnitMonth | NSCalendarUnitYear | NSCalendarUnitWeekday) fromDate:date]; NSInteger weekday = [comps weekday]; return weekday; } - (NSString *)dayFromWeekday { return [NSDate dayFromWeekday:self]; } + (NSString *)dayFromWeekday:(NSDate *)date { switch([date weekday]) { case 1: return @"星期天"; break; case 2: return @"星期一"; break; case 3: return @"星期二"; break; case 4: return @"星期三"; break; case 5: return @"星期四"; break; case 6: return @"星期五"; break; case 7: return @"星期六"; break; default: break; } return @""; } - (BOOL)isSameDay:(NSDate *)anotherDate { NSCalendar *calendar = [NSCalendar currentCalendar]; NSDateComponents *components1 = [calendar components:(NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay) fromDate:self]; NSDateComponents *components2 = [calendar components:(NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay) fromDate:anotherDate]; return ([components1 year] == [components2 year] && [components1 month] == [components2 month] && [components1 day] == [components2 day]); } - (BOOL)isToday { return [self isSameDay:[NSDate date]]; } - (NSDate *)dateByAddingDays:(NSUInteger)days { NSDateComponents *c = [[NSDateComponents alloc] init]; c.day = days; return [[NSCalendar currentCalendar] dateByAddingComponents:c toDate:self options:0]; } /** * Get the month as a localized string from the given month number * * @param month The month to be converted in string * [1 - January] * [2 - February] * [3 - March] * [4 - April] * [5 - May] * [6 - June] * [7 - July] * [8 - August] * [9 - September] * [10 - October] * [11 - November] * [12 - December] * * @return Return the given month as a localized string */ + (NSString *)monthWithMonthNumber:(NSInteger)month { switch(month) { case 1: return @"January"; break; case 2: return @"February"; break; case 3: return @"March"; break; case 4: return @"April"; break; case 5: return @"May"; break; case 6: return @"June"; break; case 7: return @"July"; break; case 8: return @"August"; break; case 9: return @"September"; break; case 10: return @"October"; break; case 11: return @"November"; break; case 12: return @"December"; break; default: break; } return @""; } + (NSString *)stringWithDate:(NSDate *)date format:(NSString *)format { return [date stringWithFormat:format]; } - (NSString *)stringWithFormat:(NSString *)format { NSDateFormatter *outputFormatter = [[NSDateFormatter alloc] init]; [outputFormatter setDateFormat:format]; NSString *retStr = [outputFormatter stringFromDate:self]; return retStr; } + (NSDate *)dateWithString:(NSString *)string format:(NSString *)format { NSDateFormatter *inputFormatter = [[NSDateFormatter alloc] init]; [inputFormatter setDateFormat:format]; NSDate *date = [inputFormatter dateFromString:string]; return date; } - (NSUInteger)daysInMonth:(NSUInteger)month { return [NSDate daysInMonth:self month:month]; } + (NSUInteger)daysInMonth:(NSDate *)date month:(NSUInteger)month { switch (month) { case 1: case 3: case 5: case 7: case 8: case 10: case 12: return 31; case 2: return [date isLeapYear] ? 29 : 28; } return 30; } - (NSUInteger)daysInMonth { return [NSDate daysInMonth:self]; } + (NSUInteger)daysInMonth:(NSDate *)date { return [self daysInMonth:date month:[date month]]; } - (NSString *)timeInfo { return [NSDate timeInfoWithDate:self]; } + (NSString *)timeInfoWithDate:(NSDate *)date { return [self timeInfoWithDateString:[self stringWithDate:date format:[self ymdHmsFormat]]]; } + (NSString *)timeInfoWithDateString:(NSString *)dateString { NSDate *date = [self dateWithString:dateString format:[self ymdHmsFormat]]; NSDate *curDate = [NSDate date]; NSTimeInterval time = -[date timeIntervalSinceDate:curDate]; int month = (int)([curDate month] - [date month]); int year = (int)([curDate year] - [date year]); int day = (int)([curDate day] - [date day]); NSTimeInterval retTime = 1.0; if (time < 3600) { // 小于一小时 retTime = time / 60; retTime = retTime <= 0.0 ? 1.0 : retTime; return [NSString stringWithFormat:@"%.0f分钟前", retTime]; } else if (time < 3600 * 24) { // 小于一天,也就是今天 retTime = time / 3600; retTime = retTime <= 0.0 ? 1.0 : retTime; return [NSString stringWithFormat:@"%.0f小时前", retTime]; } else if (time < 3600 * 24 * 2) { return @"昨天"; } // 第一个条件是同年,且相隔时间在一个月内 // 第二个条件是隔年,对于隔年,只能是去年12月与今年1月这种情况 else if ((abs(year) == 0 && abs(month) <= 1) || (abs(year) == 1 && [curDate month] == 1 && [date month] == 12)) { int retDay = 0; if (year == 0) { // 同年 if (month == 0) { // 同月 retDay = day; } } if (retDay <= 0) { // 获取发布日期中,该月有多少天 int totalDays = (int)[self daysInMonth:date month:[date month]]; // 当前天数 + (发布日期月中的总天数-发布日期月中发布日,即等于距离今天的天数) retDay = (int)[curDate day] + (totalDays - (int)[date day]); } return [NSString stringWithFormat:@"%d天前", (abs)(retDay)]; } else { if (abs(year) <= 1) { if (year == 0) { // 同年 return [NSString stringWithFormat:@"%d个月前", abs(month)]; } // 隔年 int month = (int)[curDate month]; int preMonth = (int)[date month]; if (month == 12 && preMonth == 12) {// 隔年,但同月,就作为满一年来计算 return @"1年前"; } return [NSString stringWithFormat:@"%d个月前", (abs)(12 - preMonth + month)]; } return [NSString stringWithFormat:@"%d年前", abs(year)]; } return @"1小时前"; } - (NSString *)ymdFormat { return [NSDate ymdFormat]; } - (NSString *)hmsFormat { return [NSDate hmsFormat]; } - (NSString *)ymdHmsFormat { return [NSDate ymdHmsFormat]; } + (NSString *)ymdFormat { return @"yyyy-MM-dd"; } + (NSString *)hmsFormat { return @"HH:mm:ss"; } + (NSString *)ymdHmsFormat { return [NSString stringWithFormat:@"%@ %@", [self ymdFormat], [self hmsFormat]]; } - (NSDate *)offsetYears:(int)numYears { return [NSDate offsetYears:numYears fromDate:self]; } + (NSDate *)offsetYears:(int)numYears fromDate:(NSDate *)fromDate { if (fromDate == nil) { return nil; } #if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_8_0 // NSDayCalendarUnit NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian]; #else NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar]; #endif NSDateComponents *offsetComponents = [[NSDateComponents alloc] init]; [offsetComponents setYear:numYears]; return [gregorian dateByAddingComponents:offsetComponents toDate:fromDate options:0]; } - (NSDate *)offsetMonths:(int)numMonths { return [NSDate offsetMonths:numMonths fromDate:self]; } + (NSDate *)offsetMonths:(int)numMonths fromDate:(NSDate *)fromDate { if (fromDate == nil) { return nil; } #if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_8_0 // NSDayCalendarUnit NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian]; #else NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar]; #endif NSDateComponents *offsetComponents = [[NSDateComponents alloc] init]; [offsetComponents setMonth:numMonths]; return [gregorian dateByAddingComponents:offsetComponents toDate:fromDate options:0]; } - (NSDate *)offsetDays:(int)numDays { return [NSDate offsetDays:numDays fromDate:self]; } + (NSDate *)offsetDays:(int)numDays fromDate:(NSDate *)fromDate { if (fromDate == nil) { return nil; } #if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_8_0 // NSDayCalendarUnit NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian]; #else NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar]; #endif NSDateComponents *offsetComponents = [[NSDateComponents alloc] init]; [offsetComponents setDay:numDays]; return [gregorian dateByAddingComponents:offsetComponents toDate:fromDate options:0]; } - (NSDate *)offsetHours:(int)hours { return [NSDate offsetHours:hours fromDate:self]; } + (NSDate *)offsetHours:(int)numHours fromDate:(NSDate *)fromDate { if (fromDate == nil) { return nil; } #if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_8_0 // NSDayCalendarUnit NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian]; #else NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar]; #endif NSDateComponents *offsetComponents = [[NSDateComponents alloc] init]; [offsetComponents setHour:numHours]; return [gregorian dateByAddingComponents:offsetComponents toDate:fromDate options:0]; } + (NSDate*)getDateWithYear:(NSString *)year month:(NSString *)month day:(NSString *)day hour:(NSString *)hour mimute:(NSString *)minute second:(NSString*)second { NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian]; NSDateComponents *comps = [[NSDateComponents alloc] init]; [comps setYear:[year integerValue]]; [comps setMonth:[month integerValue]]; [comps setDay:[day integerValue]]; [comps setHour:[hour integerValue]]; [comps setMinute:[minute integerValue]]; [comps setSecond:[second integerValue]]; NSDate *date = [calendar dateFromComponents:comps]; NSTimeInterval timeInterval = [self offsetTimeIntervalFromCurrentTimeZoneForDate:date]; return [NSDate dateWithTimeInterval:timeInterval sinceDate:date]; } + (NSTimeInterval)offsetTimeIntervalFromCurrentTimeZoneForDate:(NSDate *)anyDate { //设置源日期时区 NSTimeZone* sourceTimeZone = [NSTimeZone timeZoneWithAbbreviation:@"UTC"];//或GMT //设置转换后的目标日期时区 NSTimeZone* destinationTimeZone = [NSTimeZone localTimeZone]; //得到源日期与世界标准时间的偏移量 NSInteger sourceGMTOffset = [sourceTimeZone secondsFromGMTForDate:anyDate]; //目标日期与本地时区的偏移量 NSInteger destinationGMTOffset = [destinationTimeZone secondsFromGMTForDate:anyDate]; //得到时间偏移量的差值 NSTimeInterval interval = destinationGMTOffset - sourceGMTOffset; return interval; } @end ================================================ FILE: zhuishushenqi/NewVersion/ZSExtension/ZSExtension/Classes/NSObject+Extension.swift ================================================ // // NSObject+Extension.swift // zhuishushenqi // // Created by yung on 2018/2/6. // Copyright © 2018年 QS. All rights reserved. // import Foundation extension NSObject { public func fetchProperties() ->[String:Any] { var outCount:UInt32 = 0 let properties = class_copyPropertyList(self.classForCoder, &outCount) var dict = [String:Any]() let count = Int(outCount) for idx in 0.. @interface NSString (Encode) - (NSString*)urlEncode; - (NSString *)urlDecode; - (NSString *)zs_urlDecode; @end ================================================ FILE: zhuishushenqi/NewVersion/ZSExtension/ZSExtension/Classes/NSString+Encode.m ================================================ // // NSString+Encode.m // zhuishushenqi // // Created by yung on 2017/4/26. // Copyright © 2017年 QS. All rights reserved. // #import "NSString+Encode.h" @implementation NSString (Encode) - (NSString*)urlEncode{ //different library use slightly different escaped and unescaped set. //below is copied from AFNetworking but still escaped [] as AF leave them for Rails array parameter which we don't use. //https://github.com/AFNetworking/AFNetworking/pull/555 NSString *result = (NSString *)CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (__bridge CFStringRef)self, CFSTR("."), CFSTR(":/?#[]@!$&'()*+,;="), kCFStringEncodingUTF8)); return result; } - (NSString *)urlDecode{ // NSString *result = (NSString *)CFBridgingRelease(CFURLCreateStringByReplacingPercentEscapesUsingEncoding(kCFAllocatorDefault,(__bridge CFStringRef)self,CFSTR(":/?#[]@!$&'()*+,;="),kCFStringEncodingUTF8)); NSString *result = (NSString *)CFBridgingRelease(CFURLCreateStringByReplacingPercentEscapes(kCFAllocatorDefault,(__bridge CFStringRef)self,CFSTR(":/?#[]@!$&'()*+,;="))); return result; } - (NSString *)zs_urlDecode{ NSString *result = [self stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; return result; } @end ================================================ FILE: zhuishushenqi/NewVersion/ZSExtension/ZSExtension/Classes/Network.swift ================================================ // // Network.swift // iflyDemo // // Created by caony on 2018/9/19. // Copyright © 2018年 QSH. All rights reserved. // import Foundation import Alamofire import Zip public typealias NetworkHandler = (T?)->Void //let filePath = "\(NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first ?? "")/speakerres/3589709422/" func download(urlString:String, handler:NetworkHandler?) { let pathURL = URL(fileURLWithPath: "", isDirectory: true) do { try FileManager.default.createDirectory(at: pathURL, withIntermediateDirectories: true, attributes: nil) } catch { print(error) } let fileName = (urlString as NSString).lastPathComponent let fileURL = pathURL.appendingPathComponent(fileName) let destination: DownloadRequest.DownloadFileDestination = { temporaryURL, _ in return (fileURL, [.createIntermediateDirectories, .removePreviousFile]) } download(urlString, to: destination).response { (response) in if let error = response.error { handler?(error) } else { handler?(response.destinationURL) } } } func unzip(fileURL:URL) { do { let documentsDirectory = FileManager.default.urls(for:.documentDirectory, in: .userDomainMask)[0].appendingPathComponent("/speakerres/3589709422/", isDirectory: true) try Zip.unzipFile(fileURL, destination: documentsDirectory, overwrite: true, password: nil, progress: { (progress) -> () in print(progress) }) // Unzip } catch { print("Something went wrong") } } ================================================ FILE: zhuishushenqi/NewVersion/ZSExtension/ZSExtension/Classes/NotificationCenter+QSExtension.swift ================================================ // // Notification+QSExtension.swift // zhuishushenqi // // Created by yung on 2018/2/6. // Copyright © 2018年 QS. All rights reserved. // import Foundation public typealias NotificationHandler = () ->Void extension NotificationCenter { static var observerHandler:NotificationHandler? public static func qs_addObserver(observer:Any,selector:Selector,name:String,object:Any?) -> Void { NotificationCenter.default.addObserver(observer, selector: selector, name: Notification.Name(rawValue: name), object: object) } public static func zs_addObserver(oberver:Any,name:String,_ handler:NotificationHandler?){ observerHandler = handler self.qs_addObserver(observer: self, selector: #selector(NotificationCenter.observerHandlerAction), name: name, object: nil) } public static func qs_postNotification(name:String,obj:Any?){ let noti = Notification(name: Notification.Name(rawValue: name), object: obj, userInfo: nil) NotificationCenter.default.post(noti) } public static func qs_removeObserver(observer:Any,name:String,object:Any?){ NotificationCenter.default.removeObserver(observer, name: Notification.Name(rawValue: name), object: object) } @objc public static func observerHandlerAction(){ NotificationCenter.observerHandler?() } } ================================================ FILE: zhuishushenqi/NewVersion/ZSExtension/ZSExtension/Classes/Reachability.swift ================================================ /* Copyright (c) 2014, Ashley Mills All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ import SystemConfiguration import Foundation import CoreTelephony public enum ReachabilityError: Error { case FailedToCreateWithAddress(sockaddr_in) case FailedToCreateWithHostname(String) case UnableToSetCallback case UnableToSetDispatchQueue } public enum QSNetworkType:Int{ case UnKnown case WWAN2G case WWAN3G case WWAN4G case WiFi case UnReachable } public let ReachabilityChangedNotification = NSNotification.Name("ReachabilityChangedNotification") func callback(reachability:SCNetworkReachability, flags: SCNetworkReachabilityFlags, info: UnsafeMutableRawPointer?) { guard let info = info else { return } let reachability = Unmanaged.fromOpaque(info).takeUnretainedValue() DispatchQueue.main.async { reachability.reachabilityChanged() } } public class Reachability { public typealias NetworkReachable = (Reachability) -> () public typealias NetworkUnreachable = (Reachability) -> () public enum NetworkStatus: CustomStringConvertible { case notReachable, reachableViaWiFi, reachableViaWWAN public var description: String { switch self { case .reachableViaWWAN: return "Cellular" case .reachableViaWiFi: return "WiFi" case .notReachable: return "No Connection" } } } public var whenReachable: NetworkReachable? public var whenUnreachable: NetworkUnreachable? public var reachableOnWWAN: Bool // The notification center on which "reachability changed" events are being posted public var notificationCenter: NotificationCenter = NotificationCenter.default public var currentReachabilityString: String { return "\(currentReachabilityStatus)" } public var currentReachabilityStatus: NetworkStatus { guard isReachable else { return .notReachable } if isReachableViaWiFi { return .reachableViaWiFi } if isRunningOnDevice { return .reachableViaWWAN } return .notReachable } fileprivate var previousFlags: SCNetworkReachabilityFlags? fileprivate var isRunningOnDevice: Bool = { #if (arch(i386) || arch(x86_64)) && os(iOS) return false #else return true #endif }() fileprivate var notifierRunning = false fileprivate var reachabilityRef: SCNetworkReachability? fileprivate let reachabilitySerialQueue = DispatchQueue(label: "uk.co.ashleymills.reachability") required public init(reachabilityRef: SCNetworkReachability) { reachableOnWWAN = true self.reachabilityRef = reachabilityRef } public convenience init?(hostname: String) { guard let ref = SCNetworkReachabilityCreateWithName(nil, hostname) else { return nil } self.init(reachabilityRef: ref) } public convenience init?() { var zeroAddress = sockaddr() zeroAddress.sa_len = UInt8(MemoryLayout.size) zeroAddress.sa_family = sa_family_t(AF_INET) guard let ref: SCNetworkReachability = withUnsafePointer(to: &zeroAddress, { SCNetworkReachabilityCreateWithAddress(nil, UnsafePointer($0)) }) else { return nil } self.init(reachabilityRef: ref) } deinit { stopNotifier() reachabilityRef = nil whenReachable = nil whenUnreachable = nil } } public extension Reachability { // MARK: - *** Notifier methods *** func startNotifier() throws { guard let reachabilityRef = reachabilityRef, !notifierRunning else { return } var context = SCNetworkReachabilityContext(version: 0, info: nil, retain: nil, release: nil, copyDescription: nil) context.info = UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque()) if !SCNetworkReachabilitySetCallback(reachabilityRef, callback, &context) { stopNotifier() throw ReachabilityError.UnableToSetCallback } if !SCNetworkReachabilitySetDispatchQueue(reachabilityRef, reachabilitySerialQueue) { stopNotifier() throw ReachabilityError.UnableToSetDispatchQueue } // Perform an intial check reachabilitySerialQueue.async { self.reachabilityChanged() } notifierRunning = true } func stopNotifier() { defer { notifierRunning = false } guard let reachabilityRef = reachabilityRef else { return } SCNetworkReachabilitySetCallback(reachabilityRef, nil, nil) SCNetworkReachabilitySetDispatchQueue(reachabilityRef, nil) } // MARK: - *** Connection test methods *** var isReachable: Bool { guard isReachableFlagSet else { return false } if isConnectionRequiredAndTransientFlagSet { return false } if isRunningOnDevice { if isOnWWANFlagSet && !reachableOnWWAN { // We don't want to connect when on 3G. return false } } return true } var isReachableViaWWAN: Bool { // Check we're not on the simulator, we're REACHABLE and check we're on WWAN return isRunningOnDevice && isReachableFlagSet && isOnWWANFlagSet } var networkType:QSNetworkType { if isReachableViaWWAN { let type2G = [CTRadioAccessTechnologyEdge, CTRadioAccessTechnologyGPRS, CTRadioAccessTechnologyCDMA1x] let type3G = [CTRadioAccessTechnologyHSDPA, CTRadioAccessTechnologyWCDMA, CTRadioAccessTechnologyHSUPA, CTRadioAccessTechnologyCDMAEVDORev0, CTRadioAccessTechnologyCDMAEVDORevA, CTRadioAccessTechnologyCDMAEVDORevB, CTRadioAccessTechnologyeHRPD] let type4G = [CTRadioAccessTechnologyLTE] let telephoneInfo = CTTelephonyNetworkInfo() let access = telephoneInfo.currentRadioAccessTechnology if type2G.contains(access ?? "") { return .WWAN2G }else if type3G.contains(access ?? ""){ return .WWAN3G }else if type4G.contains(access ?? ""){ return .WWAN4G }else { return .UnKnown } }else if isReachableViaWiFi { return .WiFi }else if isReachable{ return .UnKnown } return .UnReachable } var isReachableViaWiFi: Bool { // Check we're reachable guard isReachableFlagSet else { return false } // If reachable we're reachable, but not on an iOS device (i.e. simulator), we must be on WiFi guard isRunningOnDevice else { return true } // Check we're NOT on WWAN return !isOnWWANFlagSet } var description: String { let W = isRunningOnDevice ? (isOnWWANFlagSet ? "W" : "-") : "X" let R = isReachableFlagSet ? "R" : "-" let c = isConnectionRequiredFlagSet ? "c" : "-" let t = isTransientConnectionFlagSet ? "t" : "-" let i = isInterventionRequiredFlagSet ? "i" : "-" let C = isConnectionOnTrafficFlagSet ? "C" : "-" let D = isConnectionOnDemandFlagSet ? "D" : "-" let l = isLocalAddressFlagSet ? "l" : "-" let d = isDirectFlagSet ? "d" : "-" return "\(W)\(R) \(c)\(t)\(i)\(C)\(D)\(l)\(d)" } } fileprivate extension Reachability { func reachabilityChanged() { let flags = reachabilityFlags guard previousFlags != flags else { return } let block = isReachable ? whenReachable : whenUnreachable block?(self) self.notificationCenter.post(name: ReachabilityChangedNotification, object:self) previousFlags = flags } var isOnWWANFlagSet: Bool { #if os(iOS) return reachabilityFlags.contains(.isWWAN) #else return false #endif } var isReachableFlagSet: Bool { return reachabilityFlags.contains(.reachable) } var isConnectionRequiredFlagSet: Bool { return reachabilityFlags.contains(.connectionRequired) } var isInterventionRequiredFlagSet: Bool { return reachabilityFlags.contains(.interventionRequired) } var isConnectionOnTrafficFlagSet: Bool { return reachabilityFlags.contains(.connectionOnTraffic) } var isConnectionOnDemandFlagSet: Bool { return reachabilityFlags.contains(.connectionOnDemand) } var isConnectionOnTrafficOrDemandFlagSet: Bool { return !reachabilityFlags.intersection([.connectionOnTraffic, .connectionOnDemand]).isEmpty } var isTransientConnectionFlagSet: Bool { return reachabilityFlags.contains(.transientConnection) } var isLocalAddressFlagSet: Bool { return reachabilityFlags.contains(.isLocalAddress) } var isDirectFlagSet: Bool { return reachabilityFlags.contains(.isDirect) } var isConnectionRequiredAndTransientFlagSet: Bool { return reachabilityFlags.intersection([.connectionRequired, .transientConnection]) == [.connectionRequired, .transientConnection] } var reachabilityFlags: SCNetworkReachabilityFlags { guard let reachabilityRef = reachabilityRef else { return SCNetworkReachabilityFlags() } var flags = SCNetworkReachabilityFlags() let gotFlags = withUnsafeMutablePointer(to: &flags) { SCNetworkReachabilityGetFlags(reachabilityRef, UnsafeMutablePointer($0)) } if gotFlags { return flags } else { return SCNetworkReachabilityFlags() } } } ================================================ FILE: zhuishushenqi/NewVersion/ZSExtension/ZSExtension/Classes/SQLite+Extension.swift ================================================ // // SQLite+Extension.swift // zhuishushenqi // // Created by yung on 2018/8/11. // Copyright © 2018年 QS. All rights reserved. // import Foundation import SQLite protocol DBSaveProtocol { func db_save() } extension NSObject { public func db_connect(_ path:String?){ var db:Connection! if let dbPath = path { db = try? Connection(dbPath) } else { let dbPath = NSHomeDirectory() db = try? Connection("\(dbPath)/db.sqlite3") } } public func test(){ // let db = try? Connection("path/to/db.sqlite3") // // let users = Table("users") // let id = Expression("id") // let name = Expression("name") // let email = Expression("email") // // try? db?.run(users.create { t in // t.column(id, primaryKey: true) // t.column(name) // t.column(email, unique: true) // }) // // CREATE TABLE "users" ( // // "id" INTEGER PRIMARY KEY NOT NULL, // // "name" TEXT, // // "email" TEXT NOT NULL UNIQUE // // ) // // let insert = users.insert(name <- "Alice", email <- "alice@mac.com") // let rowid = try? db?.run(insert) // // INSERT INTO "users" ("name", "email") VALUES ('Alice', 'alice@mac.com') // // for user in try? db?.prepare(users) { // print("id: \(user[id]), name: \(user[name]), email: \(user[email])") // // id: 1, name: Optional("Alice"), email: alice@mac.com // } // // SELECT * FROM "users" // // let alice = users.filter(id == rowid) // // try? db.run(alice.update(email <- email.replace("mac.com", with: "me.com"))) // // UPDATE "users" SET "email" = replace("email", 'mac.com', 'me.com') // // WHERE ("id" = 1) // // try? db?.run(alice.delete()) // // DELETE FROM "users" WHERE ("id" = 1) // // try? db?.scalar(users.count) // 0 // // SELECT count(*) FROM "users" } } ================================================ FILE: zhuishushenqi/NewVersion/ZSExtension/ZSExtension/Classes/String+QSExtension.swift ================================================ // // String+crypto.swift // zhuishushenqi // // Created by Nory Cao on 2017/3/6. // Copyright © 2017年 QS. All rights reserved. // import Foundation import ZSAppConfig import CommonCrypto extension String { //MARK: - crypto public func md5() ->String{ let str = self.cString(using: String.Encoding.utf8) let strLen = CUnsignedInt(self.lengthOfBytes(using: String.Encoding.utf8)) let digestLen = Int(CC_MD5_DIGEST_LENGTH) let result = UnsafeMutablePointer.allocate(capacity: digestLen) CC_MD5(str!, strLen, result) let hash = NSMutableString() for i in 0 ..< digestLen { hash.appendFormat("%02x", result[i]) } result.deinitialize(count: digestLen) return String(format: hash as String) } //由于移动设备的内存有限,以下代码实现是将文件分块读出并且计算md5值的方法 public func fileMD5()->String? { let handler = FileHandle(forReadingAtPath: self) if handler == nil { return nil } let ctx = UnsafeMutablePointer.allocate(capacity: MemoryLayout.size) CC_MD5_Init(ctx) var done = false while !done { let fileData = handler?.readData(ofLength: 256) fileData?.withUnsafeBytes({ (bytes) -> Void in CC_MD5_Update(ctx, bytes, CC_LONG(fileData!.count)) }) if fileData?.count == 0 { done = true } } let digestLen = Int(CC_MD5_DIGEST_LENGTH) let digest = UnsafeMutablePointer.allocate(capacity: digestLen) CC_MD5_Final(digest, ctx) var hash = "" for i in 0..String{ if self == "" { return self } var ends = end if self.count < ends { ends = self.count } let startIndex = self.index(self.startIndex, offsetBy: start) let endIndex = self.index(self.startIndex, offsetBy: ends) let range = startIndex..String{ if self == "" { return self } let startIndex = self.index(self.startIndex, offsetBy: start) let endIndex = self.index(self.startIndex, offsetBy: start + length) let range = startIndex..String{ if self == "" { return self } let startIndex = self.index(self.startIndex, offsetBy: from) let endIndex = self.endIndex let range = startIndex..String{ if self == "" { return self } let startIndex = self.startIndex let endIndex = self.index(self.startIndex, offsetBy: to) let range = startIndex..)->String{ if self == "" { return self } let startIndex = range.startIndex let endIndex = range.endIndex let sub = self.qs_subStr(start: startIndex, end: endIndex) return sub } public func qs_subStr(range:NSRange)->String{ if self == "" { return self } let start = range.location let end = range.location + range.length return self.qs_subStr(start: start, end: end) } //MARK:- count public func qs_width(_ font:UIFont,height:CGFloat) ->CGFloat { let dict = [NSAttributedString.Key.font:font] let sttt:NSString = self as NSString let rect:CGRect = sttt.boundingRect(with: CGSize(width: CGFloat(MAXFLOAT), height: CGFloat(height)), options: NSStringDrawingOptions.usesLineFragmentOrigin, attributes: dict, context: nil) return rect.size.width } public func qs_height(_ font:UIFont,width:CGFloat) ->CGFloat { let dict = [NSAttributedString.Key.font:font] let sttt:NSString = self as NSString let rect:CGRect = sttt.boundingRect(with: CGSize(width: width, height: CGFloat(MAXFLOAT)), options: NSStringDrawingOptions.usesLineFragmentOrigin, attributes: dict, context: nil) return rect.size.height } public func qs_width(_ fontSize:CGFloat,height:CGFloat) -> CGFloat{ let dict = [NSAttributedString.Key.font:UIFont.systemFont(ofSize: fontSize)] let sttt:NSString = self as NSString let rect:CGRect = sttt.boundingRect(with: CGSize(width: CGFloat(MAXFLOAT), height: CGFloat(height)), options: NSStringDrawingOptions.usesLineFragmentOrigin, attributes: dict, context: nil) return rect.size.width } public func qs_height(_ fontSize:CGFloat,width:CGFloat) ->CGFloat { let dict = [NSAttributedString.Key.font:UIFont.systemFont(ofSize: fontSize)] let sttt:NSString = self as NSString let rect:CGRect = sttt.boundingRect(with: CGSize(width: width, height: CGFloat(MAXFLOAT)), options: NSStringDrawingOptions.usesLineFragmentOrigin, attributes: dict, context: nil) return rect.size.height } //获取子字符串 public func substingInRange(_ r: Range) -> String? { if r.lowerBound < 0 || r.upperBound > self.count { return nil } let startIndex = self.index(self.startIndex, offsetBy:r.lowerBound) let endIndex = self.index(self.startIndex, offsetBy:r.upperBound) return String(self[startIndex..Bool { if self == string { return true } return false } //MARK: - string public func asNSString() ->NSString { return (self as NSString) } public func isEmpty() ->Bool { if self == "" || self.count == 0 { return true } return false } //MARK: - transform public var length:Int { return self.lengthOfBytes(using: .utf8) } public var toArray:Array<[String:Any]>? { var json = self if json.contains("\\") { json = json.components(separatedBy: "\\").joined() } if let data = json.data(using: .utf8) { do { let arr = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.init(rawValue: 0)) as? Array<[String:Any]> return arr } catch let error { QSLog(error) } } return nil } public var toDict:Dictionary? { var json = self if json.contains("\\") { json = json.components(separatedBy: "\\").joined() } if let data = json.data(using: .utf8) { do { let dict = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.init(rawValue: 0)) as? Dictionary guard let _ = dict?.first else { return nil } return dict } catch let error { QSLog(error) } } return nil } } extension Optional { } ================================================ FILE: zhuishushenqi/NewVersion/ZSExtension/ZSExtension/Classes/SwiftStdlib/FloatExtensions.swift ================================================ // // FloatExtensions.swift // zhuishushenqi // // Created by caony on 2019/1/22. // Copyright © 2019年 QS. All rights reserved. // import Foundation public extension Float { public var int:Int { return Int(self) } public var double:Double { return Double(self) } public var cgfloat:CGFloat { return CGFloat(self) } } ================================================ FILE: zhuishushenqi/NewVersion/ZSExtension/ZSExtension/Classes/SwiftStdlib/IntExtensions.swift ================================================ // // IntExtensions.swift // zhuishushenqi // // Created by caony on 2019/1/24. // Copyright © 2019年 QS. All rights reserved. // import Foundation extension Int { public var float:Float { return Float(self) } public var double:Double { return Double(self) } public var cgfloat:CGFloat { return CGFloat(self) } } ================================================ FILE: zhuishushenqi/NewVersion/ZSExtension/ZSExtension/Classes/UIButton+Extension.swift ================================================ // // UIButton+Extension.swift // zhuishushenqi // // Created by caony on 2019/7/2. // Copyright © 2019 QS. All rights reserved. // import UIKit import ZSAppConfig extension UIButton { func qs_setBookCoverWithURLString(urlString:String){ setImage(UIImage(named: "default_book_cover"), for: .normal) // DispatchQueue.global().async { let noPercentStr:String = urlString.removingPercentEncoding ?? "" var urlStr = noPercentStr if urlStr.qs_subStr(to: 4) != "http"{ // urlStr = urlStr.qs_subStr(from: 7) urlStr = "\(IMAGE_BASEURL)\(urlStr)-coverl" } if urlStr.contains("http") == false { urlStr = "\(IMAGE_BASEURL)\(noPercentStr)" } let url = URL(string: urlStr) guard let imageURL = url else { QSLog("Invalid URL") return } let resource:QSResource = QSResource(url: imageURL) self.kf.setImage(with: resource, for: .normal, placeholder: UIImage(named: "default_book_cover")) } func qs_setAvatarWithURLString(urlString:String){ setImage(UIImage(named: "default_avatar_light"), for: .normal) let imageUrlString = "\(IMAGE_BASEURL)\(urlString)" let url:URL? = URL(string: imageUrlString) guard let imageURL = url else { QSLog("Invalid URL") return } let resource:QSResource = QSResource(url: imageURL) self.kf.setImage(with: resource, for: .normal, placeholder: UIImage(named: "default_avatar_light")) } } ================================================ FILE: zhuishushenqi/NewVersion/ZSExtension/ZSExtension/Classes/UICollectionView+QSExtension.swift ================================================ // // UICollectionView+QSExtension.swift // zhuishushenqi // // Created by yung on 2017/8/22. // Copyright © 2017年 QS. All rights reserved. // import Foundation import UIKit protocol ReusableView: class { static var reuseId: String {get} } extension ReusableView where Self: UIView { static var reuseId: String { return String(describing: self) } } extension UICollectionReusableView: ReusableView { } extension UICollectionView { func qs_registerCellNib(_ aClass:T.Type){ let name = String(describing: aClass) let nib = UINib(nibName: name, bundle: nil) self.register(nib, forCellWithReuseIdentifier: name) } func qs_registerCellClass(_ aClass:T.Type){ let name = String(describing:aClass) self.register(aClass, forCellWithReuseIdentifier: name) } func qs_dequeueReusableCell(_ aClass:T.Type, for indexPath:IndexPath)->T where T:ReusableView{ let name = String(describing:aClass) guard let cell = dequeueReusableCell(withReuseIdentifier: name, for: indexPath) as? T else { fatalError("\(name) is not registered") } return cell } // func qs_registerHeaderFooterNib(_ aClass:T.Type){ // let name = String(describing: aClass) // let nib = UINib(nibName: name, bundle: nil) // self.register(nib, forHeaderFooterViewReuseIdentifier: name) // } // // func qs_registerHeaderFooterClass(_ aClass:T.Type){ // let name = String(describing:aClass) // self.register(aClass, forHeaderFooterViewReuseIdentifier: name) // } // // func qs_dequeueReusableHeaderFooterView(_ aClass:T.Type)->T!{ // let name = String(describing:aClass) // guard let cell = dequeueReusableHeaderFooterView(withIdentifier: name) as? T else { // fatalError("\(name) is not registered") // } // return cell // } } ================================================ FILE: zhuishushenqi/NewVersion/ZSExtension/ZSExtension/Classes/UIColor+Theme.swift ================================================ // // UIColor+Theme.swift // zhuishushenqi // // Created by yung on 2017/4/28. // Copyright © 2017年 QS. All rights reserved. // import Foundation import UIKit extension UIColor{ func daylightTextColor()->UIColor{ return UIColor.black } func daylightToolBarColor()->UIColor{ return UIColor.black } func nightCellColor()->UIColor{ return UIColor.black } func nightSegColor()->UIColor{ return UIColor.darkGray } func nightNavColor()->UIColor{ return UIColor.darkGray } } ================================================ FILE: zhuishushenqi/NewVersion/ZSExtension/ZSExtension/Classes/UIFont+Extension.swift ================================================ // // UIFont+Extension.swift // zhuishushenqi // // Created by caony on 2018/9/14. // Copyright © 2018年 QS. All rights reserved. // import Foundation extension UIFont { } ================================================ FILE: zhuishushenqi/NewVersion/ZSExtension/ZSExtension/Classes/UIFont+ZSExtension.h ================================================ // // UIFont+ZSExtension.h // zhuishushenqi // // Created by caony on 2018/9/14. // Copyright © 2018年 QS. All rights reserved. // #import @interface UIFont (ZSExtension) /** * Create a font array from path while ttc is a colleciton of font. * * @param path Path of the font. * @param size Font size that you want. */ -(NSArray*)customFontArrayWithPath:(NSString*)path size:(CGFloat)size; /** * Create a font from path,TTF,OTF available. * * @param path Path of the font. * @param size Font size that you want. */ -(UIFont*)customFontWithPath:(NSString*)path size:(CGFloat)size; @end ================================================ FILE: zhuishushenqi/NewVersion/ZSExtension/ZSExtension/Classes/UIFont+ZSExtension.m ================================================ // // UIFont+ZSExtension.m // zhuishushenqi // // Created by caony on 2018/9/14. // Copyright © 2018年 QS. All rights reserved. // #import "UIFont+ZSExtension.h" #import @implementation UIFont (ZSExtension) -(NSArray*)customFontArrayWithPath:(NSString*)path size:(CGFloat)size { CFStringRef fontPath = CFStringCreateWithCString(NULL, [path UTF8String], kCFStringEncodingUTF8); CFURLRef fontUrl = CFURLCreateWithFileSystemPath(NULL, fontPath, kCFURLPOSIXPathStyle, 0); CFArrayRef fontArray = CTFontManagerCreateFontDescriptorsFromURL(fontUrl); CTFontManagerRegisterFontsForURL(fontUrl, kCTFontManagerScopeNone, NULL); NSMutableArray *customFontArray = [NSMutableArray array]; for (CFIndex i = 0 ; i < CFArrayGetCount(fontArray); i++){ CTFontDescriptorRef descriptor = CFArrayGetValueAtIndex(fontArray, i); CTFontRef fontRef = CTFontCreateWithFontDescriptor(descriptor, size, NULL); NSString *fontName = CFBridgingRelease(CTFontCopyName(fontRef, kCTFontPostScriptNameKey)); UIFont *font = [UIFont fontWithName:fontName size:size]; [customFontArray addObject:font]; } return customFontArray; } -(UIFont*)customFontWithPath:(NSString*)path size:(CGFloat)size { NSURL *fontUrl = [NSURL fileURLWithPath:path]; CGDataProviderRef fontDataProvider = CGDataProviderCreateWithURL((__bridge CFURLRef)fontUrl); CGFontRef fontRef = CGFontCreateWithDataProvider(fontDataProvider); CGDataProviderRelease(fontDataProvider); CTFontManagerRegisterGraphicsFont(fontRef, NULL); NSString *fontName = CFBridgingRelease(CGFontCopyPostScriptName(fontRef)); UIFont *font = [UIFont fontWithName:fontName size:size]; CGFontRelease(fontRef); return font; } @end ================================================ FILE: zhuishushenqi/NewVersion/ZSExtension/ZSExtension/Classes/UIImage+QSData.swift ================================================ // // UIImage+QSData.swift // zhuishushenqi // // Created by Nory Cao on 2017/3/22. // Copyright © 2017年 QS. All rights reserved. // import Foundation import UIKit import ZSAppConfig extension UIImage{ static func image(with base64:String) -> UIImage? { let data:Data? = Data(base64Encoded: base64, options: .ignoreUnknownCharacters) if let decodeData = data{ return UIImage(data: decodeData) } return nil } static func image(from url:String)->UIImage?{ var image:UIImage? DispatchQueue.global().async { do{ let url:URL? = URL(string: url) if let imageUrl = url { let data:Data = try Data(contentsOf: imageUrl, options: .alwaysMapped) image = UIImage(data: data) } }catch{ QSLog(error) } } return image } func imageHasAlpha(image:UIImage)->Bool{ let alpha:CGImageAlphaInfo? = image.cgImage?.alphaInfo return (alpha == CGImageAlphaInfo.first || alpha == CGImageAlphaInfo.last || alpha == CGImageAlphaInfo.premultipliedLast || alpha == CGImageAlphaInfo.premultipliedFirst) } func base64() -> String? { var imageData:Data? var mimeType:String? if self.imageHasAlpha(image: self) { imageData = self.pngData() mimeType = "image/png" }else{ imageData = self.jpegData(compressionQuality: 1.0) mimeType = "image/jpeg" } QSLog(mimeType) return imageData?.base64EncodedString(options: .endLineWithCarriageReturn) } func qs_drawRectWithRoundedCorner(radius: CGFloat, _ sizetoFit: CGSize) -> UIImage { let rect = CGRect(origin: CGPoint(x: 0, y: 0), size: sizetoFit) UIGraphicsBeginImageContextWithOptions(rect.size, false, UIScreen.main.scale) UIGraphicsGetCurrentContext()?.addPath(UIBezierPath(roundedRect: rect, byRoundingCorners: UIRectCorner.allCorners, cornerRadii: CGSize(width: radius, height: radius)).cgPath) UIGraphicsGetCurrentContext()?.clip() self.draw(in: rect) UIGraphicsGetCurrentContext()?.drawPath(using: .fillStroke) let output = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); return output! } } ================================================ FILE: zhuishushenqi/NewVersion/ZSExtension/ZSExtension/Classes/UIImageView+zhuishu.swift ================================================ // // UIImageView+zhuishu.swift // zhuishushenqi // // Created by Nory Cao on 2017/3/16. // Copyright © 2017年 QS. All rights reserved. // import Foundation import UIKit import ZSAppConfig import Kingfisher extension UIImageView{ func qs_setBookCoverWithURLString(urlString:String){ self.image = UIImage(named: "default_book_cover") // DispatchQueue.global().async { let noPercentStr:String = urlString.removingPercentEncoding ?? "" var urlStr = noPercentStr if urlStr.qs_subStr(to: 4) != "http"{ // urlStr = urlStr.qs_subStr(from: 7) urlStr = "\(IMAGE_BASEURL)\(urlStr)-coverl" } if urlStr.contains("http") == false { urlStr = "\(IMAGE_BASEURL)\(noPercentStr)" } let url = URL(string: urlStr) guard let imageURL = url else { QSLog("Invalid URL") return } let resource:QSResource = QSResource(url: imageURL) self.kf.setImage(with: resource, placeholder: UIImage(named: "default_book_cover")) // } } func qs_setAvatarWithURLString(urlString:String){ self.image = UIImage(named: "default_avatar_light") let imageUrlString = "\(IMAGE_BASEURL)\(urlString)" let url:URL? = URL(string: imageUrlString) guard let imageURL = url else { QSLog("Invalid URL") return } let resource:QSResource = QSResource(url: imageURL) self.kf.setImage(with: resource, placeholder: UIImage(named: "default_avatar_light")) } func qs_addCorner(radius: CGFloat) { //1.一种圆角添加方式,效率比直接CornerRadius高 self.image = self.image?.qs_drawRectWithRoundedCorner(radius: radius, self.bounds.size) } func qs_addCornerRadius(cornerRadius:CGFloat){ //2.高效添加圆角的方式 let maskPath = UIBezierPath(roundedRect: self.bounds, byRoundingCorners: .allCorners, cornerRadii: CGSize(width:cornerRadius, height: cornerRadius)) let maskLayer = CAShapeLayer() maskLayer.frame = self.bounds maskLayer.path = maskPath.cgPath self.layer.mask = maskLayer } } extension UIButton{ func qs_setBookCoverWithUrlString(urlString:String){ let noPercentStr:String = urlString.removingPercentEncoding ?? "" self.setImage(UIImage(named: "default_book_cover"), for: .normal) var urlStr = noPercentStr if urlStr.qs_subStr(to: 4) != "http"{ urlStr = urlStr.qs_subStr(from: 7) } if urlStr.contains("http") == false { urlStr = "\(IMAGE_BASEURL)\(urlString)" } let url = URL(string: urlStr) guard let imageURL = url else { QSLog("Invalid URL") return } let resource:QSResource = QSResource(url: imageURL) self.kf.setImage(with:resource, for: .normal, placeholder: UIImage(named:"default_book_cover")) } } public class QSResource:Resource{ public var imageURL:URL? = URL(string: "http://statics.zhuishushenqi.com/ranking-cover/142319144267827") public var downloadURL: URL { return imageURL! } public var cacheKey: String{ return "\(String(describing: self.imageURL))" } init(url:URL) { self.imageURL = url } } public class ZSPlaceHolder:Placeholder { public func add(to imageView: ImageView) { } public func remove(from imageView: ImageView) { } } ================================================ FILE: zhuishushenqi/NewVersion/ZSExtension/ZSExtension/Classes/UILabel+zhuishu.swift ================================================ // // UILabel+zhuishu.swift // zhuishushenqi // // Created by Nory Cao on 2017/3/16. // Copyright © 2017年 QS. All rights reserved. // import Foundation import UIKit extension UILabel{ func qs_setCreateTime(createTime:String,append:String){ if createTime.lengthOfBytes(using: String.Encoding.utf8) > 18{ DispatchQueue.global().async { let year = createTime.qs_subStr(to: 4) let month = createTime.qs_subStr(start: 5, end: 7) let day = createTime.qs_subStr(start: 8, length: 2) let hour = createTime.qs_subStr(start: 11, length: 2) let mimute = createTime.qs_subStr(start: 14, length: 2) let second = createTime.qs_subStr(start: 17, length: 2) let beginDate = NSDate.getWithYear(year, month: month, day: day, hour: hour, mimute: mimute, second: second) let endDate = Date() let formatter = DateIntervalFormatter() let out = formatter.timeInfo(from: beginDate!, to: endDate) DispatchQueue.main.async { self.text = "\(out)\(append)" } } } } } ================================================ FILE: zhuishushenqi/NewVersion/ZSExtension/ZSExtension/Classes/UINavigationItem+BackItem.h ================================================ // // UINavigationItem+BackItem.h // zhuishushenqi // // Created by Nory Cao on 16/10/4. // Copyright © 2016年 QS. All rights reserved. // #import @interface UINavigationItem (BackItem) @end ================================================ FILE: zhuishushenqi/NewVersion/ZSExtension/ZSExtension/Classes/UINavigationItem+BackItem.m ================================================ // // UINavigationItem+BackItem.m // zhuishushenqi // // Created by Nory Cao on 16/10/4. // Copyright © 2016年 QS. All rights reserved. // #import "UINavigationItem+BackItem.h" #import @implementation UINavigationItem (BackItem) +(void)load{ static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ Method originalMethodImp = class_getInstanceMethod(self, @selector(backBarButtonItem)); Method destMethodImp = class_getInstanceMethod(self, @selector(myCustomBackButton)); method_exchangeImplementations(originalMethodImp, destMethodImp); }); } static char kCustomBackButtonKey; -(UIBarButtonItem *)myCustomBackButton{ UIBarButtonItem *item = [self myCustomBackButton]; if (item) { return item; } item = objc_getAssociatedObject(self, &kCustomBackButtonKey); if (!item) { item = [[UIBarButtonItem alloc] initWithTitle:@"返回" style:UIBarButtonItemStylePlain target:nil action:NULL]; objc_setAssociatedObject(self, &kCustomBackButtonKey, item, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } return item; } @end ================================================ FILE: zhuishushenqi/NewVersion/ZSExtension/ZSExtension/Classes/UITableView+FINAutomaticHeightCell.swift ================================================ // // UITableView+FINAutomaticHeightCell.swift // V2ex-Swift // // Created by huangfeng on 1/12/16. // Copyright © 2016 Fin. All rights reserved. // import UIKit extension UITableView { public func fin_heightForCellWithIdentifier(_ identifier: String, configuration: ((_ cell: T) -> Void)?) -> CGFloat { if identifier.count <= 0 { return 0 } let cell = self.fin_templateCellForReuseIdentifier(identifier) cell.prepareForReuse() if configuration != nil { configuration!(cell as! T) } // cell.setNeedsUpdateConstraints(); // cell.updateConstraintsIfNeeded(); // self.setNeedsLayout(); // self.layoutIfNeeded(); var fittingSize = cell.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize) if self.separatorStyle != .none { fittingSize.height += 1.0 / UIScreen.main.scale } return fittingSize.height } fileprivate func fin_templateCellForReuseIdentifier(_ identifier: String) -> UITableViewCell { assert(identifier.count > 0, "Expect a valid identifier - \(identifier)") if self.fin_templateCellsByIdentifiers == nil { self.fin_templateCellsByIdentifiers = [:] } var templateCell = self.fin_templateCellsByIdentifiers?[identifier] as? UITableViewCell if templateCell == nil { templateCell = self.dequeueReusableCell(withIdentifier: identifier) assert(templateCell != nil, "Cell must be registered to table view for identifier - \(identifier)") templateCell?.contentView.translatesAutoresizingMaskIntoConstraints = false self.fin_templateCellsByIdentifiers?[identifier] = templateCell } return templateCell! } public func fin_heightForCellWithIdentifier(_ identifier: T.Type, indexPath: IndexPath, configuration: ((_ cell: T) -> Void)?) -> CGFloat { let identifierStr = "\(identifier)"; if identifierStr.count == 0 { return 0 } // Hit cache if self.fin_hasCachedHeightAtIndexPath(indexPath) { let height: CGFloat = self.fin_indexPathHeightCache![indexPath.section][indexPath.row] // NSLog("hit cache by indexPath:[\(indexPath.section),\(indexPath.row)] -> \(height)"); return height } let height = self.fin_heightForCellWithIdentifier(identifierStr, configuration: configuration) self.fin_indexPathHeightCache![indexPath.section][indexPath.row] = height // NSLog("cached by indexPath:[\(indexPath.section),\(indexPath.row)] --> \(height)") return height } fileprivate struct AssociatedKey { static var CellsIdentifier = "me.fin.cellsIdentifier" static var HeightsCacheIdentifier = "me.fin.heightsCacheIdentifier" static var finHeightCacheAbsendValue = CGFloat(-1); } fileprivate func fin_hasCachedHeightAtIndexPath(_ indexPath:IndexPath) -> Bool { self.fin_buildHeightCachesAtIndexPathsIfNeeded([indexPath]); let height = self.fin_indexPathHeightCache![indexPath.section][indexPath.row]; return height >= 0; } fileprivate func fin_buildHeightCachesAtIndexPathsIfNeeded(_ indexPaths:Array) -> Void { if indexPaths.count <= 0 { return ; } if self.fin_indexPathHeightCache == nil { self.fin_indexPathHeightCache = []; } for indexPath in indexPaths { let cacheSectionCount = self.fin_indexPathHeightCache!.count; if indexPath.section >= cacheSectionCount { for i in cacheSectionCount...indexPath.section { if i >= self.fin_indexPathHeightCache!.count{ self.fin_indexPathHeightCache!.append([]) } } } let cacheCount = self.fin_indexPathHeightCache![indexPath.section].count; if indexPath.row >= cacheCount { for i in cacheCount...indexPath.row { if i >= self.fin_indexPathHeightCache![indexPath.section].count { self.fin_indexPathHeightCache![indexPath.section].append(AssociatedKey.finHeightCacheAbsendValue); } } } } } fileprivate var fin_templateCellsByIdentifiers: [String : AnyObject]? { get { return objc_getAssociatedObject(self, &AssociatedKey.CellsIdentifier) as? [String : AnyObject] } set { objc_setAssociatedObject(self, &AssociatedKey.CellsIdentifier, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } } fileprivate var fin_indexPathHeightCache: [ [CGFloat] ]? { get { return objc_getAssociatedObject(self, &AssociatedKey.HeightsCacheIdentifier) as? [[CGFloat]] } set { objc_setAssociatedObject(self, &AssociatedKey.HeightsCacheIdentifier, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } } public func fin_reloadData(){ self.fin_indexPathHeightCache = [[]]; self.reloadData(); } } ================================================ FILE: zhuishushenqi/NewVersion/ZSExtension/ZSExtension/Classes/UITableView+QSGeneric.swift ================================================ // // UITableView+QSCreate.swift // zhuishushenqi // // Created by Nory Cao on 2017/3/19. // Copyright © 2017年 QS. All rights reserved. // import Foundation import UIKit extension UITableView{ // private struct AssociatedKey { // static var viewExtension = "viewExtension" // } // // var tableHander: ZSBaseTableViewManger { // get { // return objc_getAssociatedObject(self, &AssociatedKey.viewExtension) as! ZSBaseTableViewManger // } // set { // newValue.handleTableViewDatasourceAndDelegate(table: self) // objc_setAssociatedObject(self, &AssociatedKey.viewExtension, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) // } // } public func qs_registerCellNib(_ aClass:T.Type){ let name = String(describing: aClass) let nib = UINib(nibName: name, bundle: nil) self.register(nib, forCellReuseIdentifier: name) } public func qs_registerCellClass(_ aClass:T.Type){ let name = String(describing:aClass) self.register(aClass, forCellReuseIdentifier: name) } public func qs_dequeueReusableCell(_ aClass:T.Type)->T!{ let name = String(describing:aClass) guard let cell = dequeueReusableCell(withIdentifier: name) as? T else { fatalError("\(name) is not registered") } return cell } public func qs_registerHeaderFooterNib(_ aClass:T.Type){ let name = String(describing: aClass) let nib = UINib(nibName: name, bundle: nil) self.register(nib, forHeaderFooterViewReuseIdentifier: name) } public func qs_registerHeaderFooterClass(_ aClass:T.Type){ let name = String(describing:aClass) self.register(aClass, forHeaderFooterViewReuseIdentifier: name) } public func qs_dequeueReusableHeaderFooterView(_ aClass:T.Type)->T!{ let name = String(describing:aClass) guard let cell = dequeueReusableHeaderFooterView(withIdentifier: name) as? T else { fatalError("\(name) is not registered") } return cell } } ================================================ FILE: zhuishushenqi/NewVersion/ZSExtension/ZSExtension/Classes/UITableView+swizzling.swift ================================================ // // UITableView+swizzling.swift // zhuishushenqi // // Created by Nory Cao on 2017/3/17. // Copyright © 2017年 QS. All rights reserved. // import Foundation import UIKit extension UITableView{ } ================================================ FILE: zhuishushenqi/NewVersion/ZSExtension/ZSExtension/Classes/UITableViewCell+ZSExtension.swift ================================================ // // UITableViewCell+ZSExtension.swift // zhuishushenqi // // Created by caony on 2018/5/21. // Copyright © 2018年 QS. All rights reserved. // import Foundation public extension UITableViewCell { class internal func nibWithIdentifier(identifier: String) -> UINib{ return UINib(nibName: identifier, bundle: nil) } /** 从nib文件中根据重用标识符注册UITableViewCell - parameter table: table description - parameter identifier: identifier description */ class func registerTable(table: UITableView, nibIdentifier identifier: String) { return table.register(self.nibWithIdentifier(identifier: identifier), forCellReuseIdentifier: identifier) } /** 配置UITableViewCell,设置UITableViewCell内容 - parameter cell: cell description - parameter obj: obj description - parameter indexPath: indexPath description */ func configure(cell: UITableViewCell, customObj obj: AnyObject, indexPath: NSIndexPath) { // Rewrite this func in SubClass ! } /** 获取自定义对象的cell高度 (已集成UITableView+FDTemplateLayoutCell,现在创建的cell自动计算高度) - parameter obj: obj description - parameter indexPath: indexPath description - returns: return value description */ class func getCellHeightWithCustomObj(obj: AnyObject?, indexPath: NSIndexPath) -> CGFloat { // Rewrite this func in SubClass if necessary return obj == nil ? 44.0 : 0.0 // default cell height 44.0 } } ================================================ FILE: zhuishushenqi/NewVersion/ZSExtension/ZSExtension/Classes/UIView+ScreenShot.swift ================================================ // // UIView+ScreenShot.swift // zhuishushenqi // // Created by Nory Cao on 2017/4/17. // Copyright © 2017年 QS. All rights reserved. // import Foundation import UIKit import PKHUD import YYCategories import ZSAppConfig func RGBAColor(_ red:T, _ green:T, _ blue:T, _ alpha:CGFloat? = 1) -> UIColor { var redString = "\(red)" var greenString = "\(green)" var blueString = "\(blue)" if redString.hasPrefix("0x") || redString.hasPrefix("0X") { redString = redString.qs_subStr(from: 2) } if greenString.hasPrefix("0x") || greenString.hasPrefix("0X") { greenString = greenString.qs_subStr(from: 2) } if blueString.hasPrefix("0x") || blueString.hasPrefix("0X") { blueString = blueString.qs_subStr(from: 2) } // if T.self == CGFloat.self || T.self == Int.self || T.self == Double.self { // let redFloatValue:CGFloat = red as! CGFloat // let greenFloatValue:CGFloat = green as! CGFloat // let blueFloatValue:CGFloat = blue as! CGFloat // return UIColor(red: redFloatValue/255, green: greenFloatValue/255, blue: blueFloatValue/255, alpha: alpha ?? 1) // } return UIColor(hexString: "#\(redString)\(greenString)\(blueString)") ?? UIColor.black } extension UIView { public func screenShot()->UIImage?{ let size = self.bounds.size let transform:CGAffineTransform = __CGAffineTransformMake(-1, 0, 0, 1, size.width, 0) UIGraphicsBeginImageContextWithOptions(size, self.isOpaque, 0.0) let context = UIGraphicsGetCurrentContext() context?.concatenate(transform) self.layer.render(in: context!) let image:UIImage? = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() return image } public func nextController()->UIViewController?{ var nextResponder:UIResponder? = self.next while (nextResponder != nil) { if nextResponder?.isKind(of: UIViewController.self) == true { return nextResponder as? UIViewController } nextResponder = nextResponder?.next } return nil } public func showTipView(tip:String) { HUD.flash(.label(tip), delay: 3) } public func showProgress() { HUD.flash(.labeledProgress(title: "正在请求...", subtitle: nil)) } public func hideProgress() { HUD.hide(animated: true) } public func showTip(tip:String) { let tag = 10124 if let tipLabel = self.viewWithTag(tag) { tipLabel.removeFromSuperview() } let width = tip.qs_width(UIFont.systemFont(ofSize: 14), height: 30) let label = UILabel(frame: CGRect(x: ScreenWidth/2 - (width + 20)/2, y: self.bounds.height/2 - 15, width: width + 20, height: 30)) label.backgroundColor = UIColor.gray label.textAlignment = .center label.layer.cornerRadius = 15 label.clipsToBounds = true label.font = UIFont.systemFont(ofSize: 14) label.textColor = UIColor.white label.text = tip label.tag = tag self.addSubview(label) DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()+3) { label.removeFromSuperview() label.text = nil } } public func addShadow(cornerRadius:CGFloat) { self.layer.shadowColor = UIColor.black.cgColor self.layer.shadowRadius = cornerRadius self.layer.shadowOffset = CGSize(width: 0.5, height: 0.5) self.layer.shadowOpacity = 0.3 } static let shadowLayerAccess = "shadowLayerAccess" static let backgroundViewAccess = "backgroundViewAccess" func addShadow(borderRadius:CGFloat) { if self.bounds.equalTo(CGRect.zero) { return } for layer in self.layer.sublayers ?? [] { if layer.accessibilityLabel == UIView.shadowLayerAccess { layer.removeFromSuperlayer() } } for view in self.subviews { if view.accessibilityLabel == UIView.backgroundViewAccess { view.removeFromSuperview() } } let backgroundView = UIView(frame: self.bounds) backgroundView.backgroundColor = UIColor.clear backgroundView.isUserInteractionEnabled = true addSubview(backgroundView) let layer = CAShapeLayer() layer.frame = self.bounds let path = UIBezierPath(roundedRect: layer.bounds, cornerRadius: borderRadius) layer.shadowPath = path.cgPath layer.shadowColor = RGBAColor(0, 0, 0, 0.5).cgColor layer.shadowRadius = borderRadius layer.masksToBounds = false self.layer.insertSublayer(layer, below: backgroundView.layer) } } ================================================ FILE: zhuishushenqi/NewVersion/ZSExtension/ZSExtension/Classes/UIViewController+Alert.swift ================================================ // // UIAlertController+Alert.swift // zhuishushenqi // // Created by Nory Cao on 2017/4/18. // Copyright © 2017年 QS. All rights reserved. // import Foundation import UIKit import MBProgressHUD import ZSAppConfig typealias AlertCallback = (_ action:UIAlertAction)->Void extension UIViewController{ func alert(with title:String?,message:String?,okTitle:String?){ self.alert(with: title, message: message, okTitle: okTitle, cancelTitle: nil, okAction: nil, cancelAction: nil) } func alert(with title:String?,message:String?,okTitle:String?,okAction:AlertCallback?){ self.alert(with: title, message: message, okTitle: okTitle, cancelTitle: nil, okAction: okAction, cancelAction: nil) } func alert(with title:String?,message:String?,okTitle:String?,cancelTitle:String?,okAction:AlertCallback?,cancelAction:AlertCallback?){ let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) if let title = okTitle { let ok = UIAlertAction(title: title, style: .default) { (alertAction) in if let action = okAction { action(alertAction) } } alert.addAction(ok) } if let title = cancelTitle { let cancel = UIAlertAction(title: title, style: .default) { (alertAction) in if let action = cancelAction { action(alertAction) } } alert.addAction(cancel) } self.present(alert, animated: true, completion: nil) } func hudAddTo(view:UIView,text:String,animated:Bool){ let hud = MBProgressHUD.showAdded(to: view, animated: animated) hud.mode = .text hud.label.text = text hud.offset.y = ScreenHeight/4 hud.hide(animated: animated, afterDelay: 3.0) } func hudAddedTo(view:UIView,text:String,timeInterVal:TimeInterval,animated:Bool){ let hud = MBProgressHUD.showAdded(to: view, animated: animated) hud.mode = MBProgressHUDMode.indeterminate hud.label.text = text hud.offset.y = ScreenHeight/4 hud.hide(animated:animated, afterDelay:timeInterVal) } func pregressHUDTo(view:UIView,animated:Bool) ->Void { let hud = MBProgressHUD.showAdded(to: view, animated: animated) hud.mode = MBProgressHUDMode.indeterminate } } ================================================ FILE: zhuishushenqi/NewVersion/ZSExtension/ZSExtension/Classes/Value.swift ================================================ // // Value.swift // zhuishushenqi // // Created by caony on 2018/5/21. // Copyright © 2018年 QS. All rights reserved. // import Foundation struct Value { let base: Base } protocol ValueCompatible { var value: Value { get } } extension ValueCompatible { var value: Value { return Value(base: self) } } ================================================ FILE: zhuishushenqi/NewVersion/ZSExtension/ZSExtension/Classes/ZSBaseService.swift ================================================ // // ZSBaseService.swift // zhuishushenqi // // Created by yung on 2018/6/16. // Copyright © 2018年 QS. All rights reserved. // import Foundation public typealias ZSBaseCallback = (T?)->Void class ZSBaseService { } ================================================ FILE: zhuishushenqi/NewVersion/ZSExtension/ZSExtension.podspec ================================================ # # Be sure to run `pod lib lint ZSExtension.podspec' to ensure this is a # valid spec before submitting. # # Any lines starting with a # are optional, but their use is encouraged # To learn more about a Podspec see https://guides.cocoapods.org/syntax/podspec.html # Pod::Spec.new do |s| s.name = 'ZSExtension' s.version = '1.0.15' s.summary = 'ZSExtension.' # This description is used to generate tags and improve search results. # * Think: What does it do? Why did you write it? What is the focus? # * Try to keep it short, snappy and to the point. # * Write the description between the DESC delimiters below. # * Finally, don't worry about the indent, CocoaPods strips it! s.description = <<-DESC zhuishushenqi baseui module. DESC s.homepage = 'https://github.com/zssq/ZSExtension' # s.screenshots = 'www.example.com/screenshots_1', 'www.example.com/screenshots_2' s.license = { :type => 'MIT', :file => 'LICENSE' } s.author = { '2252055382@qq.com' => 'norycao' } s.source = { :git => 'https://github.com/zssq/ZSExtension.git', :tag => s.version.to_s } # s.social_media_url = 'https://twitter.com/norycao' s.ios.deployment_target = '10.0' s.swift_version = '5.0' s.source_files = 'ZSExtension/Classes/**/*' s.resource_bundles = { 'ZSExtension' => ['ZSExtension/Assets/*.png'] } #s.frameworks = 'UIKit', 'SystemConfiguration', 'Security', 'CoreTelephony', 'CFNetwork', 'CoreGraphics', 'CoreText', 'QuartzCore', 'ImageIO', 'Photos' #s.libraries = 'z', 'sqlite3.0', 'c++' #s.public_header_files = 'Pod/Classes/**/*.h' # s.frameworks = 'UIKit', 'MapKit' s.dependency "Alamofire" s.dependency "SQLite.swift" s.dependency "PKHUD" s.dependency "MBProgressHUD" s.dependency "ZSAppConfig" s.dependency "Zip" s.dependency "HandyJSON" s.dependency "Kingfisher" s.dependency "YYCategories" end ================================================ FILE: zhuishushenqi/NewVersion/ZSThirdPartSDK/LICENSE ================================================ Copyright (c) 2019 2252055382@qq.com Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: zhuishushenqi/NewVersion/ZSThirdPartSDK/ZSThirdPartSDK/Classes/TencentOpenAPI.framework/Headers/QQApiInterface.h ================================================ /// /// \file QQApiInterface.h /// \brief QQApi接口简化封装 /// /// Created by Tencent on 12-5-15. /// Copyright (c) 2012年 Tencent. All rights reserved. /// #import #import "QQApiInterfaceObject.h" /** \brief 处理来至QQ的请求及响应的回调协议 */ @protocol QQApiInterfaceDelegate /** 处理来至QQ的请求 */ - (void)onReq:(QQBaseReq *)req; /** 处理来至QQ的响应 */ - (void)onResp:(QQBaseResp *)resp; /** 处理QQ在线状态的回调 */ - (void)isOnlineResponse:(NSDictionary *)response; @end /** \brief 对QQApi的简单封装类 */ @interface QQApiInterface : NSObject /** 处理由手Q唤起的跳转请求 \param url 待处理的url跳转请求 \param delegate 第三方应用用于处理来至QQ请求及响应的委托对象 \return 跳转请求处理结果,YES表示成功处理,NO表示不支持的请求协议或处理失败 */ + (BOOL)handleOpenURL:(NSURL *)url delegate:(id)delegate; /** 向手Q发起分享请求 \param req 分享内容的请求 \return 请求发送结果码 */ + (QQApiSendResultCode)sendReq:(QQBaseReq *)req; /** 向手Q QZone结合版发起分享请求 \note H5分享只支持单张网络图片的传递 \param req 分享内容的请求 \return 请求发送结果码 */ + (QQApiSendResultCode)SendReqToQZone:(QQBaseReq *)req; /** 检测是否已安装QQ \return 如果QQ已安装则返回YES,否则返回NO \note SDK目前已经支持QQ、TIM授权登录及分享功能, 会按照QQ>TIM的顺序进行调用。 只要用户安装了QQ、TIM中任意一个应用,都可为第三方应用进行授权登录、分享功能。 第三方应用在接入SDK时不需要判断是否安装QQ、TIM。若有判断安装QQ、TIM的逻辑建议移除。 */ + (BOOL)isQQInstalled; /** 检测是否已安装TIM \return 如果TIM已安装则返回YES,否则返回NO \note SDK目前已经支持QQ、TIM授权登录及分享功能, 会按照QQ>TIM的顺序进行调用。 只要用户安装了QQ、TIM中任意一个应用,都可为第三方应用进行授权登录、分享功能。 第三方应用在接入SDK时不需要判断是否安装QQ、TIM。若有判断安装QQ、TIM的逻辑建议移除。 */ + (BOOL)isTIMInstalled; /** 检测QQ是否支持API调用 \return 如果当前安装QQ版本支持API调用则返回YES,否则返回NO */ + (BOOL)isQQSupportApi; /** 检测TIM是否支持API调用 \return 如果当前安装TIM版本支持API调用则返回YES,否则返回NO */ + (BOOL)isTIMSupportApi; /** 检测是否支持分享 \return 如果当前已安装QQ且QQ版本支持API调用 或者 当前已安装TIM且TIM版本支持API调用则返回YES,否则返回NO */ + (BOOL)isSupportShareToQQ; /** 检测是否支持分享到QQ结合版QZone \return 如果当前已安装QQ且QQ版本支持API调用则返回YES,否则返回NO */ + (BOOL)isSupportPushToQZone; /** 启动QQ \return 成功返回YES,否则返回NO */ + (BOOL)openQQ; /** 启动TIM \return 成功返回YES,否则返回NO */ + (BOOL)openTIM; /** 获取QQ下载地址 如果App通过QQApiInterface#isQQInstalledQQApiInterface#isQQSupportApi检测发现QQ没安装或当前版本QQ不支持API调用,可引导用户通过打开此链接下载最新版QQ。 \return iPhoneQQ下载地址 */ + (NSString *)getQQInstallUrl; /** 获取TIM下载地址 如果App通过QQApiInterface#isTIMInstalledQQApiInterface#isTIMSupportApi检测发现TIM没安装或当前版本TIM不支持API调用,可引导用户通过打开此链接下载最新版TIM。 \return iPhoneTIM下载地址 */ + (NSString *)getTIMInstallUrl; @end ================================================ FILE: zhuishushenqi/NewVersion/ZSThirdPartSDK/ZSThirdPartSDK/Classes/TencentOpenAPI.framework/Headers/QQApiInterfaceObject.h ================================================ /// /// \file QQApiInterfaceObject.h /// \brief QQApiInterface所依赖的请求及应答消息对象封装帮助类 /// /// Created by Tencent on 12-5-15. /// Copyright (c) 2012年 Tencent. All rights reserved. /// #ifndef QQApiInterface_QQAPIOBJECT_h #define QQApiInterface_QQAPIOBJECT_h #import typedef enum { EQQAPISENDSUCESS = 0, EQQAPIQQNOTINSTALLED = 1, //QQ未安装 EQQAPIQQNOTSUPPORTAPI = 2, // QQ api不支持 EQQAPIMESSAGETYPEINVALID = 3, EQQAPIMESSAGECONTENTNULL = 4, EQQAPIMESSAGECONTENTINVALID = 5, EQQAPIAPPNOTREGISTED = 6, EQQAPIAPPSHAREASYNC = 7, EQQAPIQQNOTSUPPORTAPI_WITH_ERRORSHOW = 8, //QQ api不支持 && SDK显示error提示(已废弃) EQQAPIMESSAGEARKCONTENTNULL = 9, //ark内容为空 EQQAPISENDFAILD = -1, //发送失败 EQQAPISHAREDESTUNKNOWN = -2, //未指定分享到QQ或TIM EQQAPITIMSENDFAILD = -3, //发送失败 EQQAPITIMNOTINSTALLED = 11, //TIM未安装 EQQAPITIMNOTSUPPORTAPI = 12, // TIM api不支持 EQQAPIQZONENOTSUPPORTTEXT = 10000, //qzone分享不支持text类型分享 EQQAPIQZONENOTSUPPORTIMAGE = 10001, //qzone分享不支持image类型分享 EQQAPIVERSIONNEEDUPDATE = 10002, //当前QQ版本太低,需要更新至新版本才可以支持 ETIMAPIVERSIONNEEDUPDATE = 10004, //当前TIM版本太低,需要更新至新版本才可以支持 } QQApiSendResultCode; #pragma mark - QQApiObject(分享对象类型) // QQApiObject control flags enum { kQQAPICtrlFlagQZoneShareOnStart = 0x01, kQQAPICtrlFlagQZoneShareForbid = 0x02, kQQAPICtrlFlagQQShare = 0x04, kQQAPICtrlFlagQQShareFavorites = 0x08, //收藏 kQQAPICtrlFlagQQShareDataline = 0x10, //数据线 kQQAPICtrlFlagQQShareEnableArk = 0x20, //支持ARK }; // 分享到QQ或TIM typedef enum ShareDestType { ShareDestTypeQQ = 0, ShareDestTypeTIM, }ShareDestType; // QQApiObject /** \brief 所有在QQ及插件间发送的数据对象的根类。 */ __attribute__((visibility("default"))) @interface QQApiObject : NSObject @property(nonatomic,retain) NSString* title; ///< 标题,最长128个字符 @property(nonatomic,retain) NSString* description; ///<简要描述,最长512个字符 @property (nonatomic, assign) uint64_t cflag; /* * 分享到QQ/TIM * SDK根据是否安装对应客户端进行判断,判断顺序:QQ > TIM * 默认分享到QQ,如果QQ未安装检测TIM是否安装 */ @property (nonatomic, assign) ShareDestType shareDestType; @end // ArkObject /** \brief 支持Ark的根类。 */ __attribute__((visibility("default"))) @interface ArkObject : NSObject @property(nonatomic,retain) NSString* arkData; ///< 显示Ark所需的数据,json串,长度暂不限制 @property(nonatomic,assign) QQApiObject* qqApiObject; ///<原有老版本的QQApiObject - (id)initWithData:(NSString *)arkData qqApiObject:(QQApiObject*)qqApiObject; + (id)objectWithData:(NSString *)arkData qqApiObject:(QQApiObject*)qqApiObject; @end // QQApiResultObject /** \brief 用于请求回应的数据类型。

可能错误码及描述如下:

errorerrorDescription注释
0nil成功
-1param error参数错误
-2group code is invalid该群不在自己的群列表里面
-3upload photo failed上传图片失败
-4user give up the current operation用户放弃当前操作
-5client internal error客户端内部处理错误
*/ __attribute__((visibility("default"))) @interface QQApiResultObject : QQApiObject @property(nonatomic,retain) NSString* error; ///<错误 @property(nonatomic,retain) NSString* errorDescription; ///<错误描述 @property(nonatomic,retain) NSString* extendInfo; ///<扩展信息 @end // QQApiTextObject /** \brief 文本对象 */ @interface QQApiTextObject : QQApiObject @property(nonatomic,retain)NSString* text; ///<文本内容,必填,最长1536个字符 -(id)initWithText:(NSString*)text; ///<初始化方法 +(id)objectWithText:(NSString*)text;///<工厂方法,获取一个QQApiTextObject对象. @end // QQApiURLObject typedef enum QQApiURLTargetType{ QQApiURLTargetTypeNotSpecified = 0x00, QQApiURLTargetTypeAudio = 0x01, QQApiURLTargetTypeVideo = 0x02, QQApiURLTargetTypeNews = 0x03 }QQApiURLTargetType; /** @brief URL对象类型。 包括URL地址,URL地址所指向的目标类型及预览图像。 */ __attribute__((visibility("default"))) @interface QQApiURLObject : QQApiObject /** URL地址所指向的目标类型. @note 参见QQApi.h 中的 QQApiURLTargetType 定义. */ @property(nonatomic)QQApiURLTargetType targetContentType; @property(nonatomic,retain)NSURL* url; ///QQApiExtendObject对象 @param data 数据内容 @param previewImageData 用于预览的图片 @param title 标题 @param description 此对象,分享的描述 @return 一个自动释放的QQApiExtendObject实例 */ + (id)objectWithData:(NSData*)data previewImageData:(NSData*)previewImageData title:(NSString*)title description:(NSString*)description; /** helper方法获取一个autorelease的QQApiExtendObject对象 @param data 数据内容 @param previewImageData 用于预览的图片 @param title 标题 @param description 此对象,分享的描述 @param imageDataArray 发送的多张图片队列 @return 一个自动释放的QQApiExtendObject实例 */ + (id)objectWithData:(NSData*)data previewImageData:(NSData*)previewImageData title:(NSString*)title description:(NSString*)description imageDataArray:(NSArray*)imageDataArray; @end // QQApiImageObject /** @brief 图片对象 用于分享图片内容的对象,是一个指定为图片类型的QQApiExtendObject */ @interface QQApiImageObject : QQApiExtendObject @end // QQApiImageArrayForQZoneObject /** @brief 图片对象 用于分享图片到空间,走写说说路径,是一个指定为图片类型的,当图片数组为空时,默认走文本写说说QQApiObject */ @interface QQApiImageArrayForQZoneObject : QQApiObject @property(nonatomic,retain) NSArray* imageDataArray;///图片数组 @property(nonatomic,retain) NSDictionary* extMap; // 扩展字段 /** 初始化方法 @param imageDataArray 图片数组 @param title 写说说的内容,可以为空 @param extMap 扩展字段 */ - (id)initWithImageArrayData:(NSArray*)imageDataArray title:(NSString*)title extMap:(NSDictionary *)extMap; /** helper方法获取一个autorelease的QQApiExtendObject对象 @param title 写说说的内容,可以为空 @param imageDataArray 发送的多张图片队列 @param extMap 扩展字段 @return 一个自动释放的QQApiExtendObject实例 */ + (id)objectWithimageDataArray:(NSArray*)imageDataArray title:(NSString*)title extMap:(NSDictionary *)extMap; @end // QQApiVideoForQZoneObject /** @brief 视频对象 用于分享视频到空间,走写说说路径QQApiObject assetURL可传ALAsset的ALAssetPropertyAssetURL,或者PHAsset的localIdentifier @param extMap 扩展字段 */ @interface QQApiVideoForQZoneObject : QQApiObject @property(nonatomic, retain) NSString *assetURL; @property(nonatomic,retain) NSDictionary* extMap; // 扩展字段 - (id)initWithAssetURL:(NSString*)assetURL title:(NSString*)title extMap:(NSDictionary *)extMap; + (id)objectWithAssetURL:(NSString*)assetURL title:(NSString*)title extMap:(NSDictionary *)extMap; @end // QQApiWebImageObject /** @brief 图片对象 用于分享网络图片内容的对象,是一个指定网络图片url的: 该类型只在2.9.0的h5分享中才支持, 原有的手q分享是不支持该类型的。 */ @interface QQApiWebImageObject : QQApiObject @property(nonatomic, retain) NSURL *previewImageURL; ///<预览图像URL /** 初始化方法 @param previewImageURL 用于预览的图片 @param title 标题 @param description 此对象,分享的描述 */ - (id)initWithPreviewImageURL:(NSURL*)previewImageURL title:(NSString*)title description:(NSString*)description; /** helper方法获取一个autorelease的QQApiWebImageObject对象 @param previewImageURL 用于预览的图片 @param title 标题 @param description 此对象,分享的描述 */ + (id)objectWithPreviewImageURL:(NSURL*)previewImageURL title:(NSString*)title description:(NSString*)description; @end //QQApiFileObject /** @brief 本地文件对象(暂只支持分享到手机QQ数据线功能) 用于分享文件内容的对象,是一个指定为文件类型的QQApiExtendObject */ @interface QQApiFileObject : QQApiExtendObject { NSString* _fileName; } @property(nonatomic, retain)NSString* fileName; @end // QQApiAudioObject /** @brief 音频URL对象 用于分享目标内容为音频的URL的对象 */ @interface QQApiAudioObject : QQApiURLObject @property (nonatomic, retain) NSURL *flashURL; ///<音频URL地址,最长512个字符 /** 获取一个autorelease的QQApiAudioObject @param url 音频内容的目标URL @param title 分享内容的标题 @param description 分享内容的描述 @param data 分享内容的预览图像 @note 如果url为空,调用QQApi#sendMessage:时将返回FALSE */ +(id)objectWithURL:(NSURL*)url title:(NSString*)title description:(NSString*)description previewImageData:(NSData*)data; /** 获取一个autorelease的QQApiAudioObject @param url 音频内容的目标URL @param title 分享内容的标题 @param description 分享内容的描述 @param previewURL 分享内容的预览图像URL @note 如果url为空,调用QQApi#sendMessage:时将返回FALSE */ +(id)objectWithURL:(NSURL*)url title:(NSString*)title description:(NSString*)description previewImageURL:(NSURL*)previewURL; @end // QQApiVideoObject /** @brief 视频URL对象 用于分享目标内容为视频的URL的对象 QQApiVideoObject类型的分享,目前在Android和PC QQ上接收消息时,展现有待完善,待手机QQ版本以后更新支持 目前如果要分享视频,推荐使用 QQApiNewsObject 类型 */ @interface QQApiVideoObject : QQApiURLObject @property (nonatomic, retain) NSURL *flashURL; ///<视频URL地址,最长512个字符 /** 获取一个autorelease的QQApiVideoObject @param url 视频内容的目标URL @param title 分享内容的标题 @param description 分享内容的描述 @param data 分享内容的预览图像 @note 如果url为空,调用QQApi#sendMessage:时将返回FALSE */ +(id)objectWithURL:(NSURL*)url title:(NSString*)title description:(NSString*)description previewImageData:(NSData*)data; /** 获取一个autorelease的QQApiVideoObject @param url 视频内容的目标URL @param title 分享内容的标题 @param description 分享内容的描述 @param previewURL 分享内容的预览图像URL @note 如果url为空,调用QQApi#sendMessage:时将返回FALSE */ +(id)objectWithURL:(NSURL*)url title:(NSString*)title description:(NSString*)description previewImageURL:(NSURL*)previewURL; @end // QQApiNewsObject /** @brief 新闻URL对象 用于分享目标内容为新闻的URL的对象 */ @interface QQApiNewsObject : QQApiURLObject /** 获取一个autorelease的QQApiNewsObject @param url 视频内容的目标URL @param title 分享内容的标题 @param description 分享内容的描述 @param data 分享内容的预览图像 @note 如果url为空,调用QQApi#sendMessage:时将返回FALSE */ +(id)objectWithURL:(NSURL*)url title:(NSString*)title description:(NSString*)description previewImageData:(NSData*)data; /** 获取一个autorelease的QQApiNewsObject @param url 视频内容的目标URL @param title 分享内容的标题 @param description 分享内容的描述 @param previewURL 分享内容的预览图像URL @note 如果url为空,调用QQApi#sendMessage:时将返回FALSE */ +(id)objectWithURL:(NSURL*)url title:(NSString*)title description:(NSString*)description previewImageURL:(NSURL*)previewURL; @end // QQApiCommonContentObject; /** @brief 通用模板类型对象 用于分享一个固定显示模板的图文混排对象 @note 图片列表和文本列表不能同时为空 */ @interface QQApiCommonContentObject : QQApiObject /** 预定义的界面布局类型 */ @property(nonatomic,assign) unsigned int layoutType; @property(nonatomic,assign) NSData* previewImageData;///<预览图 @property(nonatomic,retain) NSArray* textArray;///<文本列表 @property(nonatomic,retain) NSArray* pictureDataArray;///<图片列表 +(id)objectWithLayoutType:(int)layoutType textArray:(NSArray*)textArray pictureArray:(NSArray*)pictureArray previewImageData:(NSData*)data; /** 将一个NSDictionary对象转化为QQApiCommomContentObject,如果无法转换,则返回空 */ +(id)objectWithDictionary:(NSDictionary*)dic; -(NSDictionary*)toDictionary; @end //////////////////////////////////////////////////////////////////////////////////////////////////////////// // Ad item object definition //////////////////////////////////////////////////////////////////////////////////////////////////////////// /** @brief 广告数据对象 */ @interface QQApiAdItem : NSObject @property(nonatomic,retain) NSString* title; ///<名称 @property(nonatomic,retain) NSString* description;///<描述 @property(nonatomic,retain) NSData* imageData;///<广告图片 @property(nonatomic,retain) NSURL* target;///<广告目标链接 @end #pragma mark - QQApiObject(关系链对象) // QQApiAddFriendObject /** \brief 添加好友 */ @interface QQApiAddFriendObject : QQApiObject @property (nonatomic,retain)NSString* openID; @property (nonatomic,retain)NSString* subID; @property (nonatomic,retain)NSString* remark; -(id)initWithOpenID:(NSString*)openID; ///<初始化方法 +(id)objecWithOpenID:(NSString*)openID; ///<工厂方法,获取一个QQApiAddFriendObject对象. @end // QQApiGameConsortiumBindingGroupObject /** \brief 游戏公会绑定群 */ @interface QQApiGameConsortiumBindingGroupObject : QQApiObject @property (nonatomic,retain)NSString* signature; @property (nonatomic,retain)NSString* unionid; @property (nonatomic,retain)NSString* zoneID; @property (nonatomic,retain)NSString* appDisplayName; -(id)initWithGameConsortium:(NSString*)signature unionid:(NSString*)unionid zoneID:(NSString*)zoneID appDisplayName:(NSString*)appDisplayName; ///<初始化方法 +(id)objectWithGameConsortium:(NSString*)signature unionid:(NSString*)unionid zoneID:(NSString*)zoneID appDisplayName:(NSString*)appDisplayName; ///<工厂方法,获取一个QQApiAddFriendObject对象. @end // QQApiGameConsortiumBindingGroupObject /** \brief 加入群 */ @interface QQApiJoinGroupObject : QQApiObject @property (nonatomic,retain)NSString* groupUin; @property (nonatomic,retain)NSString* groupKey; - (id)initWithGroupInfo:(NSString*)groupUin key:(NSString*)groupKey; ///<初始化方法 + (id)objectWithGroupInfo:(NSString*)groupUin key:(NSString*)groupKey; ///<同时提供群号和群KEY 工厂方法,获取一个QQApiAddFriendObject对象. + (id)objectWithGroupKey:(NSString*)groupKey; ///<只需要群的KEY 工厂方法,获取一个QQApiAddFriendObject对象. @end #pragma mark - QQApi请求消息类型 /** QQApi请求消息类型 */ enum QQApiInterfaceReqType { EGETMESSAGEFROMQQREQTYPE = 0, ///< 手Q -> 第三方应用,请求第三方应用向手Q发送消息 ESENDMESSAGETOQQREQTYPE = 1, ///< 第三方应用 -> 手Q,第三方应用向手Q分享消息 ESHOWMESSAGEFROMQQREQTYPE = 2, ///< 手Q -> 第三方应用,请求第三方应用展现消息中的数据 ESENDMESSAGEARKTOQQREQTYPE = 3 ///< 第三方应用 -> 手Q,第三方应用向手Q分享Ark消息 }; /** QQApi应答消息类型 */ enum QQApiInterfaceRespType { ESHOWMESSAGEFROMQQRESPTYPE = 0, ///< 第三方应用 -> 手Q,第三方应用应答消息展现结果 EGETMESSAGEFROMQQRESPTYPE = 1, ///< 第三方应用 -> 手Q,第三方应用回应发往手Q的消息 ESENDMESSAGETOQQRESPTYPE = 2 ///< 手Q -> 第三方应用,手Q应答处理分享消息的结果 }; /** QQApi请求消息基类 */ @interface QQBaseReq : NSObject /** 请求消息类型,参见\ref QQApiInterfaceReqType */ @property (nonatomic, assign) int type; @end /** QQApi应答消息基类 */ @interface QQBaseResp : NSObject /** 请求处理结果 */ @property (nonatomic, copy) NSString* result; /** 具体错误描述信息 */ @property (nonatomic, copy) NSString* errorDescription; /** 应答消息类型,参见\ref QQApiInterfaceRespType */ @property (nonatomic, assign) int type; /** 扩展信息 */ @property (nonatomic, assign) NSString* extendInfo; @end /** GetMessageFromQQReq请求帮助类 */ @interface GetMessageFromQQReq : QQBaseReq /** 创建一个GetMessageFromQQReq请求实例 */ + (GetMessageFromQQReq *)req; @end @interface SendMessageToQQReq : QQBaseReq /** 创建一个SendMessageToQQReq请求实例 \param message 具体分享消息实例 \return 新创建的SendMessageToQQReq请求实例 */ + (SendMessageToQQReq *)reqWithContent:(QQApiObject *)message; /** 创建一个支持Ark的SendMessageToQQReq请求实例 \param message 具体分享消息实例 \return 新创建的SendMessageToQQReq请求实例 */ + (SendMessageToQQReq *)reqWithArkContent:(ArkObject *)message; /** 具体分享消息 */ @property (nonatomic, retain) QQApiObject *message; /** 支持Ark的具体分享消息 */ @property (nonatomic, retain) ArkObject *arkMessage; @end /** SendMessageToQQResp应答帮助类 */ @interface SendMessageToQQResp : QQBaseResp /** 创建一个SendMessageToQQResp应答实例 \param result 请求处理结果 \param errDesp 具体错误描述信息 \param extendInfo 扩展信息 \return 新创建的SendMessageToQQResp应答实例 */ + (SendMessageToQQResp *)respWithResult:(NSString *)result errorDescription:(NSString *)errDesp extendInfo:(NSString*)extendInfo; @end /** ShowMessageFromQQReq请求帮助类 */ @interface ShowMessageFromQQReq : QQBaseReq /** 创建一个ShowMessageFromQQReq请求实例 \param message 具体待展现消息实例 \return 新创建的ShowMessageFromQQReq请求实例 */ + (ShowMessageFromQQReq *)reqWithContent:(QQApiObject *)message; /** 具体待展现消息 */ @property (nonatomic, retain) QQApiObject *message; @end #endif ================================================ FILE: zhuishushenqi/NewVersion/ZSThirdPartSDK/ZSThirdPartSDK/Classes/TencentOpenAPI.framework/Headers/TencentOAuth.h ================================================ /// /// \file TencentOAuth.h /// \brief QQ互联开放平台授权登录及相关开放接口实现类 /// /// Created by Tencent on 12-12-21. /// Copyright (c) 2012年 Tencent. All rights reserved. /// #import #import "sdkdef.h" @protocol TencentSessionDelegate; @protocol TencentLoginDelegate; @protocol TencentApiInterfaceDelegate; @protocol TencentWebViewDelegate; @class TencentApiReq; @class TencentApiResp; typedef enum { kTencentNotAuthorizeState, kTencentSSOAuthorizeState, kTencentWebviewAuthorzieState, } TencentAuthorizeState; typedef enum { kAuthModeClientSideToken, kAuthModeServerSideCode, } TencentAuthMode; #pragma mark - TencentOAuth(授权登录及相关开放接口调用) /** * \brief TencentOpenAPI授权登录及相关开放接口调用 * * TencentOAuth实现授权登录逻辑以及相关开放接口的请求调用 */ @interface TencentOAuth : NSObject { NSMutableDictionary* _apiRequests; NSString* _accessToken; NSDate* _expirationDate; id _sessionDelegate; NSString* _localAppId; NSString* _openId; NSString* _redirectURI; NSArray* _permissions; } /** Access Token凭证,用于后续访问各开放接口 */ @property(nonatomic, copy) NSString* accessToken; /** Access Token的失效期 */ @property(nonatomic, copy) NSDate* expirationDate; /** 已实现的开放接口的回调委托对象 */ @property(nonatomic, assign) id sessionDelegate; /** 第三方应用在开发过程中设置的URLSchema,用于浏览器登录后后跳到第三方应用 */ @property(nonatomic, copy) NSString* localAppId; /** 用户授权登录后对该用户的唯一标识 */ @property(nonatomic, copy) NSString* openId; /** 用户登录成功过后的跳转页面地址 */ @property(nonatomic, copy) NSString* redirectURI; /** 第三方应用在互联开放平台申请的appID */ @property(nonatomic, retain) NSString* appId; /** 主要是互娱的游戏设置uin */ @property(nonatomic, retain) NSString* uin; /** 主要是互娱的游戏设置鉴定票据 */ @property(nonatomic, retain) NSString* skey; /** 登陆透传的数据 */ @property(nonatomic, copy) NSDictionary* passData; /** 授权方式(Client Side Token或者Server Side Code) */ @property(nonatomic, assign) TencentAuthMode authMode; /** union id */ @property(nonatomic, retain) NSString* unionid; /** 第三方在授权登录/分享 时选择 QQ,还是TIM 。在授权前一定要指定其中一个类型*/ @property(nonatomic, assign) TencentAuthShareType authShareType; /** * 获取上次登录得到的token * **/ - (NSString *)getCachedToken; /** * 获取上次登录得到的openid * **/ - (NSString *)getCachedOpenID; /** * 获取上次登录的token过期日期 * **/ - (NSDate *)getCachedExpirationDate; /** * 上次登录的token是否过期 * **/ - (BOOL)isCachedTokenValid; /** * 删除上次登录登录的token信息 * **/ - (BOOL)deleteCachedToken; /** * 用来获得当前sdk的版本号 * \return 返回sdk版本号 **/ + (NSString*)sdkVersion; /** * 用来获得当前sdk的小版本号 * \return 返回sdk小版本号 **/ + (NSString*)sdkSubVersion; /** * 用来获得当前sdk的是否精简版 * \return 返回YES表示精简版 **/ + (BOOL)isLiteSDK; /** * 主要是用来帮助判断是否有登陆被发起,但是还没有过返回结果 * \return * kTencentNotAuthorizeState:无授权 * kTencentSSOAuthorizeState:有人发起了sso授权但无返回 * kTencentWebviewAuthorzieState:有人发起了webview授权还未返回 **/ + (TencentAuthorizeState *)authorizeState; /** * 用来获得当前手机qq的版本号 * \return 返回手机qq版本号 **/ + (QQVersion)iphoneQQVersion; /** * 用来获得当前手机TIM的版本号 * \return 返回手机qq版本号 **/ + (QQVersion)iphoneTIMVersion; /** * 初始化TencentOAuth对象 * \param appId 第三方应用在互联开放平台申请的唯一标识 * \param delegate 第三方应用用于接收请求返回结果的委托对象 * \return 初始化后的授权登录对象 */ - (id)initWithAppId:(NSString *)appId andDelegate:(id)delegate; /** * 判断用户手机上是否安装手机QQ * \return YES:安装 NO:没安装 * * \note SDK目前已经支持QQ、TIM授权登录及分享功能, 会按照QQ>TIM的顺序进行调用。 * 只要用户安装了QQ、TIM中任意一个应用,都可为第三方应用进行授权登录、分享功能。 * 第三方应用在接入SDK时不需要判断是否安装QQ、TIM。若有判断安装QQ、TIM的逻辑建议移除。 */ + (BOOL)iphoneQQInstalled; /** * 判断用户手机上是否安装手机TIM * \return YES:安装 NO:没安装 * * \note SDK目前已经支持QQ、TIM授权登录及分享功能, 会按照QQ>TIM的顺序进行调用。 * 只要用户安装了QQ、TIM中任意一个应用,都可为第三方应用进行授权登录、分享功能。 * 第三方应用在接入SDK时不需要判断是否安装QQ、TIM。若有判断安装QQ、TIM的逻辑建议移除。 */ + (BOOL)iphoneTIMInstalled; /** * 判断用户手机上的手机QQ是否支持SSO登录 * \return YES:支持 NO:不支持 */ + (BOOL)iphoneQQSupportSSOLogin; /** * 判断用户手机上的手机TIM是否支持SSO登录 * \return YES:支持 NO:不支持 */ + (BOOL)iphoneTIMSupportSSOLogin; /** * 登录授权 * * \param permissions 授权信息列 */ - (BOOL)authorize:(NSArray *)permissions; /** * 登录授权 * \param permissions 授权信息列表 * \param bInSafari 是否使用safari进行登录.IOS SDK 1.3版本开始此参数废除 */ - (BOOL)authorize:(NSArray *)permissions inSafari:(BOOL)bInSafari; /** * 登录授权 * \param permissions 授权信息列表 * \param localAppId 应用APPID * \param bInSafari 是否使用safari进行登录.IOS SDK 1.3版本开始此参数废除 */ - (BOOL)authorize:(NSArray *)permissions localAppId:(NSString *)localAppId inSafari:(BOOL)bInSafari; /** * 登录授权 * * \param permissions 授权信息列 */ - (BOOL)authorizeWithQRlogin:(NSArray *)permissions; /** * 增量授权,因用户没有授予相应接口调用的权限,需要用户确认是否授权 * \param permissions 需增量授权的信息列表 * \return 增量授权调用是否成功 */ - (BOOL)incrAuthWithPermissions:(NSArray *)permissions; /** * 重新授权,因token废除或失效导致接口调用失败,需用户重新授权 * \param permissions 授权信息列表,同登录授权 * \return 授权调用是否成功 */ - (BOOL)reauthorizeWithPermissions:(NSArray *)permissions; /** * 获取UnindID,可以根据UnindID的比较来确定OpenID是否属于同一个用户 * \return NO未登录,信息不足;YES条件满足,发送请求成功,请等待回调 */ - (BOOL)RequestUnionId; /** * (静态方法)处理应用拉起协议 * \param url 处理被其他应用呼起时的逻辑 * \return 处理结果,YES表示成功,NO表示失败 */ + (BOOL)HandleOpenURL:(NSURL *)url; /** * (静态方法)sdk是否可以处理应用拉起协议 * \param url 处理被其他应用呼起时的逻辑 * \return 处理结果,YES表示可以 NO表示不行 */ + (BOOL)CanHandleOpenURL:(NSURL *)url; /** * (静态方法)获取TencentOAuth调用的上一次错误信息 */ + (NSString *)getLastErrorMsg; /** * 以Server Side Code模式授权登录时,通过此接口获取返回的code值; * 以Client Side Token模式授权登录时,忽略此接口。 */ - (NSString *)getServerSideCode; /** * 退出登录(退出登录后,TecentOAuth失效,需要重新初始化) * \param delegate 第三方应用用于接收请求返回结果的委托对象 */ - (void)logout:(id)delegate; /** * 判断登录态是否有效 * \return 处理结果,YES表示有效,NO表示无效,请用户重新登录授权 */ - (BOOL)isSessionValid; /** * 获取用户个人信息 * \return 处理结果,YES表示API调用成功,NO表示API调用失败,登录态失败,重新登录 */ - (BOOL)getUserInfo; /** * 退出指定API调用 * \param userData 用户调用某条API的时候传入的保留参数 * \return 处理结果,YES表示成功 NO表示失败 */ - (BOOL)cancel:(id)userData; /** * CGI类任务创建接口 * \param apiURL CGI请求的URL地址 * \param method CGI请求方式:"GET","POST" * \param params CGI请求参数字典 * \param callback CGI请求结果的回调接口对象 * \return CGI请求任务实例,用于取消任务,返回nil代表任务创建失败 */ - (TCAPIRequest *)cgiRequestWithURL:(NSURL *)apiURL method:(NSString *)method params:(NSDictionary *)params callback:(id)callback; /** * TencentOpenApi发送任务统一接口 * \param request 请求发送的任务 * \param callback 任务发送后的回调地址 */ - (BOOL)sendAPIRequest:(TCAPIRequest *)request callback:(id)callback; - (NSString *)getUserOpenID; @end #pragma mark - TencentLoginDelegate(授权登录回调协议) /** * \brief TencentLoginDelegate iOS Open SDK 1.3 API回调协议 * * 第三方应用实现登录的回调协议 */ @protocol TencentLoginDelegate @required /** * 登录成功后的回调 */ - (void)tencentDidLogin; /** * 登录失败后的回调 * \param cancelled 代表用户是否主动退出登录 */ - (void)tencentDidNotLogin:(BOOL)cancelled; /** * 登录时网络有问题的回调 */ - (void)tencentDidNotNetWork; @optional /** * 登录时权限信息的获得 */ - (NSArray *)getAuthorizedPermissions:(NSArray *)permissions withExtraParams:(NSDictionary *)extraParams; /** * unionID获得 */ - (void)didGetUnionID; @end #pragma mark - TencentSessionDelegate(开放接口回调协议) /** * \brief TencentSessionDelegate iOS Open SDK 1.3 API回调协议 * * 第三方应用需要实现每条需要调用的API的回调协议 */ @protocol TencentSessionDelegate @optional /** * 退出登录的回调 */ - (void)tencentDidLogout; /** * 因用户未授予相应权限而需要执行增量授权。在用户调用某个api接口时,如果服务器返回操作未被授权,则触发该回调协议接口,由第三方决定是否跳转到增量授权页面,让用户重新授权。 * \param tencentOAuth 登录授权对象。 * \param permissions 需增量授权的权限列表。 * \return 是否仍然回调返回原始的api请求结果。 * \note 不实现该协议接口则默认为不开启增量授权流程。若需要增量授权请调用\ref TencentOAuth#incrAuthWithPermissions: \n注意:增量授权时用户可能会修改登录的帐号 */ - (BOOL)tencentNeedPerformIncrAuth:(TencentOAuth *)tencentOAuth withPermissions:(NSArray *)permissions; /** * [该逻辑未实现]因token失效而需要执行重新登录授权。在用户调用某个api接口时,如果服务器返回token失效,则触发该回调协议接口,由第三方决定是否跳转到登录授权页面,让用户重新授权。 * \param tencentOAuth 登录授权对象。 * \return 是否仍然回调返回原始的api请求结果。 * \note 不实现该协议接口则默认为不开启重新登录授权流程。若需要重新登录授权请调用\ref TencentOAuth#reauthorizeWithPermissions: \n注意:重新登录授权时用户可能会修改登录的帐号 */ - (BOOL)tencentNeedPerformReAuth:(TencentOAuth *)tencentOAuth; /** * 用户通过增量授权流程重新授权登录,token及有效期限等信息已被更新。 * \param tencentOAuth token及有效期限等信息更新后的授权实例对象 * \note 第三方应用需更新已保存的token及有效期限等信息。 */ - (void)tencentDidUpdate:(TencentOAuth *)tencentOAuth; /** * 用户增量授权过程中因取消或网络问题导致授权失败 * \param reason 授权失败原因,具体失败原因参见sdkdef.h文件中\ref UpdateFailType */ - (void)tencentFailedUpdate:(UpdateFailType)reason; /** * 获取用户个人信息回调 * \param response API返回结果,具体定义参见sdkdef.h文件中\ref APIResponse * \remarks 正确返回示例: \snippet example/getUserInfoResponse.exp success * 错误返回示例: \snippet example/getUserInfoResponse.exp fail */ - (void)getUserInfoResponse:(APIResponse*) response; /** * 社交API统一回调接口 * \param response API返回结果,具体定义参见sdkdef.h文件中\ref APIResponse * \param message 响应的消息,目前支持‘SendStory’,‘AppInvitation’,‘AppChallenge’,‘AppGiftRequest’ */ - (void)responseDidReceived:(APIResponse*)response forMessage:(NSString *)message; /** * post请求的上传进度 * \param tencentOAuth 返回回调的tencentOAuth对象 * \param bytesWritten 本次回调上传的数据字节数 * \param totalBytesWritten 总共已经上传的字节数 * \param totalBytesExpectedToWrite 总共需要上传的字节数 * \param userData 用户自定义数据 */ - (void)tencentOAuth:(TencentOAuth *)tencentOAuth didSendBodyData:(NSInteger)bytesWritten totalBytesWritten:(NSInteger)totalBytesWritten totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite userData:(id)userData; /** * 通知第三方界面需要被关闭 * \param tencentOAuth 返回回调的tencentOAuth对象 * \param viewController 需要关闭的viewController */ - (void)tencentOAuth:(TencentOAuth *)tencentOAuth doCloseViewController:(UIViewController *)viewController; @end #pragma mark - TencentWebViewDelegate(H5登录webview旋转方向回调) /** * \brief TencentWebViewDelegate: H5登录webview旋转方向回调协议 * * 第三方应用可以根据自己APP的旋转方向限制,通过此协议设置 */ @protocol TencentWebViewDelegate @optional - (BOOL) tencentWebViewShouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation; - (NSUInteger) tencentWebViewSupportedInterfaceOrientationsWithWebkit; - (BOOL) tencentWebViewShouldAutorotateWithWebkit; @end ================================================ FILE: zhuishushenqi/NewVersion/ZSThirdPartSDK/ZSThirdPartSDK/Classes/TencentOpenAPI.framework/Headers/sdkdef.h ================================================ /// /// \file sdkdef.h /// \brief SDK中相关常量定义 /// /// Created by Tencent on 12-12-25. /// Copyright (c) 2012年 Tencent. All rights reserved. /// #import #import /** * \brief 设置sdk的log等级 */ typedef enum { TCOLogLevel_Disabled = -1, // 关闭所有log TCOLogLevel_Error = 0, TCOLogLevel_Warning, TCOLogLevel_Info, TCOLogLevel_Debug, } TCOLogLevel; /** * \brief 手机qq的当前版本 */ typedef enum QQVersion { kQQUninstall, kQQVersion3_0, kQQVersion4_0, //支持sso登陆 kQQVersion4_2_1, //ios7兼容 kQQVersion4_5, //4.5版本,wpa会话 kQQVersion4_6, //4.6版本,sso登陆信令通道切换 kQQVersion4_7, //4.7版本 不确定新支持了什么样的属性 } QQVersion; /** * \breif TIM的当前版本 */ typedef enum TIMVersion { kTIMUinstall, kTIMVersion1_1, }TIMVersion; /** * \breif 授权/分享 方式 */ typedef enum TencentAuthShareType { AuthShareType_QQ, AuthShareType_TIM, }TencentAuthShareType; /** * \brief APIResponse.retCode可能的枚举常量 */ typedef enum { URLREQUEST_SUCCEED = 0, /**< 网络请求成功发送至服务器,并且服务器返回数据格式正确 * \note 这里包括所请求业务操作失败的情况,例如没有授权等原因导致 */ URLREQUEST_FAILED = 1, /**< 网络异常,或服务器返回的数据格式不正确导致无法解析 */ } REPONSE_RESULT; /** * \brief 增量授权失败原因 * * \note 增量授权失败不影响原token的有效性(原token已失效的情况除外) */ typedef enum { kUpdateFailUnknown = 1, ///< 未知原因 kUpdateFailUserCancel, ///< 用户取消 kUpdateFailNetwork, ///< 网络问题 } UpdateFailType; /** * \brief 封装服务器返回的结果 * * APIResponse用于封装所有请求的返回结果,包括错误码、错误信息、原始返回数据以及返回数据的json格式字典 */ @interface APIResponse : NSObject { int _detailRetCode; int _retCode; int _seq; NSString *_errorMsg; NSDictionary *_jsonResponse; NSString *_message; id _userData; } /** * 新增的详细错误码\n * detailRetCode主要用于区分不同的错误情况,参见\ref OpenSDKError */ @property (nonatomic, assign) int detailRetCode; /** * 网络请求是否成功送达服务器,以及服务器返回的数据格式是否正确\n * retCode具体取值可参考\ref REPONSE_RESULT */ @property (nonatomic, assign) int retCode; /** * 网络请求对应的递增序列号,方便内部管理 */ @property (nonatomic, assign) int seq; /** * 错误提示语 */ @property (nonatomic, retain) NSString *errorMsg; /** * 服务器返回数据的json格式字典\n * 字典内具体参数的命名和含义请参考\ref api_spec */ @property (nonatomic, retain) NSDictionary *jsonResponse; /** * 服务器返回的原始数据字符串 */ @property (nonatomic, retain) NSString *message; /** * 用户保留数据 */ @property (nonatomic, retain) id userData; @end /** * 用户自定义的保留字段 */ FOUNDATION_EXTERN NSString * const PARAM_USER_DATA; /** * \name 应用邀请参数字段定义 */ ///@{ /** 应用邀请展示图片url的key */ FOUNDATION_EXTERN NSString * const PARAM_APP_ICON; /** 应用邀请描述文本的key */ FOUNDATION_EXTERN NSString * const PARAM_APP_DESC; /** 应用邀请好友列表的key */ FOUNDATION_EXTERN NSString * const PARAM_APP_INVITED_OPENIDS; ///@} /** * \name sendStory新分享参数字段定义 */ ///@{ /** 预填入接受人列表的key */ FOUNDATION_EXTERN NSString * const PARAM_SENDSTORY_RECEIVER; /** 分享feeds标题的key */ FOUNDATION_EXTERN NSString * const PARAM_SENDSTORY_TITLE; /** 分享feeds评论内容的key */ FOUNDATION_EXTERN NSString * const PARAM_SENDSTORY_COMMENT; /** 分享feeds摘要的key */ FOUNDATION_EXTERN NSString * const PARAM_SENDSTORY_SUMMARY; /** 分享feeds展示图片url的key */ FOUNDATION_EXTERN NSString * const PARAM_SENDSTORY_IMAGE; /** 分享feeds跳转链接url的key */ FOUNDATION_EXTERN NSString * const PARAM_SENDSTORY_URL; /** 分享feeds点击操作默认行为的key */ FOUNDATION_EXTERN NSString * const PARAM_SENDSTORY_ACT; ///@} /** * \name 设置头像参数字段定义 */ ///@{ /** 头像图片数据的key */ FOUNDATION_EXTERN NSString * const PARAM_SETUSERHEAD_PIC; /** 头像图片文件名的key */ FOUNDATION_EXTERN NSString * const PARAM_SETUSERHEAD_FILENAME; ///@} /** * \name 服务器返回数据的参数字段定义 */ ///@{ /** 服务器返回码的key */ FOUNDATION_EXTERN NSString * const PARAM_RETCODE; /** 服务器返回错误信息的key */ FOUNDATION_EXTERN NSString * const PARAM_MESSAGE; /** 服务器返回额外数据的key */ FOUNDATION_EXTERN NSString * const PARAM_DATA; ///@} /** * \name 错误信息相关常量定义 */ ///@{ /** 详细错误信息字典中额外信息的key */ FOUNDATION_EXTERN NSString * const TCOpenSDKErrorKeyExtraInfo; /** 详细错误信息字典中返回码的key */ FOUNDATION_EXTERN NSString * const TCOpenSDKErrorKeyRetCode; /** 详细错误信息字典中错误语句的key */ FOUNDATION_EXTERN NSString * const TCOpenSDKErrorKeyMsg; /** 不支持的接口 */ FOUNDATION_EXTERN NSString * const TCOpenSDKErrorMsgUnsupportedAPI; /** 操作成功 */ FOUNDATION_EXTERN NSString * const TCOpenSDKErrorMsgSuccess; /** 未知错误 */ FOUNDATION_EXTERN NSString * const TCOpenSDKErrorMsgUnknown; /** 用户取消 */ FOUNDATION_EXTERN NSString * const TCOpenSDKErrorMsgUserCancel; /** 请重新登录 */ FOUNDATION_EXTERN NSString * const TCOpenSDKErrorMsgReLogin; /** 应用没有操作权限 */ FOUNDATION_EXTERN NSString * const TCOpenSDKErrorMsgOperationDeny; /** 网络异常或没有网络 */ FOUNDATION_EXTERN NSString * const TCOpenSDKErrorMsgNetwork; /** URL格式或协议错误 */ FOUNDATION_EXTERN NSString * const TCOpenSDKErrorMsgURL; /** 解析数据出错 */ FOUNDATION_EXTERN NSString * const TCOpenSDKErrorMsgDataParse; /** 传入参数有误 */ FOUNDATION_EXTERN NSString * const TCOpenSDKErrorMsgParam; /** 连接超时 */ FOUNDATION_EXTERN NSString * const TCOpenSDKErrorMsgTimeout; /** 安全问题 */ FOUNDATION_EXTERN NSString * const TCOpenSDKErrorMsgSecurity; /** 文件读写错误 */ FOUNDATION_EXTERN NSString * const TCOpenSDKErrorMsgIO; /** 服务器端错误 */ FOUNDATION_EXTERN NSString * const TCOpenSDKErrorMsgServer; /** 页面错误 */ FOUNDATION_EXTERN NSString * const TCOpenSDKErrorMsgWebPage; /** 设置头像图片过大 */ FOUNDATION_EXTERN NSString * const TCOpenSDKErrorMsgUserHeadPicLarge; ///@} /** * \brief SDK新增详细错误常量 */ typedef enum { kOpenSDKInvalid = -1, ///< 无效的错误码 kOpenSDKErrorUnsupportedAPI = -2, ///< 不支持的接口 /** * \name CommonErrorCode * 公共错误码 */ ///@{ kOpenSDKErrorSuccess = 0, ///< 成功 kOpenSDKErrorUnknown, ///< 未知错误 kOpenSDKErrorUserCancel, ///< 用户取消 kOpenSDKErrorReLogin, ///< token无效或用户未授权相应权限需要重新登录 kOpenSDKErrorOperationDeny, ///< 第三方应用没有该api操作的权限 ///@} /** * \name NetworkRelatedErrorCode * 网络相关错误码 */ ///@{ kOpenSDKErrorNetwork, ///< 网络错误,网络不通或连接不到服务器 kOpenSDKErrorURL, ///< URL格式或协议错误 kOpenSDKErrorDataParse, ///< 数据解析错误,服务器返回的数据解析出错 kOpenSDKErrorParam, ///< 传入参数错误 kOpenSDKErrorConnTimeout, ///< http连接超时 kOpenSDKErrorSecurity, ///< 安全问题 kOpenSDKErrorIO, ///< 下载和文件IO错误 kOpenSDKErrorServer, ///< 服务器端错误 ///@} /** * \name WebViewRelatedError * webview特有错误 */ ///@{ kOpenSDKErrorWebPage, ///< 页面错误 ///@} /** * \name SetUserHeadRelatedErrorCode * 设置头像自定义错误码段 */ ///@{ kOpenSDKErrorUserHeadPicLarge = 0x010000, ///< 图片过大 设置头像自定义错误码 ///@} } OpenSDKError; /** * \name SDK版本(v1.3)支持的授权列表常量 */ ///@{ /** 发表一条说说到QQ空间(需要申请权限) */ FOUNDATION_EXTERN NSString *const kOPEN_PERMISSION_ADD_TOPIC; /** 发表一篇日志到QQ空间(需要申请权限) */ FOUNDATION_EXTERN NSString *const kOPEN_PERMISSION_ADD_ONE_BLOG; /** 创建一个QQ空间相册(需要申请权限) */ FOUNDATION_EXTERN NSString *const kOPEN_PERMISSION_ADD_ALBUM; /** 上传一张照片到QQ空间相册(需要申请权限) */ FOUNDATION_EXTERN NSString *const kOPEN_PERMISSION_UPLOAD_PIC; /** 获取用户QQ空间相册列表(需要申请权限) */ FOUNDATION_EXTERN NSString *const kOPEN_PERMISSION_LIST_ALBUM; /** 同步分享到QQ空间、腾讯微博 */ FOUNDATION_EXTERN NSString *const kOPEN_PERMISSION_ADD_SHARE; /** 验证是否认证空间粉丝 */ FOUNDATION_EXTERN NSString *const kOPEN_PERMISSION_CHECK_PAGE_FANS; /** 获取登录用户自己的详细信息 */ FOUNDATION_EXTERN NSString *const kOPEN_PERMISSION_GET_INFO; /** 获取其他用户的详细信息 */ FOUNDATION_EXTERN NSString *const kOPEN_PERMISSION_GET_OTHER_INFO; /** 获取会员用户基本信息 */ FOUNDATION_EXTERN NSString *const kOPEN_PERMISSION_GET_VIP_INFO; /** 获取会员用户详细信息 */ FOUNDATION_EXTERN NSString *const kOPEN_PERMISSION_GET_VIP_RICH_INFO; /** 获取用户信息 */ FOUNDATION_EXTERN NSString *const kOPEN_PERMISSION_GET_USER_INFO; /** 移动端获取用户信息 */ FOUNDATION_EXTERN NSString *const kOPEN_PERMISSION_GET_SIMPLE_USER_INFO; ///@} /** * \name CGI接口相关参数类型定义 */ /** 可选的字符串类型参数 */ typedef NSString *TCOptionalStr; /** 可选的不定类型参数 */ typedef id TCRequiredId; ///@} /** * \brief CGI请求的参数字典封装辅助基类 * * 将相应属性的值以key-value的形式保存到参数字典中 */ @interface TCAPIRequest : NSMutableDictionary /** CGI请求的URL地址 */ @property (nonatomic, readonly) NSURL *apiURL; /** CGI请求方式:"GET","POST" */ @property (nonatomic, readonly) NSString *method; /** * API参数中的保留字段,可以塞入任意字典支持的类型,再调用完成后会带回给调用方 */ @property (nonatomic, retain) TCRequiredId paramUserData; /** * APIResponse,API的返回结果 */ @property (nonatomic, readonly) APIResponse *response; /** 取消相应的CGI请求任务 */ - (void)cancel; @end @protocol TCAPIRequestDelegate @optional - (void)cgiRequest:(TCAPIRequest *)request didResponse:(APIResponse *)response; @end ================================================ FILE: zhuishushenqi/NewVersion/ZSThirdPartSDK/ZSThirdPartSDK/Classes/ThirdPardSDK-Info.plist ================================================ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType FMWK CFBundleShortVersionString 1.0 CFBundleVersion $(CURRENT_PROJECT_VERSION) ================================================ FILE: zhuishushenqi/NewVersion/ZSThirdPartSDK/ZSThirdPartSDK/Classes/WXApiRequestHandler.h ================================================ // // WXApiRequestHandler.h // ZSThirdPartSDK // // Created by caony on 2019/6/22. // Copyright © 2019 cj. All rights reserved. // #import #import "WXApi.h" #import "WechatAuthSDK.h" NS_ASSUME_NONNULL_BEGIN @interface WXApiRequestHandler : NSObject + (instancetype)share; - (BOOL)sendWXAuthWithScope:(NSString *)scope state:(NSString *)state; @end NS_ASSUME_NONNULL_END ================================================ FILE: zhuishushenqi/NewVersion/ZSThirdPartSDK/ZSThirdPartSDK/Classes/WXApiRequestHandler.m ================================================ // // WXApiRequestHandler.m // ZSThirdPartSDK // // Created by caony on 2019/6/22. // Copyright © 2019 cj. All rights reserved. // #import "WXApiRequestHandler.h" #import "ZSThirdLogin.h" #import "ZSThirdPartSDK-Swift.h" @implementation WXApiRequestHandler + (instancetype)share { static WXApiRequestHandler * sharedInstance = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ sharedInstance = [[self alloc] init]; }); return sharedInstance; } - (BOOL)sendWXAuthRequestScope:(NSString *)scope state:(NSString *)state inViewController:(UIViewController *)inViewController { SendAuthReq * req = [[SendAuthReq alloc] init]; req.scope = scope; req.state = state; return [WXApi sendAuthReq:req viewController:inViewController delegate:self]; } - (BOOL)sendWXAuthWithScope:(NSString *)scope state:(NSString *)state { SendAuthReq * req = [[SendAuthReq alloc] init]; req.scope = scope; req.state = state; return [WXApi sendReq:req]; } - (void)onReq:(BaseReq *)req { } - (void)onResp:(BaseResp *)resp { SendAuthResp * res = (SendAuthResp *)resp; if (res.errCode == 0) { NSString * code = res.code; NSString * getAccessTokenUrl = [NSString stringWithFormat:@"https://api.weixin.qq.com/sns/oauth2/access_token?appid=%@&secret=%@&code=%@&grant_type=authorization_code", WXAppID, WXAppSecret, code]; [[ZSRequestHelper share] request:getAccessTokenUrl parameters:nil :^(ZSWXAccessTokenResp * _Nullable resp) { [[ZSThirdLoginStorage share] saveWXTokenWithWxTokenResp:resp]; [[ZSThirdLogin shared] loginWithType:(ThirdLoginTypeWX)]; }]; } else { NSLog(@"请求出错,code:%d \nerror:%@", resp.errCode, resp.errStr); } } @end ================================================ FILE: zhuishushenqi/NewVersion/ZSThirdPartSDK/ZSThirdPartSDK/Classes/WeChatSDK1.8.3/README.txt ================================================ 重要! SDK1.8.3 1. SDK增加调起微信刷卡支付接口 2. SDK增加小程序订阅消息接口 3. 修复小程序订阅消息接口没有resp的问题 SDK1.8.2 1. SDK增加开发票授权 WXInvoiceAuthInsert 2. SDK增加非税接口 WXNontaxPay 3. SDK增加医保接口 WXPayInsurance 4. 更换MTA库 SDK1.8.1 1. SDK打开小程序支持指定版本(体验,开发,正式版) 2. SDK分享小程序支持指定版本(体验,开发,正式版) 3. SDK支持输出log日志 SDK1.8.0 1. SDK支持打开小程序 2. SDK分享小程序支持shareTicket SDK1.7.9 1. SDK订阅一次性消息 SDK1.7.8 1 SDK分享小程序支持大图 SDK1.7.7 1 增加SDK分享小程序 2 增加选择发票接口 SDK1.7.6 1. 提高稳定性 1 修复mta崩溃 2 新增接口支持开发者关闭mta数据统计上报 SDK1.7.5 1. 提高稳定性 2. 加快registerApp接口启动速度 SDK1.7.4 1. 更新支持iOS启用 ATS(App Transport Security) 2. 需要在工程中链接CFNetwork.framework 3. 在工程配置中的”Other Linker Flags”中加入”-Objc -all_load” SDK1.7.3 1. 增强稳定性,适配iOS10 2. 修复小于32K的jpg格式缩略图设置失败的问题 SDK1.7.2 1. 修复因CTTeleponyNetworkInfo引起的崩溃问题 SDK1.7.1 1. 支持兼容ipv6(提升稳定性) 2. xCode Version 7.3.1 (7D1014) 编译 SDK1.7 1. 支持兼容ipv6 2. 修复若干问题增强稳定性 SDK1.6.3 1. xCode7.2 构建的sdk包。 2. 请使用xCode7.2进行编译。 3. 需要在Build Phases中Link Security.framework 4. 修复若干小问题。 SDK1.6.2 1、xCode7.1 构建的sdk包 2、请使用xCode7.1进行编译 SDK1.6.1 1、修复armv7s下,bitcode可能编译不过 2、解决warning SDK1.6 1、iOS 9系统策略更新,限制了http协议的访问,此外应用需要在“Info.plist”中将要使用的URL Schemes列为白名单,才可正常检查其他应用是否安装。 受此影响,当你的应用在iOS 9中需要使用微信SDK的相关能力(分享、收藏、支付、登录等)时,需要在“Info.plist”里增加如下代码: LSApplicationQueriesSchemes weixin NSAppTransportSecurity NSAllowsArbitraryLoads 2、开发者需要在工程中链接上 CoreTelephony.framework 3、解决bitcode编译不过问题 SDK1.5 1、废弃safeSendReq:接口,使用sendReq:即可。 2、新增+(BOOL) sendAuthReq:(SendAuthReq*) req viewController : (UIViewController*) viewController delegate:(id) delegate; 支持未安装微信情况下Auth,具体见WXApi.h接口描述 3、微信开放平台新增了微信模块用户统计功能,便于开发者统计微信功能模块的用户使用和活跃情况。开发者需要在工程中链接上:SystemConfiguration.framework,libz.dylib,libsqlite3.0.dylib。 ================================================ FILE: zhuishushenqi/NewVersion/ZSThirdPartSDK/ZSThirdPartSDK/Classes/WeChatSDK1.8.3/WXApi.h ================================================ // // WXApi.h // 所有Api接口 // // Created by Wechat on 12-2-28. // Copyright (c) 2012年 Tencent. All rights reserved. // #import #import "WXApiObject.h" #pragma mark - WXApiDelegate /*! @brief 接收并处理来自微信终端程序的事件消息 * * 接收并处理来自微信终端程序的事件消息,期间微信界面会切换到第三方应用程序。 * WXApiDelegate 会在handleOpenURL:delegate:中使用并触发。 */ @protocol WXApiDelegate @optional /*! @brief 收到一个来自微信的请求,第三方应用程序处理完后调用sendResp向微信发送结果 * * 收到一个来自微信的请求,异步处理完成后必须调用sendResp发送处理结果给微信。 * 可能收到的请求有GetMessageFromWXReq、ShowMessageFromWXReq等。 * @param req 具体请求内容,是自动释放的 */ -(void) onReq:(BaseReq*)req; /*! @brief 发送一个sendReq后,收到微信的回应 * * 收到一个来自微信的处理结果。调用一次sendReq后会收到onResp。 * 可能收到的处理结果有SendMessageToWXResp、SendAuthResp等。 * @param resp具体的回应内容,是自动释放的 */ -(void) onResp:(BaseResp*)resp; @end #pragma mark - WXApiLogDelegate @protocol WXApiLogDelegate -(void) onLog:(NSString*)log logLevel:(WXLogLevel)level; @end #pragma mark - WXApi /*! @brief 微信Api接口函数类 * * 该类封装了微信终端SDK的所有接口 */ @interface WXApi : NSObject /*! @brief WXApi的成员函数,向微信终端程序注册第三方应用。 * * 需要在每次启动第三方应用程序时调用。第一次调用后,会在微信的可用应用列表中出现,默认开启MTA数据上报。 * iOS7及以上系统需要调起一次微信才会出现在微信的可用应用列表中。 * @attention 请保证在主线程中调用此函数 * @param appid 微信开发者ID * @param typeFlag 应用支持打开的文件类型 * @return 成功返回YES,失败返回NO。 */ +(BOOL) registerApp:(NSString *)appid; /*! @brief WXApi的成员函数,向微信终端程序注册第三方应用。 * * 需要在每次启动第三方应用程序时调用。第一次调用后,会在微信的可用应用列表中出现。 * iOS7及以上系统需要调起一次微信才会出现在微信的可用应用列表中。 * @attention 请保证在主线程中调用此函数 * @param appid 微信开发者ID * @param isEnableMTA 是否支持MTA数据上报 * @return 成功返回YES,失败返回NO。 */ +(BOOL) registerApp:(NSString *)appid enableMTA:(BOOL)isEnableMTA; /*! @brief WXApi的成员函数,向微信终端程序注册应用支持打开的文件类型。 * * 需要在每次启动第三方应用程序时调用。调用后并第一次成功分享数据到微信后,会在微信的可用应用列表中出现。 * @see registerApp * @param typeFlag 应用支持打开的数据类型, enAppSupportContentFlag枚举类型 “|” 操作后结果 */ +(void) registerAppSupportContentFlag:(UInt64)typeFlag; /*! @brief 处理微信通过URL启动App时传递的数据 * * 需要在 application:openURL:sourceApplication:annotation:或者application:handleOpenURL中调用。 * @param url 微信启动第三方应用时传递过来的URL * @param delegate WXApiDelegate对象,用来接收微信触发的消息。 * @return 成功返回YES,失败返回NO。 */ +(BOOL) handleOpenURL:(NSURL *) url delegate:(id) delegate; /*! @brief 检查微信是否已被用户安装 * * @return 微信已安装返回YES,未安装返回NO。 */ +(BOOL) isWXAppInstalled; /*! @brief 判断当前微信的版本是否支持OpenApi * * @return 支持返回YES,不支持返回NO。 */ +(BOOL) isWXAppSupportApi; /*! @brief 获取微信的itunes安装地址 * * @return 微信的安装地址字符串。 */ +(NSString *) getWXAppInstallUrl; /*! @brief 获取当前微信SDK的版本号 * * @return 返回当前微信SDK的版本号 */ +(NSString *) getApiVersion; /*! @brief 打开微信 * * @return 成功返回YES,失败返回NO。 */ +(BOOL) openWXApp; /*! @brief 发送请求到微信,等待微信返回onResp * * 函数调用后,会切换到微信的界面。第三方应用程序等待微信返回onResp。微信在异步处理完成后一定会调用onResp。支持以下类型 * SendAuthReq、SendMessageToWXReq、PayReq等。 * @param req 具体的发送请求,在调用函数后,请自己释放。 * @return 成功返回YES,失败返回NO。 */ +(BOOL) sendReq:(BaseReq*)req; /*! @brief 发送Auth请求到微信,支持用户没安装微信,等待微信返回onResp * * 函数调用后,会切换到微信的界面。第三方应用程序等待微信返回onResp。微信在异步处理完成后一定会调用onResp。支持SendAuthReq类型。 * @param req 具体的发送请求,在调用函数后,请自己释放。 * @param viewController 当前界面对象。 * @param delegate WXApiDelegate对象,用来接收微信触发的消息。 * @return 成功返回YES,失败返回NO。 */ +(BOOL) sendAuthReq:(SendAuthReq*)req viewController:(UIViewController*)viewController delegate:(id)delegate; /*! @brief 收到微信onReq的请求,发送对应的应答给微信,并切换到微信界面 * * 函数调用后,会切换到微信的界面。第三方应用程序收到微信onReq的请求,异步处理该请求,完成后必须调用该函数。可能发送的相应有 * GetMessageFromWXResp、ShowMessageFromWXResp等。 * @param resp 具体的应答内容,调用函数后,请自己释放 * @return 成功返回YES,失败返回NO。 */ +(BOOL) sendResp:(BaseResp*)resp; /*! @brief WXApi的成员函数,接受微信的log信息。byBlock 注意1:SDK会强引用这个block,注意不要导致内存泄漏,注意不要导致内存泄漏 注意2:调用过一次startLog by block之后,如果再调用一次任意方式的startLoad,会释放上一次logBlock,不再回调上一个logBlock * * @param level 打印log的级别 * @param logBlock 打印log的回调block */ +(void) startLogByLevel:(WXLogLevel)level logBlock:(WXLogBolock)logBlock; /*! @brief WXApi的成员函数,接受微信的log信息。byDelegate 注意1:sdk会弱引用这个delegate,这里可加任意对象为代理,不需要与WXApiDelegate同一个对象 注意2:调用过一次startLog by delegate之后,再调用一次任意方式的startLoad,不会再回调上一个logDelegate对象 * @param level 打印log的级别 * @param logDelegate 打印log的回调代理, */ + (void)startLogByLevel:(WXLogLevel)level logDelegate:(id)logDelegate; /*! @brief 停止打印log,会清理block或者delegate为空,释放block * @param */ + (void)stopLog; @end ================================================ FILE: zhuishushenqi/NewVersion/ZSThirdPartSDK/ZSThirdPartSDK/Classes/WeChatSDK1.8.3/WXApiObject.h ================================================ // // MMApiObject.h // Api对象,包含所有接口和对象数据定义 // // Created by Wechat on 12-2-28. // Copyright (c) 2012年 Tencent. All rights reserved. // #import #import /*! @brief 错误码 * */ enum WXErrCode { WXSuccess = 0, /**< 成功 */ WXErrCodeCommon = -1, /**< 普通错误类型 */ WXErrCodeUserCancel = -2, /**< 用户点击取消并返回 */ WXErrCodeSentFail = -3, /**< 发送失败 */ WXErrCodeAuthDeny = -4, /**< 授权失败 */ WXErrCodeUnsupport = -5, /**< 微信不支持 */ }; /*! @brief 请求发送场景 * */ enum WXScene { WXSceneSession = 0, /**< 聊天界面 */ WXSceneTimeline = 1, /**< 朋友圈 */ WXSceneFavorite = 2, /**< 收藏 */ WXSceneSpecifiedSession = 3, /**< 指定联系人 */ }; enum WXAPISupport { WXAPISupportSession = 0, }; /*! @brief 跳转profile类型 * */ enum WXBizProfileType{ WXBizProfileType_Normal = 0, //**< 普通公众号 */ WXBizProfileType_Device = 1, //**< 硬件公众号 */ }; /*! @brief 分享小程序类型 * */ typedef NS_ENUM(NSUInteger, WXMiniProgramType){ WXMiniProgramTypeRelease = 0, //**< 正式版 */ WXMiniProgramTypeTest = 1, //**< 开发版 */ WXMiniProgramTypePreview = 2, //**< 体验版 */ }; /*! @brief 跳转mp网页类型 * */ enum WXMPWebviewType { WXMPWebviewType_Ad = 0, /**< 广告网页 **/ }; /*! @brief 应用支持接收微信的文件类型 * */ typedef NS_ENUM(UInt64, enAppSupportContentFlag) { MMAPP_SUPPORT_NOCONTENT = 0x0, MMAPP_SUPPORT_TEXT = 0x1, MMAPP_SUPPORT_PICTURE = 0x2, MMAPP_SUPPORT_LOCATION = 0x4, MMAPP_SUPPORT_VIDEO = 0x8, MMAPP_SUPPORT_AUDIO = 0x10, MMAPP_SUPPORT_WEBPAGE = 0x20, // Suport File Type MMAPP_SUPPORT_DOC = 0x40, // doc MMAPP_SUPPORT_DOCX = 0x80, // docx MMAPP_SUPPORT_PPT = 0x100, // ppt MMAPP_SUPPORT_PPTX = 0x200, // pptx MMAPP_SUPPORT_XLS = 0x400, // xls MMAPP_SUPPORT_XLSX = 0x800, // xlsx MMAPP_SUPPORT_PDF = 0x1000, // pdf }; /*! @brief log的级别 * */ typedef NS_ENUM(NSInteger,WXLogLevel){ WXLogLevelNormal = 0, // 打印日常的日志 WXLogLevelDetail = 1, // 打印详细的日志 }; /*! @brief 打印回调的block * */ typedef void(^WXLogBolock)(NSString * log); #pragma mark - BaseReq /*! @brief 该类为微信终端SDK所有请求类的基类 * */ @interface BaseReq : NSObject /** 请求类型 */ @property (nonatomic, assign) int type; /** 由用户微信号和AppID组成的唯一标识,发送请求时第三方程序必须填写,用于校验微信用户是否换号登录*/ @property (nonatomic, retain) NSString* openID; @end #pragma mark - BaseResp /*! @brief 该类为微信终端SDK所有响应类的基类 * */ @interface BaseResp : NSObject /** 错误码 */ @property (nonatomic, assign) int errCode; /** 错误提示字符串 */ @property (nonatomic, retain) NSString *errStr; /** 响应类型 */ @property (nonatomic, assign) int type; @end #pragma mark - WXMediaMessage @class WXMediaMessage; #ifndef BUILD_WITHOUT_PAY /*! @brief 第三方向微信终端发起支付的消息结构体 * * 第三方向微信终端发起支付的消息结构体,微信终端处理后会向第三方返回处理结果 * @see PayResp */ @interface PayReq : BaseReq /** 商家向财付通申请的商家id */ @property (nonatomic, retain) NSString *partnerId; /** 预支付订单 */ @property (nonatomic, retain) NSString *prepayId; /** 随机串,防重发 */ @property (nonatomic, retain) NSString *nonceStr; /** 时间戳,防重发 */ @property (nonatomic, assign) UInt32 timeStamp; /** 商家根据财付通文档填写的数据和签名 */ @property (nonatomic, retain) NSString *package; /** 商家根据微信开放平台文档对数据做的签名 */ @property (nonatomic, retain) NSString *sign; @end #pragma mark - PayResp /*! @brief 微信终端返回给第三方的关于支付结果的结构体 * * 微信终端返回给第三方的关于支付结果的结构体 */ @interface PayResp : BaseResp /** 财付通返回给商家的信息 */ @property (nonatomic, retain) NSString *returnKey; @end #pragma mark - WXOfflinePay /*! @brief 第三方向微信终端发起离线支付 * * 第三方向微信终端发起离线支付的消息结构体 */ @interface WXOfflinePayReq : BaseReq @end /*! @brief 第三方向微信终端发起离线支付返回 * * 第三方向微信终端发起离线支付返回的消息结构体 */ @interface WXOfflinePayResp : BaseResp @end #endif #pragma mark - SendAuthReq /*! @brief 第三方程序向微信终端请求认证的消息结构 * * 第三方程序要向微信申请认证,并请求某些权限,需要调用WXApi的sendReq成员函数, * 向微信终端发送一个SendAuthReq消息结构。微信终端处理完后会向第三方程序发送一个处理结果。 * @see SendAuthResp */ @interface SendAuthReq : BaseReq /** 第三方程序要向微信申请认证,并请求某些权限,需要调用WXApi的sendReq成员函数,向微信终端发送一个SendAuthReq消息结构。微信终端处理完后会向第三方程序发送一个处理结果。 * @see SendAuthResp * @note scope字符串长度不能超过1K */ @property (nonatomic, retain) NSString* scope; /** 第三方程序本身用来标识其请求的唯一性,最后跳转回第三方程序时,由微信终端回传。 * @note state字符串长度不能超过1K */ @property (nonatomic, retain) NSString* state; @end #pragma mark - SendAuthResp /*! @brief 微信处理完第三方程序的认证和权限申请后向第三方程序回送的处理结果。 * * 第三方程序要向微信申请认证,并请求某些权限,需要调用WXApi的sendReq成员函数,向微信终端发送一个SendAuthReq消息结构。 * 微信终端处理完后会向第三方程序发送一个SendAuthResp。 * @see onResp */ @interface SendAuthResp : BaseResp @property (nonatomic, retain) NSString* code; /** 第三方程序发送时用来标识其请求的唯一性的标志,由第三方程序调用sendReq时传入,由微信终端回传 * @note state字符串长度不能超过1K */ @property (nonatomic, retain) NSString* state; @property (nonatomic, retain) NSString* lang; @property (nonatomic, retain) NSString* country; @end #pragma mark - SendMessageToWXReq /*! @brief 第三方程序发送消息至微信终端程序的消息结构体 * * 第三方程序向微信发送信息需要传入SendMessageToWXReq结构体,信息类型包括文本消息和多媒体消息, * 分别对应于text和message成员。调用该方法后,微信处理完信息会向第三方程序发送一个处理结果。 * @see SendMessageToWXResp */ @interface SendMessageToWXReq : BaseReq /** 发送消息的文本内容 * @note 文本长度必须大于0且小于10K */ @property (nonatomic, retain) NSString* text; /** 发送消息的多媒体内容 * @see WXMediaMessage */ @property (nonatomic, retain) WXMediaMessage* message; /** 发送消息的类型,包括文本消息和多媒体消息两种,两者只能选择其一,不能同时发送文本和多媒体消息 */ @property (nonatomic, assign) BOOL bText; /** 发送的目标场景,可以选择发送到会话(WXSceneSession)或者朋友圈(WXSceneTimeline)。 默认发送到会话。 * @see WXScene */ @property (nonatomic, assign) int scene; /** 指定发送消息的人,WXSceneSpecifiedSession时有效 */ @property (nonatomic, retain) NSString* toUserOpenId; @end #pragma mark - SendMessageToWXResp /*! @brief 微信终端向第三方程序返回的SendMessageToWXReq处理结果。 * * 第三方程序向微信终端发送SendMessageToWXReq后,微信发送回来的处理结果,该结果用SendMessageToWXResp表示。 */ @interface SendMessageToWXResp : BaseResp @property(nonatomic, retain) NSString* lang; @property(nonatomic, retain) NSString* country; @end #pragma mark - GetMessageFromWXReq /*! @brief 微信终端向第三方程序请求提供内容的消息结构体。 * * 微信终端向第三方程序请求提供内容,微信终端会向第三方程序发送GetMessageFromWXReq消息结构体, * 需要第三方程序调用sendResp返回一个GetMessageFromWXResp消息结构体。 */ @interface GetMessageFromWXReq : BaseReq @property (nonatomic, retain) NSString* lang; @property (nonatomic, retain) NSString* country; @end #pragma mark - GetMessageFromWXResp /*! @brief 微信终端向第三方程序请求提供内容,第三方程序向微信终端返回的消息结构体。 * * 微信终端向第三方程序请求提供内容,第三方程序调用sendResp向微信终端返回一个GetMessageFromWXResp消息结构体。 */ @interface GetMessageFromWXResp : BaseResp /** 向微信终端提供的文本内容 @note 文本长度必须大于0且小于10K */ @property (nonatomic, retain) NSString* text; /** 向微信终端提供的多媒体内容。 * @see WXMediaMessage */ @property (nonatomic, retain) WXMediaMessage* message; /** 向微信终端提供内容的消息类型,包括文本消息和多媒体消息两种,两者只能选择其一,不能同时发送文本和多媒体消息 */ @property (nonatomic, assign) BOOL bText; @end #pragma mark - ShowMessageFromWXReq /*! @brief 微信通知第三方程序,要求第三方程序显示的消息结构体。 * * 微信需要通知第三方程序显示或处理某些内容时,会向第三方程序发送ShowMessageFromWXReq消息结构体。 * 第三方程序处理完内容后调用sendResp向微信终端发送ShowMessageFromWXResp。 */ @interface ShowMessageFromWXReq : BaseReq /** 微信终端向第三方程序发送的要求第三方程序处理的多媒体内容 * @see WXMediaMessage */ @property (nonatomic, retain) WXMediaMessage* message; @property (nonatomic, retain) NSString* lang; @property (nonatomic, retain) NSString* country; @end #pragma mark - ShowMessageFromWXResp /*! @brief 微信通知第三方程序,要求第三方程序显示或处理某些消息,第三方程序处理完后向微信终端发送的处理结果。 * * 微信需要通知第三方程序显示或处理某些内容时,会向第三方程序发送ShowMessageFromWXReq消息结构体。 * 第三方程序处理完内容后调用sendResp向微信终端发送ShowMessageFromWXResp。 */ @interface ShowMessageFromWXResp : BaseResp @end #pragma mark - LaunchFromWXReq /*! @brief 微信终端打开第三方程序携带的消息结构体 * * 微信向第三方发送的结构体,第三方不需要返回 */ @interface LaunchFromWXReq : BaseReq @property (nonatomic, retain) WXMediaMessage* message; @property (nonatomic, retain) NSString* lang; @property (nonatomic, retain) NSString* country; @end #pragma mark - OpenTempSessionReq /* ! @brief 第三方通知微信,打开临时会话 * * 第三方通知微信,打开临时会话 */ @interface OpenTempSessionReq : BaseReq /** 需要打开的用户名 * @attention 长度不能超过512字节 */ @property (nonatomic, retain) NSString* username; /** 开发者自定义参数,拉起临时会话后会发给开发者后台,可以用于识别场景 * @attention 长度不能超过32位 */ @property (nonatomic, retain) NSString* sessionFrom; @end #pragma mark - OpenTempSessionResp /*! @brief 微信终端向第三方程序返回的OpenTempSessionReq处理结果。 * * 第三方程序向微信终端发送OpenTempSessionReq后,微信发送回来的处理结果,该结果用OpenTempSessionResp表示。 */ @interface OpenTempSessionResp : BaseResp @end #pragma mark - OpenWebviewReq /* ! @brief 第三方通知微信启动内部浏览器,打开指定网页 * * 第三方通知微信启动内部浏览器,打开指定Url对应的网页 */ @interface OpenWebviewReq : BaseReq /** 需要打开的网页对应的Url * @attention 长度不能超过1024 */ @property(nonatomic,retain)NSString* url; @end #pragma mark - OpenWebviewResp /*! @brief 微信终端向第三方程序返回的OpenWebviewReq处理结果 * * 第三方程序向微信终端发送OpenWebviewReq后,微信发送回来的处理结果,该结果用OpenWebviewResp表示 */ @interface OpenWebviewResp : BaseResp @end #pragma mark - WXOpenBusinessWebViewReq /*! @brief 第三方通知微信启动内部浏览器,打开指定业务的网页 * * */ @interface WXOpenBusinessWebViewReq : BaseReq /** 网页业务类型 * @attention */ @property (nonatomic, assign) UInt32 businessType; /** 网页业务参数 * @attention */ @property (nonatomic, retain) NSDictionary *queryInfoDic; @end #pragma mark - WXOpenBusinessWebViewResp /*! @brief 微信终端向第三方程序返回的WXOpenBusinessWebViewResp处理结果。 * * 第三方程序向微信终端发送WXOpenBusinessWebViewReq后,微信发送回来的处理结果,该结果用WXOpenBusinessWebViewResp表示。 */ @interface WXOpenBusinessWebViewResp : BaseResp /** 第三方程序自定义简单数据,微信终端会回传给第三方程序处理 * @attention 长度不能超过2k */ @property (nonatomic, retain) NSString *result; /** 网页业务类型 * @attention */ @property (nonatomic, assign) UInt32 businessType; @end #pragma mark - OpenRankListReq /* ! @brief 第三方通知微信,打开硬件排行榜 * * 第三方通知微信,打开硬件排行榜 */ @interface OpenRankListReq : BaseReq @end #pragma mark - OpenRanklistResp /*! @brief 微信终端向第三方程序返回的OpenRankListReq处理结果。 * * 第三方程序向微信终端发送OpenRankListReq后,微信发送回来的处理结果,该结果用OpenRankListResp表示。 */ @interface OpenRankListResp : BaseResp @end #pragma mark - JumpToBizProfileReq /* ! @brief 第三方通知微信,打开指定微信号profile页面 * * 第三方通知微信,打开指定微信号profile页面 */ @interface JumpToBizProfileReq : BaseReq /** 跳转到该公众号的profile * @attention 长度不能超过512字节 */ @property (nonatomic, retain) NSString* username; /** 如果用户加了该公众号为好友,extMsg会上传到服务器 * @attention 长度不能超过1024字节 */ @property (nonatomic, retain) NSString* extMsg; /** * 跳转的公众号类型 * @see WXBizProfileType */ @property (nonatomic, assign) int profileType; @end #pragma mark - JumpToBizWebviewReq /* ! @brief 第三方通知微信,打开指定usrname的profile网页版 * */ @interface JumpToBizWebviewReq : BaseReq /** 跳转的网页类型,目前只支持广告页 * @see WXMPWebviewType */ @property(nonatomic, assign) int webType; /** 跳转到该公众号的profile网页版 * @attention 长度不能超过512字节 */ @property(nonatomic, retain) NSString* tousrname; /** 如果用户加了该公众号为好友,extMsg会上传到服务器 * @attention 长度不能超过1024字节 */ @property(nonatomic, retain) NSString* extMsg; @end #pragma mark - WXCardItem @interface WXCardItem : NSObject /** 卡id * @attention 长度不能超过1024字节 */ @property (nonatomic,retain) NSString* cardId; /** ext信息 * @attention 长度不能超过2024字节 */ @property (nonatomic,retain) NSString* extMsg; /** * @attention 卡的状态,req不需要填。resp:0为未添加,1为已添加。 */ @property (nonatomic,assign) UInt32 cardState; /** * @attention req不需要填,chooseCard返回的。 */ @property (nonatomic,retain) NSString* encryptCode; /** * @attention req不需要填,chooseCard返回的。 */ @property (nonatomic,retain) NSString* appID; @end; #pragma mark - WXInvoiceItem @interface WXInvoiceItem : NSObject /** 卡id * @attention 长度不能超过1024字节 */ @property (nonatomic,retain) NSString* cardId; /** ext信息 * @attention 长度不能超过2024字节 */ @property (nonatomic,retain) NSString* extMsg; /** * @attention 卡的状态,req不需要填。resp:0为未添加,1为已添加。 */ @property (nonatomic,assign) UInt32 cardState; /** * @attention req不需要填,chooseCard返回的。 */ @property (nonatomic,retain) NSString* encryptCode; /** * @attention req不需要填,chooseCard返回的。 */ @property (nonatomic,retain) NSString* appID; @end #pragma mark - AddCardToWXCardPackageReq /* ! @brief 请求添加卡券至微信卡包 * */ @interface AddCardToWXCardPackageReq : BaseReq /** 卡列表 * @attention 个数不能超过40个 类型WXCardItem */ @property (nonatomic,retain) NSArray* cardAry; @end #pragma mark - AddCardToWXCardPackageResp /** ! @brief 微信返回第三方添加卡券结果 * */ @interface AddCardToWXCardPackageResp : BaseResp /** 卡列表 * @attention 个数不能超过40个 类型WXCardItem */ @property (nonatomic,retain) NSArray* cardAry; @end #pragma mark - WXChooseCardReq /* ! @brief 请求从微信选取卡券 * */ @interface WXChooseCardReq : BaseReq @property(nonatomic, strong) NSString *appID; @property(nonatomic, assign) UInt32 shopID; @property(nonatomic, assign) UInt32 canMultiSelect; @property(nonatomic, strong) NSString *cardType; @property(nonatomic, strong) NSString *cardTpID; @property(nonatomic, strong) NSString *signType; @property(nonatomic, strong) NSString *cardSign; @property(nonatomic, assign) UInt32 timeStamp; @property(nonatomic, strong) NSString *nonceStr; @end #pragma mark - WXChooseCardResp /** ! @brief 微信返回第三方请求选择卡券结果 * */ @interface WXChooseCardResp : BaseResp @property (nonatomic,retain) NSArray* cardAry; @end #pragma mark - WXChooseInvoiceReq /* ! @brief 请求从微信选取发票 * */ @interface WXChooseInvoiceReq : BaseReq @property (nonatomic, strong) NSString *appID; @property (nonatomic, assign) UInt32 shopID; @property (nonatomic, strong) NSString *signType; @property (nonatomic, strong) NSString *cardSign; @property (nonatomic, assign) UInt32 timeStamp; @property (nonatomic, strong) NSString *nonceStr; @end #pragma mark - WXChooseInvoiceResp /** ! @brief 微信返回第三方请求选择发票结果 * */ @interface WXChooseInvoiceResp : BaseResp @property (nonatomic, strong) NSArray* cardAry; @end #pragma mark - WXSubscriptionReq @interface WXSubscribeMsgReq : BaseReq @property (nonatomic, assign) UInt32 scene; @property (nonatomic, strong) NSString * templateId; @property (nonatomic, strong) NSString * reserved; @end #pragma mark - WXSubscriptionReq @interface WXSubscribeMsgResp : BaseResp @property (nonatomic, strong) NSString *templateId; @property (nonatomic, assign) UInt32 scene; @property (nonatomic, strong) NSString *action; @property (nonatomic, strong) NSString * reserved; @property (nonatomic, strong) NSString * openId; @end #pragma mark - WXSubscribeMiniProgramMsg /** ! @brief 微信返回第三方请求选择发票结果 * */ @interface WXSubscribeMiniProgramMsgReq : BaseReq @property (nonatomic, strong) NSString * miniProgramAppid; @end #pragma mark - WXSubscriptionReq @interface WXSubscribeMiniProgramMsgResp : BaseResp @property(nonatomic, strong) NSString *openId; // 小程序openid @property(nonatomic, strong) NSString *unionId; // unionId @property(nonatomic, strong) NSString *nickName; // 用户昵称 @end #pragma mark - WXinvoiceAuthInsertReq @interface WXInvoiceAuthInsertReq : BaseReq @property (nonatomic, strong) NSString *urlString; @end #pragma mark - WXinvoiceAuthInsertResp @interface WXInvoiceAuthInsertResp : BaseResp @property (nonatomic, strong) NSString * wxOrderId; @end #pragma mark - WXNontaxPayReq @interface WXNontaxPayReq:BaseReq @property (nonatomic, strong) NSString *urlString; @end #pragma mark - WXNontaxPayResp @interface WXNontaxPayResp : BaseResp @property (nonatomic, strong) NSString *wxOrderId; @end #pragma mark - WXPayInsuranceReq @interface WXPayInsuranceReq : BaseReq @property (nonatomic, strong) NSString *urlString; @end #pragma mark - WXPayInsuranceResp @interface WXPayInsuranceResp : BaseResp @property (nonatomic, strong) NSString *wxOrderId; @end #pragma mark - WXMediaMessage #pragma mark - WXMediaMessage /*! @brief 多媒体消息结构体 * * 用于微信终端和第三方程序之间传递消息的多媒体消息内容 */ @interface WXMediaMessage : NSObject +(WXMediaMessage *) message; /** 标题 * @note 长度不能超过512字节 */ @property (nonatomic, retain) NSString *title; /** 描述内容 * @note 长度不能超过1K */ @property (nonatomic, retain) NSString *description; /** 缩略图数据 * @note 大小不能超过32K */ @property (nonatomic, retain) NSData *thumbData; /** * @note 长度不能超过64字节 */ @property (nonatomic, retain) NSString *mediaTagName; /** * */ @property (nonatomic, retain) NSString *messageExt; @property (nonatomic, retain) NSString *messageAction; /** * 多媒体数据对象,可以为WXImageObject,WXMusicObject,WXVideoObject,WXWebpageObject等。 */ @property (nonatomic, retain) id mediaObject; /*! @brief 设置消息缩略图的方法 * * @param image 缩略图 * @note 大小不能超过32K */ - (void) setThumbImage:(UIImage *)image; @end #pragma mark - WXImageObject /*! @brief 多媒体消息中包含的图片数据对象 * * 微信终端和第三方程序之间传递消息中包含的图片数据对象。 * @note imageData成员不能为空 * @see WXMediaMessage */ @interface WXImageObject : NSObject /*! @brief 返回一个WXImageObject对象 * * @note 返回的WXImageObject对象是自动释放的 */ +(WXImageObject *) object; /** 图片真实数据内容 * @note 大小不能超过10M */ @property (nonatomic, retain) NSData *imageData; @end #pragma mark - WXMusicObject /*! @brief 多媒体消息中包含的音乐数据对象 * * 微信终端和第三方程序之间传递消息中包含的音乐数据对象。 * @note musicUrl和musicLowBandUrl成员不能同时为空。 * @see WXMediaMessage */ @interface WXMusicObject : NSObject /*! @brief 返回一个WXMusicObject对象 * * @note 返回的WXMusicObject对象是自动释放的 */ +(WXMusicObject *) object; /** 音乐网页的url地址 * @note 长度不能超过10K */ @property (nonatomic, retain) NSString *musicUrl; /** 音乐lowband网页的url地址 * @note 长度不能超过10K */ @property (nonatomic, retain) NSString *musicLowBandUrl; /** 音乐数据url地址 * @note 长度不能超过10K */ @property (nonatomic, retain) NSString *musicDataUrl; /**音乐lowband数据url地址 * @note 长度不能超过10K */ @property (nonatomic, retain) NSString *musicLowBandDataUrl; @end #pragma mark - WXVideoObject /*! @brief 多媒体消息中包含的视频数据对象 * * 微信终端和第三方程序之间传递消息中包含的视频数据对象。 * @note videoUrl和videoLowBandUrl不能同时为空。 * @see WXMediaMessage */ @interface WXVideoObject : NSObject /*! @brief 返回一个WXVideoObject对象 * * @note 返回的WXVideoObject对象是自动释放的 */ +(WXVideoObject *) object; /** 视频网页的url地址 * @note 长度不能超过10K */ @property (nonatomic, retain) NSString *videoUrl; /** 视频lowband网页的url地址 * @note 长度不能超过10K */ @property (nonatomic, retain) NSString *videoLowBandUrl; @end #pragma mark - WXWebpageObject /*! @brief 多媒体消息中包含的网页数据对象 * * 微信终端和第三方程序之间传递消息中包含的网页数据对象。 * @see WXMediaMessage */ @interface WXWebpageObject : NSObject /*! @brief 返回一个WXWebpageObject对象 * * @note 返回的WXWebpageObject对象是自动释放的 */ +(WXWebpageObject *) object; /** 网页的url地址 * @note 不能为空且长度不能超过10K */ @property (nonatomic, retain) NSString *webpageUrl; @end #pragma mark - WXAppExtendObject /*! @brief 多媒体消息中包含的App扩展数据对象 * * 第三方程序向微信终端发送包含WXAppExtendObject的多媒体消息, * 微信需要处理该消息时,会调用该第三方程序来处理多媒体消息内容。 * @note url,extInfo和fileData不能同时为空 * @see WXMediaMessage */ @interface WXAppExtendObject : NSObject /*! @brief 返回一个WXAppExtendObject对象 * * @note 返回的WXAppExtendObject对象是自动释放的 */ +(WXAppExtendObject *) object; /** 若第三方程序不存在,微信终端会打开该url所指的App下载地址 * @note 长度不能超过10K */ @property (nonatomic, retain) NSString *url; /** 第三方程序自定义简单数据,微信终端会回传给第三方程序处理 * @note 长度不能超过2K */ @property (nonatomic, retain) NSString *extInfo; /** App文件数据,该数据发送给微信好友,微信好友需要点击后下载数据,微信终端会回传给第三方程序处理 * @note 大小不能超过10M */ @property (nonatomic, retain) NSData *fileData; @end #pragma mark - WXEmoticonObject /*! @brief 多媒体消息中包含的表情数据对象 * * 微信终端和第三方程序之间传递消息中包含的表情数据对象。 * @see WXMediaMessage */ @interface WXEmoticonObject : NSObject /*! @brief 返回一个WXEmoticonObject对象 * * @note 返回的WXEmoticonObject对象是自动释放的 */ +(WXEmoticonObject *) object; /** 表情真实数据内容 * @note 大小不能超过10M */ @property (nonatomic, retain) NSData *emoticonData; @end #pragma mark - WXFileObject /*! @brief 多媒体消息中包含的文件数据对象 * * @see WXMediaMessage */ @interface WXFileObject : NSObject /*! @brief 返回一个WXFileObject对象 * * @note 返回的WXFileObject对象是自动释放的 */ +(WXFileObject *) object; /** 文件后缀名 * @note 长度不超过64字节 */ @property (nonatomic, retain) NSString *fileExtension; /** 文件真实数据内容 * @note 大小不能超过10M */ @property (nonatomic, retain) NSData *fileData; @end #pragma mark - WXLocationObject /*! @brief 多媒体消息中包含的地理位置数据对象 * * 微信终端和第三方程序之间传递消息中包含的地理位置数据对象。 * @see WXMediaMessage */ @interface WXLocationObject : NSObject /*! @brief 返回一个WXLocationObject对象 * * @note 返回的WXLocationObject对象是自动释放的 */ +(WXLocationObject *) object; /** 地理位置信息 * @note 经纬度 */ @property (nonatomic, assign) double lng; //经度 @property (nonatomic, assign) double lat; //纬度 @end @interface WXMiniProgramObject : NSObject /*! @brief WXMiniProgramObject对象 * * @note 返回的WXMiniProgramObject对象是自动释放的 */ +(WXMiniProgramObject *) object; @property (nonatomic, strong) NSString *webpageUrl; //低版本网页链接 @property (nonatomic, strong) NSString *userName; //小程序username @property (nonatomic, strong) NSString *path; //小程序页面的路径 @property (nonatomic, strong) NSData *hdImageData; // 小程序新版本的预览图 128k @property (nonatomic, assign) BOOL withShareTicket; //是否使用带 shareTicket 的转发 @property (nonatomic, assign) WXMiniProgramType miniProgramType; // 分享小程序的版本(正式,开发,体验) @end #pragma mark - WXLaunchMiniProgramReq /*! @brief WXLaunchMiniProgramReq对象, 可实现通过sdk拉起微信小程序 * * @note 返回的WXLaunchMiniProgramReq对象是自动释放的 */ @interface WXLaunchMiniProgramReq : BaseReq +(WXLaunchMiniProgramReq *) object; @property (nonatomic, strong) NSString *userName; //拉起的小程序的username @property (nonatomic, strong) NSString *path; //拉起小程序页面的路径,不填默认拉起小程序首页 @property (nonatomic, assign) WXMiniProgramType miniProgramType; //拉起小程序的类型 @property (nonatomic, strong) NSString *extMsg; //json格式 @end #pragma mark - WXLaunchMiniProgramResp /*! @brief 微信终端向第三方程序返回的WXLaunchMiniProgramReq处理结果。 * * 第三方程序向微信终端发送WXLaunchMiniProgramReq后,微信发送回来的处理结果,该结果用WXLaunchMiniProgramResp表示。 */ @interface WXLaunchMiniProgramResp : BaseResp @property (nonatomic, retain) NSString *extMsg; @end #pragma mark - WXTextObject /*! @brief 多媒体消息中包含的文本数据对象 * * 微信终端和第三方程序之间传递消息中包含的文本数据对象。 * @see WXMediaMessage */ @interface WXTextObject : NSObject /*! @brief 返回一个WXTextObject对象 * * @note 返回的WXTextObject对象是自动释放的 */ +(WXTextObject *) object; /** 地理位置信息 * @note 文本内容 */ @property (nonatomic, retain) NSString *contentText; @end ================================================ FILE: zhuishushenqi/NewVersion/ZSThirdPartSDK/ZSThirdPartSDK/Classes/WeChatSDK1.8.3/WechatAuthSDK.h ================================================ // // WechatAuthSDK.h // WechatAuthSDK // // Created by 李凯 on 13-11-29. // Copyright (c) 2013年 Tencent. All rights reserved. // #import #import enum AuthErrCode { WechatAuth_Err_Ok = 0, //Auth成功 WechatAuth_Err_NormalErr = -1, //普通错误 WechatAuth_Err_NetworkErr = -2, //网络错误 WechatAuth_Err_GetQrcodeFailed = -3, //获取二维码失败 WechatAuth_Err_Cancel = -4, //用户取消授权 WechatAuth_Err_Timeout = -5, //超时 }; @protocol WechatAuthAPIDelegate @optional - (void)onAuthGotQrcode:(UIImage *)image; //得到二维码 - (void)onQrcodeScanned; //二维码被扫描 - (void)onAuthFinish:(int)errCode AuthCode:(NSString *)authCode; //成功登录 @end @interface WechatAuthSDK : NSObject{ NSString *_sdkVersion; __weak id _delegate; } @property(nonatomic, weak) id delegate; @property(nonatomic, readonly) NSString *sdkVersion; //authSDK版本号 /*! @brief 发送登录请求,等待WechatAuthAPIDelegate回调 * * @param appId 微信开发者ID * @param nonceStr 一个随机的尽量不重复的字符串,用来使得每次的signature不同 * @param timeStamp 时间戳 * @param scope 应用授权作用域,拥有多个作用域用逗号(,)分隔 * @param signature 签名 * @param schemeData 会在扫码后拼在scheme后 * @return 成功返回YES,失败返回NO 注:该实现只保证同时只有一个Auth在运行,Auth未完成或未Stop再次调用Auth接口时会返回NO。 */ - (BOOL)Auth:(NSString *)appId nonceStr:(NSString *)nonceStr timeStamp:(NSString*)timeStamp scope:(NSString *)scope signature:(NSString *)signature schemeData:(NSString *)schemeData; /*! @brief 暂停登录请求 * * @return 成功返回YES,失败返回NO。 */ - (BOOL)StopAuth; @end ================================================ FILE: zhuishushenqi/NewVersion/ZSThirdPartSDK/ZSThirdPartSDK/Classes/WeChatSDK1.8.3/libWeChatSDK.a ================================================ [File too large to display: 18.4 MB] ================================================ FILE: zhuishushenqi/NewVersion/ZSThirdPartSDK/ZSThirdPartSDK/Classes/ZSLogin.h ================================================ // // ZSLogin.h // ZSThirdPartSDK // // Created by caony on 2019/6/22. // Copyright © 2019 cj. All rights reserved. // #import #import "ZSThirdLogin.h" NS_ASSUME_NONNULL_BEGIN @interface ZSLogin : NSObject @property (nonatomic, copy) NSString * token; @property (nonatomic, assign) NSInteger lastLoginType; @property (nonatomic, assign) BOOL mobileLogin; + (instancetype)share; - (BOOL)hasLogin; - (void)logout; @end NS_ASSUME_NONNULL_END ================================================ FILE: zhuishushenqi/NewVersion/ZSThirdPartSDK/ZSThirdPartSDK/Classes/ZSLogin.m ================================================ // // ZSLogin.m // ZSThirdPartSDK // // Created by caony on 2019/6/22. // Copyright © 2019 cj. All rights reserved. // #import "ZSLogin.h" #import "ZSThirdPartSDK-Swift.h" @interface ZSLogin () @end @implementation ZSLogin + (instancetype)share { static ZSLogin * shareInstance = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ shareInstance = [[self alloc] init]; }); return shareInstance; } - (instancetype)init { self = [super init]; if (self) { NSString * token = [[ZSThirdLogin shared] token]; NSString * mobileToken = [ZSMobileLogin share].userInfo.token; if (token.length > 0) { self.token = token; } else if (mobileToken.length > 0) { self.token = mobileToken; self.mobileLogin = YES; } } return self; } - (BOOL)hasLogin { return self.token.length == 0; } - (void)logout { [[ZSThirdLoginStorage share] resetLocalUserInfo]; [[ZSThirdLogin shared] logout]; } - (NSInteger)lastLoginType { return [ZSThirdLoginStorage share].lastLoginType; } @end ================================================ FILE: zhuishushenqi/NewVersion/ZSThirdPartSDK/ZSThirdPartSDK/Classes/ZSLoginHelper.h ================================================ // // ZSLoginHelper.h // ZSThirdPartSDK // // Created by caony on 2019/6/21. // Copyright © 2019年 cj. All rights reserved. // #import NS_ASSUME_NONNULL_BEGIN @interface ZSLoginHelper : NSObject @end NS_ASSUME_NONNULL_END ================================================ FILE: zhuishushenqi/NewVersion/ZSThirdPartSDK/ZSThirdPartSDK/Classes/ZSLoginHelper.m ================================================ // // ZSLoginHelper.m // ZSThirdPartSDK // // Created by caony on 2019/6/21. // Copyright © 2019年 cj. All rights reserved. // #import "ZSLoginHelper.h" @implementation ZSLoginHelper @end ================================================ FILE: zhuishushenqi/NewVersion/ZSThirdPartSDK/ZSThirdPartSDK/Classes/ZSLoginService.swift ================================================ // // ZSLoginService.swift // zhuishushenqi // // Created by caonongyun on 2018/10/19. // Copyright © 2018年 QS. All rights reserved. // import UIKit import HandyJSON import ZSAppConfig import ZSExtension public class ZSLoginService: NSObject { public func QQLogin(url:String, parameter:[String:Any]?,completion:@escaping ZSBaseCallback) { zs_post(url, parameters: parameter) { (json) in if let user = ZSQQLoginResponse.deserialize(from: json) { completion(user) } else { completion(nil) } } } public func WXLogin(url:String, parameter:[String:Any]?,completion:@escaping ZSBaseCallback) { zs_post(url, parameters: parameter) { (json) in if let user = ZSQQLoginResponse.deserialize(from: json) { completion(user) } else { completion(nil) } } } public func WBLogin(url:String, parameter:[String:Any]?,completion:@escaping ZSBaseCallback) { zs_post(url, parameters: parameter) { (json) in if let user = ZSQQLoginResponse.deserialize(from: json) { completion(user) } else { completion(nil) } } } public func fetchSMSCode(url:String, parameter:[String:Any]?,completion:@escaping ZSBaseCallback<[String:Any]>) { zs_post(url, parameters: parameter) { (json) in completion(json) } } public func mobileLgin(urlString:String, param:[String:Any]?, completion:@escaping ZSBaseCallback) { zs_post(urlString, parameters: param) { (json) in if let user = ZSQQLoginResponse.deserialize(from: json) { completion(user) } } } } ================================================ FILE: zhuishushenqi/NewVersion/ZSThirdPartSDK/ZSThirdPartSDK/Classes/ZSMobileLogin.swift ================================================ // // ZSMobileLogin.swift // zhuishushenqi // // Created by caonongyun on 2018/10/24. // Copyright © 2018年 QS. All rights reserved. // import UIKit @objcMembers public class ZSMobileLogin: NSObject { public let tencentCaptChaAppID = "2061491951" public var mobile:String = "" public var userInfo:ZSQQLoginResponse? public let MobileLoginQQUserFilePathKey = "MobileLoginQQUserFilePathKey" public let MobileLoginLastLoginTypeKey = "MobileLoginLastLoginTypeKey" public static let share = ZSMobileLogin() private override init() { super.init() let user = self.localUserInfo() self.userInfo = user } public func startVerifyHTML() ->String { let sdkOpts = ["sdkOpts":["height":140,"width":140]] let data = try! JSONSerialization.data(withJSONObject: sdkOpts, options: .prettyPrinted) if let string = String(data: data, encoding: .utf8) { if let percentString = string.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed) { let radom = self.getRandomSeq() let jsString = "
" return jsString } } return "" } public func fetchSMSCode() { } public func getRandomSeq() ->String { let v2 = arc4random() var v3:CLongLong = 0 if v2 != 0 { v3 = CLongLong(v2) } else { v3 = 1 } let v4 = Date() let v6 = v4.timeIntervalSince1970 let v7 = v6 * 1000.0 let result = String(format: "%llu_%ld", v7,v3) return result } public func saveUserInfo(userInfo:ZSQQLoginResponse) { let filePath = "\(NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0])/\(MobileLoginQQUserFilePathKey.md5())" NSKeyedArchiver.archiveRootObject(userInfo, toFile: filePath) UserDefaults.standard.setValue("mobile", forKey: MobileLoginLastLoginTypeKey.md5() ?? "") UserDefaults.standard.synchronize() } public func localUserInfo() -> ZSQQLoginResponse? { let filePath = "\(NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0])/\(MobileLoginQQUserFilePathKey.md5())" let user = NSKeyedUnarchiver.unarchiveObject(withFile: filePath) as? ZSQQLoginResponse return user } public func resetLocalUserInfo() { let filePath = "\(NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0])/\(MobileLoginQQUserFilePathKey.md5())" try? FileManager.default.removeItem(atPath: filePath) } } ================================================ FILE: zhuishushenqi/NewVersion/ZSThirdPartSDK/ZSThirdPartSDK/Classes/ZSQQUser.swift ================================================ // // ZSQQUser.swift // zhuishushenqi // // Created by caonongyun on 2018/10/19. // Copyright © 2018年 QS. All rights reserved. // import UIKit import HandyJSON @objcMembers public class ZSQQLoginResponse: NSObject, HandyJSON ,NSCoding { public var user:ZSQQUser? public var token:String = "" public var isWeixinNew:Bool = false public var ok:Bool = true public var isNew:Bool = true public var bindMobile:Bool = false public var register:Bool = false public var code:String = "" public required override init() {} public required init?(coder aDecoder: NSCoder) { self.user = aDecoder.decodeObject(forKey: "user") as? ZSQQUser self.token = aDecoder.decodeObject(forKey: "token") as! String self.isWeixinNew = aDecoder.decodeBool(forKey: "isWeixinNew") self.ok = aDecoder.decodeBool(forKey: "ok") self.isNew = aDecoder.decodeBool(forKey: "isNew") self.bindMobile = aDecoder.decodeBool(forKey: "bindMobile") self.register = aDecoder.decodeBool(forKey: "register") } public func encode(with aCoder: NSCoder) { aCoder.encode(self.user, forKey: "user") aCoder.encode(self.token, forKey: "token") aCoder.encode(self.isWeixinNew, forKey: "isWeixinNew") aCoder.encode(self.ok, forKey: "ok") aCoder.encode(self.isNew, forKey: "isNew") aCoder.encode(self.bindMobile, forKey: "bindMobile") aCoder.encode(self.register, forKey: "register") } } @objcMembers public class ZSQQUser: NSObject, HandyJSON, NSCoding { public var _id:String = "" public var nickname:String = "" public var avatar:String = "" public var exp:Int = 0 public var lv:Int = 0 public var gender:String = "" public var type:String = "" public var likeCate:ZSLikeCate? public required override init() {} public required init?(coder aDecoder: NSCoder) { self._id = aDecoder.decodeObject(forKey: "_id") as! String self.nickname = aDecoder.decodeObject(forKey: "nickname") as! String self.avatar = aDecoder.decodeObject(forKey: "avatar") as! String self.exp = aDecoder.decodeInteger(forKey: "exp") self.lv = aDecoder.decodeInteger(forKey: "lv") self.gender = aDecoder.decodeObject(forKey: "gender") as! String self.type = aDecoder.decodeObject(forKey: "type") as! String self.likeCate = aDecoder.decodeObject(forKey: "likeCate") as? ZSLikeCate } public func encode(with aCoder: NSCoder) { aCoder.encode(self._id, forKey: "_id") aCoder.encode(self.nickname, forKey: "nickname") aCoder.encode(self.avatar, forKey: "avatar") aCoder.encode(self.exp, forKey: "exp") aCoder.encode(self.lv, forKey: "lv") aCoder.encode(self.gender, forKey: "gender") aCoder.encode(self.type, forKey: "type") aCoder.encode(self.likeCate, forKey: "likeCate") } } @objcMembers public class ZSLikeCate: NSObject, HandyJSON, NSCoding { public var male:[String] = [] public var female:[String] = [] public var press:[String] = [] public var picture:[String] = [] public required override init() {} public required init?(coder aDecoder: NSCoder) { self.male = aDecoder.decodeObject(forKey: "male") as! [String] self.female = aDecoder.decodeObject(forKey: "female") as! [String] self.press = aDecoder.decodeObject(forKey: "press") as! [String] self.picture = aDecoder.decodeObject(forKey: "picture") as! [String] } public func encode(with aCoder: NSCoder) { aCoder.encode(self.male, forKey: "male") aCoder.encode(self.female, forKey: "female") aCoder.encode(self.press, forKey: "press") aCoder.encode(self.picture, forKey: "picture") } } //{ // "user": { // "_id": "57ac9879c12b61e826bd7221", // "nickname": "高手寂寞", // "avatar": "/avatar/80/9c/809cf8858bc8c8008793612a935f72f7", // "exp": 2470, // "lv": 8, // "gender": "female", // "type": "normal", // "likeCate": { // "male": ["都市", "历史", "游戏"], // "female": [], // "press": [], // "picture": [] // } // }, // "token": "2T6IONkhfG4LEg49w9rCMqwA", // "isWeixinNew": false, // "ok": true, // "isNew": false, // "bindMobile": false, // "register": false //} ================================================ FILE: zhuishushenqi/NewVersion/ZSThirdPartSDK/ZSThirdPartSDK/Classes/ZSThirdLogin.h ================================================ // // ZSThirdLogin.h // ZSThirdPartSDK // // Created by caony on 2019/6/22. // Copyright © 2019 cj. All rights reserved. // #import typedef void(^ZSLoginSuccess)(void); typedef void(^ZSThirdLoginResultHandler)(BOOL); @interface ZSThirdLogin : NSObject @property (nonatomic, copy) ZSLoginSuccess successHandler; @property (nonatomic, copy) ZSThirdLoginResultHandler loginResultHandler; + (instancetype)shared; - (void)loginWithType:(NSInteger)type; - (void)logout; - (NSString *)token; // 调起各种三方认证 - (void)QQAuth; - (void)WXAuth; - (void)WBAuth; @end FOUNDATION_EXTERN NSString * _Nullable const QQAppID; FOUNDATION_EXTERN NSString * _Nullable const WXAppID; FOUNDATION_EXTERN NSString * _Nullable const WXAppSecret; FOUNDATION_EXTERN NSString * _Nullable const WBAppID; FOUNDATION_EXTERN NSString * _Nullable const WBAppSecret; FOUNDATION_EXTERN NSString * _Nullable const WBRedirectURI; ================================================ FILE: zhuishushenqi/NewVersion/ZSThirdPartSDK/ZSThirdPartSDK/Classes/ZSThirdLogin.m ================================================ // // ZSThirdLogin.m // ZSThirdPartSDK // // Created by caony on 2019/6/22. // Copyright © 2019 cj. All rights reserved. // #import "ZSThirdLogin.h" #import "WeiboSDK.h" #import "WXApi.h" #import "WechatAuthSDK.h" #import "TencentOpenAPI/QQApiInterface.h" #import "TencentOpenAPI/TencentOAuth.h" #import #import #import "ZSLogin.h" #import "WXApiRequestHandler.h" NSString * const QQAppID = @"100497199"; NSString * const WXAppID = @"wxaf0fdeed6872dfcf"; NSString * const WXAppSecret = @"0464c67bdd87c303c5bfdc5761beb329"; NSString * const WBAppID = @"2023668704"; NSString * const WBAppSecret = @"26efa7a6a6bed540092c9535bda75db9"; NSString * const WBRedirectURI = @"http://ushaqi.com"; @interface ZSThirdLogin () @property (nonatomic, strong) TencentOAuth * tencentOAuth; @property (nonatomic, strong) ZSLoginService * webService; @property (nonatomic, strong) ZSQQLoginResponse * userInfo; @property (nonatomic, strong) ZSWXAccessTokenResp * wxTokenResp; @property (nonatomic, strong) WBAuthorizeResponse * wbAuthorizeResp; @property (nonatomic, strong) NSArray * permissions; @property (nonatomic, strong) NSString * wxAuthScope; @end @implementation ZSThirdLogin + (instancetype)shared { static ZSThirdLogin * sharedInstance = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ sharedInstance = [[self alloc] init]; }); return sharedInstance; } - (instancetype)init { self = [super init]; if (self) { _tencentOAuth = [[TencentOAuth alloc] initWithAppId:QQAppID andDelegate:self]; ZSResponseModel * respModel = [ZSThirdLoginStorage share].localUserInfo; self.userInfo = respModel.qqResp; self.wxTokenResp = respModel.wxResp; } return self; } - (NSString *)token { return self.userInfo.token; } - (void)logout { self.userInfo = nil; self.wxTokenResp = nil; } - (void)QQAuth { [self.tencentOAuth authorize:self.permissions inSafari:NO]; } - (void)WXAuth { [WXApi registerApp:WXAppID]; [[WXApiRequestHandler share] sendWXAuthWithScope:self.wxAuthScope state:@"YouShaQi"]; } - (void)WBAuth { WBAuthorizeRequest * request = [[WBAuthorizeRequest alloc] init]; request.redirectURI = WBRedirectURI; request.scope = @"all"; [WeiboSDK sendRequest:request]; } - (void)loginWithType:(ThirdLoginType)type { switch (type) { case ThirdLoginTypeQQ: [self QQLogin]; break; case ThirdLoginTypeWX: [self WXLogin]; break; case ThirdLoginTypeWB: [self WBLogin]; break; case ThirdLoginTypeXM: break; default: break; } } - (void)refreshWXToken { // // https://api.weixin.qq.com/sns/oauth2/refresh_token?appid=APPID&grant_type=refresh_token&refresh_token=REFRESH_TOKEN // // 一般第一次获取到access_token后直接登录追书d,获取token,后面基本不需要access_token了 // } - (void)QQLogin { NSString * idfa = [ASIdentifierManager sharedManager].advertisingIdentifier.UUIDString; NSString * platform_code = @"QQ"; NSString * platform_token = _tencentOAuth.accessToken; NSString * platform_uid = _tencentOAuth.openId; NSString * version = @"2"; [[ZSRequestHelper share] QQLoginWithWebService:self.webService idfa:idfa platform_code:platform_code platform_token:platform_token platform_uid:platform_uid version:version tag:@"" successHandler:^(ZSQQLoginResponse * _Nullable resp) { [ZSLogin share].token = resp.token; if (self.successHandler) { self.successHandler(); } } loginResultHandler:^(BOOL result) { if (self.loginResultHandler) { self.loginResultHandler(result); } }]; } - (void)WXLogin { NSString * idfa = [ASIdentifierManager sharedManager].advertisingIdentifier.UUIDString; NSString * platform_code = @"WeixinNew"; NSString * platform_token = _wxTokenResp.access_token; NSString * platform_uid = _wxTokenResp.openid; NSString * version = @"2"; NSString * tag = @"zssq"; [[ZSRequestHelper share] WXLoginWithWebService:self.webService idfa:idfa platform_code:platform_code platform_token:platform_token platform_uid:platform_uid version:version tag:tag successHandler:^(ZSQQLoginResponse * _Nullable resp) { [ZSLogin share].token = resp.token; if (self.successHandler) { self.successHandler(); } } loginResultHandler:^(BOOL result) { if (self.loginResultHandler) { self.loginResultHandler(result); } }]; } - (void)WBLogin { NSString * idfa = [ASIdentifierManager sharedManager].advertisingIdentifier.UUIDString; NSString * platform_code = @"SinaWeibo"; NSString * platform_token = _wbAuthorizeResp.accessToken; NSString * platform_uid = _wbAuthorizeResp.userID; NSString * version = @"2"; NSString * tag = @"zssq"; [[ZSRequestHelper share] WBLoginWithWebService:self.webService idfa:idfa platform_code:platform_code platform_token:platform_token platform_uid:platform_uid version:version tag:tag successHandler:^(ZSQQLoginResponse * _Nullable resp) { [ZSLogin share].token = resp.token; if (self.successHandler) { self.successHandler(); } } loginResultHandler:^(BOOL result) { if (self.loginResultHandler) { self.loginResultHandler(result); } }]; } - (NSArray *)permissions { return @[@"get_user_info",@"get_simple_userinfo",@"add_t"]; } - (NSString *)wxAuthScope { return @"snsapi_userinfo,snsapi_friend,snsapi_contact,snsapi_message"; } #pragma mark - TencentSessionDelegate - (void)tencentDidLogin { [self loginWithType:(ThirdLoginTypeQQ)]; } - (void)tencentDidNotLogin:(BOOL)cancelled { if (cancelled) { #warning toast 用户取消了操作 NSLog(@"用户取消了操作"); } } - (void)tencentDidNotNetWork { #warning toast 网络连接失败 NSLog(@"网络连接失败"); } #pragma mark - QQApiInterfaceDelegate - (void)onReq:(QQBaseReq *)req { } - (void)onResp:(QQBaseResp *)resp { } - (void)isOnlineResponse:(NSDictionary *)response { } #pragma mark - WeiboSDKDelegate - (void)didReceiveWeiboRequest:(WBBaseRequest *)request { } - (void)didReceiveWeiboResponse:(WBBaseResponse *)response { WBAuthorizeResponse * authorize = (WBAuthorizeResponse *)response; if ([authorize isKindOfClass:[WBAuthorizeResponse class]]) { self.wbAuthorizeResp = authorize; [self loginWithType:(ThirdLoginTypeWB)]; } } @end ================================================ FILE: zhuishushenqi/NewVersion/ZSThirdPartSDK/ZSThirdPartSDK/Classes/ZSThirdPartSDK.h ================================================ // // ZSThirdPartSDK.h // ZSThirdPartSDK // // Created by caony on 2019/6/21. // Copyright © 2019年 cj. All rights reserved. // #import //! Project version number for ZSThirdPartSDK. FOUNDATION_EXPORT double ZSThirdPartSDKVersionNumber; //! Project version string for ZSThirdPartSDK. FOUNDATION_EXPORT const unsigned char ZSThirdPartSDKVersionString[]; // In this header, you should import all the public headers of your framework using statements like #import #import "ZSThirdLogin.h" #import "ZSLogin.h" ================================================ FILE: zhuishushenqi/NewVersion/ZSThirdPartSDK/ZSThirdPartSDK/Classes/ZSWXAccessTokenResp.swift ================================================ // // ZSWXAccessTokenResp.swift // ZSThirdPartSDK // // Created by caony on 2019/6/22. // Copyright © 2019 cj. All rights reserved. // import Foundation import HandyJSON import ZSExtension import ZSAppConfig import ZSAPI @objc public enum ThirdLoginType:Int { case None = 0 case WX case QQ case WB case XM } @objcMembers public class ZSRequestHelper: NSObject { public static let share = ZSRequestHelper() private override init () {} public func request(_ urlStr: String,parameters: [String:Any]? = nil,_ handler:@escaping ZSBaseCallback) { zs_get(urlStr, parameters: parameters) { (json) in let tokenResp = ZSWXAccessTokenResp.deserialize(from: json) handler(tokenResp) } } public func WXLogin(webService:ZSLoginService, idfa:String, platform_code:String, platform_token:String, platform_uid:String, version:String, tag:String, successHandler:@escaping (ZSQQLoginResponse?)->Void, loginResultHandler:@escaping (Bool)->Void) { KeyWindow?.showProgress() let loginApi:ZSAPI = ZSAPI.login(idfa: idfa, platform_code: platform_code, platform_token: platform_token, platform_uid: platform_uid, version: version ,tag: tag) webService.WXLogin(url: loginApi.path, parameter: loginApi.parameters) { (json) in KeyWindow?.hideProgress() if let user = json { // 登录成功 KeyWindow?.showTip(tip: "登录成功") ZSThirdLoginStorage.share.saveUserInfo(userInfo: user, type: .QQ) successHandler(user) loginResultHandler(true) } else { KeyWindow?.showTip(tip: "登录失败") successHandler(nil) loginResultHandler(false) } } } public func WBLogin(webService:ZSLoginService, idfa:String, platform_code:String, platform_token:String, platform_uid:String, version:String, tag:String, successHandler:@escaping (ZSQQLoginResponse?)->Void, loginResultHandler:@escaping (Bool)->Void) { KeyWindow?.showProgress() let loginApi:ZSAPI = ZSAPI.login(idfa: idfa, platform_code: platform_code, platform_token: platform_token, platform_uid: platform_uid, version: version ,tag: tag) webService.WBLogin(url: loginApi.path, parameter: loginApi.parameters) { (json) in KeyWindow?.hideProgress() if let user = json { // 登录成功 KeyWindow?.showTip(tip: "登录成功") ZSThirdLoginStorage.share.saveUserInfo(userInfo: user, type: .QQ) successHandler(user) loginResultHandler(true) } else { KeyWindow?.showTip(tip: "登录失败") successHandler(nil) loginResultHandler(false) } } } public func QQLogin(webService:ZSLoginService, idfa:String, platform_code:String, platform_token:String, platform_uid:String, version:String, tag:String, successHandler:@escaping (ZSQQLoginResponse?)->Void, loginResultHandler:@escaping (Bool)->Void) { KeyWindow?.showProgress() let loginApi:ZSAPI = ZSAPI.login(idfa: idfa, platform_code: platform_code, platform_token: platform_token, platform_uid: platform_uid, version: version ,tag: tag) webService.QQLogin(url: loginApi.path, parameter: loginApi.parameters) { (json) in KeyWindow?.hideProgress() if let user = json { // 登录成功 KeyWindow?.showTip(tip: "登录成功") // ZSLogin.share.token = self.userInfo?.token ?? "" ZSThirdLoginStorage.share.saveUserInfo(userInfo: user, type: .QQ) successHandler(user) loginResultHandler(true) } else { KeyWindow?.showTip(tip: "登录失败") successHandler(nil) loginResultHandler(false) } } } } @objcMembers open class ZSThirdLoginStorage: NSObject { public var rawData:[String:Any] = [:] public var works:[Any] = [] public var educations:[Any] = [] public var level:CLongLong = 0 public var regAt:Double = 0 public var shareCount:CLongLong = 0 public var friendCount:CLongLong = 0 public var followerCount:CLongLong = 0 public var birthday:Date? public var verifyReason:String = "" public var verifyType:CLongLong = 0 public var aboutMe:String = "" public var url:String = "" public var gender:CLongLong = 0 public var icon:String = "" public var nickname:String = "" public var uid:String = "" public var credential:SSDKCredential? public var data:SSDKQQData? public var platformType:CLongLong = 0 // 只作为上次登录方式的记录,如果该登录方式的数据不存在,则应该将该值置为None public var lastLoginType:ThirdLoginType = .None public let ThirdLoginQQUserFilePathKey = "ThirdLoginQQUserFilePathKey" public let ThirdLoginWXTokenFilePathKey = "ThirdLoginWXTokenFilePathKey" public let ThirdLoginLastLoginTypeKey = "ThirdLoginLastLoginTypeKey" public static let share = ZSThirdLoginStorage() private override init() { super.init() let lastTypeObj = UserDefaults.standard.integer(forKey: ThirdLoginLastLoginTypeKey.md5() ?? "") self.lastLoginType = ThirdLoginType(rawValue: lastTypeObj ) ?? .None } public func canHandle(pasteData:[String:Any]) -> Bool { if pasteData.allKeys().count > 0 { if pasteData.allKeys()[0].contains("tencent") { return true } } return false } public func handle(pasteData:[String:Data]) { for (key,value) in pasteData { if key.contains("tencent") { if let obj = NSKeyedUnarchiver.unarchiveObject(with: value) as? [String:Any] { self.rawData = obj self.data = SSDKQQData.deserialize(from: obj) print(self.rawData) } } } } public func saveWXToken(wxTokenResp:ZSWXAccessTokenResp?) { if let resp = wxTokenResp { let TokenFilePath = "\(NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0])/\(ThirdLoginWXTokenFilePathKey.md5())" NSKeyedArchiver.archiveRootObject(resp, toFile: TokenFilePath) } } public func localUserInfo() -> ZSResponseModel { let filePath = "\(NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0])/\(ThirdLoginQQUserFilePathKey.md5())" let TokenFilePath = "\(NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0])/\(ThirdLoginWXTokenFilePathKey.md5())" let user = NSKeyedUnarchiver.unarchiveObject(withFile: filePath) as? ZSQQLoginResponse let token = NSKeyedUnarchiver.unarchiveObject(withFile: TokenFilePath) as? ZSWXAccessTokenResp let respModel = ZSResponseModel() respModel.qqResp = user respModel.wxResp = token return respModel } public func resetLocalUserInfo() { let filePath = "\(NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0])/\(ThirdLoginQQUserFilePathKey.md5())" let TokenFilePath = "\(NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0])/\(ThirdLoginWXTokenFilePathKey.md5())" try? FileManager.default.removeItem(atPath: filePath) try? FileManager.default.removeItem(atPath: TokenFilePath) } public func saveUserInfo(userInfo:ZSQQLoginResponse, type:ThirdLoginType) { let filePath = "\(NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0])/\(ThirdLoginQQUserFilePathKey.md5())" NSKeyedArchiver.archiveRootObject(userInfo, toFile: filePath) switch type { case .QQ: UserDefaults.standard.setValue(ThirdLoginType.QQ.rawValue, forKey: ThirdLoginLastLoginTypeKey.md5() ?? "") UserDefaults.standard.synchronize() break case .WX: // 要自己保存微信登录成功拿到的token信息 // saveWXToken(wxTokenResp: ZSThirdLogin.share.wxTokenResp) UserDefaults.standard.setValue(ThirdLoginType.WX.rawValue, forKey: ThirdLoginLastLoginTypeKey.md5() ?? "") UserDefaults.standard.synchronize() break case .WB: break case .None: break default: break } } } @objcMembers public class ZSResponseModel: NSObject { public var qqResp:ZSQQLoginResponse? public var wxResp:ZSWXAccessTokenResp? } @objcMembers public class SSDKCredential: NSObject { public var expirationDate:Date? public var openId:String = "" public var available:Bool = false public var rawData:[String:Any] = [:] public var expired:String = "" public var secret:String = "" public var token:String = "" public var uid:String = "" } @objcMembers public class SSDKQQData: NSObject,HandyJSON { public var pf:String = "" public var pfkey:String = "" public var access_token:String = "" public var passDataResp:[Any] = [] public var pay_token:String = "" public var msg:String = "" public var user_cancelled:Bool = false public var ret:Int = 0 public var encrytoken:String = "" public var expires_in:TimeInterval = 7776000 public required override init() {} } @objcMembers public class ZSWXAccessTokenResp: NSObject ,HandyJSON, NSCoding{ public var openid:String = "" public var scope:String = "" public var expires_in:Float = 0 public var access_token:String = "" public var unionid:String = "" public var refresh_token:String = "" public required override init() {} public required init?(coder aDecoder: NSCoder) { aDecoder.decodeObject(forKey: "openid") aDecoder.decodeObject(forKey: "scope") aDecoder.decodeObject(forKey: "expires_in") aDecoder.decodeObject(forKey: "access_token") aDecoder.decodeObject(forKey: "unionid") aDecoder.decodeObject(forKey: "refresh_token") } public func encode(with aCoder: NSCoder) { aCoder.encode(self.openid, forKey: "openid") aCoder.encode(self.scope, forKey: "scope") aCoder.encode(self.expires_in, forKey: "expires_in") aCoder.encode(self.access_token, forKey: "access_token") aCoder.encode(self.unionid, forKey: "unionid") aCoder.encode(self.refresh_token, forKey: "refresh_token") } } ================================================ FILE: zhuishushenqi/NewVersion/ZSThirdPartSDK/ZSThirdPartSDK/Classes/libWeiboSDK/WBHttpRequest.h ================================================ // // WBHttpRequest.h // WeiboSDK // // Created by DannionQiu on 14-9-18. // Copyright (c) 2014年 SINA iOS Team. All rights reserved. // #import #import #pragma mark - WBHttpRequest and WBHttpRequestDelegate @class WBHttpRequest; /** 接收并处理来自微博sdk对于网络请求接口的调用响应 以及logOutWithToken的请求 */ @protocol WBHttpRequestDelegate /** 收到一个来自微博Http请求的响应 @param response 具体的响应对象 */ @optional - (void)request:(WBHttpRequest *)request didReceiveResponse:(NSURLResponse *)response; /** 收到一个来自微博Http请求失败的响应 @param error 错误信息 */ @optional - (void)request:(WBHttpRequest *)request didFailWithError:(NSError *)error; /** 收到一个来自微博Http请求的网络返回 @param result 请求返回结果 */ @optional - (void)request:(WBHttpRequest *)request didFinishLoadingWithResult:(NSString *)result; /** 收到一个来自微博Http请求的网络返回 @param data 请求返回结果 */ @optional - (void)request:(WBHttpRequest *)request didFinishLoadingWithDataResult:(NSData *)data; /** 收到快速SSO授权的重定向 @param URI */ @optional - (void)request:(WBHttpRequest *)request didReciveRedirectResponseWithURI:(NSURL *)redirectUrl; @end /** 微博封装Http请求的消息结构 */ @interface WBHttpRequest : NSObject { NSURLConnection *connection; NSMutableData *responseData; } /** 用户自定义请求地址URL */ @property (nonatomic, strong) NSString *url; /** 用户自定义请求方式 支持"GET" "POST" */ @property (nonatomic, strong) NSString *httpMethod; /** 用户自定义请求参数字典 */ @property (nonatomic, strong) NSDictionary *params; /** WBHttpRequestDelegate对象,用于接收微博SDK对于发起的接口请求的请求的响应 */ @property (nonatomic, weak) id delegate; /** 用户自定义TAG 用于区分回调Request */ @property (nonatomic, strong) NSString* tag; /** 统一HTTP请求接口 调用此接口后,将发送一个HTTP网络请求 @param url 请求url地址 @param httpMethod 支持"GET" "POST" @param params 向接口传递的参数结构 @param delegate WBHttpRequestDelegate对象,用于接收微博SDK对于发起的接口请求的请求的响应 @param tag 用户自定义TAG,将通过回调WBHttpRequest实例的tag属性返回 */ + (WBHttpRequest *)requestWithURL:(NSString *)url httpMethod:(NSString *)httpMethod params:(NSDictionary *)params delegate:(id)delegate withTag:(NSString *)tag; /** 统一微博Open API HTTP请求接口 调用此接口后,将发送一个HTTP网络请求(用于访问微博open api) @param accessToken 应用获取到的accessToken,用于身份验证 @param url 请求url地址 @param httpMethod 支持"GET" "POST" @param params 向接口传递的参数结构 @param delegate WBHttpRequestDelegate对象,用于接收微博SDK对于发起的接口请求的请求的响应 @param tag 用户自定义TAG,将通过回调WBHttpRequest实例的tag属性返回 */ + (WBHttpRequest *)requestWithAccessToken:(NSString *)accessToken url:(NSString *)url httpMethod:(NSString *)httpMethod params:(NSDictionary *)params delegate:(id)delegate withTag:(NSString *)tag; /** 取消网络请求接口 调用此接口后,将取消当前网络请求,建议同时[WBHttpRequest setDelegate:nil]; 注意:该方法只对使用delegate的request方法有效。无法取消任何使用block的request的网络请求接口。 */ - (void)disconnect; #pragma mark - block extension typedef void (^WBRequestHandler)(WBHttpRequest *httpRequest, id result, NSError *error); /** 统一微博Open API HTTP请求接口 调用此接口后,将发送一个HTTP网络请求(用于访问微博open api) @param url 请求url地址 @param httpMethod 支持"GET" "POST" @param params 向接口传递的参数结构 @param queue 发起请求的NSOperationQueue对象,如queue为nil,则在主线程([NSOperationQueue mainQueue])发起请求。 @param handler 接口请求返回调用的block方法 */ + (WBHttpRequest *)requestWithURL:(NSString *)url httpMethod:(NSString *)httpMethod params:(NSDictionary *)params queue:(NSOperationQueue*)queue withCompletionHandler:(WBRequestHandler)handler; /** 统一HTTP请求接口 调用此接口后,将发送一个HTTP网络请求 @param url 请求url地址 @param httpMethod 支持"GET" "POST" @param params 向接口传递的参数结构 @param queue 发起请求的NSOperationQueue对象,如queue为nil,则在主线程([NSOperationQueue mainQueue])发起请求。 @param handler 接口请求返回调用的block方法 */ + (WBHttpRequest *)requestWithAccessToken:(NSString *)accessToken url:(NSString *)url httpMethod:(NSString *)httpMethod params:(NSDictionary *)params queue:(NSOperationQueue*)queue withCompletionHandler:(WBRequestHandler)handler; @end ================================================ FILE: zhuishushenqi/NewVersion/ZSThirdPartSDK/ZSThirdPartSDK/Classes/libWeiboSDK/WeiboSDK.h ================================================ // // WeiboSDKHeaders.h // WeiboSDKDemo // // Created by Wade Cheng on 4/3/13. // Copyright (c) 2013 SINA iOS Team. All rights reserved. // #import #import #import "WBHttpRequest.h" typedef NS_ENUM(NSInteger, WeiboSDKResponseStatusCode) { WeiboSDKResponseStatusCodeSuccess = 0,//成功 WeiboSDKResponseStatusCodeUserCancel = -1,//用户取消发送 WeiboSDKResponseStatusCodeSentFail = -2,//发送失败 WeiboSDKResponseStatusCodeAuthDeny = -3,//授权失败 WeiboSDKResponseStatusCodeUserCancelInstall = -4,//用户取消安装微博客户端 WeiboSDKResponseStatusCodePayFail = -5,//支付失败 WeiboSDKResponseStatusCodeShareInSDKFailed = -8,//分享失败 详情见response UserInfo WeiboSDKResponseStatusCodeUnsupport = -99,//不支持的请求 WeiboSDKResponseStatusCodeUnknown = -100, }; @protocol WeiboSDKDelegate; @protocol WBHttpRequestDelegate; @class WBBaseRequest; @class WBBaseResponse; @class WBMessageObject; @class WBImageObject; @class WBBaseMediaObject; @class WBHttpRequest; @class PHAsset; @class WBNewVideoObject; /** 微博SDK接口类 */ @interface WeiboSDK : NSObject /** 检查用户是否安装了微博客户端程序 @return 已安装返回YES,未安装返回NO */ + (BOOL)isWeiboAppInstalled; /** 检查用户是否可以通过微博客户端进行分享 @return 可以使用返回YES,不可以使用返回NO */ + (BOOL)isCanShareInWeiboAPP; /** 检查用户是否可以使用微博客户端进行SSO授权 @return 可以使用返回YES,不可以使用返回NO */ + (BOOL)isCanSSOInWeiboApp; /** 打开微博客户端程序 @return 成功打开返回YES,失败返回NO */ + (BOOL)openWeiboApp; /** 获取微博客户端程序的itunes安装地址 @return 微博客户端程序的itunes安装地址 */ + (NSString *)getWeiboAppInstallUrl; /** 获取当前微博SDK的版本号 @return 当前微博SDK的版本号 */ + (NSString *)getSDKVersion; extern NSString * const WeiboSDKGetAidSucessNotification; extern NSString * const WeiboSDKGetAidFailNotification; /** 获取当前微博SDK的aid 返回的aid值可能为 nil ,当值为 nil 时会尝试获取 aid 值。 当获取成功( aid 值变为有效值)时,SDK会发出名为 WeiboSDKGetAidSucessNotification 的通知,通知中带有 aid 值。 当获取失败时,SDK会发出名为 WeiboSDKGetAidFailNotification 的通知,通知中带有 NSError 对象。 @return aid 用于广告的与设备信息相关的标识符 */ + (NSString *)getWeiboAid; /** 向微博客户端程序注册第三方应用 @param appKey 微博开放平台第三方应用appKey @return 注册成功返回YES,失败返回NO */ + (BOOL)registerApp:(NSString *)appKey; /** 处理微博客户端程序通过URL启动第三方应用时传递的数据 需要在 application:openURL:sourceApplication:annotation:或者application:handleOpenURL中调用 @param url 启动第三方应用的URL @param delegate WeiboSDKDelegate对象,用于接收微博触发的消息 @see WeiboSDKDelegate */ + (BOOL)handleOpenURL:(NSURL *)url delegate:(id)delegate; /** 发送请求给微博客户端程序,并切换到微博 请求发送给微博客户端程序之后,微博客户端程序会进行相关的处理,处理完成之后一定会调用 [WeiboSDKDelegate didReceiveWeiboResponse:] 方法将处理结果返回给第三方应用 @param request 具体的发送请求 @see [WeiboSDKDelegate didReceiveWeiboResponse:] @see WBBaseResponse */ + (BOOL)sendRequest:(WBBaseRequest *)request; /** 收到微博客户端程序的请求后,发送对应的应答给微博客户端端程序,并切换到微博 第三方应用收到微博的请求后,异步处理该请求,完成后必须调用该函数将应答返回给微博 @param response 具体的应答内容 @see WBBaseRequest */ + (BOOL)sendResponse:(WBBaseResponse *)response; /** 设置WeiboSDK的调试模式 当开启调试模式时,WeiboSDK会在控制台输出详细的日志信息,开发者可以据此调试自己的程序。默认为 NO @param enabled 开启或关闭WeiboSDK的调试模式 */ + (void)enableDebugMode:(BOOL)enabled; /** 取消授权,登出接口 调用此接口后,token将失效 @param token 第三方应用之前申请的Token @param delegate WBHttpRequestDelegate对象,用于接收微博SDK对于发起的接口请求的请求的响应 @param tag 用户自定义TAG,将通过回调WBHttpRequest实例的tag属性返回 */ + (void)logOutWithToken:(NSString *)token delegate:(id)delegate withTag:(NSString*)tag; /** 呼起微博客户端或打开微博H5页面,SDK自动检测是否安装微博客户端,当调用SDK相关方法时: 有的话呼起微博客户端定位到对应界面; 没有的话打开 webView 加载相应的微博H5页面; @param uid 用户id @param mid 微博id @param aid 文章id */ //连接到指定用户的微博个人主页,连接后可进行加关注等互动 + (void)linkToUser:(NSString *)uid; //连接到指定的单条微博详情页,连接后可对这条微博进行转、评、赞等互动 + (void)linkToSingleBlog:(NSString *)uid blogID:(NSString *)mid; //连接到指定的微博头条文章页 + (void)linkToArticle:(NSString *)aid; //分享到微博 + (void)shareToWeibo:(NSString *)content; //评论指定的微博 + (void)commentToWeibo:(NSString *)mid; //连接到微博搜索内容流 + (void)linkToSearch:(NSString *)keyword; //连接到我的微博消息流 + (void)linkToTimeLine; //连接到我的微博个人主页 + (void)linkToProfile; @end /** 接收并处理来至微博客户端程序的事件消息 */ @protocol WeiboSDKDelegate /** 收到一个来自微博客户端程序的请求 收到微博的请求后,第三方应用应该按照请求类型进行处理,处理完后必须通过 [WeiboSDK sendResponse:] 将结果回传给微博 @param request 具体的请求对象 */ - (void)didReceiveWeiboRequest:(WBBaseRequest *)request; /** 收到一个来自微博客户端程序的响应 收到微博的响应后,第三方应用可以通过响应类型、响应的数据和 WBBaseResponse.userInfo 中的数据完成自己的功能 @param response 具体的响应对象 */ - (void)didReceiveWeiboResponse:(WBBaseResponse *)response; @end #pragma mark - DataTransferObject and Base Request/Response /** 微博客户端程序和第三方应用之间传输数据信息的基类 */ @interface WBDataTransferObject : NSObject /** 自定义信息字典,用于数据传输过程中存储相关的上下文环境数据 第三方应用给微博客户端程序发送 request 时,可以在 userInfo 中存储请求相关的信息。 @warning userInfo中的数据必须是实现了 `NSCoding` 协议的对象,必须保证能序列化和反序列化 @warning 序列化后的数据不能大于10M */ @property (nonatomic, strong) NSDictionary *userInfo; /** 发送该数据对象的SDK版本号 如果数据对象是自己生成的,则sdkVersion为当前SDK的版本号;如果是接收到的数据对象,则sdkVersion为数据发送方SDK版本号 */ @property (strong, nonatomic, readonly) NSString *sdkVersion; /** 当用户没有安装微博客户端程序时是否提示用户打开微博安装页面 如果设置为YES,当用户未安装微博时会弹出Alert询问用户是否要打开微博App的安装页面。默认为YES */ @property (nonatomic, assign) BOOL shouldOpenWeiboAppInstallPageIfNotInstalled; @end /** 微博SDK所有请求类的基类 */ @interface WBBaseRequest : WBDataTransferObject /** 返回一个 WBBaseRequest 对象 @return 返回一个*自动释放的*WBBaseRequest对象 */ + (id)request; @end /** 微博SDK所有响应类的基类 */ @interface WBBaseResponse : WBDataTransferObject /** 对应的 request 中的自定义信息字典 如果当前 response 是由微博客户端响应给第三方应用的,则 requestUserInfo 中会包含原 request.userInfo 中的所有数据 @see WBBaseRequest.userInfo */ @property (strong, nonatomic, readonly) NSDictionary *requestUserInfo; /** 响应状态码 第三方应用可以通过statusCode判断请求的处理结果 */ @property (nonatomic, assign) WeiboSDKResponseStatusCode statusCode; /** 返回一个 WBBaseResponse 对象 @return 返回一个*自动释放的*WBBaseResponse对象 */ + (id)response; @end #pragma mark - Authorize Request/Response /** 第三方应用向微博客户端请求认证的消息结构 第三方应用向微博客户端申请认证时,需要调用 [WeiboSDK sendRequest:] 函数, 向微博客户端发送一个 WBAuthorizeRequest 的消息结构。 微博客户端处理完后会向第三方应用发送一个结构为 WBAuthorizeResponse 的处理结果。 */ @interface WBAuthorizeRequest : WBBaseRequest /** 微博开放平台第三方应用授权回调页地址,默认为`http://` 参考 http://open.weibo.com/wiki/%E6%8E%88%E6%9D%83%E6%9C%BA%E5%88%B6%E8%AF%B4%E6%98%8E#.E5.AE.A2.E6.88.B7.E7.AB.AF.E9.BB.98.E8.AE.A4.E5.9B.9E.E8.B0.83.E9.A1.B5 @warning 必须保证和在微博开放平台应用管理界面配置的“授权回调页”地址一致,如未进行配置则默认为`http://` @warning 不能为空,长度小于1K */ @property (nonatomic, strong) NSString *redirectURI; /** 微博开放平台第三方应用scope,多个scrope用逗号分隔 参考 http://open.weibo.com/wiki/%E6%8E%88%E6%9D%83%E6%9C%BA%E5%88%B6%E8%AF%B4%E6%98%8E#scope @warning 长度小于1K */ @property (nonatomic, strong) NSString *scope; /** 当用户没有安装微博客户端或微博客户端过低无法支持SSO的时候是否弹出SDK自带的Webview进行授权 如果设置为YES,当用户没有安装微博客户端或微博客户端过低无法支持SSO的时候会自动弹出SDK自带的Webview进行授权。 如果设置为NO,会根据 shouldOpenWeiboAppInstallPageIfNotInstalled 属性判断是否弹出安装/更新微博的对话框 默认为YES */ @property (nonatomic, assign) BOOL shouldShowWebViewForAuthIfCannotSSO; @end /** 微博客户端处理完第三方应用的认证申请后向第三方应用回送的处理结果 WBAuthorizeResponse 结构中仅包含常用的 userID 、accessToken 和 expirationDate 信息,其他的认证信息(比如部分应用可以获取的 refresh_token 信息)会统一存放到 userInfo 中 */ @interface WBAuthorizeResponse : WBBaseResponse /** 用户ID */ @property (nonatomic, strong) NSString *userID; /** 认证口令 */ @property (nonatomic, strong) NSString *accessToken; /** 认证过期时间 */ @property (nonatomic, strong) NSDate *expirationDate; /** 当认证口令过期时用于换取认证口令的更新口令 */ @property (nonatomic, strong) NSString *refreshToken; @end #pragma mark - ProvideMessageForWeibo Request/Response /** 微博客户端向第三方程序请求提供内容的消息结构 */ @interface WBProvideMessageForWeiboRequest : WBBaseRequest @end /** 微博客户端向第三方应用请求提供内容,第三方应用向微博客户端返回的消息结构 */ @interface WBProvideMessageForWeiboResponse : WBBaseResponse /** 提供给微博客户端的消息 */ @property (nonatomic, strong) WBMessageObject *message; /** 返回一个 WBProvideMessageForWeiboResponse 对象 @param message 需要回送给微博客户端程序的消息对象 @return 返回一个*自动释放的*WBProvideMessageForWeiboResponse对象 */ + (id)responseWithMessage:(WBMessageObject *)message; @end #pragma mark - SendMessageToWeibo Request/Response /** 第三方应用发送消息至微博客户端程序的消息结构体 */ @interface WBSendMessageToWeiboRequest : WBBaseRequest /** 发送给微博客户端的消息 */ @property (nonatomic, strong) WBMessageObject *message; /** 返回一个 WBSendMessageToWeiboRequest 对象 此方法生成对象被[WeiboSDK sendRequest:]会唤起微博客户端的发布器进行分享,如果未安装微博客户端或客户端版本太低 会根据 shouldOpenWeiboAppInstallPageIfNotInstalled 属性判断是否弹出安装/更新微博的对话框 @param message 需要发送给微博客户端的消息对象 @return 返回一个*自动释放的*WBSendMessageToWeiboRequest对象 */ + (id)requestWithMessage:(WBMessageObject *)message; /** 返回一个 WBSendMessageToWeiboRequest 对象 当用户安装了可以支持微博客户端內分享的微博客户端时,会自动唤起微博并分享 当用户没有安装微博客户端或微博客户端过低无法支持通过客户端內分享的时候会自动唤起SDK內微博发布器 @param message 需要发送给微博的消息对象 @param authRequest 授权相关信息,与access_token二者至少有一个不为空,当access_token为空并且需要弹出SDK內发布器时会通过此信息先进行授权后再分享 @param access_token 第三方应用之前申请的Token,当此值不为空并且无法通过客户端分享的时候,会使用此token进行分享。 @return 返回一个*自动释放的*WBSendMessageToWeiboRequest对象 */ + (id)requestWithMessage:(WBMessageObject *)message authInfo:(WBAuthorizeRequest *)authRequest access_token:(NSString *)access_token; @end /** WBSendMessageToWeiboResponse */ @interface WBSendMessageToWeiboResponse : WBBaseResponse /** 可能在分享过程中用户进行了授权操作,当此值不为空时,为用户相应授权信息 */ @property (nonatomic,strong) WBAuthorizeResponse *authResponse; @end #pragma mark - MessageObject / ImageObject /** 微博客户端程序和第三方应用之间传递的消息结构 一个消息结构由三部分组成:文字、图片和多媒体数据。三部分内容中至少有一项不为空,图片和多媒体数据不能共存。(新版的多图和视频属于图片数据,并且图片和视频也不能共存) */ @interface WBMessageObject : NSObject /** 消息的文本内容 @warning 长度小于2000个汉字 */ @property (nonatomic, strong) NSString *text; /** 消息的图片内容 @see WBImageObject */ @property (nonatomic, strong) WBImageObject *imageObject; /** 消息的多媒体内容 @see WBBaseMediaObject */ @property (nonatomic, strong) WBBaseMediaObject *mediaObject; /** 消息的视频内容 @see WBVideoObject */ @property (nonatomic, strong) WBNewVideoObject *videoObject; /** 返回一个 WBMessageObject 对象 @return 返回一个*自动释放的*WBMessageObject对象 */ + (id)message; @end /** 图片视频分享时错误枚举 */ typedef NS_ENUM(NSInteger, WBSDKMediaTransferErrorCode) { WBSDKMediaTransferAlbumPermissionError = 0,//相册权限 WBSDKMediaTransferAlbumWriteError = 0,//相册写入错误 WBSDKMediaTransferAlbumAssetTypeError = 0,//资源类型错误 }; /** 图片视频分享协议 */ @protocol WBMediaTransferProtocol /** 数据准备成功回调 */ -(void)wbsdk_TransferDidReceiveObject:(id)object; /** 数据准备失败回调 */ -(void)wbsdk_TransferDidFailWithErrorCode:(WBSDKMediaTransferErrorCode)errorCode andError:(NSError*)error; @end /** 消息中包含的图片数据对象 */ @interface WBImageObject : NSObject /** 图片真实数据内容 @warning 大小不能超过10M */ @property (nonatomic, strong) NSData *imageData; /** 是否分享到story */ @property (nonatomic) BOOL isShareToStory; /** 返回一个 WBImageObject 对象 @return 返回一个*自动释放的*WBImageObject对象 */ + (id)object; /** 返回一个 UIImage 对象 @return 返回一个*自动释放的*UIImage对象 */ - (UIImage *)image; /** 多图分享委托 */ @property(nonatomic,weak)id delegate; /** 图片对象添加图片数组 */ - (void)addImages:(NSArray*)imageArray; /** 图片对象添加照片数组 */ - (void)addImageAssets:(NSArray*)assetArray; /** 多图最终传递对象 */ -(NSArray*)finalAssetArray; @end @interface WBNewVideoObject : NSObject /** 返回一个 WBNewVideoObject 对象 @return 返回一个*自动释放的*WBNewVideoObject对象 */ + (id)object; /** 是否分享到story */ @property (nonatomic) BOOL isShareToStory; /** 多图分享委托 */ @property(nonatomic,weak)id delegate; /** 视频对象添加视频 */ -(void)addVideo:(NSURL*)videoUrl; /** 视频对象添加视频资源 */ -(void)addVideoAsset:(PHAsset*)videoAsset; /** 视频最终传递对象 */ -(NSString*)finalAsset; @end #pragma mark - Message Media Objects /** 消息中包含的多媒体数据对象基类,该类后期会被废弃,在发布器不再显示为linkcard样式,只显示为普通网络连接 */ @interface WBBaseMediaObject : NSObject /** 对象唯一ID,用于唯一标识一个多媒体内容 当第三方应用分享多媒体内容到微博时,应该将此参数设置为被分享的内容在自己的系统中的唯一标识 @warning 不能为空,长度小于255 */ @property (nonatomic, strong) NSString *objectID; /** 多媒体内容标题 @warning 不能为空且长度小于1k */ @property (nonatomic, strong) NSString *title; /** 多媒体内容描述 @warning 长度小于1k */ @property (nonatomic, strong) NSString *description; /** 多媒体内容缩略图 @warning 大小小于32k */ @property (nonatomic, strong) NSData *thumbnailData; /** 点击多媒体内容之后呼起第三方应用特定页面的scheme @warning 长度小于255 */ @property (nonatomic, strong) NSString *scheme; /** 返回一个 WBBaseMediaObject 对象 @return 返回一个*自动释放的*WBBaseMediaObject对象 */ + (id)object; @end #pragma mark - Message WebPage Objects /** 消息中包含的网页数据对象 */ @interface WBWebpageObject : WBBaseMediaObject /** 网页的url地址 @warning 不能为空且长度不能超过255 */ @property (nonatomic, strong) NSString *webpageUrl; @end ================================================ FILE: zhuishushenqi/NewVersion/ZSThirdPartSDK/ZSThirdPartSDK/Classes/libWeiboSDK/libWeiboSDK.a ================================================ [File too large to display: 12.7 MB] ================================================ FILE: zhuishushenqi/NewVersion/ZSThirdPartSDK/ZSThirdPartSDK.podspec ================================================ # # Be sure to run `pod spec lint ZSThirdPartSDK.podspec' to ensure this is a # valid spec and to remove all comments including this before submitting the spec. # # To learn more about Podspec attributes see https://docs.cocoapods.org/specification.html # To see working Podspecs in the CocoaPods repo see https://github.com/CocoaPods/Specs/ # Pod::Spec.new do |spec| # ――― Spec Metadata ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # # # These will help people to find your library, and whilst it # can feel like a chore to fill in it's definitely to your advantage. The # summary should be tweet-length, and the description more in depth. # spec.name = "ZSThirdPartSDK" spec.version = "0.0.1" spec.summary = "ZSThirdPartSDK." # This description is used to generate tags and improve search results. # * Think: What does it do? Why did you write it? What is the focus? # * Try to keep it short, snappy and to the point. # * Write the description between the DESC delimiters below. # * Finally, don't worry about the indent, CocoaPods strips it! spec.description = <<-DESC third part login sdk. DESC spec.homepage = "https://github.com/norycao/ZSThirdPartSDK" # spec.screenshots = "www.example.com/screenshots_1.gif", "www.example.com/screenshots_2.gif" # ――― Spec License ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # # # Licensing your code is important. See https://choosealicense.com for more info. # CocoaPods will detect a license file if there is a named LICENSE* # Popular ones are 'MIT', 'BSD' and 'Apache License, Version 2.0'. # spec.license = "MIT" # spec.license = { :type => "MIT", :file => "LICENSE" } # ――― Author Metadata ――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # # # Specify the authors of the library, with email addresses. Email addresses # of the authors are extracted from the SCM log. E.g. $ git log. CocoaPods also # accepts just a name if you'd rather not provide an email address. # # Specify a social_media_url where others can refer to, for example a twitter # profile URL. # spec.author = { "norycao" => "2252055382@qq.com" } # Or just: spec.author = "norycao" # spec.authors = { "norycao" => "2252055382@qq.com" } # spec.social_media_url = "https://twitter.com/norycao" # ――― Platform Specifics ――――――――――――――――――――――――――――――――――――――――――――――――――――――― # # # If this Pod runs only on iOS or OS X, then specify the platform and # the deployment target. You can optionally include the target after the platform. # # spec.platform = :ios # spec.platform = :ios, "5.0" # When using multiple platforms spec.ios.deployment_target = "9.0" # spec.osx.deployment_target = "10.7" # spec.watchos.deployment_target = "2.0" # spec.tvos.deployment_target = "9.0" # ――― Source Location ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # # # Specify the location from where the source should be retrieved. # Supports git, hg, bzr, svn and HTTP. # spec.source = { :git => ".", :tag => "#{spec.version}" } # ――― Source Code ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # # # CocoaPods is smart about how it includes source code. For source files # giving a folder will include any swift, h, m, mm, c & cpp files. # For header files it will include any header in the folder. # Not including the public_header_files will make all headers public. # spec.source_files = "ZSThirdPartSDK", "ZSThirdPartSDK/**/*" # spec.exclude_files = "ZSThirdPartSDK/Exclude" # spec.public_header_files = "Classes/**/*.h" # ――― Resources ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # # # A list of resources included with the Pod. These are copied into the # target bundle with a build phase script. Anything else will be cleaned. # You can preserve files from being cleaned, please don't preserve # non-essential files like tests, examples and documentation. # spec.resource_bundles = { 'ZSThirdPartSDK' => ['ZSThirdPartSDK/Assets/*.png'] } # spec.resource = "icon.png" # spec.resources = "Resources/*.png" # spec.preserve_paths = "FilesToSave", "MoreFilesToSave" # ――― Project Linking ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # # # Link your library with frameworks, or libraries. Libraries do not include # the lib prefix of their name. # # spec.framework = "SomeFramework" # spec.frameworks = "SomeFramework", "AnotherFramework" # spec.library = "iconv" # spec.libraries = "iconv", "xml2" spec.vendored_frameworks = 'ZSThirdPartSDK/Classes/*.framework' spec.vendored_libraries = 'ZSThirdPartSDK/Classes/**/*.a' spec.frameworks = 'UIKit', 'SystemConfiguration', 'Security', 'CoreTelephony', 'CFNetwork', 'CoreGraphics', 'Photos' spec.libraries = 'z', 'sqlite3.0', 'c++' # ――― Project Settings ――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # # # If your library depends on compiler flags you can set them in the xcconfig hash # where they will only apply to your library. If you depend on other Podspecs # you can include multiple dependencies to ensure it works. # spec.requires_arc = true spec.swift_version = '5.0' # spec.xcconfig = { "HEADER_SEARCH_PATHS" => "$(SDKROOT)/usr/include/libxml2" } # spec.dependency "JSONKit", "~> 1.4" spec.dependency 'HandyJSON' spec.dependency 'ZSAPI' spec.dependency 'ZSAppConfig' spec.dependency 'ZSExtension' end ================================================ FILE: zhuishushenqi/RightSide/Category/Controllers/Category/ZSCatelogViewController.swift ================================================ // // CategoryViewController.swift // zhuishushenqi // // Created by Nory Cao on 2017/3/10. // Copyright © 2017年 QS. All rights reserved. // import UIKit class ZSCatelogViewController:BaseViewController { var collectionView:UICollectionView! var viewModel:ZSCatelogViewModel = ZSCatelogViewModel() override func viewDidLoad() { super.viewDidLoad() title = "分类" configureCollectionView() request() } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) navigationController?.isNavigationBarHidden = false self.collectionView.frame = self.view.bounds } fileprivate func request() { viewModel.request { (_) in self.collectionView.reloadData() } } fileprivate func configureCollectionView() { let layout = UICollectionViewFlowLayout() layout.minimumLineSpacing = 0.01 layout.minimumInteritemSpacing = 0.01 layout.scrollDirection = .vertical collectionView = UICollectionView(frame: CGRect.zero, collectionViewLayout: layout) collectionView.dataSource = self collectionView.delegate = self // collectionView.isPagingEnabled = true collectionView.register(ZSCatelogCell.self, forCellWithReuseIdentifier: "ZSCatelogCell") collectionView.register(ZSCatelogHeaderView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "ZSCatelogHeaderView") collectionView.showsHorizontalScrollIndicator = false collectionView.backgroundColor = UIColor.white view.addSubview(collectionView) } } extension ZSCatelogViewController:UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout { func numberOfSections(in collectionView: UICollectionView) -> Int { return viewModel.catelogModel.sections() } func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { let items = viewModel.catelogModel.items(at: section) return items.count } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ZSCatelogCell", for: indexPath) as? ZSCatelogCell let items = viewModel.catelogModel.items(at: indexPath.section) cell?.updateCell(items[indexPath.row]) return cell! } func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { return CGSize(width: self.view.bounds.width/2 - 10, height: 100) } func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize { return CGSize(width: self.collectionView.bounds.width, height: 40) } func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView { let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "ZSCatelogHeaderView", for: indexPath) as! ZSCatelogHeaderView let name = viewModel.catelogModel.name(for: indexPath.section) headerView.titleLabel.text = name return headerView } func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { let catalogDetailVC = ZSCatelogDetailViewController() let item = viewModel.catelogModel.items(at: indexPath.section)[indexPath.row] let gender = viewModel.catelogModel.gender(for: indexPath.section) catalogDetailVC.parameterModel = ZSCatelogParameterModel(major: item.name, gender: gender) self.navigationController?.pushViewController(catalogDetailVC, animated: true) } } //http://api.zhuishushenqi.com/cats/lv2/statistics ================================================ FILE: zhuishushenqi/RightSide/Category/Controllers/CategoryDetail/ZSBaseSegmentItemViewController.swift ================================================ // // QSRankProtocols.swift // zhuishushenqi // // Created by Nory Cao on 2017/4/13. // Copyright © 2017年 QS. All rights reserved. // class ZSBaseSegmentItemViewController: BaseViewController, UITableViewDataSource, UITableViewDelegate { //标记控制器在父控制器中的序号 var index:Int = 0 var tableView:UITableView! var clickRow:ZSBaseCallback? private let _viewModel = ZSSegmentBaseViewModel() var viewModel:ZSSegmentBaseViewModel { return _viewModel } override func viewDidLoad() { super.viewDidLoad() setupSubviews() request() } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) tableView.frame = view.bounds } func setupSubviews(){ tableView = UITableView(frame: CGRect.zero, style: .grouped) tableView.dataSource = self tableView.delegate = self tableView.sectionHeaderHeight = CGFloat.leastNormalMagnitude tableView.sectionFooterHeight = CGFloat.leastNormalMagnitude tableView.rowHeight = 93 tableView.qs_registerCellNib(TopDetailCell.self) view.addSubview(tableView) self.tableView.isAutoControlErrorView = true } func request() { } func numberOfSections(in tableView: UITableView) -> Int { return 1 } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return viewModel.books.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell:TopDetailCell? = tableView.qs_dequeueReusableCell(TopDetailCell.self) cell?.backgroundColor = UIColor.white cell?.selectionStyle = .none cell!.model = viewModel.books[indexPath.row] return cell! } func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { return 0.01 } func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { return 0.01 } func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { return UIView() } func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? { return UIView() } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { clickRow?(viewModel.books[indexPath.row]) } } ================================================ FILE: zhuishushenqi/RightSide/Category/Controllers/CategoryDetail/ZSCategoryDetailViewController.swift ================================================ // // QSCategoryDetailViewController.swift // zhuishushenqi // // Created yung on 2017/4/20. // Copyright © 2017年 QS. All rights reserved. // // Template generated by Juanpe Catalán @JuanpeCMiOS // import UIKit class ZSCatelogDetailViewController:BaseViewController { var segmentViewController = ZSSegmenuViewController() var parameterModel:ZSCatelogParameterModel? override func viewDidLoad() { super.viewDidLoad() setupSubviews() } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) segmentViewController.view.frame = view.bounds } func setupSubviews(){ title = parameterModel?.major ?? "" segmentViewController.delegate = self addChild(segmentViewController) didMove(toParent: self) view.addSubview(segmentViewController.view) } } extension ZSCatelogDetailViewController:ZSSegmenuProtocol { func viewControllersForSegmenu(_ segmenu: ZSSegmenuViewController) -> [UIViewController] { var viewControllers:[UIViewController] = [] let titles = ["新书","热度","口碑","完结"] let types = ["new","hot","reputation","over"] for i in 0.. String { return "/user/BOOKSHELF" } override func requestMethod() -> XYCRequestMethod? { return XYCRequestMethod.post } override func requestArgument() -> NSDictionary? { return ["token":"iGbZwXqxxsf1A8duVXyyReLq","books":id ?? ""] } } ================================================ FILE: zhuishushenqi/RightSide/Category/Models/AllChapterAPI.swift ================================================ // // AllChapterAPI.swift // zhuishushenqi // // Created by Nory Cao on 16/10/11. // Copyright © 2016年 QS. All rights reserved. // import UIKit class AllChapterAPI: XYCBaseRequest { var id:NSString = "" override func requestUrl() -> String { return "/ctoc/\(id)" } override func requestMethod() -> XYCRequestMethod? { return XYCRequestMethod.get } override func requestArgument() -> NSDictionary? { return ["view":"chapters"] } } ================================================ FILE: zhuishushenqi/RightSide/Category/Models/BookDetailAPI.swift ================================================ // // BookDetailAPI.swift // zhuishushenqi // // Created by Nory Cao on 16/10/4. // Copyright © 2016年 QS. All rights reserved. // import UIKit class BookDetailAPI: XYCBaseRequest { var id:NSString = "" override func requestUrl() -> String { return "/book/\(id)" } override func requestMethod() -> XYCRequestMethod? { return XYCRequestMethod.get } override func requestArgument() -> NSDictionary? { return ["":""] } } ================================================ FILE: zhuishushenqi/RightSide/Category/Models/BookShelfAPI.swift ================================================ // // BookShelfAPI.swift // zhuishushenqi // // Created by Nory Cao on 16/10/5. // Copyright © 2016年 QS. All rights reserved. // import UIKit class BookShelfAPI: XYCBaseRequest { override func requestUrl() -> String { return "/user/BOOKSHELF" } override func requestMethod() -> XYCRequestMethod? { return XYCRequestMethod.get } override func requestArgument() -> NSDictionary? { return ["books":"","token":"pv4GJDGNlK6xomuTblrFSM5q"] } } ================================================ FILE: zhuishushenqi/RightSide/Category/Models/CategoryModel.swift ================================================ // // CategoryModel.swift // zhuishushenqi // // Created by Nory Cao on 2017/3/10. // Copyright © 2017年 QS. All rights reserved. // import UIKit @objc(CategoryModel) class CategoryModel: NSObject { var _id:String = "" var title:String = "" var author:String = "" var shortIntro:String = "" var cover:String = "" var site:String = "" var majorCate:String = "" var latelyFollower:Int = 0 var latelyFollowerBase:Int = 0 var minRetentionRatio:CGFloat = 0 var retentionRatio:CGFloat = 0 var lastChapter:String = "" var tags:String = "" class func modelCustomPropertyMapper() ->NSDictionary{ return ["_id":"_id","shortIntro":"shortIntro","title":"title","author":"author","cover":"cover","site":"site","majorCate":"majorCate","latelyFollower":"latelyFollower","latelyFollowerBase":"latelyFollowerBase","minRetentionRatio":"minRetentionRatio","retentionRatio":"retentionRatio","lastChapter":"lastChapter","tags":"tags"] } } ================================================ FILE: zhuishushenqi/RightSide/Category/Models/DynamicAPI.swift ================================================ // // DynamicAPI.swift // zhuishushenqi // // Created by Nory Cao on 16/10/22. // Copyright © 2016年 QS. All rights reserved. // import UIKit class DynamicAPI: XYCBaseRequest { var id:NSString = "57ac9879c12b61e826bd7221" override func requestUrl() -> String { return "/user/twitter/timeline/\(id)" } override func requestMethod() -> XYCRequestMethod? { return XYCRequestMethod.get } override func requestArgument() -> NSDictionary? { return ["token":"EsIH3Jn6Pn4md8Pe7uEc8GFA"] } } ================================================ FILE: zhuishushenqi/RightSide/Category/Models/LatestChapterAPI.swift ================================================ // // LatestChapterAPI.swift // zhuishushenqi // // Created by Nory Cao on 16/10/11. // Copyright © 2016年 QS. All rights reserved. // import UIKit class LatestChapterAPI: XYCBaseRequest { var id:NSString = "" override func requestUrl() -> String { return "/btoc" } override func requestMethod() -> XYCRequestMethod? { return XYCRequestMethod.get } override func requestArgument() -> NSDictionary? { return ["view":"summary","book":id] } } ================================================ FILE: zhuishushenqi/RightSide/Category/Models/RankingAPI.swift ================================================ // // RankingAPI.swift // zhuishushenqi // // Created by Nory Cao on 16/10/1. // Copyright © 2016年 QS. All rights reserved. // import UIKit class RankingAPI: XYCBaseRequest{ override func requestUrl() -> String { return "/ranking/gender" } override func requestMethod() -> XYCRequestMethod? { return XYCRequestMethod.get } override func requestArgument() -> NSDictionary? { return ["":""] } } ================================================ FILE: zhuishushenqi/RightSide/Category/Models/RankingDetailAPI.swift ================================================ // // RankingDetailAPI.swift // zhuishushenqi // // Created by Nory Cao on 16/10/3. // Copyright © 2016年 QS. All rights reserved. // import UIKit class RankingDetailAPI: XYCBaseRequest { var id:NSString = "" override func requestUrl() -> String { return "/ranking/\(id)" } override func requestMethod() -> XYCRequestMethod? { return XYCRequestMethod.get } override func requestArgument() -> NSDictionary? { return ["": ""] } } ================================================ FILE: zhuishushenqi/RightSide/Category/Models/ShelfMessageAPI.swift ================================================ // // ShelfMessageAPI.swift // zhuishushenqi // // Created by Nory Cao on 16/9/17. // Copyright © 2016年 QS. All rights reserved. // import UIKit class ShelfMessageAPI: XYCBaseRequest { override func requestUrl() -> String { return "/notification/shelfMessage" } override func requestMethod() -> XYCRequestMethod? { return XYCRequestMethod.get } override func requestArgument() -> NSDictionary? { return ["platform":"ios"] } } ================================================ FILE: zhuishushenqi/RightSide/Category/Models/ZSCatelogHeaderView.swift ================================================ // // ZSCatelogHeaderView.swift // zhuishushenqi // // Created by caony on 2019/3/21. // Copyright © 2019年 QS. All rights reserved. // import Foundation class ZSCatelogHeaderView: UICollectionReusableView { var titleLabel:UILabel! override init(frame: CGRect) { super.init(frame: frame) titleLabel = UILabel(frame: CGRect.zero) titleLabel.textColor = UIColor.black titleLabel.font = UIFont.systemFont(ofSize: 15) addSubview(titleLabel) } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func layoutSubviews() { super.layoutSubviews() self.titleLabel.frame = CGRect(x: 20, y: self.bounds.height/2 - 10, width: 100, height: 20) } } ================================================ FILE: zhuishushenqi/RightSide/Category/Models/ZSCatelogModel.swift ================================================ // // ZSCatelogModel.swift // zhuishushenqi // // Created by caony on 2019/3/18. // Copyright © 2019年 QS. All rights reserved. // import Foundation import HandyJSON struct ZSCatelogItem:HandyJSON { var name:String var bookCount:Int var monthlyCount:Int var icon:String var bookCover:[String] init() { self.name = "" self.bookCount = 0 self.monthlyCount = 0 self.icon = "" self.bookCover = [] } } struct ZSCatelogModel:HandyJSON { var female:[ZSCatelogItem] var male:[ZSCatelogItem] var picture:[ZSCatelogItem] var press:[ZSCatelogItem] var code:Bool init() { self.female = [] self.male = [] self.picture = [] self.press = [] self.code = false } func sections() ->Int { var count = 0 if self.female.count > 0 { count += 1 } if self.male.count > 0 { count += 1 } if self.picture.count > 0 { count += 1 } if self.press.count > 0 { count += 1 } return count } func items(at index:Int) ->[ZSCatelogItem] { var items:[ZSCatelogItem] = [] switch index { case 0: items = self.male break case 1: items = self.female break case 2: items = self.picture break case 3: items = self.press default: items = [] } return items } func gender(for section:Int) ->String { var gender:String = "" switch section { case 0: gender = "male" break case 1: gender = "female" break case 2: gender = "picture" break case 3: gender = "press" break default: gender = "" } return gender } func name(for section:Int) ->String { var name:String = "" switch section { case 0: name = "男生" break case 1: name = "女生" break case 2: name = "漫画" break case 3: name = "出版" break default: name = "" } return name } } ================================================ FILE: zhuishushenqi/RightSide/Category/Models/ZSCatelogParameterModel.swift ================================================ // // QSRankDetailProtoocol.swift // zhuishushenqi // // Created by Nory Cao on 2017/4/13. // Copyright © 2017年 QS. All rights reserved. // struct ZSCatelogParameterModel { var major:String = "" var gender:String = "" init(major:String, gender:String) { self.major = major self.gender = gender } } ================================================ FILE: zhuishushenqi/RightSide/Category/ViewModel/ZSCatelogDetailViewModel.swift ================================================ // // QSRankRouter.swift // zhuishushenqi // // Created by Nory Cao on 2017/4/13. // Copyright © 2017年 QS. All rights reserved. // import ZSAPI class ZSCatelogDetailViewModel:ZSSegmentBaseViewModel { var parameterModel:ZSCatelogParameterModel? var type:String = "" var title:String = "" var segmentIndex:Int = 0 private var startIndex:Int = 0 private var limit = 50 override init() { } func request(_ handler:ZSBaseCallback?) { startIndex = 0 limit = 50 let major = parameterModel?.major ?? "" let gender = parameterModel?.gender ?? "" let api = ZSAPI.categoryList(gender: gender, type: type, major: major, minor: "", start: "\(startIndex)", limit: "\(limit)") zs_get(api.path, parameters: api.parameters) { [weak self](response) in guard let sSelf = self else { return } guard let books = response?["books"] as? [Any] else { handler?(nil) return } if let models = [Book].deserialize(from: books) as? [Book] { sSelf.books = models } handler?(nil) } } func requestMore(_ handler:ZSBaseCallback?) { if startIndex < books.count { startIndex += limit } else { handler?(nil) return } let major = parameterModel?.major ?? "" let gender = parameterModel?.gender ?? "" let api = ZSAPI.categoryList(gender: gender, type: type, major: major, minor: "", start: "\(startIndex)", limit: "\(limit)") zs_get(api.path, parameters: api.parameters) { [weak self](response) in guard let sSelf = self else { return } guard let books = response?["books"] as? [Any] else { handler?(nil) return } if let models = [Book].deserialize(from: books) as? [Book] { sSelf.books.append(contentsOf: models) } handler?(nil) } } } ================================================ FILE: zhuishushenqi/RightSide/Category/ViewModel/ZSCatelogViewModel.swift ================================================ // // ZSCatelogDetailViewModel.swift // zhuishushenqi // // Created by caony on 2019/3/18. // Copyright © 2019年 QS. All rights reserved. // import Foundation import ZSAPI class ZSCatelogViewModel { var catelogModel:ZSCatelogModel = ZSCatelogModel() func request(_ handler:ZSBaseCallback?) { let api = ZSAPI.category("" as AnyObject) zs_get(api.path, parameters: api.parameters) { (response) in guard let books = response else { handler?(nil) return } if let catelogModel = ZSCatelogModel.deserialize(from: books) { self.catelogModel = catelogModel } handler?(nil) } } } ================================================ FILE: zhuishushenqi/RightSide/Category/ViewModel/ZSSegmentBaseViewModel.swift ================================================ // // ZSSegmentBaseViewModel.swift // zhuishushenqi // // Created by caony on 2019/3/18. // Copyright © 2019年 QS. All rights reserved. // import Foundation protocol ZSSegmentBaseViewProtocol { var books:[Book] { get set } } class ZSSegmentBaseViewModel { var books:[Book] = [] } ================================================ FILE: zhuishushenqi/RightSide/Category/Views/ZSCatelogCell.swift ================================================ // // ZSCatelogCell.swift // zhuishushenqi // // Created by caony on 2019/3/19. // Copyright © 2019年 QS. All rights reserved. // import Foundation class ZSCatelogCell: UICollectionViewCell { var titleLabel:UILabel! var descLabel:UILabel! var leftImageView:UIImageView! var rightImageView:UIImageView! var centerImageView:UIImageView! override init(frame: CGRect) { super.init(frame: frame) setupSubview() } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func layoutSubviews() { super.layoutSubviews() titleLabel.frame = CGRect(x: 0, y: self.bounds.size.height/2 - 35/2, width: self.bounds.size.width/2, height: 15) descLabel.frame = CGRect(x: 0, y: titleLabel.frame.maxY + 5, width: self.bounds.size.width/2, height: 15) leftImageView.frame = CGRect(x: titleLabel.frame.maxX + 20, y: self.bounds.height - 80, width: 44 , height: 60) centerImageView.frame = CGRect(x: leftImageView.frame.minX + 10, y: leftImageView.frame.minY - 10, width: 50, height: 70) rightImageView.frame = CGRect(x: centerImageView.frame.minX + 20, y: centerImageView.frame.minY + 20, width: 40, height: 50) } func updateCell(_ model:ZSCatelogItem) { titleLabel.text = model.name descLabel.text = "\(model.bookCount)" leftImageView.qs_setBookCoverWithURLString(urlString: model.bookCover[safe: 0] ?? "") centerImageView.qs_setBookCoverWithURLString(urlString: model.bookCover[safe: 1] ?? "") rightImageView.qs_setBookCoverWithURLString(urlString: model.bookCover[safe: 2] ?? "") } private func setupSubview() { titleLabel = UILabel(frame: CGRect.zero) titleLabel.textAlignment = .center titleLabel.textColor = UIColor.gray titleLabel.font = UIFont.systemFont(ofSize: 13) contentView.addSubview(titleLabel) descLabel = UILabel(frame: CGRect.zero) descLabel.textAlignment = .center descLabel.textColor = UIColor.darkGray descLabel.font = UIFont.systemFont(ofSize: 11) contentView.addSubview(descLabel) leftImageView = UIImageView(frame: CGRect.zero) leftImageView.backgroundColor = UIColor.red contentView.addSubview(leftImageView) rightImageView = UIImageView(frame: CGRect.zero) rightImageView.backgroundColor = UIColor.orange contentView.addSubview(rightImageView) centerImageView = UIImageView(frame: CGRect.zero) centerImageView.backgroundColor = UIColor.cyan contentView.addSubview(centerImageView) } } ================================================ FILE: zhuishushenqi/RightSide/Network/QSHotwords.swift ================================================ // // QSHotwords.swift // zhuishushenqi // // Created by Nory Cao on 2017/4/10. // Copyright © 2017年 QS. All rights reserved. // import Foundation struct API { static let base = BASEURL } protocol QSHotWord { var staURL:String { get } var contain:String { get } } enum QSHotwords { enum QSSearchWords:QSHotWord { case fetch(id:String) public var staURL :String { switch self { case .fetch: return "\(API.base)/book/hot-word" } } public var contain: String { switch self { case .fetch(let id): return "/book/hot-word/\(id)" } } } } ================================================ FILE: zhuishushenqi/RightSide/Ranking/Controllers/QSRankViewController.swift ================================================ // // QSRankViewController.swift // zhuishushenqi // // Created by Nory Cao on 2017/4/13. // Copyright © 2017年 QS. All rights reserved. // import UIKit class QSRankViewController: BaseViewController,UITableViewDataSource,UITableViewDelegate { var viewModel = ZSRankViewModel() var tableView:UITableView! var ranks:[[QSRankModel]]? var show:[Bool] = [false,false] let kCellHeaderHeight:CGFloat = 60 override func viewDidLoad() { super.viewDidLoad() self.view.backgroundColor = UIColor.white // Do any additional setup after loading the view. title = "排行榜" initSubview() viewModel.fetchRanking { (_) in self.tableView.reloadData() } } fileprivate func initSubview(){ tableView = UITableView(frame: CGRect(x: 0, y: 64, width: ScreenWidth, height: ScreenHeight - 64), style: .grouped) tableView.dataSource = self tableView.delegate = self tableView.rowHeight = kCellHeaderHeight tableView.sectionFooterHeight = 0.001 tableView.separatorInset = UIEdgeInsets(top: 0, left: kCellHeaderHeight, bottom: 0, right: 0) tableView.qs_registerCellClass(RankingViewCell.self) self.view.addSubview(self.tableView) } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { let models = viewModel.showRanks[section] return models.count } func numberOfSections(in tableView: UITableView) -> Int { return viewModel.showRanks.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell:RankingViewCell? = tableView.qs_dequeueReusableCell(RankingViewCell.self) cell?.imageView?.contentMode = .scaleAspectFill cell?.backgroundColor = UIColor.white cell?.selectionStyle = .none let models = viewModel.showRanks[indexPath.section] cell?.model = models[indexPath.row] cell?.accessoryImageView.isSelected = (indexPath.section == 0 ? viewModel.showMaleRank:viewModel.showFemaleRank) return cell! } func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { let headerView = UIView(frame: CGRect(x: 0,y: 0,width: ScreenWidth,height: 60)) let label = UILabel(frame: CGRect(x: 15,y: 0,width: 100,height: 60)) label.textColor = UIColor.gray label.font = UIFont.systemFont(ofSize: 11) if section == 0 { label.text = "男生" }else if section == 1{ label.text = "女生" } headerView.addSubview(label) return headerView } func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { return kCellHeaderHeight } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { viewModel.clickRow(indexPath: indexPath) { (models) in if let _ = models { self.tableView.reloadData() } else { let detailVC = ZSRankViewController() detailVC.rank = self.viewModel.showRanks[indexPath.section][indexPath.row] detailVC.title = detailVC.rank?.title self.navigationController?.pushViewController(detailVC, animated: true) } } } override var preferredStatusBarStyle : UIStatusBarStyle { return .default } func showRanks(ranks:[[QSRankModel]]){ self.ranks = ranks self.tableView.reloadData() } func showEmpty(){ } func show(show:[Bool]){ self.show = show self.tableView.reloadData() } func collapse(maleRank:[QSRankModel]?)->Int{ var collapseIndex = 0 if let models = maleRank { for model in models { if model.collapse == 1 { break } collapseIndex += 1 } } return collapseIndex } } ================================================ FILE: zhuishushenqi/RightSide/Ranking/Controllers/RankingViewController.swift ================================================ // // RankingViewController.swift // zhuishushenqi // // Created by Nory Chao on 16/9/19. // Copyright © 2016年 QS. All rights reserved. // import UIKit import QSNetwork import QSKingfisher // FIXME: comparison operators with optionals were removed from the Swift Standard Libary. // Consider refactoring the code to use the non-optional operators. fileprivate func < (lhs: T?, rhs: T?) -> Bool { switch (lhs, rhs) { case let (l?, r?): return l < r case (nil, _?): return true default: return false } } // FIXME: comparison operators with optionals were removed from the Swift Standard Libary. // Consider refactoring the code to use the non-optional operators. fileprivate func > (lhs: T?, rhs: T?) -> Bool { switch (lhs, rhs) { case let (l?, r?): return l > r default: return rhs < lhs } } class RankingViewController: BaseViewController,UITableViewDataSource,UITableViewDelegate { fileprivate var tableView:UITableView? fileprivate var maleRank:[QSRankModel]? = [QSRankModel]() fileprivate var femaleRank:[QSRankModel]? = [QSRankModel]() fileprivate var showMale:Bool = false fileprivate var showFemale:Bool = false override func viewDidLoad() { super.viewDidLoad() self.view.backgroundColor = UIColor.white // Do any additional setup after loading the view. title = "排行榜" initSubview() requestData() } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) } fileprivate func initSubview(){ let tableView = UITableView(frame: CGRect(x: 0, y: 64, width: ScreenWidth, height: ScreenHeight - 64), style: .grouped) tableView.dataSource = self tableView.delegate = self tableView.separatorInset = UIEdgeInsetsMake(0, 44, 0, 0) tableView.qs_registerCellClass(RankingViewCell.self) self.tableView = tableView self.tableView?.checkEmpty() } fileprivate func requestData(){ QSNetwork.request("\(BASEURL)/\(RANKING)") { (response) in if let dict = response.json as? NSDictionary { do{ if let male:[Any] = dict["male"] as? [Any] { self.maleRank = try XYCBaseModel.model(withModleClass: QSRankModel.self, withJsArray: male) as? [QSRankModel] //添加别人家的榜单 let otherRank = self.rankModel(title: "别人家的榜单", image: "ranking_other") self.maleRank?.insert(otherRank, at: 5) } if let female:[Any] = dict["female"] as? [Any] { self.femaleRank = try XYCBaseModel.model(withModleClass: QSRankModel.self, withJsArray: female ) as? [QSRankModel] let otherRank = self.rankModel(title: "别人家的榜单", image: "ranking_other") self.femaleRank?.insert(otherRank, at: 5) } DispatchQueue.main.async { self.view.addSubview(self.tableView!) self.tableView?.reloadData() } }catch{ } } } } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { if section == 0 { return (showMale ? maleRank!.count : 6) }else if section == 1{ return (showFemale ? femaleRank!.count : 6) } return 0 } func numberOfSections(in tableView: UITableView) -> Int { return 2 } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell:RankingViewCell? = tableView.qs_dequeueReusableCell(RankingViewCell.self) cell?.imageView?.contentMode = .scaleAspectFill cell?.backgroundColor = UIColor.white cell?.selectionStyle = .none var rank:[QSRankModel]? = [QSRankModel]() indexPath.section == 0 ? (rank = maleRank) : (rank = femaleRank) cell?.model = rank?[indexPath.row] cell?.accessoryClosure = { if indexPath.section == 0 { self.showMale = !self.showMale self.tableView?.reloadData() }else{ self.showFemale = !self.showFemale self.tableView?.reloadData() } } tableView.checkEmpty() return cell! } func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { let headerView = UIView(frame: CGRect(x: 0,y: 0,width: ScreenWidth,height: 60)) let label = UILabel(frame: CGRect(x: 15,y: 15,width: 100,height: 15)) label.textColor = UIColor.gray label.font = UIFont.systemFont(ofSize: 11) if section == 0 { label.text = "男生" }else if section == 1{ label.text = "女生" } headerView.addSubview(label) return headerView } func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { return 40 } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { var rank:[[QSRankModel]] = [maleRank!,femaleRank!] if indexPath.row == 5{ if indexPath.section == 0 { showMale = !showMale }else{ showFemale = !showFemale } let cell:RankingViewCell? = self.tableView?.cellForRow(at: indexPath) as? RankingViewCell cell?.accessoryImageView.isSelected = showMale self.tableView?.reloadData() return } let topVC = TopDetailViewController() topVC.id = rank[indexPath.section][indexPath.row]._id topVC.title = rank[indexPath.section][indexPath.row].title topVC.model = rank[indexPath.section][indexPath.row] self.navigationController?.pushViewController(topVC, animated: true) } override var preferredStatusBarStyle : UIStatusBarStyle { return .default } func rankModel(title:String,image:String)->QSRankModel{ let otherRank = QSRankModel() otherRank.title = title otherRank.image = image return otherRank } } public class QSResource:Resource{ public var imageURL:URL? = URL(string: "http://statics.zhuishushenqi.com/ranking-cover/142319144267827") public var downloadURL: URL { return imageURL! } public var cacheKey: String{ return "\(self.imageURL)" } init(url:URL) { self.imageURL = url } } ================================================ FILE: zhuishushenqi/RightSide/Ranking/Controllers/TopDetailViewController.swift ================================================ // // TopDetailViewController.swift // zhuishushenqi // // Created by Nory Chao on 16/10/4. // Copyright © 2016年 QS. All rights reserved. // import UIKit import QSNetwork // FIXME: comparison operators with optionals were removed from the Swift Standard Libary. // Consider refactoring the code to use the non-optional operators. fileprivate func < (lhs: T?, rhs: T?) -> Bool { switch (lhs, rhs) { case let (l?, r?): return l < r case (nil, _?): return true default: return false } } // FIXME: comparison operators with optionals were removed from the Swift Standard Libary. // Consider refactoring the code to use the non-optional operators. fileprivate func > (lhs: T?, rhs: T?) -> Bool { switch (lhs, rhs) { case let (l?, r?): return l > r default: return rhs < lhs } } class TopDetailViewController: BaseViewController ,SegMenuDelegate,UITableViewDataSource,UITableViewDelegate{ var id:String? = "" var selectedIndex = 0 var model:QSRankModel? var books:NSArray? = [] fileprivate var booksModel:NSArray? = [] fileprivate lazy var tableView:UITableView = { let tableView = UITableView(frame: CGRect(x: 0, y: 104, width: ScreenWidth, height: ScreenHeight - 104), style: .grouped) tableView.dataSource = self tableView.delegate = self tableView.sectionHeaderHeight = CGFloat.leastNormalMagnitude tableView.sectionFooterHeight = CGFloat.leastNormalMagnitude tableView.rowHeight = 93 tableView.qs_registerCellNib(TopDetailCell.self) return tableView }() override func viewDidLoad() { super.viewDidLoad() initSubview() requestDetail(idString: model?._id ?? "") } fileprivate func initSubview(){ let segView = SegMenu(frame: CGRect(x: 0, y: 64, width: UIScreen.main.bounds.size.width, height: 40), WithTitles: ["周榜","月榜","总榜"]) segView.menuDelegate = self view.addSubview(segView) self.tableView.checkEmpty() } fileprivate func requestDetail(idString:String){ let urlString = "\(BASEURL)/ranking/\(idString)" QSNetwork.request(urlString, method: HTTPMethodType.get, parameters: nil, headers: nil) { (response) in QSLog(response.json) if let json = response.json { if let books = (json.object(forKey: "ranking") as AnyObject).object(forKey: "books") { do{ self.booksModel = try XYCBaseModel.model(withModleClass: Book.self, withJsArray:books as! [AnyObject]) as NSArray }catch{ } } } DispatchQueue.main.async { self.tableView.removeFromSuperview() self.view.addSubview(self.tableView) self.tableView.reloadData() self.tableView.checkEmpty() } } } func didSelectAtIndex(_ index:Int){ if selectedIndex == index { return } selectedIndex = index let ids:[String] = [model?._id ?? "1",model?.monthRank ?? "2",model?.totalRank ?? "3"] requestDetail(idString: ids[selectedIndex]) } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return booksModel?.count > 0 ? booksModel!.count:0 } func numberOfSections(in tableView: UITableView) -> Int { return 1 } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell:TopDetailCell? = tableView.qs_dequeueReusableCell(TopDetailCell.self) cell?.backgroundColor = UIColor.white cell?.selectionStyle = .none cell!.model = booksModel?.count > indexPath.row ? booksModel![indexPath.row] as? Book:nil tableView.checkEmpty() return cell! } func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { return tableView.sectionHeaderHeight } func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { return tableView.sectionHeaderHeight } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } } ================================================ FILE: zhuishushenqi/RightSide/Ranking/Controllers/ZSRankViewController.swift ================================================ // // QSRankDetailViewController.swift // zhuishushenqi // // Created by Nory Cao on 2017/4/13. // Copyright © 2017年 QS. All rights reserved. // import UIKit import Kingfisher class ZSRankItemViewController:BaseViewController { //标记控制器在父控制器中的序号 var index:Int = 0 var dataSource:[Book] = [] { didSet { self.tableView.reloadData() } } var tableView:UITableView! var rank:QSRankModel? var viewModel = ZSRankDetailViewModel() var clickRow:ZSBaseCallback? override func viewDidLoad() { super.viewDidLoad() setupSubviews() request() tableView.frame = view.bounds } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) tableView.frame = view.bounds } func setupSubviews(){ tableView = UITableView(frame: CGRect.zero, style: .grouped) tableView.dataSource = self tableView.delegate = self tableView.sectionHeaderHeight = CGFloat.leastNormalMagnitude tableView.sectionFooterHeight = CGFloat.leastNormalMagnitude tableView.rowHeight = 93 tableView.qs_registerCellNib(TopDetailCell.self) view.addSubview(tableView) } func request(){ if let rankInfo = rank { viewModel.fetchRanking(rank: rankInfo, index: index) { (books) in if let models = books { self.dataSource = models self.tableView.reloadData() } } } } } extension ZSRankItemViewController:UITableViewDataSource,UITableViewDelegate { func numberOfSections(in tableView: UITableView) -> Int { return 1 } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return dataSource.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell:TopDetailCell? = tableView.qs_dequeueReusableCell(TopDetailCell.self) cell?.backgroundColor = UIColor.white cell?.selectionStyle = .none cell!.model = dataSource[indexPath.row] return cell! } func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { return 0.01 } func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { return 0.01 } func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { return UIView() } func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? { return UIView() } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { clickRow?(dataSource[indexPath.row]) } } class ZSRankViewController:BaseViewController { var rank:QSRankModel? var segmentViewController = ZSSegmenuViewController() override func viewDidLoad() { super.viewDidLoad() setupSubviews() } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) segmentViewController.view.frame = view.bounds } func setupSubviews(){ segmentViewController.delegate = self view.addSubview(segmentViewController.view) addChild(segmentViewController) } } extension ZSRankViewController:ZSSegmenuProtocol { func viewControllersForSegmenu(_ segmenu: ZSSegmenuViewController) -> [UIViewController] { var viewControllers:[UIViewController] = [] var titles = ["周榜","月榜","总榜"] for i in 0..<3 { let viewController = ZSRankItemViewController() viewController.index = i viewController.rank = rank viewController.title = titles[i] viewController.clickRow = { (book) in self.navigationController?.pushViewController(QSBookDetailRouter.createModule(id: book?._id ?? ""), animated: true) } viewControllers.append(viewController) } return viewControllers } func segmenu(_ segmenu: ZSSegmenuViewController, didSelectSegAt index: Int) { } func segmenu(_ segmenu: ZSSegmenuViewController, didScrollToSegAt index: Int) { } } public class QSResource:Resource{ public var imageURL:URL? = URL(string: "http://statics.zhuishushenqi.com/ranking-cover/142319144267827") public var downloadURL: URL { return imageURL! } public var cacheKey: String{ return "\(String(describing: self.imageURL))" } init(url:URL) { self.imageURL = url } } public class ZSPlaceHolder:Placeholder { public func add(to imageView: ImageView) { } public func remove(from imageView: ImageView) { } } ================================================ FILE: zhuishushenqi/RightSide/Ranking/Models/QSRankModel.swift ================================================ // // QSRankModel.swift // zhuishushenqi // // Created by Nory Cao on 2017/3/7. // Copyright © 2017年 QS. All rights reserved. // import UIKit import HandyJSON @objc(QSRankModel) class QSRankModel: NSObject,HandyJSON { var totalRank:String = "" { didSet { } } var _id:String = "" var title:String = "" var collapse:Int = 0 var cover:String = "" var monthRank:String = "" var image:String = "" required override init() { super.init() } } ================================================ FILE: zhuishushenqi/RightSide/Ranking/Service/ZSRankDetailWebService.swift ================================================ // // QSRankDetailInteractor.swift // zhuishushenqi // // Created by Nory Cao on 2017/4/13. // Copyright © 2017年 QS. All rights reserved. // import Foundation import HandyJSON import ZSAPI class ZSRankDetailWebService { func fetchRanking(rank:QSRankModel,index:Int,_ handler:ZSBaseCallback<[Book]>?){ let api = ZSAPI.rankList(id: ID(novel: rank, index: index)) zs_get(api.path) { (json) in if let ranking = json?["ranking"] as? [String:AnyObject] { if let books = ranking["books"] as? [Any] { if let models = [Book].deserialize(from: books) as? [Book] { handler?(models) } } } } } func ID( novel:QSRankModel,index:Int)->String{ var idStr = "" switch index { case 0: idStr = novel._id break case 1: idStr = novel.monthRank break case 2: idStr = novel.totalRank break default: idStr = novel._id break } if idStr == "" { idStr = novel._id } return idStr } } ================================================ FILE: zhuishushenqi/RightSide/Ranking/Service/ZSRankService.swift ================================================ // // ZSRankService.swift // zhuishushenqi // // Created by yung on 2018/8/13. // Copyright © 2018年 QS. All rights reserved. // import Foundation import ZSAPI class ZSRankService { func fetchRanking(_ handler:ZSBaseCallback<[[QSRankModel]]>?){ let api = ZSAPI.ranking("" as AnyObject) zs_get(api.path).responseJSON { (response) in if let data = response.data { if let obj = try? JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.allowFragments) as? [String:Any] { var ranking:[[QSRankModel]] = [] if let male = obj["male"] as? [Any] { if let maleModels = [QSRankModel].deserialize(from: male) as? [QSRankModel] { var males = maleModels let otherRank = self.rankModel(title: "别人家的榜单", image: "ranking_other") males.insert(otherRank, at: self.collapse(maleRank: males)) ranking.append(males) } } if let female = obj["female"] as? [Any] { if let femaleModels = [QSRankModel].deserialize(from: female) as? [QSRankModel] { var females = femaleModels let otherRank = self.rankModel(title: "别人家的榜单", image: "ranking_other") females.insert(otherRank, at: self.collapse(maleRank: females)) ranking.append(females) } } handler?(ranking) } } } } func collapse(maleRank:[QSRankModel]?)->Int{ var collapseIndex = 0 if let models = maleRank { for model in models { if model.collapse == 1 { break } collapseIndex += 1 } } return collapseIndex } fileprivate func rankModel(title:String,image:String)->QSRankModel{ let otherRank = QSRankModel() otherRank.title = title otherRank.image = image return otherRank } } ================================================ FILE: zhuishushenqi/RightSide/Ranking/ViewModel/ZSRankDetailViewModel.swift ================================================ // // QSRankDetailPresenter.swift // zhuishushenqi // // Created by Nory Cao on 2017/4/13. // Copyright © 2017年 QS. All rights reserved. // import Foundation class ZSRankDetailViewModel { var webService = ZSRankDetailWebService() func fetchRanking(rank:QSRankModel,index:Int,_ handler:ZSBaseCallback<[Book]>?){ webService.fetchRanking(rank: rank, index: index, handler) } } ================================================ FILE: zhuishushenqi/RightSide/Ranking/ViewModel/ZSRankViewModel.swift ================================================ // // QSRankPresenter.swift // zhuishushenqi // // Created by Nory Cao on 2017/4/13. // Copyright © 2017年 QS. All rights reserved. // import Foundation class ZSRankViewModel { var ranks:[[QSRankModel]] = [] var showRanks:[[QSRankModel]] = [] var showMaleRank:Bool = false var showFemaleRank:Bool = false fileprivate var webService:ZSRankService = ZSRankService() init() { fetchRanking { (_) in self.showRanks = self.ranks self.clickSection(index: 0, nil) self.clickSection(index: 1, nil) } } func fetchRanking(_ handler:ZSBaseCallback?) { webService.fetchRanking { (ranking) in if let realRanking = ranking { self.ranks = realRanking } handler?(nil) } } func clickRow(indexPath:IndexPath,_ handler:ZSBaseCallback<[QSRankModel]>?) { let collapseIndex = webService.collapse(maleRank: ranks[indexPath.section]) if indexPath.row == collapseIndex - 1 { if indexPath.section == 0 { showMaleRank = !showMaleRank } else { showFemaleRank = !showFemaleRank } clickSection(index: indexPath.section,handler) } else { handler?(nil) } } func clickSection(index:Int,_ handler:ZSBaseCallback<[QSRankModel]>?) { if showMaleRank { showRanks[index] = ranks[index] handler?(showRanks[index]) } else { let collapseIndex = webService.collapse(maleRank: ranks[index]) subRanking(index: index, collapseIndex: collapseIndex) { (models) in self.showRanks[index] = models ?? [] handler?(self.showRanks[index]) } } } func subRanking(index:Int,collapseIndex:Int,_ handler:ZSBaseCallback<[QSRankModel]>?) { var sub:[QSRankModel] = [] for i in 0..Void class RankingViewCell: UITableViewCell { var accessoryClosure:AccessoryImageViewClosure? var model:QSRankModel? { didSet{ self.textLabel?.text = "\(model?.title ?? "")" if let image = model?.image ,model?.image != "" { self.imageView?.image = UIImage(named: image) self.accessoryImageView.isHidden = false return } let imageUrlString = "\(IMAGE_BASEURL)\(model?.cover ?? "")" self.imageView?.qs_setBookCoverWithURLString(urlString: imageUrlString) } } override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) self.contentView.addSubview(self.accessoryImageView) self.accessoryImageView.isHidden = true } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func awakeFromNib() { super.awakeFromNib() // Initialization code } override func setSelected(_ selected: Bool, animated: Bool) { super.setSelected(selected, animated: animated) // Configure the view for the selected state } override func prepareForReuse() { self.imageView?.image = nil; self.textLabel?.text = nil; self.accessoryImageView.isHidden = true } public lazy var accessoryImageView:UIButton = { let imageView = UIButton() imageView.frame = CGRect(x: self.bounds.width - 29, y: self.bounds.height/2 - 3.5, width: 14, height: 7) imageView.setImage(UIImage(named: "ranking_arrow_down"), for: .normal) imageView.setImage(UIImage(named: "IQButtonBarArrowUp"), for: .selected) imageView.isSelected = false imageView.addTarget(self, action: #selector(accessoryTouch(sender:)), for: .touchUpInside) return imageView }() @objc func accessoryTouch(sender:Any){ self.accessoryImageView.isSelected = !self.accessoryImageView.isSelected if let closure = accessoryClosure { closure() } } override func layoutSubviews() { super.layoutSubviews() let imageViewHeight = self.bounds.height - 20 let imageViewWidth = self.bounds.height - 20 self.imageView?.frame = CGRect(x: 15, y: 10, width: imageViewWidth, height: imageViewHeight) let textLabelX = (self.imageView?.bounds.maxX ?? 0) + 20 let textLabelWidth = self.bounds.width - (self.imageView?.bounds.maxX ?? 0) - 50 let textLabelHeight = self.bounds.height - 10 self.textLabel?.frame = CGRect(x: textLabelX , y: 5, width: textLabelWidth, height: textLabelHeight) self.textLabel?.font = UIFont.systemFont(ofSize: 13) let accessoryX = self.bounds.width - 29 let accessoryY = self.bounds.height/2 - 3.5 self.accessoryImageView.frame = CGRect(x: accessoryX, y: accessoryY, width: 14, height: 7) } } ================================================ FILE: zhuishushenqi/RightSide/Ranking/Views/ReadHistoryCell.swift ================================================ // // TopDetailCell.swift // zhuishushenqi // // Created by Nory Cao on 16/10/4. // Copyright © 2016年 QS. All rights reserved. // import UIKit class ReadHistoryCell: UITableViewCell { @IBOutlet weak var authorWidth: NSLayoutConstraint! @IBOutlet weak var remain: UILabel! @IBOutlet weak var reading: UILabel! @IBOutlet weak var profile: UILabel! @IBOutlet weak var type: UILabel! @IBOutlet weak var name: UILabel! @IBOutlet weak var author: UILabel! @IBOutlet weak var icon: UIImageView! @IBOutlet weak var readingWidth: NSLayoutConstraint! var model:BookDetail? func configureCell(model:BookDetail){ self.model = model self.remain.text = String(format: "%.2f %%读者留存", Float(self.model?.retentionRatio ?? "0") ?? 0) self.reading.text = String(format: "%.0f 人在追", Float(self.model?.latelyFollower ?? "0") ?? 0) self.profile.text = "\(self.model?.longIntro ?? "")" self.type.text = "\(self.model?.cat ?? "")" self.name.text = "\(self.model?.title ?? "")" self.author.text = "\(self.model?.author ?? "")" let width = (self.author.text ?? "").qs_width(UIFont.systemFont(ofSize: 11), height: 21) self.authorWidth.constant = width + 5 let readWidth = (self.reading.text ?? "").qs_width(UIFont.systemFont(ofSize: 11), height: 21) readingWidth.constant = readWidth + 5 let urlString = "\((self.model?.cover ?? "qqqqqqqq"))" self.icon.qs_setBookCoverWithURLString(urlString: urlString) if type.text == "" { type.text = self.model?.majorCate ?? "" } } override func awakeFromNib() { super.awakeFromNib() // Initialization code } override func setSelected(_ selected: Bool, animated: Bool) { super.setSelected(selected, animated: animated) // Configure the view for the selected state } } ================================================ FILE: zhuishushenqi/RightSide/Ranking/Views/ReadHistoryCell.xib ================================================ ================================================ FILE: zhuishushenqi/RightSide/Ranking/Views/TopDetailCell.swift ================================================ // // TopDetailCell.swift // zhuishushenqi // // Created by Nory Cao on 16/10/4. // Copyright © 2016年 QS. All rights reserved. // import UIKit class TopDetailCell: UITableViewCell { @IBOutlet weak var authorWidth: NSLayoutConstraint! @IBOutlet weak var remain: UILabel! @IBOutlet weak var reading: UILabel! @IBOutlet weak var profile: UILabel! @IBOutlet weak var type: UILabel! @IBOutlet weak var name: UILabel! @IBOutlet weak var author: UILabel! @IBOutlet weak var icon: UIImageView! @IBOutlet weak var readingWidth: NSLayoutConstraint! var model:Book?{ didSet{ self.remain.text = String(format: "%.2f %%读者留存", self.model?.retentionRatio ?? 0) self.reading.text = String(format: "%.0f 人在追", self.model?.latelyFollower ?? 0) self.profile.text = "\(self.model?.shortIntro ?? "")" self.type.text = "\(self.model?.cat ?? "")" self.name.text = "\(self.model?.title ?? "")" self.author.text = "\(self.model?.author ?? "")" let width = (self.author.text ?? "").qs_width(UIFont.systemFont(ofSize: 11), height: 21) self.authorWidth.constant = width + 5 let readWidth = (self.reading.text ?? "").qs_width(UIFont.systemFont(ofSize: 11), height: 21) readingWidth.constant = readWidth + 5 let urlString = "\((self.model?.cover ?? "qqqqqqqq"))" self.icon.qs_setBookCoverWithURLString(urlString: urlString) if type.text == "" { type.text = model?.majorCate ?? "" } } } func bindData(model: AnyObject?) { self.model = model as? Book self.remain.text = String(format: "%.2f %%读者留存", self.model?.retentionRatio ?? 0) self.reading.text = String(format: "%.0f 人在追", self.model?.latelyFollower ?? 0) self.profile.text = "\(self.model?.shortIntro ?? "")" self.type.text = "\(self.model?.cat ?? "")" self.name.text = "\(self.model?.title ?? "")" self.author.text = "\(self.model?.author ?? "")" let width = (self.author.text ?? "").qs_width(UIFont.systemFont(ofSize: 11), height: 21) self.authorWidth.constant = width + 5 let readWidth = (self.reading.text ?? "").qs_width(UIFont.systemFont(ofSize: 11), height: 21) readingWidth.constant = readWidth + 5 let urlString = "\((self.model?.cover ?? "qqqqqqqq"))" self.icon.qs_setBookCoverWithURLString(urlString: urlString) if type.text == "" { type.text = self.model?.majorCate ?? "" } } override func awakeFromNib() { super.awakeFromNib() // Initialization code } override func setSelected(_ selected: Bool, animated: Bool) { super.setSelected(selected, animated: animated) // Configure the view for the selected state } } ================================================ FILE: zhuishushenqi/RightSide/Ranking/Views/TopDetailCell.xib ================================================ ================================================ FILE: zhuishushenqi/RightSide/Search/Controllers/SearchDetailViewController.swift ================================================ // // SearchDetailViewController.swift // zhuishushenqi // // Created by Nory Cao on 2017/3/11. // Copyright © 2017年 QS. All rights reserved. // import UIKit //import YTKKeyValueStore class SearchDetailViewController: BaseViewController,UITableViewDataSource,UITableViewDelegate,UISearchResultsUpdating,UISearchControllerDelegate,UISearchBarDelegate,SearchViewDelegate { var books = [Book]() var searchWords:String = "" var store:YTKKeyValueStore? let storeKey = "SearchHistory" var historyList:[String]? lazy var searchView:SearchView = { let searchView = SearchView(frame: CGRect(x: 0, y: 108, width: ScreenWidth, height: ScreenHeight - 108)) searchView.delegate = self return searchView }() fileprivate lazy var tableView:UITableView = { let tableView = UITableView(frame: CGRect(x: 0, y:108, width: ScreenWidth, height: ScreenHeight - 108), style: .grouped) tableView.dataSource = self tableView.delegate = self tableView.sectionHeaderHeight = CGFloat.leastNormalMagnitude tableView.sectionFooterHeight = CGFloat.leastNormalMagnitude tableView.rowHeight = 93 tableView.qs_registerCellNib(TopDetailCell.self) return tableView }() fileprivate lazy var searchController:UISearchController = { let searchVC:UISearchController = UISearchController(searchResultsController: nil) searchVC.searchBar.placeholder = "输入书名或作者名" searchVC.searchResultsUpdater = self searchVC.delegate = self searchVC.searchBar.delegate = self searchVC.hidesNavigationBarDuringPresentation = true searchVC.searchBar.sizeToFit() searchVC.searchBar.backgroundColor = UIColor.darkGray return searchVC }() override func viewDidLoad() { super.viewDidLoad() title = "搜索" self.automaticallyAdjustsScrollViewInsets = false requestHot() initSubview() let store = YTKKeyValueStore(dbWithName: dbName) if store?.isTableExists(searchHistory) == false { store?.createTable(withName: searchHistory) } self.store = store self.historyList = store?.getObjectById(storeKey, fromTable: searchHistory) as? [String] searchView.historyList = historyList } func searchViewClearButtonClicked() { store?.clearTable(searchHistory) self.historyList = store?.getObjectById(storeKey, fromTable: searchHistory) as? [String] searchView.historyList = historyList } func searchViewHotWordClick(index: Int){ searchController.dismiss(animated: true, completion: nil) searchController.searchBar.text = searchView.hotWords[index] self.saveHistoryList(searchBar: searchController.searchBar) self.searchWords = searchController.searchBar.text ?? "" requestData() } func requestData(){ // http://api.zhuishushenqi.com/book/fuzzy-search?query=偷&start=0&limit=100 let urlString = "\(BASEURL)/book/fuzzy-search" let param = ["query":self.searchWords,"start":"0","limit":"100"] zs_get(urlString, parameters: param) { (response) in QSLog(response) if let books = response?["books"] { if let models = [Book].deserialize(from: books as? [Any]) as? [Book] { self.books = models } } DispatchQueue.main.async { self.view.addSubview(self.tableView) self.tableView.reloadData() } } } func requestHot() -> Void { // http://api.zhuishushenqi.com/book/hot-word let urlString = "\(BASEURL)/book/hot-word" zs_get(urlString, parameters: nil) { (response) in QSLog(response) if let json = response { DispatchQueue.main.async { self.searchView.hotWords = json["hotWords"] as? [String] ?? [""] self.view.addSubview(self.searchView) } } } } func initSubview(){ let bgView = UIView() // [UIColor colorWithRed:0.94f green:0.94f blue:0.96f alpha:1.00f]; bgView.backgroundColor = UIColor(red: 0.94, green: 0.94, blue: 0.96, alpha: 1.0) bgView.frame = CGRect(x: 0, y: 64, width: self.view.bounds.width, height: 44) bgView.addSubview(self.searchController.searchBar) view.addSubview(bgView) } func isExistSearchWord(key:String)->Bool{ var isExist = false if let list = historyList { for item in list { if item == key { isExist = true } } } return isExist } func willPresentSearchController(_ searchController: UISearchController){ UIView.animate(withDuration: 0.35) { self.searchView.frame = CGRect(x: 0, y: 64, width: ScreenWidth, height: ScreenHeight - 64) self.view.addSubview(self.searchView) } } func willDismissSearchController(_ searchController: UISearchController) { UIView.animate(withDuration: 0.35) { self.searchView.frame = CGRect(x: 0, y: 108, width: ScreenWidth, height: ScreenHeight - 108) } } func searchBarShouldBeginEditing(_ searchBar: UISearchBar) -> Bool { return true }// return NO to not become first responder func saveHistoryList(searchBar:UISearchBar){ let exist = self.isExistSearchWord(key: searchBar.text ?? "") if exist == false { if let text = searchBar.text ,searchBar.text?.trimmingCharacters(in: CharacterSet(charactersIn: " ")) != "" { if historyList == nil { historyList = [text] } else { historyList?.append(text) } store?.put(historyList, withId: storeKey, intoTable: searchHistory) searchView.historyList = historyList } } } func searchBarSearchButtonClicked(_ searchBar: UISearchBar){ self.saveHistoryList(searchBar: searchBar) self.searchWords = searchBar.text ?? "" searchController.dismiss(animated: true, completion: nil) searchView.removeFromSuperview() requestData() }// called when keyboard search button pressed func searchBarCancelButtonClicked(_ searchBar: UISearchBar){ }// called when cancel button pressed func updateSearchResults(for searchController: UISearchController){ self.searchWords = self.searchController.searchBar.text ?? "" } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return self.books.count } func numberOfSections(in tableView: UITableView) -> Int { return 1 } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell:TopDetailCell? = tableView.qs_dequeueReusableCell(TopDetailCell.self) cell?.backgroundColor = UIColor.white cell?.selectionStyle = .none cell!.model = self.books.count > indexPath.row ? books[indexPath.row]:nil return cell! } func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { return tableView.sectionHeaderHeight } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } } ================================================ FILE: zhuishushenqi/RightSide/Search/Controllers/SearchViewController.swift ================================================ // // SearchViewController.swift // zhuishushenqi // // Created by Nory Chao on 2017/3/11. // Copyright © 2017年 QS. All rights reserved. // import UIKit import QSNetwork class SearchViewController: BaseViewController,UITableViewDataSource,UITableViewDelegate,UISearchResultsUpdating,UISearchControllerDelegate,UISearchBarDelegate { var hotWords = [String]() var headerHeight:CGFloat = 0 var searchWords:String = "" var books = [Book]() fileprivate var tagColor = [UIColor(red: 0.56, green: 0.77, blue: 0.94, alpha: 1.0), UIColor(red: 0.75, green: 0.41, blue: 0.82, alpha: 1.0), UIColor(red: 0.96, green: 0.74, blue: 0.49, alpha: 1.0), UIColor(red: 0.57, green: 0.81, blue: 0.84, alpha: 1.0), UIColor(red: 0.40, green: 0.80, blue: 0.72, alpha: 1.0), UIColor(red: 0.91, green: 0.56, blue: 0.56, alpha: 1.0), UIColor(red: 0.56, green: 0.77, blue: 0.94, alpha: 1.0), UIColor(red: 0.75, green: 0.41, blue: 0.82, alpha: 1.0)] fileprivate lazy var tableView:UITableView = { let tableView = UITableView(frame: CGRect(x: 0, y: 114, width: ScreenWidth, height: ScreenHeight - 114), style: .grouped) tableView.dataSource = self tableView.delegate = self tableView.sectionHeaderHeight = CGFloat.leastNormalMagnitude tableView.sectionFooterHeight = 10 tableView.rowHeight = 44 tableView.backgroundColor = UIColor.white tableView.qs_registerCellClass(UITableViewCell.self) return tableView }() lazy var searchController:UISearchController = { let searchVC:UISearchController = UISearchController(searchResultsController: nil) searchVC.searchBar.placeholder = "输入书名或作者名111" searchVC.searchResultsUpdater = self searchVC.delegate = self searchVC.searchBar.delegate = self // searchVC.obscuresBackgroundDuringPresentation = true searchVC.hidesNavigationBarDuringPresentation = true searchVC.searchBar.sizeToFit() searchVC.searchBar.backgroundColor = UIColor.darkGray return searchVC }() var headerView:UIView? var changeIndex = 0 override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. title = "搜索" initSubview() requestData() view.backgroundColor = UIColor.red } func requestData() -> Void { // http://api.zhuishushenqi.com/book/hot-word let urlString = "\(BASEURL)/book/hot-word" QSNetwork.request(urlString) { (response) in QSLog(response.json) if let json = response.json { self.hotWords = json["hotWords"] as? [String] ?? [""] // self.tableView.reloadData() DispatchQueue.main.async { self.headerView = self.headView() self.view.addSubview(self.tableView) self.tableView.reloadData() } } } } func initSubview(){ let bgView = UIView() bgView.frame = CGRect(x: 0, y: 64, width: self.view.bounds.width, height: 50) bgView.addSubview(self.searchController.searchBar) view.addSubview(bgView) } func searchBarSearchButtonClicked(_ searchBar: UISearchBar) { self.view.removeFromSuperview() self.removeFromParentViewController() }// called when keyboard search button pressed func searchBarBookmarkButtonClicked(_ searchBar: UISearchBar){ }// called when bookmark button pressed func searchBarCancelButtonClicked(_ searchBar: UISearchBar){ dismiss(animated: false, completion: nil) }// called when cancel button pressed fileprivate func headView()->UIView{ let headerView = UIView() headerView.backgroundColor = UIColor.white let label = UILabel() label.frame = CGRect(x: 15, y: 0, width: 200, height: 21) label.font = UIFont.systemFont(ofSize: 15) label.textColor = UIColor.black label.text = "大家都在搜" headerView.addSubview(label) let btn = UIButton(type: .custom) btn.setImage(UIImage(named:"actionbar_refresh"), for: .normal) btn.setTitle("换一批", for: .normal) btn.setTitleColor(UIColor.darkGray, for: .normal) btn.titleLabel?.font = UIFont.systemFont(ofSize: 13) btn.contentHorizontalAlignment = .right btn.addTarget(self, action: #selector(changeHotWord(btn:)), for: .touchUpInside) btn.frame = CGRect(x: self.view.bounds.width - 90, y: 0, width: 70, height: 21) headerView.addSubview(btn) var x:CGFloat = 20 var y:CGFloat = 10 + 21 let spacex:CGFloat = 10 let spacey:CGFloat = 10 let height:CGFloat = 20 var count = 0 for index in changeIndex..<(changeIndex + 6) { count = index if count >= hotWords.count { count = count - hotWords.count } let width = widthOfString(hotWords[count], font: UIFont.systemFont(ofSize: 11), height: 21) + 20 if x + width + 20 > ScreenWidth { x = 20 y = y + spacey + height } let btn = UIButton(type: .custom) btn.frame = CGRect(x: x, y: y, width: width, height: height) btn.setTitle(hotWords[count], for: UIControlState()) btn.titleLabel?.font = UIFont.systemFont(ofSize: 11) btn.setTitleColor(UIColor.white, for: UIControlState()) btn.backgroundColor = tagColor[index%tagColor.count] btn.layer.cornerRadius = 2 headerView.addSubview(btn) x = x + width + spacex } changeIndex = count + 1 headerHeight = y + height + 10 headerView.frame = CGRect(x: 0, y: 0, width: self.view.bounds.width, height: headerHeight) return headerView } @objc func changeHotWord(btn:UIButton){ if changeIndex > self.hotWords.count { } let views = self.headerView?.subviews for index in 0..<(views?.count ?? 0) { let view = views?[index] view?.removeFromSuperview() } self.headerView = headView() self.tableView.reloadData() } func updateSearchResults(for searchController: UISearchController) { } @objc func backAction(item:UIBarButtonItem){ } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return 1 } func numberOfSections(in tableView: UITableView) -> Int { return 1 } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell:UITableViewCell = tableView.qs_dequeueReusableCell(UITableViewCell.self) cell.backgroundColor = UIColor.white cell.selectionStyle = .none return cell } func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { return self.headerView } func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { return headerHeight } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } } ================================================ FILE: zhuishushenqi/RightSide/Search/Models/QSSearchItem.swift ================================================ // // QSSearchItem.swift // zhuishushenqi // // Created by Nory Cao on 2017/4/11. // Copyright © 2017年 QS. All rights reserved. // import UIKit class QSSearchItem { var title:String = "" } ================================================ FILE: zhuishushenqi/RightSide/Search/QSHistoryHeaderView.swift ================================================ // // QSHistoryHeaderView.swift // zhuishushenqi // // Created by Nory Cao on 2017/4/11. // Copyright © 2017年 QS. All rights reserved. // import UIKit typealias ClearClick = ()->Void class QSHistoryHeaderView: UIView { var clear:ClearClick? override init(frame: CGRect) { super.init(frame: frame) setHeaderView() } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } func setHeaderView(){ frame = CGRect(x: 0, y: 0, width: ScreenWidth, height: 41) let label = UILabel() label.frame = CGRect(x: 15, y: 10, width: 200, height: 21) label.font = UIFont.systemFont(ofSize: 15) label.textColor = UIColor.black label.text = "搜索历史" self.addSubview(label) let btn = UIButton(type: .custom) btn.setImage(UIImage(named:"search_delete"), for: .normal) btn.setTitle("清空", for: .normal) btn.setTitleColor(UIColor.darkGray, for: .normal) btn.titleLabel?.font = UIFont.systemFont(ofSize: 13) btn.contentHorizontalAlignment = .right btn.addTarget(self, action: #selector(clearHistory(btn:)), for: .touchUpInside) btn.frame = CGRect(x: self.bounds.width - 90, y: 10, width: 70, height: 21) self.addSubview(btn) } @objc func clearHistory(btn:UIButton){ if let clearClick = clear { clearClick() } } } ================================================ FILE: zhuishushenqi/RightSide/Search/QSSearchAutoCompleteTable.swift ================================================ // // QSSearchAutoCompleteTable.swift // zhuishushenqi // // Created by Nory Cao on 2017/4/13. // Copyright © 2017年 QS. All rights reserved. // import UIKit class ZSSearchAutoCompleteController: ZSBaseTableViewController ,UISearchResultsUpdating{ var books:[String] = [] { didSet{ self.tableView.reloadData() } } var selectRow:DidSelectRow? override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = UIColor(red: 1.0, green: 0.98, blue: 0.82, alpha: 1.0) tableView.qs_registerCellClass(UITableViewCell.self) // automaticallyAdjustsScrollViewInsets = false if #available(iOS 11.0, *) { self.tableView.contentInsetAdjustmentBehavior = .automatic } } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) self.tableView.frame = CGRect(x: 0, y: kNavgationBarHeight, width: ScreenWidth, height: ScreenHeight - kNavgationBarHeight) } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int{ return books.count } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell:UITableViewCell? = tableView.qs_dequeueReusableCell(UITableViewCell.self) cell?.backgroundColor = UIColor.white cell?.selectionStyle = .none cell?.textLabel?.text = self.books.count > indexPath.row ? books[indexPath.row]:"" return cell! } override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { return 0.0001 } override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { return UIView() } override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { if let select = selectRow { select(indexPath) } } //MARK: - UISearchBarDelegate func updateSearchResults(for searchController: UISearchController) { } } class QSSearchAutoCompleteTable: UIView,UITableViewDataSource,UITableViewDelegate { var books:[String]? { didSet{ self.tableView.reloadData() } } var selectRow:DidSelectRow? lazy var tableView:UITableView = { let tableView = UITableView(frame: CGRect(x: 0, y: 0, width: ScreenWidth, height: self.bounds.height), style: .grouped) tableView.dataSource = self tableView.delegate = self tableView.sectionHeaderHeight = CGFloat.leastNonzeroMagnitude tableView.rowHeight = 44 tableView.qs_registerCellClass(UITableViewCell.self) return tableView }() override init(frame: CGRect) { super.init(frame: frame) self.backgroundColor = UIColor(red: 1.0, green: 0.98, blue: 0.82, alpha: 1.0) self.addSubview(self.tableView) } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int{ return books?.count ?? 0 } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell:UITableViewCell? = tableView.qs_dequeueReusableCell(UITableViewCell.self) cell?.backgroundColor = UIColor.white cell?.selectionStyle = .none cell?.textLabel?.text = self.books?.count ?? 0 > indexPath.row ? books?[indexPath.row]:"" return cell! } func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { return 0.0001 } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { if let select = selectRow { select(indexPath) } } } ================================================ FILE: zhuishushenqi/RightSide/Search/QSSearchHeaderView.swift ================================================ // // QSSearchHeaderView.swift // zhuishushenqi // // Created by Nory Cao on 2017/4/10. // Copyright © 2017年 QS. All rights reserved. // import UIKit typealias Change = ()->Void typealias HotwordClick = (_ hotword:String)->Void class QSSearchHeaderView: UIView { var hotwords:[String] = [] { didSet{ if hotwords.count > 0{ stop() } setNeedsLayout() } } var change:Change? var hotwordClick:HotwordClick? var activity:UIActivityIndicatorView! fileprivate var tagColor = [UIColor(red: 0.56, green: 0.77, blue: 0.94, alpha: 1.0), UIColor(red: 0.75, green: 0.41, blue: 0.82, alpha: 1.0), UIColor(red: 0.96, green: 0.74, blue: 0.49, alpha: 1.0), UIColor(red: 0.57, green: 0.81, blue: 0.84, alpha: 1.0), UIColor(red: 0.40, green: 0.80, blue: 0.72, alpha: 1.0), UIColor(red: 0.91, green: 0.56, blue: 0.56, alpha: 1.0), UIColor(red: 0.56, green: 0.77, blue: 0.94, alpha: 1.0), UIColor(red: 0.75, green: 0.41, blue: 0.82, alpha: 1.0)] override init(frame: CGRect) { super.init(frame: frame) setHeaderView() } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } func setHeaderView(){ self.backgroundColor = UIColor.white let label = UILabel() label.frame = CGRect(x: 15, y: 10, width: 200, height: 21) label.font = UIFont.systemFont(ofSize: 15) label.textColor = UIColor.black label.text = "大家都在搜" self.addSubview(label) let btn = UIButton(type: .custom) btn.setImage(UIImage(named:"actionbar_refresh"), for: .normal) btn.setTitle("换一批", for: .normal) btn.setTitleColor(UIColor.darkGray, for: .normal) btn.titleLabel?.font = UIFont.systemFont(ofSize: 13) btn.contentHorizontalAlignment = .right btn.addTarget(self, action: #selector(changeHotWord(btn:)), for: .touchUpInside) btn.frame = CGRect(x: self.bounds.width - 90, y: 10, width: 70, height: 21) self.addSubview(btn) animate() } @objc func changeHotWord(btn:UIButton){ if let changeHot = change { changeHot() } } @objc func hotwordClicked(btn:UIButton){ if let hotword = hotwordClick { hotword(btn.titleLabel?.text ?? ""); } } func animate(){ activity = UIActivityIndicatorView(style: .gray) activity.frame = CGRect(x: 0, y: 0, width: 30, height: 30) activity.center = self.center self.addSubview(activity) activity.startAnimating() } func stop(){ activity.stopAnimating() activity.removeFromSuperview() } override func layoutSubviews() { super.layoutSubviews() for item in self.subviews { if item.isKind(of: UIButton.self) { let btn:UIButton? = item as? UIButton if btn?.imageView?.image == nil{ btn?.removeFromSuperview() } } } var x:CGFloat = 20 var y:CGFloat = 20 + 21 let spacex:CGFloat = 10 let spacey:CGFloat = 10 let height:CGFloat = 20 for index in 0.. ScreenWidth { x = 20 y = y + spacey + height } let btn = UIButton(type: .custom) btn.frame = CGRect(x: x, y: y, width: width, height: height) btn.setTitle(hotwords[index], for: UIControl.State()) btn.titleLabel?.font = UIFont.systemFont(ofSize: 11) btn.setTitleColor(UIColor.white, for: UIControl.State()) btn.backgroundColor = tagColor[index%tagColor.count] btn.addTarget(self, action: #selector(hotwordClicked(btn:)), for: .touchUpInside) btn.layer.cornerRadius = 2 self.addSubview(btn) x = x + width + spacex } self.frame = CGRect(x: 0, y: 0, width: self.bounds.width, height: 121) } } ================================================ FILE: zhuishushenqi/RightSide/Search/QSSearchInteractor.swift ================================================ // // QSSearchInteractor.swift // zhuishushenqi // // Created Nory Cao on 2017/4/10. // Copyright © 2017年 QS. All rights reserved. // // Template generated by Juanpe Catalán @JuanpeCMiOS // import UIKit //import YTKKeyValueStore import ZSAPI typealias ZSSearchWebCallback = (_ hotwords:[String]?)->Void typealias ZSSearchWebAnyCallback = (_ hotwords:T?)->Void class ZSSearchWebService { //NARK: - webserver data func fetchHotwords (_ callback: ZSSearchWebCallback?){ let api = ZSAPI.hotwords("" as AnyObject) zs_get(api.path, parameters: nil) { (response) in QSLog(response) if let hotwords:[String] = response?["hotWords"] as? [String] { callback?(hotwords) }else{ callback?(nil) } } } func fetchAutoComplete(key:String,_ callback:ZSSearchWebCallback?){ let api = ZSAPI.autoComplete(query: key) zs_get(api.path, parameters: api.parameters) { (response) in if let keywords = response?["keywords"] as? [String] { callback?(keywords) } else { callback?(nil) } } } func fetchBooks( key:String, start:Int, limit:Int, _ callback:ZSSearchWebAnyCallback<[Book]>?){ let api = ZSAPI.searchBook(id: key, start: "\(start)", limit: "\(limit)") zs_get(api.path, parameters: api.parameters) { (response) in if let books = response?["books"] { if let models = [Book].deserialize(from: books as? [Any]) as? [Book] { callback?(models) } else { callback?(nil) } } else { callback?(nil) } } } } class QSSearchInteractor: QSSearchInteractorProtocol { weak var output: QSSearchInteractorOutputProtocol! var hotwords:[String] = [] var offset:Int = 0 private let SearchStoreKey = "SearchHistory" func fetchHotwords(){ let api = ZSAPI.hotwords("" as AnyObject) zs_get(api.path, parameters: nil) { (response) in QSLog(response) if let hotwords:[String] = response?["hotWords"] as? [String] { self.hotwords = hotwords self.output.fetchHotwordsSuccess(hotwords: self.subWords()) }else{ self.output.fetchHotwordsFailed() } } } func fetchSearchList(){ var list:[[String]] = [] let hot:[String] = [] var history:[String] = [] history = getHistoryList() list.append(hot) list.append(history) self.output.searchListFetch(list: list) } func clearSearchList(){ let store = getHistoryStore() let list:[[String]] = [[],[]] store?.clearTable(searchHistory) self.output.searchListFetch(list: list) } func autoComplete(key:String){ let api = ZSAPI.autoComplete(query: key) zs_get(api.path, parameters: api.parameters) { (response) in guard let keywords = response?["keywords"] as? [String] else { return } self.output.fetchAutoComplete(keys: keywords) } } func updateHistoryList(history:String){ if history == ""{ return } var list = getHistoryList() if !isExistSearchWord(key: history, historyList:list) { let store = getHistoryStore() list.append(history.trimmingCharacters(in: CharacterSet(charactersIn: " "))) store?.clearTable(searchHistory) store?.put(list, withId: SearchStoreKey, intoTable: searchHistory) self.output.searchListFetch(list: [ [],list]) } } func fetchBooks(key:String){ let api = ZSAPI.searchBook(id: key, start: "0", limit: "100") zs_get(api.path, parameters: api.parameters) { (response) in if let books = response?["books"] { if let models = [Book].deserialize(from: books as? [Any]) as? [Book] { self.output.fetchBooksSuccess(books: models,key:key) } else { self.output.fetchBooksFailed(key: key) } } else { self.output.fetchBooksFailed(key: key) } } } func getHistoryList()->[String]{ let store = getHistoryStore() return store?.getObjectById(SearchStoreKey, fromTable: searchHistory) as? [String] ?? [] } func getHistoryStore()->YTKKeyValueStore?{ let store = YTKKeyValueStore(dbWithName: dbName) if store?.isTableExists(searchHistory) == false { store?.createTable(withName: searchHistory) } return store } func isExistSearchWord(key:String,historyList:[String])->Bool{ var isExist = false for item in historyList { if item == key { isExist = true } } return isExist } func subWords()->[String]{ if hotwords.count == 0 { return [] } var subWords:[String] = [] for item in offset..?){ webService.fetchBooks(key: key, start: start, limit: limit) { (books) in callback?(books) } } fileprivate func subHotwords(callback:ZSSearchWebCallback?){ if hotwords.count > 0 { var subWords:[String] = [] for item in newIndex.. [String]?{ let store = getHistoryStore() let historyList = store?.getObjectById(SearchStoreKey, fromTable: searchHistory) as? [String] callback?(historyList) return historyList } func clearSearchList(){ let store = getHistoryStore() store?.clearTable(searchHistory) } func addToHistory(history:String){ if history == ""{ return } if !searchWordExist(key: history) { let store = getHistoryStore() var list = fetchHistoryList(nil) ?? [] list.append(history.trimmingCharacters(in: CharacterSet(charactersIn: " "))) store?.clearTable(searchHistory) store?.put(list, withId: SearchStoreKey, intoTable: searchHistory) } } fileprivate func searchWordExist(key:String)->Bool{ var isExist = false let list = fetchHistoryList(nil) if let historyList = list { for item in historyList { if item == key { isExist = true break; } } } return isExist } fileprivate func getHistoryStore()->YTKKeyValueStore?{ let store = YTKKeyValueStore(dbWithName: dbName) if store?.isTableExists(searchHistory) == false { store?.createTable(withName: searchHistory) } return store } } class QSSearchPresenter: QSSearchPresenterProtocol { weak var view: QSSearchViewProtocol? var interactor: QSSearchInteractorProtocol var router: QSSearchWireframeProtocol var hotwords:[String] = [] { didSet{ } } var history:[String] = [] { didSet{ } } var keywords:[String] = [] var books:[Book] = [] init(interface: QSSearchViewProtocol, interactor: QSSearchInteractorProtocol, router: QSSearchWireframeProtocol) { self.view = interface self.interactor = interactor self.router = router } func viewDidLoad(){ self.interactor.fetchHotwords() self.interactor.fetchSearchList() } func didClickClearBtn(){ interactor.clearSearchList() } func didSelectHotWord(hotword:String){ view?.showActivityView() interactor.updateHistoryList(history: hotword) interactor.fetchBooks(key: hotword) } func didClickChangeBtn(){ fetchHotwordsSuccess(hotwords: interactor.subWords()) } func didSelectResultRow(indexPath:IndexPath){ router.presentDetails(books[indexPath.row]) } func didSelectHistoryRow(indexPath:IndexPath){ view?.showActivityView() view?.showBooks(books: [], key: history[indexPath.row]) interactor.fetchBooks(key: history[indexPath.row]) } func didSelectAutoCompleteRow(indexPath: IndexPath) { view?.showActivityView() view?.showBooks(books: [], key: keywords[indexPath.row]) interactor.updateHistoryList(history: keywords[indexPath.row]) interactor.fetchBooks(key: keywords[indexPath.row]) } func fetchBooks(key:String){ view?.showActivityView() interactor.fetchBooks(key: key) } } extension QSSearchPresenter:QSSearchInteractorOutputProtocol{ func fetchHotwordsSuccess(hotwords:[String]){ self.hotwords = hotwords view?.showHotwordsData(hotwords: hotwords) } func fetchHotwordsFailed(){ } func fetchAutoComplete(keys: [String]) { self.keywords = keys view?.hideActivityView() view?.showAutoComplete(keywords: keys) } func searchListFetch(list:[[String]]){ self.history = list[1] view?.showSearchListData(searchList: list) } func fetchBooksSuccess(books:[Book],key:String){ self.books = books view?.hideActivityView() view?.showBooks(books: self.books,key:key) } func fetchBooksFailed(key:String) { self.books = [] view?.hideActivityView() view?.showBooks(books: self.books,key:key) } func showResult(key: String) { self.books = [] view?.showBooks(books: self.books, key: key) } } ================================================ FILE: zhuishushenqi/RightSide/Search/QSSearchProtocols.swift ================================================ // // QSSearchProtocols.swift // zhuishushenqi // // Created Nory Cao on 2017/4/10. // Copyright © 2017年 QS. All rights reserved. // // Template generated by Juanpe Catalán @JuanpeCMiOS // import Foundation import UIKit //MARK: Wireframe - protocol QSSearchWireframeProtocol: class { var viewController: UIViewController? { get set } func presentDetails(_ novel:Book) static func createModule() -> UIViewController } //MARK: Presenter - protocol QSSearchPresenterProtocol: class { var view: QSSearchViewProtocol?{ get set } var interactor: QSSearchInteractorProtocol{ get set } var router: QSSearchWireframeProtocol{ get set } func viewDidLoad() func didClickClearBtn() func didSelectHotWord(hotword:String) func didClickChangeBtn() func didSelectResultRow(indexPath:IndexPath) func didSelectHistoryRow(indexPath:IndexPath) func didSelectAutoCompleteRow(indexPath:IndexPath) func fetchBooks(key:String) } //MARK: Output - protocol QSSearchInteractorOutputProtocol: class { func fetchHotwordsSuccess(hotwords:[String]) func fetchHotwordsFailed() func fetchAutoComplete(keys:[String]) func searchListFetch(list:[[String]]) func fetchBooksSuccess(books:[Book],key:String) func fetchBooksFailed(key:String) func showResult(key:String) } //MARK: Interactor - protocol QSSearchInteractorProtocol: class { var output: QSSearchInteractorOutputProtocol! { get set } func fetchHotwords() func subWords()->[String] func fetchSearchList() func autoComplete(key:String) func clearSearchList() func updateHistoryList(history:String) func fetchBooks(key:String) } //MARK: View - protocol QSSearchViewProtocol: IndicatableView { var presenter: QSSearchPresenterProtocol? { get set } func showNoHotwordsView() func showHotwordsData(hotwords:[String]) func showNoHistoryView() func showSearchListData(searchList:[[String]]) func showBooks(books:[Book],key:String) func showAutoComplete(keywords:[String]) } protocol IndicatableView: class { func showActivityView() func hideActivityView() //非遮罩 func showLoadingPageView() func hideLoadingPageView() } ================================================ FILE: zhuishushenqi/RightSide/Search/QSSearchResultTable.swift ================================================ // // QSSearchResultTable.swift // zhuishushenqi // // Created by Nory Cao on 2017/4/12. // Copyright © 2017年 QS. All rights reserved. // import UIKit typealias DidSelectRow = (_ indexPath:IndexPath)->Void class QSSearchResultTable: UIView,UITableViewDataSource,UITableViewDelegate { var books:[Book]? { didSet{ self.tableView.reloadData() } } var selectRow:DidSelectRow? lazy var tableView:UITableView = { let tableView = UITableView(frame: CGRect(x: 0, y: 0, width: ScreenWidth, height: self.bounds.height), style: .grouped) tableView.dataSource = self tableView.delegate = self tableView.sectionHeaderHeight = CGFloat.leastNonzeroMagnitude tableView.rowHeight = 93 tableView.qs_registerCellNib(TopDetailCell.self) return tableView }() override init(frame: CGRect) { super.init(frame: frame) self.backgroundColor = UIColor(red: 1.0, green: 0.98, blue: 0.82, alpha: 1.0) self.addSubview(self.tableView) } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int{ return books?.count ?? 0 } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell:TopDetailCell? = tableView.qs_dequeueReusableCell(TopDetailCell.self) cell?.backgroundColor = UIColor.white cell?.selectionStyle = .none cell?.model = self.books?.count ?? 0 > indexPath.row ? books?[indexPath.row]:nil return cell! } func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { return 0.0001 } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { if let select = selectRow { select(indexPath) } } } ================================================ FILE: zhuishushenqi/RightSide/Search/QSSearchRouter.swift ================================================ // // QSSearchRouter.swift // zhuishushenqi // // Created Nory Cao on 2017/4/10. // Copyright © 2017年 QS. All rights reserved. // // Template generated by Juanpe Catalán @JuanpeCMiOS // import UIKit class QSSearchRouter: QSSearchWireframeProtocol { weak var viewController: UIViewController? static func createModule() -> UIViewController { // Change to get view from storyboard if not using progammatic UI let view = QSSearchViewController(style: .grouped) let interactor = QSSearchInteractor() let router = QSSearchRouter() let presenter = QSSearchPresenter(interface: view, interactor: interactor, router: router) view.presenter = presenter interactor.output = presenter router.viewController = view return view } func presentDetails(_ novel:Book){ viewController?.navigationController?.pushViewController(QSBookDetailRouter.createModule(id: novel._id ?? ""), animated: true) } } ================================================ FILE: zhuishushenqi/RightSide/Search/QSSearchViewController+SearchBar.swift ================================================ // // QSSearchViewController+SearchBar.swift // zhuishushenqi // // Created by Nory Cao on 2017/4/12. // Copyright © 2017年 QS. All rights reserved. // import Foundation import UIKit extension QSSearchViewController:UISearchResultsUpdating,UISearchControllerDelegate,UISearchBarDelegate { //MARK: - UISearchBarDelegate func searchBarSearchButtonClicked(_ searchBar: UISearchBar) { searchController.dismiss(animated: true, completion: nil) showResultTable(key: searchBar.text ?? "") self.presenter?.interactor.updateHistoryList(history: searchBar.text ?? "") self.presenter?.fetchBooks(key: searchBar.text ?? "") }// called when keyboard search button pressed func searchBarBookmarkButtonClicked(_ searchBar: UISearchBar){ }// called when bookmark button pressed func searchBarCancelButtonClicked(_ searchBar: UISearchBar){ }// called when cancel button pressed //MARK: - UISearchControllerDelegate func willPresentSearchController(_ searchController: UISearchController){ showSearching() } func didPresentSearchController(_ searchController: UISearchController){ } func willDismissSearchController(_ searchController: UISearchController){ showHistory() } func didDismissSearchController(_ searchController: UISearchController){ } //MARK: - UISearchResultsUpdating func updateSearchResults(for searchController: UISearchController){ let text = searchController.searchBar.text ?? "" if searchWords == text { return } searchWords = text if !text.isEmpty { presenter?.interactor.autoComplete(key: text) } } } ================================================ FILE: zhuishushenqi/RightSide/Search/QSSearchViewController+Transition.swift ================================================ // // QSSearchViewController+Transition.swift // zhuishushenqi // // Created by Nory Cao on 2017/4/12. // Copyright © 2017年 QS. All rights reserved. // import Foundation import UIKit //MARK: - UI extension QSSearchViewController{ enum SearchShowType { case history case searching case associate } func showHistory(){ UIView.animate(withDuration: 0.35) { self.tableView.frame = self.getFrame(type: .history) } // self.tableView.removeFromSuperview() // self.view.addSubview(self.tableView) } func showSearching(){ self.tableView.removeFromSuperview() self.autoCompleteTable.removeFromSuperview() KeyWindow?.addSubview(self.tableView) UIView.animate(withDuration: 0.35, animations: { self.tableView.frame = self.getFrame(type: .searching) }) { (finish) in } } func showAssociate(){ // self.tableView.removeFromSuperview() self.tableView.frame = self.getFrame(type: .associate) self.searchController.view.addSubview(self.tableView) } func showAutoComplete(){ self.autoCompleteTable.frame = getFrame(type: .searching) self.tableView.removeFromSuperview() self.searchController.view.addSubview(self.autoCompleteTable) } func showResultTable(key:String){ self.searchController.dismiss(animated: true, completion: nil) self.searchController.searchBar.text = key self.tableView.removeFromSuperview() self.view.addSubview(self.resultTableView) } func getFrame( type:SearchShowType)->CGRect{ var searchFrame = CGRect.zero switch type { case .history: searchFrame = CGRect(x: 0, y: kNavgationBarHeight + 56, width: ScreenWidth, height: ScreenHeight - 114) break case .searching: searchFrame = CGRect(x: 0, y: kNavgationBarHeight, width: ScreenWidth, height: ScreenHeight - kNavgationBarHeight - 216) break case .associate: searchFrame = CGRect(x: 0, y: kNavgationBarHeight, width: ScreenWidth, height: ScreenHeight - kNavgationBarHeight) break } return searchFrame } } ================================================ FILE: zhuishushenqi/RightSide/Search/QSSearchViewController.swift ================================================ // // QSSearchViewController.swift // zhuishushenqi // // Created Nory Cao on 2017/4/10. // Copyright © 2017年 QS. All rights reserved. // // Template generated by Juanpe Catalán @JuanpeCMiOS // import UIKit import RxCocoa import RxSwift import YYCategories class ZSSearchViewController: ZSBaseTableViewController { fileprivate let searchBarHeight:CGFloat = 56 var searchViewModel:ZSSearchViewModel = ZSSearchViewModel() fileprivate var searchHeaderView = ZSSearchHeaderView(frame:CGRect.zero) fileprivate var historyHeaderView = QSHistoryHeaderView() fileprivate var resultViewController = ZSSearchResultViewController() fileprivate var autoCompleteController = ZSSearchAutoCompleteController() fileprivate lazy var searchBackgroundView:UIView = { let view = UIView() view.isUserInteractionEnabled = true return view }() fileprivate lazy var searchController:UISearchController = { let searchVC:UISearchController = UISearchController(searchResultsController: self.autoCompleteController) searchVC.searchBar.placeholder = "输入书名或作者名" searchVC.searchResultsUpdater = self.autoCompleteController searchVC.delegate = self searchVC.searchBar.delegate = self searchVC.dimsBackgroundDuringPresentation = false searchVC.hidesNavigationBarDuringPresentation = true searchVC.searchBar.sizeToFit() searchVC.searchBar.backgroundColor = UIColor(red: 0.84, green: 0.84, blue: 0.86, alpha: 1.0) searchVC.searchBar.barTintColor = UIColor.lightGray searchVC.searchBar.layer.borderColor = UIColor.white.cgColor return searchVC }() let dispose = DisposeBag() override func viewDidLoad() { super.viewDidLoad() title = "搜索" searchBackgroundView.addSubview(searchController.searchBar) searchBackgroundView.addSubview(searchHeaderView) searchHeaderView.rx.observe(CGFloat.self, "totalHeight").subscribe(onNext: { (height) in QSLog(height) self.searchHeaderView.snp.remakeConstraints { (make) in make.top.equalToSuperview().offset(self.searchBarHeight) make.left.right.equalToSuperview() make.height.equalTo(height!) } self.tableView.reloadData() }).disposed(by: dispose) searchHeaderView.change = { self.searchViewModel.newHotwords { (hotwords) in self.searchHeaderView.hotwords = hotwords ?? [] } } searchHeaderView.hotwordClick = { text in self.searchController.searchBar.text = text self.searchBarSearchButtonClicked(self.searchController.searchBar) } searchViewModel.newHotwords { (hotwords) in self.searchHeaderView.hotwords = hotwords ?? [] } tableView.qs_registerCellNib(QSHistoryCell.self) autoCompleteController.selectRow = { (indexPath) in self.searchController.searchBar.text = self.autoCompleteController.books[indexPath.row] self.searchBarSearchButtonClicked(self.searchController.searchBar) } resultViewController.didSelectIndexPathAtRow = { (book) in self.navigationController?.pushViewController(QSBookDetailRouter.createModule(id: book?._id ?? ""), animated: true) } if self.searchViewModel.keywords != "" { self.searchController.searchBar.text = self.searchViewModel.keywords searchViewModel.fetchAutoComplete(key: searchController.searchBar.text ?? "") { (books) in self.autoCompleteController.books = books ?? [] _ = IndexPath(row: 0, section: 0) if self.autoCompleteController.books.count > 0 { self.resultViewController.view.origin.y = self.searchBarHeight self.resultViewController.view.size.height = ScreenHeight - self.searchBarHeight - kNavgationBarHeight self.searchViewModel.addToHistory(history: self.searchController.searchBar.text ?? "") self.view.addSubview(self.resultViewController.view) self.searchViewModel.fetchBooks(key: self.searchController.searchBar.text ?? "", start: 0, limit: 50) { (books) in self.resultViewController.books = books } } } } self.automaticallyAdjustsScrollViewInsets = true } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) } } extension ZSSearchViewController:UISearchResultsUpdating,UISearchControllerDelegate,UISearchBarDelegate{ //MARK: - UISearchBarDelegate func updateSearchResults(for searchController: UISearchController) { } func searchBarSearchButtonClicked(_ searchBar: UISearchBar) { self.resultViewController.view.origin.y = self.searchBarHeight self.resultViewController.view.size.height = ScreenHeight - self.searchBarHeight - kNavgationBarHeight searchViewModel.addToHistory(history: searchBar.text ?? "") self.tableView.reloadData() self.view.addSubview(self.resultViewController.view) searchController.dismiss(animated: true) { // 完成后将搜索结果展示出来 self.searchViewModel.fetchBooks(key: searchBar.text ?? "", start: 0, limit: 50) { (books) in self.resultViewController.books = books } } } // called when keyboard search button pressed func searchBarCancelButtonClicked(_ searchBar: UISearchBar) { } // called when cancel button pressed func searchBarTextDidEndEditing(_ searchBar: UISearchBar) { } // called when text ends editing func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) { searchViewModel.fetchAutoComplete(key: searchText) { (books) in self.autoCompleteController.books = books ?? [] } } //MARK: - UISearchControllerDelegate func willPresentSearchController(_ searchController: UISearchController) { resultViewController.view.removeFromSuperview() searchViewModel.fetchAutoComplete(key: searchController.searchBar.text ?? "") { (books) in self.autoCompleteController.books = books ?? [] } } func didPresentSearchController(_ searchController: UISearchController) { // let indexPath = IndexPath(row: 0, section: 0) // // if indexPath.row < self.autoCompleteController.books.count { // self.searchController.searchBar.text = self.autoCompleteController.books[indexPath.row] // self.searchBarSearchButtonClicked(self.searchController.searchBar) // } } func willDismissSearchController(_ searchController: UISearchController) { } func didDismissSearchController(_ searchController: UISearchController) { } func presentSearchController(_ searchController: UISearchController) { } } extension ZSSearchViewController{ override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { if section == 0 { return 0 } return searchViewModel.fetchHistoryList(nil)?.count ?? 0 } override func numberOfSections(in tableView: UITableView) -> Int { return 2 } override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { if section == 0 { return searchBackgroundView } return historyHeaderView } override func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? { return UIView() } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.qs_dequeueReusableCell(QSHistoryCell.self) if indexPath.section > 0 { if let list = searchViewModel.fetchHistoryList(nil) { cell?.titleLabel?.text = list[indexPath.row] } } return cell! } override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { if section == 0 { return searchBarHeight + searchHeaderView.totalHeight } return 42 } override func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { return 0.01 } override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { return 44 } override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) if let list = searchViewModel.fetchHistoryList(nil) { searchController.searchBar.text = list[indexPath.row] self.searchBarSearchButtonClicked(searchController.searchBar) } } } class QSSearchViewController: ZSBaseTableViewController{ var presenter: QSSearchPresenterProtocol? var hotWords = [String]() { didSet{ tableView.reloadData() } } var searchList:[[String]] = [] var searchWords:String = "" var books = [Book]() var headerView:QSSearchHeaderView! var historyHeader:QSHistoryHeaderView! var resultTableView:QSSearchResultTable! var autoCompleteTable:QSSearchAutoCompleteTable! // new var searchHeaderView:ZSSearchHeaderView = ZSSearchHeaderView() lazy var searchController:UISearchController = { let searchVC:UISearchController = UISearchController(searchResultsController: nil) searchVC.searchBar.placeholder = "输入书名或作者名" searchVC.searchResultsUpdater = self searchVC.delegate = self searchVC.searchBar.delegate = self searchVC.hidesNavigationBarDuringPresentation = true searchVC.searchBar.sizeToFit() searchVC.searchBar.backgroundColor = UIColor(red: 0.84, green: 0.84, blue: 0.86, alpha: 1.0) searchVC.searchBar.barTintColor = UIColor.white searchVC.searchBar.layer.borderColor = UIColor.white.cgColor return searchVC }() override init(style: UITableView.Style) { super.init(style: .grouped) } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func viewDidLoad() { super.viewDidLoad() initSubview() self.presenter?.viewDidLoad() } func initSubview(){ tableView.qs_registerCellNib(QSHistoryCell.self) } } extension QSSearchViewController{ //MARK: - tableView override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return searchList[section].count } override func numberOfSections(in tableView: UITableView) -> Int { return 1 } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell:QSHistoryCell? = tableView.qs_dequeueReusableCell(QSHistoryCell.self) cell?.backgroundColor = UIColor.white cell?.selectionStyle = .none cell?.titleLabel.text = searchList[indexPath.section][indexPath.row] return cell! } override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { let headerView = UIView() headerView.backgroundColor = UIColor(red: 0.94, green: 0.94, blue: 0.96, alpha: 1.0) headerView.addSubview(self.searchController.searchBar) headerView.addSubview(self.headerView) headerView.addSubview(self.historyHeader) // self.headerView.hotwords = self.hotWords return headerView } override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { return 256 } override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { self.presenter?.didSelectHistoryRow(indexPath: indexPath) } } extension QSSearchViewController:QSSearchViewProtocol{ func showNoHotwordsView(){ } func showHotwordsData(hotwords:[String]){ self.hotWords = hotwords self.tableView.reloadData() } func showNoHistoryView(){ } func showSearchListData(searchList:[[String]]){ self.searchList = searchList self.tableView.reloadData() } func showBooks(books: [Book],key:String) { self.books = books self.resultTableView.books = self.books showResultTable(key:key) } func showAutoComplete(keywords: [String]) { self.autoCompleteTable.books = keywords showAutoComplete() } } ================================================ FILE: zhuishushenqi/RightSide/Search/Views/QSHistoryCell.swift ================================================ // // QSHistoryCell.swift // zhuishushenqi // // Created by Nory Cao on 2017/4/12. // Copyright © 2017年 QS. All rights reserved. // import UIKit class QSHistoryCell: UITableViewCell { override func awakeFromNib() { super.awakeFromNib() // Initialization code } @IBOutlet weak var titleLabel: UILabel! override func setSelected(_ selected: Bool, animated: Bool) { super.setSelected(selected, animated: animated) // Configure the view for the selected state } } ================================================ FILE: zhuishushenqi/RightSide/Search/Views/QSHistoryCell.swift~876a9ee6afa162f4fb71eff5fa02f2e0dbe52ae2 ================================================ // // QSHistoryCell.swift // zhuishushenqi // // Created by Nory Cao on 2017/4/12. // Copyright © 2017年 QS. All rights reserved. // import UIKit class QSHistoryCell: UITableViewCell { override func awakeFromNib() { super.awakeFromNib() // Initialization code } @IBOutlet weak var titleLabel: UILabel! override func setSelected(_ selected: Bool, animated: Bool) { super.setSelected(selected, animated: animated) // Configure the view for the selected state } } ================================================ FILE: zhuishushenqi/RightSide/Search/Views/QSHistoryCell.swift~HEAD ================================================ // // QSHistoryCell.swift // zhuishushenqi // // Created by Nory Cao on 2017/4/12. // Copyright © 2017年 QS. All rights reserved. // import UIKit class QSHistoryCell: UITableViewCell { override func awakeFromNib() { super.awakeFromNib() // Initialization code } @IBOutlet weak var titleLabel: UILabel! override func setSelected(_ selected: Bool, animated: Bool) { super.setSelected(selected, animated: animated) // Configure the view for the selected state } } ================================================ FILE: zhuishushenqi/RightSide/Search/Views/QSHistoryCell.xib ================================================ ================================================ FILE: zhuishushenqi/RightSide/Search/Views/SearchView.swift ================================================ // // SearchView.swift // zhuishushenqi // // Created by Nory Cao on 2017/3/11. // Copyright © 2017年 QS. All rights reserved. // import UIKit protocol SearchViewDelegate { func searchViewClearButtonClicked() func searchViewHotWordClick(index:Int) } class SearchView: UIView,UITableViewDataSource,UITableViewDelegate { var delegate:SearchViewDelegate? var hotWords = [String](){ didSet{ self.headerView = headView() self.tableView.dataSource = self self.tableView.delegate = self self.addSubview(self.tableView) } } var historyList:[String]?{ didSet{ self.tableView.reloadData() } } var headerHeight:CGFloat = 0 var searchWords:String = "" var books = [Book]() var headerView:UIView? var changeIndex = 0 fileprivate lazy var tableView:UITableView = { let tableView = UITableView(frame: CGRect(x: 0, y:0, width: ScreenWidth, height: ScreenHeight - 114), style: .grouped) tableView.dataSource = self tableView.estimatedSectionHeaderHeight = 114 tableView.delegate = self tableView.rowHeight = 44 // tableView.register(UINib (nibName: self.iden, bundle: nil), forCellReuseIdentifier: self.iden) return tableView }() fileprivate var tagColor = [UIColor(red: 0.56, green: 0.77, blue: 0.94, alpha: 1.0), UIColor(red: 0.75, green: 0.41, blue: 0.82, alpha: 1.0), UIColor(red: 0.96, green: 0.74, blue: 0.49, alpha: 1.0), UIColor(red: 0.57, green: 0.81, blue: 0.84, alpha: 1.0), UIColor(red: 0.40, green: 0.80, blue: 0.72, alpha: 1.0), UIColor(red: 0.91, green: 0.56, blue: 0.56, alpha: 1.0), UIColor(red: 0.56, green: 0.77, blue: 0.94, alpha: 1.0), UIColor(red: 0.75, green: 0.41, blue: 0.82, alpha: 1.0)] fileprivate lazy var clearBtn:UIButton = { let btn = UIButton(type: .custom) btn.setTitle("清空", for: .normal) btn.setImage(UIImage(named:"d_delete"), for: .normal) btn.setTitleColor(UIColor.darkGray, for: .normal) btn.frame = CGRect(x: self.bounds.width - 80, y: 11, width: 60, height: 21) btn.addTarget(self, action: #selector(clearAction(btn:)), for: .touchUpInside) btn.titleLabel?.font = UIFont.systemFont(ofSize: 13) return btn }() override init(frame: CGRect) { super.init(frame: frame) self.tableView.reloadData() } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } fileprivate func headView()->UIView{ let headerView = UIView() headerView.backgroundColor = UIColor.white let label = UILabel() label.frame = CGRect(x: 15, y: 20, width: 200, height: 21) label.font = UIFont.systemFont(ofSize: 15) label.textColor = UIColor.black label.text = "大家都在搜" headerView.addSubview(label) let btn = UIButton(type: .custom) btn.setImage(UIImage(named:"actionbar_refresh"), for: .normal) btn.setTitle("换一批", for: .normal) btn.setTitleColor(UIColor.darkGray, for: .normal) btn.titleLabel?.font = UIFont.systemFont(ofSize: 13) btn.contentHorizontalAlignment = .right btn.addTarget(self, action: #selector(changeHotWord(btn:)), for: .touchUpInside) btn.frame = CGRect(x: self.bounds.width - 90, y: 20, width: 70, height: 21) headerView.addSubview(btn) var x:CGFloat = 20 var y:CGFloat = 10 + 21 + 20 let spacex:CGFloat = 10 let spacey:CGFloat = 10 let height:CGFloat = 20 var count = 0 for index in changeIndex..<(changeIndex + 6) { count = index if count >= hotWords.count { count = count - hotWords.count } let width = hotWords[count].qs_width(UIFont.systemFont(ofSize: 11), height: 21) + 20 if x + width + 20 > ScreenWidth { x = 20 y = y + spacey + height } let btn = UIButton(type: .custom) btn.frame = CGRect(x: x, y: y, width: width, height: height) btn.setTitle(hotWords[count], for: UIControl.State()) btn.titleLabel?.font = UIFont.systemFont(ofSize: 11) btn.setTitleColor(UIColor.white, for: UIControl.State()) btn.backgroundColor = tagColor[index%tagColor.count] btn.tag = count + 12121 btn.addTarget(self, action: #selector(hotWordSearchAction(btn:)), for: .touchUpInside) btn.layer.cornerRadius = 2 headerView.addSubview(btn) x = x + width + spacex } changeIndex = count + 1 headerHeight = y + height + 10 headerView.frame = CGRect(x: 0, y: 0, width: self.bounds.width, height: headerHeight) return headerView } @objc func changeHotWord(btn:UIButton){ if changeIndex > self.hotWords.count { } let views = self.headerView?.subviews for index in 0..<(views?.count ?? 0) { let view = views?[index] view?.removeFromSuperview() } self.headerView = headView() self.tableView.reloadData() } @objc func hotWordSearchAction(btn:UIButton){ delegate?.searchViewHotWordClick(index: btn.tag - 12121) } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return (historyList?.count ?? 0) + 1 } func numberOfSections(in tableView: UITableView) -> Int { return 1 } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { var cell:UITableViewCell? = tableView.dequeueReusableCell(withIdentifier: "HotWords") if cell == nil { cell = UITableViewCell(style: .default, reuseIdentifier: "HotWords") } cell?.backgroundColor = UIColor.white cell?.selectionStyle = .none cell?.textLabel?.textColor = UIColor.darkGray if indexPath.row == 0 { cell?.textLabel?.textColor = UIColor.black cell?.textLabel?.text = "搜索历史" clearBtn.removeFromSuperview() cell?.contentView.addSubview(clearBtn) cell?.imageView?.image = nil }else{ cell?.imageView?.image = UIImage(named: "bs_last_read") cell?.textLabel?.text = historyList?[indexPath.row - 1] } return cell! } func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { return self.headerView } func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { return self.headerHeight } @objc func clearAction(btn:UIButton){ delegate?.searchViewClearButtonClicked() } } ================================================ FILE: zhuishushenqi/RightSide/Search/Views/ZSHistoryHeaderView.swift ================================================ // // ZSHistoryHeaderView.swift // zhuishushenqi // // Created by yung on 2018/7/1. // Copyright © 2018年 QS. All rights reserved. // import UIKit class ZSHistoryHeaderView: UIView { /* // Only override draw() if you perform custom drawing. // An empty implementation adversely affects performance during animation. override func draw(_ rect: CGRect) { // Drawing code } */ } ================================================ FILE: zhuishushenqi/RightSide/Search/Views/ZSSearchHeaderView.swift ================================================ // // ZSSearchHeaderView.swift // zhuishushenqi // // Created by yung on 2018/7/1. // Copyright © 2018年 QS. All rights reserved. // import UIKit import SnapKit @objcMembers class ZSSearchHeaderView: UIView { fileprivate var tagColor = [UIColor(red: 0.56, green: 0.77, blue: 0.94, alpha: 1.0), UIColor(red: 0.75, green: 0.41, blue: 0.82, alpha: 1.0), UIColor(red: 0.96, green: 0.74, blue: 0.49, alpha: 1.0), UIColor(red: 0.57, green: 0.81, blue: 0.84, alpha: 1.0), UIColor(red: 0.40, green: 0.80, blue: 0.72, alpha: 1.0), UIColor(red: 0.91, green: 0.56, blue: 0.56, alpha: 1.0), UIColor(red: 0.56, green: 0.77, blue: 0.94, alpha: 1.0), UIColor(red: 0.75, green: 0.41, blue: 0.82, alpha: 1.0)] fileprivate var activity:UIActivityIndicatorView! fileprivate lazy var titleLabel:UILabel = { let lb = UILabel() lb.text = "大家都在搜" lb.font = UIFont.systemFont(ofSize: 13) lb.textColor = UIColor.black return lb }() fileprivate lazy var refreshButton:UIButton = { let bt = UIButton(type: .custom) bt.setTitle("换一批", for: .normal) bt.setTitleColor(UIColor.gray, for: .normal) bt.titleLabel?.font = UIFont.systemFont(ofSize: 13) bt.setImage(UIImage(named:"actionbar_refresh"), for: .normal) bt.addTarget(self, action: #selector(changeHotWord(btn:)), for: .touchUpInside) return bt }() var hotwords:[String] = [] { didSet { setupSubviews() } } var change:Change? var hotwordClick:HotwordClick? fileprivate let hotwordsBaseTag = 121 dynamic var totalHeight:CGFloat = 20 override init(frame: CGRect) { super.init(frame: frame) setupSubviews() } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func awakeFromNib() { super.awakeFromNib() // Initialization code setupSubviews() } func setupSubviews() { titleLabel.removeFromSuperview() refreshButton.removeFromSuperview() addSubview(titleLabel) addSubview(refreshButton) titleLabel.snp.remakeConstraints { (make) in make.left.equalTo(self).offset(16) make.width.equalTo(200) make.top.equalTo(self).offset(10) make.height.equalTo(21) } refreshButton.snp.remakeConstraints { (make) in make.right.equalTo(self).offset(-16) make.width.equalTo(70) make.height.equalTo(21) make.top.equalTo(self).offset(10) } // 移除之前的button for item in hotwordsBaseTag.. ScreenWidth { x = 20 y = y + spacey + height self.totalHeight = y + height } let btn = UIButton(type: .custom) btn.frame = CGRect(x: x, y: y, width: width, height: height) btn.setTitle(hotwords[index], for: UIControl.State()) btn.titleLabel?.font = UIFont.systemFont(ofSize: 11) btn.setTitleColor(UIColor.white, for: UIControl.State()) btn.backgroundColor = tagColor[index%tagColor.count] btn.addTarget(self, action: #selector(hotwordClicked(btn:)), for: .touchUpInside) btn.layer.cornerRadius = 2 btn.tag = hotwordsBaseTag + index addSubview(btn) x = x + width + spacex } } func animate(){ activity = UIActivityIndicatorView(style: .gray) activity.frame = CGRect(x: 0, y: 0, width: 30, height: 30) activity.center = self.center self.addSubview(activity) activity.startAnimating() } func stop(){ activity.stopAnimating() activity.removeFromSuperview() } @objc func hotwordClicked(btn:UIButton){ if let hotword = hotwordClick { hotword(btn.titleLabel?.text ?? ""); } } @objc func changeHotWord(btn:UIButton){ if let changeHot = change { changeHot() } } } ================================================ FILE: zhuishushenqi/RightSide/Search/Views/ZSSearchViewCell.swift ================================================ // // ZSSearchViewCell.swift // zhuishushenqi // // Created by yung on 2018/6/28. // Copyright © 2018年 QS. All rights reserved. // import UIKit class ZSSearchViewCell: UITableViewCell { fileprivate var tagColor = [UIColor(red: 0.56, green: 0.77, blue: 0.94, alpha: 1.0), UIColor(red: 0.75, green: 0.41, blue: 0.82, alpha: 1.0), UIColor(red: 0.96, green: 0.74, blue: 0.49, alpha: 1.0), UIColor(red: 0.57, green: 0.81, blue: 0.84, alpha: 1.0), UIColor(red: 0.40, green: 0.80, blue: 0.72, alpha: 1.0), UIColor(red: 0.91, green: 0.56, blue: 0.56, alpha: 1.0), UIColor(red: 0.56, green: 0.77, blue: 0.94, alpha: 1.0), UIColor(red: 0.75, green: 0.41, blue: 0.82, alpha: 1.0)] var activity:UIActivityIndicatorView! var hotwords:[String] = [] { didSet { setupSubviews() } } var change:Change? var hotwordClick:HotwordClick? let hotwordsBaseTag = 121 var totalHeight:CGFloat = 20 override func awakeFromNib() { super.awakeFromNib() // Initialization code setupSubviews() } func setupSubviews() { var x:CGFloat = 20 var y:CGFloat = 10 + 21 let spacex:CGFloat = 10 let spacey:CGFloat = 10 let height:CGFloat = 20 for index in 0.. ScreenWidth { x = 20 y = y + spacey + height totalHeight = totalHeight + height } let btn = UIButton(type: .custom) btn.frame = CGRect(x: x, y: y, width: width, height: height) btn.setTitle(hotwords[index], for: UIControl.State()) btn.titleLabel?.font = UIFont.systemFont(ofSize: 11) btn.setTitleColor(UIColor.white, for: UIControl.State()) btn.backgroundColor = tagColor[index%tagColor.count] btn.addTarget(self, action: #selector(hotwordClicked(btn:)), for: .touchUpInside) btn.layer.cornerRadius = 2 btn.tag = hotwordsBaseTag + index contentView.addSubview(btn) x = x + width + spacex } } func animate(){ activity = UIActivityIndicatorView(style: .gray) activity.frame = CGRect(x: 0, y: 0, width: 30, height: 30) activity.center = self.center self.addSubview(activity) activity.startAnimating() } func stop(){ activity.stopAnimating() activity.removeFromSuperview() } @objc func hotwordClicked(btn:UIButton){ if let hotword = hotwordClick { hotword(btn.titleLabel?.text ?? ""); } } override func layoutSubviews() { super.layoutSubviews() } override func setSelected(_ selected: Bool, animated: Bool) { super.setSelected(selected, animated: animated) // Configure the view for the selected state } } ================================================ FILE: zhuishushenqi/RightSide/Search/Views/ZSSearchViewCell.xib ================================================ ================================================ FILE: zhuishushenqi/RightSide/Search/ZSSearchResultViewController.swift ================================================ // // ZSSearchResultViewController.swift // zhuishushenqi // // Created by yung on 2018/7/1. // Copyright © 2018年 QS. All rights reserved. // import UIKit class ZSSearchResultViewController: ZSBaseTableViewController { override func viewDidLoad() { super.viewDidLoad() self.view.backgroundColor = UIColor(red: 1.0, green: 0.98, blue: 0.82, alpha: 1.0) tableView.sectionHeaderHeight = CGFloat.leastNonzeroMagnitude tableView.rowHeight = 93 tableView.qs_registerCellNib(TopDetailCell.self) // Do any additional setup after loading the view. } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } var books:[Book]? { didSet{ self.tableView.reloadData() } } var selectRow:DidSelectRow? var didSelectIndexPathAtRow:ZSBaseCallback? override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int{ return books?.count ?? 0 } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell:TopDetailCell? = tableView.qs_dequeueReusableCell(TopDetailCell.self) cell?.backgroundColor = UIColor.white cell?.selectionStyle = .none cell?.model = self.books?.count ?? 0 > indexPath.row ? books?[indexPath.row]:nil return cell! } override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { return 0.0001 } override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { if let select = selectRow { select(indexPath) } if let didSelect = didSelectIndexPathAtRow { didSelect(books?[indexPath.row]) } } } ================================================ FILE: zhuishushenqi/RightSide/Topic/Controllers/Filter/ZSFilterThemeViewController.swift ================================================ // // FilterThemeViewController.swift // zhuishushenqi // // Created by Nory Cao on 2017/3/13. // Copyright © 2017年 QS. All rights reserved. // import UIKit import ZSAPI import SnapKit class ZSFilterThemeViewController: BaseViewController, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout { private var collectionView:UICollectionView! private var viewModel:ZSFilterThemeViewModel = ZSFilterThemeViewModel() var clickAction:ClickAction? override func viewDidLoad() { super.viewDidLoad() title = "筛选书单" configureCollectionView() viewModel.request { (_) in self.collectionView.reloadData() } } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) layoutSubview() } override func viewWillLayoutSubviews() { layoutSubview() } private func layoutSubview() { collectionView.snp.remakeConstraints { (make) in make.left.equalToSuperview().offset(0) make.right.equalToSuperview().offset(0) make.top.bottom.equalToSuperview() } } fileprivate func configureCollectionView() { let layout = UICollectionViewFlowLayout() layout.minimumLineSpacing = 10 layout.minimumInteritemSpacing = 15 layout.sectionInset = UIEdgeInsets(top: 0, left: 15, bottom: 0, right: 15) layout.scrollDirection = .vertical collectionView = UICollectionView(frame: CGRect.zero, collectionViewLayout: layout) collectionView.dataSource = self collectionView.delegate = self collectionView.isPagingEnabled = true collectionView.register(ZSFilterThemeCell.self, forCellWithReuseIdentifier: "ZSFilterThemeCell") collectionView.register(ZSCatelogHeaderView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "ZSCatelogHeaderView") collectionView.showsHorizontalScrollIndicator = false collectionView.backgroundColor = UIColor.white view.addSubview(collectionView) view.backgroundColor = UIColor.white } //MARK: - UICollectionView func numberOfSections(in collectionView: UICollectionView) -> Int { return viewModel.items.count } func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return viewModel.items[section].tags.count } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ZSFilterThemeCell", for: indexPath) as! ZSFilterThemeCell cell.layer.cornerRadius = 5 cell.layer.masksToBounds = true cell.layer.borderWidth = 0.3 cell.layer.borderColor = UIColor.gray.cgColor cell.titleLabel.text = viewModel.items[indexPath.section].tags[indexPath.item] return cell } func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { if indexPath.section == 0 { return CGSize(width: collectionView.bounds.size.width - 30, height: 30) } return CGSize(width: 80, height: 30) } func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize { let name = viewModel.items[section].name if name.count > 0 { return CGSize(width: self.collectionView.bounds.width, height: 40) } return CGSize(width: self.collectionView.bounds.width, height: 10) } func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView { let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "ZSCatelogHeaderView", for: indexPath) as! ZSCatelogHeaderView let name = viewModel.items[indexPath.section].name headerView.titleLabel.text = name return headerView } func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { if let click = self.clickAction { _ = self.navigationController?.popViewController(animated: true) let item = viewModel.items[indexPath.section].tags[indexPath.row] click(indexPath.row,item,viewModel.items[indexPath.section].name) } } } typealias ClickAction = (_ index:Int,_ title:String,_ name:String)->Void ================================================ FILE: zhuishushenqi/RightSide/Topic/Controllers/ThemeTopic/QSThemeTopicInteractor.swift ================================================ // // QSThemeTopicInteractor.swift // zhuishushenqi // // Created Nory Cao on 2017/4/13. // Copyright © 2017年 QS. All rights reserved. // // Template generated by Juanpe Catalán @JuanpeCMiOS // import UIKit import ZSAPI class QSThemeTopicInteractor: QSThemeTopicInteractorProtocol { var output: QSThemeTopicInteractorOutputProtocol! var models:[[ThemeTopicModel]] = [[],[],[]] var selectedIndex = 0 var tag = "" var gender = "" let segTitles = ["本周最热","最新发布","最多收藏"] func filter(index:Int,title:String,name:String){ //此处index为tag的index var titleString = title let genders = ["male","female"] if name == "性别" { gender = genders[index] tag = "" titleString = "\(titleString)生书单" }else{ gender = "" tag = title } self.output.showFilter(title: titleString,index: selectedIndex, tag: tag, gender: gender) } func initTitle(index:Int){ self.output.showFilter(title: "全部书单", index: index, tag: tag, gender: gender) } func setupSeg(index:Int){ self.output.showSegView(titles:segTitles ) } // func requestTitle(index:Int,tag:String,gender:String){ selectedIndex = index models = [[],[],[]] request(index: index, tag: tag, gender: gender) } func requestDetail(index:Int){ if selectedIndex == index { return } selectedIndex = index if self.models[index].count > 0 { self.output.fetchModelSuccess(models: self.models[index]) return } request(index: index, tag: tag, gender: gender) } func request(index:Int,tag:String,gender:String){ //本周最热 // http://api.zhuishushenqi.com/book-list?sort=collectorCount&duration=last-seven-days&start=0 //最新发布 // http://api.zhuishushenqi.com/book-list?sort=created&duration=all&start=0 //最多收藏 (全部书单) // http://api.zhuishushenqi.com/book-list?sort=collectorCount&duration=all&start=0 var sorts:[String] = ["collectorCount","created","collectorCount"] var durations:[String] = ["last-seven-days","all","all"] let api = ZSAPI.themeTopic(sort: sorts[index], duration: durations[index], start: "0", gender: gender, tag: tag) zs_get(api.path, parameters: api.parameters) { (response) in QSLog(response) if let books = response?["bookLists"] { if let models = [ThemeTopicModel].deserialize(from: books as? [Any]) as? [ThemeTopicModel] { self.models[index] = models self.output.fetchModelSuccess(models: models) } else { self.output.fetchModelFailed() } } else{ self.output.fetchModelFailed() } } } } ================================================ FILE: zhuishushenqi/RightSide/Topic/Controllers/ThemeTopic/QSThemeTopicPresenter.swift ================================================ // // QSThemeTopicPresenter.swift // zhuishushenqi // // Created Nory Cao on 2017/4/13. // Copyright © 2017年 QS. All rights reserved. // // Template generated by Juanpe Catalán @JuanpeCMiOS // import UIKit class QSThemeTopicPresenter: QSThemeTopicPresenterProtocol { weak var view: QSThemeTopicViewProtocol? let interactor: QSThemeTopicInteractorProtocol let router: QSThemeTopicWireframeProtocol init(interface: QSThemeTopicViewProtocol, interactor: QSThemeTopicInteractorProtocol, router: QSThemeTopicWireframeProtocol) { self.view = interface self.interactor = interactor self.router = router } func viewDidLoad(index:Int) { interactor.initTitle(index: index) interactor.setupSeg(index: index) view?.showActivityView() } func didSelectTitle(index: Int, tag: String, gender: String) { view?.showActivityView() interactor.requestTitle(index: index, tag: tag, gender: gender) } func didSelectSeg(index: Int){ view?.showActivityView() view?.showThemeTopic(models: []) interactor.requestDetail(index: index) } func didClickFilter(index:Int,title:String,name:String){ interactor.filter(index: index, title: title, name: name) } func didSelectAt(indexPath:IndexPath,models:[ThemeTopicModel]){ router.presentDetail(indexPath: indexPath, models: models) } } extension QSThemeTopicPresenter:QSThemeTopicInteractorOutputProtocol{ func showSegView(titles: [String]) { view?.showSeg(titles: titles) } func showFilter(title: String, index: Int, tag: String, gender: String) { view?.showTitle(title: title, index: index, tag: tag, gender: gender) } func fetchModelSuccess(models: [ThemeTopicModel]) { view?.hideActivityView() view?.showThemeTopic(models: models) } func fetchModelFailed() { view?.hideActivityView() } } ================================================ FILE: zhuishushenqi/RightSide/Topic/Controllers/ThemeTopic/QSThemeTopicProtocols.swift ================================================ // // QSThemeTopicProtocols.swift // zhuishushenqi // // Created Nory Cao on 2017/4/13. // Copyright © 2017年 QS. All rights reserved. // // Template generated by Juanpe Catalán @JuanpeCMiOS // import Foundation //MARK: Wireframe - protocol QSThemeTopicWireframeProtocol: class { func presentDetail(indexPath:IndexPath,models:[ThemeTopicModel]) } //MARK: Presenter - protocol QSThemeTopicPresenterProtocol: class { func viewDidLoad(index:Int) func didSelectTitle(index:Int,tag:String,gender:String) func didSelectSeg(index: Int) func didClickFilter(index:Int,title:String,name:String) func didSelectAt(indexPath:IndexPath,models:[ThemeTopicModel]) } //MARK: Interactor - protocol QSThemeTopicInteractorProtocol: class { func initTitle(index:Int) func setupSeg(index:Int) func filter(index:Int,title:String,name:String) func requestTitle(index:Int,tag:String,gender:String) func requestDetail(index:Int) } //MARK: Output - protocol QSThemeTopicInteractorOutputProtocol: class { func showFilter(title:String,index: Int, tag: String, gender: String) func showSegView(titles:[String]) func fetchModelSuccess(models:[ThemeTopicModel]) func fetchModelFailed() } //MARK: View - protocol QSThemeTopicViewProtocol: IndicatableView { var presenter: QSThemeTopicPresenterProtocol? { get set } func showSeg(titles:[String]) func showThemeTopic(models:[ThemeTopicModel]) func showTitle(title:String,index: Int, tag: String, gender: String) } ================================================ FILE: zhuishushenqi/RightSide/Topic/Controllers/ThemeTopic/QSThemeTopicRouter.swift ================================================ // // QSThemeTopicRouter.swift // zhuishushenqi // // Created Nory Cao on 2017/4/13. // Copyright © 2017年 QS. All rights reserved. // // Template generated by Juanpe Catalán @JuanpeCMiOS // import UIKit class QSThemeTopicRouter: QSThemeTopicWireframeProtocol { weak var viewController: UIViewController? static func createModule() -> UIViewController { // Change to get view from storyboard if not using progammatic UI let view = QSThemeTopicViewController(nibName: nil, bundle: nil) let interactor = QSThemeTopicInteractor() let router = QSThemeTopicRouter() let presenter = QSThemeTopicPresenter(interface: view, interactor: interactor, router: router) view.presenter = presenter interactor.output = presenter router.viewController = view return view } func presentDetail(indexPath:IndexPath,models:[ThemeTopicModel]){ let model = models[indexPath.row] viewController?.navigationController?.pushViewController(QSTopicDetailRouter.createModule(id: model._id), animated: true) } func presentReading(model:[ResourceModel],booDetail:BookDetail){ let viewController = ZSReaderViewController() viewController.viewModel.book = booDetail self.viewController?.present(viewController, animated: true, completion: nil) } } ================================================ FILE: zhuishushenqi/RightSide/Topic/Controllers/ThemeTopic/QSThemeTopicViewController.swift ================================================ // // QSBookDetailViewController.swift // zhuishushenqi // // Created Nory Cao on 2017/4/13. // Copyright © 2017年 QS. All rights reserved. // // Template generated by Juanpe Catalán @JuanpeCMiOS // import UIKit class QSThemeTopicViewController: BaseViewController ,SegMenuDelegate,UITableViewDataSource,UITableViewDelegate,QSThemeTopicViewProtocol{ var presenter: QSThemeTopicPresenterProtocol? var segView:SegMenu! fileprivate var booksModel:[ThemeTopicModel]? fileprivate var titleView:UIButton? fileprivate lazy var tableView:UITableView = { let tableView = UITableView(frame: CGRect(x: 0, y: kNavgationBarHeight + 40, width: ScreenWidth, height: ScreenHeight - kNavgationBarHeight - 40), style: .grouped) tableView.dataSource = self tableView.delegate = self tableView.sectionHeaderHeight = CGFloat.leastNormalMagnitude tableView.sectionFooterHeight = CGFloat.leastNormalMagnitude tableView.rowHeight = 93 tableView.qs_registerCellNib(ThemeTopicCell.self) return tableView }() override func viewDidLoad() { super.viewDidLoad() view.addSubview(tableView) presenter?.viewDidLoad(index: 0) } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) layoutSubview() } override func viewWillLayoutSubviews() { layoutSubview() } private func layoutSubview() { segView.snp.makeConstraints { (make) in let statusHeight = UIApplication.shared.statusBarFrame.height let navHeight = self.navigationController?.navigationBar.height ?? 0 make.left.right.equalToSuperview() make.top.equalToSuperview().offset(statusHeight + navHeight) make.height.equalTo(kTootSegmentViewHeight) } tableView.snp.makeConstraints { (make) in make.left.bottom.right.equalToSuperview() make.top.equalTo(segView.snp.bottom) } } func setupSegView(titles:[String]){ segView = SegMenu(frame: CGRect(x: 0, y: kNavgationBarHeight, width: UIScreen.main.bounds.size.width, height: 40), WithTitles: titles) segView.menuDelegate = self view.addSubview(segView) } func titleView(title:String) -> Void { if let _ = self.titleView { self.titleView?.setTitle(title, for: .normal) let size = self.titleView?.titleLabel?.sizeThatFits(CGSize(width: CGFloat.greatestFiniteMagnitude, height: 21)) ?? CGSize.zero let titleSize = self.titleView?.sizeThatFits(CGSize(width: CGFloat.greatestFiniteMagnitude, height: 21)) ?? CGSize.zero self.titleView?.frame = CGRect(x: 0, y: 0, width: titleSize.width, height: 21) self.titleView?.titleEdgeInsets = UIEdgeInsets(top: 0, left: -(titleSize.width - size.width), bottom: 0, right: 0) self.titleView?.imageEdgeInsets = UIEdgeInsets(top: 0, left: size.width + 6, bottom: 0, right: 0) return } let titleView = UIButton(type: .custom) titleView.frame = CGRect(x: 0, y: 0, width: 200, height: 21) titleView.setImage(UIImage(named: "c_arrow_down"), for: .normal) titleView.setTitle(title, for: .normal) let size = titleView.titleLabel?.sizeThatFits(CGSize(width: CGFloat.greatestFiniteMagnitude, height: 21)) ?? CGSize.zero let titleSize = titleView.sizeThatFits(CGSize(width: CGFloat.greatestFiniteMagnitude, height: 21)) titleView.frame = CGRect(x: 0, y: 0, width: titleSize.width, height: 21) titleView.titleEdgeInsets = UIEdgeInsets(top: 0, left: -(titleSize.width - size.width), bottom: 0, right: 0) titleView.imageEdgeInsets = UIEdgeInsets(top: 0, left: size.width + 6, bottom: 0, right: 0) titleView.setTitleColor(UIColor.black, for: .normal) titleView.addTarget(self, action: #selector(titleViewAction(btn:)), for: .touchUpInside) self.titleView = titleView self.navigationItem.titleView = self.titleView } @objc func titleViewAction(btn:UIButton){ let filterVC = ZSFilterThemeViewController() filterVC.clickAction = { (index:Int,title:String,name:String) in self.presenter?.didClickFilter(index: index, title: title, name: name) } self.navigationController?.pushViewController(filterVC, animated: true) } func didSelectAtIndex(_ index:Int){ presenter?.didSelectSeg(index: index) } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return booksModel?.count ?? 0 > 0 ? booksModel!.count:0 } func numberOfSections(in tableView: UITableView) -> Int { return 1 } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell:ThemeTopicCell? = tableView.qs_dequeueReusableCell(ThemeTopicCell.self) cell?.backgroundColor = UIColor.white cell?.selectionStyle = .none cell!.model = booksModel?.count ?? 0 > indexPath.row ? booksModel![indexPath.row]:nil return cell! } func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { return tableView.sectionHeaderHeight } func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { return tableView.sectionHeaderHeight } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { if let models = booksModel { presenter?.didSelectAt(indexPath: indexPath, models: models) } } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } func showThemeTopic(models: [ThemeTopicModel]) { booksModel = models self.tableView.reloadData() } func showTitle(title: String, index: Int, tag: String, gender: String) { self.titleView(title: title) presenter?.didSelectTitle(index: index, tag: tag, gender: gender) } func showSeg(titles:[String]){ setupSegView(titles: titles) } } ================================================ FILE: zhuishushenqi/RightSide/Topic/Controllers/TopicDetail/QSTopicDetailInteractor.swift ================================================ // // QSTopicDetailInteractor.swift // zhuishushenqi // // Created yung on 2017/4/20. // Copyright © 2017年 QS. All rights reserved. // // Template generated by Juanpe Catalán @JuanpeCMiOS // import UIKit import ZSAPI class QSTopicDetailInteractor: QSTopicDetailInteractorProtocol { var output: QSTopicDetailInteractorOutputProtocol? var id:String = "" var title:String = "主题书单" func requestDetail(){ // http://api.zhuishushenqi.com/book-list/58b782f5a7674a5f67618731 let api = ZSAPI.themeDetail(key: id) zs_get(api.path, parameters: nil) { (response) in QSLog(response) if let bookList = response?["bookList"] as? [AnyHashable : Any], let books = (response?["bookList"] as AnyObject).object(forKey:"books"){ if let headerModel = TopicDetailHeader.deserialize(from: bookList as? [String:Any]) ,let booksModel = [TopicDetailModel].deserialize(from: books as? [Any]) as? [TopicDetailModel] { self.output?.fetchListSuccess(list: booksModel, header: headerModel) } else { self.output?.fetchListFailed() } }else{ self.output?.fetchListFailed() } } } func showTitle(){ self.output?.showTitle(title: title) } } ================================================ FILE: zhuishushenqi/RightSide/Topic/Controllers/TopicDetail/QSTopicDetailPresenter.swift ================================================ // // QSTopicDetailPresenter.swift // zhuishushenqi // // Created yung on 2017/4/20. // Copyright © 2017年 QS. All rights reserved. // // Template generated by Juanpe Catalán @JuanpeCMiOS // import UIKit class QSTopicDetailPresenter: QSTopicDetailPresenterProtocol { weak var view: QSTopicDetailViewProtocol? let interactor: QSTopicDetailInteractorProtocol let router: QSTopicDetailWireframeProtocol init(interface: QSTopicDetailViewProtocol, interactor: QSTopicDetailInteractorProtocol, router: QSTopicDetailWireframeProtocol) { self.view = interface self.interactor = interactor self.router = router } func viewDidLoad(){ interactor.requestDetail() interactor.showTitle() view?.showActivityView() } func didSelectAt(indexPath:IndexPath,models:[TopicDetailModel]){ router.presentDetail(indexPath: indexPath, models: models) } } extension QSTopicDetailPresenter:QSTopicDetailInteractorOutputProtocol{ func fetchListSuccess(list: [TopicDetailModel], header: TopicDetailHeader) { view?.hideActivityView() view?.showList(list: list, header: header) } func fetchListFailed() { view?.hideActivityView() } func showTitle(title:String){ view?.showTitle(title: title) } } ================================================ FILE: zhuishushenqi/RightSide/Topic/Controllers/TopicDetail/QSTopicDetailProtocols.swift ================================================ // // QSTopicDetailProtocols.swift // zhuishushenqi // // Created yung on 2017/4/20. // Copyright © 2017年 QS. All rights reserved. // // Template generated by Juanpe Catalán @JuanpeCMiOS // import Foundation //MARK: Wireframe - protocol QSTopicDetailWireframeProtocol: class { func presentDetail(indexPath:IndexPath,models:[TopicDetailModel]) } //MARK: Presenter - protocol QSTopicDetailPresenterProtocol: class { func viewDidLoad() func didSelectAt(indexPath:IndexPath,models:[TopicDetailModel]) } //MARK: Interactor - protocol QSTopicDetailInteractorProtocol: class { func requestDetail() func showTitle() } //MARK: Output - protocol QSTopicDetailInteractorOutputProtocol: class { func fetchListSuccess(list:[TopicDetailModel],header:TopicDetailHeader) func fetchListFailed() func showTitle(title:String) } //MARK: View - protocol QSTopicDetailViewProtocol: IndicatableView { var presenter: QSTopicDetailPresenterProtocol? { get set } func showList(list:[TopicDetailModel],header:TopicDetailHeader) func showEmpty() func showTitle(title:String) } ================================================ FILE: zhuishushenqi/RightSide/Topic/Controllers/TopicDetail/QSTopicDetailRouter.swift ================================================ // // QSTopicDetailRouter.swift // zhuishushenqi // // Created yung on 2017/4/20. // Copyright © 2017年 QS. All rights reserved. // // Template generated by Juanpe Catalán @JuanpeCMiOS // import UIKit class QSTopicDetailRouter: QSTopicDetailWireframeProtocol { weak var viewController: UIViewController? static func createModule(id:String) -> UIViewController { // Change to get view from storyboard if not using progammatic UI let view = QSTopicDetailViewController(nibName: nil, bundle: nil) let interactor = QSTopicDetailInteractor() let router = QSTopicDetailRouter() let presenter = QSTopicDetailPresenter(interface: view, interactor: interactor, router: router) view.presenter = presenter interactor.output = presenter router.viewController = view interactor.id = id return view } func presentDetail(indexPath:IndexPath,models:[TopicDetailModel]){ if indexPath.section > 0 { let model = models[indexPath.section - 1] viewController?.navigationController?.pushViewController(QSBookDetailRouter.createModule(id: model.book._id), animated: true) } } } ================================================ FILE: zhuishushenqi/RightSide/Topic/Controllers/TopicDetail/QSTopicDetailViewController.swift ================================================ // // QSTopicDetailViewController.swift // zhuishushenqi // // Created yung on 2017/4/20. // Copyright © 2017年 QS. All rights reserved. // // Template generated by Juanpe Catalán @JuanpeCMiOS // import UIKit class QSTopicDetailViewController: BaseViewController ,UITableViewDataSource,UITableViewDelegate, QSTopicDetailViewProtocol { var presenter: QSTopicDetailPresenterProtocol? private var booksModel:[TopicDetailModel] = [] private var headerModel:TopicDetailHeader? private lazy var tableView:UITableView = { let tableView = UITableView(frame: CGRect(x: 0, y: 64, width: ScreenWidth, height: ScreenHeight - 64), style: .grouped) tableView.dataSource = self tableView.delegate = self tableView.sectionHeaderHeight = CGFloat.leastNormalMagnitude tableView.sectionFooterHeight = 10 tableView.rowHeight = 93 tableView.qs_registerCellNib(TopicDetailCell.self) tableView.qs_registerCellNib(TopicDetailHeaderCell.self) return tableView }() override func viewDidLoad() { super.viewDidLoad() view.addSubview(tableView) presenter?.viewDidLoad() self.automaticallyAdjustsScrollViewInsets = false } func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { if indexPath.section == 0{ return TopicDetailHeaderCell.height(model: headerModel) } return TopicDetailCell.height(models: booksModel, indexPath: indexPath) } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return 1 } func numberOfSections(in tableView: UITableView) -> Int { return booksModel.count + 1 } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { if indexPath.section == 0 { let headerCell:TopicDetailHeaderCell? = tableView.qs_dequeueReusableCell(TopicDetailHeaderCell.self) headerCell?.backgroundColor = UIColor.white headerCell?.selectionStyle = .none headerCell?.model = headerModel return headerCell! } let cell:TopicDetailCell? = tableView.qs_dequeueReusableCell(TopicDetailCell.self) cell?.backgroundColor = UIColor.white cell?.selectionStyle = .none cell!.model = booksModel.count > (indexPath.section - 1) ? booksModel[indexPath.section - 1]:nil return cell! } func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { return tableView.sectionHeaderHeight } func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { return tableView.sectionFooterHeight } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { presenter?.didSelectAt(indexPath: indexPath, models: booksModel) } func showList(list: [TopicDetailModel], header: TopicDetailHeader) { self.booksModel = list self.headerModel = header self.tableView.reloadData() } func showEmpty() { } func showTitle(title: String) { self.title = title } } ================================================ FILE: zhuishushenqi/RightSide/Topic/Controllers/TopicDetail/TopicDetailViewController.swift ================================================ // // TopicDetailViewController.swift // zhuishushenqi // // Created by Nory Cao on 2017/3/9. // Copyright © 2017年 QS. All rights reserved. // import UIKit class TopicDetailViewController: BaseViewController ,SegMenuDelegate,UITableViewDataSource,UITableViewDelegate{ var id:String? = "" var books:NSArray? = [] fileprivate var booksModel:NSArray? = [] fileprivate var headerModel:TopicDetailHeader? fileprivate lazy var tableView:UITableView = { let tableView = UITableView(frame: CGRect(x: 0, y: 64, width: ScreenWidth, height: ScreenHeight - 64), style: .grouped) tableView.dataSource = self tableView.delegate = self tableView.sectionHeaderHeight = CGFloat.leastNormalMagnitude tableView.sectionFooterHeight = 10 tableView.rowHeight = 93 tableView.qs_registerCellNib(TopicDetailCell.self) tableView.qs_registerCellNib(TopicDetailHeaderCell.self) return tableView }() override func viewDidLoad() { super.viewDidLoad() title = "主题书单" requestDetail() self.automaticallyAdjustsScrollViewInsets = false } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) } fileprivate func requestDetail(){ // http://api.zhuishushenqi.com/book-list/58b782f5a7674a5f67618731 let urlString = "\(BASEURL)/book-list/\(self.id ?? "")" // QSNetwork.setDefaultURL(url: BASEURL) zs_get(urlString, parameters: nil) { (response) in QSLog(response) if let bookList = response?["bookList"] as? [AnyHashable : Any] { self.headerModel = TopicDetailHeader.deserialize(from: bookList as! [String:Any]) } if let books = (response?["bookList"] as AnyObject).object(forKey:"books") { if let models = [TopicDetailModel].deserialize(from: books as? [Any]) as NSArray? { self.booksModel = models } } DispatchQueue.main.async { self.view.addSubview(self.tableView) self.tableView.reloadData() } } } func didSelectAtIndex(_ index:Int){ } func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { if let headermodel:TopicDetailHeader = self.headerModel { if indexPath.section == 0 { if headermodel.descHeight > 45 { return headermodel.descHeight - 45 + 176 }else{ return 176 - (45 - headermodel.descHeight) } } } if let model:TopicDetailModel = self.booksModel?[indexPath.section - 1] as? TopicDetailModel { if model.commentHeight > 32 { return model.commentHeight - 32 + 108 } } return 108 } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return 1 } func numberOfSections(in tableView: UITableView) -> Int { return booksModel?.count ?? 0 > 0 ? (booksModel!.count + 1):1 } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { if indexPath.section == 0 { let headerCell:TopicDetailHeaderCell? = tableView.qs_dequeueReusableCell(TopicDetailHeaderCell.self) headerCell?.backgroundColor = UIColor.white headerCell?.selectionStyle = .none headerCell?.model = headerModel return headerCell! } let cell:TopicDetailCell? = tableView.qs_dequeueReusableCell(TopicDetailCell.self) cell?.backgroundColor = UIColor.white cell?.selectionStyle = .none cell!.model = booksModel?.count ?? 0 > indexPath.section ? booksModel![indexPath.section - 1] as? TopicDetailModel:nil return cell! } func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { return tableView.sectionHeaderHeight } func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { return tableView.sectionFooterHeight } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { if indexPath.section > 0 { let bookDetailVC = BookDetailViewController() let book:TopicDetailModel? = booksModel![indexPath.section - 1] as? TopicDetailModel bookDetailVC.id = book?.book._id ?? "" self.navigationController?.pushViewController(bookDetailVC, animated: true) } } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } } ================================================ FILE: zhuishushenqi/RightSide/Topic/Models/ThemeTopicModel.swift ================================================ // // ThemeTopicModel.swift // zhuishushenqi // // Created by Nory Cao on 2017/3/9. // Copyright © 2017年 QS. All rights reserved. // import UIKit import HandyJSON @objc(ThemeTopicModel) class ThemeTopicModel: NSObject,HandyJSON { var _id:String = "" var title:String = "" var author:String = "" var desc:String = "" var gender:String = "" var collectorCount:Int = 0 var cover:String = "" var bookCount:Int = 0 required override init() { } } ================================================ FILE: zhuishushenqi/RightSide/Topic/Models/TopicDetailHeader.swift ================================================ // // TopicDetailHeader.swift // zhuishushenqi // // Created by Nory Cao on 2017/3/10. // Copyright © 2017年 QS. All rights reserved. // //bookList": { //"_id": "58ba10c10a121c9755fc7d4d", //"updated": "2017-03-04T00:56:34.011Z", //"title": "【女频】BOSS攻略最TOP", //"author": { // "_id": "56c86c06e8a5daac0aa25819", // "avatar": "/avatar/db/4e/db4e2af927b6f744b75268982afd5acd", // "nickname": "桑桑(燃桑)", // "type": "author", // "lv": 8 //}, //"desc": "渣文虐我,推几本男女主不小白,剧情不狗血,文笔不弱智,有深度的爽文,双洁1V1,总裁深深宠,女主虐渣渣,一起爽歪歪~\r\n\r\n主推总裁高干", //"gender": "female", //"created": "2017-03-04T00:56:33.996Z", //"tags": ["豪门世家", "现代言情", "总裁"], //"stickStopTime": null, //"isDraft": false, //"isDistillate": true, //"collectorCount": 703, import UIKit import HandyJSON @objc(TopicDetailHeader) class TopicDetailHeader: NSObject ,HandyJSON{ var _id:String = "" var updated:String = "" var title:String = "" var author:TopicDetailAuthor = TopicDetailAuthor() var desc:String = ""{ didSet{ let width = UIScreen.main.bounds.width let height = desc.qs_height(13, width: width - 22) descHeight = height } } var gender:String = "" var created:String = "" var tags:NSArray = [String]() as NSArray var stickStopTime:String = "" var isDraft:Bool = false var isDistillate:Bool = false var collectorCount:Int = 0 var descHeight:CGFloat = 0.0 required override init() { } } @objc(TopicDetailAuthor) class TopicDetailAuthor: NSObject ,HandyJSON{ var _id:String = "" var avatar:String = "" var nickname:String = "" var type:String = "" var lv:Int = 0 required override init() { } } ================================================ FILE: zhuishushenqi/RightSide/Topic/Models/TopicDetailModel.swift ================================================ // // TopicDetailModel.swift // zhuishushenqi // // Created by Nory Cao on 2017/3/9. // Copyright © 2017年 QS. All rights reserved. // import UIKit import HandyJSON @objc(TopicDetailModel) class TopicDetailModel: NSObject ,HandyJSON{ var book:TopicDetailBook = TopicDetailBook() var comment:String = "" { didSet{ let width = UIScreen.main.bounds.width - 46 let height = comment.qs_height(11, width: width) self.commentHeight = height + 15 } } var commentHeight:CGFloat = 0.0 required override init() { } } @objc(TopicDetailBook) class TopicDetailBook: NSObject,HandyJSON { var book:String = "" var _id:String = "" var author:String = "" var cover:String = "" var longIntro:String = "" var title:String = "" var site:String = "" var cat:String = "" var majorCate:String = "" var minorCate:String = "" var banned:Int = 0 var latelyFollower:Int = 0 var latelyFollowerBase:Int = 0 var wordCount:Int = 0 var minRetentionRatio:Int = 0 var retentionRatio:Float = 0.0 required override init() { } } // "book": { // "_id": "50adada05da9cfc66300007a", // "author": "黄金战士", // "cover": "/agent/http://image.cmfu.com/books/1321240/1321240.jpg", // "longIntro": "重生不可怕,就怕重生妖孽化。 // 林风重生回到1999年,这一年,网络和电子商务刚刚兴起,网络游戏逐渐进入玩家眼界。而林风的重生人生,将从打造他的游戏帝国开始。 // 《传奇》在他手中成为真正的传奇,收购3DO,吞并暴雪,强购“KONAMI”,让“第二世界”成为世界最火爆的游戏公司。 // 再踏足英超,和切尔西比富,和曼城比有钱,打造全新的《冠军教父》。 // 而这只是林风妖孽人生的开始... // ", // "title": "重生之妖孽人生", // "site": "zhuishuvip", // "cat": "都市", // "majorCate": "都市", // "minorCate": "都市生活", // "banned": 0, // "latelyFollower": 8561, // "latelyFollowerBase": 0, // "wordCount": 22731216, // "minRetentionRatio": 0, // "retentionRatio": 45.96 //}, //"comment": "其实我在14年就知道唐纳德要当总统,别问我为什么,我不会告诉你是在那时候看了这本书,作者预言帝不解释" ================================================ FILE: zhuishushenqi/RightSide/Topic/Models/ZSFilterThemeModel.swift ================================================ // // ZSFilterThemeModel.swift // zhuishushenqi // // Created by caony on 2019/3/22. // Copyright © 2019年 QS. All rights reserved. // import Foundation import HandyJSON struct ZSFilterThemeModel: HandyJSON { var name:String = "" var tags:[String] = [] init () { } } ================================================ FILE: zhuishushenqi/RightSide/Topic/ViewModel/ZSFilterThemeViewModel.swift ================================================ // // ZSFilterThemeViewModel.swift // zhuishushenqi // // Created by caony on 2019/3/22. // Copyright © 2019年 QS. All rights reserved. // import Foundation import ZSAPI class ZSFilterThemeViewModel { var items:[ZSFilterThemeModel] = [] func request(_ handler:ZSBaseCallback?) { let api = ZSAPI.tagType("" as AnyObject) zs_get(api.path, parameters: nil) { (response) in guard let books = response?["data"] as? [Any] else { handler?(nil) return } if let items = [ZSFilterThemeModel].deserialize(from: books) as? [ZSFilterThemeModel] { self.items = [] var item = ZSFilterThemeModel() item.name = "" item.tags = ["全部书单"] self.items.append(item) var itemGender = ZSFilterThemeModel() itemGender.name = "性别" itemGender.tags = ["男","女"] self.items.append(itemGender) self.items.append(contentsOf: items) } handler?(nil) } } } ================================================ FILE: zhuishushenqi/RightSide/Topic/ViewModel/ZSThemeTopicViewModel.swift ================================================ // // QSCatalogInteractor.swift // zhuishushenqi // // Created yung on 2017/4/21. // Copyright © 2017年 QS. All rights reserved. // // Template generated by Juanpe Catalán @JuanpeCMiOS // class ZSThemeTopicViewModel { var sort:String = "" var duration:String = "" func request() { } } ================================================ FILE: zhuishushenqi/RightSide/Topic/Views/ThemeTopicCell.swift ================================================ // // TopDetailCell.swift // zhuishushenqi // // Created by Nory Cao on 16/10/4. // Copyright © 2016年 QS. All rights reserved. // import UIKit class ThemeTopicCell: UITableViewCell { @IBOutlet weak var persueWidth: NSLayoutConstraint! @IBOutlet weak var remain: UILabel! @IBOutlet weak var reading: UILabel! @IBOutlet weak var profile: UILabel! @IBOutlet weak var name: UILabel! @IBOutlet weak var author: UILabel! @IBOutlet weak var icon: UIImageView! var model:ThemeTopicModel?{ didSet{ self.remain.text = "\(self.model?.collectorCount ?? 0)人收藏" self.reading.text = "共\(self.model?.bookCount ?? 0) 本书" self.profile.text = "\(self.model?.desc ?? "")" // self.type.text = "\(self.model?.cat ?? "")" self.name.text = "\(self.model?.title ?? "")" self.author.text = "\(self.model?.author ?? "")" self.author.text = "\(self.model?.author ?? "")" let width = (self.reading.text ?? "").qs_width(UIFont.systemFont(ofSize: 11), height: 21) self.persueWidth.constant = width + 5 let urlString = "\(IMAGE_BASEURL)\(self.model?.cover ?? "qqqqqqqq")" self.icon.qs_setBookCoverWithURLString(urlString: urlString) } } override func awakeFromNib() { super.awakeFromNib() // Initialization code } override func setSelected(_ selected: Bool, animated: Bool) { super.setSelected(selected, animated: animated) // Configure the view for the selected state } } ================================================ FILE: zhuishushenqi/RightSide/Topic/Views/ThemeTopicCell.xib ================================================ ================================================ FILE: zhuishushenqi/RightSide/Topic/Views/TopicDetailCell.swift ================================================ // // TopicDetailCell.swift // zhuishushenqi // // Created by Nory Cao on 2017/3/9. // Copyright © 2017年 QS. All rights reserved. // import UIKit class TopicDetailCell: UITableViewCell { @IBOutlet weak var lineHeight: NSLayoutConstraint! @IBOutlet weak var icon: UIImageView! @IBOutlet weak var name: UILabel! @IBOutlet weak var author: UILabel! @IBOutlet weak var type: UILabel! @IBOutlet weak var reading: UILabel! @IBOutlet weak var comment: UILabel! @IBOutlet weak var typeWidth: NSLayoutConstraint! @IBOutlet weak var authorWidth: NSLayoutConstraint! @IBOutlet weak var commentHeight: NSLayoutConstraint! var model:TopicDetailModel?{ didSet{ self.name.text = "\(model?.book.title ?? "")" self.author.text = "\(model?.book.author ?? "")" let authorWidthS = (self.author.text ?? "").qs_width(UIFont.systemFont(ofSize: 11), height: 21) authorWidth.constant = authorWidthS + 5 self.type.text = "\(model?.book.majorCate ?? "")" let typeWidthS = (self.type.text ?? "").qs_width(UIFont.systemFont(ofSize: 11), height: 21) typeWidth.constant = typeWidthS + 5 self.reading.text = "\(model?.book.latelyFollower ?? 0)人在追" self.comment.text = "\(model?.comment ?? "")" self.commentHeight.constant = model?.commentHeight ?? 0 + 10 self.lineHeight.constant = 0.5 if self.model?.book.cover == "" { return; } let urlString = "\(IMAGE_BASEURL)\(self.model?.book.cover ?? "qqqqqqqq")" self.icon.qs_setBookCoverWithURLString(urlString: urlString) } } static func height(models:[TopicDetailModel],indexPath:IndexPath)->CGFloat{ let baseCellHeight:CGFloat = 108 let baseCellTextHeight:CGFloat = 25 let model:TopicDetailModel = models[indexPath.section - 1] if model.commentHeight > baseCellTextHeight { let height = model.commentHeight - baseCellTextHeight + baseCellHeight return height } return baseCellHeight } override func awakeFromNib() { super.awakeFromNib() // Initialization code } override func setSelected(_ selected: Bool, animated: Bool) { super.setSelected(selected, animated: animated) // Configure the view for the selected state } } ================================================ FILE: zhuishushenqi/RightSide/Topic/Views/TopicDetailCell.xib ================================================ ================================================ FILE: zhuishushenqi/RightSide/Topic/Views/TopicDetailHeaderCell.swift ================================================ // // TopicDetailHeaderCell.swift // zhuishushenqi // // Created by Nory Cao on 2017/3/10. // Copyright © 2017年 QS. All rights reserved. // import UIKit class TopicDetailHeaderCell: UITableViewCell { @IBOutlet weak var icon: UIImageView! @IBOutlet weak var name: UILabel! @IBOutlet weak var updateTime: UILabel! @IBOutlet weak var title: UILabel! @IBOutlet weak var content: UILabel! @IBOutlet weak var iconRect: UIImageView! @IBOutlet weak var bottomName: UILabel! @IBOutlet weak var share: UIButton! @IBOutlet weak var contentHeight: NSLayoutConstraint! var model:TopicDetailHeader? { didSet{ self.isHidden = false self.name.text = "\(model?.author.nickname ?? "") lv.\(model?.author.lv ?? 0)" // self.updateTime.text = "" self.title.text = "\(model?.title ?? "")" self.content.text = "\(model?.desc ?? "")" self.contentHeight.constant = model?.descHeight ?? 0 self.bottomName.text = "\(model?.author.nickname ?? "")" self.iconRect.layer.cornerRadius = self.iconRect.bounds.width/2 if self.model?.author.avatar == "" { return; } let urlString = "\(IMAGE_BASEURL)\(self.model?.author.avatar ?? "qqqqqqqq")" let url = URL(string: urlString) if let urlstring = url { let resource:QSResource = QSResource(url: urlstring) self.icon.kf.setImage(with: resource, placeholder: UIImage(named: "default_avatar_light")) self.iconRect.kf.setImage(with: resource, placeholder: UIImage(named: "default_avatar_light")) } } } static func height(model:TopicDetailHeader?)->CGFloat{ if let header = model { let baseHeaderHeight:CGFloat = 176 let baseHeaderTextHeight:CGFloat = 45 let headerCell = UINib(nibName: "TopicDetailHeaderCell", bundle: nil).instantiate(withOwner: nil, options: nil).last as? TopicDetailHeaderCell headerCell?.model = header if header.descHeight > baseHeaderTextHeight { return header.descHeight - baseHeaderTextHeight + baseHeaderHeight }else{ return baseHeaderHeight - (baseHeaderTextHeight - header.descHeight) } } return 0.0001 } override func awakeFromNib() { super.awakeFromNib() // Initialization code self.isHidden = true } override func setSelected(_ selected: Bool, animated: Bool) { super.setSelected(selected, animated: animated) // Configure the view for the selected state } } ================================================ FILE: zhuishushenqi/RightSide/Topic/Views/TopicDetailHeaderCell.xib ================================================ ================================================ FILE: zhuishushenqi/RightSide/Topic/Views/ZSFilterThemeCell.swift ================================================ // // ZSFilterThemeCell.swift // zhuishushenqi // // Created by caony on 2019/3/22. // Copyright © 2019年 QS. All rights reserved. // import Foundation import UIKit class ZSFilterThemeCell: UICollectionViewCell { var titleLabel:UILabel! override init(frame: CGRect) { super.init(frame: frame) setupSubviews() } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func layoutSubviews() { super.layoutSubviews() titleLabel.frame = contentView.bounds } private func setupSubviews() { titleLabel = UILabel(frame: CGRect.zero) titleLabel.textAlignment = .center titleLabel.textColor = UIColor.gray titleLabel.font = UIFont.systemFont(ofSize: 15) contentView.addSubview(titleLabel) } } ================================================ FILE: zhuishushenqi/Root/Controllers/DynamicViewController.swift ================================================ // // DynamicViewController.swift // zhuishushenqi // // Created by Nory Cao on 16/10/22. // Copyright © 2016年 QS. All rights reserved. // import UIKit class DynamicViewController: BaseViewController,UITableViewDataSource,UITableViewDelegate ,Refreshable{ var timeline:[QSHotModel]? fileprivate var segment:UISegmentedControl = { let seg = UISegmentedControl(frame: CGRect.zero) seg.insertSegment(withTitle: "动态", at: 0, animated: false) seg.insertSegment(withTitle: "热门", at: 1, animated: false) seg.insertSegment(withTitle: "我的", at: 2, animated: false) seg.tintColor = UIColor.red seg.backgroundColor = UIColor.clear seg.selectedSegmentIndex = 1 return seg }() fileprivate lazy var tableView:UITableView = { let tableView = UITableView(frame: CGRect(x: 0, y: kNavgationBarHeight + 40, width: ScreenWidth, height: ScreenHeight - kNavgationBarHeight - 40), style: .grouped) tableView.dataSource = self tableView.delegate = self tableView.separatorStyle = .singleLine tableView.estimatedRowHeight = 140 tableView.rowHeight = 140 tableView.sectionHeaderHeight = 0.0001 tableView.sectionFooterHeight = 0.0001 tableView.backgroundColor = UIColor.clear return tableView }() override func viewDidLoad() { super.viewDidLoad() title = "动态" initRefreshHeader(tableView) { self.requestData() } tableView.qs_registerCellNib(DynamicCell.self) view.addSubview(segment) view.addSubview(tableView) requestData() } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(true) layoutSubview() tableView.reloadData() } override func viewWillLayoutSubviews() { layoutSubview() } func layoutSubview() { segment.snp.remakeConstraints { (make) in let statusHeight = UIApplication.shared.statusBarFrame.height let navHeight = self.navigationController?.navigationBar.height ?? 0 make.left.right.equalToSuperview() make.top.equalToSuperview().offset(navHeight + statusHeight + 5) make.height.equalTo(30) } tableView.snp.remakeConstraints { (make) in make.left.right.bottom.equalToSuperview() make.top.equalTo(segment.snp.bottom).offset(5) } } func requestData(){ self.showProgress() let urlString = "\(BASEURL)/user/twitter/hottweets" zs_get(urlString, parameters: nil) { (response) in self.hideProgress() if let hottweets = response?["tweets"] as? [Any] { if let time = [QSHotModel].deserialize(from: hottweets) as? [QSHotModel] { self.timeline = time self.tableView.reloadData() self.tableView.mj_header.endRefreshing() } } } } func numberOfSections(in tableView: UITableView) -> Int { return 1 } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return timeline?.count ?? 0 } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell:DynamicCell = tableView.qs_dequeueReusableCell(DynamicCell.self) if let model = self.timeline?[indexPath.row] { cell.setContent(model: model) } return cell } func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { return 0.0001 } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { //post为null // http://api.zhuishushenqi.com/user/twitter/58d14859d0693ae736034619/comments //post不为null,id从post中取 // http://api.zhuishushenqi.com/post/58d1d313bd7cc9961f93192d/comment?start=0&limit=50 tableView.deselectRow(at: indexPath, animated: true) let model = self.timeline?[indexPath.row] let comment = BookComment() comment._id = (model?.tweet.post._id) ?? "" let commentVC = ZSBookCommentViewController(style: .grouped) commentVC.viewModel.model = comment self.navigationController?.pushViewController(commentVC, animated: true) // let reviewVC = ZSBookReviewDetailViewController() // reviewVC.viewModel.model = comment // self.navigationController?.pushViewController(reviewVC, animated: true) } override var preferredStatusBarStyle : UIStatusBarStyle { return .default } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } } ================================================ FILE: zhuishushenqi/Root/Controllers/LeftViewController.swift ================================================ // // LeftViewController.swift // zhuishushenqi // // Created by Nory Cao on 16/9/16. // Copyright © 2016年 QS. All rights reserved. // import UIKit class LeftViewController: UIViewController,UITableViewDataSource,UITableViewDelegate,XYCActionSheetDelegate { var images = ["hsm_default_avatar","hsm_icon_1","hsm_icon_2","hsm_icon_3"] var selectedIndex = 1 override func viewDidLoad() { super.viewDidLoad() view.addSubview(tableView) view.backgroundColor = UIColor.brown } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) self.tableView.reloadData() } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() } func numberOfSections(in tableView: UITableView) -> Int { return 1 } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return images.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.qs_dequeueReusableCell(ZSLeftViewCell.self) cell?.backgroundColor = UIColor ( red: 0.16, green: 0.16, blue: 0.16, alpha: 1.0 ) cell?.selectionStyle = .none if indexPath.row == 0 { if ZSLogin.share.hasLogin() { cell?.iconView.qs_setAvatarWithURLString(urlString: ZSThirdLogin.share.userInfo?.user?.avatar ?? "") cell?.nameLabel.text = ZSThirdLogin.share.userInfo?.user?.nickname ?? "" } else { cell?.iconView.image = UIImage(named: images[indexPath.row]) cell?.nameLabel.text = "登录" } } else { cell?.iconView.image = UIImage(named: images[indexPath.row]) cell?.nameLabel.text = "" } if indexPath.row == selectedIndex { cell?.selectedView.isHidden = false } else { cell?.selectedView.isHidden = true } return cell! } func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { return 44 } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { selectedIndex = indexPath.row if indexPath.row == 0 { if ZSLogin.share.hasLogin() { let myVC = ZSMyViewController.init(style: .grouped) myVC.backHandler = { self.selectedIndex = 1 self.tableView.reloadData() } SideVC.closeSideViewController() SideVC.navigationController?.pushViewController(myVC, animated: true) } else { let loginVC = ZSLoginViewController() loginVC.backHandler = { self.selectedIndex = 1 self.tableView.reloadData() } loginVC.loginResultHandler = { success in NotificationCenter.qs_postNotification(name: LoginSuccess, obj: nil) } SideVC.closeSideViewController() SideVC.present(loginVC, animated: true, completion: nil) } }else{ SideVC.closeSideViewController() } self.tableView.reloadData() } func showShare(){ maskView.addSubview(shareActionSheet) KeyWindow?.addSubview(maskView) UIView.animate(withDuration: 0.35, animations: { self.shareActionSheet.frame = CGRect(x: 0, y: ScreenHeight - 200, width: ScreenWidth, height: 200) }, completion: { (finished) in }) } func didSelectedAtIndex(_ index:Int,sheet:XYCActionSheet){ SideVC.closeSideViewController() self.maskView.removeFromSuperview() self.tableView.reloadData() // let indexPath = IndexPath(row: 1, section: 0) // self.tableView.selectRow(at: indexPath, animated: true, scrollPosition: .middle) if index == 1 { ZSThirdLogin.share.successHandler = { self.tableView.reloadData() } ZSThirdLogin.share.QQAuth() self.shareActionSheet.removeFromSuperview() } else if index == 2 { ZSThirdLogin.share.successHandler = { self.tableView.reloadData() } ZSThirdLogin.share.WXAuth() self.shareActionSheet.removeFromSuperview() } if index == 3 { UIView.animate(withDuration: 0.35, animations: { sheet.frame = CGRect(x: 0, y: ScreenHeight, width: ScreenWidth, height: 200) }, completion: { (finished) in self.maskView.removeFromSuperview() self.shareActionSheet.removeFromSuperview() }) } } fileprivate lazy var tableView:UITableView = { let tableView = UITableView(frame: CGRect(x: 0, y: 0, width: ScreenWidth, height: ScreenHeight), style: .grouped) tableView.dataSource = self tableView.delegate = self tableView.separatorStyle = .none tableView.backgroundColor = UIColor(red: 0.211, green: 0.211, blue: 0.211, alpha: 1.00) tableView.qs_registerCellClass(ZSLeftViewCell.self) return tableView }() fileprivate lazy var maskView:UIView = { let maskView = UIView() maskView.frame = self.view.bounds maskView.backgroundColor = UIColor(white: 0.00, alpha: 0.2) return maskView }() fileprivate lazy var shareActionSheet:XYCActionSheet = { let showActionSheet = XYCActionSheet(frame: CGRect(x: 0, y: ScreenHeight, width: ScreenWidth, height: 200), titles: ["新浪微博账号登录","QQ账号登录","微信登录"]) showActionSheet.delegate = self return showActionSheet }() } ================================================ FILE: zhuishushenqi/Root/Controllers/LookBookViewController.swift ================================================ // // LookBookViewController.swift // zhuishushenqi // // Created by yung on 2017/6/18. // Copyright © 2017年 QS. All rights reserved. // import UIKit import MJRefresh import RxSwift import SnapKit protocol ZSDiscussCellProtocol { func configureCell(with model:Any?) } typealias ZSDiscussHandler = ()->Void class ZSDiscussTestViewController: ZSDiscussBaseViewController { private var _viewModel:ZSDiscussBaseViewModel = ZSDiscussBaseViewModel() override var viewModel: ZSDiscussViewModelProtocol? { return _viewModel } override var titles: [[String]] { return [["全部","精品"],["默认排序","最新发布","最多评论"]] } override var headerRefreshHandler: ZSDiscussHandler? { return { self.viewModel?.fetchDiscuss({ (_) in self.tableView.reloadData() }) } } override var footerRefreshHandler: ZSDiscussHandler? { return { self.viewModel?.fetchMoreDiscuss({ (_) in self.tableView.reloadData() }) } } override func registerCellNibs() -> Array { return [QSHelpViewCell.self] } override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { return 97 } override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { if let model = viewModel?.models[indexPath.row] { let commentVC = ZSBookCommentViewController(style: .grouped) commentVC.viewModel.model = model as? BookComment self.navigationController?.pushViewController(commentVC, animated: true) } } } class ZSDiscussBaseViewController:BaseViewController,UITableViewDataSource,UITableViewDelegate,Refreshable { var selectIndexs:[Int] = [] var block:String = "girl" { didSet{ // self.viewModel?.block = block } } var viewModel:ZSDiscussViewModelProtocol? { return nil } var headerRefresh:MJRefreshHeader? var footerRefresh:MJRefreshFooter? // new var selectionView:ZSMultiSelectionView! fileprivate let disposeBag = DisposeBag() lazy var tableView:UITableView = { let tableView = UITableView(frame: CGRect(x: 0, y: kNavgationBarHeight + 40, width: ScreenWidth, height: ScreenHeight - kNavgationBarHeight - 40), style: .grouped) tableView.dataSource = self tableView.delegate = self tableView.sectionHeaderHeight = CGFloat.leastNormalMagnitude tableView.sectionFooterHeight = CGFloat.leastNormalMagnitude tableView.rowHeight = UITableView.automaticDimension tableView.estimatedRowHeight = 97 return tableView }() /// 标题,子类实现 public var titles:[[String]] { return [[]] } /// 头部刷新事件,子类实现 var headerRefreshHandler:ZSDiscussHandler? { return nil } /// 尾部刷新事件,子类实现 var footerRefreshHandler:ZSDiscussHandler? { return nil } override func viewDidLoad() { super.viewDidLoad() register() initSubview() } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) self.layoutSubviews() } override func viewDidDisappear(_ animated: Bool) { super.viewDidDisappear(animated) self.selectionView.hideSelection() } override func viewWillLayoutSubviews() { layoutSubviews() } private func layoutSubviews() { selectionView.snp.remakeConstraints { (make) in let statusHeight = UIApplication.shared.statusBarFrame.height let navHeight = self.navigationController?.navigationBar.height ?? 0 make.top.equalTo(statusHeight + navHeight) make.left.right.equalToSuperview() make.height.equalTo(40) } tableView.snp.remakeConstraints { (make) in make.left.right.bottom.equalToSuperview() make.top.equalTo(selectionView.snp.bottom) } } private func register(){ let classes = registerCellClasses() for cls in classes { if cls is UITableViewCell.Type { self.tableView.qs_registerCellClass(cls as! UITableViewCell.Type) } } let nibs = registerCellNibs() for cls in nibs { if cls is UITableViewCell.Type { self.tableView.qs_registerCellNib(cls as! UITableViewCell.Type) } } } func qs_equal(x:T,y:T)->Bool{ return x == y } func initSubview(){ selectionView = ZSMultiSelectionView(frame: CGRect(x: 0, y: kNavgationBarHeight, width: self.view.bounds.width, height: 40)) selectionView.delegate = self view.addSubview(selectionView) for _ in titles { selectIndexs.append(0) } viewModel?.updateSelectSectionIndexs(indexs: selectIndexs) view.addSubview(self.tableView) let header = initRefreshHeader(tableView) { self.headerRefreshHandler?() } let footer = initRefreshFooter(tableView) { self.footerRefreshHandler?() } headerRefresh = header footerRefresh = footer headerRefresh?.beginRefreshing() viewModel?.autoSetRefreshHeaderStatus(header: header, footer: footer).disposed(by: disposeBag) } func fetchHelp(_ selectIndexs:[Int]){ // viewModel?.fetchDiscuss(selectIndexs: selectIndexs) { (comments) in // self.tableView.reloadData() // if self.viewModel?.models.count ?? 0 > 0 { // let indexPath = IndexPath(row: 0, section: 0) // self.tableView.scrollToRow(at: indexPath, at: UITableView.ScrollPosition.none, animated: false) // } // } } //MARK: - UITableViewDataSource func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return viewModel?.models.count ?? 0 } func numberOfSections(in tableView: UITableView) -> Int { return 1 } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell:QSHelpViewCell? = tableView.qs_dequeueReusableCell(QSHelpViewCell.self) cell?.backgroundColor = UIColor.white cell?.selectionStyle = .none cell?.configureCell(with: viewModel?.models[indexPath.row]) return cell! } func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { return tableView.sectionHeaderHeight } func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { return tableView.sectionHeaderHeight } func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { return 97 } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { } /// 注册cell的方法,子类实现 func registerCellClasses() ->Array { return [] } func registerCellNibs() ->Array { return [] } } extension ZSDiscussBaseViewController:ZSMultiSelectionDelegate { func numberOfSections(in multiSelectionView: ZSMultiSelectionView) -> Int { return titles.count } func multiSelectionView(_ multiSelectionView: ZSMultiSelectionView, numberOfRowsInSection section: Int) -> Int { return titles[section].count } func multiSelectionView(_ multiSelectionView: ZSMultiSelectionView, titleForRowAt indexPath: IndexPath) -> String { return titles[indexPath.section][indexPath.row] } func multiSelectionView(_ multiSelectionView: ZSMultiSelectionView, titleForHeaderIn section: Int) -> String { return titles[section][0] } @objc func multiSelectionView(_ multiSelectionView: ZSMultiSelectionView, didSelectAt indexPath: IndexPath) { selectIndexs[indexPath.section] = indexPath.row viewModel?.updateSelectSectionIndexs(indexs: selectIndexs) viewModel?.fetchDiscuss({ (_) in self.tableView.reloadData() }) } } class ZSDiscussViewController:BaseViewController,UITableViewDataSource,UITableViewDelegate,Refreshable { var selectIndexs:[Int] = [] var viewModel:ZSDiscussViewModel = ZSDiscussViewModel() var block:String = "girl" { didSet{ self.viewModel.block = block } } var headerRefresh:MJRefreshHeader? var footerRefresh:MJRefreshFooter? // new var selectionView:ZSMultiSelectionView! fileprivate let disposeBag = DisposeBag() lazy var tableView:UITableView = { let tableView = UITableView(frame: CGRect(x: 0, y: kNavgationBarHeight + 40, width: ScreenWidth, height: ScreenHeight - kNavgationBarHeight - 40), style: .grouped) tableView.dataSource = self tableView.delegate = self tableView.sectionHeaderHeight = CGFloat.leastNormalMagnitude tableView.sectionFooterHeight = CGFloat.leastNormalMagnitude tableView.rowHeight = UITableView.automaticDimension tableView.estimatedRowHeight = 97 tableView.qs_registerCellNib(QSHelpViewCell.self) return tableView }() let titles = [["全部","精品"],["默认排序","最新发布","最多评论"]] override func viewDidLoad() { super.viewDidLoad() initSubview() } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) layoutSubviews() } override func viewWillLayoutSubviews() { layoutSubviews() } override func viewDidDisappear(_ animated: Bool) { super.viewDidDisappear(animated) self.selectionView.hideSelection() } private func layoutSubviews() { selectionView.snp.remakeConstraints { (make) in let statusHeight = UIApplication.shared.statusBarFrame.height let navHeight = self.navigationController?.navigationBar.height ?? 0 make.top.equalTo(statusHeight + navHeight) make.left.right.equalToSuperview() make.height.equalTo(40) } tableView.snp.remakeConstraints { (make) in make.left.right.bottom.equalToSuperview() make.top.equalTo(selectionView.snp.bottom) } } func qs_equal(x:T,y:T)->Bool{ return x == y } func initSubview(){ selectionView = ZSMultiSelectionView(frame: CGRect(x: 0, y: kNavgationBarHeight, width: self.view.bounds.width, height: 40)) selectionView.delegate = self view.addSubview(selectionView) selectIndexs = [0,0] fetchHelp(self.selectIndexs) view.addSubview(self.tableView) let header = initRefreshHeader(tableView) { self.viewModel.fetchDiscuss(selectIndexs: self.selectIndexs, completion: { (comments) in self.tableView.reloadData() }) } let footer = initRefreshFooter(tableView) { self.viewModel.fetchMore(selectIndexs: self.selectIndexs, completion: { (comments) in self.tableView.reloadData() }) } headerRefresh = header footerRefresh = footer headerRefresh?.beginRefreshing() viewModel.autoSetRefreshHeaderStatus(header: header, footer: footer).disposed(by: disposeBag) } func fetchHelp(_ selectIndexs:[Int]){ viewModel.fetchDiscuss(selectIndexs: selectIndexs) { (comments) in self.tableView.reloadData() if self.viewModel.models.count > 0 { let indexPath = IndexPath(row: 0, section: 0) self.tableView.scrollToRow(at: indexPath, at: UITableView.ScrollPosition.none, animated: false) } } } //MARK: - UITableViewDataSource func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return viewModel.models.count } func numberOfSections(in tableView: UITableView) -> Int { return 1 } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell:QSHelpViewCell? = tableView.qs_dequeueReusableCell(QSHelpViewCell.self) cell?.backgroundColor = UIColor.white cell?.selectionStyle = .none cell?.configureCell(model: viewModel.models[indexPath.row]) return cell! } func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { return tableView.sectionHeaderHeight } func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { return tableView.sectionHeaderHeight } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { let model = viewModel.models[indexPath.row] let commentVC = ZSBookCommentViewController(style: .grouped) commentVC.viewModel.model = model self.navigationController?.pushViewController(commentVC, animated: true) } } extension ZSDiscussViewController:ZSMultiSelectionDelegate { func numberOfSections(in multiSelectionView: ZSMultiSelectionView) -> Int { return titles.count } func multiSelectionView(_ multiSelectionView: ZSMultiSelectionView, numberOfRowsInSection section: Int) -> Int { return titles[section].count } func multiSelectionView(_ multiSelectionView: ZSMultiSelectionView, titleForRowAt indexPath: IndexPath) -> String { return titles[indexPath.section][indexPath.row] } func multiSelectionView(_ multiSelectionView: ZSMultiSelectionView, titleForHeaderIn section: Int) -> String { return titles[section][0] } func multiSelectionView(_ multiSelectionView: ZSMultiSelectionView, didSelectAt indexPath: IndexPath) { selectIndexs[indexPath.section] = indexPath.row fetchHelp(selectIndexs) } } class ZSBookReviewViewController:BaseViewController,UITableViewDataSource,UITableViewDelegate { var models:[BookComment] = [] private var selectIndexs:[Int] = [] private var configure:[[[String:String]]] = [] // private var selectionView:QSSegmentDropView! private var selectionView:ZSMultiSelectionView! lazy var tableView:UITableView = { let tableView = UITableView(frame: CGRect(x: 0, y: kNavgationBarHeight + 40, width: ScreenWidth, height: ScreenHeight - kNavgationBarHeight - 40), style: .grouped) tableView.dataSource = self tableView.delegate = self tableView.sectionHeaderHeight = CGFloat.leastNormalMagnitude tableView.sectionFooterHeight = CGFloat.leastNormalMagnitude tableView.rowHeight = UITableView.automaticDimension tableView.estimatedRowHeight = 99 tableView.qs_registerCellNib(ZSReviewsCell.self) return tableView }() override func viewDidLoad() { super.viewDidLoad() initSubview() } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) layoutSubviews() } override func viewWillLayoutSubviews() { layoutSubviews() } private func layoutSubviews() { selectionView.snp.remakeConstraints { (make) in let statusHeight = UIApplication.shared.statusBarFrame.height let navHeight = self.navigationController?.navigationBar.height ?? 0 make.top.equalTo(statusHeight + navHeight) make.left.right.equalToSuperview() make.height.equalTo(40) } tableView.snp.remakeConstraints { (make) in make.left.right.bottom.equalToSuperview() make.top.equalTo(selectionView.snp.bottom) } } func qs_equal(x:T,y:T)->Bool{ return x == y } func initSubview(){ self.title = "书评区" configure = [[ ["title":"全部","key":"duration","value":"all"], ["title":"精品","key":"duration,distillate","value":"all,true"]] ,[["title":"全部类型","key":"type","value":"all"], ["title":"玄幻奇幻","key":"type","value":"xhqh"], ["title":"武侠仙侠","key":"type","value":"wxxx"], ["title":"都市异能","key":"type","value":"dsyn"], ["title":"历史军事","key":"type","value":"lsjs"], ["title":"游戏竞技","key":"type","value":"yxjj"], ["title":"科幻灵异","key":"type","value":"khly"], ["title":"穿越架空","key":"type","value":"cyjk"], ["title":"豪门总裁","key":"type","value":"hmzc"], ["title":"现代言情","key":"type","value":"xdyq"], ["title":"古代言情","key":"type","value":"gdyq"], ["title":"幻想言情","key":"type","value":"hxyq"], ["title":"耽美同人","key":"type","value":"dmtr"]], [ ["title":"默认排序","key":"sort","value":"updated"], ["title":"最新发布","key":"sort","value":"created"], ["title":"最有用的","key":"sort","value":"helpful"], ["title":"最多评论","key":"sort","value":"comment-count"] ]] selectIndexs = [0,0,0] fetchHelp(self.selectIndexs) view.addSubview(self.tableView) selectionView = ZSMultiSelectionView(frame: CGRect(x: 0, y: kNavgationBarHeight, width: self.view.bounds.width, height: 40)) selectionView.delegate = self view.addSubview(selectionView) } func getTitles(models:[[[String:String]]]) -> [[String]]{ var titles:[[String]] = [] for item in models { var items:[String] = [] for dict in item { items.append(dict["title"]!) } titles.append(items) } return titles } func fetchHelp(_ selectIndexs:[Int]){ // 1.全部-全部类型- //1.1默认排序 http://api.zhuishushenqi.com/post/review?duration=all&sort=updated&type=all&start=0&limit=20 //1.2最新发布 http://api.zhuishushenqi.com/post/review?duration=all&sort=created&type=all&start=0&limit=20 //1.3最有用的 http://api.zhuishushenqi.com/post/review?duration=all&sort=helpful&type=all&start=0&limit=20 //1.4最多评论 http://api.zhuishushenqi.com/post/review?duration=all&sort=comment-count&type=all&start=0&limit=20 //2.精品-奇幻玄幻-最多评论 // http://api.zhuishushenqi.com/post/review?distillate=true&duration=all&sort=comment-count&type=xhqh&start=0&limit=20 let urlString = getURLString(selectIndexs: selectIndexs) zs_get(urlString, parameters: nil) { (response) in let helps = response?["reviews"] as? [[String:Any]] if let commnets = [BookComment].deserialize(from: helps) as? [BookComment] { self.models = commnets self.tableView.reloadData() } } } func getURLString(selectIndexs:[Int])->String{ var section = 0 var query = "start=0&limit=20" for selectIndex in selectIndexs { let item = getItem(section: section, index: selectIndex) let key = item["key"] ?? "" let keys = key.components(separatedBy: ",") let value = item["value"] ?? "" let values = value.components(separatedBy: ",") if keys.count > 1 { var param = "" for pIndex in 0..[String:String]{ var currentSection = 0 var value:[String:String] = [:] for conf in configure { if currentSection == section { var currentIndex = 0 for item in conf { if currentIndex == index { value = item break; } currentIndex += 1 } } currentSection += 1 } return value } //MARK: - UITableViewDataSource func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return models.count } func numberOfSections(in tableView: UITableView) -> Int { return 1 } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell:ZSReviewsCell? = tableView.qs_dequeueReusableCell(ZSReviewsCell.self) cell?.backgroundColor = UIColor.white cell?.selectionStyle = .none cell?.tag(items: configure[1]) cell?.configureCell(model: models[indexPath.row]) return cell! } func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { return tableView.sectionHeaderHeight } func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { return tableView.sectionHeaderHeight } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { let model = models[indexPath.row] let commentVC = ZSBookCommentViewController(style: .grouped) commentVC.viewModel.model = model self.navigationController?.pushViewController(commentVC, animated: true) } } extension ZSBookReviewViewController: ZSMultiSelectionDelegate { func numberOfSections(in multiSelectionView: ZSMultiSelectionView) -> Int { return configure.count } func multiSelectionView(_ multiSelectionView: ZSMultiSelectionView, numberOfRowsInSection section: Int) -> Int { return configure[section].count } func multiSelectionView(_ multiSelectionView: ZSMultiSelectionView, titleForRowAt indexPath: IndexPath) -> String { return configure[indexPath.section][indexPath.row]["title"] ?? "" } func multiSelectionView(_ multiSelectionView: ZSMultiSelectionView, titleForHeaderIn section: Int) -> String { return configure[section][0]["title"] ?? "" } func multiSelectionView(_ multiSelectionView: ZSMultiSelectionView, didSelectAt indexPath: IndexPath) { selectIndexs[indexPath.section] = indexPath.row fetchHelp(selectIndexs) } } class ZSFemaleViewController:BaseViewController,UITableViewDataSource,UITableViewDelegate,QSSegmentDropViewDelegate { var models:[BookComment] = [] var selectIndexs:[Int] = [] let titles = [["全部","精品"],["默认排序","最新发布","最多评论"]] private var selectionView:ZSMultiSelectionView! lazy var tableView:UITableView = { let tableView = UITableView(frame: CGRect(x: 0, y: kNavgationBarHeight + 40, width: ScreenWidth, height: ScreenHeight - kNavgationBarHeight - 40), style: .grouped) tableView.dataSource = self tableView.delegate = self tableView.sectionHeaderHeight = CGFloat.leastNormalMagnitude tableView.sectionFooterHeight = CGFloat.leastNormalMagnitude tableView.rowHeight = UITableView.automaticDimension tableView.estimatedRowHeight = 97 tableView.qs_registerCellNib(QSHelpViewCell.self) return tableView }() override func viewDidLoad() { super.viewDidLoad() initSubview() } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) layoutSubviews() } override func viewWillLayoutSubviews() { layoutSubviews() } private func layoutSubviews() { selectionView.snp.remakeConstraints { (make) in let statusHeight = UIApplication.shared.statusBarFrame.height let navHeight = self.navigationController?.navigationBar.height ?? 0 make.top.equalTo(statusHeight + navHeight) make.left.right.equalToSuperview() make.height.equalTo(40) } tableView.snp.remakeConstraints { (make) in make.left.right.bottom.equalToSuperview() make.top.equalTo(selectionView.snp.bottom) } } func qs_equal(x:T,y:T)->Bool{ return x == y } func initSubview(){ self.title = "女生区" selectIndexs = [0,0] fetchHelp(self.selectIndexs) view.addSubview(self.tableView) selectionView = ZSMultiSelectionView(frame: CGRect(x: 0, y: kNavgationBarHeight, width: self.view.bounds.width, height: 40)) selectionView.delegate = self view.addSubview(selectionView) } func fetchHelp(_ selectIndexs:[Int]){ // local list //all ,默认排序 // http://api.zhuishushenqi.com/post/by-block?block=girl&duration=all&sort=updated&start=0&limit=20 // all,最新发布 // http://api.zhuishushenqi.com/post/by-block?block=girl&duration=all&sort=created&start=0&limit=20 // all,最多评论 // http://api.zhuishushenqi.com/post/by-block?block=girl&duration=all&sort=comment-count&start=0&limit=20 // 精品,默认 // http://api.zhuishushenqi.com/post/by-block?block=girl&distillate=true&duration=all&sort=updated&start=0&limit=20 // 精品,最新发布 // http://api.zhuishushenqi.com/post/by-block?block=girl&distillate=true&duration=all&sort=created&start=0&limit=20 // 精品,最多评论 // http://api.zhuishushenqi.com/post/by-block?block=girl&distillate=true&duration=all&sort=comment-count&start=0&limit=20 let urlString = getURLString(selectIndexs: selectIndexs) zs_get(urlString, parameters: nil) { (response) in let helps = response?["posts"] as? [[String:Any]] if let commnets = [BookComment].deserialize(from: helps) as? [BookComment] { self.models = commnets self.tableView.reloadData() } } } func getURLString(selectIndexs:[Int])->String{ let durations = ["duration=all","duration=all&distillate=true"] let sort = ["sort=updated","sort=created","sort=comment-count"] let urlString = "\(BASEURL)/post/by-block?block=girl&\(durations[selectIndexs[0]])&\(sort[selectIndexs[1]])&start=0&limit=20" return urlString } //MARK: - QSSegmentDropViewDelegate func didSelectAtIndexs(_ indexs: [Int]) { self.selectIndexs = indexs fetchHelp(selectIndexs) } //MARK: - UITableViewDataSource func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return models.count } func numberOfSections(in tableView: UITableView) -> Int { return 1 } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell:QSHelpViewCell? = tableView.qs_dequeueReusableCell(QSHelpViewCell.self) cell?.backgroundColor = UIColor.white cell?.selectionStyle = .none cell?.configureCell(model: models[indexPath.row]) return cell! } func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { return tableView.sectionHeaderHeight } func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { return tableView.sectionHeaderHeight } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { let model = models[indexPath.row] let commentVC = ZSBookCommentViewController(style: .grouped) commentVC.viewModel.model = model self.navigationController?.pushViewController(commentVC, animated: true) } } extension ZSFemaleViewController: ZSMultiSelectionDelegate { func numberOfSections(in multiSelectionView: ZSMultiSelectionView) -> Int { return titles.count } func multiSelectionView(_ multiSelectionView: ZSMultiSelectionView, numberOfRowsInSection section: Int) -> Int { return titles[section].count } func multiSelectionView(_ multiSelectionView: ZSMultiSelectionView, titleForRowAt indexPath: IndexPath) -> String { return titles[indexPath.section][indexPath.row] } func multiSelectionView(_ multiSelectionView: ZSMultiSelectionView, titleForHeaderIn section: Int) -> String { return titles[section][0] } func multiSelectionView(_ multiSelectionView: ZSMultiSelectionView, didSelectAt indexPath: IndexPath) { selectIndexs[indexPath.section] = indexPath.row fetchHelp(selectIndexs) } } class LookBookViewController: BaseViewController,UITableViewDataSource,UITableViewDelegate { var models:[BookComment] = [] var selectIndexs:[Int] = [] private var selectionView:ZSMultiSelectionView! let titles = [["全部","精品"],["默认排序","最新发布","最多评论"]] lazy var tableView:UITableView = { let tableView = UITableView(frame: CGRect(x: 0, y: kNavgationBarHeight + 40, width: ScreenWidth, height: ScreenHeight - kNavgationBarHeight - 40), style: .grouped) tableView.dataSource = self tableView.delegate = self tableView.sectionHeaderHeight = CGFloat.leastNormalMagnitude tableView.sectionFooterHeight = CGFloat.leastNormalMagnitude tableView.rowHeight = UITableView.automaticDimension tableView.estimatedRowHeight = 97 tableView.qs_registerCellNib(QSHelpViewCell.self) return tableView }() override func viewDidLoad() { super.viewDidLoad() initSubview() } override func viewWillLayoutSubviews() { layoutSubviews() } private func layoutSubviews() { selectionView.snp.remakeConstraints { (make) in let statusHeight = UIApplication.shared.statusBarFrame.height let navHeight = self.navigationController?.navigationBar.height ?? 0 make.top.equalTo(statusHeight + navHeight) make.left.right.equalToSuperview() make.height.equalTo(40) } tableView.snp.remakeConstraints { (make) in make.left.right.bottom.equalToSuperview() make.top.equalTo(selectionView.snp.bottom) } } func qs_equal(x:T,y:T)->Bool{ return x == y } func initSubview(){ self.title = "书荒求助区" selectIndexs = [0,0] fetchHelp(self.selectIndexs) view.addSubview(self.tableView) selectionView = ZSMultiSelectionView(frame: CGRect(x: 0, y: kNavgationBarHeight, width: self.view.bounds.width, height: 40)) selectionView.delegate = self view.addSubview(selectionView) } func fetchHelp(_ selectIndexs:[Int]){ // local list //all // http://api.zhuishushenqi.com/post/help?duration=all&sort=updated&start=0&limit=20 // http://api.zhuishushenqi.com/post/help?duration=all&sort=created&start=0&limit=20 // http://api.zhuishushenqi.com/post/help?duration=all&sort=comment-count&start=0&limit=20 //great // http://api.zhuishushenqi.com/post/help?distillate=true&duration=all&sort=updated&start=0&limit=20 // http://api.zhuishushenqi.com/post/help?distillate=true&duration=all&sort=created&start=0&limit=20 // http://api.zhuishushenqi.com/post/help?distillate=true&duration=all&sort=comment-count&start=0&limit=20 let urlString = getURLString(selectIndexs: selectIndexs) zs_get(urlString, parameters: nil) { (response) in let helps = response?["helps"] as? [[String:Any]] if let commnets = [BookComment].deserialize(from: helps) as? [BookComment] { self.models = commnets self.tableView.reloadData() } } } func getURLString(selectIndexs:[Int])->String{ let durations = ["duration=all","duration=all&distillate=true"] let sort = ["sort=updated","sort=created","sort=comment-count"] let urlString = "\(BASEURL)/post/help?\(durations[selectIndexs[0]])&\(sort[selectIndexs[1]])&start=0&limit=20" return urlString } //MARK: - UITableViewDataSource func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return models.count } func numberOfSections(in tableView: UITableView) -> Int { return 1 } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell:QSHelpViewCell? = tableView.qs_dequeueReusableCell(QSHelpViewCell.self) cell?.backgroundColor = UIColor.white cell?.selectionStyle = .none cell?.configureCell(model: models[indexPath.row]) return cell! } func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { return tableView.sectionHeaderHeight } func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { return tableView.sectionHeaderHeight } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { let model = models[indexPath.row] let commentVC = ZSBookCommentViewController(style: .grouped) commentVC.viewModel.model = model self.navigationController?.pushViewController(commentVC, animated: true) } } extension LookBookViewController: ZSMultiSelectionDelegate { func numberOfSections(in multiSelectionView: ZSMultiSelectionView) -> Int { return titles.count } func multiSelectionView(_ multiSelectionView: ZSMultiSelectionView, numberOfRowsInSection section: Int) -> Int { return titles[section].count } func multiSelectionView(_ multiSelectionView: ZSMultiSelectionView, titleForRowAt indexPath: IndexPath) -> String { return titles[indexPath.section][indexPath.row] } func multiSelectionView(_ multiSelectionView: ZSMultiSelectionView, titleForHeaderIn section: Int) -> String { return titles[section][0] } func multiSelectionView(_ multiSelectionView: ZSMultiSelectionView, didSelectAt indexPath: IndexPath) { selectIndexs[indexPath.section] = indexPath.row fetchHelp(selectIndexs) } } ================================================ FILE: zhuishushenqi/Root/Controllers/ReadHistoryViewController.swift ================================================ // // ReadHistoryViewController.swift // zhuishushenqi // // Created by yung on 2017/6/17. // Copyright © 2017年 QS. All rights reserved. // import UIKit class ReadHistoryViewController: BaseViewController,UITableViewDataSource,UITableViewDelegate { lazy var tableView:UITableView = { let tableView = UITableView(frame: CGRect(x: 0, y: 64, width: ScreenWidth, height: ScreenHeight - 64), style: .grouped) tableView.dataSource = self tableView.delegate = self tableView.sectionHeaderHeight = CGFloat.leastNormalMagnitude tableView.sectionFooterHeight = CGFloat.leastNormalMagnitude tableView.rowHeight = 93 tableView.qs_registerCellNib(ReadHistoryCell.self) return tableView }() override func viewDidLoad() { super.viewDidLoad() initSubview() } func initSubview(){ self.title = "浏览记录" view.addSubview(self.tableView) } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return ZSBookManager.shared.historyIds.count } func numberOfSections(in tableView: UITableView) -> Int { return 1 } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell:ReadHistoryCell? = tableView.qs_dequeueReusableCell(ReadHistoryCell.self) cell?.backgroundColor = UIColor.white cell?.selectionStyle = .none let models = ZSBookManager.shared.historyBooks.allValues() as! [BookDetail] cell?.configureCell(model: models[indexPath.row]) return cell! } func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { return tableView.sectionHeaderHeight } func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { return tableView.sectionHeaderHeight } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { let models = ZSBookManager.shared.historyBooks.allValues() as! [BookDetail] let model = models[indexPath.row] self.navigationController?.pushViewController(QSBookDetailRouter.createModule(id: model._id ), animated: true) } } ================================================ FILE: zhuishushenqi/Root/Controllers/RightViewController.swift ================================================ // // RightViewController.swift // zhuishushenqi // // Created by Nory Cao on 16/9/16. // Copyright © 2016年 QS. All rights reserved. // import UIKit import ZSAPI import SnapKit class RightViewController: BaseViewController, UITableViewDataSource, UITableViewDelegate { var titles:[String]? var images:[String]? var tableView:UITableView = UITableView(frame: CGRect.zero, style: .grouped) override func viewDidLoad() { super.viewDidLoad() titles = ["搜索","书城","VIP专区","排行榜","主题书单","分类","免费专区","专属推荐","漫画专区","听书专区","随机看书","WIFI传书"] images = ["rsm_icon_0","rsm_icon_store","rsm_icon_monthly","rsm_icon_3","rsm_icon_4","rsm_icon_5","rsm_icon_exclusive","rsm_icon_recommended","rsm_icon_comic","rsm_icon_6","rsm_icon_7","rsm_icon_wifi"] tableView.backgroundColor = UIColor(red: 0.211, green: 0.211, blue: 0.211, alpha: 1.00) tableView.frame = CGRect(x: self.view.bounds.width - SideVC.maximumRightOffsetWidth , y: 0, width: SideVC.maximumRightOffsetWidth, height: self.view.bounds.height) tableView.rowHeight = 60 tableView.dataSource = self tableView.delegate = self tableView.separatorInset = UIEdgeInsets(top: 0, left: 55, bottom: 0, right: 0) tableView.qs_registerCellClass(RightTableViewCell.self) view.addSubview(tableView) } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) navigationController?.setNavigationBarHidden(true, animated: true) automaticallyAdjustsScrollViewInsets = false tableView.snp.remakeConstraints { (make) in make.top.right.bottom.equalToSuperview() make.left.equalToSuperview().offset(self.view.bounds.width - SideVC.maximumRightOffsetWidth) } } override func viewWillLayoutSubviews() { } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return titles?.count ?? 0 } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell:RightTableViewCell? = tableView.qs_dequeueReusableCell(RightTableViewCell.self) cell?.selectionStyle = .none cell?.backgroundColor = UIColor ( red: 0.16, green: 0.16, blue: 0.16, alpha: 1.0 ) cell?.imageView?.image = UIImage(named: images![indexPath.row]) cell?.textLabel?.text = titles![indexPath.row] return cell! } func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { return 0.0 } func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { return 60 } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) if indexPath.row == 0 {//搜索 self.navigationItem.backBarButtonItem?.tintColor = UIColor ( red: 0.7235, green: 0.0, blue: 0.1146, alpha: 1.0 ) let searchVC = ZSSearchViewController(style: .grouped) SideVC.navigationController?.pushViewController(searchVC, animated: true) } else if indexPath.row == 1 { let webVC = ZSWebViewController() let timeInterval = Date().timeIntervalSince1970 webVC.url = "https://h5.zhuishushenqi.com/explore?timestamp=\(timeInterval)&platform=ios&gender=male&appversion=2.29.5&version=8" webVC.title = "书城" SideVC.navigationController?.pushViewController(webVC, animated: true) } else if indexPath.row == 2 { let webVC = ZSWebViewController() let timeInterval = Date().timeIntervalSince1970 var url = "" if ZSLogin.share.hasLogin() { url = "https://h5.zhuishushenqi.com/monthly?platform=ios&gender=male&token=\(ZSLogin.share.token)&timeInterval=\(timeInterval*10)&appversion=2.29.5×tamp=\(timeInterval)&version=8" } else { url = "https://h5.zhuishushenqi.com/monthly?platform=ios&gender=male&timeInterval=\(timeInterval*10)&appversion=2.29.5×tamp=\(timeInterval)&version=8" } webVC.url = url webVC.title = "VIP专区" SideVC.navigationController?.pushViewController(webVC, animated: true) } else if indexPath.row == 3 {//排行榜 self.navigationItem.backBarButtonItem?.tintColor = UIColor ( red: 0.7235, green: 0.0, blue: 0.1146, alpha: 1.0 ) let rankVC = QSRankViewController() SideVC.navigationController?.pushViewController(rankVC, animated: true) } else if indexPath.row == 4 {//主题书单 self.navigationItem.backBarButtonItem?.tintColor = UIColor ( red: 0.7235, green: 0.0, blue: 0.1146, alpha: 1.0 ) SideVC.navigationController?.pushViewController(QSThemeTopicRouter.createModule(), animated: true) } else if indexPath.row == 5 { self.navigationItem.backBarButtonItem?.tintColor = UIColor ( red: 0.7235, green: 0.0, blue: 0.1146, alpha: 1.0 ) let catelogVC = ZSCatelogViewController() SideVC.navigationController?.pushViewController(catelogVC, animated: true) } else if indexPath.row == 6 { let webVC = ZSWebViewController() let timeInterval = Date().timeIntervalSince1970 webVC.url = "https://h5.zhuishushenqi.com/original?platform=ios&gender=male&timeInterval=\(timeInterval*10)&appversion=2.29.5×tamp=\(timeInterval)&version=8" webVC.title = "免费专区" SideVC.navigationController?.pushViewController(webVC, animated: true) } else if indexPath.row == 7 { let webVC = ZSWebViewController() let timeInterval = Date().timeIntervalSince1970 webVC.url = "https://h5.zhuishushenqi.com/original?platform=ios&gender=male&timeInterval=\(timeInterval*10)&appversion=2.29.5×tamp=\(timeInterval)&version=8" webVC.title = "专属推荐" SideVC.navigationController?.pushViewController(webVC, animated: true) } else if indexPath.row == 8 { let webVC = ZSWebViewController() let timeInterval = Date().timeIntervalSince1970 webVC.url = "https://h5.zhuishushenqi.com/cartoon?platform=ios×tamp=\(timeInterval)&gender=male&appversion=2.29.5&version=8" webVC.title = "漫画专区" SideVC.navigationController?.pushViewController(webVC, animated: true) } else if indexPath.row == 9 { let voiceVC = ZSVoiceBookCategoryViewController() voiceVC.title = "听书专区" SideVC.navigationController?.pushViewController(voiceVC, animated: true) } else if indexPath.row == 10 { // https://api.zhuishushenqi.com/book/mystery-box let api = ZSAPI.mysteryBook("" as AnyObject) zs_get(api.path) { (json) in if let books = json?["books"] as? [[String:[String:Any]]] { if let book = books[0]["book"] { if let model = BookDetail.deserialize(from: book) { let viewController = ZSReaderViewController() viewController.viewModel.book = model self.present(viewController, animated: true, completion: nil) } } } } } else if indexPath.row == 11 { } } } ================================================ FILE: zhuishushenqi/Root/Controllers/RootViewController+FetchData.swift ================================================ // // RootViewController+FetchData.swift // zhuishushenqi // // Created by Nory Cao on 2017/3/19. // Copyright © 2017年 QS. All rights reserved. // import Foundation import ZSAPI extension RootViewController{ func requetShelfMsg(){ autoreleasepool { self.bookShelfLB.text = USER_DEFAULTS.object(forKey: PostLink) as? String ?? "" let shelfApi = ZSAPI.shelfMSG("" as AnyObject) zs_get(shelfApi.path, parameters: shelfApi.parameters) { (response) in if let json = response { let message = json["message"] as? NSDictionary if let msg = message{ let postLink = msg.object(forKey: "postLink") as? String if let highlight = msg.object(forKey: "highlight") as? Bool { if highlight { self.bookShelfLB.textColor = UIColor.red } else { self.bookShelfLB.textColor = UIColor.gray } } let post = self.getPost(postLink) USER_DEFAULTS.set(post.1, forKey: PostLink) self.bookShelfLB.attributedText = NSAttributedString(string: "\(post.1)") } } } } } func requestBookShelf(){ //已登录状态的书架 //未登录中状态下,图书的信息保存在userdefault中 if !User.user.isLogin { self.bookshelf = BookManager.shared.books self.books = BookManager.shared.bookshelf() let url:NSString = "http://www.luoqiu.com/read/175859" QSLog(url.lastPathComponent) QSLog(url.deletingLastPathComponent) QSLog(url.pathComponents) QSLog(url.pathExtension) if url.pathExtension == "" { QSLog(url.pathExtension) } } } //匹配当前书籍的更新信息 func updateInfo(){ guard self.bookshelf != nil else { return } let ids = self.booksID(books: self.bookshelf) let api = ZSAPI.update(id: ids) zs_get(api.path, parameters: api.parameters) { (response) in self.tableView.mj_header.endRefreshing() self.tableView.mj_footer.endRefreshing() // 防止返回时卡顿,采用子线程 DispatchQueue.global().async { if let json:[Any] = response as? [Any] { if let models = [BookShelf].deserialize(from: json) as? [BookShelf] { BookManager.shared.updateToModel(updateModels: models) DispatchQueue.main.async { self.tableView.reloadData() } } } } } } func isUpdated(update:UpdateInfo,book:BookDetail)->Bool{ if (update.updated ?? "1") != (book.updateInfo?.updated ?? "2") { return true } return false } func requestAllChapters(withUrl url:String,param:[String:Any],index:Int){ self.present(QSTextRouter.createModule(bookDetail:self.bookShelfArr![index],callback: {(book:BookDetail) in }), animated: true, completion: nil) } func booksID(books:[String:Any]?)->String{ if let keys = books{ var ids = "" for book in keys.keys { if ids != "" { ids.append(",") } ids.append(book) } return ids } return "" } func param(bookArr:[BookDetail])->String{ var idString = "" var index = 0 for item in bookArr { idString = idString.appending(item._id) if index != bookArr.count - 1 { idString = idString.appending(",") } index += 1 } return idString } func getPost(_ postLink:String?) ->(String,String){ var id:String = "" var title:String = "" if let link = postLink { // 此处如果过滤方式不正确,则返回空,按中文【 开头,]]结尾 let qsLink:NSString = link as NSString let startRange = qsLink.range(of: "【") let endRange = qsLink.range(of: "]]") let endContainRange = qsLink.range(of: "]]]") let post = qsLink.range(of: "post:") if startRange.location == NSNotFound { if endRange.location != NSNotFound { if qsLink.length > 32 { // 过滤方式变更 title = link.qs_subStr(start: 32, end: endRange.location) id = link.qs_subStr(start: 7, length: 24) } } return (id,title) } title = link.qs_subStr(start: startRange.location, end: endRange.location) if endContainRange.location != NSNotFound { title = link.qs_subStr(start: startRange.location, end: endContainRange.location - 1) } if post.location == NSNotFound { return (id,title) } id = link.qs_subStr(start: post.location + post.length, end: startRange.location) return (id,title) } return (id,title) } func setupReachability(_ hostName: String?, useClosures: Bool) { let reachability = hostName == nil ? Reachability() : Reachability(hostname: hostName!) self.reachability = reachability if useClosures { reachability?.whenReachable = { reachability in DispatchQueue.main.async { self.hudAddTo(view: self.view, text: "网络已连接", animated: true) } } reachability?.whenUnreachable = { reachability in DispatchQueue.main.async { self.hudAddTo(view: self.view, text: "网络未连接", animated: true) } } } else { NotificationCenter.default.addObserver(self, selector: #selector(reachabilityChanged(_:)), name: ReachabilityChangedNotification, object: reachability) } } @objc func reachabilityChanged(_ no:Notification){ let reachability = no.object as! Reachability DispatchQueue.main.async { self.typeOfNetwork(type: reachability.networkType) } } func typeOfNetwork(type:QSNetworkType){ switch type { case .WWAN2G: hudAddTo(view: self.view, text: "当前为2G网络", animated: true) break case .WWAN3G: hudAddTo(view: self.view, text: "当前为3G网络", animated: true) break case .WWAN4G: hudAddTo(view: self.view, text: "当前为4G网络", animated: true) break case .WiFi: hudAddTo(view: self.view, text: "当前为WiFi网络", animated: true) break case .UnKnown: hudAddTo(view: self.view, text: "当前网络状态未知", animated: true) break default: hudAddTo(view: self.view, text: "当前网络不可达", animated: true) break } } func startNotifier() { print("--- start notifier") do { try reachability?.startNotifier() } catch { // networkStatus.textColor = .red // networkStatus.text = "Unable to start\nnotifier" return } } func stopNotifier() { print("--- stop notifier") reachability?.stopNotifier() NotificationCenter.default.removeObserver(self, name: ReachabilityChangedNotification, object: nil) reachability = nil } } ================================================ FILE: zhuishushenqi/Root/Controllers/RootViewController+Subviews.swift ================================================ // // RootViewController-Subviews.swift // zhuishushenqi // // Created by Nory Cao on 2017/3/19. // Copyright © 2017年 QS. All rights reserved. // import Foundation import UIKit import SnapKit let kTootSegmentViewHeight:CGFloat = 40 extension RootViewController{ func setupSubviews(){ RootNavigationView.make(delegate: self,leftAction: #selector(leftAction(_:)),rightAction: #selector(rightAction(_:))) self.automaticallyAdjustsScrollViewInsets = false self.setupSegMenu() self.setupBookSheldLB() self.setupHeaderView() self.setupTableView() self.setupCommunityView() self.layoutAllViews() } fileprivate func setupSegMenu(){ segMenu = SegMenu(frame: CGRect.zero, WithTitles: ["追书架","追书社区"]) segMenu.menuDelegate = self self.view.addSubview(segMenu) } fileprivate func layoutAllViews(){ segMenu.snp.makeConstraints { (make) in make.top.equalTo(kNavgationBarHeight) make.left.right.equalTo(self.view) make.height.equalTo(kTootSegmentViewHeight) } communityView.snp.makeConstraints { (make) in make.left.right.bottom.equalTo(self.view) make.top.equalTo(segMenu.snp.bottom) } tableView.snp.makeConstraints { (make) in make.left.right.bottom.equalTo(self.view) make.top.equalTo(segMenu.snp.bottom) } } fileprivate func setupCommunityView() -> Void { communityView = CommunityView() communityView.frame = CGRect(x: 0, y: kNavgationBarHeight + 40, width: ScreenWidth, height: ScreenHeight - 104) communityView.delegate = self communityView.isHidden = true self.view.addSubview(communityView) } fileprivate func setupBookSheldLB(){ bookShelfLB = UILabel() bookShelfLB.frame = CGRect(x: 5,y: 0,width: ScreenWidth - 10,height: 44) bookShelfLB.textColor = UIColor.gray bookShelfLB.font = UIFont.systemFont(ofSize: 13) bookShelfLB.textAlignment = .center } fileprivate func setupTableView(){ self.tableView.frame = CGRect(x: 0, y: kNavgationBarHeight + 40, width: ScreenWidth, height: ScreenHeight - kNavgationBarHeight - 40) self.view.addSubview(self.tableView) } fileprivate func setupHeaderView(){ headerView = UIView() headerView.frame = CGRect(x: 0, y: 0, width: ScreenWidth, height: kHeaderViewHeight) headerView.backgroundColor = UIColor ( red: 1.0, green: 1.0, blue: 1.0, alpha: 0.7 ) headerView.layer.masksToBounds = true // let signIn = UIButton(type: .custom) // signIn.setBackgroundImage(UIImage(named: "sign_bg"), for: UIControl.State()) // signIn.setBackgroundImage(UIImage(named: "sign_bg"), for: .highlighted) // signIn.frame = CGRect(x: -3, y: 0, width: ScreenWidth + 6, height: kHeaderViewHeight) // // let sign_gotoSign = UIButton(type: .custom) // sign_gotoSign.setBackgroundImage(UIImage(named: "sign_gotoSign"), for: UIControl.State()) // sign_gotoSign.setBackgroundImage(UIImage(named: "sign_gotoSign"), for: .highlighted) // sign_gotoSign.frame = CGRect(x: ScreenWidth - 100 - 10, y: 17, width: 100, height: 37) // signIn.addSubview(sign_gotoSign) // headerView.addSubview(signIn) // let signIn = UIButton(type: .custom) // signIn.setBackgroundImage(UIImage(named: "sign_bg"), for: UIControl.State()) // signIn.setBackgroundImage(UIImage(named: "sign_bg"), for: .highlighted) // signIn.frame = CGRect(x: -3, y: 0, width: ScreenWidth + 6, height: kHeaderViewHeight) // // let sign_gotoSign = UIButton(type: .custom) // sign_gotoSign.setBackgroundImage(UIImage(named: "sign_gotoSign"), for: UIControl.State()) // sign_gotoSign.setBackgroundImage(UIImage(named: "sign_gotoSign"), for: .highlighted) // sign_gotoSign.frame = CGRect(x: ScreenWidth - 100 - 10, y: 17, width: 100, height: 37) // signIn.addSubview(sign_gotoSign) // headerView.addSubview(signIn) // if bookShelfLB.text == nil || bookShelfLB.text == ""{ // signIn.frame = CGRect(x: -3, y: 0, width: ScreenWidth + 6, height: 72) // sign_gotoSign.frame = CGRect(x: ScreenWidth - 100 - 10, y: 17, width: 100, height: 37) // } headerView.addSubview(bookShelfLB) } } ================================================ FILE: zhuishushenqi/Root/Controllers/RootViewController.swift ================================================ // // RootViewController.swift // zhuishushenqi // // Created by Nory Cao on 16/9/16. // Copyright © 2016年 QS. All rights reserved. // import UIKit let AllChapterUrl = "http://api.zhuishushenqi.com/ctoc/57df797cb061df9e19b8b030" class RootViewController: UIViewController { let kHeaderViewHeight:CGFloat = 5 fileprivate let kHeaderBigHeight:CGFloat = 44 fileprivate let kCellHeight:CGFloat = 60 // 采用dict保存书架中的书籍 var bookshelf:[String:Any]? // 保存所有书籍的id var books:[String]? var bookShelfArr:[BookDetail]? var updateInfoArr:[UpdateInfo]? var segMenu:SegMenu! var bookShelfLB:UILabel! var communityView:CommunityView! var headerView:UIView! var reachability:Reachability? var semaphore:DispatchSemaphore! var totalCacheChapter:Int = 0 var curCacheChapter:Int = 0 private var tipImageView:UIImageView! private var recView:QSLaunchRecView! lazy var tableView:UITableView = { let tableView = UITableView(frame: CGRect.zero, style: .grouped) tableView.dataSource = self tableView.delegate = self tableView.rowHeight = self.kCellHeight tableView.estimatedRowHeight = self.kCellHeight tableView.estimatedSectionHeaderHeight = self.kHeaderViewHeight tableView.sectionFooterHeight = CGFloat.leastNonzeroMagnitude tableView.qs_registerCellClass(SwipableCell.self) // let refresh = PullToRefresh(height: 50, position: .top, tip: "正在刷新") // tableView.addPullToRefresh(refresh, action: { // self.requetShelfMsg() // self.requestBookShelf() // self.updateInfo() // }) return tableView }() override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. setupReachability(IMAGE_BASEURL, useClosures: false) self.startNotifier() NotificationCenter.default.addObserver(self, selector: #selector(bookShelfUpdate(noti:)), name: Notification.Name(rawValue: BOOKSHELF_ADD), object: nil) NotificationCenter.default.addObserver(self, selector: #selector(bookShelfUpdate(noti:)), name: Notification.Name(rawValue: BOOKSHELF_DELETE), object: nil) NotificationCenter.default.addObserver(self, selector: #selector(showRecommend), name: Notification.Name(rawValue:SHOW_RECOMMEND), object: nil) self.setupSubviews() self.requetShelfMsg() self.requestBookShelf() self.updateInfo() } func removeBook(book:BookDetail){ if let books = self.books { var index = 0 for bookid in books { if book._id == bookid { self.books?.remove(at: index) self.bookshelf?.removeValue(forKey: bookid) BookManager.shared.deleteBook(book: book) } index += 1 } } } @objc func bookShelfUpdate(noti:Notification){ let name = noti.name.rawValue if let book = noti.object as? BookDetail { if name == BOOKSHELF_ADD { self.bookshelf?[book._id] = book self.books?.append(book._id) BookManager.shared.addBook(book: book) } else { removeBook(book: book) } } //先刷新书架,然后再请求 self.tableView.reloadData() self.updateInfo() } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) self.view.backgroundColor = UIColor.cyan self.navigationController?.navigationBar.barTintColor = UIColor ( red: 0.7235, green: 0.0, blue: 0.1146, alpha: 1.0 ) UIApplication.shared.isStatusBarHidden = false // 刷新书架 self.bookshelf = BookManager.shared.books } @objc private func showRecommend(){ // animate let recommend = ZSRecommend() recommend.show(boyTipCallback: { (btn) in }, girlTipCallback: { (btn) in }) { (btn) in } // let nib = UINib(nibName: "QSLaunchRecView", bundle: nil) // recView = nib.instantiate(withOwner: nil, options: nil).first as? QSLaunchRecView // recView.frame = self.view.bounds // recView.alpha = 0.0 // recView.closeCallback = { (btn) in // self.dismissRecView() // } // recView.boyTipCallback = { (btn) in // self.fetchRecList(index: 0) // self.perform(#selector(self.dismissRecView), with: nil, afterDelay: 1) // } // // recView?.girlTipCallback = { (btn) in // self.fetchRecList(index: 1) // self.perform(#selector(self.dismissRecView), with: nil, afterDelay: 1) // } // KeyWindow?.addSubview(recView) // UIView.animate(withDuration: 0.35, animations: { // self.recView.alpha = 1.0 // }) { (finished) in // // } } func fetchRecList(index:Int){ let gender = ["male","female"] let recURL = "\(BASEURL)/book/recommend?gender=\(gender[index])" zs_get(recURL, parameters: nil) { (response) in if let books = response?["books"] { if let models = [BookDetail].deserialize(from: books as? [Any]) as? [BookDetail] { ZSBookManager.shared.addBooks(books: models) BookManager.shared.modifyBookshelf(books: models) self.tableView.reloadData() } self.updateInfo() } } } func showUserTipView(){ tipImageView = UIImageView(frame: self.view.bounds) tipImageView.image = UIImage(named: "add_book_hint") tipImageView.isUserInteractionEnabled = true let tap = UITapGestureRecognizer(target: self, action: #selector(dismissTipView(sender:))) tipImageView.addGestureRecognizer(tap) KeyWindow?.addSubview(tipImageView) } @objc func dismissTipView(sender:Any){ tipImageView.removeFromSuperview() } @objc func dismissRecView(){ UIView.animate(withDuration: 0.35, animations: { self.recView.alpha = 0.0 }) { (finished) in self.recView.removeFromSuperview() self.showUserTipView() } } @objc func leftAction(_ btn:UIButton){ SideVC.showLeftViewController() } @objc func rightAction(_ btn:UIButton){ SideVC.showRightViewController() } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } override var preferredStatusBarStyle : UIStatusBarStyle { return .lightContent } } extension RootViewController:UITableViewDataSource,UITableViewDelegate{ //MARK: - UITableViewDataSource and UITableViewDelegate func numberOfSections(in tableView: UITableView) -> Int { return 1 } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { if let keys = books?.count { return keys } return 0 } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell:SwipableCell? = tableView.qs_dequeueReusableCell(SwipableCell.self) cell?.delegate = self if let id = books?[indexPath.row] { if let value = bookshelf?.valueForKey(key: id) as? BookDetail { cell?.configureCell(model: value) } } return cell! } func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { return self.kCellHeight } func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { return headerView } func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { if bookShelfLB.text == nil || bookShelfLB.text == ""{ return kHeaderViewHeight } return kHeaderBigHeight } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) if let id = self.books?[indexPath.row] { if let value = bookshelf?.valueForKey(key: id) as? BookDetail { let viewController = QSTextRouter.createModule(bookDetail:value,callback: { (book:BookDetail) in let beginDate = Date() self.bookshelf = BookManager.shared.modifyBookshelf(book:book) tableView.reloadRow(at: indexPath, with: .automatic) let endDate = Date() let time = endDate.timeIntervalSince(beginDate as Date) QSLog("用时\(time)") }) self.present(viewController, animated: true, completion: nil) } // 进入阅读,当前书籍移到最前面 self.books?.remove(at: indexPath.row) self.books?.insert(id, at: 0) let deadline = DispatchTime.now() + 1 DispatchQueue.main.asyncAfter(deadline:deadline , execute: { self.tableView.reloadData() }) } } } extension RootViewController:ComnunityDelegate{ //MARK: - CommunityDelegate func didSelectCellAtIndex(_ index:Int){ if index == 3{ let lookVC = LookBookViewController() SideVC.navigationController?.pushViewController(lookVC, animated: true) }else if index == 5 { let historyVC = ReadHistoryViewController() SideVC.navigationController?.pushViewController(historyVC, animated: true) } else if (index == 1) { let rootVC = ZSRootViewController() SideVC.navigationController?.pushViewController(rootVC, animated: true) } // else if (index == 2) { // let rootVC = ZSForumViewController() // SideVC.navigationController?.pushViewController(rootVC, animated: true) // } else{ let dynamicVC = DynamicViewController() SideVC.navigationController?.pushViewController(dynamicVC, animated: true) } } } extension RootViewController:SwipableCellDelegate{ func swipableCell(swipableCell: SwipableCell, didSelectAt index: Int) { } func swipeCell(clickAt: Int,model:BookDetail,cell:SwipableCell,selected:Bool) { if clickAt == 0 { if selected == false { // 取消下载 return } let indexPath = tableView.indexPath(for: cell) // 选择一种缓存方式后,缓存按钮变为选中状态,小说图标变为在缓存中 let alert = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) let firstAcion = UIAlertAction(title: "全本缓存", style: .default, handler: { (action) in self.cacheChapter(book: model, startChapter: 0,indexPath: indexPath) }) let secondAction = UIAlertAction(title: "从当前章节缓存", style: .default, handler: { (action) in self.cacheChapter(book: model, startChapter: model.chapter,indexPath: indexPath) }) let cancelAction = UIAlertAction(title: "取消", style: .cancel, handler: { (action) in }) alert.addAction(firstAcion) alert.addAction(secondAction) alert.addAction(cancelAction) present(alert, animated: true, completion: nil) } else if clickAt == 3 { self.removeBook(book: model) self.tableView.reloadData() } } func cacheChapter(book:BookDetail,startChapter:Int,indexPath:IndexPath?){ let cell:SwipableCell = tableView.cellForRow(at: indexPath ?? IndexPath(row: 0, section: 0)) as! SwipableCell cell.state = .prepare // 使用信号量控制,避免创建过多线程浪费性能 semaphore = DispatchSemaphore(value: 5) if let chapters = book.book?.chapters { self.totalCacheChapter = chapters.count let group = DispatchGroup() let global = DispatchQueue.global(qos: .userInitiated) for index in startChapter..= (chapters?.count ?? 0) { cell.state = .none return } var link:NSString = "\(chapters?[index].object(forKey: "link") ?? "")" as NSString link = link.urlEncode() as NSString let url = "\(CHAPTERURL)/\(link)?k=22870c026d978c75&t=1489933049" zs_get(url, parameters: nil) { (response) in DispatchQueue.global().async { if let json = response as? Dictionary { QSLog("JSON:\(json)") if let chapter = json["chapter"] as? Dictionary { self.cacheSuccessHandler(chapterIndex: index, chapter: chapter, bookDetail: bookDetail,indexPath: indexPath) } else { self.cacheFailureHandler(chapterIndex: index) } }else{ self.cacheFailureHandler(chapterIndex:index) } } } } } extension RootViewController:SegMenuDelegate{ func didSelectAtIndex(_ index: Int) { QSLog("选择了第\(index)个") if index == 1{ communityView.models = self.bookShelfArr ?? [] } communityView.isHidden = (index == 0 ? true:false) } func cacheSuccessHandler(chapterIndex:Int,chapter: Dictionary,bookDetail:BookDetail,indexPath:IndexPath?){ let cell:SwipableCell = tableView.cellForRow(at: indexPath ?? IndexPath(row: 0, section: 0)) as! SwipableCell cell.state = .download if curCacheChapter == totalCacheChapter - 1 { // 缓存完成 cell.state = .finish } self.updateChapter(chapterIndex: chapterIndex, chapter: chapter, bookDetail: bookDetail, indexPath: indexPath) self.semaphore.signal() } func cacheFailureHandler(chapterIndex:Int){ QSLog("下载失败第\(chapterIndex)章节") self.semaphore.signal() } // 更新当前model,更新本地保存model func updateChapter(chapterIndex:Int,chapter: Dictionary,bookDetail:BookDetail,indexPath:IndexPath?){ if let chapters = bookDetail.book?.chapters { if chapterIndex < chapters.count { let qsChapter = chapters[chapterIndex] qsChapter.content = chapter["body"] as? String ?? "" bookDetail.book.chapters[chapterIndex] = qsChapter BookManager.shared.modifyBookshelf(book: bookDetail) } } } } ================================================ FILE: zhuishushenqi/Root/Controllers/ZSBookShelvesViewController.swift ================================================ // // ZSBookShelvesViewController.swift // zhuishushenqi // // Created by caony on 2018/6/7. // Copyright © 2018年 QS. All rights reserved. // import UIKit import Then import RxCocoa import RxSwift import MJRefresh import SnapKit import RxDataSources class ZSBookShelvesViewController: BaseViewController ,UITableViewDelegate,Refreshable{ var shelfMsg = UIButton(type: .custom).then { $0.frame = CGRect.zero $0.backgroundColor = UIColor ( red: 1.0, green: 1.0, blue: 1.0, alpha: 0.7 ) $0.setTitleColor(UIColor.gray, for: .normal) $0.titleLabel?.font = UIFont.systemFont(ofSize: 13) } var tableView:UITableView = UITableView(frame: CGRect.zero, style: .grouped).then { $0.qs_registerCellClass(SwipableCell.self) $0.rowHeight = kCellHeight $0.estimatedRowHeight = kCellHeight $0.estimatedSectionHeaderHeight = 0.01 } fileprivate let kHeaderBigHeight:CGFloat = 44 static let kCellHeight:CGFloat = 60 fileprivate let disposeBag = DisposeBag() var headerRefresh:MJRefreshHeader? let viewModel = ZSRootViewModel() override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = UIColor.red configureTableDataSource() configureNavigateOnRowClick() configureShelfMessage() } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) tableView.snp.makeConstraints { (make) in make.edges.equalToSuperview() } if #available(iOS 11.0, *) { tableView.contentInsetAdjustmentBehavior = .never } else { // Fallback on earlier versions } self.navigationController?.navigationBar.barTintColor = UIColor ( red: 0.7235, green: 0.0, blue: 0.1146, alpha: 1.0 ) } func configureTableDataSource(){ view.addSubview(tableView) //RxTableViewSectionedReloadDataSource的必须遵循协议:SectionModelType let dataSource = RxTableViewSectionedReloadDataSource(configureCell: { dataSource, tableView, indexPath, item in let cell = tableView.dequeueReusableCell(withIdentifier: SwipableCell.reuseIdentifier, for: indexPath) as! SwipableCell cell.configureCell(model: item) return cell }) viewModel .section? .drive(tableView.rx.items(dataSource:dataSource)) .disposed(by: disposeBag) let header = initRefreshHeader(tableView) { self.viewModel.refreshCommand.onNext([:]) self.viewModel.fetchShelfMessage() } headerRefresh = header headerRefresh?.beginRefreshing() viewModel .autoSetRefreshHeaderStatus(header: header, footer: nil) .disposed(by: disposeBag) tableView .rx .setDelegate(self) .disposed(by: disposeBag) } func configureNavigateOnRowClick(){ tableView .rx .itemSelected .bind { [unowned self] indexPath in self.tableView.deselectRow(at: indexPath, animated: true) let books = self.viewModel.books if let model = books[books.allKeys()[indexPath.row]] as? BookDetail { let viewController = QSTextRouter.createModule(bookDetail: model, callback: { (book) in BookManager.calTime { // 计算本地数据存储用时 BookManager.shared.modifyBookshelf(book: book) self.tableView.reloadRow(at: indexPath, with: .automatic) } }) self.present(viewController, animated: true, completion: nil) } } .disposed(by: disposeBag) } func configureShelfMessage(){ viewModel .rx .observe(ZSRootViewModel.self, #keyPath(ZSRootViewModel.shelfMessage)) .debug() .observeOn(MainScheduler.instance) .subscribe(onNext: { (model) in self.tableView.reloadData() }, onError: { (error) in QSLog("error:\(error)") }, onCompleted: { }) .disposed(by: disposeBag) } //MARK: - UITableViewDelegate func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { if let message = viewModel.shelfMessage { let title = message.postMessage() shelfMsg.setTitle(title.1, for: .normal) shelfMsg.setTitleColor(title.2, for: .normal) return shelfMsg } return UIView() } func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { if let _ = viewModel.shelfMessage?.postLink { return kHeaderBigHeight } return 0.01 } } ================================================ FILE: zhuishushenqi/Root/Controllers/ZSForumViewController.swift ================================================ // // ZSForumViewController.swift // zhuishushenqi // // Created by caony on 2018/6/7. // Copyright © 2018年 QS. All rights reserved. // import UIKit import RxCocoa import RxSwift import SnapKit class ZSForumViewController: BaseViewController,UITableViewDelegate, UITableViewDataSource { var titles:[[String:String]] = [ ["title":"动态","image":"d_icon"], ["title":"综合讨论区","image":"f_ramble_icon"], ["title":"书评区[找书必看]","image":"forum_public_review_icon"], ["title":"书荒互助区","image":"forum_public_help_icon"], ["title":"活动福利","image":"fuliv3"], ["title":"原创写作","image":"yuanchuangv3"], ["title":"女生区","image":"nvshengv3"], ["title":"电子竞技","image":"jinjiv3"], ["title":"二次元","image":"erciyuanv3"], ["title":"网文江湖","image":"wangwenv3"], ["title":"大话历史","image":"lishiv3"], ["title":"浏览记录","image":"f_invent_icon"], ["title":"导入本地书籍","image":"f_invent_icon"], ["title":"切换新版","image":"f_invent_icon"]] lazy var tableView:UITableView = { let table = UITableView(frame: CGRect.zero, style: .grouped) table.qs_registerCellClass(ZSForumCell.self) return table }() let disposeBag = DisposeBag() override func viewDidLoad() { super.viewDidLoad() configureTableDataSource() configureNavigateOnRowClick() } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) tableView.snp.makeConstraints { (make) in make.edges.equalToSuperview() } self.navigationController?.navigationBar.barTintColor = UIColor ( red: 0.7235, green: 0.0, blue: 0.1146, alpha: 1.0 ) } func showExchangeAlert() { // 新版本弹窗入口 let alertVC = UIAlertController(title: "提示", message: "现已开发追书神器新版本,是否切换至新版[切换后无法撤销]?", preferredStyle: UIAlertController.Style.alert) let okAction = UIAlertAction(title: "确定", style: UIAlertAction.Style.default) { (action) in let tabVC = ZSTabBarController() let delegate = UIApplication.shared.delegate as! AppDelegate delegate.lastTabVC = delegate.window!.rootViewController! UIView.transition(from: delegate.window!.rootViewController!.view, to: tabVC.view, duration: 1, options: UIView.AnimationOptions.transitionCrossDissolve, completion: { (finished) in if finished { delegate.window?.rootViewController = tabVC UserDefaults.standard.setValue(true, forKey: rootVCKey) } }) } let cancelAction = UIAlertAction(title: "取消", style: UIAlertAction.Style.default) { (action) in } alertVC.addAction(okAction) alertVC.addAction(cancelAction) present(alertVC, animated: true, completion: nil) } func configureTableDataSource(){ view.addSubview(tableView) tableView.dataSource = self // let items = Observable.just(titles.map { $0 }) // // items.bind(to: tableView.rx.items(cellIdentifier: "ZSForumCell", cellType: ZSForumCell.self)) { (row,element,cell) in // cell.selectionStyle = .none // let name = (element as! NSDictionary)["image"] as? String ?? "" // let title = (element as! NSDictionary)["title"] as? String // cell.textLabel?.text = title // cell.imageView?.image = UIImage(named:name) // cell.textLabel?.font = UIFont.systemFont(ofSize: 15) // cell.accessoryType = .disclosureIndicator // } // .disposed(by: disposeBag) // tableView.rx.setDelegate(self).disposed(by: disposeBag) tableView.delegate = self } func configureNavigateOnRowClick(){ tableView.rx.itemSelected.bind { [unowned self] indexPath in self.tableView.deselectRow(at: indexPath, animated: true) if indexPath.row == 1 { let discussVC = ZSDiscussViewController() discussVC.title = self.titles[indexPath.row]["title"] discussVC.block = "ramble" SideVC.navigationController?.pushViewController(discussVC, animated: true) } else if indexPath.row == 2 { let reviewVC = ZSBookReviewViewController() SideVC.navigationController?.pushViewController(reviewVC, animated: true) } else if indexPath.row == 3{ let lookVC = LookBookViewController() SideVC.navigationController?.pushViewController(lookVC, animated: true) }else if indexPath.row == 4{ let lookVC = ZSDiscussViewController() lookVC.block = "fuli" lookVC.title = self.titles[indexPath.row]["title"] SideVC.navigationController?.pushViewController(lookVC, animated: true) }else if indexPath.row == 5{ let lookVC = ZSDiscussViewController() lookVC.block = "original" lookVC.title = self.titles[indexPath.row]["title"] SideVC.navigationController?.pushViewController(lookVC, animated: true) } else if indexPath.row == 6 { let femaleVC = ZSFemaleViewController() SideVC.navigationController?.pushViewController(femaleVC, animated: true) }else if indexPath.row == 7 { let femaleVC = ZSDiscussViewController() femaleVC.title = self.titles[indexPath.row]["title"] femaleVC.block = "yingxiong" SideVC.navigationController?.pushViewController(femaleVC, animated: true) }else if indexPath.row == 8 { let femaleVC = ZSDiscussViewController() femaleVC.title = self.titles[indexPath.row]["title"] femaleVC.block = "erciyuan" SideVC.navigationController?.pushViewController(femaleVC, animated: true) }else if indexPath.row == 9 { let femaleVC = ZSDiscussViewController() femaleVC.title = self.titles[indexPath.row]["title"] femaleVC.block = "wangwen" SideVC.navigationController?.pushViewController(femaleVC, animated: true) }else if indexPath.row == 10 { let femaleVC = ZSDiscussViewController() femaleVC.title = self.titles[indexPath.row]["title"] femaleVC.block = "dahua" SideVC.navigationController?.pushViewController(femaleVC, animated: true) } else if indexPath.row == 11 { let historyVC = ReadHistoryViewController() SideVC.navigationController?.pushViewController(historyVC, animated: true) } else if indexPath.row == 12 { let importBookVC = ZSImportBookViewController() SideVC.navigationController?.pushViewController(importBookVC, animated: true) } else if indexPath.row == 13 { self.showExchangeAlert() } else{ let dynamicVC = DynamicViewController() SideVC.navigationController?.pushViewController(dynamicVC, animated: true) } }.disposed(by: disposeBag) } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return titles.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.qs_dequeueReusableCell(ZSForumCell.self) as ZSForumCell cell.selectionStyle = .none let element = titles[indexPath.row] let name = element["image"] ?? "" let title = element["title"] cell.textLabel?.text = title cell.imageView?.image = UIImage(named:name) cell.textLabel?.font = UIFont.systemFont(ofSize: 15) cell.accessoryType = .disclosureIndicator return cell } func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { return UIView() } func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { return 0.01 } func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { return 50 } } class ZSForumCell:UITableViewCell{ override func layoutSubviews() { super.layoutSubviews() self.imageView?.frame = CGRect(x: 20, y: 9, width: 30, height: 30) self.textLabel?.frame = CGRect(x: 65, y: 0, width: self.bounds.width - 65 - 38, height: 49.67) self.separatorInset = UIEdgeInsets(top: 0, left: 65, bottom: 0, right: 0) } } ================================================ FILE: zhuishushenqi/Root/Controllers/ZSImportBookViewController.swift ================================================ // // ZSImportBookViewController.swift // zhuishushenqi // // Created by yung on 2018/7/30. // Copyright © 2018年 QS. All rights reserved. // import UIKit class ZSImportBookViewController: BaseViewController { let httpServer = HTTPServer() override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. title = "导入本地书籍" setupSubviews() setupNoti() setupHTTPServer() } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) ipLabel.frame = CGRect(x: 0, y: 0, width: ScreenWidth, height: 21) ipLabel.center = view.center textLabel.frame = CGRect(x: 0, y: 0, width: ScreenWidth, height: 21) textLabel.center = CGPoint(x: ipLabel.centerX, y: ipLabel.centerY - 31) tipLabel.frame = CGRect(x: 0, y: 0, width: 190, height: 40) tipLabel.center = CGPoint(x: ipLabel.centerX, y: ipLabel.centerY + 31) } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) httpServer.stop() } func setupSubviews(){ view.addSubview(textLabel) view.addSubview(ipLabel) view.addSubview(tipLabel) textLabel.isHidden = true ipLabel.isHidden = true tipLabel.isHidden = true } func setupHTTPServer(){ httpServer.setType("_http._tcp.") let webPath = Bundle.main.resourcePath httpServer.setDocumentRoot(webPath) httpServer.setConnectionClass(ZSHTTPConnection.self) do { try httpServer.start() QSLog("IP: \(ZSHTTPTool.getIPAddress(true) ?? ""):\(httpServer.listeningPort())") textLabel.isHidden = false ipLabel.isHidden = false tipLabel.isHidden = false ipLabel.text = "http://\(ZSHTTPTool.getIPAddress(true) ?? ""):\(httpServer.listeningPort())" } catch { QSLog("an error occured :\(error)") } } func setupNoti(){ NotificationCenter.default.addObserver(self, selector: #selector(uploadFinished(noti:)), name: NSNotification.Name.init(rawValue: ZSHTTPConnectionUploadFileFinished), object: nil) } @objc func uploadFinished(noti:Notification){ let fileName = noti.userInfo?["filename"] as? String ?? "" let message = "文件\(fileName)上传成功,请到书架查看" DispatchQueue.main.async { self.hudAddTo(view: self.view, text: message, animated: true) } } lazy var textLabel:UILabel = { let label = UILabel() label.text = "请用电脑打开浏览器,输入以下网址" label.textColor = UIColor.gray label.font = UIFont.systemFont(ofSize: 15) label.textAlignment = .center return label }() lazy var ipLabel:UILabel = { let label = UILabel() label.textColor = UIColor.black label.font = UIFont.systemFont(ofSize: 17) label.textAlignment = .center return label }() lazy var tipLabel:UILabel = { let label = UILabel() label.text = "手机和电脑需要在同一个无线网络下文件上传时请保持屏幕点亮状态" label.textColor = UIColor.gray label.font = UIFont.systemFont(ofSize: 11) label.textAlignment = .center label.numberOfLines = 0 return label }() override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } } ================================================ FILE: zhuishushenqi/Root/Controllers/ZSLocalShelfViewController.swift ================================================ // // ZSLocalShelfViewController.swift // zhuishushenqi // // Created by yung on 2018/7/31. // Copyright © 2018年 QS. All rights reserved. // import UIKit import SnapKit class ZSLocalShelfViewController: BaseViewController ,UITableViewDataSource,UITableViewDelegate { lazy var tableView:UITableView = { let tableView = UITableView(frame: CGRect.zero, style: .grouped) tableView.qs_registerCellClass(SwipableCell.self) tableView.rowHeight = ZSLocalShelfViewController.kCellHeight tableView.estimatedRowHeight = ZSLocalShelfViewController.kCellHeight tableView.dataSource = self tableView.delegate = self return tableView }() static let kCellHeight:CGFloat = 60 var viewModel = ZSLocalShelfViewModel() override func viewDidLoad() { super.viewDidLoad() title = "本地书架" view.addSubview(tableView) // viewModel.fetchBook(path: <#T##String#>, completion: <#T##((BookDetail) -> Void)?##((BookDetail) -> Void)?##(BookDetail) -> Void#>) // viewModel.fetchBook(path: <#T##String#>, completion: <#T##((BookDetail) -> Void)?##((BookDetail) -> Void)?##(BookDetail) -> Void#>) } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) tableView.snp.makeConstraints { (make) in make.left.right.bottom.equalToSuperview() make.top.equalToSuperview().offset(kNavgationBarHeight) } } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } //MARK: - UITableViewDataSource func numberOfSections(in tableView: UITableView) -> Int { return 1 } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return viewModel.paths.count } func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { return 10 } func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { return 0.01 } func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat { return ZSLocalShelfViewController.kCellHeight } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: SwipableCell.reuseIdentifier, for: indexPath) as! SwipableCell cell.title?.text = ((viewModel.paths[indexPath.row] as NSString).lastPathComponent as NSString).deletingPathExtension return cell } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) showActivityView() let path = viewModel.paths[indexPath.row] viewModel.fetchBook(path: path) { (book) in self.hideActivityView() let viewController = QSTextRouter.createModule(bookDetail: book, callback: { (book) in BookManager.calTime { // 计算本地数据存储用时 BookManager.shared.modifyBookshelf(book: book) self.tableView.reloadRow(at: indexPath, with: .automatic) } }) self.present(viewController, animated: true, completion: nil) } } } ================================================ FILE: zhuishushenqi/Root/Controllers/ZSLoginViewController.swift ================================================ // // ZSLoginViewController.swift // zhuishushenqi // // Created by caony on 2018/10/22. // Copyright © 2018 QS. All rights reserved. // import UIKit import SafariServices typealias ZSLoginVCBackHandler = ()->Void typealias ZSLoginVCResultHandler = (_ success:Bool)->Void let LoginSuccess = "LoginSuccess" class ZSLoginViewController: BaseViewController { lazy var phoneImageView:UIImageView = { let imageView = UIImageView(frame: CGRect(x: 0, y: 100, width: 40, height: 74)) imageView.image = UIImage(named: "artboard") return imageView }() lazy var loginView:ZSLoginView = { let loginView = ZSLoginView(frame: CGRect(x: 0, y: self.phoneImageView.frame.maxY + 50, width: self.view.bounds.width, height: 190)) return loginView }() lazy var thirdLoginView:ZSThirdLoginView = { let thirdLoginView = ZSThirdLoginView(frame: CGRect(x: 0, y: self.loginView.frame.maxY, width: self.view.bounds.width, height: 320)) return thirdLoginView }() var loginVerifyView:ZSLoginVerifyView! let viewModel = ZSMyViewModel() var backHandler:ZSLoginVCBackHandler? var loginResultHandler:ZSLoginVCResultHandler? override func viewDidLoad() { super.viewDidLoad() self.thirdLoginView.thirdLoginHandler = { [weak self] type in self?.thirdLogin(type: type) } self.thirdLoginView.closeHandler = { [weak self] in self?.closeLoginView() } self.thirdLoginView.userProtocolHandler = { [weak self] in self?.userProtocolAction() } self.loginView.getSMSCodeHandler = { [weak self] in self?.getSMSCodeVerify() } self.loginView.loginHandler = { [weak self] in self?.login() } self.phoneImageView.centerX = self.view.centerX self.view.addSubview(self.phoneImageView) self.view.addSubview(self.loginView) self.view.addSubview(self.thirdLoginView) } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) navigationController?.setNavigationBarHidden(true, animated: false) } override func popAction() { backHandler?() self.loginView.destroy() super.popAction() } private func closeLoginView() { backHandler?() self.loginView.destroy() self.dismiss(animated: true, completion: nil) } private func userProtocolAction() { if let url = URL(string: "") { let safariVC = SFSafariViewController(url: url) self.present(safariVC, animated: true, completion: nil) } } private func thirdLogin(type:ThirdLoginType) { switch type { case .QQ: ZSThirdLogin.share.loginResultHandler = { [weak self] success in self?.loginResultHandle(success: success, type: .QQ) } ZSThirdLogin.share.QQAuth() break case .WX: ZSThirdLogin.share.loginResultHandler = { [weak self] success in self?.loginResultHandle(success: success, type: .WX) } ZSThirdLogin.share.WXAuth() break case .WB: ZSThirdLogin.share.loginResultHandler = { [weak self] success in self?.loginResultHandle(success: success, type: .WB) } ZSThirdLogin.share.WBAuth() break case .XM: self.view.showTip(tip: "尚未支持此种登录方式") break default: break } } private func getSMSCode(param:[String:String]) { viewModel.fetchSMSCode(param: param) { [weak self] (json) in if json?["ok"] as? Bool == true { // 获取验证码成功,开始计时 self?.loginView.fire() self?.loginVerifyView.removeFromSuperview() } else { self?.loginVerifyView.removeFromSuperview() } } } private func getSMSCodeVerify() { let mobile = self.loginView.phoneNumber() var errorMsg = "" if mobile.lengthOfBytes(using: .utf8) == 0 { errorMsg = "请输入手机号" } else if mobile.lengthOfBytes(using: .utf8) != 11 { errorMsg = "手机号输入错误" } if errorMsg != "" { self.view.showTip(tip: errorMsg) return } ZSMobileLogin.share.mobile = mobile loginVerifyView = ZSLoginVerifyView(frame: self.view.bounds) loginVerifyView.resultHandler = { [weak self] (ret, param) in if ret == 0 { self?.getSMSCode(param: param) } else { self?.view.showTip(tip: "验证失败") } } self.view.addSubview(loginVerifyView) let html = ZSMobileLogin.share.startVerifyHTML() loginVerifyView.startVerify(str: html) } private func login() { let mobile = self.loginView.phoneNumber() let smsCode = self.loginView.smsCodeText() var errorMsg = "" if mobile.lengthOfBytes(using: .utf8) == 0 { errorMsg = "请输入手机号" } else if mobile.lengthOfBytes(using: .utf8) != 11 { errorMsg = "请输入一个正确的手机号" } else if smsCode.lengthOfBytes(using: .utf8) == 0 { errorMsg = "请输入验证码" } else if smsCode.lengthOfBytes(using: .utf8) != 4 { errorMsg = "请输入一个正确的验证码" } if errorMsg != "" { self.view.showTip(tip: errorMsg) return } self.view.showProgress() viewModel.mobileLogin(mobile: mobile, smsCode: smsCode) { [weak self] (json) in self?.view.hideProgress() if let user = json { if user.ok == true { ZSMobileLogin.share.userInfo = user self?.view.showTip(tip: "登录成功") NotificationCenter.qs_postNotification(name: LoginSuccess, obj: nil) ZSLogin.share.token = ZSMobileLogin.share.userInfo?.token ?? "" ZSThirdLoginStorage.share.saveUserInfo(userInfo: user, type: .WX) self?.backHandler?() self?.dismiss(animated: true, completion: nil) } else { self?.view.showTip(tip: user.code) } } } } private func loginResultHandle(success:Bool,type:ThirdLoginType) { loginResultHandler?(success) if success { // backHandler?() self.dismiss(animated: true, completion: nil) } else { // 登录失败 self.view.showTip(tip: "登录失败,请稍后再试") } } } ================================================ FILE: zhuishushenqi/Root/Controllers/ZSModifyNicknameViewController.swift ================================================ // // ZSModifyNicknameViewController.swift // zhuishushenqi // // Created by yung on 2018/10/25. // Copyright © 2018年 QS. All rights reserved. // import UIKit class ZSModifyNicknameViewController: BaseViewController { let viewModel = ZSMyViewModel() private var nicknameTextField:ZSLoginTextField! private var tipLabel:UILabel! var nickname:String = "" override func viewDidLoad() { super.viewDidLoad() setupSubviews() let rightItem = UIBarButtonItem(title: "保存", style: .plain, target: self, action: #selector(saveAction)) self.navigationItem.rightBarButtonItem = rightItem } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) nicknameTextField.becomeFirstResponder() nicknameTextField.text = nickname } private func setupSubviews() { nicknameTextField = ZSLoginTextField(frame: CGRect(x: 0, y: kNavgationBarHeight + 30, width: self.view.bounds.width, height: 50)) nicknameTextField.font = UIFont.systemFont(ofSize: 15) nicknameTextField.textColor = UIColor.black nicknameTextField.backgroundColor = UIColor.white nicknameTextField.textAlignment = .left nicknameTextField.placeholder = "请输入昵称" view.addSubview(nicknameTextField) tipLabel = UILabel(frame: CGRect(x: 15, y: nicknameTextField.frame.maxY , width: self.view.bounds.width - 30, height: 30)) tipLabel.font = UIFont.systemFont(ofSize: 13) tipLabel.textAlignment = .left tipLabel.textColor = UIColor.gray tipLabel.text = "修改后需要30天才能再次修改" view.addSubview(tipLabel) } @objc func saveAction() { let nickname = nicknameTextField.text ?? "" if nickname == self.nickname { self.view.showTipView(tip: "新昵称不能与原昵称相同") } viewModel.fetchNicknameChange(nickname: nickname, token: ZSLogin.share.token) { (json) in if json?["ok"] as? Bool == true { self.view.showTipView(tip: "昵称修改成功") self.navigationController?.popViewController(animated: true) } else { if let msg = json?["msg"] as? String { self.view.showTipView(tip: msg) } } } } } ================================================ FILE: zhuishushenqi/Root/Controllers/ZSMyViewController.swift ================================================ // // ZSMyViewController.swift // zhuishushenqi // // Created by yung on 2018/10/20. // Copyright © 2018年 QS. All rights reserved. // import UIKit class ZSMyViewController: ZSBaseTableViewController { var cells = [[], [["title":"我的账户", "image":"userCenter_account", "rightB":"充值"], ["title":"经验等级", "image":"userCenter_experience", "rightL":"1级"], ["title":"激活兑换码", "image":"userCenter_exchange", ]], [["title":"阅读历史", "image":"userCenter_history", ] ], [["title":"消息", "image":"userCenter_msg", ], ["title":"书荒提问", "image":"userCenter_rate", ], ["title":"话题", "image":"userCenter_topic", ], ["title":"书单", "image":"userCenter_bookList", ] ], [["title":"给追书神器好评", "image":"userCenter_comment", "rightL":"+15经验"], ["title":"微信公众号", "image":"userCenter_wechat", "rightL":"送书券"], ["title":"设置", "image":"userCenter_setting2", ] ] ] let viewModel:ZSMyViewModel = ZSMyViewModel() var backHandler:ZSLoginVCBackHandler? override func viewDidLoad() { super.viewDidLoad() title = "我" var lv = "1" if let token = ZSThirdLogin.share.userInfo { lv = "\(token.user?.lv ?? 1)" } else if let token = ZSMobileLogin.share.userInfo { lv = "\(token.user?.lv ?? 1)" } cells[1][1]["rightL"] = lv self.tableView.reloadData() request() } func request() { self.view.showProgress() viewModel.fetchAccount(token: ZSLogin.share.token) { (account) in self.view.hideProgress() self.tableView.reloadData() } viewModel.fetchCoin(token: ZSLogin.share.token) { (coin) in self.view.hideProgress() self.tableView.reloadData() } } override func popAction() { backHandler?() super.popAction() } //MARK: - override func numberOfSections(in tableView: UITableView) -> Int { return cells.count } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { let sectionModel = cells[section] return sectionModel.count } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let model = cells[indexPath.section][indexPath.row] if let right = model["rightB"] { let cell = tableView.qs_dequeueReusableCell(ZSRTMyCell.self) cell?.rightTitle = right cell?.textLabel?.text = model["title"] cell?.imageView?.image = UIImage(named: model["image"] ?? "") return cell! } else if let right = model["rightL"] { let cell = tableView.qs_dequeueReusableCell(ZSRLMyCell.self) cell?.rightTitle = right cell?.textLabel?.text = model["title"] cell?.imageView?.image = UIImage(named: model["image"] ?? "") return cell! } else { let cell = tableView.qs_dequeueReusableCell(UITableViewCell.self) cell?.textLabel?.text = model["title"] cell?.imageView?.image = UIImage(named: model["image"] ?? "") cell?.accessoryType = .disclosureIndicator return cell! } } override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { if section == 0 { let header = tableView.qs_dequeueReusableHeaderFooterView(ZSMyHeaderView.self) header?.userInfo = ZSThirdLogin.share.userInfo header?.account = viewModel.account header?.coin = viewModel.coin header?.frame = CGRect(x: 0, y: 0, width: self.view.bounds.width, height: 260) header?.handler = { let userInfoVC = ZSUserInfoViewController.init(style: .grouped) self.navigationController?.pushViewController(userInfoVC, animated: true) } return header } return nil } override func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? { if section == cells.count - 1 { let footer = tableView.qs_dequeueReusableHeaderFooterView(ZSMyFooterView.self) footer?.footerHandler = { self.view.showProgress() self.viewModel.fetchLogout(token: ZSLogin.share.token , completion: { (json) in self.hideProgress() if let ok = json?["ok"] as? Bool { if ok { ZSLogin.share.logout() //退出登录成功,关闭菜单 self.navigationController?.popViewController(animated: false) SideVC.closeSideViewController() } else { self.view.showTip(tip: "退出登录失败,请稍后再试") } } }) } return footer } return UIView() } override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { if section == 0 { return 260 } return 0.01 } override func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { if section == 0 { return 50 } else if section == cells.count - 1 { return 100 } return 20 } override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) if indexPath.section == 1 { if indexPath.row == 0 { let userAccountVC = ZSUserAccountViewController() self.navigationController?.pushViewController(userAccountVC, animated: true) } } } //MARK: - override func registerCellClasses() -> Array { return [ZSRLMyCell.self,ZSRTMyCell.self,UITableViewCell.self] } override func registerHeaderViewClasses() -> Array { return [ZSMyHeaderView.self,ZSMyFooterView.self] } } typealias ZSMyFooterHandler = ()->Void class ZSMyFooterView: UITableViewHeaderFooterView { var button:UIButton! var footerHandler:ZSMyFooterHandler? override init(reuseIdentifier: String?) { super.init(reuseIdentifier: reuseIdentifier) button = UIButton(type: .custom) button.setTitle("退出登录", for: .normal) button.setTitleColor(UIColor.white, for: .normal) button.backgroundColor = UIColor.red button.frame = CGRect(x: 20, y: 25, width: self.bounds.width - 40, height: 50) button.addTarget(self, action: #selector(footerAction(btn:)), for: .touchUpInside) self.addSubview(button) } @objc func footerAction(btn:UIButton) { footerHandler?() } override func layoutSubviews() { super.layoutSubviews() button.frame = CGRect(x: 20, y: 25, width: self.bounds.width - 40, height: 50) } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } } ================================================ FILE: zhuishushenqi/Root/Controllers/ZSRootViewController.swift ================================================ // // ZSRootViewController.swift // zhuishushenqi // // Created by caony on 2018/5/21. // Copyright © 2018年 QS. All rights reserved. // import UIKit import RxSwift import RxCocoa import SnapKit class ZSRootViewController: BaseViewController,UITableViewDelegate,UICollectionViewDelegate,UICollectionViewDataSource,UICollectionViewDelegateFlowLayout,SegMenuDelegate { static let kCellHeight:CGFloat = 60 lazy var tableView:UITableView = { let table = UITableView(frame: CGRect.zero, style: .grouped) table.estimatedRowHeight = ZSRootViewController.kCellHeight table.rowHeight = UITableView.automaticDimension return table }() var collectionView:UICollectionView! var segMenu:SegMenu! var viewControllers:[UIViewController] = [] let disposeBag = DisposeBag() let recommend = ZSRecommend() private var recView:QSLaunchRecView! private var tipImageView:UIImageView! override func viewDidLoad() { super.viewDidLoad() NotificationCenter.default.addObserver(self, selector: #selector(showRecommend), name: Notification.Name(rawValue:SHOW_RECOMMEND), object: nil) NotificationCenter.default.addObserver(self, selector: #selector(rootDisappear), name: Notification.Name(rawValue:RootDisappearNotificationName), object: nil) RootNavigationView.make(delegate: self,leftAction: #selector(leftAction(_:)),rightAction: #selector(rightAction(_:))) setupSegMenu() // DispatchQueue.main.asyncAfter(deadline: 5) { // let delegate = UIApplication.shared.delegate as! AppDelegate // delegate.window?.rootViewController = tabVC // } /* let items = Observable.just( [1,2,3].map{ $0 } ) items.bind(to: tableView.rx.items(cellIdentifier: "eee", cellType: UITableViewCell.self)) { (row,element,cell) in cell.textLabel?.text = "" }.disposed(by: DisposeBag()) */ view.backgroundColor = UIColor.cyan configureChildViewController() configureCollectionView() configureCollectionOffset() } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) layoutSubview() if #available(iOS 11.0, *) { collectionView.contentInsetAdjustmentBehavior = .never } else { // Fallback on earlier versions } } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) layoutSubview() } private func layoutSubview() { segMenu.snp.remakeConstraints { (make) in let statusHeight = UIApplication.shared.statusBarFrame.height let navHeight = self.navigationController?.navigationBar.height ?? 0 make.left.right.equalToSuperview() make.top.equalToSuperview().offset(navHeight + statusHeight) make.height.equalTo(kTootSegmentViewHeight) } collectionView.snp.remakeConstraints { (make) in make.left.right.bottom.equalToSuperview() make.top.equalTo(segMenu.snp.bottom) } } override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { coordinator.animate(alongsideTransition: { (context) in self.didSelectAtIndex(self.segMenu.selectedIndex) }, completion: nil) } override func viewWillLayoutSubviews() { segMenu.snp.remakeConstraints { (make) in let statusHeight = UIApplication.shared.statusBarFrame.height let navHeight = self.navigationController?.navigationBar.height ?? 0 make.left.right.equalToSuperview() make.top.equalToSuperview().offset(navHeight + statusHeight) make.height.equalTo(kTootSegmentViewHeight) } collectionView.snp.remakeConstraints { (make) in make.left.right.bottom.equalToSuperview() make.top.equalTo(segMenu.snp.bottom) } collectionView.reloadData() } override func viewDidLayoutSubviews() { self.didSelectAtIndex(self.segMenu.selectedIndex) } func configureChildViewController(){ let shelvesVC = ZSShelfViewController() let forumVC = ZSForumViewController() addChild(shelvesVC) addChild(forumVC) viewControllers.append(shelvesVC) viewControllers.append(forumVC) } func configureCollectionView() { let layout = UICollectionViewFlowLayout() layout.itemSize = CGSize(width: ScreenWidth, height: ScreenHeight - kNavgationBarHeight - kTootSegmentViewHeight) layout.minimumLineSpacing = 0.01 layout.minimumInteritemSpacing = 0.01 layout.scrollDirection = .horizontal collectionView = UICollectionView(frame: CGRect.zero, collectionViewLayout: layout) collectionView.dataSource = self collectionView.delegate = self collectionView.isPagingEnabled = true collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "UICollectionViewCell") collectionView.showsHorizontalScrollIndicator = false collectionView.backgroundColor = UIColor.white view.addSubview(collectionView) } func configureCollectionOffset() -> Void { collectionView.rx.contentOffset .subscribe{ point in // QSLog(point) } .disposed(by: disposeBag) collectionView.rx.didEndDecelerating .subscribe { _ in let contentOffset = self.collectionView.contentOffset if contentOffset.x.truncatingRemainder(dividingBy: ScreenWidth) == 0 { let index = contentOffset.x/ScreenWidth self.segMenu.selectIndex(Int(index)) } } .disposed(by: disposeBag) } @objc func leftAction(_ btn:UIButton){ SideVC.showLeftViewController() } @objc func rightAction(_ btn:UIButton){ SideVC.showRightViewController() } fileprivate func setupSegMenu(){ segMenu = SegMenu(frame: CGRect.zero, WithTitles: ["追书架","追书社区"]) segMenu.menuDelegate = self self.view.addSubview(segMenu) } @objc private func showRecommend(){ // animate recommend.show(boyTipCallback: { (btn) in self.fetchRecList(index: 0) self.dismissRecView() }, girlTipCallback: { (btn) in self.fetchRecList(index: 1) self.dismissRecView() }) { (btn) in self.dismissRecView() } } @objc private func rootDisappear() { layoutSubview() } @objc func dismissRecView(){ self.showUserTipView() } @objc func dismissTipView(sender:Any){ tipImageView.removeFromSuperview() } func showUserTipView(){ tipImageView = UIImageView(frame: self.view.bounds) tipImageView.image = UIImage(named: "add_book_hint") tipImageView.isUserInteractionEnabled = true let tap = UITapGestureRecognizer(target: self, action: #selector(dismissTipView(sender:))) tipImageView.addGestureRecognizer(tap) KeyWindow?.addSubview(tipImageView) } func fetchRecList(index:Int){ let gender = ["male","female"] let recURL = "\(BASEURL)/book/recommend?gender=\(gender[index])" zs_get(recURL) { (json) in if let books = json?["books"] as? [Any] { if let models = [BookDetail].deserialize(from: books) as? [BookDetail] { ZSBookManager.shared.addBooks(books: models) let shelvesVC = self.viewControllerFor(index: 0) as! ZSShelfViewController shelvesVC.headerRefresh?.beginRefreshing() } } } } //MARK: - SegMenuDelegate func didSelectAtIndex(_ index:Int){ let indexPath = IndexPath(row: index, section: 0) collectionView.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: false) } func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { return self.collectionView.bounds.size } //MARK: - public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int{ return viewControllers.count } // The cell that is returned must be retrieved from a call to -dequeueReusableCellWithReuseIdentifier:forIndexPath: @available(iOS 6.0, *) public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "UICollectionViewCell", for: indexPath) cell.contentView.backgroundColor = UIColor.white let viewController = viewControllerFor(index: indexPath.row) if let _ = viewController.view.superview { viewController.view.removeFromSuperview() } cell.contentView.addSubview(viewController.view) viewController.view.snp.remakeConstraints { (make) in make.edges.equalToSuperview() } return cell } func viewControllerFor(index:Int) -> UIViewController{ if index > viewControllers.count - 1 { return UIViewController() } return viewControllers[index] } } ================================================ FILE: zhuishushenqi/Root/Controllers/ZSSettingViewController.swift ================================================ // // ZSSettingViewController.swift // zhuishushenqi // // Created by yung on 2018/8/15. // Copyright © 2018年 QS. All rights reserved. // import UIKit import FLEX class ZSSettingViewController: BaseViewController { var tableView:UITableView! var menu:[ZSMineMenuItem] = [] var viewModel:ZSSettingViewModel = ZSSettingViewModel() override func viewDidLoad() { super.viewDidLoad() setupMenu() setupSubviews() } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) navigationController?.isNavigationBarHidden = false tableView.frame = view.bounds } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } func setupMenu() { let source = ZSMineMenuItem(type: .account, disclosureType: .controllerWithTitle,cellType: .indicator, isSwitchOn: false, title: "书源设置", icon: "personal_icon_account_24_24_24x24_", detailTitle: nil, disclosureText: nil) let regularVerify = ZSMineMenuItem(type: .level, disclosureType: .controllerWithTitle,cellType: .none, isSwitchOn: false, title: "书源规则验证", icon: "personal_icon_account_24_24_24x24_", detailTitle: nil, disclosureText: nil) let exchangeToOld = ZSMineMenuItem(type: .vip, disclosureType: .controllerWithTitle,cellType: .none, isSwitchOn: false, title: "切换旧版", icon: "personal_icon_account_24_24_24x24_", detailTitle: nil, disclosureText: nil) let flex = ZSMineMenuItem(type: .darkmode, disclosureType: .controllerWithTitle,cellType: .swtch, isSwitchOn: false, title: "FLEX开启", icon: "personal_icon_darkmode_24_24_23x23_", detailTitle: nil, disclosureText: nil) let doraemon = ZSMineMenuItem(type: .darkmode, disclosureType: .controllerWithTitle,cellType: .swtch, isSwitchOn: false, title: "Doraemon开启", icon: "personal_icon_darkmode_24_24_23x23_", detailTitle: nil, disclosureText: nil) menu.append(source) menu.append(regularVerify) menu.append(exchangeToOld) menu.append(flex) menu.append(doraemon) } func setupSubviews(){ title = "设置" tableView = UITableView(frame: CGRect.zero, style: .grouped) tableView.dataSource = self tableView.delegate = self tableView.qs_registerCellClass(ZSDetailButtonCell.self) tableView.qs_registerHeaderFooterClass(ZSMyFooterView.self) view.addSubview(tableView) } } extension ZSSettingViewController:UITableViewDataSource,UITableViewDelegate { func numberOfSections(in tableView: UITableView) -> Int { return 1 } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return menu.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.qs_dequeueReusableCell(ZSDetailButtonCell.self) let item = menu[indexPath.row] cell?.type = item.cellType cell?.configure(title: item.title ?? "", icon: item.icon ?? "", detail: item.detailTitle, detailButtonText: item.disclosureText) cell?.detailHandler = { } cell?.detailTextHandler = { } cell?.switchHandler = { [weak item] isOn in guard let sItem = item else { return } if sItem.type == .darkmode { if isOn { FLEXManager.shared().showExplorer() } else { FLEXManager.shared().hideExplorer() } } } return cell! } func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { return nil } func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? { let footer = tableView.qs_dequeueReusableHeaderFooterView(ZSMyFooterView.self) footer?.footerHandler = { [weak self] in self?.view.showProgress() self?.viewModel.fetchLogout(token: ZSLogin.share.token , completion: { [weak self] (json) in self?.hideProgress() if let ok = json?["ok"] as? Bool { if ok { ZSLogin.share.logout() //退出登录成功,关闭菜单 self?.navigationController?.popViewController(animated: false) } else { self?.view.showTip(tip: "退出登录失败,请稍后再试") } } }) } return footer } func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { return 44 } func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { return 44 } func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { return 100 } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { let item = menu[indexPath.row] switch item.type { case .account: let addSourceVC = ZSSourcesViewController() addSourceVC.title = item.title self.navigationController?.pushViewController(addSourceVC, animated: true) break case .vip: showExchangeAlert() break case .level: let regVC = ZSRegularVerifyViewController() regVC.title = item.title self.navigationController?.pushViewController(regVC, animated: true) break default: break } } func showExchangeAlert() { // 新版本弹窗入口 let alertVC = UIAlertController(title: "提示", message: "是否切换至旧版?", preferredStyle: UIAlertController.Style.alert) let okAction = UIAlertAction(title: "确定", style: UIAlertAction.Style.default) { (action) in let sideVC = SideViewController.shared sideVC.contentViewController = ZSRootViewController() sideVC.rightViewController = RightViewController() sideVC.leftViewController = LeftViewController() let sideNavVC = ZSBaseNavigationViewController(rootViewController: sideVC) let delegate = UIApplication.shared.delegate as! AppDelegate let tabVC = delegate.window!.rootViewController! delegate.lastTabVC = tabVC UIView.transition(from: tabVC.view, to: sideNavVC.view, duration: 1, options: UIView.AnimationOptions.transitionCrossDissolve, completion: { (finished) in if finished { delegate.window?.rootViewController = sideNavVC UserDefaults.standard.setValue(true, forKey: rootVCKey) } }) } let cancelAction = UIAlertAction(title: "取消", style: UIAlertAction.Style.default) { (action) in } alertVC.addAction(okAction) alertVC.addAction(cancelAction) present(alertVC, animated: true, completion: nil) } } ================================================ FILE: zhuishushenqi/Root/Controllers/ZSShelfViewController.swift ================================================ // // ZSShelfViewController.swift // zhuishushenqi // // Created by yung on 2018/7/31. // Copyright © 2018年 QS. All rights reserved. // import UIKit import RxSwift import SafariServices import MJRefresh class ZSShelfViewController: BaseViewController,Refreshable,UITableViewDataSource,UITableViewDelegate { lazy var shelfMsg:UIButton = { let btn = UIButton(type: .custom) btn.frame = CGRect(x: 0, y: 0, width: ScreenWidth, height: 44) btn.backgroundColor = UIColor ( red: 1.0, green: 1.0, blue: 1.0, alpha: 0.7 ) btn.setTitleColor(UIColor.gray, for: .normal) btn.titleLabel?.font = UIFont.systemFont(ofSize: 13) return btn }() lazy var tableView:UITableView = { let tableView = UITableView(frame: CGRect.zero, style: .grouped) tableView.qs_registerCellClass(SwipableCell.self) tableView.rowHeight = ZSShelfViewController.kCellHeight tableView.estimatedRowHeight = ZSShelfViewController.kCellHeight tableView.dataSource = self tableView.delegate = self tableView.separatorInset = UIEdgeInsets(top: 0, left: 15, bottom: 0, right: 0) return tableView }() fileprivate let kHeaderBigHeight:CGFloat = 44 static let kCellHeight:CGFloat = 60 fileprivate let disposeBag = DisposeBag() var headerRefresh:MJRefreshHeader? let viewModel = ZSShelfViewModel() override func viewDidLoad() { super.viewDidLoad() setupSubviews() NotificationCenter.qs_addObserver(observer: self, selector: #selector(loginSuccessAction), name: LoginSuccess, object: nil) NotificationCenter.qs_addObserver(observer: self, selector: #selector(addBookToShelf(noti:)), name: BOOKSHELF_ADD, object: nil) NotificationCenter.qs_addObserver(observer: self, selector: #selector(deleteFromShelf(noti:)), name: BOOKSHELF_DELETE, object: nil) loginSuccessAction() } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) tableView.snp.makeConstraints { (make) in make.edges.equalToSuperview() } if #available(iOS 11.0, *) { tableView.contentInsetAdjustmentBehavior = .never } else { // Fallback on earlier versions } self.navigationController?.navigationBar.barTintColor = UIColor ( red: 0.7235, green: 0.0, blue: 0.1146, alpha: 1.0 ) self.tableView.reloadData() } override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } func setupSubviews(){ let header = initRefreshHeader(tableView) { self.viewModel.fetchShelvesBooks(completion: { (_) in self.view.showTip(tip: "更新了几本书") self.tableView.reloadData() }) self.viewModel.fetchShelfMessage(completion: { (_) in self.tableView.reloadData() }) } headerRefresh = header headerRefresh?.beginRefreshing() viewModel .autoSetRefreshHeaderStatus(header: header, footer: nil) .disposed(by: disposeBag) view.addSubview(tableView) viewModel.fetchBlessingBag(token: ZSLogin.share.token) { (json) in print(json) } viewModel.fetchJudgeIn(token: ZSLogin.share.token) { (json) in if json?["ok"] as? Bool == true { if let activityId = json?["activityId"] as? String { self.viewModel.fetchSignIn(token: ZSLogin.share.token, activityId: activityId, version: "2", type: "2", completion: { (json) in if json?["ok"] as? Bool == true { print("签到成功") let amount = json?["amount"] as? Int ?? 0 self.view.showTip(tip: "自动签到获得\(amount)书券") } }) } } } shelfMsg.addTarget(self, action: #selector(openSafari), for: .touchUpInside) } @objc func openSafari() { // 存在三种可能,post,link,booklist if let message = viewModel.shelfMessage { let title = message.postMessage() let type = title.2 if type == .link { if let url = URL(string: title.0) { let safariVC = SFSafariViewController(url: url) self .present(safariVC, animated: true, completion: nil) } } else if type == .post { let id = title.0 let comment = BookComment() comment._id = id let commentVC = ZSBookCommentViewController(style: .grouped) commentVC.viewModel.model = comment SideVC.navigationController?.pushViewController(commentVC, animated: true) } else if type == .booklist { let topicVC = QSTopicDetailRouter.createModule(id: title.0) SideVC.navigationController?.pushViewController(topicVC, animated: true) } } } @objc func loginSuccessAction() { viewModel.fetchShelfAdd(books: viewModel.books.allValues() as! [BookDetail], token: ZSLogin.share.token) { (json) in if json?["ok"] as? Bool == true { self.view.showTip(tip: "书架书籍上传成功") } else { self.view.showTip(tip: "书架书籍上传失败") } } viewModel.fetchUserBookshelf(token: ZSLogin.share.token) { (bookshelf) in self.viewModel.fetchShelvesBooks(completion: { (_) in self.view.showTip(tip: "更新了几本书") self.tableView.reloadData() }) self.tableView.reloadData() } } @objc func deleteFromShelf(noti:Notification) { if let book = noti.object as? BookDetail { self.tableView.reloadData() if ZSLogin.share.hasLogin() { viewModel.fetchShelfDelete(books: [book], token: ZSLogin.share.token) { (json) in if json?["ok"] as? Bool == true { self.view.showTip(tip: "\(book.title)从书架删除成功") self.headerRefresh?.beginRefreshing() } else { self.view.showTip(tip: "\(book.title)从书架删除失败") } } } } } @objc func addBookToShelf(noti:Notification) { if let book = noti.object as? BookDetail { self.tableView.reloadData() if ZSLogin.share.hasLogin() { viewModel.fetchShelfAdd(books: [book], token: ZSLogin.share.token) { (json) in if json?["ok"] as? Bool == true { self.view.showTip(tip: "添加到书架成功") self.headerRefresh?.beginRefreshing() } else { self.view.showTip(tip: "添加到书架失败") } } } else { self.view.showTip(tip: "添加到书架成功") self.headerRefresh?.beginRefreshing() } } } //MARK: - UITableView func numberOfSections(in tableView: UITableView) -> Int { if viewModel.localBooks.count > 0 { return 2 } return 1 } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { if viewModel.localBooks.count > 0 { if section == 0 { return 1 } return viewModel.books.count } // return viewModel.fetchBooks().count return viewModel.booksID.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: SwipableCell.reuseIdentifier, for: indexPath) as! SwipableCell cell.delegate = self cell.selectionStyle = .none if viewModel.localBooks.count > 0 { if indexPath.section == 0 { cell.title?.text = "本地书架" return cell } let id = viewModel.booksID[indexPath.row] if let item = viewModel.books[id] { cell.configureCell(model: item) } } else { // let book = viewModel.fetchBooks()[indexPath.row] // cell.configureCell(model: book) if viewModel.booksID.count > indexPath.row { let id = viewModel.booksID[indexPath.row] if let item = viewModel.books[id] { cell.configureCell(model: item) } } } return cell } //MARK: - UITableViewDelegate func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { if viewModel.localBooks.count > 0 { if section == 0 { if let message = viewModel.shelfMessage { let title = message.postMessage() shelfMsg.setTitle(title.1, for: .normal) shelfMsg.setTitleColor(title.3, for: .normal) return shelfMsg } } } else { if section == 0 { if let message = viewModel.shelfMessage { let title = message.postMessage() shelfMsg.setTitle(title.1, for: .normal) shelfMsg.setTitleColor(title.3, for: .normal) return shelfMsg } } } return UIView() } func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? { return UIView() } func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { if viewModel.localBooks.count > 0 { if section == 0 { if let _ = viewModel.shelfMessage?.postLink { return kHeaderBigHeight } } } else { if section == 0 { if let _ = viewModel.shelfMessage?.postLink { return kHeaderBigHeight } } } return 1 } func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { return 1 } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) // if let url = URL(string: "IFlySpeechPlus://?version=1.006&businessType=1&callType=0") { // UIApplication.shared.openURL(url) // return // } if viewModel.localBooks.count > 0 { if indexPath.section == 0 { let localVC = ZSLocalShelfViewController() SideVC.navigationController?.pushViewController(localVC, animated: true) return } } // let books = viewModel.fetchBooks() // let viewController = ZSReaderViewController() // viewController.viewModel.book = books[indexPath.row] // self.present(viewController, animated: true, completion: nil) // self.tableView.reloadRow(at: indexPath, with: .automatic) let books = self.viewModel.books if let model = books[viewModel.booksID[indexPath.row]] { // 刷新id的排序,当前点击的书籍置顶 viewModel.topBook(key: model._id) let viewController = ZSReaderViewController() viewController.viewModel.book = model self.present(viewController, animated: true, completion: nil) self.tableView.reloadData() } } } extension ZSShelfViewController:SwipableCellDelegate { func swipableCell(swipableCell:SwipableCell, didSelectAt index:Int) { if index == 0 { if let indexPath = tableView.indexPath(for: swipableCell) { alert(with: swipableCell, indexPath: indexPath) } else { self.hudAddTo(view: self.view, text: "当前书籍不存在...", animated: true) } } else if index == 3 { if let indexPath = tableView.indexPath(for: swipableCell) { self.removeBook(at: indexPath.row) } self.tableView.reloadData() } else { self.hudAddTo(view: self.view, text: "暂不支持当前功能,敬请期待...", animated: true) } } func alert(with cell:SwipableCell, indexPath:IndexPath) { let alert = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) let firstAcion = UIAlertAction(title: "全本缓存", style: .default, handler: { (action) in cell.state = .prepare let ids = self.viewModel.booksID if indexPath.row >= 0 && indexPath.row < ids.count { let id = ids[indexPath.row] guard let book = self.viewModel.books[id] else { return } cell.state = .download ZSBookDownloader.shared.download(book: book, start: 0, handler: { (finish) in // cell状态变更 cell.state = .finish }) } else { cell.state = .none } }) let secondAction = UIAlertAction(title: "从当前章节缓存", style: .default, handler: { (action) in cell.state = .prepare let ids = self.viewModel.booksID if indexPath.row > 0 && indexPath.row < ids.count { let id = ids[indexPath.row] guard let book = self.viewModel.books[id] else { return } if let chapter = book.record?.chapter, let chaptersInfo = book.chaptersInfo { cell.state = .download if chapter < chaptersInfo.count { ZSBookDownloader.shared.download(book: book, start: chapter, handler: { (finish) in // cell状态变更 cell.state = .finish }) } else { ZSBookDownloader.shared.download(book: book, start: 0, handler: { (finish) in // cell状态变更 cell.state = .finish }) } } else { cell.state = .none } } else { cell.state = .none } }) let cancelAction = UIAlertAction(title: "取消", style: .cancel, handler: { (action) in cell.state = .none }) alert.addAction(firstAcion) alert.addAction(secondAction) alert.addAction(cancelAction) present(alert, animated: true, completion: nil) } func removeBook(at index:Int){ let books = self.viewModel.booksID guard let bookid = books[safe: index] else { return } if let book = self.viewModel.books[bookid] { ZSBookManager.shared.deleteBook(book: book) self.viewModel.fetchShelfDelete(books: [book], token: ZSLogin.share.token) { (json) in if json?["ok"] as? Bool == true { self.view.showTip(tip: "\(book.title)已从书架中删除") } else { self.view.showTip(tip: "\(book.title)从书架中删除失败") } } } else { let book = BookDetail() book._id = bookid ZSBookManager.shared.deleteBook(book: book) } } } ================================================ FILE: zhuishushenqi/Root/Controllers/ZSUserAccountViewController.swift ================================================ // // ZSUserAccountViewController.swift // zhuishushenqi // // Created by yung on 2019/1/9. // Copyright © 2019年 QS. All rights reserved. // import UIKit class ZSUserAccountViewController: ZSBaseTableViewController { var viewModel:ZSMyViewModel = ZSMyViewModel() var cells = [ ["header":"财产","section":[ ["title":"追书币","detail":"","image":"a_zhuishubi_new","center":"","type":"btlabel","rightLabelTitle":"","rightTitle":"充值"], ["title":"追书券","detail":"","image":"a_zhuishuquan_new","center":"","type":"label","rightLabelTitle":""], ["title":"兑换追书券","detail":"","image":"a_exchange_new","center":"","type":"none"] ]], ["header":"记录","section":[ ["title":"充值记录","detail":"","image":"a_charge_record_new","center":"","type":"none"], ["title":"消费记录","detail":"","image":"a_purchase_record_new","center":"","type":"none"] ] ], ["header":"","section":[ ["title":"版本号","detail":"用户ID","image":"","center":"","type":"detail"] ] ] ] override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. title = "个人账户" setupSubviews() viewModel.fetchAccount(token: ZSLogin.share.token) { (account) in if let acc = account { self.updateCells(acc: acc) } } } func updateCells(acc:ZSAccount) { let sectionOne = cells[0] let list = sectionOne["section"] as! [[String:String]] var zhuishubi = list[0] var zhuishuquan = list[1] let addquan = list[2] zhuishubi["rightLabelTitle"] = "余额: \(acc.balance)" zhuishuquan["rightLabelTitle"] = "\(acc.voucherCount)张" cells[0]["section"] = [zhuishubi,zhuishuquan,addquan] self.tableView.reloadData() } func setupSubviews() { } override func registerCellClasses() -> Array { return [ZSRLTMyCell.self,ZSRLMyCell.self,UITableViewCell.self] } override func registerHeaderViewClasses() -> Array { return [ZSUserAccountHeaderView.self] } override func numberOfSections(in tableView: UITableView) -> Int { return cells.count } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { if let sectionInfo = cells[section]["section"] as? [[String:String]] { return sectionInfo.count } return 0 } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let sectionInfo = cells[indexPath.section]["section"] as! [[String:String]] let dict = sectionInfo[indexPath.row] let type = dict["type"] ?? "" let title = dict["title"] ?? "" let image = dict["image"] ?? "" if type == "btlabel" { let rightTitle = dict["rightLabelTitle"] ?? "" let rightBtTitle = dict["rightTitle"] ?? "" let cell = tableView.qs_dequeueReusableCell(ZSRLTMyCell.self) cell?.textLabel?.text = title cell?.imageView?.image = UIImage(named: image) cell?.rightTitle = rightBtTitle cell?.rightLabelTitle = rightTitle return cell! } else if type == "label" { let rightBtTitle = dict["rightLabelTitle"] ?? "" let cell = tableView.qs_dequeueReusableCell(ZSRLMyCell.self) cell?.textLabel?.text = title cell?.imageView?.image = UIImage(named: image) cell?.rightTitle = rightBtTitle cell?.accessoryType = .disclosureIndicator return cell! } else { let cell = tableView.qs_dequeueReusableCell(UITableViewCell.self) cell?.textLabel?.text = title cell?.imageView?.image = UIImage(named: image) cell?.accessoryType = .disclosureIndicator return cell! } } override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { let section = cells[section] let title = section["header"] as? String let headerView = tableView.qs_dequeueReusableHeaderFooterView(ZSUserAccountHeaderView.self) headerView?.titleLabel.text = title return headerView } override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { return 50 } override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { return 50 } override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) if indexPath.section == 0 { if indexPath.row == 1 { let sectionInfo = cells[indexPath.section]["section"] as! [[String:String]] let dict = sectionInfo[indexPath.row] let title = dict["title"] ?? "" let voucherVC = ZSVoucherParentViewController() voucherVC.title = title self.navigationController?.pushViewController(voucherVC, animated: true) } } } } class ZSUserAccountHeaderView: UITableViewHeaderFooterView { var titleLabel:UILabel! override init(reuseIdentifier: String?) { super.init(reuseIdentifier: reuseIdentifier) setupSubviews() } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } private func setupSubviews() { titleLabel = UILabel(frame: CGRect(x: 20, y: 20, width: 200, height: 20)) titleLabel.textColor = UIColor.gray titleLabel.font = UIFont.systemFont(ofSize: 11) addSubview(titleLabel) } } ================================================ FILE: zhuishushenqi/Root/Controllers/ZSUserInfoViewController.swift ================================================ // // ZSUserInfoViewController.swift // zhuishushenqi // // Created by yung on 2018/10/20. // Copyright © 2018年 QS. All rights reserved. // import UIKit class ZSUserInfoViewController: ZSBaseTableViewController { var cells = [[["title":"头像","detail":"","image":"","center":"","type":"custom"], ["title":"昵称","detail":"","image":"","center":"","type":"label"], ["title":"性别","detail":"","image":"","center":"","type":"label"], ["title":"用户ID","detail":"复制","image":"","center":"","type":"all"]], [["title":"手机号","detail":"","image":"","center":"","type":"bind"], ["title":"QQ","detail":"","image":"","center":"","type":"bind"], ["title":"微信","detail":"","image":"","center":"","type":"bind"], ["title":"微博","detail":"","image":"","center":"","type":"bind"] ]] lazy var iconView:UIImageView = { let imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 40, height: 40)) imageView.isUserInteractionEnabled = true imageView.layer.cornerRadius = 20 imageView.layer.masksToBounds = true return imageView }() var viewModel:ZSMyViewModel = ZSMyViewModel() override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. self.tableView.rowHeight = 50 title = "个人信息" } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) request() } func request() { view.showProgress() viewModel.fetchDetail(token: ZSLogin.share.token) { [weak self](detail) in guard let sSelf = self else { return } sSelf.view.hideProgress() sSelf.updateUserInfo() sSelf.tableView.reloadData() } viewModel.fetchUserBind(token: ZSLogin.share.token) { [weak self](bind) in guard let sSelf = self else { return } sSelf.view.hideProgress() sSelf.updateBind() sSelf.tableView.reloadData() } } func updateUserInfo() { cells[0][0]["image"] = viewModel.detail?.avatar ?? "" cells[0][1]["detail"] = viewModel.detail?.nickname ?? "" cells[0][2]["detail"] = genderHZ(gender: viewModel.detail?.gender ?? "") cells[0][3]["center"] = viewModel.detail?._id ?? "" } func updateBind() { if let bind = viewModel.bind { let mobile = bind.bind?.Mobile if mobile?.name != "" { cells[1][0]["detail"] = "已绑定" cells[1][0]["center"] = mobile?.name ?? "" } let qq = bind.bind?.QQ if qq?.name != "" { cells[1][1]["detail"] = "已绑定" cells[1][1]["center"] = qq?.name ?? "" } let wx = bind.bind?.Weixin if wx?.name != "" { cells[1][2]["detail"] = "已绑定" cells[1][2]["center"] = wx?.name ?? "" } let wb = bind.bind?.SinaWeibo if wb?.name != "" { cells[1][3]["detail"] = "已绑定" cells[1][3]["center"] = wb?.name ?? "" } } } func genderHZ(gender:String) -> String { var name = "" if gender == "female" { name = "女" } else if gender == "male" { name = "男" } return name } //MARK: - override func numberOfSections(in tableView: UITableView) -> Int { return cells.count } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return cells[section].count } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let dict = cells[indexPath.section][indexPath.row] if let type = dict["type"] { if type == "custom" { let cell = tableView.qs_dequeueReusableCell(ZSRLMyCell.self) cell?.customView = self.iconView cell?.textLabel?.text = dict["title"] let image = dict["image"] ?? "" self.iconView.qs_setAvatarWithURLString(urlString: image) return cell! } else if type == "label" { let cell = tableView.qs_dequeueReusableCell(ZSRLMyCell.self) cell?.textLabel?.text = dict["title"] let right = dict["detail"] ?? "" cell?.rightTitle = right return cell! } else if type == "all" { let cell = tableView.qs_dequeueReusableCell(ZSUserBindCell.self) cell?.textLabel?.text = dict["title"] let center = dict["center"] ?? "" cell?.rightLabel.text = center cell?.rightButton.setTitle("复制", for: .normal) cell?.buttonHandler = { [weak self] selected in guard let sSelf = self else { return } UIPasteboard.general.string = center sSelf.view.showTip(tip: "用户ID已复制到您的剪切板") } return cell! } else if type == "bind" { let cell = tableView.qs_dequeueReusableCell(ZSUserBindCell.self) let detail = dict["detail"] if detail != "" { cell?.rightButton.isEnabled = false cell?.rightButton.setTitle(detail, for: .normal) cell?.rightLabel.text = dict["center"] } cell?.textLabel?.text = dict["title"] return cell! } } return UITableViewCell() } override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) if indexPath.section == 0 { if indexPath.row == 1 { let dict = cells[indexPath.section][indexPath.row] let nicknameVC = ZSModifyNicknameViewController() nicknameVC.title = "修改昵称" nicknameVC.nickname = dict["detail"] ?? "" self.navigationController?.pushViewController(nicknameVC, animated: true) } } } override func registerCellClasses() -> Array { return [ZSUserBindCell.self,ZSRLMyCell.self,ZSRTMyCell.self] } } ================================================ FILE: zhuishushenqi/Root/Controllers/ZSVoiceBookCategoryViewController.swift ================================================ // // ZSVoiceBookCategoryViewController.swift // zhuishushenqi // // Created by caony on 2019/3/23. // Copyright © 2019年 QS. All rights reserved. // import UIKit import SnapKit class ZSVoiceSectionCenter: NSObject { static var sectionName:String = "" static var categoryType:Int = 0 static var imageName:String = "" private static let shared = ZSVoiceSectionCenter() private override init() { } @discardableResult static func shared(section:Int) -> ZSVoiceSectionCenter{ switch section { case 0: sectionName = "最新" categoryType = 2 imageName = "zuixin" break case 1: sectionName = "热门" categoryType = 1 imageName = "zuire" break case 2: sectionName = "经典" categoryType = 3 imageName = "jingdian" break default: break } return ZSVoiceSectionCenter.shared } } class ZSVoiceBookCategoryViewController: BaseViewController ,XMReqDelegate, UITableViewDataSource, UITableViewDelegate, ZSVoiceSegmentProtocol { let dataSource = ["nav":[ ["title":"都市言情", "tags":["言情","官场商战","都市"] ], ["title":"玄幻仙侠", "tags":["幻想", "武侠"] ], ["title":"科幻惊悚", "tags":["悬疑"] ], ["title":"历史", "tags":["历史"] ], ["title":"出版", "tags":["经管", "社科", "读客图书", "果麦文化", "中信出版", "博集天卷", "磨铁阅读", "速播专区", "蓝狮子", "推理世界", "正能量有声书"] ] ], ] var tableView:UITableView! var segmentView:ZSVoiceSegmentView! // var albums:[ZSVoiceAlbums] = [ZSVoiceAlbums(),ZSVoiceAlbums(),ZSVoiceAlbums()] var albums:[[XMAlbum]] = [[], [], []] private let albumsCount = 3 override func viewDidLoad() { super.viewDidLoad() XMReqMgr.sharedInstance()?.delegate = self setupSubviews() // NSDictionary *dict = @{@"calc_dimension":@2, // @"category_id":@3, // @"count":@20, // @"page":@1, // @"tag_name":@"武侠", // @"type":@0 // }; for index in 0.. index { tagName = tags[index] } else { let tagNum = arc4random() % UInt32(tags.count) tagName = tags[Int(tagNum)] } let params = [ "category_id":3, "count":20, "page":1, "tag_name":tagName, "type":0, "calc_dimension":calcDimension] as [String : Any] XMReqMgr.sharedInstance()?.requestXMData(XMReqType.albumsList, params: params, withCompletionHander: { (result, error) in if let dict = result as? [String:Any] { if let albumArr = dict["albums"] as? [[AnyHashable : Any]] { var albumsModel:[XMAlbum] = [] for item in albumArr { if let albumModel = XMAlbum(dictionary: item) { albumsModel.append(albumModel) } } self.albums[index] = albumsModel self.tableView.reloadData() } } }) } } func request(category_id:Int, tagName:String, handler:@escaping ZSBaseCallback) { let params = [ "category_id":3, "count":20, "page":1, "tag_name":tagName, "type":0, "calc_dimension":category_id] as [String : Any] XMReqMgr.sharedInstance()?.requestXMData(XMReqType.albumsList, params: params, withCompletionHander: { (result, error) in if let albums = ZSVoiceAlbums.deserialize(from: result as? [String:Any]) { handler(albums) } else { handler(nil) } }) } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) layoutSubviews() } override func viewWillLayoutSubviews() { layoutSubviews() } private func layoutSubviews() { segmentView.snp.remakeConstraints { (make) in make.top.equalToSuperview().offset(kNavgationBarHeight) make.left.right.equalToSuperview() make.height.equalTo(30) } tableView.snp.remakeConstraints { (make) in make.left.right.bottom.equalToSuperview() make.top.equalTo(segmentView.snp.bottom) } } private func setupSubviews() { title = "有声书分类" segmentView = ZSVoiceSegmentView(frame: CGRect.zero) segmentView.delegate = self view.addSubview(segmentView) tableView = UITableView(frame: CGRect.zero, style: .grouped) tableView.dataSource = self tableView.delegate = self tableView.qs_registerCellClass(ZSVoiceCategoryCell.self) tableView.qs_registerHeaderFooterClass(ZSVoiceCategoryHeaderView.self) view.addSubview(tableView) } //MARK: - UITableViewDataSource func numberOfSections(in tableView: UITableView) -> Int { return albumsCount } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { let rows = [3,3,5] if section < rows.count { return rows[section] } return 0 } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.qs_dequeueReusableCell(ZSVoiceCategoryCell.self) cell?.selectionStyle = .none if let albums = albums[safe: indexPath.section] { if let voiceAlbum:XMAlbum = albums[safe: indexPath.row] { if let url = URL(string: voiceAlbum.coverUrlLarge) { cell?.imageView?.kf.setImage(with: QSResource(url: url)) } cell?.textLabel?.text = voiceAlbum.albumTitle; cell?.detailTextLabel?.text = voiceAlbum.albumIntro let attr = NSMutableAttributedString(string: "\(voiceAlbum.playCount)人气") attr.addAttribute(.foregroundColor, value: UIColor.red, range: NSMakeRange(0, attr.length - 2)) cell?.popularityLabel.attributedText = attr let totalAttr = NSMutableAttributedString(string: "共\(voiceAlbum.includeTrackCount)集 内容来自喜马拉雅FM") totalAttr.addAttribute(.foregroundColor, value: UIColor.red, range: NSMakeRange(1, totalAttr.length - 13)) cell?.totalLabel.attributedText = totalAttr } } return cell! } func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { return 100 } func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { return 44 } func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { return 6 } func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { let headerView = tableView.qs_dequeueReusableHeaderFooterView(ZSVoiceCategoryHeaderView.self) headerView?.contentView.backgroundColor = UIColor.white ZSVoiceSectionCenter.shared(section: section) headerView?.titleLabel.text = ZSVoiceSectionCenter.sectionName headerView?.imageView.image = UIImage(named: ZSVoiceSectionCenter.imageName) return headerView } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { let palyVC = ZSVoicePlayViewController() if let albumsModel = albums[safe: indexPath.section] { if let voiceAlbum:XMAlbum = albumsModel[safe: indexPath.row] { palyVC.title = voiceAlbum.albumTitle palyVC.album = voiceAlbum } palyVC.albums = albumsModel } self.navigationController?.pushViewController(palyVC, animated: true) } //MARK: - XMReqDelegate func didXMInitReqOK(_ result: Bool) { } func didXMInitReqFail(_ respModel: XMErrorModel!) { } //MARK: - ZSVoiceSegmentProtocol func titlesForSegment(segmentView: ZSVoiceSegmentView) -> [String] { var titles:[String] = [] if let arr = dataSource["nav"] { for item in arr { titles.append(item["title"] as! String) } } return titles } func didSelect(segment: ZSVoiceSegmentView, at index: Int) { } } ================================================ FILE: zhuishushenqi/Root/Controllers/ZSVoiceBookSegmentViewController.swift ================================================ // // QSCatalogRouter.swift // zhuishushenqi // // Created yung on 2017/4/21. // Copyright © 2017年 QS. All rights reserved. // // Template generated by Juanpe Catalán @JuanpeCMiOS // class ZSVoiceBookSegmentViewController: BaseViewController { override func viewDidLoad() { super.viewDidLoad() } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) } override func viewWillLayoutSubviews() { } } ================================================ FILE: zhuishushenqi/Root/Controllers/ZSVoicePlayListViewController.swift ================================================ // // ZSVoicePlayListViewController.swift // zhuishushenqi // // Created by caony on 2019/3/23. // Copyright © 2019年 QS. All rights reserved. // import UIKit class ZSVoicePlayListViewController: UIViewController { var albums:[XMAlbum] = [] override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. view.backgroundColor = UIColor.cyan } } ================================================ FILE: zhuishushenqi/Root/Controllers/ZSVoicePlayViewController.swift ================================================ // // ZSVoicePlayViewController.swift // zhuishushenqi // // Created by caony on 2019/3/23. // Copyright © 2019年 QS. All rights reserved. // import UIKit class ZSVoicePlayViewController: BaseViewController, ZSSegmentProtocol { var album:XMAlbum = XMAlbum() var albums:[XMAlbum] = [] fileprivate var segmentViewController:ZSSegmentViewController = ZSSegmentViewController() override func viewDidLoad() { super.viewDidLoad() setupSubviews() } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) layoutSubview() } override func viewWillLayoutSubviews() { layoutSubview() } override var supportedInterfaceOrientations: UIInterfaceOrientationMask { return .portrait } override var shouldAutorotate: Bool { return false } private func layoutSubview() { segmentViewController.view.snp.remakeConstraints { (make) in let statusHeight = UIApplication.shared.statusBarFrame.height let navHeight = self.navigationController?.navigationBar.height ?? 0 make.top.equalToSuperview().offset(statusHeight + navHeight) make.left.right.bottom.equalToSuperview() } } private func setupSubviews() { segmentViewController.delegate = self segmentViewController.scrollViewDidEndDeceleratingHandler = { index in if index == 0 { self.setupAddBooShelfRightBar() } else { self.setupClearRightBar() } } view.addSubview(segmentViewController.view) addChild(segmentViewController) setupAddBooShelfRightBar() } private func setupAddBooShelfRightBar() { let rightBar = UIBarButtonItem(title: "加入书架", style: .plain, target: self, action: #selector(addBookShelfAction)) navigationController?.navigationItem.rightBarButtonItem = rightBar } private func setupClearRightBar() { let rightBar = UIBarButtonItem(title: "清空", style: .plain, target: self, action: #selector(clearAction)) navigationController?.navigationItem.rightBarButtonItem = rightBar } @objc private func addBookShelfAction() { } @objc private func clearAction() { } //MARK: - ZSSegmentProtocol func segmentViewControllers() -> [UIViewController] { let playerVC = ZSVoicePlayerViewController() playerVC.album = self.album let playListVC = ZSVoicePlayListViewController() playListVC.albums = self.albums return [playerVC, playListVC] } } ================================================ FILE: zhuishushenqi/Root/Controllers/ZSVoicePlayerViewController.swift ================================================ // // ZSVoicePlayerViewController.swift // zhuishushenqi // // Created by caony on 2019/3/23. // Copyright © 2019年 QS. All rights reserved. // import UIKit class ZSVoicePlayerViewController: UIViewController { var album:XMAlbum? var tracks:[XMTrack] = [] var playIndex:Int = 0 private func requestTrack() { if let albumValue = album { var count = 20 if albumValue.includeTrackCount >= 1 && albumValue.includeTrackCount <= 200 { count = albumValue.includeTrackCount } let params = ["album_id":albumValue.albumId, "count":count, "page":1] XMReqMgr.sharedInstance()?.requestXMData(XMReqType.albumsBrowse, params: params, withCompletionHander: { (result, error) in if let dict = result as? [String:Any] { if let albumArr = dict["tracks"] as? [[AnyHashable : Any]] { var albumsModel:[XMTrack] = [] for item in albumArr { if let albumModel = XMTrack(dictionary: item) { albumsModel.append(albumModel) } } self.tracks = albumsModel self.play() } } }) } } override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. setupSubview() requestTrack() } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) layoutSubview() } override func viewWillLayoutSubviews() { layoutSubview() } override func viewDidDisappear(_ animated: Bool) { super.viewDidDisappear(animated) XMSDKPlayer.shared()?.stopTrackPlay() } private func play(at index:Int) { if index < self.tracks.count { XMSDKPlayer.shared()?.stopTrackPlay() XMSDKPlayer.shared()?.trackPlayDelegate = self XMSDKPlayer.shared()?.setVolume(0.5) XMSDKPlayer.shared()?.setPlayMode(XMSDKPlayMode.track) XMSDKPlayer.shared()?.setTrackPlayMode(XMSDKTrackPlayMode.XMTrackPlayerModeList) XMSDKPlayer.shared()?.play(with: self.tracks[index], playlist: self.tracks) XMSDKPlayer.shared()?.setAutoNexTrack(true) refreshView(track: self.tracks[index]) } else { view.showTip(tip: "超出了tracks限制,请重试") } } private func play() { if self.tracks.count > 0 { play(at: playIndex) } } private func refreshView(track:XMTrack) { let coverUrlLarge = track.coverUrlLarge ?? "" if let url = URL(string: coverUrlLarge) { let resource = QSResource(url: url) self.imageView.kf.setImage(with: resource) self.descLabel.text = track.trackTitle self.sourceLabel.text = "内容来自喜马拉雅FM" } } private func layoutSubview() { descLabel.snp.remakeConstraints { (make) in make.left.right.equalToSuperview() make.top.equalToSuperview().offset(40) make.height.equalTo(20) } imageView.snp.remakeConstraints { (make) in make.left.equalToSuperview().offset(self.view.bounds.width/3) make.right.equalToSuperview().offset(-self.view.bounds.width/3) make.top.equalTo(self.descLabel.snp.bottom).offset(80) make.height.equalTo(self.view.bounds.width/3) } sourceLabel.snp.remakeConstraints { (make) in make.left.right.equalToSuperview() make.top.equalTo(self.imageView.snp.bottom).offset(30) make.height.equalTo(20) } playerView.snp.remakeConstraints { (make) in make.left.equalToSuperview().offset(20) make.right.equalToSuperview().offset(-20) make.bottom.equalToSuperview().offset(-20) make.height.equalTo(200) } imageView.qs_addCornerRadius(cornerRadius: 4) imageView.addShadow(cornerRadius: 4) } private func setupSubview() { backgroundView = UIView(frame: view.bounds) backgroundView.backgroundColor = UIColor(white: 0.4, alpha: 0.4) let tap = UITapGestureRecognizer(target: self, action: #selector(dismissCatelogView)) backgroundView.addGestureRecognizer(tap) catelogView = ZSVoicePlayerCatelogView(frame: CGRect.zero) catelogView.tracks = tracks catelogView.album = album catelogView.backhandler = { self.dismissCatelogView() } catelogView.cellCickHandler = { indexPath in self.dismissCatelogView() self.playIndex = indexPath.row self.play(at: self.playIndex) } descLabel = UILabel(frame: CGRect.zero) descLabel.textAlignment = .center descLabel.textColor = UIColor.gray descLabel.font = UIFont.systemFont(ofSize: 15) imageView = UIImageView(frame: CGRect.zero) sourceLabel = UILabel(frame: CGRect.zero) sourceLabel.textAlignment = .center sourceLabel.textColor = UIColor.gray sourceLabel.font = UIFont.systemFont(ofSize: 15) playerView = ZSVoicePlayerView(frame: CGRect.zero) playerView.playerDelegate = self playerView.isUserInteractionEnabled = true view.addSubview(descLabel) view.addSubview(imageView) view.addSubview(sourceLabel) view.addSubview(playerView) } @objc private func dismissCatelogView() { showCatelogView() } private func showCatelogView() { catelogView.tracks = tracks catelogView.album = album if let _ = backgroundView.superview { UIView.animate(withDuration: 0.25, animations: { self.catelogView.frame = CGRect(x: 0, y: 1.0 * (self.navigationController?.view.bounds.height ?? 0), width: self.navigationController?.view.bounds.width ?? 0, height: 0.8 * (self.navigationController?.view.bounds.height ?? 0)) }) { (finished) in self.backgroundView.removeFromSuperview() self.catelogView.removeFromSuperview() } } else { navigationController?.view.addSubview(backgroundView) navigationController?.view.addSubview(catelogView) self.catelogView.frame = CGRect(x: 0, y: 1.0 * (self.navigationController?.view.bounds.height ?? 0), width: self.navigationController?.view.bounds.width ?? 0, height: 0.8 * (self.navigationController?.view.bounds.height ?? 0)) UIView.animate(withDuration: 0.25) { self.catelogView.frame = CGRect(x: 0, y: 0.2 * (self.navigationController?.view.bounds.height ?? 0), width: self.navigationController?.view.bounds.width ?? 0, height: 0.8 * (self.navigationController?.view.bounds.height ?? 0)) } } } // fileprivate var titleLabel:UILabel! fileprivate var descLabel:UILabel! fileprivate var backgroundView:UIView! fileprivate var imageView:UIImageView! fileprivate var sourceLabel:UILabel! fileprivate var playerView:ZSVoicePlayerView! fileprivate var catelogView:ZSVoicePlayerCatelogView! } extension ZSVoicePlayerViewController:XMTrackPlayerDelegate { func xmTrackPlayNotifyProcess(_ percent: CGFloat, currentSecond: UInt) { playerView.progress = Float(percent) playerView.currentSeconds = currentSecond } func xmTrackPlayerWillPlaying() { if let track = XMSDKPlayer.shared()?.currentTrack() { playerView.bind(track: track) } } func xmTrackPlayerDidPlaying() { playerView.didStartPlay() } func xmTrackPlayerDidStopped() { } func xmTrackPlayerDidEnd() { if playIndex + 1 < self.tracks.count { playIndex += 1 catelogView.selectedIndex = playIndex play(at: playIndex) } } func xmTrackPlayerDidPaused() { } func xmTrackPlayerDidPlaylistEnd() { } func xmTrackPlayerDidChange(to track: XMTrack!) { } func xmTrackPlayerDidPausePlayForBadNetwork() { } func xmTrackPlayerDidReplacePlayList(_ list: [Any]!) { } func xmTrackPlayerShouldContinueNextTrack(whenFailed track: XMTrack!) -> Bool { return true } func xmTrackPlayerDidFailed(toPlay track: XMTrack!, withError error: Error!) { playerView.didFailedToPlay() } func xmTrackPlayerDidError(withType type: String!, withData data: [AnyHashable : Any]!) { playerView.didFailedToPlay() } } extension ZSVoicePlayerViewController:ZSVoicePlayerDelegate { func playerView(playerView: ZSVoicePlayerView, didClickPauseButton: UIButton) { if self.tracks.count > 0 { if didClickPauseButton.isSelected { if XMSDKPlayer.shared()?.playerState == XMSDKPlayerState.paused { XMSDKPlayer.shared()?.resumeTrackPlay() } else { play() } } else { XMSDKPlayer.shared()?.pauseTrackPlay() } } } func playerView(playerView: ZSVoicePlayerView, didClickLastButton: UIButton) { } func playerView(playerView: ZSVoicePlayerView, didClickNextButton: UIButton) { } func playerView(playerView: ZSVoicePlayerView, didClickCatelogButton: UIButton) { showCatelogView() } func playerView(playerView: ZSVoicePlayerView, didClickShelfButton: UIButton) { } func playerView(playerView: ZSVoicePlayerView, didClickTimerButton: UIButton) { } func playerView(playerView: ZSVoicePlayerView, valueChangedTo seconds: Float) { XMSDKPlayer.shared()?.seek(toTime: CGFloat(seconds)) } } ================================================ FILE: zhuishushenqi/Root/Controllers/ZSVoucherViewController.swift ================================================ // // ZSVoucherViewController.swift // zhuishushenqi // // Created by caony on 2019/1/9. // Copyright © 2019年 QS. All rights reserved. // import UIKit import RxSwift import RxCocoa import MJRefresh typealias ZSVoucherHandler = (_ indexPath:IndexPath)->Void class ZSVoucherParentViewController: BaseViewController, ZSSegmentProtocol { var segmentViewController = ZSSegmentViewController() var segmentView:UISegmentedControl! var headerView:UIView! private var vcs:[UIViewController] = [] override func viewDidLoad() { super.viewDidLoad() setupSubviews() } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) segmentViewController.scrollViewDidEndDeceleratingHandler = { index in self.segmentView.selectedSegmentIndex = index } segmentViewController.view.frame = CGRect(x: 0, y: headerView.frame.maxY, width: self.view.bounds.width, height: self.view.bounds.height - headerView.frame.maxY) } func setupSubviews() { headerView = UIView(frame: CGRect(x: 0, y: kNavgationBarHeight, width: self.view.bounds.width, height: 60)) headerView.backgroundColor = UIColor.white segmentView = UISegmentedControl(frame: CGRect(x: 0, y: 20, width: self.view.bounds.width, height: 30)) segmentView.tintColor = UIColor.red headerView.addSubview(segmentView) view.addSubview(headerView) var viewControllers:[UIViewController] = [] var titles = ["可使用","已用完","已过期"] for i in 0..<3 { let viewController = ZSVoucherViewController() viewController.index = i viewController.title = titles[i] viewController.didSelectRowHandler = { indexPath in } viewControllers.append(viewController) segmentView.insertSegment(withTitle: titles[i], at: i, animated: false) } vcs = viewControllers segmentView.selectedSegmentIndex = 0 segmentView.addTarget(self, action: #selector(segmentTap), for: .valueChanged) addChild(segmentViewController) view.addSubview(segmentViewController.view) } func segmentViewControllers() -> [UIViewController] { return vcs } @objc func segmentTap() { let index = segmentView.selectedSegmentIndex self.segmentViewController.didSelect(index: index) } } class ZSVoucherViewController: ZSBaseTableViewController, Refreshable { var index:Int = 0 var viewModel:ZSMyViewModel = ZSMyViewModel() fileprivate let disposeBag = DisposeBag() var headerRefresh:MJRefreshHeader? var footerRefresh:MJRefreshFooter? var start = 0 var limit = 20 var didSelectRowHandler:ZSVoucherHandler? override func viewDidLoad() { super.viewDidLoad() setupSubviews() } func setupSubviews() { let header = initRefreshHeader(tableView) { self.start = 0 self.viewModel.fetchVoucher(token: ZSLogin.share.token, type: self.voucherType(), start: self.start, limit: self.limit, completion: { (voucherList) in self.tableView.reloadData() }) } let footer = initRefreshFooter(tableView) { self.start += self.limit self.viewModel.fetchMoreVoucher(token: ZSLogin.share.token, type: self.voucherType(), start: self.start, limit: self.limit, completion: { (vouchers) in self.tableView.reloadData() }) } headerRefresh = header footerRefresh = footer headerRefresh?.beginRefreshing() viewModel .autoSetRefreshHeaderStatus(header: header, footer: footer) .disposed(by: disposeBag) } func vouchers() ->[ZSVoucher]? { if index == 0 { return viewModel.useableVoucher } else if index == 1 { return viewModel.unuseableVoucher } return viewModel.expiredVoucher } func voucherType()->String { let types = ["useable","unuseable","expired"] return types[index] } override func registerCellClasses() -> Array { return [ZSVoucherCell.self] } //MARK: - UITableView override func numberOfSections(in tableView: UITableView) -> Int { return 1 } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return vouchers()?.count ?? 0 } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.qs_dequeueReusableCell(ZSVoucherCell.self) if let voucherList = vouchers() { cell?.titleText = "\(voucherList[indexPath.row].amount)" cell?.rightText = "\(voucherList[indexPath.row].balance)" cell?.titleDetailText = "\(voucherList[indexPath.row].expired.qs_subStr(to: 10))" cell?.rightDetailText = voucherList[indexPath.row].from } return cell! } override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { return 60 } } ================================================ FILE: zhuishushenqi/Root/Controllers/ZSWebItem.swift ================================================ // // ZSWebItem.swift // zhuishushenqi // // Created by caony on 2019/1/6. // Copyright © 2019年 QS. All rights reserved. // import Foundation class ZSWebItem: NSObject { var callbackParamStr:String? var callback:String? var paramDic:[String:Any]? var queryDic:[String:Any]? var funcName:String? } ================================================ FILE: zhuishushenqi/Root/Controllers/ZSWebStoreViewController.swift ================================================ // // ZSWebStoreViewController.swift // zhuishushenqi // // Created by caony on 2019/1/6. // Copyright © 2019年 QS. All rights reserved. // import UIKit class ZSWebStoreViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. } /* // MARK: - Navigation // In a storyboard-based application, you will often want to do a little preparation before navigation override func prepare(for segue: UIStoryboardSegue, sender: Any?) { // Get the new view controller using segue.destination. // Pass the selected object to the new view controller. } */ } extension ZSWebStoreViewController :ZSWebViewControllerDelegate { } //#import "BaseViewController.h" // //#import "ShareSDKUtilsDelegate-Protocol.h" //#import "UIActionSheetDelegate-Protocol.h" //#import "ZSWebViewControllerDelegate-Protocol.h" // //@class NSArray, NSString, ShareMessageItem, YJActivityFloatLayerView, ZSWebViewController; // //@interface WebStoreViewController : BaseViewController //{ // unsigned long long currentShareType; // _Bool shouldAutoSelect; // ShareMessageItem *shareItem; // NSArray *segmentArray; // _Bool briberyShareHasCallBack; // _Bool _showSearchBtn; // _Bool _isModal; // _Bool _needRefreshHongbao; // _Bool _hadAddPromoter; // int _typeTag; // NSString *_viewTitle; // NSString *_urlStr; // NSString *_booklistId; // NSString *_info; // ZSWebViewController *_zsWebView; // *_activityFloatLayerView; //} // //@property(retain, nonatomic) YJActivityFloatLayerView *activityFloatLayerView; // @synthesize activityFloatLayerView=_activityFloatLayerView; //@property(nonatomic) _Bool hadAddPromoter; // @synthesize hadAddPromoter=_hadAddPromoter; //@property(nonatomic) _Bool needRefreshHongbao; // @synthesize needRefreshHongbao=_needRefreshHongbao; //@property(retain, nonatomic) ZSWebViewController *zsWebView; // @synthesize zsWebView=_zsWebView; //@property(copy, nonatomic) NSString *info; // @synthesize info=_info; //@property(nonatomic) _Bool isModal; // @synthesize isModal=_isModal; //@property(retain, nonatomic) NSString *booklistId; // @synthesize booklistId=_booklistId; //@property(nonatomic) _Bool showSearchBtn; // @synthesize showSearchBtn=_showSearchBtn; //@property(nonatomic) int typeTag; // @synthesize typeTag=_typeTag; //@property(retain, nonatomic) NSString *urlStr; // @synthesize urlStr=_urlStr; //@property(retain, nonatomic) NSString *viewTitle; // @synthesize viewTitle=_viewTitle; //- (void).cxx_destruct; //- (void)geTuiPushOpenURl; //- (void)webViewDidSetNavBarStyle:(id)arg1; //- (void)webViewDidTitleShown:(id)arg1; //- (void)webViewHandleBackEventWithCallBack:(id)arg1; //- (void)webViewHandlePopEventWithCallBack:(id)arg1; //- (id)handleShareResult:(unsigned long long)arg1 paramas:(id)arg2; //- (id)getHongbaoHomeView:(id)arg1; //- (void)webViewShareStatus:(unsigned long long)arg1 callBack:(id)arg2 parama:(id)arg3; //- (void)webViewNeedClose:(id)arg1 animated:(_Bool)arg2; //- (_Bool)needForceSetToFemale; //- (void)onClickSegment:(id)arg1; //- (void)collectBookList; //- (void)onClickFirstRightItem; //- (void)onClickLeftItem; //- (void)shareSDKUtilsWillShowBindPhoneView; //- (void)loginSucceed; //- (void)actionSheet:(id)arg1 didDismissWithButtonIndex:(long long)arg2; //- (void)goBackWithAnimated:(_Bool)arg1; //- (void)resetExclusiveStoreUrlStrWithForceSet:(_Bool)arg1 female:(_Bool)arg2; //- (void)resetMonthlyUrlStrWithForceSet:(_Bool)arg1 female:(_Bool)arg2; //- (void)resetViewTitle; //- (void)appendUrlParam; //- (void)resetUrlStr; //- (id)parseQueryStr:(id)arg1; //- (void)handleShareBack; //- (void)setPositionIdForLog:(id)arg1; //- (void)viewWillDisappear:(_Bool)arg1; //- (void)viewDidLoad; //- (void)viewDidAppear:(_Bool)arg1; //- (void)viewWillAppear:(_Bool)arg1; //- (void)handleUrlWithMainVC:(id)arg1; //- (void)dealloc; ================================================ FILE: zhuishushenqi/Root/Controllers/ZSWebViewController.swift ================================================ // // ZSWebViewController.swift // zhuishushenqi // // Created by yung on 2018/8/19. // Copyright © 2018年 QS. All rights reserved. // import UIKit import WebKit import SnapKit //https://h5.zhuishushenqi.com/monthly?platform=ios&gender=female&token=xAk9Ac8k3Jj9Faf11q8mBVPQ&timeInterval=1546603628999.309082&appversion=2.29.14×tamp=1546603628.999911&version=8 class ZSWebViewController: BaseViewController { var webView:WKWebView! var url:String = "" var webTitle:String = "" { didSet { self.title = webTitle } } var navBarRightItems:[ZSWebItem] = [] var jumpHandler:ZSWebJumpHandler? var userHandler:ZSWebUserHandler? var toolHandler:ZSWebToolHandler? var biHandler:ZSWebBIHandler? var speakHandler:ZSWebSpeakHandler? weak var delegate:ZSWebViewControllerDelegate? var currentUrlStr:String = "" override func viewDidLoad() { super.viewDidLoad() setupSubviews() navigationBarHiden = false } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) navigationController?.setNavigationBarHidden(false, animated: true) } override func viewWillLayoutSubviews() { layoutSubview() } private func layoutSubview() { // webView.snp.remakeConstraints { (make) in // make.edges.equalToSuperview() // } } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } override func popAction() { if self.webView.canGoBack { self.webView.goBack() } else { super.popAction() } } func setupSubviews(){ UIApplication.shared.isNetworkActivityIndicatorVisible = false UIApplication.shared.isStatusBarHidden = false let systemVersion = Double(UIDevice.current.systemVersion) ?? 8.0 if systemVersion >= 8.0 { let userScript = WKUserScript(source: "var meta = document.createElement('meta'); meta.setAttribute('name', 'viewport'); meta.setAttribute('content', 'width=device-width'); document.getElementsByTagName('head')[0].appendChild(meta);", injectionTime: WKUserScriptInjectionTime.atDocumentStart, forMainFrameOnly: true) let userContent = WKUserContentController() userContent.addUserScript(userScript) let configuration = WKWebViewConfiguration() configuration.userContentController = userContent userContent.add(self, name: "ZssqApi") if let tmpWebView = self.createWebViewWithCustomUserAgentWithConfiguratuion(configuration: configuration) { self.webView = tmpWebView self.webView.navigationDelegate = self self.webView.uiDelegate = self self.view.addSubview(self.webView) self.loadWebPage() } } webView.snp.makeConstraints { (make) in make.edges.equalToSuperview() } } func addObservers() { self.webView.addObserver(self, forKeyPath: "title", options: NSKeyValueObservingOptions.new, context: nil) } override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { if keyPath == "title" { if (object as? WKWebView) == self.webView { } } } func createWebViewWithCustomUserAgentWithConfiguratuion(configuration:WKWebViewConfiguration) ->WKWebView? { let webView = WKWebView(frame: self.view.frame, configuration: configuration) webView.evaluateJavaScript("navigator.userAgent") { (result, error) in let str = "\(String(describing: result))/YouShaQi" let userAgentDict = ["UserAgent":str] UserDefaults.standard.register(defaults: userAgentDict) UserDefaults.standard.synchronize() if #available(iOS 9.0, *) { webView.customUserAgent = str } else { webView.setValue(str, forKey: "applicationNameForUserAgent") } } return webView } func canUAAppendYouShaQi() -> Bool { return true } func loadWebPage() { if url != "" { // complete url if let url = URL(string: self.url) { let request = URLRequest(url: url) webView.load(request) } } } func isBlankString(str:String?) ->Bool { var isBlank = false if str == nil { isBlank = true } if let trimStr = str?.trimmingCharacters(in: NSCharacterSet.whitespacesAndNewlines) { if trimStr.count > 0 { if trimStr == "(null)" { isBlank = true } } } return isBlank } func parseURL(urlString:String) ->Bool { let replaceStr = urlString.replacingOccurrences(of: "/?", with: "?") guard let webItem = matchedResultWithLink(link: replaceStr) else { return false } let funcName = webItem.funcName var result = true if isBlankString(str: funcName) { result = true // if funcName! == "jump" { // perform(#selector(jumpToViewWithItem(item:)), with: webItem) // result = false // } else if funcName! == "getUserInfo" { // let queryDict = webItem.queryDic // let callback = queryDict?["callback"] // result = false // } else if funcName! == "login" { // loginWithWebItem(item: webItem) // } else if funcName! == "copyBoard" { // triggerCopyBoardWithItem(item: webItem) // } else if funcName! == "pop" { // let jsStr = jsStrFromWebItem(item: webItem) // // webViewHandlePopEventWithCallBack:jsstr // self.navigationController?.popViewController(animated: true) // } else if funcName! == "backEvent" { // setBackEventItem(item: webItem) // } else if funcName! == "setUserBehavior" { // handleBuryPointsWithJSItem(item: webItem) // } else if funcName! == "openTaobaoDetail" { // ZSYJSchemeHandle.handleTaobaoUrl(url: webItem.callbackParamStr ?? "") // } else if funcName! == "baseRecharge" { // // } // else { // if funcName! != "openBookstore" { // if funcName! == "openBookshelf" { // // } else if funcName! == "openBindPhone" { // // } // } // } } else { result = handleWebItem(item: webItem) } return result } func handleWebItem(item:ZSWebItem) ->Bool { var result:Bool = false if item.funcName != "setBounces" { if item.funcName == "setNavigationBar" { setNavigtionBarItems(item: item) } else if item.funcName == "backEvent" { setBackEventItem(item: item) } else if ZSWebJumpHandler.canHandleWebItem(item: item) { if self.jumpHandler == nil { self.jumpHandler = ZSWebJumpHandler() } let context = ZSWebContext() context.fromVC = self context.delegate = self.delegate self.jumpHandler?.handleWebItem(item: item, context: context, block: { (result) in }) } else if ZSWebUserHandler.canHandleWebItem(item: item) { if self.userHandler == nil { self.userHandler = ZSWebUserHandler() } } else if ZSWebToolHandler.canHandleWebItem(item: item) { let context = ZSWebContext() context.fromVC = self context.delegate = self.delegate if self.toolHandler == nil { self.toolHandler = ZSWebToolHandler() } self.toolHandler?.handleWebItem(item: item, context: context, block: { [weak self] (result) in self?.runJavaScriptMethodWithName(name: result) }) } else if ZSWebBIHandler.canHandleWebItem(item: item) { if self.biHandler == nil { self.biHandler = ZSWebBIHandler() } } else if ZSWebSpeakHandler.canHandleWebItem(item: item) { let context = ZSWebContext() context.fromVC = self context.delegate = self.delegate if self.speakHandler == nil { self.speakHandler = ZSWebSpeakHandler() } self.speakHandler?.handleWebItem(item: item, context: context, block: { [weak self] (result) in self?.runJavaScriptMethodWithName(name: result) }) } result = true } else { result = false let paramDic = item.paramDic if let enabled = paramDic?["enabled"] as? Bool { self.webView.scrollView.bounces = enabled } } return result } func setBackEventItem(item:ZSWebItem) { } func runJavaScriptMethodWithName(name:String) { self.webView.evaluateJavaScript(name) { (result, error) in } } func setNavigtionBarItems(item:ZSWebItem) { let paramDic = item.paramDic if let navItems = paramDic?["setNavigationItems"] as? [[String:Any]] { var index = navItems.count - 1 while index >= 0 { let item = navItems[index] let webItem = ZSWebItem() var dic:[String:Any] = [:] dic["jumpType"] = "webview" dic["title"] = item["title"] ?? "" dic["link"] = item["link"] ?? "" dic["itemType"] = item["itemType"] ?? "" webItem.paramDic = dic navBarRightItems.append(webItem) index -= 1 } } } @objc func jumpToViewWithItem(item:ZSWebItem) { if let paramDic = item.paramDic { let jumpType = paramDic["jumpType"] as? String ?? "" // let pageType = paramDic["pageType"] as? String ?? "" let title = paramDic["title"] as? String ?? "" var link = paramDic["link"] as? String ?? "" let id = paramDic["id"] as? String ?? "" // let sourceType = paramDic["sourceType"] as? String ?? "" var platform = "" if (link as NSString).range(of: "platform=ios").location == NSNotFound { let whRange = (link as NSString).range(of: "?") let timeInterval = Date().timeIntervalSince1970 if whRange.location == NSNotFound { platform = "?platform=ios×tamp=\(timeInterval)" } else { platform = "&platform=ios×tamp=\(timeInterval)" } } link.append(platform) if jumpType == "webview" { let webVC = ZSWebViewController() webVC.title = title webVC.url = link self.navigationController?.pushViewController(webVC, animated: true) return } if jumpType != "native" { if jumpType == "safari" { if let webUrl = URL(string: link) { if UIApplication.shared.canOpenURL(webUrl) { UIApplication.shared.openURL(webUrl) return } } } } if jumpType != "bookDetail" { if jumpType == "login" { // -[ZSWebViewController loginWithWebItem:](v139, "loginWithWebItem:", v4); // goto LABEL_44; return } if jumpType != "post" { if jumpType != "account" { } if !ZSLogin.share.hasLogin() { // showLoginView } } } if id != "" { let detailVC = QSBookDetailRouter.createModule(id: id) self.navigationController?.pushViewController(detailVC, animated: true) } } } @objc func loginWithWebItem(item:ZSWebItem) { } @objc func triggerCopyBoardWithItem(item:ZSWebItem) { } @objc func jsStrFromWebItem(item:ZSWebItem) { } @objc func handleBuryPointsWithJSItem(item:ZSWebItem) { } func matchedResultWithLink(link:String) ->ZSWebItem? { let reg = try? NSRegularExpression(pattern: "^jsbridge://(\\w+)(\\?)?", options: NSRegularExpression.Options.allowCommentsAndWhitespace) let length = link.count let match = reg?.firstMatch(in: link, options: NSRegularExpression.MatchingOptions(rawValue: 2), range: NSMakeRange(0, length)) guard let result = match?.range(at: 1) else { return nil } if result.location != NSNotFound { let subStr = link.qs_subStr(range: result) let webItem = ZSWebItem() if subStr.count > 0 { webItem.funcName = subStr let rangeTwo = match?.range(at: 2) if rangeTwo?.location != NSNotFound { let linkLength = link.count let twoLength = rangeTwo?.length ?? 0 let twoLocation = rangeTwo?.location ?? 0 let query = link.qs_subStr(range: NSMakeRange(twoLocation,linkLength - twoLength)) let queryDict = parseQueryStr(str: query) webItem.queryDic = queryDict if let param = queryDict["param"] { if let data = param.data(using: String.Encoding(rawValue: 4)) { if let paramObj:[String:Any] = try! JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions(rawValue: 0)) as? [String:Any] { webItem.paramDic = paramObj } } } if let callback = queryDict["callback"] { webItem.callback = callback webItem.callbackParamStr = queryDict["param"] } } } return webItem } return nil } func parseQueryStr(str:String) ->[String:String] { let queryStr = str.replacingOccurrences(of: "?", with: "") let comps = queryStr.components(separatedBy: "&") var queryDict:[String:String] = [:] for comp in comps { let keyValueArr = comp.components(separatedBy: "=") if keyValueArr.count == 2 { let key = keyValueArr[0] let value = keyValueArr[1] let noPerValue = value.removingPercentEncoding queryDict[key] = noPerValue } } return queryDict } } extension ZSWebViewController:WKNavigationDelegate,WKUIDelegate { func isiTunesURL(url:String) ->Bool { var isiTunes = false let reg = try? NSRegularExpression(pattern: "\\/\\/itunes\\.apple\\.com\\/", options: NSRegularExpression.Options(rawValue: 0)) if let regular = reg { let urlLength = url.count if let _ = regular.firstMatch(in: url, options: NSRegularExpression.MatchingOptions(rawValue: 0), range: NSMakeRange(0, urlLength)) { isiTunes = true } } return isiTunes } func isAppStoreURL(url:String) ->Bool { if url.hasPrefix("itms-apps://") { return true } return false } func isWXScheme(url:String) ->Bool { if url.hasPrefix("weixin://") { return true } return false } func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { if let url = navigationAction.request.url { if url.absoluteString.count > 0 { if isiTunesURL(url: url.absoluteString) || isAppStoreURL(url: url.absoluteString) { // self.delegate webViewWillLoadUrl:isOutSide:1 UIApplication.shared.openURL(url) // self.delegate webViewNeedClose:animated:1 } else if !isWXScheme(url: url.absoluteString) { if parseURL(urlString: url.absoluteString) { // self.delegate webViewWillLoadUrl:isOutSide:0 } } else { UIApplication.shared.openURL(url) let backForwardList = webView.backForwardList let backList = backForwardList.backList let firstObject = backList.first if let firstString = firstObject?.url.absoluteString { if firstString.contains("m.zhuishushenqi.com/publicPay/") { UserDefaults.standard.set(1, forKey: "kIsPurchasingByW") UserDefaults.standard.synchronize() // needsCloseWebView = 1 // self.delegate webViewNeedClose:animated:1 } } } } if url.absoluteString.hasPrefix("https://h5.zhuishushenqi.com") { decisionHandler(.allow) } else if url.absoluteString.hasPrefix("jsbridge://") { // configure(urlString: url) decisionHandler(.cancel) } else { decisionHandler(.cancel) } } else { decisionHandler(.cancel) } } func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) { } func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { // 去除广告 // let classNames = ["c-top-app-download clearfix", // "c-page-header", // "c-full-screen-page-header", // "c-bottom-app-download clearfix"] // let jsAddClearOpHead = "document.getElementsByClassName('" // let jsAddClearOpTail = "')[0].style.display = 'none'" // for className in classNames { // let js = "\(jsAddClearOpHead)\(className)\(jsAddClearOpTail)" // self.webView.evaluateJavaScript(js) { (result, error) in // // } // } } func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) { } func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) { } } extension ZSWebViewController:WKScriptMessageHandler { func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { } } ================================================ FILE: zhuishushenqi/Root/Controllers/ZSWebViewControllerDelegate.swift ================================================ // // ZSWebViewControllerDelegate.swift // zhuishushenqi // // Created by caony on 2019/1/6. // Copyright © 2019年 QS. All rights reserved. // import Foundation @objc protocol ZSWebViewControllerDelegate { @objc optional func webViewDidSetNavBarStyle(style:[String:Any]) @objc optional func webViewDidTitleShown(title:String) @objc optional func webViewWillLoadUrl(url:String, isOutSide:Bool) @objc optional func webViewHandleBackEventWithCallBack(callback:String) @objc optional func webViewHandlePopEventWithCallBack(callback:String) @objc optional func webViewShareStatus(status:CLongLong, callback:String, parama:String) @objc optional func webViewNeedRefreshShareClickStatus() @objc optional func webViewNeedClose(webView:ZSWebViewController, animated:Bool) @objc optional func webViewNeedUpdateToolbar(webView:ZSWebViewController) } ================================================ FILE: zhuishushenqi/Root/Models/QSHotModel.swift ================================================ // // QSHotModel.swift // zhuishushenqi // // Created by Nory Cao on 2017/3/22. // Copyright © 2017年 QS. All rights reserved. // import UIKit import HandyJSON class QSHotModel:HandyJSON { var user:QSHotUser = QSHotUser() var tweet:QSHotTweet = QSHotTweet() required init() {} } class QSHotUser:HandyJSON{ var _id:String = "" var avatar:String = "" var nickname:String = "" var activityAvatar:String = "" var type:UserType = .none var lv:Int = 0 var gender:String = "" required init() { } } class QSHotTweet: HandyJSON{ var _id:String = "" var title:String = "" var content:String = "" var __v:Int = 0 var hotAt:String = "" var post:ZSHotTweetPost = ZSHotTweetPost() var votes:[Any] = [] var deleted:Bool = false var isHot:Bool = false var score:Int = 0 var commented:Int = 0 var retweeted:Int = 0 var type:String = "" var created:String = "" var book:ZSHotBook? var haveImage:Bool = false required init() {} } struct ZSHotBook:HandyJSON { var _id:String = "" var author:String = "" var title:String = "" var cover:String = "" var allowFree:Bool = false var apptype:[Int] = [] var allowMonthly:Bool = false var wordCount:Int = 0 init() {} } struct ZSHotTweetPost:HandyJSON { var _id:String = "" var block:String = "" init() {} } @objcMembers class ZSDBTestModel:NSObject, ZSDBModel { var id:String? var num:Int = 0 var data:Data? var subModel:ZSDBTestSubModel? func tableName() -> String! { return "bookshelf" } func primaryKey() -> String! { return "" } func foreignKey() -> String! { return "" } func dbColumnMapping() -> [String : String]! { return [:] } func ignoredKeys() -> [String]! { return [] } } @objcMembers class ZSDBTestSubModel:NSObject, ZSDBModel { @objc dynamic var key:String? func tableName() -> String! { return "record" } func primaryKey() -> String! { return "" } func foreignKey() -> String! { return "" } func dbColumnMapping() -> [String : String]! { return [:] } func ignoredKeys() -> [String]! { return [] } } //{ // "user": { // "_id": "56ada4e2c911b9455cedeaee", // "avatar": "/avatar/87/40/8740ea7f3c175a40092bdd7ab906d5ed", // "nickname": "魅影鹿", // "activityAvatar": "", // "type": "normal", // "lv": 8, // "gender": "male" // }, // "tweet": { // "_id": "58d128b8c7d432f527f1d736", // "title": "搞笑段子!!三十一!!", // "content": "1、邻居家孩子百日宴,作为邻居老王的我参加这次庆生,喝高兴了,就唱了首最拿手的歌曲:我种下一颗种子,终于长出了果实...我擦...不说了,被打的血到现在还没止住呢!\n2、一哥们姓王,大家都调侃的叫他“隔壁老王”,有天他终于给他说急了,他强忍住后淡定的和我们说:以后你们就叫我老王吧!后来...我们就叫他“老王八...老王八”\n3、老王经常和他媳妇吵架,一吵架他媳妇就会娘家了,老王也不管,过了几天后又回来了,久而久之就习惯了,一天又和媳妇吵架了,老王事后想想挺对不住媳妇的,就去娘家接她,到了娘家老丈人说:几个月不见你们回来一次,怎么?你媳妇没和你一起回来啊?\n4、下班回家的路上看到小侄子和一个男孩还有一个女孩一起玩耍,小女孩说:我演妈妈,你演粑粑...然后指着小侄子说:你演隔壁老王,重要的是那两熊孩子竟然同意了!\n5、老王说:我打错我媳妇了。我说:怎么了?老王说:我发现我媳妇在日记本里写着和隔壁邻居偷情的事。我激动的说:恩...这种事打就对了!老王说:不是,后来我才想起那个隔壁邻居是我...\n6问:为什么有的人找对象就像在菜市场买菜,想要就能有呢?神回复:可能他认识优秀的农民。\n7、问:为什么很多男人在结婚后有了孩子就会长胖。神回复:因为有奶喝,营养高。\n8、问:我得了健忘症怎么办?神回复:那岂不是很爽,每天早上起来都发现自己睡不同的男人...\n9、问:男友和我闹矛盾了,是我逼太紧了吗?神回:不,是太松了!\n10、问:你见过最娘的男人是什么样?神回复:打飞机用兰花指...", // "__v": 0, // "hotAt": "2017-03-22T01:07:23.875Z", // "post": null, // "votes": [], // "deleted": false, // "isHot": true, // "score": 0, // "commented": 2, // "retweeted": 0, // "type": "ARTICLE", // "created": "2017-03-21T13:20:56.736Z" // } //} ================================================ FILE: zhuishushenqi/Root/Models/ZSAccount.swift ================================================ // // ZSAccount.swift // zhuishushenqi // // Created by yung on 2018/10/20. // Copyright © 2018年 QS. All rights reserved. // import UIKit import HandyJSON class ZSAccount: HandyJSON { var balance:CGFloat = 0 var iosBalance:CGFloat = 0 var voucherBalance:CGFloat = 62 var beanVoucherBalance:CGFloat = 0 var monthly:Int = 0 var isMonthly:Bool = false var voucherCount:Int = 0 var beanVoucherCount:Int = 0 var isNewUser:Bool = false var time:CGFloat = 0 var voucherSum:Int = 0 var isFree:Bool = false var freeTime:CLongLong = 1537431804 var isAppstoreAutoMonthly = false var ok = true required init() {} } //{ // "balance": 0, // "iosBalance": 0, // "voucherBalance": 62, // "beanVoucherBalance": 0, // "monthly": 0, // "isMonthly": false, // "voucherCount": 2, // "beanVoucherCount": 0, // "isNewUser": false, // "time": 0, // "voucherSum": 62, // "isFree": false, // "freeTime": 1537431804, // "isAppstoreAutoMonthly": false, // "ok": true //} ================================================ FILE: zhuishushenqi/Root/Models/ZSCoin.swift ================================================ // // ZSCoin.swift // zhuishushenqi // // Created by yung on 2018/10/20. // Copyright © 2018年 QS. All rights reserved. // import UIKit import HandyJSON class ZSCoin: NSObject, HandyJSON { var ok = true var info:ZSCoinInfo? required override init() {} } class ZSCoinInfo: NSObject, HandyJSON { var total:CGFloat = 0 var gold:CGFloat = 100 var balance:String = "0.00" var yGold:CGFloat = 0 var yBalance:String = "0.00" var name:String = "" var IDCardNo:String = "" var phone:String = "" var address:String = "" var autoEnchashmentVoucher = false var nickname:String = "" var avatar:String = "" required override init() {} } //{ // "ok": true, // "info": { // "total": 0, // "gold": 100, // "balance": "0.00", // "yGold": 0, // "yBalance": "0.00", // "name": "", // "IDCardNo": "", // "phone": "", // "address": "", // "autoEnchashmentVoucher": false, // "nickname": "神偷520", // "avatar": "/icon/avatar.png" // } //} ================================================ FILE: zhuishushenqi/Root/Models/ZSConfigUtil.swift ================================================ // // ZSConfigUtil.swift // zhuishushenqi // // Created by yung on 2019/6/30. // Copyright © 2019 QS. All rights reserved. // import UIKit //import YYCategories import AdSupport class ZSConfigUtil: NSObject { static func publisherId() ->String { let shortCode = versionShortCode() let data = shortCode.data(using: .utf8) let base64 = data?.base64EncodedString(options: Data.Base64EncodingOptions.init(rawValue: 0)) return base64?.md5() ?? "" } static func versionShortCode() ->String { let bundle = Bundle.main let info = bundle.infoDictionary if let shortCode = info?["CFBundleShortVersionString"] as? String { return shortCode } return "" } static func channel() ->String { return "App Store" } static func appName() ->String { return "" } static func platformString() ->String { return UIDevice.current.machineModelName ?? UIDevice.current.model } static func uniquelIdfa() ->String { let idfa = ASIdentifierManager.shared().advertisingIdentifier.uuidString return idfa } } ================================================ FILE: zhuishushenqi/Root/Models/ZSQQUser.swift ================================================ // // ZSQQUser.swift // zhuishushenqi // // Created by yung on 2018/10/19. // Copyright © 2018年 QS. All rights reserved. // import UIKit import HandyJSON class ZSQQLoginResponse: NSObject, HandyJSON ,NSCoding { var user:ZSQQUser? var token:String = "" var isWeixinNew:Bool = false var ok:Bool = true var isNew:Bool = true var bindMobile:Bool = false var register:Bool = false var code:String = "" required override init() {} required init?(coder aDecoder: NSCoder) { self.user = aDecoder.decodeObject(forKey: "user") as? ZSQQUser self.token = aDecoder.decodeObject(forKey: "token") as! String self.isWeixinNew = aDecoder.decodeBool(forKey: "isWeixinNew") self.ok = aDecoder.decodeBool(forKey: "ok") self.isNew = aDecoder.decodeBool(forKey: "isNew") self.bindMobile = aDecoder.decodeBool(forKey: "bindMobile") self.register = aDecoder.decodeBool(forKey: "register") } func encode(with aCoder: NSCoder) { aCoder.encode(self.user, forKey: "user") aCoder.encode(self.token, forKey: "token") aCoder.encode(self.isWeixinNew, forKey: "isWeixinNew") aCoder.encode(self.ok, forKey: "ok") aCoder.encode(self.isNew, forKey: "isNew") aCoder.encode(self.bindMobile, forKey: "bindMobile") aCoder.encode(self.register, forKey: "register") } } class ZSQQUser: NSObject, HandyJSON, NSCoding { var _id:String = "" var nickname:String = "" var avatar:String = "" var exp:Int = 0 var lv:Int = 0 var gender:String = "" var type:String = "" var likeCate:ZSLikeCate? required override init() {} required init?(coder aDecoder: NSCoder) { self._id = aDecoder.decodeObject(forKey: "_id") as! String self.nickname = aDecoder.decodeObject(forKey: "nickname") as! String self.avatar = aDecoder.decodeObject(forKey: "avatar") as! String self.exp = aDecoder.decodeInteger(forKey: "exp") self.lv = aDecoder.decodeInteger(forKey: "lv") self.gender = aDecoder.decodeObject(forKey: "gender") as! String self.type = aDecoder.decodeObject(forKey: "type") as! String self.likeCate = aDecoder.decodeObject(forKey: "likeCate") as? ZSLikeCate } func encode(with aCoder: NSCoder) { aCoder.encode(self._id, forKey: "_id") aCoder.encode(self.nickname, forKey: "nickname") aCoder.encode(self.avatar, forKey: "avatar") aCoder.encode(self.exp, forKey: "exp") aCoder.encode(self.lv, forKey: "lv") aCoder.encode(self.gender, forKey: "gender") aCoder.encode(self.type, forKey: "type") aCoder.encode(self.likeCate, forKey: "likeCate") } } class ZSLikeCate: NSObject, HandyJSON, NSCoding { var male:[String] = [] var female:[String] = [] var press:[String] = [] var picture:[String] = [] required override init() {} required init?(coder aDecoder: NSCoder) { self.male = aDecoder.decodeObject(forKey: "male") as! [String] self.female = aDecoder.decodeObject(forKey: "female") as! [String] self.press = aDecoder.decodeObject(forKey: "press") as! [String] self.picture = aDecoder.decodeObject(forKey: "picture") as! [String] } func encode(with aCoder: NSCoder) { aCoder.encode(self.male, forKey: "male") aCoder.encode(self.female, forKey: "female") aCoder.encode(self.press, forKey: "press") aCoder.encode(self.picture, forKey: "picture") } } //{ // "user": { // "_id": "57ac9879c12b61e826bd7221", // "nickname": "高手寂寞", // "avatar": "/avatar/80/9c/809cf8858bc8c8008793612a935f72f7", // "exp": 2470, // "lv": 8, // "gender": "female", // "type": "normal", // "likeCate": { // "male": ["都市", "历史", "游戏"], // "female": [], // "press": [], // "picture": [] // } // }, // "token": "2T6IONkhfG4LEg49w9rCMqwA", // "isWeixinNew": false, // "ok": true, // "isNew": false, // "bindMobile": false, // "register": false //} ================================================ FILE: zhuishushenqi/Root/Models/ZSShelfMessage.swift ================================================ // // ZSShelfMessage.swift // zhuishushenqi // // Created by caony on 2018/6/8. // Copyright © 2018年 QS. All rights reserved. // import UIKit import HandyJSON enum ZSShelfMessageType { case post case link case booklist } //@objc(ZSShelfMessage) class ZSShelfMessage: NSObject,HandyJSON { @objc dynamic var postLink:String = "" @objc var highlight:Bool = false { didSet{ if highlight { textColor = UIColor.red } } } required override init() { } internal var textColor:UIColor = UIColor.gray func postMessage() ->(String,String, ZSShelfMessageType,UIColor){ var id:String = "" var title:String = "" // 过滤方式变更 // 目前已知的有三种 post或者link或booklist // post一般跳转到评论页,post后跟的默认为24位的字符串 // link一般打开链接,link一般以link后的第一个空格作为区分 // booklist一般打开书单 var type:ZSShelfMessageType = .post let qsLink:NSString = self.postLink as NSString var typePost = qsLink.range(of: "post:") //[[post:5baf14726f660bbe4fe5dc36 🇨🇳喜迎国庆【惊喜】追书又双叒叕送福利!]] if typePost.location == NSNotFound { typePost = qsLink.range(of: "link:") type = .link } if typePost.location == NSNotFound { typePost = qsLink.range(of: "booklist:") type = .booklist } // 去除首尾的中括号,再以空格区分文字与post let noLeadLink = self.postLink.qs_subStr(from: 2) let noTailLink = noLeadLink.substingInRange(0..<(noLeadLink.count - 2)) ?? noLeadLink let spaceRange = (noTailLink as NSString).range(of: " ") id = noTailLink.substingInRange(typePost.length.. Bool { return true } func handleWebItem(item: ZSWebItem, context: ZSWebContext, block: (String) -> Void) { } } ================================================ FILE: zhuishushenqi/Root/Models/ZSWebContext.swift ================================================ // // ZSWebContext.swift // zhuishushenqi // // Created by yung on 2019/6/30. // Copyright © 2019 QS. All rights reserved. // import UIKit class ZSWebContext: NSObject { weak var delegate:ZSWebViewControllerDelegate? weak var fromVC:ZSWebViewController? } ================================================ FILE: zhuishushenqi/Root/Models/ZSWebJumpHandler.swift ================================================ // // ZSWebJumpHandler.swift // zhuishushenqi // // Created by yung on 2019/6/30. // Copyright © 2019 QS. All rights reserved. // import UIKit protocol ZSWebEventHandlerProtocol { static func canHandleWebItem(item:ZSWebItem) ->Bool func handleWebItem(item:ZSWebItem, context:ZSWebContext, block:(_ result:String)->Void) } class ZSWebJumpHandler: ZSWebEventHandlerProtocol { var content:ZSWebContext? func handleWebItem(item: ZSWebItem, context: ZSWebContext, block: (String) -> Void) { self.content = context if item.funcName == "jump" { jumpToViewWith(item: item) } else if item.funcName == "pop" { } else if item.funcName == "openTaobaoDetail" { } else if item.funcName == "baseRecharge" { } else if item.funcName == "openBookstore" { } else if item.funcName == "openBookshelf" { } else if item.funcName == "openBindPhone" { } else if item.funcName == "openBookReview" { } else if item.funcName == "openSupport" { } else if item.funcName == "openPayForSignin" { } else if item.funcName == "jumpToReader" { } else if item.funcName == "openVideoAd" { } else if item.funcName == "openWinMoney" { } } static func canHandleWebItem(item:ZSWebItem) ->Bool { let items = ["jump","pop","openTaobaoDetail","baseRecharge","openBookstore","openBookshelf","openBindPhone","openBookReview","openSupport","openPayForSignin","jumpToReader","openVideoAd", "openWinMoney"] return items.contains(item.funcName ?? "") } func jumpToViewWith(item:ZSWebItem) { let jumpType = item.paramDic?["jumpType"] as? String ?? "" let pageType = item.paramDic?["pageType"] as? String ?? "" let title = item.paramDic?["title"] as? String ?? "" var link = item.paramDic?["link"] as? String ?? "" let id = item.paramDic?["id"] as? String ?? "" let sourceType = item.paramDic?["sourceType"] as? String ?? "" let topBar = item.paramDic?["topBar"] let navigationBar = item.paramDic?["navigationBar"] if let nav = navigationBar { } if link.asNSString().range(of: "platform=").location == NSNotFound { let range = link.asNSString().range(of: "?") let timeInterval = Date().timeIntervalSince1970 var queryStr = "" if range.location == NSNotFound { queryStr = "?platform=ios×tamp=\(timeInterval)" } else { queryStr = "&platform=ios×tamp=\(timeInterval)" } link = link.appending(queryStr) } if jumpType == "webview" { gotoWebWith(title: title, url: link, navItems: [], navStyle: topBar, pageType: pageType, idValue: id) } else if jumpType == "native" { var v78 = "" var v94 = "" var v95 = "" if pageType == "bookDetail" { gotoBookDetailVCWith(params: item.paramDic, idValue: id, sourceType: sourceType) return } else if pageType == "listenBook" { v78 = "goToVoiceCategoryVC" } else if pageType == "login" { loginWithWebItem(item: item) } else if pageType == "monthlyPay" { v78 = "goToMonthPayVC" } else if pageType == "baseRecharge" { v78 = "goToRechargeVC" } else if pageType == "personalCenter" { v78 = "goToProfileVC" } else if pageType == "tasks" || pageType == "personalinfo" { let paramDic = item.paramDic let mobile = paramDic?["mobile"] // updateLocalInfo // ZSTasksManager.shared.doTaskWithActionName(name:"fl-bind-phone", completionBlock:nil) // dismissVC 1 0 } else if pageType == "post" { gotoTopicDetailVCWith(id: id) return } else if pageType == "bookShortage" { v78 = "goToShortageVC" } else if pageType == "account" { v78 = "goToAccountVC" } else if pageType != "search" { if pageType == "category" { let paramDic = item.paramDic let href = paramDic?["href"] as? String ?? "" goToCategoryVCWith(url: href) return } else if pageType == "createBookList" { v94 = "goToEditBookListVC" // go_label_50(v94 v100) } else if pageType == "bookShortageQuestion" { v95 = "goToShortageQuestionDetailVCWithIdValue:" // go_label_56(v95 v100) } else if pageType == "bookShortageAnswer" { v95 = "goToShortageAnswerDetailVCWithIdValue:" // go_label_56(v95 v100) } else if pageType != "createBookShortageQuestion" { if pageType != "createBookShortageAnswer" { // go_label_61 } else { v95 = "goToEditShortageAnswerVCWithIdValue:" // go_label_56(v95 v100) } } else { v94 = "goToEditShortageQuestionVC" } // goto_label_50(perform v94) } else { v78 = "goToSearchVC" gotoSearchVC() return } // self v78(perform) } else if jumpType == "safari" { if let url = URL(string: link) { if UIApplication.shared.canOpenURL(url) { UIApplication.shared.open(url, options: [:], completionHandler: nil) } } } // label_61: if pageType == "bookReview" { // v95 = "goToReviewVCWithIdValue:" // self v95:id } else if pageType == "createBookReview" { // v94 = "goToEditReviewVC" // self v94 } else if pageType == "communityPersonalHomepage" { // v95 = "goToUserHomePageWithIdValue:" // self v95:id } else { if pageType == "createPost" { goToEditPostVC() } } } func gotoWebWith(title:String, url:String, navItems:[Any],navStyle:Any, pageType:String, idValue:String) { if pageType != "explore" { let webVC = ZSWebViewController() webVC.webTitle = title webVC.url = url webVC.hidesBottomBarWhenPushed = true currentNav()?.pushViewController(webVC, animated: true) } } func currentNav() ->UINavigationController? { var nav:UINavigationController? if let vc = self.content?.fromVC?.navigationController { nav = vc } else if let vc = self.content?.delegate as? UIViewController { nav = vc.navigationController } return nav } func gotoBookDetailVCWith(params:[String:Any]?, idValue:String, sourceType:String) { let detailVC = QSBookDetailRouter.createModule(id: idValue) detailVC.hidesBottomBarWhenPushed = true var nav:UINavigationController? if let vc = self.content?.fromVC?.navigationController { nav = vc } else if let vc = self.content?.delegate as? UIViewController { nav = vc.navigationController } nav?.pushViewController(detailVC, animated: true) } func gotoTopicDetailVCWith(id:String) { } func goToCategoryVCWith(url:String) { let categorySwitch = true if categorySwitch { // 走原生的分类 let catelogVC = ZSCatelogViewController() catelogVC.hidesBottomBarWhenPushed = true currentNav()?.pushViewController(catelogVC, animated: true) } else { let webVC = ZSWebViewController() // setShowSearchBtn:0 webVC.url = url currentNav()?.pushViewController(webVC, animated: true) } } func goToEditPostVC() { } func loginWithWebItem(item:ZSWebItem) { } func gotoSearchVC() { let searchVC = ZSSearchViewController() searchVC.hidesBottomBarWhenPushed = true currentNav()?.pushViewController(searchVC, animated: true) } } ================================================ FILE: zhuishushenqi/Root/Models/ZSWebSpeakHandler.swift ================================================ // // ZSWebSpeakHandler.swift // zhuishushenqi // // Created by yung on 2019/6/30. // Copyright © 2019 QS. All rights reserved. // import UIKit class ZSWebSpeakHandler: ZSWebEventHandlerProtocol { static func canHandleWebItem(item: ZSWebItem) -> Bool { return true } func handleWebItem(item: ZSWebItem, context: ZSWebContext, block: (String) -> Void) { } } ================================================ FILE: zhuishushenqi/Root/Models/ZSWebToolHandler.swift ================================================ // // ZSWebToolHandler.swift // zhuishushenqi // // Created by yung on 2019/6/30. // Copyright © 2019 QS. All rights reserved. // import UIKit class ZSWebToolHandler: ZSWebEventHandlerProtocol { static func canHandleWebItem(item: ZSWebItem) -> Bool { let items = ["share","copyBoard","sharespread","shareEarnings","setTopBarColor","getDeviceInfo","setOptionButton","saveImage","showToast","jumptoPublic"] return items.contains(item.funcName ?? "") } func handleWebItem(item: ZSWebItem, context: ZSWebContext, block: (String) -> Void) { if item.funcName == "share" { triggerShareFunctionWithItem(item: item) } else if item.funcName == "copyBoard" { triggerCopyBoardWithItem(item: item) } else if item.funcName == "sharespread" || item.funcName == "shareEarnings" { shareShituWithItem(item: item) } else if item.funcName == "setTopBarColor" { } else if item.funcName == "getDeviceInfo" { let userId = ZSThirdLogin.share.userInfo?.user?._id ?? "" let version = ZSConfigUtil.versionShortCode() let platformString = ZSConfigUtil.platformString() let systemVersion = UIDevice.current.systemVersion let uniquelIdfa = ZSConfigUtil.uniquelIdfa() let publisherID = ZSConfigUtil.publisherId() let channel = ZSConfigUtil.channel() let appName = ZSConfigUtil.appName() let deviceInfo = ["userId":userId, "deviceId":uniquelIdfa, "channel":channel, "appName":appName, "version":version, "systemVersion":systemVersion, "publisherID":publisherID, "deviceModel":platformString] item.callbackParamStr = deviceInfo.toJSON block("\(item.callback ?? "")(\(item.callbackParamStr ?? ""))") } } func triggerShareFunctionWithItem(item:ZSWebItem) { } func triggerCopyBoardWithItem(item:ZSWebItem) { } func shareShituWithItem(item:ZSWebItem) { } } ================================================ FILE: zhuishushenqi/Root/Models/ZSWebUserHandler.swift ================================================ // // ZSWebUserHandler.swift // zhuishushenqi // // Created by yung on 2019/6/30. // Copyright © 2019 QS. All rights reserved. // import UIKit class ZSWebUserHandler: ZSWebEventHandlerProtocol { func handleWebItem(item: ZSWebItem, context: ZSWebContext, block: (String) -> Void) { } static func canHandleWebItem(item:ZSWebItem) ->Bool { let items = ["getUserInfo","login","syncContacts","updateUserPreference","handleBookShelf","isWinMoneyOpen","recharge"] return items.contains(item.funcName ?? "") } } ================================================ FILE: zhuishushenqi/Root/Models/ZSYJSchemeHandle.swift ================================================ // // ZSYJSchemeHandle.swift // zhuishushenqi // // Created by caony on 2019/1/7. // Copyright © 2019年 QS. All rights reserved. // import Foundation class ZSYJSchemeHandle: NSObject { // + (id)dictionaryWithJsonString:(id)arg1; // + (void)handleTaobaoUrl:(id)arg1; static func handleTaobaoUrl(url:String) { } static func dictionaryWithJsonString(str:String) { } } ================================================ FILE: zhuishushenqi/Root/Service/ZSBaseService.swift ================================================ // // ZSBaseService.swift // zhuishushenqi // // Created by yung on 2018/6/16. // Copyright © 2018年 QS. All rights reserved. // import Foundation import RxSwift import RxCocoa import RxAlamofire import Alamofire import HandyJSON public typealias ZSBaseCallback = (T?)->Void public typealias ZSReaderBaseCallback = (T)->Void public typealias ZSReaderTypeCallback = (T,S)->Void class ZSBaseService { } ================================================ FILE: zhuishushenqi/Root/Service/ZSDiscussWebService.swift ================================================ // // ZSDiscussWebService.swift // zhuishushenqi // // Created by caony on 2018/8/21. // Copyright © 2018年 QS. All rights reserved. // import UIKit class ZSDiscussWebService: ZSBaseService { func fetchDiscuss(url:String,completion:@escaping ZSBaseCallback<[BookComment]>){ zs_get(url) { (json) in if let posts = json?["posts"] as? [[String:Any]] { if let comments = [BookComment].deserialize(from: posts) as? [BookComment] { completion(comments) } } } } } ================================================ FILE: zhuishushenqi/Root/Service/ZSLoginService.swift ================================================ // // ZSLoginService.swift // zhuishushenqi // // Created by yung on 2018/10/19. // Copyright © 2018年 QS. All rights reserved. // import UIKit import HandyJSON class ZSLoginService { func QQLogin(url:String, parameter:[String:Any]?,completion:@escaping ZSBaseCallback) { zs_post(url, parameters: parameter) { (json) in if let user = ZSQQLoginResponse.deserialize(from: json) { completion(user) } else { completion(nil) } } } func WXLogin(url:String, parameter:[String:Any]?,completion:@escaping ZSBaseCallback) { zs_post(url, parameters: parameter) { (json) in if let user = ZSQQLoginResponse.deserialize(from: json) { completion(user) } else { completion(nil) } } } func WBLogin(url:String, parameter:[String:Any]?,completion:@escaping ZSBaseCallback) { zs_post(url, parameters: parameter) { (json) in if let user = ZSQQLoginResponse.deserialize(from: json) { completion(user) } else { completion(nil) } } } func fetchSMSCode(url:String, parameter:[String:Any]?,completion:@escaping ZSBaseCallback<[String:Any]>) { zs_post(url, parameters: parameter) { (json) in completion(json) } } func mobileLgin(urlString:String, param:[String:Any]?, completion:@escaping ZSBaseCallback) { zs_post(urlString, parameters: param) { (json) in if let user = ZSQQLoginResponse.deserialize(from: json) { completion(user) } } } } ================================================ FILE: zhuishushenqi/Root/Service/ZSMyService.swift ================================================ // // ZSMyService.swift // zhuishushenqi // // Created by yung on 2018/10/20. // Copyright © 2018年 QS. All rights reserved. // import UIKit import ZSAPI class ZSMyService { func fetchAccount(token:String ,completion:@escaping ZSBaseCallback) { let api = ZSAPI.account(token: token) // https://api.zhuishushenqi.com/user/account?token=MR7bHBNojupotkWO0IH1QZY0 zs_get(api.path, parameters: api.parameters) { (json) in if let account = ZSAccount.deserialize(from: json) { completion(account) } else { completion(nil) } } } func fetchCoin(token:String, completion:@escaping ZSBaseCallback) { // http://goldcoin.zhuishushenqi.com/account?token=MR7bHBNojupotkWO0IH1QZY0 let api = ZSAPI.golden(token: token) zs_get(api.path, parameters: api.parameters) { (json) in let coin = ZSCoin.deserialize(from: json) completion(coin) } } func fetchDetail(token:String, completion:@escaping ZSBaseCallback) { // https://api.zhuishushenqi.com/user/detail-info?token=MR7bHBNojupotkWO0IH1QZY0 let api = ZSAPI.userDetail(token: token) zs_get(api.path, parameters: api.parameters) { (json) in let detail = ZSUserDetail.deserialize(from: json) completion(detail) } } func fetchUserBind (token:String, completion:@escaping ZSBaseCallback) { // https://api.zhuishushenqi.com/user/loginBind?token=MR7bHBNojupotkWO0IH1QZY0 let api = ZSAPI.userBind(token: token) zs_get(api.path, parameters: api.parameters) { (json) in let bind = ZSUserBind.deserialize(from: json) completion(bind) } } func fetchLogout(token:String, completion:@escaping ZSBaseCallback<[String:Any]>) { // https://api.zhuishushenqi.com/user/logout let api = ZSAPI.logout(token: token) zs_post(api.path, parameters: api.parameters) { (json) in // let books = [ZSUserBookshelf].deserialize(from: json?["books"] as? [Any]) as? [String:Any] completion(json) } } func fetchNicknameChange(url:String, param:[String:Any]?, completion:@escaping ZSBaseCallback<[String:Any]>) { zs_post(url, parameters: param) { (json) in completion(json) } } func fetchVoucherList(url:String, param:[String:Any]?, completion:@escaping ZSBaseCallback<[ZSVoucher]>) { zs_get(url, parameters: param) { (json) in if let vouchers = json?["vouchers"] as? [[String:Any]] { if let voucherModels = [ZSVoucher].deserialize(from: vouchers) as? [ZSVoucher] { completion(voucherModels) } } else { completion([]) } } } } ================================================ FILE: zhuishushenqi/Root/Service/ZSRootWebService.swift ================================================ // // ZSRootWebService.swift // zhuishushenqi // // Created by caony on 2018/6/7. // Copyright © 2018年 QS. All rights reserved. // import Foundation import RxSwift import RxCocoa import RxAlamofire import Alamofire import HandyJSON import ZSAPI final class ZSRootWebService:ZSBaseService { func fetchShelvesUpdate(for books:[BookDetail]) ->Observable<[BookDetail]>{ let id = getIDSOf(books: books) let api = ZSAPI.update(id: id) return requestJSON(.get, api.path, parameters: api.parameters) .observeOn(ConcurrentDispatchQueueScheduler(qos:.background)) .map { responseData in return [BookDetail]() } } func fetchShelvesUpdate(for ids:[String]) ->Observable<[String:Any]>{ let id = (ids as NSArray).componentsJoined(by: ",") let api = ZSAPI.update(id: id) return requestJSON(.get, api.path, parameters: api.parameters) .observeOn(ConcurrentDispatchQueueScheduler(qos:.background)) .map { (_,responseData) in // json解析 if let models = [BookShelf].deserialize(from: responseData as? [Any]) { if let arr = models as? [BookShelf]{ BookManager.shared.updateInfoUpdate(updateInfo: arr) return BookManager.shared.books } } return [:] } } func fetchShelvesMsg() -> Observable{ let shelfApi = ZSAPI.shelfMSG("" as AnyObject) return requestJSON(.get, shelfApi.path, parameters: shelfApi.parameters) .observeOn(ConcurrentDispatchQueueScheduler(qos:.background)) .map{ (_,responseData) in if let message = ZSShelfMessage.deserialize(from: (responseData as! [String:Any])["message"] as? [String:Any]) { return message } return ZSShelfMessage() } } private func getIDSOf(books:[BookDetail]) ->String{ var id = "" for book in books { if (book._id != "") { if id != "" { id.append(",") } id.append(book._id) } } return id } } ================================================ FILE: zhuishushenqi/Root/Service/ZSShelfWebService.swift ================================================ // // ZSShelfWebService.swift // zhuishushenqi // // Created by yung on 2018/7/31. // Copyright © 2018年 QS. All rights reserved. // import Foundation import ZSAPI class ZSShelfWebService: ZSBaseService { func fetchShelvesUpdate(for ids:[String],completion:@escaping ZSBaseCallback<[BookShelf]>) { let id = (ids as NSArray).componentsJoined(by: ",") let api = ZSAPI.update(id: id) zs_get(api.path, parameters: api.parameters).responseJSON { (response) in if let data = response.data { if let obj = try? JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.allowFragments) as? [Any] { if let models = [BookShelf].deserialize(from: obj) { if let arr = models as? [BookShelf]{ completion(arr) } } } } } } func fetchShelfMsg(_ completion:ZSBaseCallback?) { let shelfApi = ZSAPI.shelfMSG("" as AnyObject) zs_get(shelfApi.path, parameters: shelfApi.parameters).responseJSON { (response) in if let data = response.data { if let obj = try? JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.allowFragments) as? [String:Any] { if let message = ZSShelfMessage.deserialize(from: obj["message"] as? [String:Any]) { completion?(message) } else { completion?(nil) } } else { completion?(nil) } } else { completion?(nil) } } } func fetchShelvesUpdate(for books:[BookDetail],completion:ZSBaseCallback?) { let id = getIDSOf(books: books) let api = ZSAPI.update(id: id) zs_get(api.path, parameters: api.parameters).responseJSON { (response) in if let data = response.data { if let obj = try? JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.allowFragments) as? [Any] { } } } } func fetchBookshelf(token:String, completion:@escaping ZSBaseCallback<[ZSUserBookshelf]>) { let api = ZSAPI.bookshelf(token: token) zs_get(api.path, parameters: api.parameters) { (json) in if let models = [ZSUserBookshelf].deserialize(from: json?["books"] as? [Any]) as? [ZSUserBookshelf] { completion(models) } } } func fetchShelfDelete(urlString:String, param:[String:Any]? ,completion:@escaping ZSBaseCallback<[String:Any]>) { zs_delete(urlString, parameters: param) { (json) in completion(json) } } func fetchShelfAdd(urlString:String, param:[String:Any]?, completion:@escaping ZSBaseCallback<[String:Any]>) { zs_put(urlString, parameters: param) { (json) in completion(json) } } func fetchBookInfo(id:String, completion:@escaping ZSBaseCallback) { let api = ZSAPI.book(key: id) zs_get(api.path, parameters: api.parameters) { (json) in if let book = BookDetail.deserialize(from: json) { completion(book) } } } func fetchBlessingBag(urlString:String, param:[String:Any]?, completion:@escaping ZSBaseCallback<[String:Any]>) { zs_get(urlString, parameters: param) { (json) in completion(json) } } func fetchJudgeIn(urlString:String, param:[String:Any]?, completion:@escaping ZSBaseCallback<[String:Any]>) { zs_get(urlString, parameters: param) { (json) in completion(json) } } func fetchSignIn(urlString:String, param:[String:Any]?, completion:@escaping ZSBaseCallback<[String:Any]>) { zs_get(urlString, parameters: param) { (json) in completion(json) } } private func getIDSOf(books:[BookDetail]) ->String{ var id = "" for book in books { if (book._id != "") { if id != "" { id.append(",") } id.append(book._id) } } return id } } ================================================ FILE: zhuishushenqi/Root/ViewModel/ZSDiscussViewModel.swift ================================================ // // ZSDiscussViewModel.swift // zhuishushenqi // // Created by caony on 2018/8/21. // Copyright © 2018年 QS. All rights reserved. // import UIKit import RxSwift import MJRefresh protocol ZSDiscussViewModelProtocol { var models:[AnyObject] { get set } var refreshStatus:Variable{ get } var start:Int { get set } var limit:Int { get set } var selectSectionIndexs:[Int] { get set } func updateSelectSectionIndexs(indexs:[Int]) func fetchDiscuss(_ handler:ZSBaseCallback?) func fetchMoreDiscuss(_ handler:ZSBaseCallback?) } extension ZSDiscussViewModelProtocol { func autoSetRefreshHeaderStatus(header:MJRefreshHeader?,footer:MJRefreshFooter?) -> Disposable{ return refreshStatus.asObservable().subscribe(onNext: { (status) in switch status { case .headerRefreshing: header?.beginRefreshing() case .headerRefreshEnd: header?.endRefreshing() case .footerRefreshing: footer?.beginRefreshing() case .footerRefreshEnd: footer?.endRefreshing() case .noMoreData: footer?.endRefreshingWithNoMoreData() default: break } }) } } class ZSDiscussBaseViewModel: NSObject, ZSRefreshProtocol, ZSDiscussViewModelProtocol { var start: Int = 0 var limit: Int = 20 var selectSectionIndexs: [Int] = [] var block:String = "girl" internal var refreshStatus: Variable = Variable(.none) var models: [AnyObject] = [] var webService = ZSDiscussWebService() func updateSelectSectionIndexs(indexs: [Int]) { selectSectionIndexs = indexs } func fetchDiscuss(_ handler: ZSBaseCallback?) { start = 0 let url = getURLString(selectIndexs: selectSectionIndexs) webService.fetchDiscuss(url: url) { (comments) in if let cModels = comments { self.models = cModels } self.refreshStatus.value = .headerRefreshEnd handler?(nil) } } func fetchMoreDiscuss(_ handler: ZSBaseCallback?) { start += 20 let url = getURLString(selectIndexs: selectSectionIndexs) webService.fetchDiscuss(url: url) { (comments) in if let cModels = comments { self.models.append(contentsOf: cModels) } self.refreshStatus.value = .footerRefreshEnd handler?(nil) } } func getURLString(selectIndexs:[Int])->String{ // local list //all ,默认排序 // http://api.zhuishushenqi.com/post/by-block?block=ramble&duration=all&sort=updated&start=0&limit=20 // all,最新发布 // http://api.zhuishushenqi.com/post/by-block?block=ramble&duration=all&sort=created&start=0&limit=20 // all,最多评论 // http://api.zhuishushenqi.com/post/by-block?block=ramble&duration=all&sort=comment-count&start=0&limit=20 // 精品,默认 // http://api.zhuishushenqi.com/post/by-block?block=ramble&distillate=true&duration=all&sort=updated&start=0&limit=20 // 精品,最新发布 // http://api.zhuishushenqi.com/post/by-block?block=ramble&distillate=true&duration=all&sort=created&start=0&limit=20 // 精品,最多评论 // http://api.zhuishushenqi.com/post/by-block?block=ramble&distillate=true&duration=all&sort=comment-count&start=0&limit=20 let durations = ["duration=all","duration=all&distillate=true"] let sort = ["sort=updated","sort=created","sort=comment-count"] let urlString = "\(BASEURL)/post/by-block?block=\(block)&\(durations[selectIndexs[0]])&\(sort[selectIndexs[1]])&start=\(start)&limit=\(limit)" return urlString } } class ZSDiscussViewModel: NSObject,ZSRefreshProtocol { internal var refreshStatus: Variable = Variable(.none) var webService = ZSDiscussWebService() var block:String = "girl" var models:[BookComment] = [] var start = 0 var limit = 20 func fetchDiscuss(selectIndexs:[Int],completion:@escaping ZSBaseCallback<[BookComment]>){ start = 0 let url = getURLString(selectIndexs: selectIndexs) webService.fetchDiscuss(url: url) { (comments) in if let cModels = comments { self.models = cModels } self.refreshStatus.value = .headerRefreshEnd completion(comments) } } func fetchMore(selectIndexs:[Int],completion:@escaping ZSBaseCallback<[BookComment]>) { start += 20 let url = getURLString(selectIndexs: selectIndexs) webService.fetchDiscuss(url: url) { (comments) in if let cModels = comments { self.models.append(contentsOf: cModels) } self.refreshStatus.value = .footerRefreshEnd completion(comments) } } func getURLString(selectIndexs:[Int])->String{ // local list //all ,默认排序 // http://api.zhuishushenqi.com/post/by-block?block=ramble&duration=all&sort=updated&start=0&limit=20 // all,最新发布 // http://api.zhuishushenqi.com/post/by-block?block=ramble&duration=all&sort=created&start=0&limit=20 // all,最多评论 // http://api.zhuishushenqi.com/post/by-block?block=ramble&duration=all&sort=comment-count&start=0&limit=20 // 精品,默认 // http://api.zhuishushenqi.com/post/by-block?block=ramble&distillate=true&duration=all&sort=updated&start=0&limit=20 // 精品,最新发布 // http://api.zhuishushenqi.com/post/by-block?block=ramble&distillate=true&duration=all&sort=created&start=0&limit=20 // 精品,最多评论 // http://api.zhuishushenqi.com/post/by-block?block=ramble&distillate=true&duration=all&sort=comment-count&start=0&limit=20 let durations = ["duration=all","duration=all&distillate=true"] let sort = ["sort=updated","sort=created","sort=comment-count"] let urlString = "\(BASEURL)/post/by-block?block=\(block)&\(durations[selectIndexs[0]])&\(sort[selectIndexs[1]])&start=\(start)&limit=\(limit)" return urlString } } ================================================ FILE: zhuishushenqi/Root/ViewModel/ZSHomeViewModel.swift ================================================ // // ZSHomeViewModel.swift // zhuishushenqi // // Created by caony on 2018/5/21. // Copyright © 2018年 QS. All rights reserved. // import Foundation import RxCocoa import RxSwift protocol HomeBookCase { func books() -> Observable<[BookDetail]> func add(book: BookDetail) -> Observable func delete(book: BookDetail) -> Observable } final class ZSHomeViewModel { let books:Observable<[BookDetail]> let reloadData: Observable let selectedBook: Observable? fileprivate let _books = BehaviorRelay<[BookDetail]>(value: []) private let disposeBag = DisposeBag() init(booksObservable:Observable<[BookDetail]>,selectedIndexPath: Observable? = nil) { self.books = _books.asObservable() self.reloadData = _books.asObservable().map { _ in } self.selectedBook = selectedIndexPath?.withLatestFrom(_books.asObservable()) {$1[$0.row] } booksObservable .bind(to: _books) .disposed(by: disposeBag) } } extension ZSHomeViewModel:ValueCompatible {} extension Value where Base == ZSHomeViewModel { var books:[BookDetail] { return base._books.value } } ================================================ FILE: zhuishushenqi/Root/ViewModel/ZSLocalShelfViewModel.swift ================================================ // // ZSLocalShelfViewModel.swift // zhuishushenqi // // Created by yung on 2018/7/31. // Copyright © 2018年 QS. All rights reserved. // import Foundation class ZSLocalShelfViewModel { var books:[String:BookDetail] = [:] var paths:[String] = [] init() { scanPath() } func scanPath(){ let path = "\(NSHomeDirectory())/Documents/Inbox/" if let items = try? FileManager.default.contentsOfDirectory(atPath: path) { paths = items.map { "\(path)\($0)" } } } func fetchBook(path:String,completion:((BookDetail)->Void)?) { if let md5 = path.fileMD5() { // 存在则直接返回,不存在则需要进行解析 if let obj = BookManager.shared.localBookInfo(key: md5) { books[path] = obj completion?(obj) } else { QSReaderParse.fetchBook(path: path) { (book) in if let model = book { self.books[path] = model completion?(model) } } } } } } ================================================ FILE: zhuishushenqi/Root/ViewModel/ZSMyViewModel.swift ================================================ // // ZSMyViewModel.swift // zhuishushenqi // // Created by yung on 2018/10/20. // Copyright © 2018年 QS. All rights reserved. // import UIKit import AdSupport import ZSAPI import RxSwift class ZSMyViewModel: NSObject, ZSRefreshProtocol { var refreshStatus: Variable = Variable(.none) let webService = ZSMyService() let loginService = ZSLoginService() var account:ZSAccount? var coin:ZSCoin? var detail:ZSUserDetail? var bind:ZSUserBind? var useableVoucher:[ZSVoucher] = [] var unuseableVoucher:[ZSVoucher] = [] var expiredVoucher:[ZSVoucher] = [] func fetchAccount(token:String ,completion:@escaping ZSBaseCallback) { webService.fetchAccount(token: token) { [weak self](account) in guard let sSelf = self else { return } sSelf.account = account completion(account) } } func fetchCoin(token:String, completion:@escaping ZSBaseCallback) { webService.fetchCoin(token: token) { [weak self](coin) in guard let sSelf = self else { return } sSelf.coin = coin completion(coin) } } func fetchDetail(token:String, completion:@escaping ZSBaseCallback) { webService.fetchDetail(token: token) { [weak self](detail) in guard let sSelf = self else { return } sSelf.detail = detail completion(detail) } } func fetchUserBind (token:String, completion:@escaping ZSBaseCallback) { webService.fetchUserBind(token: token) { [weak self](bind) in guard let sSelf = self else { return } sSelf.bind = bind completion(bind) } } func fetchLogout(token:String, completion:@escaping ZSBaseCallback<[String:Any]>) { webService.fetchLogout(token: token) { (json) in completion(json) } } func fetchSMSCode(param:[String:String]?, completion:@escaping ZSBaseCallback<[String:Any]>) { let mobile = param?["mobile"] ?? "" let randstr = param?["Randstr"] ?? "" let ticket = param?["Ticket"] ?? "" let captchaType = param?["captchaType"] ?? "" let type = param?["type"] ?? "" let api = ZSAPI.SMSCode(mobile: mobile, Randstr: randstr, Ticket: ticket, captchaType: captchaType, type: type) loginService.fetchSMSCode(url: api.path, parameter: api.parameters) { (json) in completion(json) } } func mobileLogin(mobile:String, smsCode:String, completion:@escaping ZSBaseCallback) { let version = "2" let platform_code = "mobile" let idfa = ASIdentifierManager.shared().advertisingIdentifier.uuidString let api = ZSAPI.mobileLogin(mobile: mobile, idfa: idfa, platform_code: platform_code, smsCode: smsCode, version: version) loginService.mobileLgin(urlString: api.path, param: api.parameters) { (json) in completion(json) } } func fetchNicknameChange(nickname:String, token:String, completion:@escaping ZSBaseCallback<[String:Any]>) { let api = ZSAPI.nicknameChange(nickname: nickname, token: token) webService.fetchNicknameChange(url: api.path, param: api.parameters) { (json) in completion(json) } } // https://api.zhuishushenqi.com/voucher?token=xAk9Ac8k3Jj9Faf11q8mBVPQ&type=useable&start=0&limit=20 func fetchVoucher(token:String, type:String, start:Int, limit:Int, completion:@escaping ZSBaseCallback<[ZSVoucher]>) { let api = QSAPI.voucherList(token: token, type: type, start: start, limit: limit) webService.fetchVoucherList(url: api.path, param: api.parameters) { [weak self](vouchers) in guard let sSelf = self else { return } if type == "useable" { sSelf.useableVoucher = vouchers ?? [] } else if type == "unuseable" { sSelf.unuseableVoucher = vouchers ?? [] } else { sSelf.expiredVoucher = vouchers ?? [] } sSelf.refreshStatus.value = .headerRefreshEnd completion(vouchers) } } func fetchMoreVoucher(token:String, type:String, start:Int, limit:Int, completion:@escaping ZSBaseCallback<[ZSVoucher]>) { let api = QSAPI.voucherList(token: token, type: type, start: start, limit: limit) webService.fetchVoucherList(url: api.path, param: api.parameters) { [weak self](vouchers) in guard let sSelf = self else { return } if type == "useable" { sSelf.useableVoucher.append(contentsOf: vouchers ?? []) } else if type == "unuseable" { sSelf.unuseableVoucher.append(contentsOf: vouchers ?? []) } else { sSelf.expiredVoucher.append(contentsOf: vouchers ?? []) } sSelf.refreshStatus.value = .footerRefreshEnd completion(vouchers) } } } ================================================ FILE: zhuishushenqi/Root/ViewModel/ZSProtocol.swift ================================================ // // ZSProtocol.swift // zhuishushenqi // // Created by caony on 2018/6/11. // Copyright © 2018年 QS. All rights reserved. // import Foundation import RxSwift import RxCocoa import MJRefresh enum ZSRefreshStatus { case none case headerRefreshing case headerRefreshEnd case footerRefreshing case footerRefreshEnd case noMoreData } protocol ZSRefreshControl { var refreshStatus:ZSRefreshStatus { get } } extension ZSRefreshControl { func autoSetRefreshHeaderStatus(header:MJRefreshHeader?,footer:MJRefreshFooter?) { } } protocol ZSRefreshProtocol { var refreshStatus:Variable{ get } } extension ZSRefreshProtocol { func autoSetRefreshHeaderStatus(header:MJRefreshHeader?,footer:MJRefreshFooter?) -> Disposable{ return refreshStatus.asObservable().subscribe(onNext: { (status) in switch status { case .headerRefreshing: header?.beginRefreshing() case .headerRefreshEnd: header?.endRefreshing() case .footerRefreshing: footer?.beginRefreshing() case .footerRefreshEnd: footer?.endRefreshing() case .noMoreData: footer?.endRefreshingWithNoMoreData() default: break } }) } } protocol Refreshable { } extension Refreshable where Self : UIViewController{ @discardableResult func initRefreshHeader(_ scrollView: UIScrollView,_ action:@escaping () ->Void) -> MJRefreshHeader{ scrollView.mj_header = MJRefreshNormalHeader(refreshingBlock: { action() }) return scrollView.mj_header } @discardableResult func initRefreshFooter(_ scrollView: UIScrollView,_ action:@escaping () ->Void) -> MJRefreshFooter{ scrollView.mj_footer = MJRefreshAutoNormalFooter(refreshingBlock: { action() }) return scrollView.mj_footer } } extension Refreshable where Self : UIScrollView { @discardableResult func initRefreshHeader(_ action:@escaping () ->Void) -> MJRefreshHeader{ mj_header = MJRefreshNormalHeader(refreshingBlock: { action() }) return mj_header } @discardableResult func initRefreshFooter(_ action:@escaping () ->Void) -> MJRefreshFooter{ mj_footer = MJRefreshAutoNormalFooter(refreshingBlock: { action() }) return mj_footer } } extension Refreshable where Self : UIView{ @discardableResult func initRefreshHeader(_ scrollView: UIScrollView,_ action:@escaping () ->Void) -> MJRefreshHeader{ scrollView.mj_header = MJRefreshNormalHeader(refreshingBlock: { action() }) return scrollView.mj_header } @discardableResult func initRefreshFooter(_ scrollView: UIScrollView,_ action:@escaping () ->Void) -> MJRefreshFooter{ scrollView.mj_footer = MJRefreshAutoNormalFooter(refreshingBlock: { action() }) return scrollView.mj_footer } } ================================================ FILE: zhuishushenqi/Root/ViewModel/ZSRootViewModel.swift ================================================ // // ZSRootViewModel.swift // zhuishushenqi // // Created by caony on 2018/6/7. // Copyright © 2018年 QS. All rights reserved. // import Foundation import RxSwift import RxCocoa import MJRefresh struct HomeSection { var items: [Item] } extension HomeSection: SectionModelType { typealias Item = BookDetail init(original: HomeSection, items: [HomeSection.Item]) { self = original self.items = items } } final class ZSRootViewModel:NSObject,ZSRefreshProtocol { // dataSource,监听 var section:Driver<[HomeSection]>? // 首页的刷新命令,入参是当前的分类 let refreshCommand = ReplaySubject.create(bufferSize: 1) // 书架广告信息 @objc var shelfMessage:ZSShelfMessage? fileprivate var bricks:BehaviorSubject<[BookDetail]>? internal var refreshStatus: Variable = Variable(.none) @objc internal var books:[String:Any] = BookManager.shared.books // 保存所有书籍的id,books存在时,他就存在 fileprivate var booksID:[String] = [] fileprivate let shelvesWebService = ZSRootWebService() fileprivate let disposeBag = DisposeBag() override init() { super.init() bricks = BehaviorSubject<[BookDetail]>(value: books.allValues() as! [BookDetail]) section = bricks? .asObservable() .map({ (bricks) ->[HomeSection] in // bricks 发送on(.next(response.allValues() as! [BookDetail] ) 会回调到这里 return [HomeSection(items: bricks)] }) .asDriver(onErrorJustReturn: []) // 详情页添加书籍信息 NotificationCenter.default.rx.notification(Notification.Name(rawValue:BOOKSHELF_ADD)) .observeOn(MainScheduler.asyncInstance) .subscribe(onNext: { (noti) in // 更新内存中books的值与archive中的值 if let book = noti.object as? BookDetail { self.books[book._id] = book self.booksID = self.books.allKeys() BookManager.shared.addBook(book: book) // 添加新的书籍之后需要刷新更新信息 self.refreshCommand.onNext([:]) } }) .disposed(by: disposeBag) refreshCommand .flatMapLatest { query in self.shelvesWebService.fetchShelvesUpdate(for: self.books.allKeys()).asDriver(onErrorJustReturn: [:]) } .subscribe({ (event) in switch event { case let .next(response): self.refreshStatus.value = .headerRefreshEnd self.bricks?.on(.next(response.allValues() as! [BookDetail] )) break case let .error(error): self.refreshStatus.value = .headerRefreshEnd QSLog(error) break case .completed: break } }) .disposed(by: disposeBag) //========初始化========== booksID = books.allKeys() refreshStatus.value = .none } } extension ZSRootViewModel{ func fetchShelvesBooks(_ completion: (() -> Void)? = nil){ refreshStatus.value = .none shelvesWebService.fetchShelvesUpdate(for: books.allKeys()) .observeOn(MainScheduler.instance) .catchErrorJustReturn(self.books) .bind(onNext: { (updates) in // 将更新信息放入books self.refreshStatus.value = .headerRefreshEnd completion?() }) .disposed(by: disposeBag) } func fetchShelfMessage(_ completion: (() -> Void)? = nil){ shelvesWebService.fetchShelvesMsg() .bind(onNext:{ (message) in self.shelfMessage = message completion?() }) .disposed(by: disposeBag) } } ================================================ FILE: zhuishushenqi/Root/ViewModel/ZSSetting.plist ================================================ Header 推送设置 Footer 如需开启/关闭提醒,请在系统的「设置」- 「通知中心」中进行设置 Rows Title 小说更新提醒 Detail 已开启 Header 通用设置 Footer Rows Title 书架排序 Detail 最近阅读 AccessoryType 1 reg 最近阅读 更新时间 Title 书架同步 Detail Switch AccessoryType 1 Title 省流量模式(无头像) Detail Switch Title 夜间模式 Detail Switch Header 关于我们V2.26.1 Footer Rows Title 清理网络缓存 Detail 0 MB AccessoryType 1 Title 书籍缓存管理 Detail AccessoryType 1 Title 免责声明 Detail AccessoryType 1 Title 意见反馈 Detail AccessoryType 1 Title 加入我们 Detail AccessoryType 1 Title 请赐予我评分 Detail AccessoryType 1 ================================================ FILE: zhuishushenqi/Root/ViewModel/ZSSettingViewModel.swift ================================================ // // ZSSettingViewModel.swift // zhuishushenqi // // Created by yung on 2018/8/15. // Copyright © 2018年 QS. All rights reserved. // import UIKit class ZSSettingViewModel: NSObject { var sections:[[String:Any]] = [] let webService = ZSMyService() func fetchSetting(){ } func fetchLogout(token:String, completion:@escaping ZSBaseCallback<[String:Any]>) { webService.fetchLogout(token: token) { (json) in completion(json) } } } ================================================ FILE: zhuishushenqi/Root/ViewModel/ZSShelfViewModel.swift ================================================ // // ZSShelfViewModel.swift // zhuishushenqi // // Created by yung on 2018/7/31. // Copyright © 2018年 QS. All rights reserved. // import Foundation import RxSwift import ZSAPI class ZSShelfViewModel:ZSRefreshProtocol { internal var refreshStatus: Variable = Variable(.none) var localBooks:[String] = [] let database = ZSDatabase() // 网络书籍 var books:[String:BookDetail] { get { return ZSBookManager.shared.books } set {} } // 保存所有书籍的id,books存在时,他就存在 var booksID:[String] { get { return ZSBookManager.shared.ids } set {} } var bookshelfBooks:[ZSUserBookshelf] = [] var bookshelfs:[BookDetail] = [] fileprivate let shelvesWebService = ZSShelfWebService() fileprivate let disposeBag = DisposeBag() @objc var shelfMessage:ZSShelfMessage? let helper = MonitorFileChangeHelp() init() { let path = "\(NSHomeDirectory())/Documents/Inbox/" scanPath(path: path) helper.watcher(forPath: path) { (type) in self.scanPath(path: path) } NotificationCenter.default .rx .notification(Notification.Name(rawValue:BOOKSHELF_ADD)) .observeOn(MainScheduler.asyncInstance) .subscribe(onNext: { (noti) in // 更新内存中books的值与archive中的值 if let book = noti.object as? BookDetail { let result = self.database.insertBookshelf(book: book) if result { } ZSBookManager.shared.addBook(book: book) } }) .disposed(by: disposeBag) NotificationCenter.default.rx.notification(Notification.Name(rawValue:BOOKSHELF_DELETE)) .observeOn(MainScheduler.asyncInstance) .subscribe(onNext: { (noti) in // 更新内存中books的值与archive中的值 if let book = noti.object as? BookDetail { ZSBookManager.shared.deleteBook(book: book) } }) .disposed(by: disposeBag) refreshStatus.value = .none } func scanPath(path:String){ let path = "\(NSHomeDirectory())/Documents/Inbox/" if let items = try? FileManager.default.contentsOfDirectory(atPath: path) { localBooks = items } } func fetchBooks() ->[BookDetail] { if bookshelfs.count > 0 { return bookshelfs } let books = database.queryBookshelf() return books } func existBook(id:String) -> Bool { var exist = false for item in self.booksID { if item == id { exist = true } } return exist } func topBook(key:String) { ZSBookManager.shared.topBook(key: key) } } extension ZSShelfViewModel { func fetchShelvesBooks(completion:ZSBaseCallback?) { refreshStatus.value = .none if booksID.count > 0 { shelvesWebService.fetchShelvesUpdate(for: booksID) { (updateInfo) in self.refreshStatus.value = .headerRefreshEnd if let info = updateInfo as? [BookShelf] { // for update in info { // self.database.updateInfo(updateInfo: update) // } ZSBookManager.shared.update(bookshelfs: info) // BookManager.shared.updateInfoUpdate(updateInfo: info) completion?(nil) } } } else { self.refreshStatus.value = .headerRefreshEnd } } func fetchShelfMessage(completion:ZSBaseCallback?){ shelvesWebService.fetchShelfMsg { (message) in self.shelfMessage = message completion?(nil) } } func fetchShelfDelete(books:[BookDetail], token:String, completion:@escaping ZSBaseCallback<[String:Any]>) { var booksID = "" for book in books { if booksID != "" { booksID.append(",") } booksID.append(book._id) } let api = ZSAPI.booksheldDelete(books: booksID, token: token) shelvesWebService.fetchShelfDelete(urlString: api.path, param: api.parameters) { (json) in completion(json) } } func fetchShelfAdd(books:[BookDetail], token:String, completion:@escaping ZSBaseCallback<[String:Any]>) { var booksID = "" for book in books { if booksID != "" { booksID.append(",") } booksID.append(book._id) } let api = ZSAPI.bookshelfAdd(books: booksID, token: token) shelvesWebService.fetchShelfAdd(urlString: api.path, param: api.parameters) { (json) in completion(json) } } func fetchUserBookshelf(token:String, completion:@escaping ZSBaseCallback<[ZSUserBookshelf]>) { shelvesWebService.fetchBookshelf(token: token) { (books) in if let models = books { self.bookshelfBooks = models self.fetchBooksInfo(books: models, completion: completion) } } } func fetchBooksInfo(books:[ZSUserBookshelf], completion:@escaping ZSBaseCallback<[ZSUserBookshelf]>) { for item in books { if !existBook(id: item.id) { self.shelvesWebService.fetchBookInfo(id: item.id, completion: { (book) in // 获取到书记信息后加入到books中 self.lock(object: self.booksID as AnyObject, callback: { if let bookDetail = book { self.booksID.append(item.id) self.books[item.id] = bookDetail ZSBookManager.shared.addBook(book: bookDetail) completion(books) } }) }) } } } func fetchBlessingBag(token:String, completion:@escaping ZSBaseCallback<[String:Any]>) { let api = ZSAPI.blessing_bag(token: token) shelvesWebService.fetchBlessingBag(urlString: api.path, param: api.parameters) { (json) in completion(json) } } func fetchJudgeIn(token:String, completion:@escaping ZSBaseCallback<[String:Any]>) { let api = ZSAPI.judgeSignIn(token: token) shelvesWebService.fetchJudgeIn(urlString: api.path, param: api.parameters) { (json) in completion(json) } } func fetchSignIn(token:String, activityId:String, version:String, type:String, completion:@escaping ZSBaseCallback<[String:Any]>) { let api = ZSAPI.signIn(token: token, activityId: activityId, version: version, type: type) shelvesWebService.fetchSignIn(urlString: api.path, param: api.parameters) { (json) in completion(json) } } // 下载章节到磁盘 // func func lock(object:AnyObject, callback:()->Void) { print("\(object)开始加锁") objc_sync_enter(object) callback() print("\(object)结束加锁") objc_sync_exit(object) } } ================================================ FILE: zhuishushenqi/Root/ViewModel/ZSVoicePlayViewModel.swift ================================================ // // ZSVoicePlayViewModel.swift // zhuishushenqi // // Created by caony on 2019/3/23. // Copyright © 2019年 QS. All rights reserved. // import Foundation class ZSVoicePlayViewModel { func request(completion:@escaping ZSBaseCallback) { } } ================================================ FILE: zhuishushenqi/Root/Views/BarButton.swift ================================================ // // BarButton.swift // zhuishushenqi // // Created by Nory Cao on 16/9/17. // Copyright © 2016年 QS. All rights reserved. // import UIKit class BarButton: UIButton { override func layoutSubviews() { super.layoutSubviews() imageView?.frame = self.bounds imageView?.contentMode = .scaleAspectFill } } ================================================ FILE: zhuishushenqi/Root/Views/DynamicCell.swift ================================================ // // DynamicCell.swift // zhuishushenqi // // Created by Nory Cao on 16/10/22. // Copyright © 2016年 QS. All rights reserved. // import UIKit class DynamicCell: UITableViewCell { var model:QSHotModel? @IBOutlet weak var icon: UIImageView! @IBOutlet weak var forward: UIImageView! @IBOutlet weak var transfer: UILabel! @IBOutlet weak var author: UILabel! @IBOutlet weak var update: UILabel! @IBOutlet weak var title: UILabel! @IBOutlet weak var content: UILabel! @IBOutlet weak var comment: UIButton! @IBOutlet weak var publish: UIButton! @IBOutlet weak var follow: UIButton! override func awakeFromNib() { super.awakeFromNib() // Initialization code publish.layer.borderColor = UIColor.gray.cgColor publish.layer.borderWidth = 0.2 comment.layer.borderWidth = 0.2 comment.layer.borderColor = UIColor.gray.cgColor follow.layer.borderColor = UIColor.gray.cgColor follow.layer.borderWidth = 0.2 icon.layer.cornerRadius = 5 icon.layer.masksToBounds = true; } func setContent(model:QSHotModel){ self.model = model setNeedsDisplay() } override func setSelected(_ selected: Bool, animated: Bool) { super.setSelected(selected, animated: animated) } override func layoutSubviews() { super.layoutSubviews() let urlString = self.model?.user.avatar ?? "" self.icon.qs_setAvatarWithURLString(urlString: urlString) transfer.isHidden = true forward.isHidden = true author.text = "\(self.model?.user.nickname ?? "") lv.\(self.model?.user.lv ?? 0)" title.text = "\(self.model?.tweet.title ?? "")" content.text = "\(self.model?.tweet.content ?? "")" comment.setTitle("\(self.model?.tweet.commented ?? 0)" , for: .normal) publish.setTitle("\(self.model?.tweet.retweeted ?? 0)", for: .normal) update.qs_setCreateTime(createTime: self.model?.tweet.hotAt ?? "", append: "") } } ================================================ FILE: zhuishushenqi/Root/Views/DynamicCell.xib ================================================ ================================================ FILE: zhuishushenqi/Root/Views/DynamicCell.xib~876a9ee6afa162f4fb71eff5fa02f2e0dbe52ae2 ================================================ ================================================ FILE: zhuishushenqi/Root/Views/DynamicCell.xib~HEAD ================================================ ================================================ FILE: zhuishushenqi/Root/Views/HomeListViewCell.swift ================================================ // // HomeListViewCell.swift // zhuishushenqi // // Created by yung on 2017/6/6. // Copyright © 2017年 QS. All rights reserved. // import UIKit protocol HomeListViewCellDelegate { func delete(model:BookDetail) } class HomeListViewCell: UITableViewCell,UIScrollViewDelegate { var delegate:HomeListViewCellDelegate? var model:BookDetail?{ didSet{ if let modelll = model { self.configureCell(model: modelll) } } } var imgView: UIImageView? var title:UILabel? var detailTitle:UILabel? var updatedLabel:UILabel! private var scrollView:UIScrollView! override func awakeFromNib() { super.awakeFromNib() // Initialization code } override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) initSubview() } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } fileprivate func initSubview(){ scrollView = UIScrollView(frame: CGRect(x: 0,y: 0,width: ScreenWidth,height: 64)) scrollView.contentSize = CGSize(width: (ScreenWidth - 74)*2, height: 64) scrollView.isPagingEnabled = true scrollView.delegate = self scrollView.showsHorizontalScrollIndicator = false contentView.addSubview(scrollView) //此处解决 UIScrollView屏蔽 UITableViewCell的点击事件,将 scrollView的手势识别交给他的父视图,苹果官方推荐的做法,但是这样回造成UIScrollView上的button无法点击,本次使用第二种方式,传递事件 // scrollView.isUserInteractionEnabled = false // contentView.addGestureRecognizer(scrollView.panGestureRecognizer) let imgView = UIImageView(frame: CGRect(x: 15, y: 10, width: 34, height: 44)) imgView.backgroundColor = UIColor.orange imgView.image = UIImage(named: "default_book_cover") self.imgView = imgView let label = UILabel(frame: CGRect(x: imgView.frame.maxX + 10,y: 10,width: ScreenWidth - imgView.frame.maxX - 20,height: 20)) label.font = UIFont.systemFont(ofSize: 13) self.title = label let detaillabel = UILabel(frame: CGRect(x: imgView.frame.maxX + 10,y: 30,width: ScreenWidth - imgView.frame.maxX - 20,height: 20)) detaillabel.font = UIFont.systemFont(ofSize: 13) self.detailTitle = detaillabel updatedLabel = UILabel(frame: CGRect(x: 0, y: 15, width: 40, height: 10)) updatedLabel.backgroundColor = UIColor.red updatedLabel.font = UIFont.systemFont(ofSize: 9) updatedLabel.isHidden = true updatedLabel.text = "更新" updatedLabel.textAlignment = .center updatedLabel.textColor = UIColor.white scrollView.addSubview(updatedLabel) scrollView.addSubview(self.imgView!) scrollView.addSubview(self.title!) scrollView.addSubview(self.detailTitle!) let delete:QSHomeDeleteBtn = QSHomeDeleteBtn(type: .custom) delete.frame = CGRect(x: scrollView.contentSize.width - 65, y: 0, width: 64, height: 64) delete.setTitle("删除", for: .normal) delete.setImage(UIImage(named:"bs_delete"), for: .normal) delete.contentHorizontalAlignment = .center delete.setTitleColor(UIColor.red, for: .normal) delete.addTarget(self, action: #selector(deleteAction(btn:)), for: .touchUpInside) // delete.layoutImageView() scrollView.addSubview(delete) } @objc func deleteAction(btn:UIButton){ delegate?.delete(model: self.model!) } func scrollViewDidScroll(_ scrollView: UIScrollView) { } func configureCell(model:BookDetail){ title!.text = model.title let created = model.updateInfo?.updated ?? "2014-02-23T16:48:18.179Z" self.detailTitle?.qs_setCreateTime(createTime: created, append: "更新:\(model.updateInfo?.lastChapter ?? "")") let urlString = "\(self.model?.cover ?? "")" self.imgView?.qs_setBookCoverWithURLString(urlString: urlString) updatedLabel.isHidden = (model.isUpdated) let width = (title?.text ?? "").qs_width(UIFont.systemFont(ofSize: 13), height: 20) let x = (title?.frame.minX ?? 0) + width + 5 updatedLabel.frame = CGRect(x: x, y: 14.5, width: 22, height: 11) } override func setSelected(_ selected: Bool, animated: Bool) { super.setSelected(selected, animated: animated) // Configure the view for the selected state } override func prepareForReuse() { scrollView.contentOffset = CGPoint(x: 0, y: 0) } } //extension UIScrollView{ // //2.解决UIScrollView屏蔽UITableViewCell的点击事件 // open override func touchesBegan(_ touches: Set, with event: UIEvent?) { // let next = touches.first?.view?.next?.next // if next?.isKind(of: UITableViewCell.self) == true { // next?.touchesBegan(touches, with: event) // } // } // // open override func touchesEnded(_ touches: Set, with event: UIEvent?) { // let next = touches.first?.view?.next?.next // if next?.isKind(of: UITableViewCell.self) == true { // next?.touchesEnded(touches, with: event) // } // } // // open override func touchesCancelled(_ touches: Set, with event: UIEvent?) { // let next = touches.first?.view?.next?.next // if next?.isKind(of: UITableViewCell.self) == true { // next?.touchesCancelled(touches, with: event) // } // } //} //protocol DeleteButton { // func layoutImageView() //} // //extension DeleteButton where Self:UIButton{ // func layoutImageView() { // self.imageView?.frame = CGRect(x: 10, y: 0, width: 30, height: 30) // self.imageView?.contentMode = .scaleToFill // self.titleLabel?.frame = CGRect(x: 0, y: 30, width: 50, height: 20) // } //} ================================================ FILE: zhuishushenqi/Root/Views/QSHelpViewCell.swift ================================================ // // QSHelpViewCell.swift // zhuishushenqi // // Created by yung on 2017/6/18. // Copyright © 2017年 QS. All rights reserved. // import UIKit class ZSDiscussCell: UITableViewCell { } class QSHelpViewCell: UITableViewCell, ZSDiscussCellProtocol { @IBOutlet weak var icon: UIImageView! @IBOutlet weak var nickName: UILabel! @IBOutlet weak var title: UILabel! @IBOutlet weak var commentCount: UIButton! @IBOutlet weak var likeCount: UIButton! @IBOutlet weak var createdTime: UILabel! @IBOutlet weak var official: UIImageView! @IBOutlet weak var flag: UIImageView! var model:BookComment? override func awakeFromNib() { super.awakeFromNib() // Initialization code } override func setSelected(_ selected: Bool, animated: Bool) { super.setSelected(selected, animated: animated) // Configure the view for the selected state } func configureCell(with model: Any?) { if model is BookComment { configureCell(model: model as! BookComment) } } func configureCell(model:BookComment){ self.model = model nickName.text = "\(self.model?.author.nickname ?? "") lv.\(self.model?.author.lv ?? 0)" title.text = "\(self.model?.title ?? "")" commentCount.setTitle("\(self.model?.commentCount ?? 0)", for: .normal) likeCount.setTitle("\(self.model?.likeCount ?? 0)", for: .normal) createdTime.qs_setCreateTime(createTime: self.model?.created ?? "", append: "") icon.qs_setAvatarWithURLString(urlString: "\(self.model?.author.avatar ?? "")") let state = self.model?.state if state == "normal" { flag.isHidden = true }else if state == "distillate" { flag.isHidden = false flag.image = UIImage(named: "f_distillate") }else if state == "hot" { flag.isHidden = false flag.image = UIImage(named: "f_hot") }else if state == "focus" { flag.isHidden = false flag.image = UIImage(named: "f_today_topic") } let type = self.model?.author.type if type == "official" { official.image = UIImage(named: "f_official_icon") official.isHidden = false }else if type == "normal" { official.isHidden = true }else if type == "doyen" { official.isHidden = false official.image = UIImage(named: "f_doyen_icon") } icon.qs_addCornerRadius(cornerRadius: 5) } func isToday()->Bool{ let year = self.model?.created.qs_subStr(to: 4) ?? "2017" let month = self.model?.created.qs_subStr(start: 5, end: 7) ?? "06" let day = self.model?.created.qs_subStr(start: 8, length: 2) ?? "18" let today = Date() if today.year() == Int(year) && today.month() == Int(month) && today.day() == Int(day) { return true } return false } } ================================================ FILE: zhuishushenqi/Root/Views/QSHelpViewCell.xib ================================================ ================================================ FILE: zhuishushenqi/Root/Views/QSHomeDeleteBtn.swift ================================================ // // QSHomeDeleteBtn.swift // zhuishushenqi // // Created by yung on 2017/4/26. // Copyright © 2017年 QS. All rights reserved. // import UIKit class QSHomeDeleteBtn: UIButton { override func layoutSubviews() { super.layoutSubviews() // self.imageView?.frame = CGRect(x: self.bounds.width/2 - 30/2, y: 0, width: 30, height: 30) // self.imageView?.contentMode = .scaleAspectFit // self.titleLabel?.frame = CGRect(x: 0, y: 30, width: self.bounds.width, height: 20) // self.titleLabel?.textAlignment = .center // self.titleLabel?.font = UIFont.systemFont(ofSize: 9) } } ================================================ FILE: zhuishushenqi/Root/Views/QSLaunchRecView.swift ================================================ // // QSLaunchRecView.swift // zhuishushenqi // // Created by yung on 2017/6/12. // Copyright © 2017年 QS. All rights reserved. // import UIKit typealias QSLaunchRecViewCallback = (_ btn:UIButton)->Void class ZSRecommend: NSObject { var window:UIWindow! var recView:QSLaunchRecView! override init() { window = UIWindow(frame: UIScreen.main.bounds) window.backgroundColor = UIColor.clear } func show(boyTipCallback:QSLaunchRecViewCallback?, girlTipCallback:QSLaunchRecViewCallback?, closeCallback:QSLaunchRecViewCallback?) { let nib = UINib(nibName: "QSLaunchRecView", bundle: nil) recView = nib.instantiate(withOwner: nil, options: nil).first as? QSLaunchRecView recView.frame = window.bounds recView.alpha = 0.0 recView.closeCallback = { (btn) in self.hide() closeCallback?(btn) } recView.boyTipCallback = { (btn) in self.hide() boyTipCallback?(btn) } recView?.girlTipCallback = { (btn) in self.hide() girlTipCallback?(btn) } window?.addSubview(recView) window.isHidden = false UIView.animate(withDuration: 0.35, animations: { self.recView.alpha = 1.0 }) { (finished) in } } private func hide() { recView.removeFromSuperview() window.isHidden = true } } class QSLaunchRecView: UIView { var closeCallback:QSLaunchRecViewCallback? var boyTipCallback:QSLaunchRecViewCallback? var girlTipCallback:QSLaunchRecViewCallback? @IBOutlet weak var headerView: UIView! @IBOutlet weak var headerIcon: UIImageView! @IBOutlet weak var tipTitle: UILabel! @IBOutlet weak var tipDetail: UILabel! @IBOutlet weak var tipIcon: UIImageView! @IBOutlet weak var close: UIButton! @IBOutlet weak var parentView: UIView! @IBOutlet weak var boyBtn: UIButton! @IBOutlet weak var girlBtn: UIButton! @IBAction func closeAction(_ sender: Any) { if let close = closeCallback { close(sender as! UIButton) } } @IBAction func boyTip(_ sender: Any) { let btn = sender as! UIButton btn.isSelected = !btn.isSelected if btn.isSelected { girlBtn.isSelected = false } tipIcon.image = UIImage(named: "g_boy_tip") headerIcon.image = UIImage(named: "g_boy_avatar") headerView.backgroundColor = UIColor(red: 0.31, green: 0.75, blue: 1.0, alpha: 1.0) tipTitle.isHidden = true tipDetail.isHidden = true if let close = boyTipCallback { close(btn) } } @IBAction func girlTip(_ sender: Any) { let btn = sender as! UIButton btn.isSelected = !btn.isSelected if btn.isSelected { boyBtn.isSelected = false } tipIcon.image = UIImage(named: "g_girl_tip") headerIcon.image = UIImage(named: "g_girl_avatar") headerView.backgroundColor = UIColor(red: 1.0, green: 0.30, blue: 0.24, alpha: 1.0) tipTitle.isHidden = true tipDetail.isHidden = true if let close = girlTipCallback { close(btn) } } func show() { } override func awakeFromNib() { super.awakeFromNib() parentView.layer.cornerRadius = 5 parentView.layer.masksToBounds = true } } ================================================ FILE: zhuishushenqi/Root/Views/QSLaunchRecView.xib ================================================ ================================================ FILE: zhuishushenqi/Root/Views/QSSegmentDropView.swift ================================================ // // QSSegmentDropView.swift // zhuishushenqi // // Created by yung on 2017/6/18. // Copyright © 2017年 QS. All rights reserved. // import UIKit protocol QSSegmentDropViewDelegate { func didSelectAtIndexs(_ indexs:[Int]) } /* [ [ "全部", "精品" ] ] */ protocol ZSMultiSelectionDelegate { func numberOfSections(in multiSelectionView: ZSMultiSelectionView) ->Int func multiSelectionView(_ multiSelectionView: ZSMultiSelectionView, numberOfRowsInSection section: Int) -> Int func multiSelectionView(_ multiSelectionView: ZSMultiSelectionView, titleForRowAt indexPath:IndexPath) -> String func multiSelectionView(_ multiSelectionView: ZSMultiSelectionView, titleForHeaderIn section:Int) -> String func multiSelectionView(_ multiSelectionView: ZSMultiSelectionView, didSelectAt indexPath:IndexPath) } class ZSMultiSelectionView: UIView { /// 下拉列表点击事件代理 var delegate:ZSMultiSelectionDelegate? { didSet { setupSubviews() } } /// 下拉列表 // private var selectionList:[[String]] = [] /// 当前选中的项 fileprivate var selectedSelectionIndexs:[Int] = [] /// 当前选中的section,非激活状态为上一次的选中 fileprivate var selectedSectionIndex:Int = 0 /// 父视图 private var parentView:UIView? /// 下拉列表section的基准tag private let selectionSectionBaseTag = 1234 /// 下拉列表section的分割线的基准tag private let selectionSectionSeparatorBaseTag = 1314 /// 背景视图 private var backgroundView:UIView! /// 下拉列表 private var tableView:UITableView! /// 底部分割线 private var horizonalSeparatorView:UIView! /// section之间的分割线 private var verticalSeparatorViews:[UIView] = [] /// 下拉列表的高度 private let selectionItemHeight:CGFloat = 44 /// 下拉列表的激活状态 private var isSelectionActive:Bool = false override init(frame: CGRect) { super.init(frame: frame) } private func setupSubviews() { backgroundColor = UIColor.white backgroundView = UIView(frame: CGRect.zero) backgroundView.backgroundColor = UIColor(red: 0.0, green: 0.0, blue: 0.0, alpha: 0.4) backgroundView.isUserInteractionEnabled = true backgroundView.isHidden = true let tap = UITapGestureRecognizer { (tap) in self.updateHeaderState(with: self.backgroundView) self.hideSelection() } backgroundView.addGestureRecognizer(tap) horizonalSeparatorView = UIView(frame: CGRect(x: 0, y: self.bounds.height - 0.5, width: self.bounds.width, height: 0.5)) horizonalSeparatorView.backgroundColor = UIColor(red: 0.0, green: 0.0, blue: 0.0, alpha: 0.3) addSubview(horizonalSeparatorView) if let sections = delegate?.numberOfSections(in: self) { for index in 0.. (screenHeight - self.frame.maxY) { tableViewHeight = screenHeight - self.frame.maxY } self.tableView.frame = CGRect(x: 0, y: self.frame.maxY, width: self.bounds.width, height: tableViewHeight) self.backgroundView.isHidden = false } UIView.animate(withDuration: 0.25, animations: animation, completion: nil) } func hideSelection() { let animation = { self.tableView.frame = CGRect(x: 0, y: self.frame.maxY, width: self.bounds.width, height: 0) self.backgroundView.isHidden = true } UIView.animate(withDuration: 0.25, animations: animation, completion: nil) } override func layoutSubviews() { super.layoutSubviews() backgroundView.frame = CGRect(x: 0, y: self.frame.maxY, width: self.bounds.width, height: ScreenHeight - self.frame.maxY) horizonalSeparatorView.frame = CGRect(x: 0, y: self.bounds.height - 0.5, width: self.bounds.width, height: 0.5) if isSelectionActive { let screenHeight = UIScreen.main.bounds.height let rows = self.delegate?.multiSelectionView(self, numberOfRowsInSection: self.selectedSectionIndex) ?? 0 var tableViewHeight = CGFloat(rows)*self.selectionItemHeight if tableViewHeight > (screenHeight - self.frame.maxY) { tableViewHeight = screenHeight - self.frame.maxY } self.tableView.frame = CGRect(x: 0, y: self.frame.maxY, width: self.bounds.width, height: tableViewHeight) } else { self.tableView.frame = CGRect(x: 0, y: self.frame.maxY, width: self.bounds.width, height: 0) } guard let sections = delegate?.numberOfSections(in: self) else { return } for index in 0.. Int { let rows = delegate?.multiSelectionView(self, numberOfRowsInSection: selectedSectionIndex) ?? 0 return rows } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.qs_dequeueReusableCell(UITableViewCell.self) cell?.contentView.backgroundColor = UIColor(red: 0.93, green: 0.93, blue: 0.93, alpha: 1.0) cell?.backgroundColor = UIColor(red: 0.93, green: 0.93, blue: 0.93, alpha: 1.0) cell?.selectionStyle = .none let truelyIndexPath = IndexPath(row: indexPath.row, section: self.selectedSectionIndex) cell?.textLabel?.text = delegate?.multiSelectionView(self, titleForRowAt: truelyIndexPath) let selectedRowIndex = selectedSelectionIndexs[selectedSectionIndex] cell?.textLabel?.textColor = indexPath.row == selectedRowIndex ? UIColor.red: UIColor.gray cell?.textLabel?.font = UIFont.systemFont(ofSize: 13) return cell! } func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { return selectionItemHeight } func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { return 0.01 } func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { return 0.01 } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { isSelectionActive = false selectedSelectionIndexs[selectedSectionIndex] = indexPath.row updateHeaderState(with: tableView) hideSelection() let truelyIndexPath = IndexPath(row: indexPath.row, section: selectedSectionIndex) delegate?.multiSelectionView(self, didSelectAt: truelyIndexPath) } } class QSSegmentDropView: UIView { var menuDelegate:QSSegmentDropViewDelegate? private var titles:[[String]] = [[],[]] var selectedSegment:Int = 0 private var parentView:UIView? private var selectIndexs:[Int] = [] fileprivate let btnTag = 1234 private var bottomLine:UILabel! private var verticalLines:[UILabel] = [] private let rowHight:CGFloat = 44 /// 记录下拉列表的激活状态 private var isSelectionActive:Bool = false private lazy var bgView:UIView = { let bgView = UIView(frame: CGRect(x: 0, y: kNavgationBarHeight + 40, width: ScreenWidth, height: ScreenHeight - kNavgationBarHeight - 40)) bgView.backgroundColor = UIColor(red: 0.0, green: 0.0, blue: 0.0, alpha: 0.4) bgView.isUserInteractionEnabled = true return bgView }() private lazy var tableView:UITableView = { let tableView = UITableView(frame: CGRect(x: 0, y: kNavgationBarHeight + 40, width: ScreenWidth, height: 0), style: .grouped) tableView.dataSource = self tableView.delegate = self tableView.rowHeight = rowHight tableView.backgroundColor = UIColor(red: 0.93, green: 0.93, blue: 0.93, alpha: 1.0) tableView.qs_registerCellClass(UITableViewCell.self) tableView.bounces = false return tableView }() init(frame:CGRect, WithTitles _titles:[[String]],parentView:UIView){ super.init(frame: frame) self.parentView = parentView titles = _titles setupSubviews(frame,titles: _titles) if #available(iOS 11, *) { self.tableView.contentInsetAdjustmentBehavior = .never } } fileprivate func setupSubviews(_ frame:CGRect,titles:[[String]]){ var index:Int = 0 let width = self.bounds.width/CGFloat(titles.count) let height = self.bounds.height for item in 0.. 0 ? tmpTitles[0]:"" let btn = ZSSegmentSelectionButton(type: .custom) btn.setTitle(title, for: UIControl.State()) btn.tag = index + btnTag btn.frame = CGRect(x: width*CGFloat(index), y: 0, width: width, height: height) let labelWidth = title.qs_width(UIFont.systemFont(ofSize: 13), height: 16) btn.imageEdgeInsets = UIEdgeInsets(top: 0, left: labelWidth, bottom: 0, right: -labelWidth) btn.addTarget(self, action: #selector(self.segmentBtnClick(_:)), for: .touchUpInside) addSubview(btn) if index > 0 && index <= titles.count - 1 { let line = UILabel(frame: CGRect(x: width*CGFloat(index),y: height/3,width: 0.5,height: height/3)) line.backgroundColor = UIColor.gray line.alpha = 0.6 verticalLines.append(line) addSubview(line) } index += 1 } bottomLine = UILabel(frame: CGRect(x: 0,y: height - 0.5,width: self.bounds.width,height: 0.5)) bottomLine.backgroundColor = UIColor(red: 0.0, green: 0.0, blue: 0.0, alpha: 0.3) addSubview(bottomLine) backgroundColor = UIColor.white let tap = UITapGestureRecognizer(target: self, action: #selector(dismissDrop(sender:))) bgView.addGestureRecognizer(tap) // initializer,default 0 for index in 0.. 0 { selectIndexs.append(0) } } } @objc fileprivate func segmentBtnClick(_ btn:UIButton){ let tag = btn.tag - btnTag selectedSegment = tag btnSelect(btn) if btn.isSelected == false { hideSelection() }else{ showSelection() tableView.reloadData() } } private func showSelection() { if self.bgView.superview == nil { self.parentView?.addSubview(self.bgView) } if self.tableView.superview == nil { parentView?.addSubview(self.tableView) } self.parentView?.bringSubviewToFront(self.tableView) self.bgView.isHidden = false UIView.animate(withDuration: 0.3) { var tableHeight:CGFloat = CGFloat(self.rowHight*CGFloat(self.titles[self.selectedSegment].count)) if tableHeight > ((self.parentView?.bounds.height ?? 0) - self.frame.maxY) { tableHeight = (self.parentView?.bounds.height ?? 0) - self.frame.maxY } self.tableView.frame = CGRect(x: 0, y: kNavgationBarHeight + 40, width: self.bounds.width, height: tableHeight) } refreshUI() } private func hideSelection() { let btn:UIButton? = viewWithTag(selectedSegment + btnTag) as? UIButton btn?.isSelected = false UIView.animate(withDuration: 0.3, animations: { self.bgView.isHidden = true self.tableView.frame = CGRect(x: 0, y: Int(kNavgationBarHeight + 40), width: Int(self.bounds.width), height: 0) }) { (finish) in self.bgView.removeFromSuperview() } } private func btnSelect(_ btn:UIButton){ btn.isSelected = !btn.isSelected for index in 0.. 0 { width = width/CGFloat(titles.count) } let height = self.bounds.height for index in 0.. ((parentView?.bounds.height ?? 0) - self.frame.maxY) { tableHeight = (parentView?.bounds.height ?? 0) - self.frame.maxY } else if tableHeight == 0 { } else { tableHeight = CGFloat(self.rowHight*CGFloat(self.titles[self.selectedSegment].count)) } self.tableView.frame = CGRect(x: 0, y: kNavgationBarHeight + 40, width: self.bounds.width, height: tableHeight) } @objc private func dismissDrop(sender:Any){ hideSelection() } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func layoutSubviews() { super.layoutSubviews() refreshUI() } } extension QSSegmentDropView:UITableViewDataSource,UITableViewDelegate{ func numberOfSections(in tableView: UITableView) -> Int { return 1 } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { if selectedSegment < titles.count{ let subs:[String] = titles[selectedSegment] return subs.count } return 0 } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.qs_dequeueReusableCell(UITableViewCell.self) cell?.textLabel?.text = titles[selectedSegment][indexPath.row] cell?.contentView.backgroundColor = UIColor(red: 0.93, green: 0.93, blue: 0.93, alpha: 1.0) cell?.backgroundColor = UIColor(red: 0.93, green: 0.93, blue: 0.93, alpha: 1.0) cell?.selectionStyle = .none cell?.textLabel?.textColor = indexPath.row == selectIndexs[selectedSegment] ? UIColor.red: UIColor.gray cell?.textLabel?.font = UIFont.systemFont(ofSize: 13) return cell! } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { selectIndexs[selectedSegment] = indexPath.row let btn:UIButton? = viewWithTag(selectedSegment + btnTag) as? UIButton btn?.setTitle(titles[selectedSegment][selectIndexs[selectedSegment]], for: .normal) tableView.reloadData() dismissDrop(sender: tableView) menuDelegate?.didSelectAtIndexs(selectIndexs) } func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { return UIView() } func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? { return UIView() } func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { return 0.001 } func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { return 0.001 } } class ZSSegmentSelectionButton:UIButton { override func layoutSubviews() { super.layoutSubviews() self.setTitleColor(UIColor.gray, for: UIControl.State()) self.setImage(UIImage(named:"nav_arrow_down"), for: .normal) self.setImage(UIImage(named:"nav_arrow_up"), for: .selected) self.titleEdgeInsets = UIEdgeInsets(top: 0, left: -13, bottom: 0, right: 13) self.contentHorizontalAlignment = .center self.titleLabel?.font = UIFont.systemFont(ofSize: 13) self.backgroundColor = UIColor.white } } ================================================ FILE: zhuishushenqi/Root/Views/RightTableViewCell.swift ================================================ // // RightTableViewCell.swift // zhuishushenqi // // Created by Nory Cao on 16/9/18. // Copyright © 2016年 QS. All rights reserved. // import UIKit class RightTableViewCell: UITableViewCell { override func awakeFromNib() { super.awakeFromNib() // Initialization code } override func setSelected(_ selected: Bool, animated: Bool) { super.setSelected(selected, animated: animated) // Configure the view for the selected state } override func layoutSubviews() { super.layoutSubviews() imageView?.frame = CGRect(x: 10, y: 15, width: 30, height: 30) textLabel?.frame = CGRect(x: imageView!.frame.maxX + 15, y: 0, width: 150, height: 60) textLabel?.textColor = UIColor.white textLabel?.font = UIFont.systemFont(ofSize: 14) } } ================================================ FILE: zhuishushenqi/Root/Views/RootNavigationView.swift ================================================ // // RootNavigationView.swift // zhuishushenqi // // Created by Nory Cao on 2017/3/16. // Copyright © 2017年 QS. All rights reserved. // import UIKit class RootNavigationView: UIView { override init(frame: CGRect) { super.init(frame: frame) } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } static func make(delegate:UIViewController,leftAction:Selector,rightAction:Selector){ let leftBtn = BarButton(type: .custom) leftBtn.addTarget(delegate, action: leftAction, for: .touchUpInside) leftBtn.setBackgroundImage(UIImage(named: "nav_home_side_menu"), for: UIControl.State()) leftBtn.setBackgroundImage(UIImage(named: "nav_home_side_menu_selected"), for: .highlighted) leftBtn.frame = CGRect(x: 0, y: 0, width: 40, height: 40) let leftBar = UIBarButtonItem(customView: leftBtn) let rightBtn = BarButton(type: .custom) rightBtn.addTarget(delegate, action: rightAction, for: .touchUpInside) rightBtn.setBackgroundImage(UIImage(named: "nav_add_book"), for: UIControl.State()) rightBtn.setBackgroundImage(UIImage(named: "nav_add_book_selected"), for: .highlighted) rightBtn.frame = CGRect(x: 0, y: 0, width: 40, height: 40) let rightBar = UIBarButtonItem(customView: rightBtn) delegate.navigationItem.leftBarButtonItem = leftBar delegate.navigationItem.rightBarButtonItem = rightBar let titleImg = UIImageView(image: UIImage(named: "zssq_image")) delegate.navigationItem.titleView = titleImg let base64String = UIImage(named:"nav_back_red")?.base64() QSLog(base64String) } } ================================================ FILE: zhuishushenqi/Root/Views/SegMenu.swift ================================================ // // SegMenu.swift // zhuishushenqi // // Created by Nory Cao on 16/9/17. // Copyright © 2016年 QS. All rights reserved. // import UIKit import RxCocoa import RxSwift protocol SegMenuDelegate:class { func didSelectAtIndex(_ index:Int) } class SegMenu: UIView { weak var menuDelegate:SegMenuDelegate? var titles:[String] = [] var selectedIndex = 0 private let lineTag = 1111 private let btnBaseTag = 1414 private var lastSelectedBtn:UIButton! private let disposeBag = DisposeBag() private lazy var bottomLine : UILabel = { let lb = UILabel() lb.frame = CGRect.zero lb.backgroundColor = UIColor.gray return lb }() init(frame:CGRect, WithTitles _titles:[String]){ super.init(frame: frame) titles = _titles initSubview(frame,titles: _titles) } fileprivate func layoutAllSubview() { let width = self.bounds.width/CGFloat(titles.count) let height = self.bounds.height for index in 0.. 0 && index <= titles.count - 1 { let line = self.viewWithTag(lineTag + index) line?.snp.remakeConstraints({ (make) in make.left.equalTo(width*CGFloat(index)) make.top.equalTo(height/3) make.width.equalTo(0.5) make.height.equalTo(height/3) }) } } bottomLine.snp.makeConstraints({ (make) in make.left.right.equalTo(self) make.top.equalTo(height - 0.5) make.height.equalTo(0.5) }) } fileprivate func initSubview(_ frame:CGRect,titles:[String]){ var index:Int = 0 for title in titles { let btn = create(title,index + btnBaseTag) btn.rx.tap.subscribe(onNext: { [weak self] in self?.segAction(btn) }).disposed(by: disposeBag) addSubview(btn) if index > 0 && index <= titles.count - 1 { let line = create(lineTag + index) addSubview(line) } if (index == 0) { lastSelectedBtn = btn lastSelectedBtn.isSelected = true lastSelectedBtn.isUserInteractionEnabled = !(lastSelectedBtn.isSelected) } index += 1 } addSubview(bottomLine) backgroundColor = UIColor.white } @objc fileprivate func segAction(_ btn:UIButton){ lastSelectedBtn.isSelected = false lastSelectedBtn.isUserInteractionEnabled = !(lastSelectedBtn.isSelected) btn.isSelected = !btn.isSelected btn.isUserInteractionEnabled = false lastSelectedBtn = btn selectedIndex = btn.tag - btnBaseTag menuDelegate?.didSelectAtIndex(selectedIndex) } func selectIndex(_ index:Int) { for i in 0..UIButton{ let btn = UIButton(type: .custom) btn.setTitle(title, for: .normal) btn.setTitleColor(UIColor.gray, for: .normal) btn.setBackgroundImage(UIImage(named: "new_nav_normal"), for: .normal) btn.setBackgroundImage(UIImage(named: "new_nav_selected"), for: .selected) btn.adjustsImageWhenHighlighted = false btn.frame = CGRect.zero btn.tag = tag btn.titleLabel?.font = UIFont.systemFont(ofSize: 13) return btn } private func create(_ tag:Int) -> UILabel { let line = UILabel(frame: CGRect.zero) line.tag = tag line.backgroundColor = UIColor.gray line.alpha = 0.6 return line } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func layoutSubviews() { super.layoutSubviews() self.layoutAllSubview() } } ================================================ FILE: zhuishushenqi/Root/Views/SwipableCell.swift ================================================ // // SwipableCell.swift // zhuishushenqi // // Created by Nory Cao on 16/9/18. // Copyright © 2016年 QS. All rights reserved. // import UIKit protocol SwipableCellDelegate { func swipableCell(swipableCell:SwipableCell, didSelectAt index:Int) } enum TranslationDirection { case left case right } enum SwipeCellState:String { case none = "none" case prepare = "prepare" case download = "download" case finish = "finish" } extension SwipeCellState { var icon:String { switch self { case .none: return "bs_download" case .prepare: return "bs_download_cancel" case .download: return "bs_download_cancel" case .finish: return "bs_download" } } var cornerIcon:String { switch self { case .none: return "" case .prepare: return "bs_download_waiting" case .download: return "bs_downloading" case .finish: return "bs_download_finished" } } } class ZSSwipeView: UIView { // container分为三部分,1.左边的view,中间的view,右边的view private var container:UIView! private var leftView:UIView! var centerView:UIView! private var rightView:UIView! private var touchOverlay:UIView! private var curOffset:CGFloat = 0 private var rightOffSet:CGFloat = 0 private var panGesture:UIPanGestureRecognizer! // 右侧的项,需要定义好后传入,根据传入的view的frame布局 var rightItems:[UIView] = [] { didSet { setupRightView() } } // 是否可以滑动显示,如果为否,需调用show方法自行展示右侧视图 var isSwipeEnable:Bool = true override init(frame: CGRect) { super.init(frame: frame) setupSubviews() } private func setupSubviews() { // 2.使用UIView作为容器,手势滑动时改变frame,即当前视图x左移即可 container = UIView(frame: CGRect(x:0,y:0,width:self.bounds.width,height:self.bounds.height)) container.backgroundColor = UIColor ( red: 1.0, green: 1.0, blue: 1.0, alpha: 0.7 ) container.isUserInteractionEnabled = true centerView = UIView() centerView.frame = self.bounds touchOverlay = UIView() touchOverlay.frame = self.centerView.bounds; let tap = UITapGestureRecognizer(target: self, action: #selector(tapGestureAction(tap:))) touchOverlay.addGestureRecognizer(tap) panGesture = UIPanGestureRecognizer(target: self, action: #selector(panGestureAction(pan:))) panGesture.delegate = self container.addGestureRecognizer(panGesture) rightView = UIView() rightView.frame = self.bounds addSubview(container) container.addSubview(rightView) container.addSubview(centerView) } private func setupRightView() { var itemWidth:CGFloat = 0 var index = 0 for itemView in rightItems { itemView.frame = CGRect(x: itemWidth, y: 0, width: itemView.bounds.height, height: itemView.bounds.height) itemWidth += itemView.bounds.width if itemView.superview == nil { rightView.addSubview(itemView) } index += 1 } layoutUI() } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } func showRightView(animated:Bool) { if rightView.bounds.width > 0 { rightOffSet = rightView.bounds.width curOffset = -rightOffSet self.translation(direction: .left, offset: curOffset, animation: true) } } private func translation(direction:TranslationDirection,offset:CGFloat,animation:Bool){ var animationDuration = 0.0001 if animation { animationDuration = 0.35 } if direction == .right && offset <= 0 { curOffset = 0 //关闭右边显示 UIView.animate(withDuration: animationDuration, animations: { self.centerView.frame = CGRect(x: 0, y: 0, width: self.bounds.width, height: self.bounds.height) self.rightView.frame = CGRect(x: self.bounds.width, y: 0, width: CGFloat(self.rightItems.count) * self.bounds.height, height: self.bounds.height) }, completion: { (finished) in self.touchOverlay.removeFromSuperview() }) }else if direction == .left && offset < 0 { UIView.animate(withDuration: animationDuration, animations: { self.centerView.frame = CGRect(x: offset, y: 0, width: self.bounds.width, height: self.bounds.height) self.rightView.frame = CGRect(x: self.bounds.width + offset, y: 0, width: CGFloat(self.rightItems.count) * self.bounds.height, height: self.bounds.height) }, completion: { (finished) in self.centerView.addSubview(self.touchOverlay) }) } } @objc private func tapGestureAction(tap:UITapGestureRecognizer) { reset() animate(for: container, duration: 1) } func reset() { self.curOffset = 0 translation(direction: .right, offset: self.curOffset, animation: true) } func animate(for content:UIView, duration:Double) { let keyframe = CAKeyframeAnimation(keyPath: "transform.translation.x") let currentTX = content.transform.tx keyframe.duration = duration keyframe.repeatCount = 1 keyframe.values = [currentTX,currentTX + 10,currentTX - 8,currentTX + 8, currentTX - 5, currentTX + 5, currentTX] keyframe.keyTimes = [0,0.225,0.425,0.6,0.75,0.885,1] keyframe.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeOut) content.layer.add(keyframe, forKey: "contentAnimatuinKey") } @objc private func panGestureAction(pan:UIPanGestureRecognizer) { if !isSwipeEnable { return } let translation = pan.translation(in: container) let velocity = pan.velocity(in: container) var offset:CGFloat = 0 if pan.state == .began { if curOffset == 0 { curOffset = translation.x } rightOffSet = rightView.bounds.width offset = curOffset }else if pan.state == .changed { //当前向左偏移,显示右边视图 if offset <= 0 { offset = curOffset + translation.x } self.translation(direction: .left, offset: offset, animation: false) }else if pan.state == .ended { curOffset = offset if curOffset <= 0 { if velocity.x > 0 { curOffset = 0 self.translation(direction: .right, offset: curOffset, animation: true) }else{ curOffset = -rightOffSet self.translation(direction: .left, offset: curOffset, animation: true) } } } } func prepareForReuse() { translation(direction: .right, offset: 0, animation: false) } private func layoutUI() { self.container.frame = self.bounds self.centerView.frame = CGRect(x: curOffset, y: 0, width: self.bounds.width, height: self.bounds.height) self.touchOverlay.frame = CGRect(x: 0, y: 0, width: self.bounds.width - curOffset, height: self.bounds.height) if self.rightItems.count > 0 { self.rightView.frame = CGRect(x: self.centerView.frame.maxX + curOffset, y: 0, width: CGFloat(self.rightItems.count) * self.bounds.height, height: self.bounds.height) } } override func layoutSubviews() { super.layoutSubviews() setupRightView() } } extension ZSSwipeView:UIGestureRecognizerDelegate { override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { if gestureRecognizer != panGesture { return true } let translation = panGesture.translation(in: container) if abs(translation.y) > abs(translation.x) { return false } return true } } class SwipableCell: UITableViewCell { class var reuseIdentifier: String { get { return "SwipableCell" } } class var nib:UINib { get { return UINib(nibName: "SwipableCell", bundle: Bundle.main) } } var swipeView:ZSSwipeView! var delegate:SwipableCellDelegate? private var imgView: UIImageView? private var stateImage: UIImageView! var title:UILabel? private var detailTitle:UILabel? private var updatedLabel:UILabel! private let btnTag = 12324 /// 缓存状态 ,初始为none var state:SwipeCellState = .none { didSet{ stateChange() } } override func awakeFromNib() { super.awakeFromNib() // Initialization code } override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) initSubview() } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } fileprivate func initSubview(){ swipeView = ZSSwipeView(frame: self.bounds) contentView.addSubview(swipeView) let imgView = UIImageView(frame: CGRect(x: 15, y: 10, width: 34, height: 44)) imgView.image = UIImage(named: "default_book_cover") self.imgView = imgView stateImage = UIImageView(frame: CGRect(x: 0, y: 0, width: 34, height: 44)) self.imgView?.addSubview(stateImage) let label = UILabel(frame: CGRect(x: imgView.frame.maxX + 10,y: 10,width: ScreenWidth - imgView.frame.maxX - 20,height: 20)) label.font = UIFont.systemFont(ofSize: 13) self.title = label let detaillabel = UILabel(frame: CGRect(x: imgView.frame.maxX + 10,y: 30,width: ScreenWidth - imgView.frame.maxX - 20,height: 20)) detaillabel.font = UIFont.systemFont(ofSize: 11) detaillabel.textColor = UIColor.gray self.detailTitle = detaillabel updatedLabel = UILabel(frame: CGRect(x: 0, y: 15, width: 40, height: 10)) updatedLabel.backgroundColor = UIColor.red updatedLabel.font = UIFont.systemFont(ofSize: 9) updatedLabel.isHidden = true updatedLabel.text = "更新" updatedLabel.textAlignment = .center updatedLabel.textColor = UIColor.white swipeView.centerView.addSubview(updatedLabel) swipeView.centerView.addSubview(self.imgView!) swipeView.centerView.addSubview(self.title!) swipeView.centerView.addSubview(self.detailTitle!) let titles = ["缓存","养肥","置顶","删除"] let images = ["bs_download","bs_feed","bs_stick","bs_delete"] var selectedImages = ["bs_download_cancel",images[1],images[2],images[3]] var selectedTitles = ["取消缓存",titles[1],titles[2],titles[3]] var buttons:[UIButton] = [] for index in 0..Void class ZSLoginVerifyView: UIView, UIWebViewDelegate { var webView:UIWebView! var backgroundView:UIView! var resultHandler:ZSLoginVerifyViewResultHandler? override init(frame: CGRect) { super.init(frame: frame) setupSubviews() } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } private func setupSubviews() { webView = UIWebView(frame: CGRect(x: 0, y: 0, width: self.bounds.width, height: 300)) webView.delegate = self webView.center = self.center let userAgent = self.webView.stringByEvaluatingJavaScript(from: "navigator.userAgent") ?? "" let m_UserAgent = "\(userAgent) TCSDK/1.0.2" UserDefaults.standard.register(defaults: ["UserAgent":m_UserAgent]) UserDefaults.standard.synchronize() backgroundView = UIView(frame: CGRect(x: 0, y: 0, width: self.bounds.size.width, height: self.bounds.height)) backgroundView.backgroundColor = UIColor(white: 0.3, alpha: 0.6) backgroundView.isUserInteractionEnabled = true addSubview(backgroundView) addSubview(webView) self.isUserInteractionEnabled = true } func startVerify(str:String) { webView.loadHTMLString(str, baseURL: nil) } func webViewDidStartLoad(_ webView: UIWebView) { } func webView(_ webView: UIWebView, didFailLoadWithError error: Error) { } func webViewDidFinishLoad(_ webView: UIWebView) { } func webView(_ webView: UIWebView, shouldStartLoadWith request: URLRequest, navigationType: UIWebView.NavigationType) -> Bool { let urlString = request.url?.absoluteString ?? "" if urlString.hasPrefix("tcwebscheme://callback") { let location = (urlString as NSString).range(of: "?retJson=").location if location != NSNotFound { let jsonString = urlString.substingInRange((location+9)..<((urlString as NSString).length)) let noPercentJson = jsonString?.removingPercentEncoding ?? "" if let data = noPercentJson.data(using: .utf8) { if let obj = try? JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String:Any] { var requestParam:[String:String] = [:] let ret = obj["ret"] as? Int ?? 1 requestParam["mobile"] = ZSMobileLogin.share.mobile requestParam["type"] = "login" requestParam["captchaType"] = "tencent" requestParam["Ticket"] = obj["ticket"] as? String ?? "" requestParam["Randstr"] = obj["randstr"] as? String ?? "" resultHandler?(ret, requestParam) } } } } return true } } ================================================ FILE: zhuishushenqi/Root/Views/ZSLoginView.swift ================================================ // // ZSLoginView.swift // zhuishushenqi // // Created by caony on 2018/10/22. // Copyright © 2018 QS. All rights reserved. // import UIKit typealias ZSLoginHandler = ()->Void class ZSLoginView: UIView { private var phoneNumTextField:UITextField! private var verifyNumTextField:UITextField! private var loginButon:UIButton! private var genCaptchaButton:UIButton! var loginHandler:ZSLoginHandler? var getSMSCodeHandler:ZSLoginHandler? private var timer:Timer! private var secondsCount = 60 override init(frame: CGRect) { super.init(frame: frame) setupSubviews() timer = Timer(timeInterval: 1, target: self, selector: #selector(timerAction), userInfo: nil, repeats: true) } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } func setupSubviews() { phoneNumTextField = ZSLoginTextField(frame: CGRect(x: 40, y: 20, width: self.bounds.width - 80, height: 40)) phoneNumTextField.placeholder = "请输入手机号" phoneNumTextField.textColor = UIColor.black phoneNumTextField.font = UIFont.systemFont(ofSize: 13) phoneNumTextField.layer.cornerRadius = 20 phoneNumTextField.backgroundColor = UIColor(white: 0.8, alpha: 0.4) phoneNumTextField.layer.masksToBounds = true phoneNumTextField.keyboardType = .numberPad addSubview(phoneNumTextField) verifyNumTextField = ZSLoginTextField(frame: CGRect(x: 40, y: self.phoneNumTextField.frame.maxY + 20, width: self.bounds.width - 80, height: 40)) verifyNumTextField.placeholder = "请输入验证码" verifyNumTextField.textColor = UIColor.black verifyNumTextField.font = UIFont.systemFont(ofSize: 13) verifyNumTextField.layer.cornerRadius = 20 verifyNumTextField.backgroundColor = UIColor(white: 0.8, alpha: 0.4) verifyNumTextField.layer.masksToBounds = true verifyNumTextField.keyboardType = .numberPad addSubview(verifyNumTextField) loginButon = UIButton(type: .custom) loginButon.frame = CGRect(x: 40, y: verifyNumTextField.frame.maxY + 30, width: self.bounds.width - 80, height: 40) loginButon.layer.cornerRadius = 20 loginButon.layer.masksToBounds = true loginButon.backgroundColor = UIColor(red: 1.0, green: 0.0, blue: 0.0, alpha: 0.7) loginButon.setTitle("手机快捷登录", for: .normal) loginButon.setTitleColor(UIColor.white, for: .normal) loginButon.addTarget(self, action: #selector(loginAction(btn:)), for: .touchUpInside) addSubview(loginButon) genCaptchaButton = UIButton(type: .custom) genCaptchaButton.setTitle("获取验证码", for: .normal) genCaptchaButton.setTitleColor(UIColor(red: 1.0, green: 0.0, blue: 0.0, alpha: 0.7), for: .normal) genCaptchaButton.titleLabel?.font = UIFont.systemFont(ofSize: 11) genCaptchaButton.frame = CGRect(x: 0, y: 0, width: 100, height: 30) genCaptchaButton.addTarget(self, action: #selector(getSMSCode(btn:)), for: .touchUpInside) verifyNumTextField.rightViewMode = .always verifyNumTextField.rightView = genCaptchaButton } override func layoutSubviews() { super.layoutSubviews() } @objc private func getSMSCode(btn:UIButton) { phoneNumTextField.resignFirstResponder() verifyNumTextField.resignFirstResponder() getSMSCodeHandler?() } @objc private func loginAction(btn:UIButton) { loginHandler?() } @objc func timerAction() { if secondsCount <= 0 { timer.invalidate() self.genCaptchaButton.isEnabled = true self.genCaptchaButton.setTitle("获取验证码", for: .normal) } else { self.genCaptchaButton.isEnabled = false let title = "\(secondsCount)秒后重新获取" let width = title.qs_width(UIFont.systemFont(ofSize: 11), height: 30) self.genCaptchaButton.setTitle(title, for: .normal) self.genCaptchaButton.width = width } self.genCaptchaButton.sizeToFit() secondsCount -= 1 } func fire() { RunLoop.current.add(timer, forMode: .common) } func destroy() { timer.invalidate() timer = nil } func getSecondsLeft() ->Int { return self.secondsCount } func phoneNumber() ->String { return phoneNumTextField.text ?? "" } func smsCodeText() ->String { return verifyNumTextField.text ?? "" } } class ZSLoginTextField: UITextField { override func textRect(forBounds bounds: CGRect) -> CGRect { return CGRect(x: bounds.origin.x + 20, y: bounds.origin.y, width: bounds.size.width - 40, height: bounds.size.height) } override func placeholderRect(forBounds bounds: CGRect) -> CGRect { return CGRect(x: bounds.origin.x + 20, y: bounds.origin.y, width: bounds.size.width - 40, height: bounds.size.height) } override func editingRect(forBounds bounds: CGRect) -> CGRect { return CGRect(x: bounds.origin.x + 20, y: bounds.origin.y, width: bounds.size.width - 40, height: bounds.size.height) } } ================================================ FILE: zhuishushenqi/Root/Views/ZSMyCell.swift ================================================ // // ZSMyCell.swift // zhuishushenqi // // Created by yung on 2018/10/20. // Copyright © 2018年 QS. All rights reserved. // import UIKit class ZSRLMyCell: UITableViewCell { var customView:UIView? { didSet { layoutSubviews() } } var rightLabel:UILabel! var rightTitle:String = "" { didSet { rightLabel.text = rightTitle let width = rightTitle.qs_width(UIFont.systemFont(ofSize: 13), height: 30) rightLabel.frame = CGRect(x: self.bounds.width - width - 40, y: self.bounds.height/2 - 15 , width: width, height: 30) } } override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) rightLabel = UILabel(frame: CGRect.zero) rightLabel.font = UIFont.systemFont(ofSize: 13) rightLabel.textColor = UIColor.red rightLabel.textAlignment = .center self.accessoryType = .disclosureIndicator contentView.addSubview(self.rightLabel) } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func awakeFromNib() { super.awakeFromNib() // Initialization code } override func setSelected(_ selected: Bool, animated: Bool) { super.setSelected(selected, animated: animated) // Configure the view for the selected state } override func layoutSubviews() { super.layoutSubviews() if let customView = self.customView { rightLabel.isHidden = true customView.frame = CGRect(x: self.bounds.width - customView.bounds.width - 40, y: self.bounds.height/2 - customView.bounds.height/2, width: customView.bounds.width, height: customView.bounds.height) if let _ = customView.superview { customView.removeFromSuperview() } contentView.addSubview(customView) } else { let width = rightTitle.qs_width(UIFont.systemFont(ofSize: 13), height: 30) rightLabel.frame = CGRect(x: self.bounds.width - width - 40, y: self.bounds.height/2 - 15 , width: width, height: 30) rightLabel.isHidden = false } } } class ZSRTMyCell: UITableViewCell { lazy var rightButton:UIButton = { let button = UIButton(type: .custom) button.layer.borderColor = UIColor.red.cgColor button.layer.borderWidth = 1 button.setTitleColor(UIColor.red, for: .normal) button.titleLabel?.font = UIFont.systemFont(ofSize: 13) return button }() var rightTitle:String = "" { didSet { rightButton.setTitle(rightTitle, for: .normal) let width = rightTitle.qs_width(UIFont.systemFont(ofSize: 13), height: 30) rightButton.frame = CGRect(x: self.bounds.width - width - 40, y: self.bounds.height/2 - 15 , width: width, height: 30) } } override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) self.accessoryType = .disclosureIndicator contentView.addSubview(self.rightButton) } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func awakeFromNib() { super.awakeFromNib() // Initialization code } override func setSelected(_ selected: Bool, animated: Bool) { super.setSelected(selected, animated: animated) } override func layoutSubviews() { super.layoutSubviews() let width = rightTitle.qs_width(UIFont.systemFont(ofSize: 13), height: 30) rightButton.frame = CGRect(x: self.bounds.width - width - 20 - 40, y: self.bounds.height/2 - 13 , width: width + 20, height: 26) rightButton.layer.cornerRadius = 5 } } class ZSRLTMyCell: ZSRTMyCell { private var rightLabel:UILabel! var rightLabelTitle:String = "" { didSet { rightLabel.text = self.rightLabelTitle let width = rightTitle.qs_width(UIFont.systemFont(ofSize: 13), height: 30) rightLabel.frame = CGRect(x: self.bounds.width - width - 40, y: self.bounds.height/2 - 15 , width: width, height: 30) } } override var rightTitle:String { didSet { rightButton.setTitle(rightTitle, for: .normal) let width = rightTitle.qs_width(UIFont.systemFont(ofSize: 13), height: 30) rightButton.frame = CGRect(x: self.bounds.width - width - 20, y: self.bounds.height/2 - 15 , width: width, height: 30) } } override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) rightLabel = UILabel(frame: CGRect.zero) rightLabel.font = UIFont.systemFont(ofSize: 13) rightLabel.textColor = UIColor.gray rightLabel.textAlignment = .center self.accessoryType = .none contentView.addSubview(self.rightLabel) } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func awakeFromNib() { super.awakeFromNib() // Initialization code } override func setSelected(_ selected: Bool, animated: Bool) { super.setSelected(selected, animated: animated) } override func layoutSubviews() { super.layoutSubviews() let width = rightTitle.qs_width(UIFont.systemFont(ofSize: 13), height: 30) rightButton.frame = CGRect(x: self.bounds.width - width - 20 - 20, y: self.bounds.height/2 - 13 , width: width + 20, height: 26) rightButton.layer.cornerRadius = 5 let labelWidth = rightLabelTitle.qs_width(UIFont.systemFont(ofSize: 13), height: 30) rightLabel.frame = CGRect(x: rightButton.frame.minX - labelWidth - 10, y: self.bounds.height/2 - 15 , width: labelWidth, height: 30) } } ================================================ FILE: zhuishushenqi/Root/Views/ZSMyHeaderView.swift ================================================ // // ZSMyHeaderView.swift // zhuishushenqi // // Created by yung on 2018/10/20. // Copyright © 2018年 QS. All rights reserved. // import UIKit typealias ZSMyHeaderHandler = ()->Void class ZSMyHeaderView: UITableViewHeaderFooterView, UIGestureRecognizerDelegate { var account:ZSAccount? { didSet { self.bookCoinView.valueLabel.text = "\(account?.iosBalance ?? 0)" self.bookBarginView.valueLabel.text = "\( account?.voucherBalance ?? 0)" } } var coin:ZSCoin? { didSet { self.goldCoinView.valueLabel.text = "\(coin?.info?.gold ?? 0)" self.leftCoinView.valueLabel.text = "\(coin?.info?.balance ?? "")" } } var userInfo:ZSQQLoginResponse? { didSet { self.iconView.qs_setAvatarWithURLString(urlString: userInfo?.user?.avatar ?? "") self.nicknameLabel.text = userInfo?.user?.nickname self.IDValueLabel.text = userInfo?.user?._id } } var handler:ZSMyHeaderHandler? override init(reuseIdentifier: String?) { super.init(reuseIdentifier: reuseIdentifier) self.isUserInteractionEnabled = true self.contentView.isUserInteractionEnabled = true setupSubviews() } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { return true } func setupSubviews() { self.topView.addSubview(self.iconView) self.topView.addSubview(self.nicknameLabel) self.contentView.addSubview(self.topView) self.topView.addSubview(self.topLine) self.bookCoinView.titleLabel.text = "书币" self.bookCoinView.valueLabel.text = "0" self.bookBarginView.titleLabel.text = "书券" self.bookBarginView.valueLabel.text = "62" self.goldCoinView.titleLabel.text = "金币" self.goldCoinView.valueLabel.text = "100" self.leftCoinView.titleLabel.text = "¥零钱" self.leftCoinView.valueLabel.text = "¥0.00" self.centerView.addSubview(self.bookCoinView) self.centerView.addSubview(self.bookBarginView) self.centerView.addSubview(self.goldCoinView) self.centerView.addSubview(self.leftCoinView) self.centerView.addSubview(self.centerLine) self.centerView.addSubview(self.topHoriLine) self.centerView.addSubview(self.centerHoriLine) self.centerView.addSubview(self.bottomHoriLine) self.contentView.addSubview(self.centerView) self.contentView.addSubview(self.bottomView) self.bottomView.addSubview(self.IDLabel) self.bottomView.addSubview(self.IDValueLabel) self.bottomView.addSubview(self.copyButton) self.bottomView.addSubview(self.bottomLine) self.copyButton.addTarget(self, action: #selector(copyAction(sender:)), for: .touchUpInside) // let tap = UIGestureRecognizer(target: self, action: #selector(tapAction(tap:))) // self.topView.addGestureRecognizer(tap) self.topView.addTarget(self, action: #selector(tapAction(tap:)), for: .touchUpInside) } override func layoutSubviews() { super.layoutSubviews() self.backgroundView?.isUserInteractionEnabled = true self.topView.frame = CGRect(x: 0, y: 0, width: self.bounds.width, height: 100) self.centerView.frame = CGRect(x: 0, y: self.topView.frame.maxY, width: self.bounds.width, height: 100) self.bottomView.frame = CGRect(x: 0, y: self.centerView.frame.maxY, width: self.bounds.width, height: 60) self.topLine.frame = CGRect(x: 0, y: self.topView.bounds.height - 0.2, width: self.bounds.width, height: 0.2) self.iconView.frame = CGRect(x: 20, y: self.topView.frame.height/2 - 25, width: 50, height: 50) self.nicknameLabel.frame = CGRect(x: self.iconView.frame.maxX + 10, y: self.topView.bounds.height/2 - 15, width: 200, height: 30) self.bookCoinView.frame = CGRect(x: 0, y: 0, width: self.bounds.width/4, height: self.centerView.frame.height) self.bookBarginView.frame = CGRect(x: self.bounds.width/4, y: 0, width: self.bounds.width/4, height: self.centerView.frame.height) self.goldCoinView.frame = CGRect(x: self.bounds.width/2, y: 0, width: self.bounds.width/4, height: self.centerView.frame.height) self.leftCoinView.frame = CGRect(x: self.bounds.width*3/4, y: 0, width: self.bounds.width/4, height: self.centerView.frame.height) self.centerLine.frame = CGRect(x: 0, y: self.centerView.bounds.height - 0.2, width: self.bounds.width, height: 0.2) self.topHoriLine.frame = CGRect(x: self.bounds.width/4, y: 100/3, width: 0.2, height: 100/3) self.centerHoriLine.frame = CGRect(x: self.bounds.width/2, y: 100/3, width: 0.2, height: 100/3) self.bottomHoriLine.frame = CGRect(x: self.bounds.width*3/4, y: 100/3, width: 0.2, height: 100/3) self.IDLabel.frame = CGRect(x: 20, y: 15, width: 30, height: 30) self.copyButton.frame = CGRect(x: self.bounds.width - 80, y: 15, width: 60, height: 30) self.IDValueLabel.frame = CGRect(x: self.IDLabel.frame.maxX + 20, y: self.bottomView.frame.height/2 - 15, width: self.bounds.width - (self.IDLabel.frame.maxX + 20 + self.copyButton.frame.width + 20 + 20), height: 30) self.bottomLine.frame = CGRect(x: 0, y: self.bottomView.bounds.height - 0.2, width: self.bounds.width, height: 0.2) } //MARK: - action @objc func copyAction(sender:UIButton) { UIPasteboard.general.string = self.IDValueLabel.text KeyWindow?.showTip(tip: "ID已复制到您的剪切板") } @objc func tapAction(tap:UITapGestureRecognizer) { handler?() } //MARK: - getter lazy var topLine:UILabel = { let label = UILabel(frame: CGRect(x: 0, y: self.topView.bounds.height - 1, width: self.bounds.width, height: 1)) label.backgroundColor = UIColor.gray label.alpha = 0.2 return label }() lazy var centerLine:UILabel = { let label = UILabel(frame: CGRect(x: 0, y: self.centerView.bounds.height - 1, width: self.bounds.width, height: 1)) label.backgroundColor = UIColor.gray label.alpha = 0.2 return label }() lazy var bottomLine:UILabel = { let label = UILabel(frame: CGRect(x: 0, y: self.bottomView.bounds.height - 1, width: self.bounds.width, height: 1)) label.backgroundColor = UIColor.gray label.alpha = 0.2 return label }() lazy var topHoriLine:UILabel = { let label = UILabel(frame: CGRect(x: self.bounds.width/4, y: 100/3, width: 0.2, height: 100/3)) label.backgroundColor = UIColor.gray label.alpha = 0.2 return label }() lazy var centerHoriLine:UILabel = { let label = UILabel(frame: CGRect(x: self.bounds.width/2, y: 100/3, width: 0.2, height: 100/3)) label.backgroundColor = UIColor.gray label.alpha = 0.2 return label }() lazy var bottomHoriLine:UILabel = { let label = UILabel(frame: CGRect(x: self.bounds.width*3/4, y: 100/3, width: 0.2, height: 100/3)) label.backgroundColor = UIColor.gray label.alpha = 0.2 return label }() lazy var copyButton:UIButton = { let btn = UIButton(type: .custom) btn.titleLabel?.font = UIFont.systemFont(ofSize: 13) btn.frame = CGRect(x: self.bounds.width - 60, y: 15, width: 60, height: 26) btn.setTitle("复制ID", for: .normal) btn.setTitleColor(UIColor.darkGray, for: .normal) btn.layer.cornerRadius = 5 btn.layer.borderColor = UIColor.gray.cgColor btn.layer.borderWidth = 0.5 return btn }() lazy var IDValueLabel:UILabel = { let label = UILabel(frame: CGRect.zero) label.font = UIFont.systemFont(ofSize: 13) label.textColor = UIColor.black label.textAlignment = .left return label }() lazy var IDLabel:UILabel = { let label = UILabel(frame: CGRect(x: 20, y: 15, width: 30, height: 30)) label.font = UIFont.systemFont(ofSize: 20) label.textColor = UIColor.white label.textAlignment = .center label.backgroundColor = UIColor.orange label.text = "ID" label.layer.cornerRadius = 15 label.layer.masksToBounds = true return label }() lazy var bookCoinView:ZSMyHeaderItemView = { let view = ZSMyHeaderItemView(frame: CGRect.zero) view.backgroundColor = UIColor.white view.isUserInteractionEnabled = true return view }() lazy var bookBarginView:ZSMyHeaderItemView = { let view = ZSMyHeaderItemView(frame: CGRect.zero) view.backgroundColor = UIColor.white view.isUserInteractionEnabled = true return view }() lazy var goldCoinView:ZSMyHeaderItemView = { let view = ZSMyHeaderItemView(frame: CGRect.zero) view.backgroundColor = UIColor.white view.isUserInteractionEnabled = true return view }() lazy var leftCoinView:ZSMyHeaderItemView = { let view = ZSMyHeaderItemView(frame: CGRect.zero) view.backgroundColor = UIColor.white view.isUserInteractionEnabled = true return view }() lazy var iconView:UIImageView = { let imageView = UIImageView(frame: CGRect.zero) imageView.frame = CGRect(x: 20, y: 30, width: 50, height: 50) imageView.layer.cornerRadius = 25 imageView.layer.masksToBounds = true imageView.isUserInteractionEnabled = true return imageView }() lazy var nicknameLabel:UILabel = { let label = UILabel(frame: CGRect.zero) label.font = UIFont.systemFont(ofSize: 15) label.textColor = UIColor.black label.textAlignment = .left return label }() lazy var topView:UIButton = { let view = UIButton(frame: CGRect(x: 0, y: 0, width: self.bounds.width, height: 100)) view.backgroundColor = UIColor.white view.isUserInteractionEnabled = true return view }() lazy var centerView:UIView = { let view = UIView(frame: CGRect.zero) view.backgroundColor = UIColor.white view.isUserInteractionEnabled = true return view }() lazy var bottomView:UIView = { let view = UIView(frame: CGRect.zero) view.backgroundColor = UIColor.white view.isUserInteractionEnabled = true return view }() } class ZSMyHeaderItemView: UIView { override init(frame: CGRect) { super.init(frame: frame) setupSubviews() } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } func setupSubviews() { self.addSubview(self.valueLabel) self.addSubview(self.titleLabel) } override func layoutSubviews() { super.layoutSubviews() self.titleLabel.frame = CGRect(x: 0, y: 50, width: self.bounds.width, height: 15) self.valueLabel.frame = CGRect(x: 0, y: 35, width: self.bounds.width, height: 15) } lazy var titleLabel:UILabel = { let label = UILabel(frame: CGRect.zero) label.font = UIFont.systemFont(ofSize: 11) label.textColor = UIColor.gray label.textAlignment = .center return label }() lazy var valueLabel:UILabel = { let label = UILabel(frame: CGRect.zero) label.font = UIFont.systemFont(ofSize: 11) label.textColor = UIColor.black label.textAlignment = .center return label }() } ================================================ FILE: zhuishushenqi/Root/Views/ZSReviewsCell.swift ================================================ // // ZSReviewsCell.swift // zhuishushenqi // // Created by yung on 2018/8/19. // Copyright © 2018年 QS. All rights reserved. // import UIKit class ZSReviewsCell: UITableViewCell { @IBOutlet weak var icon: UIImageView! @IBOutlet weak var book: UILabel! @IBOutlet weak var content: UILabel! @IBOutlet weak var useful: UILabel! @IBOutlet weak var date: UILabel! @IBOutlet weak var flag: UIImageView! var model:BookComment? var tags:[[String:String]] = [] override func awakeFromNib() { super.awakeFromNib() } func configureCell(model:BookComment){ self.model = model book.text = "\(model.book.title)[\(pyToHz(py: model.book.type))]" content.text = "\(model.title)" useful.text = "\(model.helpful.total + model.helpful.no)/\(model.helpful.yes + model.helpful.no) 有用" date.qs_setCreateTime(createTime: self.model?.created ?? "", append: "") icon.qs_setBookCoverWithURLString(urlString: "\(model.book.cover)") let state = self.model?.state if state == "normal" { flag.isHidden = true }else if state == "distillate" { flag.isHidden = false flag.image = UIImage(named: "f_distillate") }else if state == "hot" { flag.isHidden = false flag.image = UIImage(named: "f_hot") }else if state == "focus" { flag.isHidden = false flag.image = UIImage(named: "f_today_topic") } } func tag(items:[[String:String]]) { self.tags = items } func pyToHz(py:String)->String{ var hz = "" if py == "qt" { hz = "其它" } else { for item in self.tags { if item["value"] == py { hz = item["title"] ?? "" break } } } return hz } func isToday()->Bool{ let year = self.model?.created.qs_subStr(to: 4) ?? "2017" let month = self.model?.created.qs_subStr(start: 5, end: 7) ?? "06" let day = self.model?.created.qs_subStr(start: 8, length: 2) ?? "18" let today = Date() if today.year() == Int(year) && today.month() == Int(month) && today.day() == Int(day) { return true } return false } } ================================================ FILE: zhuishushenqi/Root/Views/ZSReviewsCell.xib ================================================ ================================================ FILE: zhuishushenqi/Root/Views/ZSSwipeCell.swift ================================================ // // ZSSwipeCell.swift // zhuishushenqi // // Created by caony on 2019/1/12. // Copyright © 2019年 QS. All rights reserved. // import UIKit class ZSSwipeCell: UITableViewCell { override func awakeFromNib() { super.awakeFromNib() // Initialization code } override func setSelected(_ selected: Bool, animated: Bool) { super.setSelected(selected, animated: animated) // Configure the view for the selected state } } //class ZSSwipeView: UIView { // // override init(frame: CGRect) { // super.init(frame: frame) // // } // // required init?(coder aDecoder: NSCoder) { // fatalError("init(coder:) has not been implemented") // } //} ================================================ FILE: zhuishushenqi/Root/Views/ZSThirdLoginView.swift ================================================ // // ZSThirdLoginView.swift // zhuishushenqi // // Created by yung on 2018/10/23. // Copyright © 2018年 QS. All rights reserved. // import UIKit typealias ZSThirdLoginHandler = (_ type:ThirdLoginType)->Void typealias ZSThirdLoginCloseHandler = ()->Void class ZSThirdLoginView: UIView { var leftLineLabel:UILabel! var rightLineLabel:UILabel! var loginTypeLabel:UILabel! var qqLoginButton:UIButton! var wxLoginButton:UIButton! var wbLoginButton:UIButton! var xmLoginButton:UIButton! var userProtocolButton:UIButton! var closeButton:UIButton! var thirdLoginHandler:ZSThirdLoginHandler? var closeHandler:ZSThirdLoginCloseHandler? var userProtocolHandler:ZSThirdLoginCloseHandler? override init(frame: CGRect) { super.init(frame: frame) setupSubviews() } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } private func setupSubviews() { var topMargin:CGFloat = 115 if UIScreen.main.bounds.height <= 1136 { topMargin = 30 } leftLineLabel = UILabel(frame: CGRect(x: 40, y: topMargin, width: 100, height: 0.5)) leftLineLabel.backgroundColor = UIColor(red: 1.0, green: 0.0, blue: 0.0, alpha: 0.7) addSubview(leftLineLabel) rightLineLabel = UILabel(frame: CGRect(x: self.bounds.width - 140, y: topMargin, width: 100, height: 0.5)) rightLineLabel.backgroundColor = UIColor(red: 1.0, green: 0.0, blue: 0.0, alpha: 0.7) addSubview(rightLineLabel) loginTypeLabel = UILabel(frame: CGRect(x: 0, y: topMargin - 15, width: 200, height: 30)) loginTypeLabel.text = "选择登录方式" loginTypeLabel.textColor = UIColor(red: 1.0, green: 0.0, blue: 0.0, alpha: 0.7) loginTypeLabel.font = UIFont.systemFont(ofSize: 11) loginTypeLabel.textAlignment = .center loginTypeLabel.centerX = self.centerX addSubview(loginTypeLabel) let buttonWidth:CGFloat = 40 let buttonHeight:CGFloat = 40 let marginSpace:CGFloat = 40 let marginTopSpace:CGFloat = 20 let centerSpace = (self.bounds.width - marginSpace*2 - 4*buttonWidth)/3 qqLoginButton = UIButton(type: .custom) qqLoginButton.frame = CGRect(x: marginSpace, y: self.loginTypeLabel.frame.maxY + marginTopSpace, width: buttonWidth, height: buttonHeight) qqLoginButton.setImage(UIImage(named: "nl_qq"), for: .normal) qqLoginButton.addTarget(self, action: #selector(qqLoginAction(btn:)), for: .touchUpInside) addSubview(qqLoginButton) wxLoginButton = UIButton(type: .custom) wxLoginButton.frame = CGRect(x: marginSpace + buttonWidth + centerSpace, y: self.loginTypeLabel.frame.maxY + marginTopSpace, width: buttonWidth, height: buttonHeight) wxLoginButton.setImage(UIImage(named: "nl_wechat"), for: .normal) wxLoginButton.addTarget(self, action: #selector(wxLoginAction(btn:)), for: .touchUpInside) addSubview(wxLoginButton) wbLoginButton = UIButton(type: .custom) wbLoginButton.frame = CGRect(x: marginSpace + buttonWidth*2 + centerSpace*2, y: self.loginTypeLabel.frame.maxY + marginTopSpace, width: buttonWidth, height: buttonHeight) wbLoginButton.setImage(UIImage(named: "nl_weibo"), for: .normal) wbLoginButton.addTarget(self, action: #selector(wbLoginAction(btn:)), for: .touchUpInside) addSubview(wbLoginButton) xmLoginButton = UIButton(type: .custom) xmLoginButton.frame = CGRect(x: marginSpace + buttonWidth*3 + centerSpace*3, y: self.loginTypeLabel.frame.maxY + marginTopSpace, width: buttonWidth, height: buttonHeight) xmLoginButton.setImage(UIImage(named: "nl_xiaomi"), for: .normal) xmLoginButton.addTarget(self, action: #selector(xmLoginAction(btn:)), for: .touchUpInside) addSubview(xmLoginButton) userProtocolButton = UIButton(type: .custom) userProtocolButton.frame = CGRect(x: 0, y: xmLoginButton.frame.maxY + marginTopSpace, width: self.bounds.width, height: 30) userProtocolButton.centerX = self.centerX userProtocolButton.setTitle("点击登录即代表同意追书神器《用户协议》", for: .normal) userProtocolButton.titleLabel?.font = UIFont.systemFont(ofSize: 11) userProtocolButton.setTitleColor(UIColor(red: 1.0, green: 0.0, blue: 0.0, alpha: 0.7), for: .normal) userProtocolButton.addTarget(self, action: #selector(userProtocolLoginAction(btn:)), for: .touchUpInside) addSubview(userProtocolButton) closeButton = UIButton(type: .custom) closeButton.frame = CGRect(x: 0, y: self.userProtocolButton.frame.maxY + 40, width: 60, height: 30) closeButton.setTitle("关闭>", for: .normal) closeButton.setTitleColor(UIColor(red: 1.0, green: 0.0, blue: 0.0, alpha: 0.7), for: .normal) closeButton.titleLabel?.font = UIFont.systemFont(ofSize: 11) closeButton.centerX = self.centerX closeButton.addTarget(self, action: #selector(closeLoginAction(btn:)), for: .touchUpInside) addSubview(closeButton) } @objc private func qqLoginAction(btn:UIButton) { thirdLoginHandler?(ThirdLoginType.QQ) } @objc private func wxLoginAction(btn:UIButton) { thirdLoginHandler?(ThirdLoginType.WX) } @objc private func wbLoginAction(btn:UIButton) { thirdLoginHandler?(ThirdLoginType.WB) } @objc private func xmLoginAction(btn:UIButton) { thirdLoginHandler?(ThirdLoginType.XM) } @objc private func closeLoginAction(btn:UIButton) { closeHandler?() } @objc private func userProtocolLoginAction(btn:UIButton) { userProtocolHandler?() } } ================================================ FILE: zhuishushenqi/Root/Views/ZSUserBindCell.swift ================================================ // // ZSUserBindCell.swift // zhuishushenqi // // Created by yung on 2018/10/20. // Copyright © 2018年 QS. All rights reserved. // import UIKit typealias ZSUserBindHandler = (_ selected:Bool)->Void class ZSUserBindCell: UITableViewCell { lazy var rightLabel:UILabel = { let label = UILabel() label.textColor = UIColor.gray label.font = UIFont.systemFont(ofSize: 12) label.textAlignment = .right label.text = "未绑定" return label }() lazy var rightButton:UIButton = { let button = UIButton(type: .custom) button.frame = CGRect(x: 0, y: 0, width: 100, height: 30) button.setTitle("立即绑定", for: .normal) // button.setTitle("已绑定", for: .selected) button.setTitleColor(UIColor.red, for: .normal) button.setTitleColor(UIColor.gray, for: .selected) button.titleLabel?.font = UIFont.systemFont(ofSize: 13) button.layer.borderColor = UIColor.red.cgColor button.layer.borderWidth = 1 button.layer.cornerRadius = 5 return button }() var buttonHandler:ZSUserBindHandler? override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) self.accessoryType = .disclosureIndicator contentView.addSubview(self.rightLabel) contentView.addSubview(self.rightButton) self.rightButton.addTarget(self, action: #selector(rightAction(btn:)), for: .touchUpInside) } @objc func rightAction(btn:UIButton) { // btn.isSelected = !btn.isSelected buttonHandler?(btn.isSelected) } override func layoutSubviews() { super.layoutSubviews() self.rightButton.frame = CGRect(x: self.bounds.width - 120, y: self.bounds.height/2 - 15, width: 80, height: 30) self.rightLabel.frame = CGRect(x: self.rightButton.frame.minX - 310, y: self.bounds.height/2 - 15, width: 300, height: 30) } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func awakeFromNib() { super.awakeFromNib() // Initialization code } override func setSelected(_ selected: Bool, animated: Bool) { super.setSelected(selected, animated: animated) // Configure the view for the selected state } } ================================================ FILE: zhuishushenqi/Root/Views/ZSVoiceCategoryCell.swift ================================================ // // QSCatalogProtocols.swift // zhuishushenqi // // Created yung on 2017/4/21. // Copyright © 2017年 QS. All rights reserved. // // Template generated by Juanpe Catalán @JuanpeCMiOS // class ZSVoiceCategoryCell: UITableViewCell { override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: .value1, reuseIdentifier: reuseIdentifier) setupSubviews() } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func layoutSubviews() { super.layoutSubviews() imageView?.frame = CGRect(x: 20, y: 10, width: bounds.height*2/3, height: bounds.height - 20) textLabel?.frame = CGRect(x: (imageView?.frame.maxX ?? 0) + 20, y: 10, width: bounds.width - (imageView?.frame.maxX ?? 0) - 20 - 20, height: 20) detailTextLabel?.frame = CGRect(x: (imageView?.frame.maxX ?? 0) + 20, y: (bounds.height - 20)/3 + 10, width: bounds.width - (imageView?.frame.maxX ?? 0) - 20 - 20, height: 20) popularityLabel.frame = CGRect(x: (imageView?.frame.maxX ?? 0) + 20, y: (bounds.height - 20)*2/3 + 10, width: (bounds.width - (imageView?.frame.maxX ?? 0))/2 - 20, height: 20) totalLabel.frame = CGRect(x: (popularityLabel?.frame.midX ?? 0) + 40 , y: (bounds.height - 20)*2/3 + 10, width: (bounds.width - (imageView?.frame.maxX ?? 0))/2 + 20, height: 20) } private func setupSubviews() { textLabel?.font = UIFont.systemFont(ofSize: 15) detailTextLabel?.font = UIFont.systemFont(ofSize: 13) popularityLabel = UILabel() popularityLabel.font = UIFont.systemFont(ofSize: 12) popularityLabel.textColor = UIColor.gray contentView.addSubview(popularityLabel) totalLabel = UILabel() totalLabel.font = UIFont.systemFont(ofSize: 12) totalLabel.textColor = UIColor.gray contentView.addSubview(totalLabel) } var popularityLabel:UILabel! var totalLabel:UILabel! } ================================================ FILE: zhuishushenqi/Root/Views/ZSVoiceCategoryHeaderView.swift ================================================ // // QSCatalogPresenter.swift // zhuishushenqi // // Created yung on 2017/4/21. // Copyright © 2017年 QS. All rights reserved. // // Template generated by Juanpe Catalán @JuanpeCMiOS // class ZSVoiceCategoryHeaderView: UITableViewHeaderFooterView { override init(reuseIdentifier: String?) { super.init(reuseIdentifier: reuseIdentifier) setupSubviews() } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } private func setupSubviews() { imageView = UIImageView(frame: CGRect.zero) contentView.addSubview(imageView) titleLabel = UILabel(frame: CGRect.zero) titleLabel.textColor = UIColor.black titleLabel.textAlignment = .left titleLabel.font = UIFont.systemFont(ofSize: 17) contentView.addSubview(titleLabel) } override func layoutSubviews() { super.layoutSubviews() guard let image = imageView.image else { titleLabel.frame = CGRect(x: 20, y: 0, width: bounds.width, height: bounds.height) return } imageView.frame = CGRect(x: 20, y: bounds.height/2 - image.size.height/2, width: image.size.width, height: image.size.height) titleLabel.frame = CGRect(x: imageView.frame.maxX + 10, y: 0, width: bounds.width, height: bounds.height) } var imageView:UIImageView! var titleLabel:UILabel! } ================================================ FILE: zhuishushenqi/Root/Views/ZSVoicePlayProgressView.swift ================================================ // // ZSVoicePlayProgressView.swift // zhuishushenqi // // Created by caony on 2019/3/23. // Copyright © 2019年 QS. All rights reserved. // import UIKit protocol ZSVoicePlayerProgressDelegate { func playerProgressView(playerProgressView:ZSVoicePlayProgressView, valueChangedTo seconds:Float) } class ZSVoicePlayProgressView: UIView { fileprivate var startTimeLabel:UILabel! fileprivate var totalTimeLabel:UILabel! fileprivate var progressBar:UISlider! fileprivate var track:XMTrack? var delegate:ZSVoicePlayerProgressDelegate? var progress:Float = 0 { didSet { progressBar.value = progress } } var currentSeconds:UInt = 0 { didSet { updateStartLabel() } } override init(frame: CGRect) { super.init(frame: frame) setupSubviews() } private func setupSubviews() { isUserInteractionEnabled = true progressBar = UISlider(frame: CGRect.zero) progressBar.thumbTintColor = UIColor.red progressBar.minimumTrackTintColor = UIColor.red progressBar.addTarget(self, action: #selector(sliderChange), for: .valueChanged) progressBar.minimumValue = 0 progressBar.maximumValue = 1.0 progressBar.value = 0.5 addSubview(progressBar) startTimeLabel = UILabel() startTimeLabel.textColor = UIColor.gray startTimeLabel.font = UIFont.systemFont(ofSize: 13) startTimeLabel.textAlignment = .left startTimeLabel.text = "00:00" addSubview(startTimeLabel) totalTimeLabel = UILabel() totalTimeLabel.textColor = UIColor.gray totalTimeLabel.font = UIFont.systemFont(ofSize: 13) totalTimeLabel.textAlignment = .right totalTimeLabel.text = "00:00" addSubview(totalTimeLabel) } @objc private func sliderChange() { let seconds = Float(self.track?.duration ?? 0) * self.progressBar.value self.currentSeconds = UInt(seconds) updateStartLabel() delegate?.playerProgressView(playerProgressView: self, valueChangedTo: seconds) } func bind(track:XMTrack) { self.track = track updateTotalLabel(with: track) } func updateTotalLabel(with track:XMTrack) { var hours = 0 var minutes = track.duration/60 let seconds = track.duration%60 if minutes > 60 { hours = minutes/60 minutes = minutes%60 } var totalTimeText = "" if hours > 0 { let hoursText = getZeroDefaultText(text: "\(hours)") let minutedText = getZeroDefaultText(text: "\(minutes)") let secondText = getZeroDefaultText(text: "\(seconds)") totalTimeText = "\(hoursText):\(minutedText):\(secondText)" } else if minutes > 0 { let minutedText = getZeroDefaultText(text: "\(minutes)") let secondText = getZeroDefaultText(text: "\(seconds)") totalTimeText = "\(minutedText):\(secondText)" } else { let secondText = getZeroDefaultText(text: "\(seconds)") totalTimeText = "00:\(secondText)" } totalTimeLabel.text = totalTimeText } func getZeroDefaultText(text:String) ->String { var zeroText:String = text if zeroText.count <= 1 { zeroText = "0\(zeroText)" } return zeroText } func updateStartLabel() { var hours = 0 var minutes = Int(currentSeconds)/60 let seconds = Int(currentSeconds)%60 if minutes > 60 { hours = minutes/60 minutes = minutes%60 } var startTimeText = "" if hours > 0 { let hoursText = getZeroDefaultText(text: "\(hours)") let minutedText = getZeroDefaultText(text: "\(minutes)") let secondText = getZeroDefaultText(text: "\(seconds)") startTimeText = "\(hoursText):\(minutedText):\(secondText)" } else if minutes > 0 { let minutedText = getZeroDefaultText(text: "\(minutes)") let secondText = getZeroDefaultText(text: "\(seconds)") startTimeText = "\(minutedText):\(secondText)" } else { let secondText = getZeroDefaultText(text: "\(seconds)") startTimeText = "00:\(secondText)" } startTimeLabel.text = startTimeText } override func layoutSubviews() { super.layoutSubviews() progressBar.snp.remakeConstraints { (make) in make.left.top.right.equalToSuperview() make.height.equalTo(15) } startTimeLabel.snp.remakeConstraints { (make) in make.left.equalToSuperview() make.top.equalTo(self.progressBar.bottom + 15) make.width.equalTo(self.bounds.width/2) make.height.equalTo(15) } totalTimeLabel.snp.remakeConstraints { (make) in make.right.equalToSuperview() make.top.equalTo(self.progressBar.bottom + 15) make.width.equalTo(self.bounds.width/2) make.height.equalTo(15) } } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } } ================================================ FILE: zhuishushenqi/Root/Views/ZSVoicePlayerCatelogHeaderView.swift ================================================ // // QSCatalogViewController.swift // zhuishushenqi // // Created yung on 2017/4/21. // Copyright © 2017年 QS. All rights reserved. // // Template generated by Juanpe Catalán @JuanpeCMiOS // typealias ZSVoicePlayerCatelogHandler = ()->Void class ZSVoicePlayerCatelogHeaderView: UITableViewHeaderFooterView { var backhandler:ZSVoicePlayerCatelogHandler? override init(reuseIdentifier: String?) { super.init(reuseIdentifier: reuseIdentifier) setupSubviews() } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func layoutSubviews() { super.layoutSubviews() backButton.frame = CGRect(x: 15, y: bounds.height/2 - 24/2, width: 24, height: 24) titleLabel.frame = CGRect(x: 50, y: bounds.height/2 - 30/2, width: bounds.width - 100, height: 30) totalLabel.frame = CGRect(x: bounds.width - 100 - 15, y: bounds.height/2 - 30/2, width: 100, height: 30) separatorLine.frame = CGRect(x: 0, y: bounds.height - 0.5, width: bounds.width, height: 0.5) } func bind(title:String, total:String) { titleLabel.text = title totalLabel.text = total } @objc private func backAction() { backhandler?() } private func setupSubviews() { contentView.backgroundColor = UIColor.white backButton = UIButton(type: .custom) backButton.setImage(UIImage(named: "discover_icon_back_24_24"), for: .normal) backButton.addTarget(self, action: #selector(backAction), for: .touchUpInside) contentView.addSubview(backButton) titleLabel = UILabel(frame: CGRect.zero) titleLabel.textAlignment = .center titleLabel.font = UIFont.systemFont(ofSize: 24) titleLabel.textColor = UIColor.black contentView.addSubview(titleLabel) totalLabel = UILabel(frame: CGRect.zero) totalLabel.textAlignment = .right totalLabel.font = UIFont.systemFont(ofSize: 13) totalLabel.textColor = UIColor.gray contentView.addSubview(totalLabel) separatorLine = UIView(frame: CGRect.zero) separatorLine.backgroundColor = UIColor.gray separatorLine.alpha = 0.3 contentView.addSubview(separatorLine) } fileprivate var backButton:UIButton! fileprivate var titleLabel:UILabel! fileprivate var totalLabel:UILabel! fileprivate var separatorLine:UIView! } ================================================ FILE: zhuishushenqi/Root/Views/ZSVoicePlayerCatelogView.swift ================================================ // // ZSVoicePlayerCatelogView.swift // zhuishushenqi // // Created by caony on 2019/4/12. // Copyright © 2019年 QS. All rights reserved. // import Foundation typealias ZSVoicePlayerCatelogCellHandler = (_ indexPath:IndexPath)->Void class ZSVoicePlayerCatelogView: UIView { var tracks:[XMTrack] = [] { didSet { self.tableView.reloadData() } } var album:XMAlbum? { didSet { self.tableView.reloadData() } } var backhandler:ZSVoicePlayerCatelogHandler? var cellCickHandler:ZSVoicePlayerCatelogCellHandler? var selectedIndex:Int = 0 { didSet { tableView.reloadData() } } override init(frame: CGRect) { super.init(frame: frame) setupSubviews() } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func layoutSubviews() { super.layoutSubviews() tableView.frame = bounds } private func setupSubviews() { tableView = UITableView(frame: CGRect.zero, style: .plain) tableView.dataSource = self tableView.delegate = self tableView.backgroundColor = UIColor.white tableView.qs_registerHeaderFooterClass(ZSVoicePlayerCatelogHeaderView.self) tableView.qs_registerCellClass(UITableViewCell.self) addSubview(tableView) } fileprivate var tableView:UITableView! } extension ZSVoicePlayerCatelogView:UITableViewDataSource, UITableViewDelegate { func numberOfSections(in tableView: UITableView) -> Int { return 1 } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return tracks.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.qs_dequeueReusableCell(UITableViewCell.self) let text = "\(indexPath.row + 1) \(tracks[indexPath.row].trackTitle ?? "")" let attr = NSMutableAttributedString(string: text) if indexPath.row == selectedIndex { cell?.accessoryView = UIImageView(image: UIImage(named: "discover_icon_palying_20_14")) attr.addAttribute( NSAttributedString.Key.foregroundColor, value: UIColor.red, range: NSMakeRange("\(indexPath.row)".count, attr.length - "\(indexPath.row)".count)) attr.addAttribute( NSAttributedString.Key.foregroundColor, value: UIColor.gray, range: NSMakeRange(0, "\(indexPath.row)".count)) } else { cell?.accessoryView = nil attr.addAttribute( NSAttributedString.Key.foregroundColor, value: UIColor.black, range: NSMakeRange("\(indexPath.row)".count, attr.length - "\(indexPath.row)".count)) attr.addAttribute( NSAttributedString.Key.foregroundColor, value: UIColor.gray, range: NSMakeRange(0, "\(indexPath.row)".count)) } cell?.textLabel?.attributedText = attr cell?.selectionStyle = .none return cell! } func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { let headerView = tableView.qs_dequeueReusableHeaderFooterView(ZSVoicePlayerCatelogHeaderView.self) headerView?.backhandler = { self.backhandler?() } if let model = album { headerView?.bind(title: model.albumTitle, total: "共\(tracks.count)集") } return headerView } func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { return 80 } func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { return 50 } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { selectedIndex = indexPath.row cellCickHandler?(indexPath) tableView.reloadData() } } ================================================ FILE: zhuishushenqi/Root/Views/ZSVoicePlayerView.swift ================================================ // // ZSVoicePlayerView.swift // zhuishushenqi // // Created by caony on 2019/3/23. // Copyright © 2019年 QS. All rights reserved. // import UIKit protocol ZSVoicePlayerDelegate { func playerView(playerView:ZSVoicePlayerView, didClickPauseButton:UIButton) func playerView(playerView:ZSVoicePlayerView, didClickLastButton:UIButton) func playerView(playerView:ZSVoicePlayerView, didClickNextButton:UIButton) func playerView(playerView:ZSVoicePlayerView, didClickCatelogButton:UIButton) func playerView(playerView:ZSVoicePlayerView, didClickShelfButton:UIButton) func playerView(playerView:ZSVoicePlayerView, didClickTimerButton:UIButton) func playerView(playerView:ZSVoicePlayerView, valueChangedTo seconds:Float) } class ZSVoicePlayerView: UIView,ZSVoicePlayerProgressDelegate { fileprivate var progressBar:ZSVoicePlayProgressView! fileprivate var catelogButton:UIButton! fileprivate var shelfButton:UIButton! fileprivate var lastButton:UIButton! fileprivate var pauseButton:UIButton! fileprivate var nextButton:UIButton! fileprivate var timerButton:UIButton! var progress:Float = 0 { didSet { self.progressBar.progress = progress } } var currentSeconds:UInt = 0 { didSet { self.progressBar.currentSeconds = currentSeconds } } var playerDelegate:ZSVoicePlayerDelegate? override init(frame: CGRect) { super.init(frame: frame) setupSubview() } func bind(track:XMTrack) { progressBar.bind(track: track) } func didStartPlay() { pauseButton.isSelected = true } func didFailedToPlay() { pauseButton.isSelected = false } private func setupSubview() { progressBar = ZSVoicePlayProgressView(frame: CGRect.zero) progressBar.delegate = self addSubview(progressBar) catelogButton = UIButton(type: .custom) catelogButton.addTarget(self, action: #selector(catelogAction(btn:)), for: .touchUpInside) catelogButton.setImage(UIImage(named: "discover_icon_more_24_24"), for: .normal) addSubview(catelogButton) lastButton = UIButton(type: .custom) lastButton.addTarget(self, action: #selector(lastAction(btn:)), for: .touchUpInside) lastButton.setImage(UIImage(named: "discover_icon_up_36_36"), for: .normal) addSubview(lastButton) nextButton = UIButton(type: .custom) nextButton.addTarget(self, action: #selector(nextAction(btn:)), for: .touchUpInside) nextButton.setImage(UIImage(named: "discover_icon_next_36_36"), for: .normal) addSubview(nextButton) pauseButton = UIButton(type: .custom) pauseButton.addTarget(self, action: #selector(pauseAction(btn:)), for: .touchUpInside) pauseButton.setImage(UIImage(named: "discover_icon_pouse_76_76"), for: .normal) pauseButton.setImage(UIImage(named: "discover_icon_play_76_76"), for: .selected) addSubview(pauseButton) shelfButton = UIButton(type: .custom) shelfButton.addTarget(self, action: #selector(shelfAction(btn:)), for: .touchUpInside) shelfButton.setImage(UIImage(named: "discover_icon_bookshelf_24_24_p"), for: .normal) shelfButton.setImage(UIImage(named: "discover_icon_bookshelf_24_24"), for: .selected) addSubview(shelfButton) timerButton = UIButton(type: .custom) timerButton.addTarget(self, action: #selector(timerAction(btn:)), for: .touchUpInside) timerButton.setImage(UIImage(named: "discover_icon_timing_24_24"), for: .normal) addSubview(timerButton) } @objc private func catelogAction(btn:UIButton) { playerDelegate?.playerView(playerView: self, didClickCatelogButton: btn) } @objc private func shelfAction(btn:UIButton) { btn.isSelected = !btn.isSelected playerDelegate?.playerView(playerView: self, didClickShelfButton: btn) } @objc private func timerAction(btn:UIButton) { playerDelegate?.playerView(playerView: self, didClickTimerButton: btn) } @objc private func lastAction(btn:UIButton) { playerDelegate?.playerView(playerView: self, didClickLastButton: btn) } @objc private func pauseAction(btn:UIButton) { btn.isSelected = !btn.isSelected playerDelegate?.playerView(playerView: self, didClickPauseButton: btn) } @objc private func nextAction(btn:UIButton) { playerDelegate?.playerView(playerView: self, didClickNextButton: btn) } //MARK: - ZSVoicePlayerProgressDelegate func playerProgressView(playerProgressView: ZSVoicePlayProgressView, valueChangedTo seconds: Float) { playerDelegate?.playerView(playerView: self, valueChangedTo: seconds) } override func layoutSubviews() { super.layoutSubviews() progressBar.snp.remakeConstraints { (make) in make.left.right.equalToSuperview() make.top.equalToSuperview() make.height.equalTo(100) } lastButton.snp.remakeConstraints { (make) in make.left.equalToSuperview().offset(20) make.top.equalTo(self.progressBar.bottom + 7) make.width.equalTo(36) make.height.equalTo(36) } pauseButton.snp.remakeConstraints { (make) in make.centerX.equalToSuperview() make.top.equalTo(self.progressBar.bottom + 7) make.width.equalTo(36) make.height.equalTo(36) } nextButton.snp.remakeConstraints { (make) in make.right.equalToSuperview().offset(-20) make.top.equalTo(self.progressBar.bottom + 7) make.width.equalTo(36) make.height.equalTo(36) } catelogButton.snp.remakeConstraints { (make) in make.left.equalToSuperview() make.bottom.equalToSuperview().offset(-18) make.width.equalTo(24) make.height.equalTo(24) } shelfButton.snp.remakeConstraints { (make) in make.centerX.equalToSuperview() make.bottom.equalToSuperview().offset(-18) make.width.equalTo(24) make.height.equalTo(24) } timerButton.snp.remakeConstraints { (make) in make.right.equalToSuperview() make.bottom.equalToSuperview().offset(-18) make.width.equalTo(24) make.height.equalTo(24) } } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } } ================================================ FILE: zhuishushenqi/Root/Views/ZSVoiceSegmentView.swift ================================================ // // ZSVoiceSegmentView.swift // zhuishushenqi // // Created by caony on 2019/3/23. // Copyright © 2019年 QS. All rights reserved. // import UIKit import SnapKit enum SegmentType { case bottom case bigselect } protocol ZSVoiceSegmentProtocol { func titlesForSegment(segmentView:ZSVoiceSegmentView) ->[String] func didSelect(segment:ZSVoiceSegmentView, at index:Int) } class ZSVoiceSegmentView: UIView { var collectionView:UICollectionView! var delegate:ZSVoiceSegmentProtocol? var selectedIndex = 0 var type:SegmentType = .bottom { didSet { change(type: type) } } override init(frame: CGRect) { super.init(frame: frame) setupSubviews() } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } func setupSubviews() { let layout = UICollectionViewFlowLayout() layout.scrollDirection = .horizontal layout.itemSize = CGSize(width: 80, height: 30) collectionView = UICollectionView(frame: CGRect.zero, collectionViewLayout: layout) collectionView.dataSource = self collectionView.delegate = self collectionView.backgroundColor = UIColor.white collectionView.showsVerticalScrollIndicator = false collectionView.showsHorizontalScrollIndicator = false collectionView.qs_registerCellClass(ZSVoiceSegmentCell.self) addSubview(collectionView) } override func layoutSubviews() { super.layoutSubviews() collectionView.frame = self.bounds } private func change(type:SegmentType) { self.collectionView.reloadData() } } extension ZSVoiceSegmentView:UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout { func numberOfSections(in collectionView: UICollectionView) -> Int { return 1 } func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { if let rows = self.delegate?.titlesForSegment(segmentView: self) { return rows.count } return 0 } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.qs_dequeueReusableCell(ZSVoiceSegmentCell.self, for: indexPath) cell.backgroundColor = type == .bigselect ? UIColor.clear:UIColor.white cell.selectedView.isHidden = (indexPath.item != selectedIndex) || (type == .bigselect) if let rows = self.delegate?.titlesForSegment(segmentView: self) { cell.titleLabel.text = rows[indexPath.row] } cell.titleLabel.font = (type == .bigselect && indexPath.item == selectedIndex) ? UIFont.systemFont(ofSize: 20):UIFont.systemFont(ofSize: 15) cell.titleLabel.textColor = type == .bigselect ? UIColor.white:UIColor.gray return cell } func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { if type == .bigselect { return CGSize(width: 48, height: 30) } return CGSize(width: 80, height: 30) } func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat { if type == .bigselect { return 0.01 } return 10 } func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { selectedIndex = indexPath.item if type == .bottom { collectionView.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: true) } collectionView.reloadData() } } class ZSVoiceSegmentCell: UICollectionViewCell { var selectedView:UIView! var titleLabel:UILabel! override init(frame: CGRect) { super.init(frame: frame) setupSubviews() } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func layoutSubviews() { super.layoutSubviews() titleLabel.frame = CGRect(x: 0, y: 0, width: self.bounds.width, height: self.bounds.height) selectedView.frame = CGRect(x: 0, y: self.bounds.height - 2, width: self.bounds.width, height: 2) } private func setupSubviews() { titleLabel = UILabel(frame: CGRect.zero) titleLabel.textColor = UIColor.gray titleLabel.textAlignment = .center titleLabel.font = UIFont.systemFont(ofSize: 13) contentView.addSubview(titleLabel) selectedView = UIView(frame: CGRect.zero) selectedView.backgroundColor = UIColor ( red: 0.7235, green: 0.0, blue: 0.1146, alpha: 1.0 ) contentView.addSubview(selectedView) } } ================================================ FILE: zhuishushenqi/Root/Views/ZSVoucherView.swift ================================================ // // ZSVoucherView.swift // zhuishushenqi // // Created by caony on 2019/1/9. // Copyright © 2019年 QS. All rights reserved. // import UIKit import YYText import YYCategories class ZSVoucherCell: UITableViewCell { var titleText:String = "" { didSet { titleLabel.attributedText = self.attributeText(number: titleText) } } var titleDetailText:String = "" { didSet { detailTitleLabel.text = "有效期至 \(titleDetailText)" } } var rightText:String = "" { didSet { rightLabel.text = "余额: \(rightText)" } } var rightDetailText:String = "" { didSet { rightDetailLabel.text = rightDetailText } } private var titleLabel:YYLabel! private var detailTitleLabel:UILabel! private var rightLabel:UILabel! private var rightDetailLabel:UILabel! override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) setupSubview() } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } func setupSubview() { titleLabel = YYLabel(frame: CGRect(x: 15, y: 0, width: 200, height: 30)) titleLabel.textAlignment = .left titleLabel.font = UIFont.systemFont(ofSize: 15) titleLabel.numberOfLines = 1 titleLabel.preferredMaxLayoutWidth = 200 detailTitleLabel = UILabel(frame: CGRect(x: 15, y: 30, width: 200, height: 30)) detailTitleLabel.textAlignment = .left detailTitleLabel.textColor = UIColor.gray detailTitleLabel.font = UIFont.systemFont(ofSize: 13) detailTitleLabel.numberOfLines = 1 rightLabel = UILabel(frame: CGRect(x: self.bounds.width - 100, y: 0, width: 100, height: 30)) rightLabel.textAlignment = .right rightLabel.textColor = UIColor.black rightLabel.font = UIFont.systemFont(ofSize: 13) rightLabel.numberOfLines = 1 rightDetailLabel = UILabel(frame: CGRect(x: self.bounds.width - 100, y: 30, width: 100, height: 30)) rightDetailLabel.textAlignment = .right rightDetailLabel.textColor = UIColor.gray rightDetailLabel.font = UIFont.systemFont(ofSize: 13) rightDetailLabel.numberOfLines = 1 addSubview(titleLabel) addSubview(detailTitleLabel) addSubview(rightLabel) addSubview(rightDetailLabel) } func attributeText(number:String) ->NSAttributedString { let attributeText = NSMutableAttributedString(string: "\(number) 追书券") attributeText.yy_font = UIFont.systemFont(ofSize: 15) attributeText.yy_color = UIColor.black attributeText.yy_alignment = .left attributeText.yy_setTextHighlight(NSMakeRange(0, number.count), color: UIColor.red, backgroundColor: UIColor.clear) { (containerView, text, range, rect) in } return attributeText } override func layoutSubviews() { super.layoutSubviews() titleLabel.frame = CGRect(x: 15, y: 0, width: 200, height: 30) detailTitleLabel.frame = CGRect(x: 15, y: 30, width: 200, height: 30) rightLabel.frame = CGRect(x: self.bounds.width - 100 - 15, y: 0, width: 100, height: 30) rightDetailLabel.frame = CGRect(x: self.bounds.width - 100 - 15, y: 30, width: 100, height: 30) } } ================================================ FILE: zhuishushenqi/Splash/QSSplashScreen.swift ================================================ // // QSSplashScreen.swift // zhuishushenqi // // Created by yung on 2017/6/8. // Copyright © 2017年 QS. All rights reserved. // import UIKit import RxSwift import RxCocoa typealias SplashCallback = ()->Void class QSSplashScreen: NSObject { var splashInfo:[String:Any] = [:] var subject:PublishSubject! private var splashRootVC:QSSplashViewController? private var remainDelay:Int = 3 private var shouldHidden:Bool = true private let splashInfoKey = "splashInfoKey" private let splashImageNameKey = "splashImageNameKey" var completion:SplashCallback? private let splashURL = "http://api.zhuishushenqi.com/splashes/ios" func show(completion:@escaping SplashCallback){ self.completion = completion let subject = PublishSubject() self.subject = subject detachRootViewController() let splashWindoww = UIWindow(frame: UIScreen.main.bounds) splashWindoww.backgroundColor = UIColor.black splashRootVC = QSSplashViewController() splashRootVC?.finishCallback = { NSObject.cancelPreviousPerformRequests(withTarget: self, selector: #selector(self.showSplash), object: nil) self.subject.onNext(true) self.hide() } splashRootVC?.view.backgroundColor = UIColor.clear splashWindoww.rootViewController = splashRootVC splashWindow = splashWindoww splashWindow?.makeKeyAndVisible() getSplashInfo() } func getSplashInfo(){ QSLog("info:\(String(describing: USER_DEFAULTS.object(forKey: splashInfoKey)))") // first check network, load image from disk,if not reachable if Reachability(hostname: IMAGE_BASEURL)?.isReachable == false { let path = "/ZSSQ/Splash" if let dict = ZSCacheHelper.shared.cachedObj(for: splashInfoKey, cachePath: path) as? [String:Any] { let splashInfo = dict // image not exist,skip // searchPah exchange everytime you run your app let imageName = splashInfo[splashImageNameKey] as? String ?? "" let imagePath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first?.appending("/\(imageName)") ?? "" let image = UIImage(contentsOfFile: imagePath) if let splashImage = image { self.perform(#selector(self.showSplash), with: nil, afterDelay: 1.0) self.splashRootVC?.setSplashImage(image: splashImage) }else{ hide() } } else { hide() } return } // request splash image zs_getObj(splashURL) { (response) in if let splash = response as? [String:Any] { if let splashInfo = splash["splash"] as? [[String:Any]] { // save splash info,download image and save it self.splashInfo = splashInfo.first ?? [:] self.downloadSplashImage() } else{ self.hide() } } else { self.hide() } } } func downloadSplashImage(){ let urlString = getSplashURLString() zs_download(url: urlString) { (response) in guard let path = response?["url"] as? String else { return } guard let splachImage = UIImage(contentsOfFile: path) else { return } self.splashInfo[self.splashImageNameKey] = path.ocString.lastPathComponent let cacheBasePath = "/ZSSQ/Splash" ZSCacheHelper.shared.storage(obj: self.splashInfo, for: self.splashInfoKey, cachePath: cacheBasePath) self.perform(#selector(self.showSplash), with: nil, afterDelay: 1.0) self.splashRootVC?.setSplashImage(image: splachImage) } } @objc func showSplash(){ remainDelay -= 1 if remainDelay == 0 { hide() } self.perform(#selector(self.showSplash), with: nil, afterDelay: 1.0) } func getSplashURLString()->String { //according to screen dimension let imageString = self.splashInfo["img"] as? String ?? "" return imageString } func hide(){ if shouldHidden { shouldHidden = false attachRootViewController() splashWindow = nil if let completion = self.completion { completion() } } } } var originalRootViewController:UIViewController? var splashWindow:UIWindow? extension QSSplashScreen { func detachRootViewController(){ if originalRootViewController == nil { let mainWindow:UIWindow? = (UIApplication.shared.delegate?.window)! originalRootViewController = mainWindow?.rootViewController } } func attachRootViewController(){ if originalRootViewController != nil { let mainWindow:UIWindow? = (UIApplication.shared.delegate?.window)! mainWindow?.rootViewController = originalRootViewController } } } class QSSplashViewController: UIViewController { private var bgView:UIImageView! private var splashIcon:UIImageView! private var skipBtn:UIButton! var finishCallback:SplashCallback? override func viewDidLoad() { super.viewDidLoad() setupSubviews() } private func setupSubviews(){ bgView = UIImageView(frame: UIScreen.main.bounds) bgView.image = UIImage(named: getImageName()) view.addSubview(bgView) splashIcon = UIImageView(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width , height: UIScreen.main.bounds.height - 100)) splashIcon.backgroundColor = UIColor.clear view.addSubview(splashIcon) skipBtn = UIButton(type: .custom) skipBtn.frame = CGRect(x: UIScreen.main.bounds.width - 80, y:UIScreen.main.bounds.height - 65, width: 60, height: 30) skipBtn.setTitle("跳过", for: .normal) skipBtn.setTitleColor(UIColor.gray, for: .normal) skipBtn.backgroundColor = UIColor(white: 0.0, alpha: 0.1) skipBtn.addTarget(self, action: #selector(skipAction(btn:)), for: .touchUpInside) skipBtn.layer.cornerRadius = 5 view.addSubview(skipBtn) } func getImageName()->String{ var imageName = "" if IPHONE4 { imageName = "Default" }else if IPHONE5 { imageName = "Default-640x1136" }else if IPHONE6 { imageName = "Default-750x1334" }else { imageName = "Default-1242x2208" } return imageName } override var preferredStatusBarStyle: UIStatusBarStyle{ return .lightContent } @objc private func skipAction(btn:UIButton){ if let callback = finishCallback { callback() } } func setSplashImage(image:UIImage){ splashIcon.image = image } } ================================================ FILE: zhuishushenqi/Splash/SplashViewController.swift ================================================ // // Copyright 2021 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // import UIKit class SplashViewController: UIViewController { /// Number of seconds remaining to show the app open ad. /// This simulates the time needed to load the app. var secondsRemaining: Int = 1 /// The countdown timer. var countdownTimer: Timer? override func viewDidLoad() { super.viewDidLoad() BUAdManager.shared.completion = { [weak self] in self?.stopTimer() self?.startMainScreen() } BUAdManager.shared.success = { [weak self] in self?.stopTimer() } BUAdManager.shared.startBUAdSDK(viewController: self) startTimer() } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) } @objc func decrementCounter() { secondsRemaining -= 1 if secondsRemaining > 0 { } else { countdownTimer?.invalidate() startMainScreen() } } func startTimer() { countdownTimer = Timer.scheduledTimer( timeInterval: 1.0, target: self, selector: #selector(SplashViewController.decrementCounter), userInfo: nil, repeats: true) } func stopTimer() { countdownTimer?.invalidate() countdownTimer = nil } func startMainScreen() { let mainStoryBoard = UIStoryboard(name: "Main", bundle: nil) let mainViewController = mainStoryBoard.instantiateViewController( withIdentifier: "MainStoryBoard") // Find the keyWindow which is currently being displayed on the device, // and set its rootViewController to mainViewController. let keyWindow = UIApplication.shared.windows.first(where: { $0.isKeyWindow }) keyWindow?.rootViewController = mainViewController } // MARK: AppOpenAdManagerDelegate func appOpenAdManagerAdDidComplete(_ appOpenAdManager: AppOpenAdManager) { startMainScreen() } } ================================================ FILE: zhuishushenqi/TXTReader/BookComment/Model/ZSBookCTLayoutModel.swift ================================================ // // ZSBookCTLayoutModel.swift // zhuishushenqi // // Created by caony on 2018/10/15. // Copyright © 2018 QS. All rights reserved. // import UIKit class ZSBookCTLayoutModel: NSObject { var nameLabelOriginY:CGFloat = 20 var nameLabelHeight:CGFloat = 20 var createLabelOriginY:CGFloat = 6 var createLabelHeight:CGFloat = 10 var titleLabelOriginY:CGFloat = 20 var titleLabelHeight:CGFloat = 30 var displayViewOriginY:CGFloat = 10 var displayViewHeight:CGFloat = 0 var bookBgViewOriginY:CGFloat = 20 var bookBgViewHeight:CGFloat = 74 var feelingViewOriginY:CGFloat = 0 var feelingViewHeight:CGFloat = 70 var totalHeight:CGFloat = 0 // depreted var contentHeight:CGFloat = 0 var titleHeight:CGFloat = 0 var config = CTFrameParserConfig() init(book:BookComment,data:CoreTextData) { super.init() // 废弃 contentHeight = data.height titleHeight = book.title.qs_height(13, width: ScreenWidth - 16) setupLayout(book: book, data: data) } func setupLayout(book:BookComment,data:CoreTextData) { // 开始计算高度 let titleTextHegiht = book.title.qs_height(UIFont.systemFont(ofSize: 18), width: UIScreen.main.bounds.width - 40) titleLabelHeight = titleTextHegiht displayViewHeight = data.height if book.book.cover == "" { bookBgViewHeight = 0 } totalHeight = nameLabelOriginY + nameLabelHeight + createLabelOriginY + createLabelHeight + titleLabelOriginY + titleLabelHeight + displayViewOriginY + displayViewHeight + bookBgViewOriginY + bookBgViewHeight + feelingViewOriginY + feelingViewHeight + 20 } } ================================================ FILE: zhuishushenqi/TXTReader/BookComment/Service/ZSBookCTService.swift ================================================ // // ZSBookCTService.swift // zhuishushenqi // // Created by caony on 2018/8/28. // Copyright © 2018年 QS. All rights reserved. // import Foundation class ZSBookCTService:ZSBaseService { func fetchCommentDetail(id:String,type:QSBookCommentType,handler:@escaping ZSBaseCallback){ let urlString = getDetailURL(id: id, type: type) zs_get(urlString) { (json) in if let review = json?["review"] as? [String:Any]{ if let model = BookComment.deserialize(from: review) { handler(model) } } } } func fetchCommentBest(id:String,handler:@escaping ZSBaseCallback<[BookCommentDetail]>){ let best = "\(BASEURL)/post/\(id)/comment/best" zs_get(best) { (json) in if let review = json?["comments"] as? [Any]{ if let model = [BookCommentDetail].deserialize(from: review) as? [BookCommentDetail] { handler(model) } } } } func fetchNormalMore(id:String,type:QSBookCommentType,start:Int,limit:Int,handler:@escaping ZSBaseCallback<[BookCommentDetail]>){ let comment = getCommentURL(id: id, type: type,start: start,limit: limit) zs_get(comment.0, parameters: comment.1) { (json) in if let review = json?["comments"] as? [Any]{ if let model = [BookCommentDetail].deserialize(from: review) as? [BookCommentDetail] { handler(model) } } } } func getCommentURL(id:String,type:QSBookCommentType,start:Int,limit:Int)->(String,[String:Any]){ var urlString = "" var param:[String:Any] = [:] switch type { case .normal: // http://api.zhuishushenqi.com/post/review/530a26522852d5280e04c19c/comment?start=0&limit=50 urlString = "\(BASEURL)/post/review/\(id)/comment" param = ["start":"\(start)","limit":"\(limit)"] break case .hotUser: // http://api.zhuishushenqi.com/user/twitter/58d14859d0693ae736034619/comments urlString = "\(BASEURL)/user/twitter/\(id)/comments" break case .hotPost: // http://api.zhuishushenqi.com/post/58d1d313bd7cc9961f93192d/comment?start=0&limit=50 urlString = "\(BASEURL)/post/\(id)/comment" param = ["start":"\(start)","limit":"\(limit)"] break } return (urlString,param) } func getDetailURL(id:String,type:QSBookCommentType)->String{ var urlString = "" switch type { case .normal: urlString = "\(BASEURL)/post/review/\(id)" break case .hotUser: urlString = "\(BASEURL)/user/twitter/\(id)" break case .hotPost: // http://api.zhuishushenqi.com/post/58d1d313bd7cc9961f93192d/comment?start=0&limit=50 urlString = "\(BASEURL)/post/\(id)" break } return urlString } } ================================================ FILE: zhuishushenqi/TXTReader/BookComment/Service/ZSBookCommentService.swift ================================================ // // ZSBookCommentService.swift // zhuishushenqi // // Created by yung on 2018/6/16. // Copyright © 2018年 QS. All rights reserved. // import Foundation import RxSwift import RxCocoa import RxAlamofire import Alamofire import HandyJSON final class ZSBookCommentService:ZSBaseService { func fetchCommentDetail(id:String,type:QSBookCommentType)->Observable{ let urlString = getDetailURL(id: id, type: type) return requestJSON(.get, urlString).observeOn(ConcurrentDispatchQueueScheduler(qos: .background)) .map({ (_,response) in if let reader = (response as AnyObject).object(forKey: "review") as? NSDictionary { if let model = BookComment.deserialize(from: reader ) { return model } } return BookComment() }) } func fetchCommentBest(id:String)->Observable<[BookCommentDetail]>{ let best = "\(BASEURL)/post/\(id)/comment/best" return requestJSON(.get, best) .observeOn(ConcurrentDispatchQueueScheduler(qos: .background)) .map({ (_,response) in if let books = (response as AnyObject).object(forKey: "comments") { if let models = [BookCommentDetail].deserialize(from: books as? [Any]) as? [BookCommentDetail] { return models } } return [BookCommentDetail]() }) } func fetchNormalMore(id:String,type:QSBookCommentType,start:Int,limit:Int)->Observable<[BookCommentDetail]>{ let comment = getCommentURL(id: id, type: type,start: start,limit: limit) return requestJSON(.get, comment.0, parameters: comment.1, encoding: URLEncoding.default, headers: nil) .observeOn(ConcurrentDispatchQueueScheduler(qos: .background)) .map({ (_,response) in if let books = (response as AnyObject).object(forKey: "comments") as? [Any]{ if let models = [BookCommentDetail].deserialize(from: books) as? [BookCommentDetail] { return models } } return [BookCommentDetail]() }) } func getCommentURL(id:String,type:QSBookCommentType,start:Int,limit:Int)->(String,[String:Any]){ var urlString = "" var param:[String:Any] = [:] switch type { case .normal: // http://api.zhuishushenqi.com/post/review/530a26522852d5280e04c19c/comment?start=0&limit=50 urlString = "\(BASEURL)/post/review/\(id)/comment" param = ["start":"\(start)","limit":"\(limit)"] break case .hotUser: // http://api.zhuishushenqi.com/user/twitter/58d14859d0693ae736034619/comments urlString = "\(BASEURL)/user/twitter/\(id)/comments" break case .hotPost: // http://api.zhuishushenqi.com/post/58d1d313bd7cc9961f93192d/comment?start=0&limit=50 urlString = "\(BASEURL)/post/\(id)/comment" param = ["start":"\(start)","limit":"\(limit)"] break } return (urlString,param) } func getDetailURL(id:String,type:QSBookCommentType)->String{ var urlString = "" switch type { case .normal: urlString = "\(BASEURL)/post/review/\(id)" break case .hotUser: urlString = "\(BASEURL)/user/twitter/\(id)" break case .hotPost: // http://api.zhuishushenqi.com/post/58d1d313bd7cc9961f93192d/comment?start=0&limit=50 urlString = "\(BASEURL)/post/\(id)" break } return urlString } } ================================================ FILE: zhuishushenqi/TXTReader/BookComment/View/BookCommentViewController.swift ================================================ // // BookCommentViewController.swift // zhuishushenqi // // Created by Nory Cao on 2017/3/13. // Copyright © 2017年 QS. All rights reserved. // import UIKit enum QSBookCommentType { case normal case hotUser case hotPost } class BookCommentViewController: BaseViewController,UITableViewDataSource,UITableViewDelegate ,Refreshable{ var id:String = "" var commentType:QSBookCommentType = .normal private var start:Int = 0 private var limit:Int = 1000 private var param:[String:Any]? fileprivate var magicComments:[BookCommentDetail]? = [BookCommentDetail]() fileprivate var normalComments:[BookCommentDetail]? = [BookCommentDetail]() fileprivate var readerModel:BookComment? fileprivate var hotModel:QSHotModel? fileprivate lazy var tableView:UITableView = { let tableView = UITableView(frame: CGRect(x: 0, y: 64, width: ScreenWidth, height: ScreenHeight - 64), style: .grouped) tableView.dataSource = self tableView.delegate = self tableView.sectionHeaderHeight = 60 tableView.sectionFooterHeight = CGFloat.leastNonzeroMagnitude tableView.estimatedRowHeight = 180 tableView.rowHeight = UITableView.automaticDimension tableView.qs_registerCellNib(BookCommentCell.self) tableView.qs_registerCellNib(UserfulCell.self) tableView.qs_registerCellNib(BookCommentViewCell.self) return tableView }() override func viewDidLoad() { super.viewDidLoad() initRefreshFooter(tableView) { self.start += self.limit self.requestMore() } title = "书评" let rightBtn = UIButton(type: .custom) rightBtn.addTarget(self, action: #selector(jump(btn:)), for: .touchUpInside) // rightBtn.setImage(UIImage(named:"actionbar_close"), for: .normal) rightBtn.setTitle("去底部", for: .normal) rightBtn.setTitleColor(UIColor.red, for: .normal) rightBtn.frame = CGRect(x: self.view.bounds.width - 75, y: 7, width: 60, height: 30) let rightBar = UIBarButtonItem(customView: rightBtn) self.navigationItem.rightBarButtonItem = rightBar requestDetail(idString: self.id) requestBest() requestMore() } @objc func jump(btn:UIButton){ var section = 2 var row = 0 if (self.magicComments?.count ?? 0) > 0 { section = 3 } if (self.normalComments?.count ?? 0) > 0 { row = (self.normalComments?.count ?? 1) - 1 } let indexPath = IndexPath(row: row, section: section) self.tableView.scrollToRow(at: indexPath, at: .bottom, animated: true) } fileprivate func requestDetail(idString:String){ let urlString = self.getDetailURL(type: commentType) zs_get(urlString, parameters: nil) { (response) in QSLog(response) if let reader = response?["review"] { self.readerModel = BookComment.deserialize(from: reader as! [String : Any]) } DispatchQueue.main.async { self.tableView.removeFromSuperview() self.view.addSubview(self.tableView) self.tableView.reloadData() } } } func requestBest(){ //http://api.zhuishushenqi.com/post/530a26522852d5280e04c19c/comment/best let best = "\(BASEURL)/post/\(self.id)/comment/best" zs_get(best, parameters: nil) { (response) in if let books = response?["comments"] { if let models = [BookCommentDetail].deserialize(from: books as? [Any]) as? [BookCommentDetail] { self.magicComments = models } } DispatchQueue.main.async { self.tableView.removeFromSuperview() self.view.addSubview(self.tableView) self.tableView.reloadData() } } } func requestMore(){ let comment = getCommentURL(type: self.commentType) zs_get(comment, parameters: nil) { (response) in if let books = response?["comments"] { if let normalComment = self.normalComments { if let models = [BookCommentDetail].deserialize(from: books as? [Any]) as? [BookCommentDetail] { self.normalComments = models + normalComment } }else{ if let models = [BookCommentDetail].deserialize(from: books as? [Any]) as? [BookCommentDetail] { self.normalComments = models } } } DispatchQueue.main.async { self.tableView.removeFromSuperview() self.view.addSubview(self.tableView) self.tableView.reloadData() // self.tableView.mj_header.endRefreshing() self.tableView.mj_footer.endRefreshing() } } } func getCommentURL(type:QSBookCommentType)->String{ var urlString = "" switch type { case .normal: // http://api.zhuishushenqi.com/post/review/530a26522852d5280e04c19c/comment?start=0&limit=50 urlString = "\(BASEURL)/post/review/\(self.id)/comment" param = ["start":"\(start)","limit":"\(self.limit)"] break case .hotUser: // http://api.zhuishushenqi.com/user/twitter/58d14859d0693ae736034619/comments urlString = "\(BASEURL)/user/twitter/\(self.id)/comments" param = nil break case .hotPost: // http://api.zhuishushenqi.com/post/58d1d313bd7cc9961f93192d/comment?start=0&limit=50 urlString = "\(BASEURL)/post/\(self.id)/comment" param = ["start":"\(start)","limit":"\(self.limit)"] break } return urlString } func getDetailURL(type:QSBookCommentType)->String{ var urlString = "" switch type { case .normal: urlString = "\(BASEURL)/post/review/\(self.id)" break case .hotUser: urlString = "\(BASEURL)/user/twitter/\(self.id)" break case .hotPost: // http://api.zhuishushenqi.com/post/58d1d313bd7cc9961f93192d/comment?start=0&limit=50 urlString = "\(BASEURL)/post/\(self.id)" break } return urlString } func numberOfSections(in tableView: UITableView) -> Int { var sections = 4 if (self.magicComments?.count ?? 0) == 0 { sections = 3 } if (self.normalComments?.count ?? 0) == 0 { sections = 2 } return sections } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { if section == 2 { return (self.magicComments?.count ?? 0) }else if section == 3{ return (self.normalComments?.count ?? 0) } return 1 } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = cellAt(indexPath: indexPath) return cell } func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { if indexPath.section == 0 { let height = BookCommentCell.totalCellHeight return height }else if indexPath.section == 1 { return 91 }else { let height:CGFloat = 0 if (magicComments?.count ?? 0) > 0 { if indexPath.section == 2 { // height = BookCommentViewCell.cellHeight(model: magicComments?[indexPath.row]) }else{ // height = BookCommentViewCell.cellHeight(model: normalComments?[indexPath.row]) } }else { // height = BookCommentViewCell.cellHeight(model: normalComments?[indexPath.row]) } return height } } func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { if section > 0 { return tableView.sectionHeaderHeight } return CGFloat.leastNonzeroMagnitude } func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { return tableView.sectionFooterHeight } func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { if section > 0 { let headerTitles = ["这个书评是否对你有用","仰望神评论","\(self.readerModel?.commentCount ?? 0)条评论"] let headerView = UIView() let headerLabel = UILabel() headerLabel.frame = CGRect(x: 15, y: 30, width: self.view.bounds.width - 30, height: 30) headerLabel.text = headerTitles[section - 1] headerLabel.textColor = UIColor.darkGray headerLabel.font = UIFont.systemFont(ofSize: 13) headerView.addSubview(headerLabel) return headerView } return nil } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } func cellAt(indexPath:IndexPath) -> UITableViewCell { if indexPath.section == 0{ let cell:BookCommentCell? = tableView.qs_dequeueReusableCell(BookCommentCell.self) cell?.backgroundColor = UIColor.white cell?.selectionStyle = .none cell?.contentView.layer.masksToBounds = true cell?.model = readerModel return cell! }else if indexPath.section == 1{ let cell:UserfulCell? = tableView.qs_dequeueReusableCell(UserfulCell.self) cell?.backgroundColor = UIColor.white cell?.selectionStyle = .none cell?.contentView.layer.masksToBounds = true cell?.model = readerModel return cell! }else { let cell:BookCommentViewCell? = tableView.qs_dequeueReusableCell(BookCommentViewCell.self) cell?.backgroundColor = UIColor.white cell?.selectionStyle = .none cell?.contentView.layer.masksToBounds = true let types = [CommentType.magical,CommentType.normal] cell?.type = .normal if (self.magicComments?.count ?? 0) > 0 { if indexPath.section == 2 { cell?.type = types[indexPath.section - 2] cell?.model = self.magicComments?[indexPath.row] }else{ cell?.model = self.normalComments?[indexPath.row] } }else{ cell?.model = self.normalComments?[indexPath.row] } return cell! } } } ================================================ FILE: zhuishushenqi/TXTReader/BookComment/View/QSBookCommentInteractor.swift ================================================ // // QSBookCommentInteractor.swift // zhuishushenqi // // Created yung on 2017/4/24. // Copyright © 2017年 QS. All rights reserved. // // Template generated by Juanpe Catalán @JuanpeCMiOS // import UIKit import ZSAPI class QSBookCommentInteractor: QSBookCommentInteractorProtocol { var output: QSBookCommentInteractorOutputProtocol? var model:BookComment! var hotComments:[BookCommentDetail] = [] var normalComments:[BookCommentDetail] = [] var start:Int = 0 var commentType:QSBookCommentType = .normal private var limit:Int = 1000 private var param:[String:Any]? fileprivate var readerModel:BookComment? fileprivate var hotModel:QSHotModel? func requestHot(){ let best = "\(BASEURL)/post/\(model._id)/comment/best" zs_get(best) { (response) in if let books = response?["comments"] { if let magicComments = [BookCommentDetail].deserialize(from: books as? [Any]) as? [BookCommentDetail] { self.hotComments = magicComments self.output?.fetchHotSuccess(hots: self.hotComments) } else { self.output?.fetchHotFailed() } }else{ self.output?.fetchHotFailed() } } } func requestNormal(){ let comment = getCommentURL(type: .normal) zs_get(comment, parameters: self.param) { (response) in if let books = response?["comments"] { if let models = [BookCommentDetail].deserialize(from: books as? [Any]) as? [BookCommentDetail] { self.normalComments.append(contentsOf: models) self.output?.fetchNormalSuccess(normals: self.normalComments) }else{ self.output?.fetchNormalFailed() } }else{ self.output?.fetchNormalFailed() } } start += 50 } func requestDetail(){ let urlString = self.getDetailURL(type: .normal) zs_get(urlString, parameters: nil) { (response) in QSLog(response) if let reader = response?["review"] { if let detail = BookComment.deserialize(from: reader as? NSDictionary) { self.output?.fetchDetailSuccess(detail: detail) }else{ self.output?.fetchDetailFailed() } }else{ self.output?.fetchDetailFailed() } } } func getCommentURL(type:QSBookCommentType)->String{ var urlString = "" switch type { case .normal: // http://api.zhuishushenqi.com/post/review/530a26522852d5280e04c19c/comment?start=0&limit=50 let api = ZSAPI.normalComment(key: model._id, start: "\(start)", limit: "\(limit)") urlString = api.path param = api.parameters break case .hotUser: // http://api.zhuishushenqi.com/user/twitter/58d14859d0693ae736034619/comments let api = ZSAPI.hotUser(key: model._id) urlString = api.path param = nil break case .hotPost: // http://api.zhuishushenqi.com/post/58d1d313bd7cc9961f93192d/comment?start=0&limit=50 let api = ZSAPI.hotPost(key: model._id, start: "\(start)", limit: "\(limit)") urlString = api.path param = api.parameters break } return urlString } func getDetailURL(type:QSBookCommentType)->String{ var urlString = "" switch type { case .normal: let api = ZSAPI.commentDetail(key: model._id) urlString = api.path break case .hotUser: urlString = "\(BASEURL)/user/twitter/\(model._id)" break case .hotPost: // http://api.zhuishushenqi.com/post/58d1d313bd7cc9961f93192d/comment?start=0&limit=50 urlString = "\(BASEURL)/post/\(model._id)" break } return urlString } } ================================================ FILE: zhuishushenqi/TXTReader/BookComment/View/QSBookCommentPresenter.swift ================================================ // // QSBookCommentPresenter.swift // zhuishushenqi // // Created yung on 2017/4/24. // Copyright © 2017年 QS. All rights reserved. // // Template generated by Juanpe Catalán @JuanpeCMiOS // import UIKit class QSBookCommentPresenter: QSBookCommentPresenterProtocol { weak var view: QSBookCommentViewProtocol? let interactor: QSBookCommentInteractorProtocol let router: QSBookCommentWireframeProtocol init(interface: QSBookCommentViewProtocol, interactor: QSBookCommentInteractorProtocol, router: QSBookCommentWireframeProtocol) { self.view = interface self.interactor = interactor self.router = router } func viewDidLoad() { interactor.requestDetail() interactor.requestHot() interactor.requestNormal() view?.showActivityView() } func requestMore() { view?.showActivityView() interactor.requestNormal() } } extension QSBookCommentPresenter:QSBookCommentInteractorOutputProtocol{ func fetchDetailSuccess(detail: BookComment) { view?.hideActivityView() view?.showDetail(detail: detail) } func fetchDetailFailed() { view?.hideActivityView() } func fetchHotSuccess(hots: [BookCommentDetail]) { view?.hideActivityView() view?.showHot(hots: hots) } func fetchHotFailed() { view?.hideActivityView() } func fetchNormalSuccess(normals: [BookCommentDetail]) { view?.hideActivityView() view?.showNormal(normals: normals) } func fetchNormalFailed() { view?.hideActivityView() } } ================================================ FILE: zhuishushenqi/TXTReader/BookComment/View/QSBookCommentProtocols.swift ================================================ // // QSBookCommentProtocols.swift // zhuishushenqi // // Created yung on 2017/4/24. // Copyright © 2017年 QS. All rights reserved. // // Template generated by Juanpe Catalán @JuanpeCMiOS // import Foundation //MARK: Wireframe - protocol QSBookCommentWireframeProtocol: class { } //MARK: Presenter - protocol QSBookCommentPresenterProtocol: class { func viewDidLoad() func requestMore() } //MARK: Interactor - protocol QSBookCommentInteractorProtocol: class { func requestHot() func requestNormal() func requestDetail() } //MARK: Output - protocol QSBookCommentInteractorOutputProtocol: class { func fetchDetailSuccess(detail:BookComment) func fetchDetailFailed() func fetchHotSuccess(hots:[BookCommentDetail]) func fetchHotFailed() func fetchNormalSuccess(normals:[BookCommentDetail]) func fetchNormalFailed() } //MARK: View - protocol QSBookCommentViewProtocol: IndicatableView { var presenter: QSBookCommentPresenterProtocol? { get set } func showDetail(detail:BookComment) func showHot(hots:[BookCommentDetail]) func showNormal(normals:[BookCommentDetail]) func showEmpty() } ================================================ FILE: zhuishushenqi/TXTReader/BookComment/View/QSBookCommentRouter.swift ================================================ // // QSBookCommentRouter.swift // zhuishushenqi // // Created yung on 2017/4/24. // Copyright © 2017年 QS. All rights reserved. // // Template generated by Juanpe Catalán @JuanpeCMiOS // import UIKit class QSBookCommentRouter: QSBookCommentWireframeProtocol { weak var viewController: UIViewController? static func createModule(model:BookComment) -> UIViewController { // Change to get view from storyboard if not using progammatic UI let view = QSBookCommentViewController(nibName: nil, bundle: nil) let interactor = QSBookCommentInteractor() let router = QSBookCommentRouter() let presenter = QSBookCommentPresenter(interface: view, interactor: interactor, router: router) view.presenter = presenter interactor.output = presenter router.viewController = view interactor.model = model return view } } ================================================ FILE: zhuishushenqi/TXTReader/BookComment/View/QSBookCommentViewController.swift ================================================ // // QSBookCommentViewController.swift // zhuishushenqi // // Created yung on 2017/4/24. // Copyright © 2017年 QS. All rights reserved. // // Template generated by Juanpe Catalán @JuanpeCMiOS // import UIKit import RxSwift import MJRefresh class ZSBookCommentViewController: ZSBaseTableViewController ,Refreshable{ var viewModel:ZSBookCTViewModel = ZSBookCTViewModel() var headerRefresh:MJRefreshHeader? var footerRefresh:MJRefreshFooter? var detailHeaderView:ZSReviewDetailView? var helpfulHeaderView:ZSBookCommentHelpfulHeaderView? var normalHeaderView:ZSBookCommentHelpfulHeaderView? var bestHeaderView:ZSBookCommentHelpfulHeaderView? var writeCommentButton:UIButton! var postView:ZSWriteReview! var disposeBag = DisposeBag() let helpfulHeaderViewTag = 11240 let normalCommentHeaderViewTag = 11241 let bestCommentHeaderViewTag = 11242 override init(style: UITableView.Style) { super.init(style: .grouped) title = "书评" } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func viewDidLoad() { super.viewDidLoad() setupSubviews() addObserver() } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) writeCommentButton.removeFromSuperview() } func addObserver(){ NotificationCenter.default.addObserver(self, selector: #selector(handleClick(noti:)), name: NSNotification.Name(CTDisplayViewLinkPressed), object: nil) // NotificationCenter.qs_addObserver(observer: self, selector: #selector(handleClick(noti:)), name: imagePressed, object: nil) // NotificationCenter.qs_addObserver(observer: self, selector: #selector(handleClick(noti:)), name: linkPressed, object: nil) } func setupSubviews() { tableView.sectionHeaderHeight = 60 tableView.sectionFooterHeight = CGFloat.leastNonzeroMagnitude tableView.estimatedRowHeight = 180 tableView.rowHeight = UITableView.automaticDimension detailHeaderView = ZSReviewDetailView(frame: CGRect(x: 0, y: 0, width: self.view.bounds.width, height: viewModel.layout?.totalHeight ?? 0)) detailHeaderView?.backgroundColor = UIColor.white detailHeaderView?.gotoBookHandler = { id in self.navigationController?.pushViewController(QSBookDetailRouter.createModule(id: id), animated: true) } helpfulHeaderView = ZSBookCommentHelpfulHeaderView(frame: CGRect(x: 0, y: 0, width: self.view.bounds.width, height: 60)) helpfulHeaderView?.titleLabel.text = "这个书评对你是否有用" normalHeaderView = ZSBookCommentHelpfulHeaderView(frame: CGRect(x: 0, y: 0, width: self.view.bounds.width, height: 60)) normalHeaderView?.titleLabel.text = "0条评论" bestHeaderView = ZSBookCommentHelpfulHeaderView(frame: CGRect(x: 0, y: 0, width: self.view.bounds.width, height: 60)) bestHeaderView?.titleLabel.text = "神评论" writeCommentButton = UIButton(type: .custom) writeCommentButton.frame = CGRect(x: 0, y: UIScreen.main.bounds.height - 50, width: self.view.bounds.width, height: 50) writeCommentButton.backgroundColor = UIColor.red writeCommentButton.setTitle("写评论", for: .normal) writeCommentButton.setTitleColor(UIColor.white, for: .normal) writeCommentButton.addTarget(self, action: #selector(writeComment(btn:)), for: .touchUpInside) writeCommentButton.isHidden = true KeyWindow?.addSubview(writeCommentButton) postView = ZSWriteReview(frame: UIScreen.main.bounds) postView.postHandler = { text in self.view.showProgress() self.viewModel.fetchPost(token: ZSLogin.share.token, content: text) { (json) in self.view.hideProgress() if json?["ok"] as? Bool == true { self.view.showTip(tip: "评论成功") } else { self.view.showTip(tip: "评论失败") } } } let header = initRefreshHeader(tableView) { self.viewModel.fetchCommentDetail(handler: { (detail) in self.tableView.reloadData() }) self.viewModel.fetchNewNormal(handler: { (normals) in self.tableView.reloadData() }) self.viewModel.fetchCommentBest(handler: { (best) in self.tableView.reloadData() }) } let footer = initRefreshFooter(tableView) { if let _ = self.viewModel.best { } else { self.viewModel.fetchCommentBest(handler: { (best) in if let _ = self.viewModel.best { self.tableView.reloadSection(2, with: .none) } }) } self.viewModel.fetchNormalMore(handler: { (more) in self.tableView.reloadData() }) } headerRefresh = header footerRefresh = footer headerRefresh?.beginRefreshing() viewModel.autoSetRefreshHeaderStatus(header: header, footer: footer).disposed(by: disposeBag) } @objc func writeComment(btn:UIButton) { if ZSLogin.share.hasLogin() { KeyWindow?.addSubview(postView) postView.textView.becomeFirstResponder() } else { self.view.showTip(tip: "请先登录") } } @objc func handleClick(noti:[String:Any]){ if let linkData = noti["linkData"] as? CoreTextLinkData { if let comment = viewModel.linkURL(linkData: linkData) as? BookComment { let commentVC = ZSBookCommentViewController(style: .grouped) commentVC.viewModel.model = comment self.navigationController?.pushViewController(commentVC, animated: true) } else if let id = viewModel.linkURL(linkData: linkData) as? String { self.navigationController?.pushViewController(QSTopicDetailRouter.createModule(id: id), animated: true) } else if linkData.linkTo == "search" { let searchVC = ZSSearchViewController(style: .grouped) searchVC.searchViewModel.keywords = linkData.key self.navigationController?.pushViewController(searchVC, animated: true) } } else if let imageData = noti["imageData"] as? CoreTextImageData { } } override func registerCellNibs() -> Array { return [BookCommentCell.self,UserfulCell.self,BookCommentViewCell.self] } //MARK: - UITableViewDataSource override func numberOfSections(in tableView: UITableView) -> Int { var sections = 0 if let detail = viewModel.detail { normalHeaderView?.titleLabel.text = "\(detail.commentCount)条评论" sections += 1 } if (viewModel.detail?.helpful.total ?? 0) != 0 { sections += 1 } if let best = viewModel.best { if best.count > 0 { sections += 1 } } if let normal = viewModel.normal { if normal.count > 0 { sections += 1 } } return sections } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { var sections = 0 if let _ = viewModel.detail { sections += 1 if section == (sections - 1) { return 0 } } if (viewModel.detail?.helpful.total ?? 0) != 0 { sections += 1 if section == (sections - 1) { return 1 } } if let best = viewModel.best { if best.count > 0 { sections += 1 } if section == (sections - 1) { return best.count } } if let normal = viewModel.normal { if normal.count > 0 { sections += 1 } if section == (sections - 1) { return normal.count } } return 0 } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { var sections = 0 if let _ = viewModel.detail { writeCommentButton.isHidden = false sections += 1 } if (viewModel.detail?.helpful.total ?? 0) != 0{ sections += 1 if indexPath.section == (sections - 1) { let cell:UserfulCell? = tableView.qs_dequeueReusableCell(UserfulCell.self) cell?.backgroundColor = UIColor.white cell?.selectionStyle = .none cell?.model = viewModel.detail return cell! } } if let best = viewModel.best { if best.count > 0 { sections += 1 } if indexPath.section == (sections - 1) { let cell:BookCommentViewCell? = tableView.qs_dequeueReusableCell(BookCommentViewCell.self) cell?.backgroundColor = UIColor.white cell?.selectionStyle = .none cell?.type = .magical cell?.bind(book: best[indexPath.row]) return cell! } } if let normal = viewModel.normal { if normal.count > 0 { sections += 1 } if indexPath.section == (sections - 1) { let cell:BookCommentViewCell? = tableView.qs_dequeueReusableCell(BookCommentViewCell.self) cell?.backgroundColor = UIColor.white cell?.selectionStyle = .none cell?.type = .normal cell?.bind(book: normal[indexPath.row]) return cell! } } return UITableViewCell(frame: CGRect.zero) } override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { if indexPath.section == 0 { if let _ = viewModel.detail { return viewModel.layout?.totalHeight ?? 0 } return 0 } else { if (viewModel.detail?.helpful.total ?? 0) != 0 { return 91 } } return UITableView.automaticDimension } override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { var sections = 0 if let detail = viewModel.detail { sections += 1 if section == (sections - 1) { if let data = viewModel.data { if viewModel.layout == nil { viewModel.layout = ZSBookCTLayoutModel(book: detail, data: data) } else { viewModel.layout?.setupLayout(book: detail, data: data) } detailHeaderView?.setupDetail(detail: detail, data: data) } return viewModel.layout?.totalHeight ?? 0 } } return tableView.sectionHeaderHeight } override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { var sections = 0 if let _ = viewModel.detail { sections += 1 if section == (sections - 1) { return detailHeaderView } } if (viewModel.detail?.helpful.total ?? 0) != 0 { sections += 1 if section == (sections - 1) { return helpfulHeaderView } } if let best = viewModel.best { if best.count > 0 { sections += 1 } if section == (sections - 1) { return bestHeaderView } } if let normal = viewModel.normal { if normal.count > 0 { sections += 1 } if section == (sections - 1) { return normalHeaderView } } return nil } } class ZSBookCommentHelpfulHeaderView:UIView { var titleLabel:UILabel! override init(frame: CGRect) { super.init(frame: frame) setupSubviews() } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } func setupSubviews() { titleLabel = UILabel() titleLabel.frame = CGRect(x: 15, y: 30, width: self.bounds.width - 30, height: 30) titleLabel.textColor = UIColor.darkGray titleLabel.font = UIFont.systemFont(ofSize: 13) addSubview(titleLabel) } } class QSBookCommentViewController: BaseViewController,UITableViewDataSource,UITableViewDelegate, QSBookCommentViewProtocol,Refreshable { var presenter: QSBookCommentPresenterProtocol? var model:BookComment? var hotComments:[BookCommentDetail]? = [BookCommentDetail]() var normalComments:[BookCommentDetail]? = [BookCommentDetail]() var detail:BookComment? fileprivate lazy var tableView:UITableView = { let tableView = UITableView(frame: CGRect(x: 0, y: 64, width: ScreenWidth, height: ScreenHeight - 64), style: .grouped) tableView.dataSource = self tableView.delegate = self tableView.sectionHeaderHeight = 60 tableView.sectionFooterHeight = CGFloat.leastNonzeroMagnitude tableView.estimatedRowHeight = 180 tableView.rowHeight = UITableView.automaticDimension tableView.qs_registerCellNib(BookCommentCell.self) tableView.qs_registerCellNib(UserfulCell.self) // tableView.qs_registerCellNib(BookCommentViewCell.self) tableView.qs_registerCellNib(BookCommentViewCell.self) return tableView }() override func viewDidLoad() { super.viewDidLoad() NotificationCenter.qs_addObserver(observer: self, selector: #selector(refreshCell(noti:)), name: "BookCellRefreshHeight", object: nil) initRefreshFooter(tableView) { self.presenter?.requestMore() } title = "精华书评" let rightBtn = UIButton(type: .custom) rightBtn.addTarget(self, action: #selector(jump(btn:)), for: .touchUpInside) // rightBtn.setImage(UIImage(named:"actionbar_close"), for: .normal) rightBtn.setTitle("去底部", for: .normal) rightBtn.setTitleColor(UIColor.red, for: .normal) rightBtn.frame = CGRect(x: self.view.bounds.width - 75, y: 7, width: 60, height: 30) let rightBar = UIBarButtonItem(customView: rightBtn) self.navigationItem.rightBarButtonItem = rightBar presenter?.viewDidLoad() view.addSubview(tableView) } @objc func refreshCell(noti:Notification){ self.tableView.reloadData() } @objc func jump(btn:UIButton){ var section = 2 var row = 0 if (self.hotComments?.count ?? 0) > 0 { section = 3 } if (self.normalComments?.count ?? 0) > 0 { row = (self.normalComments?.count ?? 1) - 1 } let indexPath = IndexPath(row: row, section: section) self.tableView.scrollToRow(at: indexPath, at: .bottom, animated: true) } func numberOfSections(in tableView: UITableView) -> Int { var sections = 4 if (self.hotComments?.count ?? 0) == 0 { sections = 3 } if (self.normalComments?.count ?? 0) == 0 { sections = 2 } return sections } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { if section == 2 { return (self.hotComments?.count ?? 0) }else if section == 3{ return (self.normalComments?.count ?? 0) } return 1 } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = cellAt(indexPath: indexPath) return cell } func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { if indexPath.section == 0 { let height = BookCommentCell.totalCellHeight return height }else if indexPath.section == 1 { return 91 }else { return UITableView.automaticDimension } } func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { if section > 0 { return tableView.sectionHeaderHeight } return CGFloat.leastNonzeroMagnitude } func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { return tableView.sectionFooterHeight } func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { // if section > 0 { // let headerTitles = ["这个书评是否对你有用","仰望神评论","\(self.detail?.commentCount ?? 0)条评论"] // let headerView = UIView() // let headerLabel = UILabel() // headerLabel.frame = CGRect(x: 15, y: 30, width: self.view.bounds.width - 30, height: 30) // headerLabel.text = headerTitles[section - 1] // headerLabel.textColor = UIColor.darkGray // headerLabel.font = UIFont.systemFont(ofSize: 13) // headerView.addSubview(headerLabel) // return headerView // } return nil } func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat { return 100 } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } func cellAt(indexPath:IndexPath) -> UITableViewCell { if indexPath.section == 0{ let cell:BookCommentCell? = tableView.qs_dequeueReusableCell(BookCommentCell.self) cell?.backgroundColor = UIColor.white cell?.selectionStyle = .none cell?.model = detail return cell! }else if indexPath.section == 1{ let cell:UserfulCell? = tableView.qs_dequeueReusableCell(UserfulCell.self) cell?.backgroundColor = UIColor.white cell?.selectionStyle = .none cell?.model = detail return cell! }else { // let cell:BookCommentViewCell? = tableView.qs_dequeueReusableCell(BookCommentViewCell.self) let cell:BookCommentViewCell? = tableView.qs_dequeueReusableCell(BookCommentViewCell.self) cell?.backgroundColor = UIColor.white cell?.selectionStyle = .none let types = [CommentType.magical,CommentType.normal] cell?.type = .normal if (self.hotComments?.count ?? 0) > 0 { if indexPath.section == 2 { cell?.type = types[indexPath.section - 2] if let model = self.hotComments?[indexPath.row]{ cell?.bind(book: model) } // cell?.model = self.hotComments?[indexPath.row] }else{ if let model = self.normalComments?[indexPath.row]{ cell?.bind(book: model) } // cell?.model = self.normalComments?[indexPath.row] } }else{ if let model = self.hotComments?[indexPath.row]{ cell?.bind(book: model) } // cell?.model = self.normalComments?[indexPath.row] } return cell! } } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { // let vc = UIStoryboard(name: "TXTReader", bundle: nil).instantiateInitialViewController() // present(vc!, animated: true, completion: nil) } func showDetail(detail: BookComment) { self.detail = detail self.tableView.reloadData() } func showHot(hots: [BookCommentDetail]) { self.hotComments = hots self.tableView.reloadData() } func showNormal(normals: [BookCommentDetail]) { self.normalComments = normals self.tableView.reloadData() } func showEmpty() { } } ================================================ FILE: zhuishushenqi/TXTReader/BookComment/View/TXTReader.storyboard ================================================ ================================================ FILE: zhuishushenqi/TXTReader/BookComment/View/ZSBestReviewView.swift ================================================ // // ZSBestReviewView.swift // zhuishushenqi // // Created by yung on 2018/10/27. // Copyright © 2018年 QS. All rights reserved. // import UIKit class ZSBestReviewView: UIView { var tableView:UITableView! var models:[BookCommentDetail]? override init(frame: CGRect) { super.init(frame: frame) setupSubviews() } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } private func setupSubviews() { tableView = UITableView(frame: self.bounds, style: .plain) tableView.dataSource = self tableView.delegate = self tableView.qs_registerCellNib(BookCommentViewCell.self) addSubview(tableView) } func bind(models:[BookCommentDetail]?) { if let _ = models { self.tableView.reloadData() } } } extension ZSBestReviewView:UITableViewDataSource, UITableViewDelegate { func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return models?.count ?? 0 } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.qs_dequeueReusableCell(BookCommentViewCell.self) if let details = self.models { cell?.bind(book: details[indexPath.row]) } return cell! } } ================================================ FILE: zhuishushenqi/TXTReader/BookComment/View/ZSBookReviewViewController.swift ================================================ // // ZSBookReviewViewController.swift // zhuishushenqi // // Created by yung on 2018/10/27. // Copyright © 2018年 QS. All rights reserved. // import UIKit import RxSwift import MJRefresh class ZSBookReviewDetailViewController: BaseViewController, UIScrollViewDelegate, Refreshable { var scrollView:UIScrollView! var detailView:ZSReviewDetailView! var bestReviewView:ZSBestReviewView! var normalReviewView:ZSBestReviewView! var viewModel:ZSBookCTViewModel = ZSBookCTViewModel() var headerRefresh:MJRefreshHeader? var footerRefresh:MJRefreshFooter? var disposeBag = DisposeBag() var layoutModel:ZSBookCTLayoutModel? override func viewDidLoad() { super.viewDidLoad() } private func setupSubviews() { scrollView = UIScrollView(frame: self.view.bounds) scrollView.delegate = self scrollView.backgroundColor = UIColor.white view.addSubview(scrollView) detailView = ZSReviewDetailView(frame: CGRect(x: 0, y: 0, width: self.view.bounds.width, height: 0)) scrollView.addSubview(detailView) bestReviewView = ZSBestReviewView(frame:CGRect(x: 0, y: self.detailView.frame.maxY, width: self.view.bounds.width, height: 0)) bestReviewView.backgroundColor = UIColor.red scrollView.addSubview(bestReviewView) normalReviewView = ZSBestReviewView(frame:CGRect(x: 0, y: self.bestReviewView.frame.maxY, width: self.view.bounds.width, height: 0)) normalReviewView.backgroundColor = UIColor.green scrollView.addSubview(normalReviewView) let header = initRefreshHeader(scrollView) { self.viewModel.fetchCommentDetail(handler: { (detail) in self.setupSubviews() self.zs_layoutSubviews() }) self.viewModel.fetchNewNormal(handler: { (normals) in self.normalReviewView.models = normals }) self.viewModel.fetchCommentBest(handler: { (best) in self.bestReviewView.models = best }) } let footer = initRefreshFooter(scrollView) { if let _ = self.viewModel.best { } else { self.viewModel.fetchCommentBest(handler: { (best) in if let _ = self.viewModel.best { } }) } self.viewModel.fetchNormalMore(handler: { (more) in }) } headerRefresh = header footerRefresh = footer headerRefresh?.beginRefreshing() viewModel.autoSetRefreshHeaderStatus(header: header, footer: footer).disposed(by: disposeBag) } func zs_layoutSubviews() { if let detail = viewModel.detail,let data = viewModel.data { if layoutModel == nil { layoutModel = ZSBookCTLayoutModel(book: detail, data: data) } else { layoutModel?.setupLayout(book: detail, data: data) } let height = layoutModel?.totalHeight ?? 0 detailView.height = height detailView.setupDetail(detail: detail, data: data) scrollView.contentSize = CGSize(width: self.view.bounds.width, height: height) } } } ================================================ FILE: zhuishushenqi/TXTReader/BookComment/View/ZSFeelingView.swift ================================================ // // ZSFeelingView.swift // zhuishushenqi // // Created by yung on 2018/10/27. // Copyright © 2018年 QS. All rights reserved. // import UIKit class ZSFeelingView: UIView { var approvalButton:QSToolButton! var shareButton:QSToolButton! var moreButton:QSToolButton! override init(frame: CGRect) { super.init(frame: frame) setupSubviews() } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } func setupSubviews() { approvalButton = QSToolButton(type: .custom) approvalButton.setTitle("同感", for: .normal) approvalButton.titleLabel?.font = UIFont.systemFont(ofSize: 13) approvalButton.setTitleColor(UIColor.gray, for: .normal) approvalButton.setImage(UIImage(named: "forum_like_icon"), for: .normal) approvalButton.frame = CGRect(x: 0, y: 0, width: self.bounds.width/3, height: self.bounds.height) addSubview(approvalButton) shareButton = QSToolButton(type: .custom) shareButton.setTitle("分享", for: .normal) shareButton.setTitleColor(UIColor.gray, for: .normal) shareButton.titleLabel?.font = UIFont.systemFont(ofSize: 13) shareButton.setImage(UIImage(named: "forum_share_icon"), for: .normal) shareButton.frame = CGRect(x: self.bounds.width/3, y: 0, width: self.bounds.width/3, height: self.bounds.height) addSubview(shareButton) moreButton = QSToolButton(type: .custom) moreButton.setTitle("更多", for: .normal) moreButton.setTitleColor(UIColor.gray, for: .normal) moreButton.titleLabel?.font = UIFont.systemFont(ofSize: 13) moreButton.setImage(UIImage(named: "forum_more_icon"), for: .normal) moreButton.frame = CGRect(x: self.bounds.width*2/3, y: 0, width: self.bounds.width/3, height: self.bounds.height) addSubview(moreButton) for index in 0..<2 { let horizonalLine = UILabel(frame: CGRect(x: (self.bounds.width/3 - 0.2)*(CGFloat(index) + 1.0), y: 10, width: 0.2, height: self.bounds.height - 20)) horizonalLine.backgroundColor = UIColor.gray horizonalLine.alpha = 0.6 addSubview(horizonalLine) } } } ================================================ FILE: zhuishushenqi/TXTReader/BookComment/View/ZSHottwitterViewController.swift ================================================ // // ZSHottwitterViewController.swift // zhuishushenqi // // Created by yung on 2018/6/18. // Copyright © 2018年 QS. All rights reserved. // import UIKit class ZSHottwitterViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() } } ================================================ FILE: zhuishushenqi/TXTReader/BookComment/View/ZSReviewDetailView.swift ================================================ // // ZSReviewDetailView.swift // zhuishushenqi // // Created by yung on 2018/10/27. // Copyright © 2018年 QS. All rights reserved. // import UIKit typealias ZSReviewDetailHandler = (_ id:String )->Void class ZSReviewDetailView: UIView { private var iconView:UIImageView! private var nameLabel:UILabel! private var createLabel:UILabel! private var titleLabel:UILabel! private var displayView:CTDisplayView! private var bookBgView:UIView! private var bookIconView:UIImageView! private var bookTitleLabel:UILabel! private var rateLabel:UILabel! private var rateStarView:RateView! private var gotoBookButton:UIButton! private var feelingView:ZSFeelingView! var detail:BookComment? var gotoBookHandler:ZSReviewDetailHandler? private var bookBgViewHeight:CGFloat = 74 private let defaultTime = "2014-02-23T16:48:18.179Z" override init(frame: CGRect) { super.init(frame: frame) setupSubviews() } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } func setupDetail(detail:BookComment, data:CoreTextData) { self.detail = detail let created = detail.created self.createLabel.qs_setCreateTime(createTime: created,append: "") nameLabel.text = "\(detail.author.nickname) lv.\(detail.author.lv)" titleLabel.text = "\(detail.title)" let titleTextHegiht = detail.title.qs_height(UIFont.systemFont(ofSize: 18), width: UIScreen.main.bounds.width - 40) titleLabel.height = titleTextHegiht bookTitleLabel.text = "\(detail.book.title)" if detail.book.cover != "" { let cover = "\(detail.book.cover)" self.bookIconView.qs_setBookCoverWithURLString(urlString: cover) rateStarView.rate = detail.rating bookBgViewHeight = 74 } else { bookBgViewHeight = 0 } displayView.data = data displayView.height = data.height let urlString = "\(detail.author.avatar)" self.iconView.qs_setAvatarWithURLString(urlString: urlString) bookTitleLabel.text = detail.book.title layoutSubview() } @objc private func gotoBookAction(btn:UIButton) { if let id = detail?.book._id { gotoBookHandler?(id) } } private func layoutSubview() { displayView.origin.y = self.titleLabel.frame.maxY + 10 bookBgView.origin.y = self.displayView.frame.maxY + 20 bookBgView.height = bookBgViewHeight if bookBgViewHeight == 0 { bookBgView.isHidden = true } feelingView.frame = CGRect(x: 20, y: self.bookBgView.frame.maxY, width: self.bounds.width - 40 , height: 70) } private func setupSubviews() { isUserInteractionEnabled = true iconView = UIImageView(frame: CGRect(x: 20, y: 20, width: 36, height: 36)) iconView.layer.cornerRadius = 5 addSubview(iconView) nameLabel = UILabel(frame: CGRect(x: self.iconView.frame.maxX + 10, y: 20, width: self.bounds.width - (self.iconView.frame.maxX + 10) - 20, height: 20)) nameLabel.textColor = UIColor(red: 0.82, green: 0.62, blue: 0.36, alpha: 1.0) nameLabel.font = UIFont.systemFont(ofSize: 17) nameLabel.textAlignment = .left addSubview(nameLabel) createLabel = UILabel(frame: CGRect(x: self.iconView.frame.maxX + 10, y: self.nameLabel.frame.maxY + 6, width: self.bounds.width - (self.iconView.frame.maxX + 10) - 20, height: 10)) createLabel.textColor = UIColor.gray createLabel.font = UIFont.systemFont(ofSize: 11) createLabel.textAlignment = .left addSubview(createLabel) titleLabel = UILabel(frame: CGRect(x: 20, y: self.iconView.frame.maxY + 20, width: self.bounds.width - 20*2, height: 30)) titleLabel.textColor = UIColor.black titleLabel.font = UIFont.systemFont(ofSize: 18) titleLabel.textAlignment = .left titleLabel.numberOfLines = 0 addSubview(titleLabel) displayView = CTDisplayView(frame: CGRect(x: 20, y: self.titleLabel.frame.maxY + 10, width: self.bounds.width - 40, height: 0)) displayView.isUserInteractionEnabled = true displayView.backgroundColor = UIColor.clear addSubview(displayView) bookBgView = UIView(frame: CGRect(x: 20, y: self.displayView.frame.maxY + 10, width: self.bounds.width - 40, height: bookBgViewHeight)) bookBgView.backgroundColor = UIColor(white: 0.9, alpha: 1.0) addSubview(bookBgView) bookIconView = UIImageView(frame: CGRect(x: 10, y: self.bookBgView.bounds.height/2 - 54/2, width: 36, height: 54)) bookBgView.addSubview(bookIconView) bookTitleLabel = UILabel(frame: CGRect(x: self.bookIconView.frame.maxX + 10, y: 10, width: self.bookBgView.bounds.width - (self.bookIconView.frame.maxX + 10) - 20, height: 27)) bookTitleLabel.textColor = UIColor.black bookTitleLabel.font = UIFont.systemFont(ofSize: 15) bookTitleLabel.textAlignment = .left bookBgView.addSubview(bookTitleLabel) rateLabel = UILabel(frame: CGRect(x: self.bookIconView.frame.maxX + 10, y: self.bookTitleLabel.frame.maxY, width: 60, height: 27)) rateLabel.textColor = UIColor.gray rateLabel.font = UIFont.systemFont(ofSize: 12) rateLabel.textAlignment = .left rateLabel.text = "楼主打分:" bookBgView.addSubview(rateLabel) gotoBookButton = UIButton(type: .custom) gotoBookButton.frame = CGRect(x: self.bookBgView.bounds.width - 36 - 20, y: bookBgViewHeight/2 - 36/2, width: 36, height: 36) gotoBookButton.setImage(UIImage(named: "forum_book_entry"), for: .normal) gotoBookButton.addTarget(self, action: #selector(gotoBookAction(btn:)), for: .touchUpInside) bookBgView.addSubview(gotoBookButton) let lightRect = CGRect(x: rateLabel.frame.maxX , y: rateLabel.frame.minY + rateLabel.frame.height/2 - 10/2, width: 60, height: 10) rateStarView = RateView(frame: lightRect, darkImage: UIImage(named: "forum_gray_star"), lightImage: UIImage(named: "forum_red_star")) bookBgView.addSubview(rateStarView) feelingView = ZSFeelingView(frame: CGRect(x: 20, y: self.bookBgView.frame.maxY, width: self.bounds.width - 40 , height: 70)) addSubview(feelingView) } } ================================================ FILE: zhuishushenqi/TXTReader/BookComment/View/ZSWriteReview.swift ================================================ // // ZSWriteReview.swift // zhuishushenqi // // Created by yung on 2018/11/7. // Copyright © 2018年 QS. All rights reserved. // import UIKit typealias ZSWriteReviewHandler = (_ text:String)->Void class ZSWriteReview: UIView { var text:String = "" var closeButton:UIButton! var postButton:UIButton! var titleLabel:UILabel! var textView:UITextView! var backgroundView:UIView! var containerView:UIView! var postHandler:ZSWriteReviewHandler? override init(frame: CGRect) { super.init(frame: frame) setupSubviews() } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } private func setupSubviews() { backgroundView = UIView(frame: UIScreen.main.bounds) backgroundView.backgroundColor = UIColor(white: 0.2, alpha: 0.6) addSubview(backgroundView) containerView = UIView(frame: CGRect(x: 0, y: UIScreen.main.bounds.height - 210, width: UIScreen.main.bounds.width, height: 210)) containerView.backgroundColor = UIColor.black addSubview(containerView) closeButton = UIButton(type: .custom) closeButton.frame = CGRect(x: 20, y: 20, width: 50, height: 30) closeButton.setImage(UIImage(named: "forum_comment_cancel"), for: .normal) closeButton.setImage(UIImage(named: "forum_comment_cancel_selected"), for: .highlighted) closeButton.addTarget(self, action: #selector(closeAction(btn:)), for: .touchUpInside) containerView.addSubview(closeButton) postButton = UIButton(type: .custom) postButton.frame = CGRect(x: UIScreen.main.bounds.width - 70, y: 20, width: 50, height: 30) postButton.setImage(UIImage(named: "forum_comment_confirm_selected"), for: .normal) postButton.setImage(UIImage(named: "forum_comment_confirm_unable"), for: .disabled) postButton.addTarget(self, action: #selector(postAction(btn:)), for: .touchUpInside) postButton.isEnabled = false containerView.addSubview(postButton) titleLabel = UILabel(frame: CGRect(x: 0, y: 20, width: UIScreen.main.bounds.width, height: 30)) titleLabel.textAlignment = .center titleLabel.font = UIFont.systemFont(ofSize: 17) titleLabel.text = "写评论" titleLabel.textColor = UIColor.white containerView.addSubview(titleLabel) textView = UITextView(frame: CGRect(x: 20, y: 70, width: UIScreen.main.bounds.width - 40, height: 120)) textView.font = UIFont.systemFont(ofSize: 15) textView.textColor = UIColor.black textView.layer.cornerRadius = 5 textView.layer.masksToBounds = true textView.delegate = self containerView.addSubview(textView) NotificationCenter.qs_addObserver(observer: self, selector: #selector(keyboardWillShow(noti:)), name: UIWindow.keyboardWillShowNotification.rawValue, object: nil) NotificationCenter.qs_addObserver(observer: self, selector: #selector(keyboardWillHide(noti:)), name: UIWindow.keyboardWillHideNotification.rawValue, object: nil) textView.becomeFirstResponder() } @objc private func closeAction(btn:UIButton) { textView.resignFirstResponder() self.removeFromSuperview() } @objc private func postAction(btn:UIButton) { textView.resignFirstResponder() removeFromSuperview() postHandler?(textView.text) } @objc private func keyboardWillShow(noti:Notification) { if let rectValue = noti.userInfo?[UIWindow.keyboardFrameEndUserInfoKey] as? CGRect { let keyboardHeight = rectValue.size.height UIView.animate(withDuration: 0.25) { self.containerView.frame = CGRect(x: 0, y: UIScreen.main.bounds.height - 210 - keyboardHeight, width: UIScreen.main.bounds.width, height: 210) } } } @objc private func keyboardWillHide(noti:Notification) { if let _ = noti.userInfo?[UIWindow.keyboardFrameEndUserInfoKey] as? CGRect { UIView.animate(withDuration: 0.25) { self.containerView.frame = CGRect(x: 0, y: UIScreen.main.bounds.height - 210 , width: UIScreen.main.bounds.width, height: 210) } } } override func touchesEnded(_ touches: Set, with event: UIEvent?) { textView.resignFirstResponder() } deinit { NotificationCenter.default.removeObserver(self) } } extension ZSWriteReview:UITextViewDelegate { func textViewDidChange(_ textView: UITextView) { if textView.text != "" { postButton.isEnabled = true } else { postButton.isEnabled = false } } } ================================================ FILE: zhuishushenqi/TXTReader/BookComment/ViewModel/ZSBookCTViewModel.swift ================================================ // // ZSBookCTViewModel.swift // zhuishushenqi // // Created by caony on 2018/8/28. // Copyright © 2018年 QS. All rights reserved. // import Foundation import RxSwift import ZSAPI class ZSBookCTViewModel:NSObject,ZSRefreshProtocol { var webService:ZSBookCTService = ZSBookCTService() var refreshStatus: Variable = Variable(.none) var model:BookComment? var detail:BookComment? var best:[BookCommentDetail]? var normal:[BookCommentDetail]? var start = 0 var limit = 50 var config = CTFrameParserConfig() var data:CoreTextData? var layout:ZSBookCTLayoutModel? func fetchCommentDetail(handler:@escaping ZSBaseCallback){ webService.fetchCommentDetail(id: model?._id ?? "", type: .normal) { (comment) in self.detail = comment self.parseData() handler(comment) } } func fetchCommentBest(handler:@escaping ZSBaseCallback<[BookCommentDetail]>){ webService.fetchCommentBest(id: model?._id ?? "") { (best) in self.best = best handler(best) } webService.fetchCommentBest(id: model?._id ?? "", handler: handler) } func fetchNewNormal(handler:@escaping ZSBaseCallback<[BookCommentDetail]>) { start = 0 webService.fetchNormalMore(id: model?._id ?? "", type: .normal, start: start, limit: limit) { (normals) in self.normal = normals self.refreshStatus.value = .headerRefreshEnd handler(normals) } } func fetchNormalMore(handler:@escaping ZSBaseCallback<[BookCommentDetail]>){ start += 50 webService.fetchNormalMore(id: model?._id ?? "", type: .normal, start: start, limit: limit) { (more) in self.refreshStatus.value = .footerRefreshEnd if let normals = more { if normals.count > 0 { self.normal?.append(contentsOf: normals) handler(normals) } } } } func fetchPost(token:String,content:String, handler:@escaping ZSBaseCallback<[String:Any]>) { let api = ZSAPI.reviewPost(token: ZSLogin.share.token, id: detail?._id ?? "", content: content) zs_post(api.path, parameters: api.parameters) { (json) in handler(json) } } func parseData() { if let detailModel = self.detail { config.width = ScreenWidth - 40 config.textColor = UIColor.gray config.textFont = UIFont.systemFont(ofSize: 15) if let data = CTFrameParser.parseString(detailModel.content, config: config) { let layout = ZSBookCTLayoutModel(book: detailModel, data: data) self.layout = layout self.data = data } } } func linkURL(linkData:CoreTextLinkData)->Any? { let linkTo = linkData.linkTo if linkTo == "post" { let comment = BookComment() comment._id = linkData.key return comment } else if linkTo == "booklist" { return linkData.key } return nil } } ================================================ FILE: zhuishushenqi/TXTReader/BookComment/ViewModel/ZSBookCommentViewModel.swift ================================================ // // ZSBookCommentViewModel.swift // zhuishushenqi // // Created by caonongyun on 2018/6/17. // Copyright © 2018年 QS. All rights reserved. // import Foundation import RxCocoa import RxSwift struct ZSBookCommentSection { var items: [BookCommentDetail] var comment:BookComment? var best:[BookCommentDetail]? } extension ZSBookCommentSection: SectionModelType{ init(original: ZSBookCommentSection, items: [BookCommentDetail]) { self = original self.items = items } typealias Item = BookCommentDetail } final class ZSBookCommentViewModel { // dataSource,监听 var section:Driver<[ZSBookCommentSection]>? // 首页的刷新命令,入参是当前的分类 let refreshCommand = ReplaySubject.create(bufferSize: 1) let requireMoreCommand = ReplaySubject.create(bufferSize: 1) fileprivate var bricks:BehaviorSubject<[BookCommentDetail]>? var refreshStatus: Variable = Variable(.none) fileprivate let disposeBag = DisposeBag() fileprivate var webService = ZSBookCommentService() init() { bricks = BehaviorSubject<[BookCommentDetail]>(value: []) section = bricks? .asObserver() .map({ (comment) -> [ZSBookCommentSection] in return [ZSBookCommentSection(items: comment, comment: BookComment(), best: comment)] }) .asDriver(onErrorJustReturn: []) refreshCommand.subscribe(onNext: { (event) in let detail = self.webService.fetchCommentDetail(id: "", type: .normal) let best = self.webService.fetchCommentBest(id: "") detail.asObservable().subscribe({ (event) in switch event { case let .error(error): QSLog(error) break case let .next(response): QSLog(response) break case .completed: break } }).disposed(by: self.disposeBag) best.asObservable().subscribe({ (event) in switch event { case let .error(error): QSLog(error) break case let .next(response): QSLog(response) break case .completed: break } }).disposed(by: self.disposeBag) }, onError: { (error) in }).disposed(by: disposeBag) } } ================================================ FILE: zhuishushenqi/TXTReader/BookDetail/BookDetailViewController.swift ================================================ // // BookDetailViewController.swift // zhuishushenqi // // Created by Nory Cao on 16/10/4. // Copyright © 2016年 QS. All rights reserved. // import UIKit class BookDetailViewController: BaseViewController,UITableViewDataSource,UITableViewDelegate { var id:String = "" var tagColor = [UIColor(red: 0.56, green: 0.77, blue: 0.94, alpha: 1.0), UIColor(red: 0.75, green: 0.41, blue: 0.82, alpha: 1.0), UIColor(red: 0.96, green: 0.74, blue: 0.49, alpha: 1.0), UIColor(red: 0.57, green: 0.81, blue: 0.84, alpha: 1.0), UIColor(red: 0.40, green: 0.80, blue: 0.72, alpha: 1.0), UIColor(red: 0.91, green: 0.56, blue: 0.56, alpha: 1.0), UIColor(red: 0.56, green: 0.77, blue: 0.94, alpha: 1.0), UIColor(red: 0.75, green: 0.41, blue: 0.82, alpha: 1.0)] fileprivate var sectionTwoY:CGFloat = 0 fileprivate var bookModel:BookDetail? fileprivate var hotComment:[QSHotComment]? let CONTENT_TAG = 11223 fileprivate var contentShow:Bool = false fileprivate lazy var tableView:UITableView = { let tableView = UITableView(frame: CGRect(x: 0, y: 64, width: ScreenWidth, height: ScreenHeight - 64), style: .grouped) tableView.dataSource = self tableView.delegate = self tableView.separatorStyle = .singleLine tableView.qs_registerCellNib(HotCommentCell.self) tableView.backgroundColor = UIColor(red: 0.95, green: 0.95, blue: 0.95, alpha: 1.0) return tableView }() override func viewDidLoad() { super.viewDidLoad() initSubview() // requestData() } fileprivate func initSubview(){ let titleView = UIView(frame: CGRect(x: 0,y: 0,width: 120,height: 30)) let titleLabel = UILabel(frame: CGRect(x: 0,y: 0,width: 90,height: 30)) titleLabel.textAlignment = .center titleLabel.text = "书籍详情" let titleShare = UIImageView(image: UIImage(named: "bd_share")) let width = (titleLabel.text! as NSString).boundingRect(with: CGSize(width: CGFloat.greatestFiniteMagnitude, height: 30), options: .usesLineFragmentOrigin, attributes: [NSAttributedString.Key.font:UIFont.systemFont(ofSize: 13)], context: nil) titleShare.frame = CGRect(x: width.size.width/2 + 120/2, y: 5, width: 20, height: 20) let ges = UITapGestureRecognizer(target: self, action: #selector(shareAction(_:))) titleShare.addGestureRecognizer(ges) titleView.addSubview(titleShare) titleView.addSubview(titleLabel) navigationItem.titleView = titleView let rightItem = UIBarButtonItem(title: "全本缓存", style: .plain, target: self, action: #selector(allCache(_:))) navigationItem.rightBarButtonItem = rightItem } @objc fileprivate func allCache(_ item:UIBarButtonItem){ } @objc fileprivate func shareAction(_ tap:UITapGestureRecognizer){ } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { if section == 0 { return 3 }else if section == 1{ return self.hotComment?.count ?? 0 }else if section == 5{ return 5 } return 1 } func numberOfSections(in tableView: UITableView) -> Int { return 2 } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let iden = "CellIden" var cell = tableView.dequeueReusableCell(withIdentifier: iden) if cell == nil { cell = UITableViewCell(style: .default, reuseIdentifier: iden) cell?.backgroundColor = UIColor.clear cell?.selectionStyle = .none } if indexPath.section == 0 { if indexPath.row == 0 { cell = sectionOne() return cell! }else if indexPath.row == 1{ cell = sectionTwo() return cell! }else { cell = sectionThree() return cell! } }else if indexPath.section == 1{ let hotCell:HotCommentCell = tableView.dequeueReusableCell(withIdentifier: "HotCommentCell", for: indexPath) as! HotCommentCell // hotCell.model = self.hotComment?[indexPath.row] return hotCell } return cell! } func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { if section == 0 { let headerview = (UINib(nibName: "BookDetailHeader", bundle: nil).instantiate(withOwner: self, options: nil) as NSArray).object(at: 0) as? BookDetailHeader headerview?.model = bookModel headerview?.addBtnAction = { (isSelected:Bool,model:BookDetail) in //需要遍历删除 if isSelected == true { BookManager.shared.modifyBookshelf(book: model) }else{ BookManager.shared.deleteBook(book: model) } QSLog(NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)) } headerview?.startReading = {(isSelected:Bool,model:BookDetail) in let allChapterUrl = "\(BASEURL)/toc" self.requestAllChapters(withUrl: allChapterUrl,param:["view":"summary","book":model._id ]) } if headerview != nil { return headerview! } }else if section == 1 { let headerView = UIView() headerView.backgroundColor = UIColor.white let label = UILabel() label.text = "热门评论" label.font = UIFont.systemFont(ofSize: 13) label.textColor = UIColor.darkGray label.frame = CGRect(x: 15, y: 0, width: 100, height: 30) headerView.addSubview(label) let moreBtn = UIButton(type: .custom) moreBtn.frame = CGRect(x: self.view.bounds.width - 135, y: 0, width: 120, height: 30) moreBtn.setTitle("更多", for: .normal) moreBtn.setTitleColor(UIColor.darkGray, for: .normal) moreBtn.titleLabel?.font = UIFont.systemFont(ofSize: 13) moreBtn.addTarget(self, action: #selector(moreCommentAction(btn:)), for: .touchUpInside) moreBtn.contentHorizontalAlignment = .right headerView.addSubview(moreBtn) return headerView } return nil } func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { if section == 0 { return 165 }else if section == 1 { return 30 } return 10 } func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { return 0.001 } func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { if indexPath.section == 0 { if indexPath.row == 0 { return 80 }else if indexPath.row == 1{ return sectionTwoY }else{ if contentShow { let height:CGFloat = (self.bookModel?.longIntro ?? "").qs_height(15, width: ScreenWidth - 40) + 50 return height } return 89 } }else if indexPath.section == 1 { return 127 }else if indexPath.section == 2 { return 80 }else if indexPath.section == 3 { return 160 } else if indexPath.section == 4 { return 100 } return 60 } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) if indexPath.section == 0 { if indexPath.row == 2 { contentShow = !contentShow self.tableView.reloadData() } } if indexPath.section == 1 { let bookCommentVC = BookCommentViewController() bookCommentVC.id = self.hotComment?[indexPath.row]._id ?? "" self.navigationController?.pushViewController(bookCommentVC, animated: true) } } @objc func moreCommentAction(btn:UIButton){ } fileprivate func sectionOne()->UITableViewCell{ let cell = UITableViewCell(style: .default, reuseIdentifier: "cell") cell.selectionStyle = .none let text = ["追书人数","读者留存率","更新字数/天"] let sText = [bookModel?.latelyFollower ?? "0","\(bookModel?.retentionRatio ?? "0")%","\(bookModel?.serializeWordCount ?? "未知")"] let x:CGFloat = 0 let y:CGFloat = 20 let width = ScreenWidth/3 let height:CGFloat = 21.0 for index in 0..<3 { let label = UILabel(frame: CGRect(x: x + width*CGFloat(index),y: y,width: width,height: height)) label.text = text[index] label.textAlignment = .center label.textColor = UIColor.gray label.font = UIFont.systemFont(ofSize: 13) cell.contentView.addSubview(label) let slabel = UILabel(frame: CGRect(x: x + width*CGFloat(index),y: y + 21,width: width,height: height)) slabel.text = sText[index] slabel.textAlignment = .center slabel.textColor = UIColor.gray slabel.font = UIFont.systemFont(ofSize: 13) cell.contentView.addSubview(slabel) } return cell } fileprivate func sectionTwo()->UITableViewCell{ let cell = UITableViewCell(style: .default, reuseIdentifier: "cellTwo") cell.selectionStyle = .none var x:CGFloat = 20 var y:CGFloat = 10 let spacex:CGFloat = 10 let spacey:CGFloat = 10 let height:CGFloat = 30 for index in 0..<(bookModel?.tags?.count ?? 0) { let width = (bookModel!.tags![index] as! String).qs_width(UIFont.systemFont(ofSize: 15), height: 21) + 20 if x + width + 20 > ScreenWidth { x = 20 y = y + spacey + height } let btn = UIButton(type: .custom) btn.frame = CGRect(x: x, y: y, width: width, height: height) btn.setTitle(bookModel!.tags![index] as? String, for: UIControl.State()) btn.titleLabel?.font = UIFont.systemFont(ofSize: 15) btn.setTitleColor(UIColor.white, for: UIControl.State()) btn.backgroundColor = tagColor[index%tagColor.count] btn.layer.cornerRadius = 2 cell.contentView.addSubview(btn) x = x + width + spacex } sectionTwoY = y + height + 10 return cell } fileprivate func sectionThree()->UITableViewCell{ let cell = UITableViewCell(style: .default, reuseIdentifier: "cellTwo") cell.selectionStyle = .none let height = tableView(self.tableView, heightForRowAt: IndexPath(row: 2, section: 0)) let label = UILabel(frame: CGRect(x: 20,y: 10,width: ScreenWidth - 40,height: height)) if !contentShow { label.frame = CGRect(x: 20,y: 5,width: ScreenWidth - 40,height: height) } label.textAlignment = .left label.font = UIFont.systemFont(ofSize: 15) label.numberOfLines = 0 label.text = bookModel?.longIntro label.textColor = UIColor.black label.tag = CONTENT_TAG cell.contentView.addSubview(label) return cell } func requestAllChapters(withUrl url:String,param:[String:Any]){ //先查询书籍来源,根据来源返回的id再查询所有章节 zs_get(url, parameters: param) { (response) in if let _:NSDictionary = (response as? NSArray)?.object(at: 1) as? NSDictionary { self.present(QSTextRouter.createModule(bookDetail:self.bookModel!,callback: {(book:BookDetail) in }), animated: true, completion: nil) } } } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } } ================================================ FILE: zhuishushenqi/TXTReader/BookDetail/Models/Book.swift ================================================ // // Book.swift // zhuishushenqi // // Created by Nory Cao on 16/10/4. // Copyright © 2016年 QS. All rights reserved. // import UIKit import HandyJSON @objc(Book) class Book: NSObject,HandyJSON { var author:String? var title:String? var cat:String? var majorCate:String = "" var shortIntro:String? var retentionRatio:CGFloat = 0 var latelyFollower:CGFloat = 0 var cover:String? var _id:String? required override init() { } } ================================================ FILE: zhuishushenqi/TXTReader/BookDetail/Models/BookComment.swift ================================================ // // BookComment.swift // zhuishushenqi // // Created by Nory Cao on 2017/3/13. // Copyright © 2017年 QS. All rights reserved. // import UIKit import HandyJSON @objc(BookComment) class BookComment: NSObject,HandyJSON { var _id:String = "" var content:String = ""{ didSet{ calContentHeight() } } var rating:Int = 1 var title:String = "" { didSet{ calTitleHeight() } } var type:String = "" var likeCount:Int = 0 var state:String = "" var updated:String = "" var created:String = "" var commentCount:Int = 0 var shareLink:String = "" var id:String = "" var titleHeight:CGFloat = 0 var contentHeight:CGFloat = 0 var author:BookCommentAuthor = BookCommentAuthor() var helpful:Helpful = Helpful() var book:BookCommentBook = BookCommentBook() required override init() {} func calTitleHeight(){ DispatchQueue.global().async { let height = self.title.qs_height(13, width: UIScreen.main.bounds.width - 16) self.titleHeight = height } } func calContentHeight(){ DispatchQueue.global().async { let height = self.content.qs_height(13, width: UIScreen.main.bounds.width - 30) self.contentHeight = height } } } @objc(BookCommentAuthor) class BookCommentAuthor: NSObject,HandyJSON { var _id:String = "" var avatar:String = "" var nickname:String = "" var activityAvatar:String = "" var type:String = "" var lv:Int = 0 var gender:String = "" var rank:String = "" var created:String = "" var id:String = "" required override init() {} } @objc(BookCommentBook) class BookCommentBook: NSObject,HandyJSON { var _id:String = "id" var cover:String = "" var title:String = "" var id:String = "" var type = "" required override init() {} } ================================================ FILE: zhuishushenqi/TXTReader/BookDetail/Models/BookCommentDetail.swift ================================================ // // BookCommentDetail.swift // zhuishushenqi // // Created by Nory Cao on 2017/3/14. // Copyright © 2017年 QS. All rights reserved. // import UIKit import YYText import HandyJSON @objc(BookCommentDetail) class BookCommentDetail: NSObject,HandyJSON { var _id:String = "" var content:String = "" { didSet{ // let attri = NSMutableAttributedString(string: "") // let scale:CGFloat = 1.149 // var font:CGFloat = 12 // let version = Double(UIDevice.current.systemVersion) ?? 10.0 // if version >= 10.0 { // if content.characters.count < 80{ // font = font*scale // }else{ // font = font*1.05 // } // } // attri.append(NSMutableAttributedString(string: content,attributes: [NSAttributedString.Key.font:UIFont.systemFont(ofSize: font)])) // // let textContainer = YYTextContainer(size: CGSize(width: ScreenWidth - 65, height: 9999)) // self.textLayout = YYTextLayout(container: textContainer, text: attri) } } var author:BookCommentAuthor = BookCommentAuthor() var floor:Int = 0 { didSet{ let width = "\(floor)楼".qs_width(UIFont.systemFont(ofSize: 12), height: 21) floorWidth = width + 5 } } var replyAuthor:String = "" var likeCount:Int = 0 var created:String = "" var replyTo:ReplyTo = ReplyTo() var height:CGFloat = 0 var replyHeight:CGFloat = 0 var floorWidth:CGFloat = 0 var textLayout:YYTextLayout? required override init() {} } @objc(ReplyTo) class ReplyTo: NSObject,HandyJSON { var _id:String = "" var floor:Int = 0 var author:BookCommentAuthor = BookCommentAuthor() required override init() {} } ================================================ FILE: zhuishushenqi/TXTReader/BookDetail/Models/BookDetail.swift ================================================ // // BookDetail.swift // zhuishushenqi // // Created by Nory Cao on 16/10/4. // Copyright © 2016年 QS. All rights reserved. // import UIKit import HandyJSON import SQLite //let db = try? Connection("path/to/db.sqlite3") // //let users = Table("users") //let id = Expression("id") //let name = Expression("name") //let email = Expression("email") // //try? db?.run(users.create { t in // t.column(id, primaryKey: true) // t.column(name) // t.column(email, unique: true) //}) //// CREATE TABLE "users" ( //// "id" INTEGER PRIMARY KEY NOT NULL, //// "name" TEXT, //// "email" TEXT NOT NULL UNIQUE //// ) // //let insert = users.insert(name <- "Alice", email <- "alice@mac.com") //let rowid = try? db?.run(insert) //// INSERT INTO "users" ("name", "email") VALUES ('Alice', 'alice@mac.com') // //for user in try? db?.prepare(users) { // print("id: \(user[id]), name: \(user[name]), email: \(user[email])") // // id: 1, name: Optional("Alice"), email: alice@mac.com //} //// SELECT * FROM "users" // //let alice = users.filter(id == rowid) // //try? db.run(alice.update(email <- email.replace("mac.com", with: "me.com"))) //// UPDATE "users" SET "email" = replace("email", 'mac.com', 'me.com') //// WHERE ("id" = 1) // //try? db?.run(alice.delete()) //// DELETE FROM "users" WHERE ("id" = 1) // //try? db?.scalar(users.count) // 0 //// SELECT count(*) FROM "users" extension BookDetail:DBSaveProtocol { func db_save(){ let homePath = NSHomeDirectory() let dbPath = "\(homePath)/db.sqlite3" if let db = try? Connection(dbPath) { let detail = Table("BookDetail") let author = Expression("author") let cover = Expression("cover") let creater = Expression("creater") let longIntro = Expression("longIntro") let title = Expression("title") let cat = Expression("cat") let majorCate = Expression("majorCate") let minorCate = Expression("minorCate") let latelyFollower = Expression("latelyFollower") let retentionRatio = Expression("retentionRatio") let serializeWordCount = Expression("serializeWordCount") let wordCount = Expression("wordCount") let updated = Expression("updated") let tags = Expression("tags") let id = Expression("_id") let postCount = Expression("postCount") let copyright = Expression("copyright") let sourceIndex = Expression("sourceIndex") let record = Expression("record") let chapter = Expression("chapter") let page = Expression("page") let resources = Expression("resources") let chapters = Expression("chapters") let chaptersInfo = Expression("chaptersInfo") let isUpdated = Expression("isUpdated") let book = Expression("book") let updateInfo = Expression("updateInfo") _ = try? db.run(detail.create(temporary: false, ifNotExists: true, withoutRowid: true) { (t) in t.column(id, primaryKey: true) t.column(author) t.column(cover) t.column(creater) t.column(longIntro) t.column(title) t.column(cat) t.column(majorCate) t.column(minorCate) t.column(latelyFollower) t.column(retentionRatio) t.column(serializeWordCount) t.column(wordCount) t.column(updated) t.column(tags) t.column(postCount) t.column(copyright) t.column(sourceIndex) t.column(record) t.column(chapter) t.column(page) t.column(resources) t.column(chapters) t.column(chaptersInfo) t.column(isUpdated) t.column(book) t.column(updateInfo) }) } } } @objc(BookDetail) class BookDetail: NSObject,NSCoding ,HandyJSON{ var _id:String = "" var author:String = "" var cover:String = "" var creater:String = "" var longIntro:String = "" var title:String = "" var cat:String = "" var majorCate:String = "" var minorCate:String = "" var latelyFollower:String = "" var retentionRatio:String = "" var serializeWordCount:String = ""//每天更新字数 var wordCount:String = "" var updated:String = ""//更新时间 var tags:NSArray? var postCount:Int = 0 var copyright:String = "" var sourceIndex:Int = 1 //当前选择的源 // 阅读记录, var record:QSRecord? // 废弃 var chapter:Int = 0 //最后阅读的章节 var page:Int = 0 //最后阅读的页数 var resources:[ResourceModel]? var chapters:[NSDictionary]? // 新的代码完成后去掉 var chaptersInfo:[ZSChapterInfo]? // 代替chapters var isUpdated:Bool = false //是否存在更新,如果存在更新,进入书籍后修改状态 // book 一直存在,默认初始化,不保存任何章节 var book:QSBook! // 书架缓存状态 var bookCacheState:SwipeCellState = .none //更新信息 var updateInfo:BookShelf? required init?(coder aDecoder: NSCoder) { super.init() self._id = aDecoder.decodeObject(forKey: "_id") as? String ?? "" self.author = aDecoder.decodeObject(forKey: "author") as? String ?? "" self.cover = aDecoder.decodeObject(forKey: "cover") as? String ?? "" self.creater = aDecoder.decodeObject(forKey: "creater") as? String ?? "" self.longIntro = aDecoder.decodeObject(forKey: "longIntro") as? String ?? "" self.title = aDecoder.decodeObject(forKey: "title") as? String ?? "" self.cat = aDecoder.decodeObject(forKey: "cat") as? String ?? "" self.majorCate = aDecoder.decodeObject(forKey: "majorCate") as? String ?? "" self.minorCate = aDecoder.decodeObject(forKey: "minorCate") as? String ?? "" self.latelyFollower = aDecoder.decodeObject(forKey: "latelyFollower") as? String ?? "" self.retentionRatio = aDecoder.decodeObject(forKey: "retentionRatio") as? String ?? "" self.serializeWordCount = aDecoder.decodeObject(forKey: "serializeWordCount") as? String ?? "" self.wordCount = aDecoder.decodeObject(forKey: "wordCount") as? String ?? "" self.updated = aDecoder.decodeObject(forKey: "updated") as? String ?? "" self.tags = aDecoder.decodeObject(forKey: "tags") as? NSArray self.updateInfo = aDecoder.decodeObject(forKey: "updateInfo") as? BookShelf self.chapter = aDecoder.decodeInteger(forKey:"chapter") self.page = aDecoder.decodeInteger(forKey:"page") self.sourceIndex = aDecoder.decodeInteger(forKey:"sourceIndex") self.resources = aDecoder.decodeObject(forKey: "resources") as? [ResourceModel] self.chapters = aDecoder.decodeObject(forKey: "chapters") as? [NSDictionary] self.copyright = aDecoder.decodeObject(forKey: "copyright") as? String ?? "" self.postCount = aDecoder.decodeInteger(forKey: "postCount") self.isUpdated = aDecoder.decodeBool(forKey: "isUpdated") self.book = aDecoder.decodeObject(forKey: "book") as? QSBook self.record = aDecoder.decodeObject(forKey: "record") as? QSRecord self.chaptersInfo = aDecoder.decodeObject(forKey: "chaptersInfo") as? [ZSChapterInfo] self.bookCacheState = SwipeCellState.init(rawValue: aDecoder.decodeObject(forKey: "bookCacheState") as? String ?? "") ?? .none setupBook() } private func setupBook(){ if book == nil { book = QSBook() } } required override init() { super.init() setupBook() } func encode(with aCoder: NSCoder) { aCoder.encode(self._id, forKey: "_id") aCoder.encode(self.author, forKey: "author") aCoder.encode(self.cover, forKey: "cover") aCoder.encode(self.creater, forKey: "creater") aCoder.encode(self.longIntro, forKey: "longIntro") aCoder.encode(self.title, forKey: "title") aCoder.encode(self.cat, forKey: "cat") aCoder.encode(self.majorCate, forKey: "majorCate") aCoder.encode(self.minorCate, forKey: "minorCate") aCoder.encode(self.latelyFollower, forKey: "latelyFollower") aCoder.encode(self.retentionRatio, forKey: "retentionRatio") aCoder.encode(self.serializeWordCount, forKey: "serializeWordCount") aCoder.encode(self.wordCount, forKey: "wordCount") aCoder.encode(self.updated, forKey: "updated") aCoder.encode(self.tags, forKey: "tags") aCoder.encode(self.updateInfo, forKey: "updateInfo") aCoder.encode(self.chapter, forKey: "chapter") aCoder.encode(self.page, forKey: "page") aCoder.encode(self.sourceIndex, forKey: "sourceIndex") aCoder.encode(self.resources, forKey: "resources") aCoder.encode(self.chapters, forKey: "chapters") aCoder.encode(self.copyright, forKey: "copyright") aCoder.encode(self.postCount, forKey: "postCount") aCoder.encode(self.isUpdated, forKey: "isUpdated") aCoder.encode(self.book, forKey: "book") aCoder.encode(self.record, forKey: "record") aCoder.encode(self.chaptersInfo,forKey:"chaptersInfo") aCoder.encode(self.bookCacheState.rawValue, forKey: "bookCacheState") } } ================================================ FILE: zhuishushenqi/TXTReader/BookDetail/Models/BookShelf.json ================================================ //https://api.zhuishushenqi.com/v3/user/bookshelf/diff //post parameter { "books": "5b10fd1b5d144d1b68581805,5b3d9ea93da83a5661317542,5ac49176ba32357ab6eee0a8,5bc0575df7634e0d9cb4a945,5bc99e7a279e1b0da1c9d033,5aa25094db70a41e4ae382db,53671112aa662d1b1500158f,566a0609469c97325459f068,582062e13ceeccd3472e25bd,56453ca255c88f8f5ba36792", "token": "rPcCW1GGh1hFPnSRJDjkwjtS" } //result { "books": [{ "_id": "5b3d9ea93da83a5661317542", "author": "武陵年少时", "cover": "/agent/http%3A%2F%2Fimg.1391.com%2Fapi%2Fv1%2Fbookcenter%2Fcover%2F1%2F2666061%2F2666061_60168061004140809ffe57244218426a.jpg%2F", "title": "兴汉室", "buytype": 0, "allowMonthly": false, "allowVoucher": true, "hasCp": true, "referenceSource": "default", "updated": "2018-11-19T07:16:52.387Z", "chaptersCount": 355, "lastChapter": "第354章 当务之急", "created": "2018-11-07T02:38:11.681Z", "_le": false, "contentType": "txt", "superscript": "", "sizetype": -1, "_mm": false, "readRecord": {}, "advertRead": true, "_gg": false, "expired": 0, "_ss": false, "modifyTime": "2018-11-19T07:16:52.388Z" }, { "_id": "5bc0575df7634e0d9cb4a945", "author": "耳东大树", "cover": "/agent/http%3A%2F%2Fimg.1391.com%2Fapi%2Fv1%2Fbookcenter%2Fcover%2F1%2F3316557%2F3316557_7253fb8192ef4062868cab38b6280ee3.jpg%2F", "title": "穿越血色浪漫", "buytype": 0, "allowMonthly": false, "allowVoucher": true, "hasCp": true, "referenceSource": "default", "updated": "2018-11-19T14:54:22.784Z", "chaptersCount": 94, "lastChapter": "正文卷 第八十九章:七个和尚", "created": "2018-11-02T11:46:23.917Z", "_le": true, "contentType": "txt", "superscript": "", "sizetype": -1, "_mm": false, "readRecord": { "tocId": "5bc0575df7634e0d9cb4a947", "tocName": "zhuishuvip", "title": "正文卷 第一章:回到从前", "order": 0, "wordIndex": 0, "updated": "2018-11-08T03:17:19.352Z", "book": "5bc0575df7634e0d9cb4a945" }, "advertRead": true, "_gg": false, "expired": 0, "_ss": false, "modifyTime": "2018-11-19T14:54:22.784Z" }, { "_id": "5bc99e7a279e1b0da1c9d033", "author": "七十二七", "cover": "/agent/http%3A%2F%2Fimg.1391.com%2Fapi%2Fv1%2Fbookcenter%2Fcover%2F1%2F3320834%2F3320834_12bd79f8270a447da24e498ef63ff626.jpg%2F", "title": "从金黄市走出的训练家", "buytype": 0, "allowMonthly": false, "allowVoucher": true, "hasCp": true, "referenceSource": "default", "updated": "2018-11-19T02:04:22.369Z", "chaptersCount": 66, "lastChapter": "正文卷 第六十四章【请求,想法】", "created": "2018-10-28T05:24:58.141Z", "_le": true, "contentType": "txt", "superscript": "", "sizetype": -1, "_mm": false, "readRecord": {}, "advertRead": true, "_gg": false, "expired": 0, "_ss": false, "modifyTime": "2018-11-19T02:04:22.369Z" }, { "_id": "53671112aa662d1b1500158f", "author": "六如和尚", "cover": "/agent/http%3A%2F%2Fimg.1391.com%2Fapi%2Fv1%2Fbookcenter%2Fcover%2F1%2F682805%2F682805_ad98924f53e74138a50e69208bd579be.png%2F", "title": "偷香高手", "buytype": 0, "allowMonthly": true, "allowVoucher": true, "hasCp": true, "referenceSource": "sogou", "updated": "2018-11-19T08:55:53.443Z", "chaptersCount": 1930, "lastChapter": "第十卷 大宗师的黄昏 第1930章 灭顶之灾", "created": "2018-10-27T07:10:56.442Z", "_le": true, "contentType": "txt", "superscript": "", "sizetype": -1, "_mm": false, "readRecord": {}, "advertRead": true, "_gg": false, "expired": 0, "_ss": false, "modifyTime": "2018-11-19T08:55:53.443Z" }, { "_id": "582062e13ceeccd3472e25bd", "author": "榕之子", "cover": "/agent/http%3A%2F%2Fimg.1391.com%2Fapi%2Fv1%2Fbookcenter%2Fcover%2F1%2F1391972%2F1391972_9fa6b0bbdea0407c84a2414df247719b.jpg%2F", "title": "造车", "buytype": 2, "allowMonthly": false, "allowVoucher": true, "hasCp": true, "referenceSource": "default", "updated": "2018-11-19T14:27:52.687Z", "chaptersCount": 570, "lastChapter": "第四卷 第一百十九章 投资考评", "created": "2018-10-27T07:10:56.442Z", "_le": false, "contentType": "txt", "superscript": "", "sizetype": -1, "_mm": false, "readRecord": { "tocId": "586387bc7280161d1eabdbd2", "tocName": "zhuishuvip", "title": "第1章 天王山之战", "order": 0, "wordIndex": 0, "updated": "2018-11-06T06:51:48.573Z", "book": "582062e13ceeccd3472e25bd" }, "advertRead": true, "_gg": false, "expired": 0, "_ss": false, "modifyTime": "2018-11-19T14:27:52.687Z" }, { "_id": "56453ca255c88f8f5ba36792", "author": "堂皇的荒唐", "cover": "/agent/http%3A%2F%2Fimg.1391.com%2Fapi%2Fv1%2Fbookcenter%2Fcover%2F1%2F902768%2F902768_9bed914fcccd4b48b55f29e7057cb922.jpg%2F", "title": "重生之电子风云", "buytype": 2, "allowMonthly": false, "allowVoucher": true, "hasCp": true, "referenceSource": "default", "updated": "2018-11-16T12:20:52.277Z", "chaptersCount": 593, "lastChapter": "第六卷 第598章 贫富两重天", "created": "2018-10-27T07:10:56.442Z", "_le": false, "contentType": "txt", "superscript": "", "sizetype": -1, "_mm": false, "readRecord": {}, "advertRead": true, "_gg": false, "expired": 0, "_ss": false, "modifyTime": "2018-11-16T12:20:52.277Z" }, { "_id": "566a0609469c97325459f068", "author": "机器人布里茨", "cover": "/agent/http%3A%2F%2Fimg.1391.com%2Fapi%2Fv1%2Fbookcenter%2Fcover%2F1%2F856493%2F856493_6d3a8091bc294345abeed36e26c002a4.jpg%2F", "title": "英雄联盟之决胜巅峰", "buytype": 2, "allowMonthly": false, "allowVoucher": true, "hasCp": true, "referenceSource": "default", "updated": "2018-11-19T09:26:52.699Z", "chaptersCount": 2634, "lastChapter": "第一千八百二十七章 拭目以待", "created": "2018-08-19T06:38:45.490Z", "_le": false, "contentType": "txt", "superscript": "", "sizetype": -1, "_mm": false, "readRecord": {}, "advertRead": true, "_gg": false, "expired": 0, "_ss": false, "modifyTime": "2018-11-19T09:26:52.699Z" }, { "_id": "5b10fd1b5d144d1b68581805", "author": "林海听涛", "cover": "/agent/http%3A%2F%2Fimg.1391.com%2Fapi%2Fv1%2Fbookcenter%2Fcover%2F1%2F2379309%2F2379309_eccf51f45dc14c079be9a399120b6da7.jpg%2F", "title": "绿茵峥嵘", "buytype": 0, "allowMonthly": false, "allowVoucher": true, "hasCp": true, "referenceSource": "default", "updated": "2018-11-20T00:16:22.491Z", "chaptersCount": 327, "lastChapter": "正文卷 第三百二十五章 回敬他们", "created": "2018-08-14T03:24:47.173Z", "_le": false, "contentType": "txt", "superscript": "", "sizetype": -1, "_mm": false, "readRecord": { "tocId": "5b10fd1b5d144d1b68581807", "tocName": "vip.zhuishushenqi.com", "title": "正文卷 第三百一十七章 破密集防守的第三种方法", "order": 318, "wordIndex": 8, "updated": "2018-11-18T10:18:48.631Z", "book": "5b10fd1b5d144d1b68581805" }, "advertRead": true, "_gg": false, "expired": 0, "_ss": false, "modifyTime": "2018-11-20T00:16:22.491Z" }, { "_id": "5aa25094db70a41e4ae382db", "author": "新海月1", "cover": "/agent/http%3A%2F%2Fimg.1391.com%2Fapi%2Fv1%2Fbookcenter%2Fcover%2F1%2F2243833%2F2243833_4a67e416d2e1485987ed9825bc347a4a_default_cover.png%2F", "title": "保加利亚帝国", "buytype": 0, "allowMonthly": false, "allowVoucher": true, "hasCp": true, "referenceSource": "default", "updated": "2018-11-19T13:59:22.613Z", "chaptersCount": 499, "lastChapter": "第六卷、新的时代 六十六章、同化——犹太人?", "created": "2018-08-14T03:24:47.173Z", "_le": true, "contentType": "txt", "superscript": "", "sizetype": -1, "_mm": false, "readRecord": { "tocId": "5acb131ee36bd0e811d270c5", "tocName": "mix", "title": "无畏舰时代 第五章、汽车工业的发展", "order": 268, "wordIndex": 4486, "updated": "2018-11-08T07:30:11.636Z", "book": "5aa25094db70a41e4ae382db" }, "advertRead": true, "_gg": false, "expired": 0, "_ss": false, "modifyTime": "2018-11-19T13:59:22.613Z" }, { "_id": "5ac49176ba32357ab6eee0a8", "author": "仲渊2", "cover": "/agent/http%3A%2F%2Fimg.1391.com%2Fapi%2Fv1%2Fbookcenter%2Fcover%2F1%2F2266163%2F2266163_7ffd0ca6a6544f5f90a557478d325ef3.jpg%2F", "title": "鹰掠九天", "buytype": 0, "allowMonthly": false, "allowVoucher": true, "hasCp": true, "referenceSource": "default", "updated": "2018-11-19T16:24:22.245Z", "chaptersCount": 737, "lastChapter": "正文卷 第七百四十七章 训练科目,电磁弹射!", "created": "2018-08-14T03:24:47.173Z", "_le": false, "contentType": "txt", "superscript": "", "sizetype": -1, "_mm": false, "readRecord": { "tocId": "5ac49176ba32357ab6eee0aa", "tocName": "zhuishuvip", "title": "第1章 生日和除夕。", "order": 0, "wordIndex": 0, "updated": "2018-11-02T16:20:33.620Z", "book": "5ac49176ba32357ab6eee0a8" }, "advertRead": true, "_gg": false, "expired": 0, "_ss": false, "modifyTime": "2018-11-19T16:24:22.245Z" }], "ok": true } ================================================ FILE: zhuishushenqi/TXTReader/BookDetail/Models/BookShelf.swift ================================================ // // BookShelf.swift // zhuishushenqi // // Created by Nory Cao on 16/10/5. // Copyright © 2016年 QS. All rights reserved. // import UIKit import HandyJSON @objc(BookShelf) class BookShelf: NSObject, HandyJSON, NSCoding { var _id:String? var title:String? var author:String? var cover:String? var allowMonthly:String? var allowVoucher:String? var updated:String? var chaptersCount:String? var lastChapter:String? var referenceSource:String? var readRecord:ZSReadRecord? var modifyTime:String? var created:String? var contentType:String? var superscript:String? var sizetype:String? required override init() {} func encode(with aCoder: NSCoder) { aCoder.encode(self._id, forKey: "_id") aCoder.encode(self.title, forKey: "title") aCoder.encode(self.author, forKey: "author") aCoder.encode(self.cover, forKey: "cover") aCoder.encode(self.allowMonthly, forKey: "allowMonthly") aCoder.encode(self.allowVoucher, forKey: "allowVoucher") aCoder.encode(self.updated, forKey: "updated") aCoder.encode(self.chaptersCount, forKey: "chaptersCount") aCoder.encode(self.lastChapter, forKey: "lastChapter") aCoder.encode(self.referenceSource, forKey: "referenceSource") aCoder.encode(self.readRecord, forKey: "readRecord") aCoder.encode(self.modifyTime, forKey: "modifyTime") aCoder.encode(self.created, forKey: "created") aCoder.encode(self.contentType, forKey: "contentType") aCoder.encode(self.superscript, forKey: "superscript") aCoder.encode(self.sizetype, forKey: "sizetype") } required init?(coder aDecoder: NSCoder) { self._id = aDecoder.decodeObject(forKey: "_id") as? String self.title = aDecoder.decodeObject(forKey: "title") as? String self.author = aDecoder.decodeObject(forKey: "author") as? String self.cover = aDecoder.decodeObject(forKey: "cover") as? String self.allowMonthly = aDecoder.decodeObject(forKey: "allowMonthly") as? String self.allowVoucher = aDecoder.decodeObject(forKey: "allowVoucher") as? String self.updated = aDecoder.decodeObject(forKey: "updated") as? String self.chaptersCount = aDecoder.decodeObject(forKey: "chaptersCount") as? String self.lastChapter = aDecoder.decodeObject(forKey: "lastChapter") as? String self.referenceSource = aDecoder.decodeObject(forKey: "referenceSource") as? String self.readRecord = aDecoder.decodeObject(forKey: "readRecord") as? ZSReadRecord self.modifyTime = aDecoder.decodeObject(forKey: "modifyTime") as? String self.created = aDecoder.decodeObject(forKey: "created") as? String self.contentType = aDecoder.decodeObject(forKey: "contentType") as? String self.superscript = aDecoder.decodeObject(forKey: "superscript") as? String self.sizetype = aDecoder.decodeObject(forKey: "sizetype") as? String } } //"_id": "5acf0f7c2eb0ad0dfb729d92", //"author": "毒心萝卜", //"cover": "/agent/http%3A%2F%2Fimg.1391.com%2Fapi%2Fv1%2Fbookcenter%2Fcover%2F1%2F2273645%2F2273645_5aa9d3087b88488286117e98d885c22a.jpg%2F", //"title": "业界大忽悠", //"buytype": 0, //"allowMonthly": false, //"allowVoucher": true, //"hasCp": true, //"referenceSource": "default", //"updated": "2018-10-20T10:11:01.000Z", //"chaptersCount": 266, //"lastChapter": "第266章 挑选功法", //"created": "2018-11-25T07:27:14.819Z", //"_le": false, //"contentType": "txt", //"superscript": "", //"sizetype": -1, //"_mm": false, //"readRecord": { // "tocId": "5acf0f7c2eb0ad0dfb729d94", // "tocName": "zhuishuvip", // "title": "第229章 广告代言", // "order": 228, // "wordIndex": 252, // "updated": "2018-11-28T01:46:42.585Z", // "book": "5acf0f7c2eb0ad0dfb729d92" //}, //"advertRead": true, //"_gg": false, //"expired": 0, //"_ss": false, //"modifyTime": "2018-12-01T05:38:25.084Z" ================================================ FILE: zhuishushenqi/TXTReader/BookDetail/Models/BookShelfInfo.swift ================================================ // // BokShelfInfo.swift // zhuishushenqi // // Created by Nory Cao on 2017/3/6. // Copyright © 2017年 QS. All rights reserved. // import UIKit class BookShelfInfo: NSObject { static let books = BookShelfInfo() private override init() { } let bookShelfInfo = "bookShelfInfo" let readHistoryKey = "readHistoryKey" let bookshelfSaveKey = "bookshelfSaveKey" var readHistory:[BookDetail]{ get{ var data:[BookDetail]? = [] let path = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).last?.appending("/\(readHistoryKey.md5())") if let filePath = path { let file:NSDictionary? = NSKeyedUnarchiver.unarchiveObject(withFile: filePath) as? NSDictionary data = file?[readHistoryKey] as? [BookDetail] } return data ?? [] } set{ let dict = [readHistoryKey:newValue] let path = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).last?.appending("/\(readHistoryKey.md5())") if let filePath = path { do { let url = URL(string: filePath) try FileManager.default.removeItem(at: url!) } catch _{} NSKeyedArchiver.archiveRootObject(dict, toFile: filePath) } } } public func delete(book:BookDetail) { } } ================================================ FILE: zhuishushenqi/TXTReader/BookDetail/Models/ChapterInfo.json ================================================ { "ok": true, "chapter": { "title": "第1章 :清华学子退学创业", "body": "\n\r\n\r\n\r请安装最新版追书 以便使用优质资源", "isVip": false, "cpContent": "第一章:清华学子退学创业\n\n  今天的清华学府,这座全国著名的高等学院来了一位世界知名的商界人物。实际上早在多年前他就来到这里演讲过,这个人就是当今国内最著名的一位商界教父之一,市值一度迈向三千亿美元的阿里巴巴商业帝国创始人——马云。\n\n  任鸿今年刚好十九岁,是一位自学自成的顶级学霸,目前为止已经在清华大学生活了一年半的时间,但在今天他做出了一个非常重要的决定,那就是准备退学创业,或者说停学。\n\n  因为他觉得学校已经不能教给他更多了,尽管他才刚刚步入大二。\n\n  “马云先生,请问......”\n\n  任鸿走向教务办公室的路上,正好目睹了这一幕,结束演讲后的马云刚好路过此地,数十个记者叽叽喳喳的围绕着这位商界教父不停的采访,边缘处学生驻留围观、记者的疯狂、保安的拥簇、马云的微笑,好不热闹。任鸿看着发生的这一切,他仍旧没有停下脚步,正巧的是,马云无意间的眼神刚好看到了任鸿,这位商界大佬露出了礼貌式地微笑,任鸿同样礼貌式地回应,没有所谓的激动、兴奋,平静的回应有的是一股自然而然的自信和从容。\n\n  对于马云再次来到清华学府演讲,演讲大厅自然汇聚了无数梦想创业的校园学子,不过任鸿却没有去。他很清楚,这位商界大佬所谓的分享和心得对他而言没有任何助益,张口闭口就是鸡汤文,这是毒药。\n\n  他成功不代表你能成功,任鸿佩服马云但绝不崇拜马云,他始终相信有一天自己也能创建一家享誉世界的伟大的科技公司。\n\n  双方的视线短暂的交汇后很快便分离,任鸿骑着一辆电动车朝计算机系教务办公室走去。\n\n  选择自主创业这条路也许很艰难,但他是如此的坚定,他始终相信总有一天,在那个大厅里,他所处的位置会在讲台上,而不是观众席。\n\n  花了几分钟的时间,终于来到计算机系教务办公室,门是开着的,任鸿来到门口轻轻地敲了几下。\n\n  “请进!”\n\n  听到里面传来的声音,任鸿平静的走了进去,没有任何紧张的情绪。\n\n  “余主任,我想要申请停学。”任鸿顿了顿,道出了自己的目的。\n\n  眼前的办公桌上坐着的是一位中年男子,他顿时抬起了头,匡扶了下眼镜,似乎要仔细看看这个学生。\n\n  “任鸿?”余主任显然认识这个学生,任鸿在计算机系成绩斐然,自然也让余主任印象深刻。\n\n  “你想要办停学申请肯定是可以的,学校尊重你的个人决定,不过好好的为什么要停学?”余主任并没有拒绝,尽管他对于这个出色的学生突然申请停学感到略微吃惊,不过对方是成年人,任鸿要停学自然由他自主决定。\n\n  “我准备出去创业。”任鸿坦然的说道。\n\n  “创业?”余主任讶异的多看了一眼任鸿,顿了顿,忽然笑道:“刚刚是不是去看了马云的演讲?”\n\n  “没有!”任鸿摇了摇头,道。\n\n  “额~”余主任惊讶的看着他,“我以为你去了,对于你们这种渴求创业的学子,你没有去看他的演讲倒是有点奇怪,我还以为你是中了他的鸡汤毒一时间冲昏了头脑。”余主任打趣的笑看着任鸿。\n\n  “我不喜欢成功学,那里只是属于他的舞台,跟我没有任何关系,也对我没有任何帮助,停学创业是我考虑了一个多月后做出的决定。”任鸿坦然的说道。\n\n  “你小子口气到不小。”余主任笑了笑,继而郑重的看着他,道:“学校尊重你的决定,但这可不是儿戏,你确定了吗?”\n\n  “我确定。”任鸿肯定的点点头。\n\n  办公室陷入了短暂的寂静,不一会儿,余主任起身离开椅子,翻出了一张表格后递给了任鸿,“那先填写这张表格吧!”\n\n  任鸿接过表格开始填写姓名学号,停学原因等。\n\n  “大学生创业是好事,也是坏事,你能考虑其中的轻重很重要。”余主任说道。\n\n  “谢谢主任,我会的。”任鸿回答。\n\n  余主任点点头,笑道:“可以告诉我准备做什么项目吗?”\n\n  任鸿填写表格非常迅速,递给余主任后,他想了想,道:“我准备创办一家科技公司,暂定为互联网公司吧,第一款产品已经快完善了。”\n\n  “哦?什么样的产品?”余主任接过表格浏览了一遍后,签上了自己的名字并盖章,同时好奇的说道。\n\n  说到自己的产品,任鸿立即充满了信心,不过他卖了个小关子,“嘿嘿,那个主任,我有一个提议您看可以不可以。”\n\n  “说来听听。”余主任笑道,没有急着拒绝。\n\n  “我查了一下,西操边的003号这栋楼基本成为了仓库,不过我发现有一处空置的教室,我想把他租下来用于公司的初始办公地址,北京的房租有点贵,您看......”任鸿干笑着说道。\n\n  余主任想了想,“003号这栋楼,的确如此,能够利用起来肯定是好事,我可以帮你这个忙,不过就事论事,几百平米这费用也不便宜,你的公司启动资金有多少?”\n\n  “50万美金。”任鸿想了想,说道。\n\n  这回余主任有些吃惊了,“50万美金?折合人民币有几百万了,你哪来这么多启动资金?我记得你家境出身一般啊。”\n\n  “是这样的,这笔资金是我前段时间帮助微软查找系统漏洞获得的报酬,这资金来源绝对没有问题。”任鸿连忙解释道。换做任何一个人,都会或多或少有所疑惑,他也对余主任的疑惑表示理解。余主任听这解释顿时露出了微笑,“好!到底是我的学生,你的计算机综合素质是我见过的最出色的学生,能替微软找漏洞我一点也不意外。现在我更加好奇你的第一款产品是什么了,看你遮遮掩掩的害怕我吃了不成。”\n\n  “这绝对是一项伟大的产品,请容许我暂时保密,很快您就能知道了。”任鸿信心满满的说道。\n\n  余主任重新把表格递给任鸿,笑道:“拿着表格去东操西边12号楼3层教务处318号房进行信息存档。一个星期后那个空置的教室就是你的公司办公室了,我会帮你搞定。就让我看看你的产品到底多伟大,先小小的期待一下!”\n\n  “谢谢主任!”任鸿欣喜的接过表格后便离开系教务办公室。\n\n  “年轻好啊,有朝气!”余主任看着门口消失的身影,失笑的摇了摇头,又继续工作。\n\n  任鸿拿着表格再度骑着电动车直奔东操西边的12号楼,来到318号房后,任鸿看了看门口上教务处的三个大字,随后便走了进去,“老师,这是停学手续。”任鸿把单子提给这个老师,对方点点头,并没有说话,只见他拿着单子在电脑上敲打着,不过这速度实在有点慢,任鸿只能耐心的等待。\n\n  最后,这个老师递给了任鸿另外一张单子,接过这张单子,上面的标题是“停学学生手续单”,任鸿道了一声谢谢后便离开318号房间,随后来到了楼下的213号房办理退货款手续,盖章过后,任鸿又来到了西操边上的图书馆“逸夫楼”二层办证处办理图书馆的还书手续。\n\n  停学手续很复杂,而且还要满学校到处跑,任鸿庆幸借来了一辆电动车,否则只能更慢,不过这些手续流程急不来,必须要一步一步搞定。\n\n  出了图书馆,任鸿来到的西边校医院,进入北边小楼的三层找到财务签字并注销公费医疗,又到挂号处左边的第一个窗口再签字,最后才能去校医院盖章。\n\n  接下来,任鸿奔向了主楼212房间办理了退网手续,搞定后又来到院系退还纸质学生证和校徽,最后退掉了学生卡,来到宿舍C楼找到楼长签字退舍并交出了宿舍钥匙。\n\n  终于,忙乎了一上午的时间,枯燥的停学流程手续步入尾声,再次回到12号楼上交表格后,任鸿领取了一张停学证明。\n\n  “拿到这张证件的这一刻起,你就暂时和学校脱离关系了,任鸿同学,去做你想做的事情吧,注意一定要在规定时间回来复学,否则就算是认作自离了。”这位老师不温不火的说道。\n\n  “谢谢老师,我会注意的。”任鸿拿到停学证明后,心中说不清的激动,终于可以做自己想做的事情了。\n\n  (新书求推荐收藏点击~~~)\n\n  (未完待续...)\n\n\n\n", "currency": 15, "id": "57df797d864946e5195ff067" } } ================================================ FILE: zhuishushenqi/TXTReader/BookDetail/Models/ChapterInfo.swift ================================================ // // ChapterInfo.swift // TXTReader // // Created by Nory Cao on 16/11/24. // Copyright © 2016年 QS. All rights reserved. // import UIKit import HandyJSON //@objc(ChapterInfo) class ChapterInfo : NSCoding,HandyJSON { //数据持久化标志,代表当前的章节,与小说对应章节可能不符 var currentIndex:Int = 0 //标题 var title:String = "" //附加信息 var body:String = "" { didSet{ body = self.trim(str: body) if body == "" { return } self.ranges = self.pageWithAttributes(attrubutes: self.attribute!, constrainedToSize: CGSize(width:UIScreen.main.bounds.size.width - 40,height: UIScreen.main.bounds.size.height - 40), string: body) as? [String] } } //正文 var cpContent:String? = "" { didSet{ cpContent = self.trim(str: cpContent!) if cpContent == "" { return } self.ranges = self.pageWithAttributes(attrubutes: self.attribute!, constrainedToSize: CGSize(width:UIScreen.main.bounds.size.width - 40,height: UIScreen.main.bounds.size.height - 40), string: cpContent!) as? [String] } } //来源 var resource:ResourceModel? //章节id,根据此id查询章节信息 var id:String = "" //花费 var currency:Int = 0 //当前章节的划分的范围,即每一页的范围 var ranges:[String]? = ["{0,0}"] //文字约束 var attribute:NSDictionary? = ["size": 20] { didSet{ //如果设置约束,则重新计算 self.ranges = self.pageWithAttributes(attrubutes: [NSAttributedString.Key.font:UIFont.systemFont(ofSize: 20)], constrainedToSize: CGSize(width:UIScreen.main.bounds.size.width - 40,height: UIScreen.main.bounds.size.height - 40), string: cpContent!) as? [String] } } func encode(with aCoder: NSCoder) { aCoder.encode(self.title,forKey:"title") aCoder.encode(self.body,forKey:"body") aCoder.encode(self.cpContent,forKey:"cpContent") aCoder.encode(self.id,forKey:"id") aCoder.encode(self.currency,forKey:"currency") aCoder.encode(self.ranges,forKey:"ranges") aCoder.encode(self.attribute,forKey:"attribute") } required init?(coder aDecoder: NSCoder) { self.title = aDecoder.decodeObject(forKey: "title") as! String self.body = aDecoder.decodeObject(forKey: "body") as! String self.cpContent = aDecoder.decodeObject(forKey: "cpContent") as? String self.id = aDecoder.decodeObject(forKey: "id") as! String self.currency = aDecoder.decodeObject(forKey: "currency") as! Int self.ranges = aDecoder.decodeObject(forKey: "ranges") as? [String] self.attribute = aDecoder.decodeObject(forKey: "attribute") as? NSDictionary } required init() { } private func pageWithAttributes(attrubutes:NSDictionary,constrainedToSize size:CGSize,string:String)->NSArray{ let resultRange = NSMutableArray(capacity: 5) let rect = CGRect(x:0,y: 0,width: size.width,height: size.height) let attributedString = NSAttributedString(string:string , attributes: attrubutes as? [NSAttributedString.Key: AnyObject]) let date = NSDate() var rangeIndex = 0 repeat{ let length = min(750, attributedString.length - rangeIndex) let childString = attributedString.attributedSubstring(from: NSMakeRange(rangeIndex, length)) let childFramesetter = CTFramesetterCreateWithAttributedString(childString) let bezierPath = UIBezierPath(rect: rect) let frame = CTFramesetterCreateFrame(childFramesetter, CFRangeMake(0, 0), bezierPath.cgPath, nil) let range = CTFrameGetVisibleStringRange(frame) let r:NSRange = NSMakeRange(rangeIndex, range.length) if r.length > 0 { resultRange.add(NSStringFromRange(r)) } rangeIndex += r.length }while (rangeIndex < attributedString.length && Int(attributedString.length) > 0 ) let millionSecond = NSDate().timeIntervalSince(date as Date) QSLog("耗时:\(millionSecond)") return resultRange } //数据持久化,存储到document目录,以章节序号 + id的md5为key class func updateLocalModel(localModel:ChapterInfo,id:String) -> Void { let key = "QSTXTReaderKeyAt\(localModel.currentIndex)\(id)".md5() let jsonString = localModel.toJSON() let filePath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first?.appending("/\(key )") NSKeyedArchiver.archiveRootObject(jsonString, toFile: filePath!) } class func localModelWithKey(key:String) ->ChapterInfo?{ let localKey = "QSTXTReaderKeyAt\(key)".md5() let filePath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first?.appending("/\(localKey )") var model:ChapterInfo? var file:[String:Any]? file = NSKeyedUnarchiver.unarchiveObject(withFile: filePath!) as? [String : Any] if let dict = file as? [String:Any]{ model = ChapterInfo.deserialize(from: dict) QSLog(model?.cpContent) } return model } //去掉章节开头跟结尾的多余的空格,防止产生空白页 func trim(str:String)->String{ var spaceStr:String = str spaceStr = spaceStr.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) return spaceStr } } ================================================ FILE: zhuishushenqi/TXTReader/BookDetail/Models/Chapters.json ================================================ { "_id": "57df797cb061df9e19b8b030", "name": "优质书源", "link": "http://vip.zhuishushenqi.com/toc/57df797cb061df9e19b8b030", "chapters": [ { "title": "第1章 :清华学子退学创业", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff067?cv=1474263421423", "id": "57df797d864946e5195ff067", "currency": 15, "unreadble": false, "isVip": false }, { "title": "第2章 :矩阵科技!改变世界!【修正】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff068?cv=1474263421423", "id": "57df797d864946e5195ff068", "currency": 15, "unreadble": false, "isVip": false }, { "title": "第3章 :首款产品", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff069?cv=1474263421423", "id": "57df797d864946e5195ff069", "currency": 15, "unreadble": false, "isVip": false }, { "title": "第4章 :估值350亿美元", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff06a?cv=1474263421423", "id": "57df797d864946e5195ff06a", "currency": 10, "unreadble": false, "isVip": false }, { "title": "第5章 :首款产品的推出", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff06b?cv=1474263421423", "id": "57df797d864946e5195ff06b", "currency": 15, "unreadble": false, "isVip": false }, { "title": "第6章 :火了", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff06c?cv=1474263421423", "id": "57df797d864946e5195ff06c", "currency": 10, "unreadble": false, "isVip": false }, { "title": "第7章 :风投机构", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff06d?cv=1474263421423", "id": "57df797d864946e5195ff06d", "currency": 10, "unreadble": false, "isVip": false }, { "title": "第8章 :惊现小札", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff06e?cv=1474263421423", "id": "57df797d864946e5195ff06e", "currency": 10, "unreadble": false, "isVip": false }, { "title": "第9章 :估值", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff06f?cv=1474263421423", "id": "57df797d864946e5195ff06f", "currency": 10, "unreadble": false, "isVip": false }, { "title": "第10章 :改变世界", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff070?cv=1474263421423", "id": "57df797d864946e5195ff070", "currency": 10, "unreadble": false, "isVip": false }, { "title": "第11章 :漫天要价", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff071?cv=1474263421423", "id": "57df797d864946e5195ff071", "currency": 15, "unreadble": false, "isVip": false }, { "title": "第12章 :合作愉快【修正】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff072?cv=1474263421423", "id": "57df797d864946e5195ff072", "currency": 15, "unreadble": false, "isVip": false }, { "title": "第13章 :战略合作", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff073?cv=1474263421423", "id": "57df797d864946e5195ff073", "currency": 10, "unreadble": false, "isVip": false }, { "title": "第14章 :天使合约【修正】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff074?cv=1474263421423", "id": "57df797d864946e5195ff074", "currency": 15, "unreadble": false, "isVip": false }, { "title": "第15章 :又来一名人【修正】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff075?cv=1474263421423", "id": "57df797d864946e5195ff075", "currency": 10, "unreadble": false, "isVip": false }, { "title": "第16章 :采访", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff076?cv=1474263421423", "id": "57df797d864946e5195ff076", "currency": 10, "unreadble": false, "isVip": false }, { "title": "第17章 :真的大火了【修正】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff077?cv=1474263421423", "id": "57df797d864946e5195ff077", "currency": 10, "unreadble": false, "isVip": false }, { "title": "第18章 :CEO【修正】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff078?cv=1474263421423", "id": "57df797d864946e5195ff078", "currency": 15, "unreadble": false, "isVip": false }, { "title": "第19章 :伟大的公司【修正】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff079?cv=1474263421423", "id": "57df797d864946e5195ff079", "currency": 10, "unreadble": false, "isVip": false }, { "title": "第20章 :Facebook新闻发布会", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff07a?cv=1474263421423", "id": "57df797d864946e5195ff07a", "currency": 15, "unreadble": false, "isVip": false }, { "title": "第21章 :头版头条", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff07b?cv=1474263421423", "id": "57df797d864946e5195ff07b", "currency": 10, "unreadble": false, "isVip": false }, { "title": "第22章 :捐赠", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff07c?cv=1474263421423", "id": "57df797d864946e5195ff07c", "currency": 10, "unreadble": false, "isVip": false }, { "title": "第23章 :初具成形的矩阵科技【修正】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff07d?cv=1474263421423", "id": "57df797d864946e5195ff07d", "currency": 10, "unreadble": false, "isVip": false }, { "title": "第24章 :XlouS公司总部", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff07e?cv=1474263421423", "id": "57df797d864946e5195ff07e", "currency": 10, "unreadble": false, "isVip": false }, { "title": "第25章 :发布会", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff07f?cv=1474263421423", "id": "57df797d864946e5195ff07f", "currency": 15, "unreadble": false, "isVip": false }, { "title": "第26章 :重回热度", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff080?cv=1474263421423", "id": "57df797d864946e5195ff080", "currency": 10, "unreadble": false, "isVip": false }, { "title": "第27章 :敲定", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff081?cv=1474263421423", "id": "57df797d864946e5195ff081", "currency": 10, "unreadble": false, "isVip": false }, { "title": "第28章 :瓜分肥单", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff082?cv=1474263421423", "id": "57df797d864946e5195ff082", "currency": 10, "unreadble": false, "isVip": false }, { "title": "第29章 :考察", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff083?cv=1474263421423", "id": "57df797d864946e5195ff083", "currency": 15, "unreadble": false, "isVip": false }, { "title": "第30章 :幻想者?", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff084?cv=1474263421423", "id": "57df797d864946e5195ff084", "currency": 10, "unreadble": false, "isVip": false }, { "title": "第31章 :任鸿的过去", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff085?cv=1474263421423", "id": "57df797d864946e5195ff085", "currency": 15, "unreadble": false, "isVip": false }, { "title": "第32章 :专属场域", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff086?cv=1474263421423", "id": "57df797d864946e5195ff086", "currency": 20, "unreadble": false, "isVip": false }, { "title": "第33章 :负面消息", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff087?cv=1474263421423", "id": "57df797d864946e5195ff087", "currency": 10, "unreadble": false, "isVip": false }, { "title": "第34章 :令人眼热的数据", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff088?cv=1474263421423", "id": "57df797d864946e5195ff088", "currency": 15, "unreadble": false, "isVip": false }, { "title": "第35章 :新总部", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff089?cv=1474263421423", "id": "57df797d864946e5195ff089", "currency": 10, "unreadble": false, "isVip": false }, { "title": "第36章 :惊喜的员工们", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff08a?cv=1474263421423", "id": "57df797d864946e5195ff08a", "currency": 10, "unreadble": false, "isVip": false }, { "title": "第37章 :可穿戴智能设备", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff08b?cv=1474263421423", "id": "57df797d864946e5195ff08b", "currency": 10, "unreadble": false, "isVip": false }, { "title": "第38章 :新闻发布会", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff08c?cv=1474263421423", "id": "57df797d864946e5195ff08c", "currency": 10, "unreadble": false, "isVip": false }, { "title": "第39章 :宣传片", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff08d?cv=1474263421423", "id": "57df797d864946e5195ff08d", "currency": 15, "unreadble": false, "isVip": false }, { "title": "第40章 :影响", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff08e?cv=1474263421423", "id": "57df797d864946e5195ff08e", "currency": 15, "unreadble": false, "isVip": false }, { "title": "第41章 :小樱入驻", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff08f?cv=1474263421423", "id": "57df797d864946e5195ff08f", "currency": 10, "unreadble": false, "isVip": false }, { "title": "第42章 :新产品发布会(一)", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff090?cv=1474263421423", "id": "57df797d864946e5195ff090", "currency": 10, "unreadble": false, "isVip": false }, { "title": "第43章 :新产品发布会(二)", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff091?cv=1474263421423", "id": "57df797d864946e5195ff091", "currency": 10, "unreadble": false, "isVip": false }, { "title": "第44章 :新产品发布会(三)", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff092?cv=1474263421423", "id": "57df797d864946e5195ff092", "currency": 15, "unreadble": false, "isVip": false }, { "title": "第45章 :新产品发布会(四)", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff093?cv=1474263421423", "id": "57df797d864946e5195ff093", "currency": 15, "unreadble": false, "isVip": false }, { "title": "第46章 :新产品发布会(五)", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff094?cv=1474263421423", "id": "57df797d864946e5195ff094", "currency": 15, "unreadble": false, "isVip": false }, { "title": "第47章 :新产品发布会(六)", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff095?cv=1474263421423", "id": "57df797d864946e5195ff095", "currency": 20, "unreadble": false, "isVip": false }, { "title": "第48章 :火爆销售", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff096?cv=1474263421423", "id": "57df797d864946e5195ff096", "currency": 15, "unreadble": false, "isVip": false }, { "title": "第49章 :神器", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff097?cv=1474263421423", "id": "57df797d864946e5195ff097", "currency": 10, "unreadble": false, "isVip": false }, { "title": "第50章 :断货", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff098?cv=1474263421423", "id": "57df797d864946e5195ff098", "currency": 10, "unreadble": false, "isVip": false }, { "title": "第51章 :领袖思维", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff099?cv=1474263421423", "id": "57df797d864946e5195ff099", "currency": 10, "unreadble": false, "isVip": false }, { "title": "第52章 :奇迹时代不奇迹", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff09a?cv=1474263421423", "id": "57df797d864946e5195ff09a", "currency": 20, "unreadble": false, "isVip": false }, { "title": "第53章 :史诗级豪华阵容", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff09b?cv=1474263421423", "id": "57df797d864946e5195ff09b", "currency": 15, "unreadble": false, "isVip": false }, { "title": "第54章 :所谓现实", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff09c?cv=1474263421423", "id": "57df797d864946e5195ff09c", "currency": 15, "unreadble": false, "isVip": false }, { "title": "第55章 :两份邀请", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff09d?cv=1474263421423", "id": "57df797d864946e5195ff09d", "currency": 10, "unreadble": false, "isVip": false }, { "title": "第56章 :聚会", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff09e?cv=1474263421423", "id": "57df797d864946e5195ff09e", "currency": 15, "unreadble": false, "isVip": false }, { "title": "第57章 :视野", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff09f?cv=1474263421423", "id": "57df797d864946e5195ff09f", "currency": 15, "unreadble": false, "isVip": false }, { "title": "第59章 :大佬云集", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff0a0?cv=1474263421423", "id": "57df797d864946e5195ff0a0", "currency": 10, "unreadble": false, "isVip": false }, { "title": "第60章 :开幕演讲", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff0a1?cv=1474263421423", "id": "57df797d864946e5195ff0a1", "currency": 20, "unreadble": false, "isVip": false }, { "title": "第61章 :DT经典案例", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff0a2?cv=1474263421423", "id": "57df797d864946e5195ff0a2", "currency": 10, "unreadble": false, "isVip": false }, { "title": "第62章 :聚餐", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff0a3?cv=1474263421423", "id": "57df797d864946e5195ff0a3", "currency": 15, "unreadble": false, "isVip": false }, { "title": "第63章 :深度对话", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff0a4?cv=1474263421423", "id": "57df797d864946e5195ff0a4", "currency": 20, "unreadble": false, "isVip": false }, { "title": "第64章 :潘多拉魔盒?", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff0a5?cv=1474263421423", "id": "57df797d864946e5195ff0a5", "currency": 15, "unreadble": false, "isVip": false }, { "title": "第65章 :重回母校", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff0a6?cv=1474263421424", "id": "57df797d864946e5195ff0a6", "currency": 15, "unreadble": false, "isVip": false }, { "title": "第66章 :何以为傲?", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff0a7?cv=1474263421424", "id": "57df797d864946e5195ff0a7", "currency": 15, "unreadble": false, "isVip": false }, { "title": "第68章 :潜藏在阴影中的视界", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff0a8?cv=1474263421424", "id": "57df797d864946e5195ff0a8", "currency": 15, "unreadble": false, "isVip": false }, { "title": "第69章 :无邪", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff0a9?cv=1474263421424", "id": "57df797d864946e5195ff0a9", "currency": 10, "unreadble": false, "isVip": false }, { "title": "第70章 :报应不爽", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff0aa?cv=1474263421424", "id": "57df797d864946e5195ff0aa", "currency": 15, "unreadble": false, "isVip": false }, { "title": "第71章 :麻烦来了?", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff0ab?cv=1474263421424", "id": "57df797d864946e5195ff0ab", "currency": 15, "unreadble": false, "isVip": false }, { "title": "第72章 :拒绝", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff0ac?cv=1474263421424", "id": "57df797d864946e5195ff0ac", "currency": 15, "unreadble": false, "isVip": false }, { "title": "第73章 :自信之源", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff0ad?cv=1474263421424", "id": "57df797d864946e5195ff0ad", "currency": 15, "unreadble": false, "isVip": false }, { "title": "第74章 :选择问题", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff0ae?cv=1474263421424", "id": "57df797d864946e5195ff0ae", "currency": 15, "unreadble": false, "isVip": false }, { "title": "第75章 :大跌眼镜(求推荐收藏点击)", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff0af?cv=1474263421424", "id": "57df797d864946e5195ff0af", "currency": 10, "unreadble": false, "isVip": false }, { "title": "第76章 :放弃国际市场", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff0b0?cv=1474263421424", "id": "57df797d864946e5195ff0b0", "currency": 10, "unreadble": false, "isVip": false }, { "title": "第77章 :分拆重组", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff0b1?cv=1474263421424", "id": "57df797d864946e5195ff0b1", "currency": 15, "unreadble": false, "isVip": false }, { "title": "第78章 :PV技术(求推荐收藏点击)", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff0b2?cv=1474263421424", "id": "57df797d864946e5195ff0b2", "currency": 10, "unreadble": false, "isVip": false }, { "title": "第79章 :量子语言和矩阵算法", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff0b3?cv=1474263421424", "id": "57df797d864946e5195ff0b3", "currency": 10, "unreadble": false, "isVip": false }, { "title": "第80章 :5000亿?", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff0b4?cv=1474263421424", "id": "57df797d864946e5195ff0b4", "currency": 15, "unreadble": false, "isVip": false }, { "title": "第81章 :全景视界下播放的《阿凡达》", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff0b5?cv=1474263421424", "id": "57df797d864946e5195ff0b5", "currency": 10, "unreadble": false, "isVip": false }, { "title": "第82章 :引领潮流", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff0b6?cv=1474263421424", "id": "57df797d864946e5195ff0b6", "currency": 15, "unreadble": false, "isVip": false }, { "title": "第83章 :震惊的卡梅隆", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff0b7?cv=1474263421424", "id": "57df797d864946e5195ff0b7", "currency": 10, "unreadble": false, "isVip": false }, { "title": "第84章 :跳票新境界", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff0b8?cv=1474263421424", "id": "57df797d864946e5195ff0b8", "currency": 10, "unreadble": false, "isVip": false }, { "title": "第85章 :皆为利往", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff0b9?cv=1474263421424", "id": "57df797d864946e5195ff0b9", "currency": 15, "unreadble": false, "isVip": false }, { "title": "第86章 :核心科技即王道", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff0ba?cv=1474263421424", "id": "57df797d864946e5195ff0ba", "currency": 10, "unreadble": false, "isVip": false }, { "title": "第87章 :第一批参观者", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff0bb?cv=1474263421424", "id": "57df797d864946e5195ff0bb", "currency": 15, "unreadble": false, "isVip": false }, { "title": "第88章 :震撼", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff0bc?cv=1474263421424", "id": "57df797d864946e5195ff0bc", "currency": 10, "unreadble": false, "isVip": false }, { "title": "第89章 :可怕在于颠覆", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff0bd?cv=1474263421424", "id": "57df797d864946e5195ff0bd", "currency": 10, "unreadble": false, "isVip": false }, { "title": "第90章 :消息外流", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff0be?cv=1474263421424", "id": "57df797d864946e5195ff0be", "currency": 15, "unreadble": false, "isVip": false }, { "title": "第91章 :电影产业大震荡?", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff0bf?cv=1474263421424", "id": "57df797d864946e5195ff0bf", "currency": 15, "unreadble": false, "isVip": false }, { "title": "第92章 :20亿美元订单(合并)", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff0c0?cv=1474263421424", "id": "57df797d864946e5195ff0c0", "currency": 20, "unreadble": false, "isVip": false }, { "title": "第93章 :创世", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff0c1?cv=1474263421424", "id": "57df797d864946e5195ff0c1", "currency": 10, "unreadble": false, "isVip": false }, { "title": "第94章 :邀请", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff0c2?cv=1474263421424", "id": "57df797d864946e5195ff0c2", "currency": 15, "unreadble": false, "isVip": false }, { "title": "第95章 :参加", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff0c3?cv=1474263421424", "id": "57df797d864946e5195ff0c3", "currency": 10, "unreadble": false, "isVip": false }, { "title": "第96章 :回家", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff0c4?cv=1474263421424", "id": "57df797d864946e5195ff0c4", "currency": 15, "unreadble": false, "isVip": false }, { "title": "第97章 :何为完整人生?", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff0c5?cv=1474263421424", "id": "57df797d864946e5195ff0c5", "currency": 10, "unreadble": false, "isVip": false }, { "title": "第98章 :安排", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff0c6?cv=1474263421424", "id": "57df797d864946e5195ff0c6", "currency": 10, "unreadble": false, "isVip": false }, { "title": "第99章 :前往(求三江票)", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff0c7?cv=1474263421424", "id": "57df797d864946e5195ff0c7", "currency": 10, "unreadble": false, "isVip": false }, { "title": "第100章 :毒蛇", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff0c8?cv=1474263421424", "id": "57df797d864946e5195ff0c8", "currency": 10, "unreadble": false, "isVip": false }, { "title": "第101章 :硅谷“钢铁侠”", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff0c9?cv=1474263421424", "id": "57df797d864946e5195ff0c9", "currency": 10, "unreadble": false, "isVip": false }, { "title": "第102章 :世博会“大PK”", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff0ca?cv=1474263421424", "id": "57df797d864946e5195ff0ca", "currency": 10, "unreadble": false, "isVip": false }, { "title": "第103章 :全景视觉冲击(一)", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff0cb?cv=1474263421424", "id": "57df797d864946e5195ff0cb", "currency": 15, "unreadble": false, "isVip": false }, { "title": "第104章 :全景视觉冲击(二)", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff0cc?cv=1474263421424", "id": "57df797d864946e5195ff0cc", "currency": 15, "unreadble": false, "isVip": false }, { "title": "第105章 :引领未来,改变世界!", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff0cd?cv=1474263421424", "id": "57df797d864946e5195ff0cd", "currency": 10, "unreadble": false, "isVip": false }, { "title": "第106章 :“玩游戏”和“玩命”", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff0ce?cv=1474263421424", "id": "57df797d864946e5195ff0ce", "currency": 10, "unreadble": false, "isVip": false }, { "title": "第107章 :B计划", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff0cf?cv=1474263421424", "id": "57df797d864946e5195ff0cf", "currency": 15, "unreadble": false, "isVip": false }, { "title": "第108章 :陷阱", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff0d0?cv=1474263421424", "id": "57df797d864946e5195ff0d0", "currency": 15, "unreadble": false, "isVip": false }, { "title": "第109章 :狂怒之举(打赏加更章)", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff0d1?cv=1474263421424", "id": "57df797d864946e5195ff0d1", "currency": 15, "unreadble": false, "isVip": false }, { "title": "第110章 :发射(打赏加更章节)", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff0d2?cv=1474263421424", "id": "57df797d864946e5195ff0d2", "currency": 15, "unreadble": false, "isVip": false }, { "title": "第111章 :达摩之剑", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff0d3?cv=1474263421424", "id": "57df797d864946e5195ff0d3", "currency": 15, "unreadble": false, "isVip": false }, { "title": "第112章 :影响发酵", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff0d4?cv=1474263421424", "id": "57df797d864946e5195ff0d4", "currency": 10, "unreadble": false, "isVip": false }, { "title": "第113章 :全球恐慌", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff0d5?cv=1474263421424", "id": "57df797d864946e5195ff0d5", "currency": 10, "unreadble": false, "isVip": false }, { "title": "第114章 :报复", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff0d6?cv=1474263421424", "id": "57df797d864946e5195ff0d6", "currency": 10, "unreadble": false, "isVip": false }, { "title": "第115章 :重塑价值", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff0d7?cv=1474263421424", "id": "57df797d864946e5195ff0d7", "currency": 10, "unreadble": false, "isVip": false }, { "title": "第116章 :领袖", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff0d8?cv=1474263421424", "id": "57df797d864946e5195ff0d8", "currency": 10, "unreadble": false, "isVip": false }, { "title": "第117章 :国之重器", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff0d9?cv=1474263421424", "id": "57df797d864946e5195ff0d9", "currency": 10, "unreadble": false, "isVip": false }, { "title": "第118章 :保还是不保?", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff0da?cv=1474263421424", "id": "57df797d864946e5195ff0da", "currency": 10, "unreadble": false, "isVip": false }, { "title": "第119章 :新规划", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff0db?cv=1474263421424", "id": "57df797d864946e5195ff0db", "currency": 10, "unreadble": false, "isVip": false }, { "title": "第120章 :国际形势", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff0dc?cv=1474263421424", "id": "57df797d864946e5195ff0dc", "currency": 15, "unreadble": false, "isVip": false }, { "title": "第121章 :以色列背锅", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff0dd?cv=1474263421424", "id": "57df797d864946e5195ff0dd", "currency": 15, "unreadble": false, "isVip": false }, { "title": "第122章 :98次失败", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff0de?cv=1474263421424", "id": "57df797d864946e5195ff0de", "currency": 10, "unreadble": false, "isVip": false }, { "title": "第123章 :成功", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff0df?cv=1478159602330", "id": "57df797d864946e5195ff0df", "currency": 10, "unreadble": false, "isVip": false }, { "title": "第124章 :复活术", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff0e0?cv=1478159602330", "id": "57df797d864946e5195ff0e0", "currency": 10, "unreadble": false, "isVip": false }, { "title": "第125章 :复活恐龙?", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff0e1?cv=1478159602330", "id": "57df797d864946e5195ff0e1", "currency": 10, "unreadble": false, "isVip": false }, { "title": "第126章 :造物主之力", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff0e2?cv=1478159602330", "id": "57df797d864946e5195ff0e2", "currency": 10, "unreadble": false, "isVip": false }, { "title": "第127章 :声音", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff0e3?cv=1478159602330", "id": "57df797d864946e5195ff0e3", "currency": 10, "unreadble": false, "isVip": false }, { "title": "第128章 上架感言", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff0e4?cv=1478159602330", "id": "57df797d864946e5195ff0e4", "currency": 10, "unreadble": false, "isVip": false }, { "title": "第129章 【:伟大的公司】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff0e5?cv=1478159602330", "id": "57df797d864946e5195ff0e5", "currency": 10, "unreadble": false, "isVip": false }, { "title": "第130章 【:任鸿现身】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff0e6?cv=1478159602330", "id": "57df797d864946e5195ff0e6", "currency": 15, "unreadble": false, "isVip": false }, { "title": "第131章 【:参观】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff0e7?cv=1474263421424", "id": "57df797d864946e5195ff0e7", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第132章 【:断肢重生】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff0e8?cv=1474263421424", "id": "57df797d864946e5195ff0e8", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第133章 【:生化部队?】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff0e9?cv=1474263421424", "id": "57df797d864946e5195ff0e9", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第134章 【:技术的正负影响】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff0ea?cv=1474263421424", "id": "57df797d864946e5195ff0ea", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第135章 【:恐龙公园设想】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff0eb?cv=1474263421424", "id": "57df797d864946e5195ff0eb", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第136章 【:理由】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff0ec?cv=1474263421424", "id": "57df797d864946e5195ff0ec", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第137章 【:公开演示 (一)】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff0ed?cv=1474263421424", "id": "57df797d864946e5195ff0ed", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第138章 【:公开演示(二)】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff0ee?cv=1474263421424", "id": "57df797d864946e5195ff0ee", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第139章 【:150岁生日】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff0ef?cv=1474263421424", "id": "57df797d864946e5195ff0ef", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第140章 【:一种恩赐】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff0f0?cv=1474263421424", "id": "57df797d864946e5195ff0f0", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第141章 【:影响】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff0f1?cv=1474263421424", "id": "57df797d864946e5195ff0f1", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第142章 【:伟大时刻】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff0f2?cv=1474263421424", "id": "57df797d864946e5195ff0f2", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第143章 【:爆炸性】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff0f3?cv=1474263421424", "id": "57df797d864946e5195ff0f3", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第144章 【:《时代》专访(上)】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff0f4?cv=1474263421424", "id": "57df797d864946e5195ff0f4", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第145章 【:《时代》专访(下)】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff0f5?cv=1474263421424", "id": "57df797d864946e5195ff0f5", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第146章 【:抢钱的矩阵科技】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff0f6?cv=1474263421424", "id": "57df797d864946e5195ff0f6", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第147章 【:再一次颠覆一个行业】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff0f7?cv=1474263421424", "id": "57df797d864946e5195ff0f7", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第148章 【:霍金之行】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff0f8?cv=1474263421424", "id": "57df797d864946e5195ff0f8", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第149章 【:“灵魂转嫁”】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff0f9?cv=1474263421424", "id": "57df797d864946e5195ff0f9", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第150章 【:分离大脑】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff0fa?cv=1474263421424", "id": "57df797d864946e5195ff0fa", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第151章 【:“组装”】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff0fb?cv=1474263421424", "id": "57df797d864946e5195ff0fb", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第152章 【:追随巨人的步伐】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff0fc?cv=1474263421424", "id": "57df797d864946e5195ff0fc", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第153章 【:“巨人”之间的握手】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff0fd?cv=1474263421424", "id": "57df797d864946e5195ff0fd", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第154章 【:延期】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff0fe?cv=1474263421424", "id": "57df797d864946e5195ff0fe", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第155章 【:恐龙计划】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff0ff?cv=1474263421424", "id": "57df797d864946e5195ff0ff", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第156章 【:进化论新验证法】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff100?cv=1474263421424", "id": "57df797d864946e5195ff100", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第157章 【:颠覆性的“基因”】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff101?cv=1474263421424", "id": "57df797d864946e5195ff101", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第158章 【:国家科学技术奖励大会】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff102?cv=1474263421424", "id": "57df797d864946e5195ff102", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第159章 【:实至名归】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff103?cv=1474263421424", "id": "57df797d864946e5195ff103", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第160章 【:暴走大事件专题】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff104?cv=1474263421424", "id": "57df797d864946e5195ff104", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第161章 【:啃下“苹果”】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff105?cv=1474263421424", "id": "57df797d864946e5195ff105", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第162章 【:出招与接招】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff106?cv=1474263421424", "id": "57df797d864946e5195ff106", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第163章 【:全民期待的“战争”】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff107?cv=1474263421424", "id": "57df797d864946e5195ff107", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第164章 【:iPhone9S亮相】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff108?cv=1474263421424", "id": "57df797d864946e5195ff108", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第165章 【:落差】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff109?cv=1474263421424", "id": "57df797d864946e5195ff109", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第166章 【:至少超过40亿人的知名度】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff10a?cv=1474263421424", "id": "57df797d864946e5195ff10a", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第167章 【:发布会(一)】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff10b?cv=1474263421424", "id": "57df797d864946e5195ff10b", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第168章 【:发布会(二)】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff10c?cv=1474263421424", "id": "57df797d864946e5195ff10c", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第169章 【:发布会(三)】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff10d?cv=1474263421424", "id": "57df797d864946e5195ff10d", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第170章 【:发布会(四)】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff10e?cv=1474263421424", "id": "57df797d864946e5195ff10e", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第171章 【:发布会(五)】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff10f?cv=1474263421424", "id": "57df797d864946e5195ff10f", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第172章 【:制裁?】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff110?cv=1474263421424", "id": "57df797d864946e5195ff110", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第173章 【:变革式节点】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff111?cv=1474263421424", "id": "57df797d864946e5195ff111", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第174章 【:人情】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff112?cv=1474263421424", "id": "57df797d864946e5195ff112", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第175章 【:多国会晤】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff113?cv=1474263421424", "id": "57df797d864946e5195ff113", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第176章 【:决议】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff114?cv=1474263421424", "id": "57df797d864946e5195ff114", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第177章 【:难眠之夜】(1_10)", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff115?cv=1474263421424", "id": "57df797d864946e5195ff115", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第178章 【:“吞金巨兽”】(2_10)", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff116?cv=1474263421424", "id": "57df797d864946e5195ff116", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第179章 【:第三小组】(3_10)", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff117?cv=1474263421424", "id": "57df797d864946e5195ff117", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第180章 【:未知天体】(4_10)", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff118?cv=1474263421424", "id": "57df797d864946e5195ff118", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第181章 【:问题】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff119?cv=1474263421424", "id": "57df797d864946e5195ff119", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第182章 【:鹰】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff11a?cv=1474263421424", "id": "57df797d864946e5195ff11a", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第183章 【:完美】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff11b?cv=1474263421424", "id": "57df797d864946e5195ff11b", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第184章 【:语不惊人死不休】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff11c?cv=1474263421424", "id": "57df797d864946e5195ff11c", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第185章 【:灭世】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff11d?cv=1474263421424", "id": "57df797d864946e5195ff11d", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第186章 【:实况直播(上)】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff11e?cv=1474263421424", "id": "57df797d864946e5195ff11e", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第187章 【:化石?(合并)】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff11f?cv=1474263421424", "id": "57df797d864946e5195ff11f", "currency": 25, "unreadble": false, "isVip": true }, { "title": "第188章 【:时代变了】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff120?cv=1474263421424", "id": "57df797d864946e5195ff120", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第189章 【:莱克斯的疯狂之举】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff121?cv=1474263421424", "id": "57df797d864946e5195ff121", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第190章 【:恶魔的聚会】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff122?cv=1474263421424", "id": "57df797d864946e5195ff122", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第191章 【:中二也可爱】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff123?cv=1474263421424", "id": "57df797d864946e5195ff123", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第192章 【:冥斧计划】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff124?cv=1474263421424", "id": "57df797d864946e5195ff124", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第193章 【:M系】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff125?cv=1474263421424", "id": "57df797d864946e5195ff125", "currency": 20, "unreadble": false, "isVip": true }, { "title": "第194章 【:示威】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff126?cv=1474263421424", "id": "57df797d864946e5195ff126", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第195章 【:坏事】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff127?cv=1474263421424", "id": "57df797d864946e5195ff127", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第196章 【:猛料】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff128?cv=1474263421424", "id": "57df797d864946e5195ff128", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第197章 【:美国“炸”了】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff129?cv=1474263421424", "id": "57df797d864946e5195ff129", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第198章 【:风水轮流转】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff12a?cv=1474263421424", "id": "57df797d864946e5195ff12a", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第199章 【:大抱歉术】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff12b?cv=1474263421424", "id": "57df797d864946e5195ff12b", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第200章 【:震惊世界】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff12c?cv=1474263421424", "id": "57df797d864946e5195ff12c", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第201章 【:球状物体】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff12d?cv=1474263421424", "id": "57df797d864946e5195ff12d", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第202章 【:猜测】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff12e?cv=1474263421424", "id": "57df797d864946e5195ff12e", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第203章 【:紫源晶】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff12f?cv=1474263421424", "id": "57df797d864946e5195ff12f", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第204章 【:“鹰:初号”】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff130?cv=1474263421424", "id": "57df797d864946e5195ff130", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第205章 【:猛禽天降】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff131?cv=1474263421424", "id": "57df797d864946e5195ff131", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第206章 【:随身携带】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff132?cv=1474263421424", "id": "57df797d864946e5195ff132", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第207章 【:未来的蓝图】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff133?cv=1474263421424", "id": "57df797d864946e5195ff133", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第208章 【:我们赢了】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff134?cv=1474263421424", "id": "57df797d864946e5195ff134", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第209章 【:引领潮流者】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff135?cv=1474263421424", "id": "57df797d864946e5195ff135", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第210章 【:震惊】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff136?cv=1474263421424", "id": "57df797d864946e5195ff136", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第211章 【:“猛禽”!天空霸主】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff137?cv=1474263421425", "id": "57df797d864946e5195ff137", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第212章 【:天价】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff138?cv=1474263421425", "id": "57df797d864946e5195ff138", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第213章 【:合法自由飞行许可】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff139?cv=1474263421425", "id": "57df797d864946e5195ff139", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第214章 【:库克密访】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff13a?cv=1474263421425", "id": "57df797d864946e5195ff13a", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第215章 【:目的】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff13b?cv=1474263421425", "id": "57df797d864946e5195ff13b", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第216章 【:激烈的谈判交锋】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff13c?cv=1474263421425", "id": "57df797d864946e5195ff13c", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第217章 【:寸土必争】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff13d?cv=1474263421425", "id": "57df797d864946e5195ff13d", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第218章 【:体验平凡】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff13e?cv=1474263421425", "id": "57df797d864946e5195ff13e", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第219章 【:危险】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff13f?cv=1474263421425", "id": "57df797d864946e5195ff13f", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第220章 【:有意而为?】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff140?cv=1474263421425", "id": "57df797d864946e5195ff140", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第221章 【:紧急快讯】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff141?cv=1474263421425", "id": "57df797d864946e5195ff141", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第222章 【:黑蛇组织】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff142?cv=1474263421425", "id": "57df797d864946e5195ff142", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第223章 【:计划开始】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff143?cv=1474263421425", "id": "57df797d864946e5195ff143", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第224章 【:疯狂】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff144?cv=1474263421425", "id": "57df797d864946e5195ff144", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第225章 【:怒火】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff145?cv=1474263421425", "id": "57df797d864946e5195ff145", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第226章 【:太平洋浪漫之旅】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff146?cv=1474263421425", "id": "57df797d864946e5195ff146", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第227章 【:猛禽降临】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff147?cv=1474263421425", "id": "57df797d864946e5195ff147", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第228章 【:蘑菇云】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff148?cv=1474263421425", "id": "57df797d864946e5195ff148", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第229章 【:真相】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff149?cv=1474263421425", "id": "57df797d864946e5195ff149", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第230章 【:筹码】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff14a?cv=1474263421425", "id": "57df797d864946e5195ff14a", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第231章 【:你紧张了!】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff14b?cv=1474263421425", "id": "57df797d864946e5195ff14b", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第232章 【:天才间的对垒】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff14c?cv=1474263421425", "id": "57df797d864946e5195ff14c", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第233章 【:时代不再属于你们】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff14d?cv=1474263421425", "id": "57df797d864946e5195ff14d", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第234章 【:回归】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff14e?cv=1474263421425", "id": "57df797d864946e5195ff14e", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第235章 【:重逢】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff14f?cv=1474263421425", "id": "57df797d864946e5195ff14f", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第236章 【:大环境】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff150?cv=1474263421425", "id": "57df797d864946e5195ff150", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第237章 【:新的野望】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff151?cv=1474263421425", "id": "57df797d864946e5195ff151", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第238章 【:科技,引领新潮流】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff152?cv=1474263421425", "id": "57df797d864946e5195ff152", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第239章 【:MST平台】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff153?cv=1474263421425", "id": "57df797d864946e5195ff153", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第240章 【:《泰坦尼克号》的昨日重现】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff154?cv=1474263421425", "id": "57df797d864946e5195ff154", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第241章 【:业界巨变】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff155?cv=1474263421425", "id": "57df797d864946e5195ff155", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第242章 【:并购】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff156?cv=1474263421425", "id": "57df797d864946e5195ff156", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第243章 【:坐入董事局】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff157?cv=1474263421425", "id": "57df797d864946e5195ff157", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第244章 【:解读】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff158?cv=1474263421425", "id": "57df797d864946e5195ff158", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第245章 【:内容与平台】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff159?cv=1474263421425", "id": "57df797d864946e5195ff159", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第246章 【:战略合作】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff15a?cv=1474263421425", "id": "57df797d864946e5195ff15a", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第247章 【:虚拟巡演】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff15b?cv=1474263421425", "id": "57df797d864946e5195ff15b", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第248章 【:虚拟主场,秋叶原】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff15c?cv=1474263421425", "id": "57df797d864946e5195ff15c", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第249章 【:尼努斯的分析】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff15d?cv=1474263421425", "id": "57df797d864946e5195ff15d", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第250章 【:任职?】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff15e?cv=1474263421425", "id": "57df797d864946e5195ff15e", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第251章 【:虚拟现实直播】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff15f?cv=1474263421425", "id": "57df797d864946e5195ff15f", "currency": 20, "unreadble": false, "isVip": true }, { "title": "第252章 【:展览会】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff160?cv=1474263421425", "id": "57df797d864946e5195ff160", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第253章 【:沉浸式体验】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff161?cv=1474263421425", "id": "57df797d864946e5195ff161", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第254章 【:记者的刁难】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff162?cv=1474263421425", "id": "57df797d864946e5195ff162", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第255章 【:完美应对】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff163?cv=1474263421425", "id": "57df797d864946e5195ff163", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第256章 【:65个名额】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff164?cv=1474263421425", "id": "57df797d864946e5195ff164", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第257章 【:虚拟世界计划】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff165?cv=1474263421425", "id": "57df797d864946e5195ff165", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第258章 【:全球化之路】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff166?cv=1474263421425", "id": "57df797d864946e5195ff166", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第259章 【:堪比全球的超算】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff167?cv=1474263421425", "id": "57df797d864946e5195ff167", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第260章 【:创造者≠拥有者?】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff168?cv=1474263421425", "id": "57df797d864946e5195ff168", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第261章 【:“人机交互”问题】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff169?cv=1474263421425", "id": "57df797d864946e5195ff169", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第262章 【:意识内核】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff16a?cv=1474263421425", "id": "57df797d864946e5195ff16a", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第263章 【:与众不同?】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff16b?cv=1474263421425", "id": "57df797d864946e5195ff16b", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第264章 【:感知能力】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff16c?cv=1474263421425", "id": "57df797d864946e5195ff16c", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第265章 【:疯了?】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff16d?cv=1474263421425", "id": "57df797d864946e5195ff16d", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第266章 【:不可思议】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff16e?cv=1474263421425", "id": "57df797d864946e5195ff16e", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第267章 【:脑核处的神秘区域】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff16f?cv=1474263421425", "id": "57df797d864946e5195ff16f", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第268章 【:消息泄露】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff170?cv=1474263421425", "id": "57df797d864946e5195ff170", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第269章 【:心电传感抑制器】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff171?cv=1474263421425", "id": "57df797d864946e5195ff171", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第270章 【:考博】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff172?cv=1474263421425", "id": "57df797d864946e5195ff172", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第271章 【:虚拟网络世界】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff173?cv=1474263421425", "id": "57df797d864946e5195ff173", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第272章 【:量子超算】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff174?cv=1474263421425", "id": "57df797d864946e5195ff174", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第273章 【:争执】(急需正版支持)", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff175?cv=1474263421425", "id": "57df797d864946e5195ff175", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第274章 【:扎克访华】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff176?cv=1474263421425", "id": "57df797d864946e5195ff176", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第275章 【:幌子】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff177?cv=1474263421425", "id": "57df797d864946e5195ff177", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第276章 【:扎克的震撼】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff178?cv=1474263421425", "id": "57df797d864946e5195ff178", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第277章 【:前所未有的体验】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff179?cv=1474263421425", "id": "57df797d864946e5195ff179", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第278章 【:卖的越火亏的越多?】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff17a?cv=1474263421425", "id": "57df797d864946e5195ff17a", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第279章 【:参观】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff17b?cv=1474263421425", "id": "57df797d864946e5195ff17b", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第280章 【:野望】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff17c?cv=1474263421425", "id": "57df797d864946e5195ff17c", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第281章 【:鼓舞人心】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff17d?cv=1474263421425", "id": "57df797d864946e5195ff17d", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第282章 【:华尔街的疯狂追逐】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff17e?cv=1474263421425", "id": "57df797d864946e5195ff17e", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第283章 【:各方扮演的角色】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff17f?cv=1474263421425", "id": "57df797d864946e5195ff17f", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第284章 【:D-Wave公司】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff180?cv=1474263421425", "id": "57df797d864946e5195ff180", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第285章 【:量子计算机】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff181?cv=1474263421425", "id": "57df797d864946e5195ff181", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第286章 【:缺乏一个灵魂】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff182?cv=1474263421425", "id": "57df797d864946e5195ff182", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第287章 【:注入“灵魂”】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff183?cv=1474263421425", "id": "57df797d864946e5195ff183", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第288章 【:一笔大生意】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff184?cv=1474263421425", "id": "57df797d864946e5195ff184", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第289章 【:D-Wave发布会】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff185?cv=1474263421425", "id": "57df797d864946e5195ff185", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第290章 【:华尔街再次疯狂】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff186?cv=1474263421425", "id": "57df797d864946e5195ff186", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第291章 【:霸气回应】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff187?cv=1474263421425", "id": "57df797d864946e5195ff187", "currency": 20, "unreadble": false, "isVip": true }, { "title": "第292章 【:警示】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff188?cv=1474263421425", "id": "57df797d864946e5195ff188", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第293章 【:仅是弄潮儿】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff189?cv=1474263421425", "id": "57df797d864946e5195ff189", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第294章 【:Google“抢”头条】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff18a?cv=1474263421425", "id": "57df797d864946e5195ff18a", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第295章 【:谷歌“XPRIZE”计划】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff18b?cv=1474263421425", "id": "57df797d864946e5195ff18b", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第296章 【:竞争或合作?】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff18c?cv=1474263421425", "id": "57df797d864946e5195ff18c", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第297章 【:谷歌要登月】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff18d?cv=1474263421425", "id": "57df797d864946e5195ff18d", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第298章 【:拉里.佩奇的野心】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff18e?cv=1474263421425", "id": "57df797d864946e5195ff18e", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第299章 【:西雅图圆桌会议】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff18f?cv=1474263421425", "id": "57df797d864946e5195ff18f", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第300章 【:都是大神级人物】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff190?cv=1474263421425", "id": "57df797d864946e5195ff190", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第301章 【:演讲】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff191?cv=1474263421425", "id": "57df797d864946e5195ff191", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第302章 【第297张:大咖思维】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff192?cv=1474263421425", "id": "57df797d864946e5195ff192", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第303章 【:肥单】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff193?cv=1474263421425", "id": "57df797d864946e5195ff193", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第304章 【:达成】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff194?cv=1474263421425", "id": "57df797d864946e5195ff194", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第305章 【:月球背面】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff195?cv=1474263421425", "id": "57df797d864946e5195ff195", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第306章 【:投送】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff196?cv=1474263421425", "id": "57df797d864946e5195ff196", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第307章 【:空降月表】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff197?cv=1474263421425", "id": "57df797d864946e5195ff197", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第308章 【:扩建】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff198?cv=1474263421425", "id": "57df797d864946e5195ff198", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第309章 【:史前太空车祸?】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff199?cv=1474263421425", "id": "57df797d864946e5195ff199", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第310章 【:三份合约】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff19a?cv=1474263421425", "id": "57df797d864946e5195ff19a", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第311章 【:紧张的微软和索尼?】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff19b?cv=1474263421425", "id": "57df797d864946e5195ff19b", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第312章 【:“神”才可以消灭你】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff19c?cv=1474263421425", "id": "57df797d864946e5195ff19c", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第313章 【:买下行业“半壁江山”】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff19d?cv=1474263421425", "id": "57df797d864946e5195ff19d", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第314章 【:众说纷纭】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff19e?cv=1474263421425", "id": "57df797d864946e5195ff19e", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第315章 【:不可思议】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff19f?cv=1474263421425", "id": "57df797d864946e5195ff19f", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第316章 【:时代下的浪潮】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff1a0?cv=1474263421425", "id": "57df797d864946e5195ff1a0", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第317章 【:开拓者们】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff1a1?cv=1474263421425", "id": "57df797d864946e5195ff1a1", "currency": 20, "unreadble": false, "isVip": true }, { "title": "第318章 【:布局完成】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff1a2?cv=1474263421425", "id": "57df797d864946e5195ff1a2", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第319章 【:冷凝切割法?】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff1a3?cv=1474263421425", "id": "57df797d864946e5195ff1a3", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第320章 【:Alphabet的声音】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff1a4?cv=1474263421425", "id": "57df797d864946e5195ff1a4", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第321章 【:遗迹内部(补昨天)】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff1a5?cv=1474263421425", "id": "57df797d864946e5195ff1a5", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第322章 【:不是地外文明?】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff1a6?cv=1474263421425", "id": "57df797d864946e5195ff1a6", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第323章 【:集结“共和国之鹰”】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff1a7?cv=1474263421425", "id": "57df797d864946e5195ff1a7", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第324章 【:美国的敏感】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff1a8?cv=1474263421425", "id": "57df797d864946e5195ff1a8", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第325章 【:整装待发】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff1a9?cv=1474263421425", "id": "57df797d864946e5195ff1a9", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第326章 【:震撼的空中打击】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff1aa?cv=1474263421425", "id": "57df797d864946e5195ff1aa", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第327章 【:为战而生】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff1ab?cv=1474263421425", "id": "57df797d864946e5195ff1ab", "currency": 20, "unreadble": false, "isVip": true }, { "title": "第328章 【:阅兵庆典(一)】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff1ac?cv=1474263421425", "id": "57df797d864946e5195ff1ac", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第329章 【:阅兵庆典(二)】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff1ad?cv=1474263421425", "id": "57df797d864946e5195ff1ad", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第330章 【:阅兵庆典(三)】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff1ae?cv=1474263421425", "id": "57df797d864946e5195ff1ae", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第331章 【:阅兵庆典(四)】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff1af?cv=1474263421425", "id": "57df797d864946e5195ff1af", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第332章 【:影响力】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff1b0?cv=1474263421425", "id": "57df797d864946e5195ff1b0", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第333章 【:美国人不乐意】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff1b1?cv=1474263421425", "id": "57df797d864946e5195ff1b1", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第334章 【:忍还是不忍?】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff1b2?cv=1474263421425", "id": "57df797d864946e5195ff1b2", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第335章 【:明暗之间的博弈】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff1b3?cv=1474263421425", "id": "57df797d864946e5195ff1b3", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第336章 【:我们被阴了】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff1b4?cv=1474263421425", "id": "57df797d864946e5195ff1b4", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第337章 【:俘获】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff1b5?cv=1474263421425", "id": "57df797d864946e5195ff1b5", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第338章 【:打一架下来?】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff1b6?cv=1474263421425", "id": "57df797d864946e5195ff1b6", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第339章 【:科学院终于找来了】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff1b7?cv=1474263421425", "id": "57df797d864946e5195ff1b7", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第340章 【:这是地外产物】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff1b8?cv=1474263421425", "id": "57df797d864946e5195ff1b8", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第341章 【:任鸿的真实目的】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff1b9?cv=1474263421425", "id": "57df797d864946e5195ff1b9", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第342章 【:野心勃勃的挖矿计划】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff1ba?cv=1474263421425", "id": "57df797d864946e5195ff1ba", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第343章 【:国际形势】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff1bb?cv=1474263421425", "id": "57df797d864946e5195ff1bb", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第344章 【:转移视线】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff1bc?cv=1474263421425", "id": "57df797d864946e5195ff1bc", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第345章 【:批了?】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff1bd?cv=1474263421425", "id": "57df797d864946e5195ff1bd", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第346章 【:虚拟世界的首发地址】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff1be?cv=1474263421425", "id": "57df797d864946e5195ff1be", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第347章 【:东京电玩展】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff1bf?cv=1474263421425", "id": "57df797d864946e5195ff1bf", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第348章 【:超级猛料】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff1c0?cv=1474263421425", "id": "57df797d864946e5195ff1c0", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第349章 【:爆炸的期待值】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff1c1?cv=1474263421425", "id": "57df797d864946e5195ff1c1", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第350章 【:投以重注】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff1c2?cv=1474263421425", "id": "57df797d864946e5195ff1c2", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第351章 【:玩的就是生态】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff1c3?cv=1474263421425", "id": "57df797d864946e5195ff1c3", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第352章 【:“天擎系统”】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff1c4?cv=1474263421425", "id": "57df797d864946e5195ff1c4", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第353章 【:发射成功!虚拟世界首发在即】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff1c5?cv=1474263421425", "id": "57df797d864946e5195ff1c5", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第354章 【:新的子公司】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff1c6?cv=1474263421425", "id": "57df797d864946e5195ff1c6", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第355章 【:发布会】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff1c7?cv=1474263421425", "id": "57df797d864946e5195ff1c7", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第356章 【:埃隆.马斯克的紧张】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff1c8?cv=1474263421425", "id": "57df797d864946e5195ff1c8", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第357章 【:2022年完美收官】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff1c9?cv=1474263421425", "id": "57df797d864946e5195ff1c9", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第358章 【:首发公布】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff1ca?cv=1474263421425", "id": "57df797d864946e5195ff1ca", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第359章 【:遥指东方明珠】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff1cb?cv=1474263421425", "id": "57df797d864946e5195ff1cb", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第360章 【:狂轰滥炸的推广】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff1cc?cv=1474263421425", "id": "57df797d864946e5195ff1cc", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第361章 【:惊声尖叫开始】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff1cd?cv=1474263421425", "id": "57df797d864946e5195ff1cd", "currency": 20, "unreadble": false, "isVip": true }, { "title": "第362章 【:漫天“星光”】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff1ce?cv=1474263421425", "id": "57df797d864946e5195ff1ce", "currency": 20, "unreadble": false, "isVip": true }, { "title": "第363章 【:摇滚吧】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff1cf?cv=1474263421425", "id": "57df797d864946e5195ff1cf", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第364章 【:有请…Mr.任!】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff1d0?cv=1474263421425", "id": "57df797d864946e5195ff1d0", "currency": 20, "unreadble": false, "isVip": true }, { "title": "第365章 【:假如能自由支配梦境】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff1d1?cv=1474263421425", "id": "57df797d864946e5195ff1d1", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第366章 【:烧出来的“未来”】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff1d2?cv=1474263421425", "id": "57df797d864946e5195ff1d2", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第367章 【:亮相】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff1d3?cv=1474263421425", "id": "57df797d864946e5195ff1d3", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第368章 【:虚拟时代】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff1d4?cv=1474263421425", "id": "57df797d864946e5195ff1d4", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第369章 【:他要敢,我就寄刀片!】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff1d5?cv=1474263421425", "id": "57df797d864946e5195ff1d5", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第370章 【:我的世界】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff1d6?cv=1474263421425", "id": "57df797d864946e5195ff1d6", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第371章 【:置身事外的超然地位】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff1d7?cv=1474263421425", "id": "57df797d864946e5195ff1d7", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第372章 【:前所未有的体验】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff1d8?cv=1474263421425", "id": "57df797d864946e5195ff1d8", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第373章 【:虚拟社区】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff1d9?cv=1474263421425", "id": "57df797d864946e5195ff1d9", "currency": 20, "unreadble": false, "isVip": true }, { "title": "第374章 【:炸锅的欧美民众】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff1da?cv=1474263421425", "id": "57df797d864946e5195ff1da", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第375章 【:亏本卖?】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff1db?cv=1474263421425", "id": "57df797d864946e5195ff1db", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第376章 【:硬件巨头的生存危机】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff1dc?cv=1474263421425", "id": "57df797d864946e5195ff1dc", "currency": 20, "unreadble": false, "isVip": true }, { "title": "第377章 【:解铃还须系铃人】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff1dd?cv=1474263421425", "id": "57df797d864946e5195ff1dd", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第378章 【:CJ游戏展会】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff1de?cv=1474263421425", "id": "57df797d864946e5195ff1de", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第379章 【:星际公民开发者座谈会】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff1df?cv=1474263421425", "id": "57df797d864946e5195ff1df", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第380章 【:史无前例】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff1e0?cv=1474263421425", "id": "57df797d864946e5195ff1e0", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第381章 【:听说有人跟我拼手办?】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff1e1?cv=1474263421425", "id": "57df797d864946e5195ff1e1", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第382章 【:舰娘顾问】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff1e2?cv=1474263421425", "id": "57df797d864946e5195ff1e2", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第383章 【:能和NPC嘿咻?】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff1e3?cv=1474263421425", "id": "57df797d864946e5195ff1e3", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第384章 【:终究不是“亲儿子”】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff1e4?cv=1474263421425", "id": "57df797d864946e5195ff1e4", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第385章 【:三重保障,稳!】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff1e5?cv=1474263421425", "id": "57df797d864946e5195ff1e5", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第386章 【:巨头会面】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff1e6?cv=1474263421425", "id": "57df797d864946e5195ff1e6", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第387章 【:合作】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff1e7?cv=1474263421425", "id": "57df797d864946e5195ff1e7", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第388章 【:炸锅的欧美消费者】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff1e8?cv=1474263421425", "id": "57df797d864946e5195ff1e8", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第389章 【:应对措施】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff1e9?cv=1474263421425", "id": "57df797d864946e5195ff1e9", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第390章 【:空前规模的经济交流会议】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff1ea?cv=1474263421425", "id": "57df797d864946e5195ff1ea", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第391章 【:演讲】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff1eb?cv=1474263421425", "id": "57df797d864946e5195ff1eb", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第392章 【:采访】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff1ec?cv=1474263421425", "id": "57df797d864946e5195ff1ec", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第393章 【:杀鸡儆猴】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff1ed?cv=1474263421425", "id": "57df797d864946e5195ff1ed", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第394章 【:宴会上】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff1ee?cv=1474263421425", "id": "57df797d864946e5195ff1ee", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第395章 【:震惊世界】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff1ef?cv=1474263421425", "id": "57df797d864946e5195ff1ef", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第396章 【:愤怒的总统】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff1f0?cv=1474263421426", "id": "57df797d864946e5195ff1f0", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第397章 【:胜利】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff1f1?cv=1474263421426", "id": "57df797d864946e5195ff1f1", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第398章 【:销售狂潮】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff1f2?cv=1474263421426", "id": "57df797d864946e5195ff1f2", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第399章 【:传奇在延续】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff1f3?cv=1474263421426", "id": "57df797d864946e5195ff1f3", "currency": 20, "unreadble": false, "isVip": true }, { "title": "第400章 【:拥抱】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff1f4?cv=1474263421426", "id": "57df797d864946e5195ff1f4", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第401章 【:冥斧计划新突破】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff1f5?cv=1474263421426", "id": "57df797d864946e5195ff1f5", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第402章 【:鲸鲨系】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff1f6?cv=1474263421426", "id": "57df797d864946e5195ff1f6", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第403章 【:颠覆性的设计】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff1f7?cv=1474263421426", "id": "57df797d864946e5195ff1f7", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第404章 【:移动轨道】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff1f8?cv=1474263421426", "id": "57df797d864946e5195ff1f8", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第405章 【:物美价不廉】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff1f9?cv=1474263421426", "id": "57df797d864946e5195ff1f9", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第406章 【:K-3018号史前生物研究所】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff1fa?cv=1474263421426", "id": "57df797d864946e5195ff1fa", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第407章 【:它要破壳了!】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff1fb?cv=1474263421426", "id": "57df797d864946e5195ff1fb", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第408章 【:亿万年时空交织的瞬间】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff1fc?cv=1474263421426", "id": "57df797d864946e5195ff1fc", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第409章 【:世纪工程计划】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff1fd?cv=1474263421426", "id": "57df797d864946e5195ff1fd", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第410章 【:进化论的验证结果】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff1fe?cv=1474263421426", "id": "57df797d864946e5195ff1fe", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第411章 【:问题和如何解决问题】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff1ff?cv=1474263421426", "id": "57df797d864946e5195ff1ff", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第412章 【:令人眩晕的疯狂计划(上)】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff200?cv=1474263421426", "id": "57df797d864946e5195ff200", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第413章 【:令人眩晕的疯狂计划(下)】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff201?cv=1474263421426", "id": "57df797d864946e5195ff201", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第414章 【:讨论】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff202?cv=1474263421426", "id": "57df797d864946e5195ff202", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第415章 【:合作事宜】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff203?cv=1474263421426", "id": "57df797d864946e5195ff203", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第416章 【:视察】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff204?cv=1474263421426", "id": "57df797d864946e5195ff204", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第417章 【:买下恐龙IP】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff205?cv=1474263421426", "id": "57df797d864946e5195ff205", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第418章 【:生命之河与死亡之河】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff206?cv=1474263421426", "id": "57df797d864946e5195ff206", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第419章 【:发布会】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff207?cv=1474263421426", "id": "57df797d864946e5195ff207", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第420章 【:自(wu)信(chi)的“棒子”】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff208?cv=1474263421426", "id": "57df797d864946e5195ff208", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第421章 【:前往大西北】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff209?cv=1474263421426", "id": "57df797d864946e5195ff209", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第422章 【:进入阿尔金山脉】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff20a?cv=1474263421426", "id": "57df797d864946e5195ff20a", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第423章 【:罗布泊惊天大发现】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff20b?cv=1474263421426", "id": "57df797d864946e5195ff20b", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第424章 【:暗湖中的生物】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff20c?cv=1474263421426", "id": "57df797d864946e5195ff20c", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第425章 【:别有洞天】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff20d?cv=1474263421426", "id": "57df797d864946e5195ff20d", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第426章 【:罗布泊史前遗迹】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff20e?cv=1474263421426", "id": "57df797d864946e5195ff20e", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第427章 【:Alphabet的动作】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff20f?cv=1474263421426", "id": "57df797d864946e5195ff20f", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第428章 【:进入遗迹】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff210?cv=1474263421426", "id": "57df797d864946e5195ff210", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第429章 【:我有办法】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff211?cv=1474263421426", "id": "57df797d864946e5195ff211", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第430章 【:新发现】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff212?cv=1474263421426", "id": "57df797d864946e5195ff212", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第431章 【:起源】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff213?cv=1474263421426", "id": "57df797d864946e5195ff213", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第432章 【:灾难】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff214?cv=1474263421426", "id": "57df797d864946e5195ff214", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第433章 【:终结】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff215?cv=1474263421426", "id": "57df797d864946e5195ff215", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第434章 【:他们会回来吗?】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff216?cv=1474263421426", "id": "57df797d864946e5195ff216", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第435章 【:人工虫洞?】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff217?cv=1474263421426", "id": "57df797d864946e5195ff217", "currency": 20, "unreadble": false, "isVip": true }, { "title": "第436章 【:时间折叠现象】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff218?cv=1474263421426", "id": "57df797d864946e5195ff218", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第437章 【:宇宙“和平大使”】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff219?cv=1474263421426", "id": "57df797d864946e5195ff219", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第438章 【:巨大收获】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff21a?cv=1474263421426", "id": "57df797d864946e5195ff21a", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第439章 【:有个三亿大项目跟你谈谈】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff21b?cv=1474263421426", "id": "57df797d864946e5195ff21b", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第440章 【:恐龙安置问题】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff21c?cv=1474263421426", "id": "57df797d864946e5195ff21c", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第441章 【:雕门岛】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff21d?cv=1474263421426", "id": "57df797d864946e5195ff21d", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第442章 【:萝卜加大棒】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff21e?cv=1474263421426", "id": "57df797d864946e5195ff21e", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第443章 【:牵线】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff21f?cv=1474263421426", "id": "57df797d864946e5195ff21f", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第444章 【:诱人的未来】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff220?cv=1474263421426", "id": "57df797d864946e5195ff220", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第445章 【:强势!】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff221?cv=1474263421426", "id": "57df797d864946e5195ff221", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第446章 【:终究是小国】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff222?cv=1474263421426", "id": "57df797d864946e5195ff222", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第447章 【:妥协】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff223?cv=1474263421426", "id": "57df797d864946e5195ff223", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第448章 【:震惊】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff224?cv=1474263421426", "id": "57df797d864946e5195ff224", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第449章 【:终究还是要秀腕力】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff225?cv=1474263421426", "id": "57df797d864946e5195ff225", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第450章 【:站队】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff226?cv=1474263421426", "id": "57df797d864946e5195ff226", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第451章 【:鲸鲨入海】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff227?cv=1474263421426", "id": "57df797d864946e5195ff227", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第452章 【:天眼系统】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff228?cv=1474263421426", "id": "57df797d864946e5195ff228", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第453章 【:新的未解之谜】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff229?cv=1474263421426", "id": "57df797d864946e5195ff229", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第454章 【:变幻莫测的局势】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff22a?cv=1474263421426", "id": "57df797d864946e5195ff22a", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第455章 【:作死的菲律宾渔船】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff22b?cv=1474263421426", "id": "57df797d864946e5195ff22b", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第456章 【:又惊又气的反侦测手段】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff22c?cv=1474263421426", "id": "57df797d864946e5195ff22c", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第457章 【:那是深海巨兽?】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff22d?cv=1474263421426", "id": "57df797d864946e5195ff22d", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第458章 【:颠覆世界观】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff22e?cv=1474263421426", "id": "57df797d864946e5195ff22e", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第459章 【:这印花标志是……】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff22f?cv=1474263421426", "id": "57df797d864946e5195ff22f", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第460章 【:来了!】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff230?cv=1474263421426", "id": "57df797d864946e5195ff230", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第461章 【:鲸鲨之跃】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff231?cv=1474263421426", "id": "57df797d864946e5195ff231", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第462章 【:影响】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff232?cv=1474263421426", "id": "57df797d864946e5195ff232", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第463章 【:三极分化?】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff233?cv=1474263421426", "id": "57df797d864946e5195ff233", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第464章 【:热血沸腾宣传短片】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff234?cv=1474263421426", "id": "57df797d864946e5195ff234", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第465章 【:好莱坞大片预告即视感】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff235?cv=1474263421426", "id": "57df797d864946e5195ff235", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第466章 【:进退维谷与应对策略】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff236?cv=1474263421426", "id": "57df797d864946e5195ff236", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第467章 【:勇闯雕门岛】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff237?cv=1474263421426", "id": "57df797d864946e5195ff237", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第468章 【:来到侏罗纪时代】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff238?cv=1474263421426", "id": "57df797d864946e5195ff238", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第469章 【:把他们拎出来】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff239?cv=1474263421426", "id": "57df797d864946e5195ff239", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第470章 【:打造全球唯一恐龙公园】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff23a?cv=1474263421426", "id": "57df797d864946e5195ff23a", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第471章 【:轰动全球】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff23b?cv=1474263421426", "id": "57df797d864946e5195ff23b", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第472章 【:谈笑风生的大佬们】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff23c?cv=1474263421426", "id": "57df797d864946e5195ff23c", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第473章 【:扎克与马斯克的“恩怨”】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff23d?cv=1474263421426", "id": "57df797d864946e5195ff23d", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第474章 【:新的趋势】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff23e?cv=1474263421426", "id": "57df797d864946e5195ff23e", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第475章 【:引导舆论】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff23f?cv=1474263421426", "id": "57df797d864946e5195ff23f", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第476章 【:两大趋势】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff240?cv=1474263421426", "id": "57df797d864946e5195ff240", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第477章 【:任鸿的大脑、天才的思维】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff241?cv=1474263421426", "id": "57df797d864946e5195ff241", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第478章 【:一揽子计划】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff242?cv=1474263421426", "id": "57df797d864946e5195ff242", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第479章 【:新的颠覆】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff243?cv=1474263421426", "id": "57df797d864946e5195ff243", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第480章 【:不屑】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff244?cv=1474263421426", "id": "57df797d864946e5195ff244", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第481章 【:烤龙肉大餐】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff245?cv=1474263421426", "id": "57df797d864946e5195ff245", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第482章 【:重启恐龙电影项目】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff246?cv=1474263421426", "id": "57df797d864946e5195ff246", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第483章 【:任鸿想要的《侏罗纪世界7》】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff247?cv=1474263421426", "id": "57df797d864946e5195ff247", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第484章 【:合作】", "link": "http://vip.zhuishushenqi.com/chapter/57df797d864946e5195ff248?cv=1474263421426", "id": "57df797d864946e5195ff248", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第485章 【:电影立项】", "link": "http://vip.zhuishushenqi.com/chapter/57dfac7bbc1c76a345a03885?cv=1474276475864", "id": "57dfac7bbc1c76a345a03885", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第486章 【:前往雕门岛】", "link": "http://vip.zhuishushenqi.com/chapter/57dfac7d20530c223e0df4c1?cv=1474276477039", "id": "57dfac7d20530c223e0df4c1", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第487章 【:电影杀青】", "link": "http://vip.zhuishushenqi.com/chapter/57e10edb0816fafc0db4dff4?cv=1474367195037", "id": "57e10edb0816fafc0db4dff4", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第488章 【:恐龙或将“攻陷全球”】", "link": "http://vip.zhuishushenqi.com/chapter/57e10edbfc4b6e75741cdc6e?cv=1474367195935", "id": "57e10edbfc4b6e75741cdc6e", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第489章 【:任鸿的远见】", "link": "http://vip.zhuishushenqi.com/chapter/57e1fd8af3c6c9210e0344ee?cv=1474428298802", "id": "57e1fd8af3c6c9210e0344ee", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第490章 【:在虚拟世界中观影是什么体验?】", "link": "http://vip.zhuishushenqi.com/chapter/57e1fd8bc4eb2ce734c1f82d?cv=1474428299650", "id": "57e1fd8bc4eb2ce734c1f82d", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第491章 【:连破记录】", "link": "http://vip.zhuishushenqi.com/chapter/57e37e37f0e683227099682d?cv=1474526775292", "id": "57e37e37f0e683227099682d", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第492章 【:恐龙公园经营模式】", "link": "http://vip.zhuishushenqi.com/chapter/57e37e380bec1577041d3d28?cv=1474526776075", "id": "57e37e380bec1577041d3d28", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第493章 【:莱克斯.韦恩】", "link": "http://vip.zhuishushenqi.com/chapter/57e4cd2463d71b0a0d0552f4?cv=1474612516745", "id": "57e4cd2463d71b0a0d0552f4", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第494章 【:疑惑的任鸿】", "link": "http://vip.zhuishushenqi.com/chapter/57e4cd25158f92af154be153?cv=1474612517325", "id": "57e4cd25158f92af154be153", "currency": 20, "unreadble": false, "isVip": true }, { "title": "第495章 【:海伦的震撼】", "link": "http://vip.zhuishushenqi.com/chapter/57e60e5266c6415e626b1cfd?cv=1474694907901", "id": "57e60e5266c6415e626b1cfd", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第496章 【:恐龙狩猎场】", "link": "http://vip.zhuishushenqi.com/chapter/57e60e537859cae1424b21e5?cv=1474694739928", "id": "57e60e537859cae1424b21e5", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第497章 【:吃肉的植物】", "link": "http://vip.zhuishushenqi.com/chapter/57e60e54b5b74a064834ec4e?cv=1474694740926", "id": "57e60e54b5b74a064834ec4e", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第498章 【:“坑钱”主力军】", "link": "http://vip.zhuishushenqi.com/chapter/57e60e55e2970d9d4938b4ab?cv=1474694741616", "id": "57e60e55e2970d9d4938b4ab", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第499章 【:巨大的市场】", "link": "http://vip.zhuishushenqi.com/chapter/57e7a14ce4befc021ac254bd?cv=1474797900492", "id": "57e7a14ce4befc021ac254bd", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第500章 【:“心虚”的小樱】", "link": "http://vip.zhuishushenqi.com/chapter/57e7a14d59d738090d4bd982?cv=1474797901775", "id": "57e7a14d59d738090d4bd982", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第501章 【:INnuEdgeAi—X公司】", "link": "http://vip.zhuishushenqi.com/chapter/57e8d1d94c7829206bb97f16?cv=1474875865465", "id": "57e8d1d94c7829206bb97f16", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第502章 【:不亚于“虚拟世界”的计划(合并)】", "link": "http://vip.zhuishushenqi.com/chapter/57e8d1da8c58ae4b2b0e1790?cv=1474875866438", "id": "57e8d1da8c58ae4b2b0e1790", "currency": 20, "unreadble": false, "isVip": true }, { "title": "第503章 【:震惊的永华】", "link": "http://vip.zhuishushenqi.com/chapter/57ea5bdeea54a77a70021d4b?cv=1474976734578", "id": "57ea5bdeea54a77a70021d4b", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第504章 【:真正掌控人类命运的那批人】", "link": "http://vip.zhuishushenqi.com/chapter/57ea5bdfd2cee72c4c291d2b?cv=1474976735598", "id": "57ea5bdfd2cee72c4c291d2b", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第505章 【:“化身”】", "link": "http://vip.zhuishushenqi.com/chapter/57eb9a6555a3f152327a9fb2?cv=1475058277697", "id": "57eb9a6555a3f152327a9fb2", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第506章 【:距离问题】", "link": "http://vip.zhuishushenqi.com/chapter/57eb9a66b89fbc8f62c3e385?cv=1475058278876", "id": "57eb9a66b89fbc8f62c3e385", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第507章 【:诠释】", "link": "http://vip.zhuishushenqi.com/chapter/57ecec019116eb79232dbd92?cv=1475144705580", "id": "57ecec019116eb79232dbd92", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第508章 【:新生】", "link": "http://vip.zhuishushenqi.com/chapter/57ecec020e3edb6a016b5465?cv=1475144706932", "id": "57ecec020e3edb6a016b5465", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第509章 【:人间至美】", "link": "http://vip.zhuishushenqi.com/chapter/57ed13ca458271692896f8cf?cv=1475154890451", "id": "57ed13ca458271692896f8cf", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第510章 【:看什么看?开车!】", "link": "http://vip.zhuishushenqi.com/chapter/57ee4de7717b5245152f7d36?cv=1475235303824", "id": "57ee4de7717b5245152f7d36", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第511章 【:手忙脚乱的】", "link": "http://vip.zhuishushenqi.com/chapter/57ee4de866c0500533554599?cv=1475235304961", "id": "57ee4de866c0500533554599", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第512章 【:亲爱的,跟你商量个事儿!】", "link": "http://vip.zhuishushenqi.com/chapter/57efb4d62aa496eb15977299?cv=1475327190121", "id": "57efb4d62aa496eb15977299", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第513章 【:莫麟】", "link": "http://vip.zhuishushenqi.com/chapter/57efb4d88c282c0c5b187891?cv=1475327192097", "id": "57efb4d88c282c0c5b187891", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第514章 【:空间探索正式启动】", "link": "http://vip.zhuishushenqi.com/chapter/57f0ec6817dfc8d74466341d?cv=1475406952232", "id": "57f0ec6817dfc8d74466341d", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第515章 【:天上宫阙——“天宫”】", "link": "http://vip.zhuishushenqi.com/chapter/57f0ec691570b91f56523d0e?cv=1475406953465", "id": "57f0ec691570b91f56523d0e", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第516章 【:视察】", "link": "http://vip.zhuishushenqi.com/chapter/57f199a921a6a6b35d3022f6?cv=1475451305794", "id": "57f199a921a6a6b35d3022f6", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第517章 【:“双子计划”】", "link": "http://vip.zhuishushenqi.com/chapter/57f199acfd70f114308b3896?cv=1475451308407", "id": "57f199acfd70f114308b3896", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第518章 【:聚焦视野】", "link": "http://vip.zhuishushenqi.com/chapter/57f38f95153cb4d601e798e6?cv=1475579797322", "id": "57f38f95153cb4d601e798e6", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第519章 【:哨戒任务】", "link": "http://vip.zhuishushenqi.com/chapter/57f38f95d45350fd27644501?cv=1475579797904", "id": "57f38f95d45350fd27644501", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第520章 【:记者提问】", "link": "http://vip.zhuishushenqi.com/chapter/57f4c4f29d6d84da11f330ee?cv=1475658994500", "id": "57f4c4f29d6d84da11f330ee", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第521章 【:闷声挖矿发大财】", "link": "http://vip.zhuishushenqi.com/chapter/57f4c4f4897adb8103c4ca21?cv=1475658996101", "id": "57f4c4f4897adb8103c4ca21", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第522章 【:星港结构】", "link": "http://vip.zhuishushenqi.com/chapter/57f6452fe8c50a4125979c29?cv=1475757359678", "id": "57f6452fe8c50a4125979c29", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第523章 【:毕格罗亲自推销产品】", "link": "http://vip.zhuishushenqi.com/chapter/57f6453458d2da9a4fb3c80f?cv=1475757364483", "id": "57f6453458d2da9a4fb3c80f", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第524章 【:火星空间站——“萤火号”】", "link": "http://vip.zhuishushenqi.com/chapter/57f7268525e7347e6807184c?cv=1475815045720", "id": "57f7268525e7347e6807184c", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第525章 【:刘宇梁的微词】", "link": "http://vip.zhuishushenqi.com/chapter/57f77a6298c9ad8a32afd8f2?cv=1475836514176", "id": "57f77a6298c9ad8a32afd8f2", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第526章 【:叹服】", "link": "http://vip.zhuishushenqi.com/chapter/57f879b5a0c0e18e488989a9?cv=1475901877544", "id": "57f879b5a0c0e18e488989a9", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第527章 【:发射升空】", "link": "http://vip.zhuishushenqi.com/chapter/57f8ce7d6cedd82e2043576c?cv=1475923581268", "id": "57f8ce7d6cedd82e2043576c", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第528章 【:有个小问题】", "link": "http://vip.zhuishushenqi.com/chapter/57f9c6b71aaaec715744e3d5?cv=1475987127421", "id": "57f9c6b71aaaec715744e3d5", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第529章 【:任务进行时】", "link": "http://vip.zhuishushenqi.com/chapter/57fa146028f0096339090fda?cv=1476007008967", "id": "57fa146028f0096339090fda", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第530章 【:空间交会对接】", "link": "http://vip.zhuishushenqi.com/chapter/57fb184e32f6d71d505044d3?cv=1476073550899", "id": "57fb184e32f6d71d505044d3", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第531章 【:扶摇直上的影响力】", "link": "http://vip.zhuishushenqi.com/chapter/57fb6f486f9350d762f68776?cv=1476095816483", "id": "57fb6f486f9350d762f68776", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第532章 【:火星生命?】", "link": "http://vip.zhuishushenqi.com/chapter/57fc69a18bb4a8e361be056f?cv=1476159905515", "id": "57fc69a18bb4a8e361be056f", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第533章 【:火星最后的“幸存者”】", "link": "http://vip.zhuishushenqi.com/chapter/57fcbe41714f7a1e1a6660db?cv=1476181569502", "id": "57fcbe41714f7a1e1a6660db", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第534章 【:母性光辉】", "link": "http://vip.zhuishushenqi.com/chapter/57fdbfd707af376076c96841?cv=1476247511582", "id": "57fdbfd707af376076c96841", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第535章 【:焦虑的马斯克】", "link": "http://vip.zhuishushenqi.com/chapter/57fe0d4f2e1e0912475a99c5?cv=1476267343319", "id": "57fe0d4f2e1e0912475a99c5", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第536章 【:任鸿的准备】", "link": "http://vip.zhuishushenqi.com/chapter/57ff0ef831bbf186321d19a5?cv=1476333304962", "id": "57ff0ef831bbf186321d19a5", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第537章 【:太空大浪潮时代】", "link": "http://vip.zhuishushenqi.com/chapter/57ff5cd1a0e0ff0a7441095c?cv=1476353233940", "id": "57ff5cd1a0e0ff0a7441095c", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第538章 【:互喷】", "link": "http://vip.zhuishushenqi.com/chapter/58005bcfa56bba04686b33db?cv=1476418511248", "id": "58005bcfa56bba04686b33db", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第539章 【:火星上有宝贝?】", "link": "http://vip.zhuishushenqi.com/chapter/5800b811bf22a8792fb14629?cv=1476442129319", "id": "5800b811bf22a8792fb14629", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第540章 【:弯道超车】", "link": "http://vip.zhuishushenqi.com/chapter/5801b2a38af77a297a632274?cv=1476506275720", "id": "5801b2a38af77a297a632274", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第541章 【:坑人的“北极熊”】", "link": "http://vip.zhuishushenqi.com/chapter/580204b0ae6d64fb7626960a?cv=1476527280574", "id": "580204b0ae6d64fb7626960a", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第542章 【:发射】", "link": "http://vip.zhuishushenqi.com/chapter/580301aa5ea9daac504562ec?cv=1476592042546", "id": "580301aa5ea9daac504562ec", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第543章 【:一种自豪】", "link": "http://vip.zhuishushenqi.com/chapter/5803454c2d6624da78cde266?cv=1476609356053", "id": "5803454c2d6624da78cde266", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第544章 【:共鸣的推文】", "link": "http://vip.zhuishushenqi.com/chapter/5804631ef9f575e677bdd05e?cv=1476682526814", "id": "5804631ef9f575e677bdd05e", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第545章 【:老大,我有个新发明…】", "link": "http://vip.zhuishushenqi.com/chapter/5804a7613fbe635055008be5?cv=1476700001391", "id": "5804a7613fbe635055008be5", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第546章 【:激光武器?】", "link": "http://vip.zhuishushenqi.com/chapter/5805c99a4b2ce16032ff3b85?cv=1476774298010", "id": "5805c99a4b2ce16032ff3b85", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第547章 【:要做点什么】", "link": "http://vip.zhuishushenqi.com/chapter/5805c99aa57119c862d9c03c?cv=1476774298724", "id": "5805c99aa57119c862d9c03c", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第548章 【:弧鹰三号】", "link": "http://vip.zhuishushenqi.com/chapter/5806ff3a7051db680f732552?cv=1476853562539", "id": "5806ff3a7051db680f732552", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第549章 【:太空主播——刘鹏】", "link": "http://vip.zhuishushenqi.com/chapter/5806ff3b0d9b4b4b47060b81?cv=1476853563139", "id": "5806ff3b0d9b4b4b47060b81", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第550章 【:直播进行时】", "link": "http://vip.zhuishushenqi.com/chapter/5808471bd69f097b30ca7de9?cv=1476937499417", "id": "5808471bd69f097b30ca7de9", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第551章 【:真会玩儿】", "link": "http://vip.zhuishushenqi.com/chapter/5808472098d3edbd67004d1c?cv=1476937504941", "id": "5808472098d3edbd67004d1c", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第552章 【:就叫“王江”吧】", "link": "http://vip.zhuishushenqi.com/chapter/580996688089c95c124b75b0?cv=1477023336125", "id": "580996688089c95c124b75b0", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第553章 【:训练】", "link": "http://vip.zhuishushenqi.com/chapter/5809e60a23802e0c530c6c41?cv=1477043722511", "id": "5809e60a23802e0c530c6c41", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第554章 【:火星!火星!】", "link": "http://vip.zhuishushenqi.com/chapter/5809e60b2d43032557eccd7d?cv=1477043723185", "id": "5809e60b2d43032557eccd7d", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第555章 【:“诺亚号”出发】", "link": "http://vip.zhuishushenqi.com/chapter/580b7562cd0058aa4cf2b1ae?cv=1477145954325", "id": "580b7562cd0058aa4cf2b1ae", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第556章 【:十周年庆典】", "link": "http://vip.zhuishushenqi.com/chapter/580b7564b54c5f7b0ee1d081?cv=1477145956010", "id": "580b7564b54c5f7b0ee1d081", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第557章 【:我最大的错误就是创办了矩阵科技】", "link": "http://vip.zhuishushenqi.com/chapter/580b75643acb25e106e97058?cv=1477145956818", "id": "580b75643acb25e106e97058", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第558章 【:记者刁难】", "link": "http://vip.zhuishushenqi.com/chapter/580b756778c39ef059b4e4e2?cv=1477145959111", "id": "580b756778c39ef059b4e4e2", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第559章 【:让我给你普及普及知识】", "link": "http://vip.zhuishushenqi.com/chapter/580b7569ebd9b9350131926a?cv=1477145961171", "id": "580b7569ebd9b9350131926a", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第560章 【:CMSX的动作】", "link": "http://vip.zhuishushenqi.com/chapter/580b756aff46b2544e0e3d0e?cv=1477145962594", "id": "580b756aff46b2544e0e3d0e", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第561章 【:火星快车号,出发!!!】", "link": "http://vip.zhuishushenqi.com/chapter/580b756ca3e97e9e4c58d60f?cv=1477145964083", "id": "580b756ca3e97e9e4c58d60f", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第562章 【:火星拜访者到来】", "link": "http://vip.zhuishushenqi.com/chapter/580b87f2f6684b9b41e9a0f4?cv=1477150706380", "id": "580b87f2f6684b9b41e9a0f4", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第563章 【:火星快车号】", "link": "http://vip.zhuishushenqi.com/chapter/580c949235afa40260da8db6?cv=1477219474152", "id": "580c949235afa40260da8db6", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第564章 【:‘猎户座’空降火星】", "link": "http://vip.zhuishushenqi.com/chapter/580c94924439d5cc5e75b36f?cv=1477219474214", "id": "580c94924439d5cc5e75b36f", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第565章 【:恶魔之巢】", "link": "http://vip.zhuishushenqi.com/chapter/580c94927538a9bc2e1aa65b?cv=1477219474431", "id": "580c94927538a9bc2e1aa65b", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第566章 【:红色警戒】", "link": "http://vip.zhuishushenqi.com/chapter/580e22f9061a83fc3e74b636?cv=1477321465059", "id": "580e22f9061a83fc3e74b636", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第567章 【:老美出大事了】", "link": "http://vip.zhuishushenqi.com/chapter/580e22f9e1b8bc010e88dd13?cv=1477321465255", "id": "580e22f9e1b8bc010e88dd13", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第568章 【:火星生物?】", "link": "http://vip.zhuishushenqi.com/chapter/580e22f93b4453c6108deee3?cv=1477321465491", "id": "580e22f93b4453c6108deee3", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第569章 【:袖手旁观还是伸出援手?】", "link": "http://vip.zhuishushenqi.com/chapter/580e22f91ff26a807238e82c?cv=1477321465593", "id": "580e22f91ff26a807238e82c", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第570章 【:这个嘛……难!】", "link": "http://vip.zhuishushenqi.com/chapter/580f5aaf275323437eee8a78?cv=1477401263409", "id": "580f5aaf275323437eee8a78", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第571章 【:超级大忽悠】", "link": "http://vip.zhuishushenqi.com/chapter/580f5aaf5987b75d3bcaa750?cv=1477401263482", "id": "580f5aaf5987b75d3bcaa750", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第572章 【:只有两个玩家】", "link": "http://vip.zhuishushenqi.com/chapter/5810b7e718b3abee43d47150?cv=1477490663436", "id": "5810b7e718b3abee43d47150", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第573章 【:消息】", "link": "http://vip.zhuishushenqi.com/chapter/5810b7e73cc78e4a25f0993f?cv=1477490663549", "id": "5810b7e73cc78e4a25f0993f", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第574章 【:世纪忽悠】", "link": "http://vip.zhuishushenqi.com/chapter/5810b7e79d0094856da9e5cc?cv=1477490663846", "id": "5810b7e79d0094856da9e5cc", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第575章 【:新指令】", "link": "http://vip.zhuishushenqi.com/chapter/5811df3b4b4c5f611fdc6af8?cv=1477566267265", "id": "5811df3b4b4c5f611fdc6af8", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第576章 【:联合出征】", "link": "http://vip.zhuishushenqi.com/chapter/5811df3ce01dbefe6aa3842f?cv=1477566268721", "id": "5811df3ce01dbefe6aa3842f", "currency": 20, "unreadble": false, "isVip": true }, { "title": "第577章 【:汇合】", "link": "http://vip.zhuishushenqi.com/chapter/5813519454a90a0b75e5cc91?cv=1477661076109", "id": "5813519454a90a0b75e5cc91", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第578章 【:目标火星:出发!】", "link": "http://vip.zhuishushenqi.com/chapter/58135194f2f977446ec16323?cv=1477661076347", "id": "58135194f2f977446ec16323", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第579章 【:大航天时代的格局】", "link": "http://vip.zhuishushenqi.com/chapter/581351940f87d06344b5915e?cv=1477661076487", "id": "581351940f87d06344b5915e", "currency": 20, "unreadble": false, "isVip": true }, { "title": "第580章 【:邓肯.琼斯回忆】", "link": "http://vip.zhuishushenqi.com/chapter/58149508a0936f565a45d2a1?cv=1477743880739", "id": "58149508a0936f565a45d2a1", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第581章 【:发现】", "link": "http://vip.zhuishushenqi.com/chapter/5814950c201f0a3669d56045?cv=1477743884161", "id": "5814950c201f0a3669d56045", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第582章 【:红鹰系】", "link": "http://vip.zhuishushenqi.com/chapter/581604f2b3e69e05045a0426?cv=1477838066172", "id": "581604f2b3e69e05045a0426", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第583章 【:整装待发】", "link": "http://vip.zhuishushenqi.com/chapter/581604f2070e4c6b348390fc?cv=1477838066286", "id": "581604f2070e4c6b348390fc", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第584章 【:异形?你不要吓我!】", "link": "http://vip.zhuishushenqi.com/chapter/581604f2fdfff8525fcd42c7?cv=1477838066444", "id": "581604f2fdfff8525fcd42c7", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第585章 【:黑匣子】", "link": "http://vip.zhuishushenqi.com/chapter/58188bd4811e48f807528140?cv=1478003668204", "id": "58188bd4811e48f807528140", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第586章 【:蛇蜥探索者】", "link": "http://vip.zhuishushenqi.com/chapter/58188bd4fca61c8738bd3407?cv=1478003668263", "id": "58188bd4fca61c8738bd3407", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第587章 【:冬眠者】", "link": "http://vip.zhuishushenqi.com/chapter/58188bd423989c1237ea989d?cv=1478003668544", "id": "58188bd423989c1237ea989d", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第588章 【:惧光】", "link": "http://vip.zhuishushenqi.com/chapter/5819fe3857e412b7569a1783?cv=1478098488166", "id": "5819fe3857e412b7569a1783", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第589章 【:太空电梯】", "link": "http://vip.zhuishushenqi.com/chapter/581a0ead7d66fda92805e2d3?cv=1478102701114", "id": "581a0ead7d66fda92805e2d3", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第590章 【:超级工程】", "id": "581b312f3f7e954827e3b669", "link": "http://vip.zhuishushenqi.com/chapter/581b312f3f7e954827e3b669?cv=1478177071218", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第591章 【第585:全新的未来生活方式】", "id": "581b312f0e2a48c473aa769e", "link": "http://vip.zhuishushenqi.com/chapter/581b312f0e2a48c473aa769e?cv=1478177071278", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第592章 【:合作结束】", "id": "581ca8261df791354942af22", "link": "http://vip.zhuishushenqi.com/chapter/581ca8261df791354942af22?cv=1478273062230", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第593章 【:要在火星上种菜?(合并)】", "id": "581ca826058f63c64af69abc", "link": "http://vip.zhuishushenqi.com/chapter/581ca826058f63c64af69abc?cv=1478273062349", "currency": 20, "unreadble": false, "isVip": true }, { "title": "第594章 【:定个小目标】", "id": "581e26974fb4ea05193edad0", "link": "http://vip.zhuishushenqi.com/chapter/581e26974fb4ea05193edad0?cv=1478370967627", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第595章 【:魂牵梦绕的晶体矿】", "id": "581e2698e18458860464e3a4", "link": "http://vip.zhuishushenqi.com/chapter/581e2698e18458860464e3a4?cv=1478370968128", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第596章 【:能量来源问题】", "id": "581f3f75a14015a163d6194b", "link": "http://vip.zhuishushenqi.com/chapter/581f3f75a14015a163d6194b?cv=1478442869127", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第597章 【:老康,给我搞100枚核弹,最好是氢弹】", "id": "581f3f75196e9057136598fb", "link": "http://vip.zhuishushenqi.com/chapter/581f3f75196e9057136598fb?cv=1478442869285", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第598章 【:串门来了】", "id": "5820aaf2b2e71c086da81adb", "link": "http://vip.zhuishushenqi.com/chapter/5820aaf2b2e71c086da81adb?cv=1478535922962", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第599章 【:新的震动】", "id": "5820aaf371735bc406c10837", "link": "http://vip.zhuishushenqi.com/chapter/5820aaf371735bc406c10837?cv=1478535923078", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第600章 【:没钱可以给我们再免税的呀!】", "id": "5821fc522a7cd8246d2fb56e", "link": "http://vip.zhuishushenqi.com/chapter/5821fc522a7cd8246d2fb56e?cv=1478622290038", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第601章 【:卖炸弹的人】", "id": "5821fc52640ac65f61e8bbe4", "link": "http://vip.zhuishushenqi.com/chapter/5821fc52640ac65f61e8bbe4?cv=1478622290143", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第602章 【:还是卖吧】", "id": "5821fc521326b77c431d1829", "link": "http://vip.zhuishushenqi.com/chapter/5821fc521326b77c431d1829?cv=1478622290190", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第603章 【:一封信】", "id": "582362cf0f185b885bb6b36e", "link": "http://vip.zhuishushenqi.com/chapter/582362cf0f185b885bb6b36e?cv=1478714063460", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第604章 【:西江通水,全球瞩目】", "id": "582362cffd10e91758a76118", "link": "http://vip.zhuishushenqi.com/chapter/582362cffd10e91758a76118?cv=1478714063523", "currency": 20, "unreadble": false, "isVip": true }, { "title": "第605章 【:轰动世界】", "id": "5824b6a7f48986557280f57c", "link": "http://vip.zhuishushenqi.com/chapter/5824b6a7f48986557280f57c?cv=1478801063464", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第606章 【:重力抵消,反重力?】", "id": "5824b6a78cb12a9f5ed5bdd7", "link": "http://vip.zhuishushenqi.com/chapter/5824b6a78cb12a9f5ed5bdd7?cv=1478801063548", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第607章 【:双星共振】", "id": "5824b6a73a70e4c773d142f1", "link": "http://vip.zhuishushenqi.com/chapter/5824b6a73a70e4c773d142f1?cv=1478801063610", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第608章 【:星门的局限性】", "id": "58251467f6cb5bad0a34dee0", "link": "http://vip.zhuishushenqi.com/chapter/58251467f6cb5bad0a34dee0?cv=1478825063924", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第609章 【:技术探讨】", "id": "5826a7905b2671c6301f3d1b", "link": "http://vip.zhuishushenqi.com/chapter/5826a7905b2671c6301f3d1b?cv=1478928272067", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第610章 【:新引擎技术构想】", "id": "5826a790e3231daa5435cbbc", "link": "http://vip.zhuishushenqi.com/chapter/5826a790e3231daa5435cbbc?cv=1478928272130", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第611章 【:曲率引擎?】", "id": "5826a790507608e140f71482", "link": "http://vip.zhuishushenqi.com/chapter/5826a790507608e140f71482?cv=1478928272185", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第612章 【:缺憾和独一无二的作用】", "id": "5826a7906bf0da6109d9df25", "link": "http://vip.zhuishushenqi.com/chapter/5826a7906bf0da6109d9df25?cv=1478928272248", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第613章 【:亚空间传送跳跃】", "id": "5826a7906e7ed81734a3b908", "link": "http://vip.zhuishushenqi.com/chapter/5826a7906e7ed81734a3b908?cv=1478928272308", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第614章 【:摊牌?】", "id": "5827c57de6ab22955fd09c6d", "link": "http://vip.zhuishushenqi.com/chapter/5827c57de6ab22955fd09c6d?cv=1479001469027", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第615章 【:你会大吃一惊的】", "id": "5829958b907bdf5854d13afa", "link": "http://vip.zhuishushenqi.com/chapter/5829958b907bdf5854d13afa?cv=1479120267109", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第616章 【:合作才是未来】", "id": "5829958b3ef4fcd97aa144dd", "link": "http://vip.zhuishushenqi.com/chapter/5829958b3ef4fcd97aa144dd?cv=1479120267160", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第617章 【:新的颠覆】", "id": "5829958b5da52c1154866576", "link": "http://vip.zhuishushenqi.com/chapter/5829958b5da52c1154866576?cv=1479120267250", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第618章 【:LuixS重磅消息】", "id": "582a8e08f5cefdbf127797ff", "link": "http://vip.zhuishushenqi.com/chapter/582a8e08f5cefdbf127797ff?cv=1479183880694", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第619章 【:那就自己建造一座】", "id": "582a8e0821788b0070c246ac", "link": "http://vip.zhuishushenqi.com/chapter/582a8e0821788b0070c246ac?cv=1479183880805", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第620章 【:新直辖市?】", "id": "582a8e0870ef63367c56c706", "link": "http://vip.zhuishushenqi.com/chapter/582a8e0870ef63367c56c706?cv=1479183880861", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第621章 【:新品发布会】", "id": "582a8e0986c200ca51696207", "link": "http://vip.zhuishushenqi.com/chapter/582a8e0986c200ca51696207?cv=1479183881115", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第622章 【:牛顿的棺材板压不住啦】", "id": "582bcf14babc7e6859e1766c", "link": "http://vip.zhuishushenqi.com/chapter/582bcf14babc7e6859e1766c?cv=1479266068079", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第623章 【:萌即正义!】", "id": "582bcf14d8df79142166481a", "link": "http://vip.zhuishushenqi.com/chapter/582bcf14d8df79142166481a?cv=1479266068273", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第624章 【:我们又一揽子庞大的计划】", "id": "582bcf1416be22d64df978c8", "link": "http://vip.zhuishushenqi.com/chapter/582bcf1416be22d64df978c8?cv=1479266068880", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第625章 【:立体化交通网络】", "id": "582d9377fa4186423b7bc99a", "link": "http://vip.zhuishushenqi.com/chapter/582d9377fa4186423b7bc99a?cv=1479381879704", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第626章 【:惊颤的汽车巨头们】", "id": "582d93781a5ead0b09780926", "link": "http://vip.zhuishushenqi.com/chapter/582d93781a5ead0b09780926?cv=1479381880729", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第627章 【:巨头之间的通讯】", "id": "582d937873b733df5f48543f", "link": "http://vip.zhuishushenqi.com/chapter/582d937873b733df5f48543f?cv=1479381880936", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第628章 【:世界为之疯狂——博宁市】", "id": "582d93798f2588b106b45f37", "link": "http://vip.zhuishushenqi.com/chapter/582d93798f2588b106b45f37?cv=1479381881098", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第629章 【:牵动世界心弦的未来之城】", "id": "582ef7fb69f52f441ad7833c", "link": "http://vip.zhuishushenqi.com/chapter/582ef7fb69f52f441ad7833c?cv=1479473147697", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第630章 【:新直辖市确立】", "id": "582ef8039976b7361628d9d1", "link": "http://vip.zhuishushenqi.com/chapter/582ef8039976b7361628d9d1?cv=1479473155230", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第631章 【:没空!】", "id": "582ef803569b08b64d4d44e6", "link": "http://vip.zhuishushenqi.com/chapter/582ef803569b08b64d4d44e6?cv=1479473155925", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第632章 【:博宁中心大厦】", "id": "582ef8050ce460ca3c004f6d", "link": "http://vip.zhuishushenqi.com/chapter/582ef8050ce460ca3c004f6d?cv=1479473157505", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第633章 【:“博宁馆”开放】", "id": "582ef806c5797b6c205c1e2a", "link": "http://vip.zhuishushenqi.com/chapter/582ef806c5797b6c205c1e2a?cv=1479473158412", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第634章 【:变态的安保系数】", "id": "5830574bffd5e8815a9b33e6", "link": "http://vip.zhuishushenqi.com/chapter/5830574bffd5e8815a9b33e6?cv=1479563083025", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第635章 【:世界第一楼】", "id": "5830574b12ba5dd87338e99e", "link": "http://vip.zhuishushenqi.com/chapter/5830574b12ba5dd87338e99e?cv=1479563083166", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第636章 【:迪士尼中标】", "id": "5830574bf5d2c5616090009d", "link": "http://vip.zhuishushenqi.com/chapter/5830574bf5d2c5616090009d?cv=1479563205239", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第637章 【:浮空水立方】", "id": "5830574cd17c392b6adae041", "link": "http://vip.zhuishushenqi.com/chapter/5830574cd17c392b6adae041?cv=1479563084214", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第638章 【:全民热议】", "id": "5830574cea7ec74b5f4f2f95", "link": "http://vip.zhuishushenqi.com/chapter/5830574cea7ec74b5f4f2f95?cv=1479563084307", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第639章 【:发酵】", "id": "5831707b26b8505d1f061cac", "link": "http://vip.zhuishushenqi.com/chapter/5831707b26b8505d1f061cac?cv=1479635067035", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第640章 【:你们赚博宁的钱,我赚全世界的钱】", "id": "5831707b1160774666b7977e", "link": "http://vip.zhuishushenqi.com/chapter/5831707b1160774666b7977e?cv=1479635067191", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第641章 【:大迁移】", "id": "5832cb76956b026e4c90f02f", "link": "http://vip.zhuishushenqi.com/chapter/5832cb76956b026e4c90f02f?cv=1479723894614", "currency": 10, "unreadble": false, "isVip": true }, { "title": "第642章 【:开山平地M系】", "id": "5832cb7750574eb45c73b997", "link": "http://vip.zhuishushenqi.com/chapter/5832cb7750574eb45c73b997?cv=1479723895410", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第643章 【:愤怒的“三哥”】", "id": "5832cb777b0958906f5b79ae", "link": "http://vip.zhuishushenqi.com/chapter/5832cb777b0958906f5b79ae?cv=1479723895550", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第644章 【:两个支线选择】", "id": "5834610c55ffef5f1d1f6cb3", "link": "http://vip.zhuishushenqi.com/chapter/5834610c55ffef5f1d1f6cb3?cv=1479827724437", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第645章 【:浮空高铁】", "id": "5834610c49b3284e3d829439", "link": "http://vip.zhuishushenqi.com/chapter/5834610c49b3284e3d829439?cv=1479827724639", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第646章 【:惯性动能驱动】", "id": "5835d0cd612b5d7f51d0da18", "link": "http://vip.zhuishushenqi.com/chapter/5835d0cd612b5d7f51d0da18?cv=1479921869598", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第647章 【:空客被黑的最惨的一次】", "id": "5835d0cd8dc617580f8286c4", "link": "http://vip.zhuishushenqi.com/chapter/5835d0cd8dc617580f8286c4?cv=1479921869709", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第648章 【:“三哥”被坑】", "id": "5836b453caba658b4cbecb57", "link": "http://vip.zhuishushenqi.com/chapter/5836b453caba658b4cbecb57?cv=1479980115351", "currency": 15, "unreadble": false, "isVip": true }, { "title": "第649章 【:被坑的最惨的一次】", "id": "5836b454b28e531877f1db13", "link": "http://vip.zhuishushenqi.com/chapter/5836b454b28e531877f1db13?cv=1479980116044", "currency": 10, "unreadble": false, "isVip": true } ], "updated": "2016-11-24T09:35:16.049Z", "host": "vip.zhuishushenqi.com" } ================================================ FILE: zhuishushenqi/TXTReader/BookDetail/Models/PageInfo.swift ================================================ // // PageInfo.swift // TXTReader // // Created by Nory Cao on 16/11/24. // Copyright © 2016年 QS. All rights reserved. // import UIKit class PageInfo: NSObject,NSCoding { //章节信息 var chapterInfo:ChapterInfo? //当前页 var pageIndex:Int? = 0 //总页数 var total:Int? = 1 init(_ chapterInfo:ChapterInfo,pageIndex:Int,total:Int) { super.init() self.chapterInfo = chapterInfo self.pageIndex = pageIndex self.total = total } func encode(with aCoder: NSCoder) { aCoder.encode(self.chapterInfo, forKey: "chapterInfo") aCoder.encode(self.pageIndex, forKey: "pageIndex") aCoder.encode(self.total, forKey: "total") } required init?(coder aDecoder: NSCoder) { super.init() self.chapterInfo = aDecoder.decodeObject(forKey: "chapterInfo") as? ChapterInfo self.pageIndex = aDecoder.decodeObject(forKey: "pageIndex") as? Int self.total = aDecoder.decodeObject(forKey: "total") as? Int } } ================================================ FILE: zhuishushenqi/TXTReader/BookDetail/Models/QSBook.swift ================================================ // // QSBook.swift // TXTReader // // Created by Nory Cao on 2017/4/14. // Copyright © 2017年 masterY. All rights reserved. // import UIKit /* 暂时不需要这个类,考虑废弃掉 */ class QSBook: NSObject,NSCoding { // 只在内存中使用,释放后不保存 var localChapters:[QSChapter] = [] var chapters:[QSChapter] = [] var totalChapters:Int = 1 var bookID:String = "" //bookID为在追书中的ID var bookName:String = "" var resources:[ResourceModel]?//书籍来源,这里面有所有的来源id var curRes:Int = 0 //dhqm选择来源 required init?(coder aDecoder: NSCoder) { self.localChapters = aDecoder.decodeObject(forKey: "localChapters") as? [QSChapter] ?? [] self.chapters = aDecoder.decodeObject(forKey: "chapters") as! [QSChapter] self.totalChapters = aDecoder.decodeInteger(forKey: "totalChapters") self.bookID = aDecoder.decodeObject(forKey: "bookID") as? String ?? "" self.bookName = aDecoder.decodeObject(forKey: "bookName") as? String ?? "" self.resources = aDecoder.decodeObject(forKey: "resources") as? [ResourceModel] self.curRes = aDecoder.decodeInteger(forKey: "curRes") } override init() { } func encode(with aCoder: NSCoder) { aCoder.encode(localChapters, forKey: "localChapters") aCoder.encode(chapters, forKey: "chapters") aCoder.encode(totalChapters, forKey: "totalChapters") aCoder.encode(bookID, forKey: "bookID") aCoder.encode(bookName, forKey: "bookName") aCoder.encode(resources, forKey: "resources") aCoder.encode(curRes, forKey: "curRes") } } ================================================ FILE: zhuishushenqi/TXTReader/BookDetail/Models/QSBookList.swift ================================================ // // QSBookList.swift // zhuishushenqi // // Created by yung on 2017/4/21. // Copyright © 2017年 QS. All rights reserved. // import UIKit import HandyJSON @objc(QSBookList) class QSBookList: NSObject ,HandyJSON{ var id:String = "" var title:String = "" var author:String = "" var desc:String = "" var bookCount:Int = 0 var cover:String = "" var collectorCount:Int = 0 required override init() { } } ================================================ FILE: zhuishushenqi/TXTReader/BookDetail/Models/QSChapter.swift ================================================ // // QSChapter.swift // TXTReader // // Created by Nory Cao on 2017/4/14. // Copyright © 2017年 masterY. All rights reserved. // import UIKit /* * 章节信息保存到NSDictionary中,key为link或者id * value为QSChapter模型,不需要持久化,每次内存释放则释放 */ /* { "http://book.my716.com/getBooks.aspx?method=content&bookId=191699&chapterFile=U_191699_201711081601557155_5451_1.txt":QSChapter } */ class QSChapter: NSObject ,NSCoding{ var id:String = "" //章节id,用于查询章节信息,vip来源才有,此处忽略 var link:String = "" //非vip来源没有id,只有link var title:String = ""// 章节标题 var isVip:Bool = false // 是否已经解密 var isDecrypted:Bool = false //章节主要内容,下载多章节时,内存会爆掉,所以只在获取章节显示时才去计算 var content:String = "" var order:Int = 0 var currency:Int = 0 // vip章节 var cpContent:String = "" //所有页面信息 var pages:[QSPage] = [] //章节划分,根据attribute划分为很多页,每页的范围只在QSBook中使用 var ranges:[NSRange] = [] var curChapter:Int = 0 @discardableResult func getPages()->[QSPage]{ if self.ranges.count > 0 && self.ranges[0] != NSMakeRange(0, 0){ return self.pages } var isZSSQSource:Bool = false if !isDecrypted && isVip { let page = QSPage() page.isDecrypted = isDecrypted page.isVip = isVip page.curChapter = curChapter page.title = title page.totalPages = 1 page.curPage = 0 self.pages = [page] return [page] } if id == "" { if !content.isEmpty { let size = QSReaderFrame let attributes = QSReaderSetting.shared.attributes() self.ranges = QSReaderParse.pageWithAttributes(attrubutes: attributes, constrainedToFrame: size, string: self.content) } } else { isZSSQSource = true let size = QSReaderFrame let attributes = QSReaderSetting.shared.attributes() self.ranges = QSReaderParse.pageWithAttributes(attrubutes: attributes, constrainedToFrame: size, string: self.cpContent) } var pages:[QSPage] = [] for item in 0..Void)?){ // unsigned long encode = CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingGB_18030_2000); let encode = CFStringConvertEncodingToNSStringEncoding(CFStringEncoding(CFStringEncodings.GB_18030_2000.rawValue)) let url = URL(fileURLWithPath: path) if let content = try? String(contentsOf: url, encoding: String.Encoding(rawValue: encode)) { let book = self.chapterInfo(text: content, path: path) completion?(book) } else if let content = try? String(contentsOf: url, encoding: .utf8) { let book = self.chapterInfo(text: content, path: path) completion?(book) } else if let content = try? String(contentsOf: url, encoding: .unicode) { let book = self.chapterInfo(text: content, path: path) completion?(book) } } class func content(shelf:ZSShelfModel) ->String { let path = NSHomeDirectory().appending(shelf.bookUrl) let gbk = CFStringConvertEncodingToNSStringEncoding(CFStringEncoding(CFStringEncodings.GB_18030_2000.rawValue)) let gb2312 = CFStringConvertEncodingToNSStringEncoding(CFStringEncoding(CFStringEncodings.GB_2312_80.rawValue)) let utf8 = String.Encoding.utf8 let unicode = String.Encoding.unicode let encodeArray = [String.Encoding(rawValue: gbk), String.Encoding(rawValue: gb2312), utf8, unicode] let url = URL(fileURLWithPath: path) let data = try! Data(contentsOf: url) let encoding = NSString.fileEncoding(path) guard let result = String.stringFromDataDetectingEncoding(data: data, suggestedEncodings:encodeArray) else { return "" } return result.0 } // failed to get chardet, use NSString.fileEncoding class func encode(path:String) ->UnsafeMutablePointer? { var buf:UnsafeMutablePointer? let file = fopen(path, "r") if file == nil { print("打开文件失败!") return nil } let len = fread(buf, CChar.bitWidth, 2048, file) fclose(file) let ud = uchardet_new() if uchardet_handle_data(ud, buf, len) != 0 { print("分析编码失败!") return nil } uchardet_data_end(ud) let encode = uchardet_get_charset(ud) print("文本的编码方式是:\(String(cString: encode!))") uchardet_delete(ud) return UnsafeMutablePointer(mutating: encode) } class func parse(shelf:ZSShelfModel)-> ZSAikanParserModel? { if shelf.bookType == .online { return nil } //章节名过滤,只有特定的名称才能识别,不过可以更改正则,做更多的的适配 let parten = "第[0-9一二三四五六七八九十百千]*[章回节].*" let txt = content(shelf: shelf) let reg = try? NSRegularExpression(pattern: parten, options: .caseInsensitive) if let match = reg?.matches(in: txt, options: .reportCompletion, range: NSMakeRange(0, txt.nsString.length)) { let book = ZSAikanParserModel() book.bookName = shelf.bookName book.bookAuthor = shelf.author book.bookUrl = shelf.bookUrl book.bookType = shelf.bookType var lastRange:NSRange? var chapters:[ZSBookChapter] = [] for index in 0..BookDetail?{ //章节名过滤,只有特定的名称才能识别,不过可以更改正则,做更多的的适配 let parten = "第[0-9一二三四五六七八九十百千]*[章回].*" let content = text as NSString let reg = try? NSRegularExpression(pattern: parten, options: .caseInsensitive) if let match = reg?.matches(in: text, options: .reportCompletion, range: NSMakeRange(0, content.length)) { let book = BookDetail() if match.count > 0 { book.title = (path as NSString).lastPathComponent var lastRange:NSRange? var chapters:[QSChapter] = [] for index in 0..String{ var spaceStr:String = str spaceStr = spaceStr.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) return spaceStr } //耗时操作,只在显示章节时才去计算,计算完成将会缓存,在改变约束,或者换源时重置 public class func pageWithAttributes(attrubutes:[NSAttributedString.Key:Any]?,constrainedToFrame frame:CGRect,string:String)->[NSRange]{ // 记录 let date = NSDate() var rangeArray:[NSRange] = [] // 拼接字符串 let attrString = NSMutableAttributedString(string: string, attributes: attrubutes) let frameSetter = CTFramesetterCreateWithAttributedString(attrString as CFAttributedString) let path = CGPath(rect: frame, transform: nil) var range = CFRangeMake(0, 0) var rangeOffset:NSInteger = 0 repeat{ let frame = CTFramesetterCreateFrame(frameSetter, CFRangeMake(rangeOffset, 0), path, nil) range = CTFrameGetVisibleStringRange(frame) rangeArray.append(NSMakeRange(rangeOffset, range.length)) rangeOffset += range.length }while(range.location + range.length < attrString.length) let millionSecond = NSDate().timeIntervalSince(date as Date) QSLog("耗时:\(millionSecond)") return rangeArray } } ================================================ FILE: zhuishushenqi/TXTReader/BookDetail/Models/QSReaderSetting.swift ================================================ // // QSReaderSetting.swift // zhuishushenqi // // Created by yung on 2018/2/6. // Copyright © 2018年 QS. All rights reserved. // import UIKit let ZSReaderAnimationStyleChangeNotification = "ZSReaderAnimationStyleChangeNotification" let QSReaderSettingKey = "QSReaderSettingKey" let QSReaderPagesClearNotificationKey = "QSReaderPagesClearNotificationKey" let QSReaderLineSpace:CGFloat = 10 let QSReaderParagraphSpace:CGFloat = 5 // 阅读器的阅读设置,全局设置 enum QSReaderFontStyle:Int { case system case lantingBalck case kaiti case weibei case yapi case pianpianti case lishu case riwen } @objc enum QSReaderPageStyle:Int { case curlPage = 0 case simple = 1 case scroll = 2 func description() ->String { switch self { case .curlPage: return "curlPage" case .simple: return "simple" case .scroll: return "scroll" } } } // 翻页方式属于全局 @objc enum ZSReaderAnimationStyle:Int { case curlPage = 0// 仿真翻页 case none // 无翻页动画 case horMove // 左右平移 case horizonal // 左右覆盖 case vertical // 上下覆盖 case verMove //上下平移 case verScroll //上下滚屏 func description() ->String { switch self { case .curlPage: return "curlPage" case .none: return "none" case .horizonal: return "horizonal" case .vertical: return "vertical" case .horMove: return "horMove" case .verMove: return "verMove" case .verScroll: return "verScroll" } } } // 简体与繁体 @objc enum QSReaderChineseFontStyle:Int { case simpleChinese = 0 case tranditionalChinese } @objc enum QSReaderBackgroundStyle:Int { case white case yellow case green } // 阅读器屏幕方向,默认竖屏 @objc enum QSReaderOrientation:Int { case portrait case landscape } // MARK: - 此种模式不是全局模式,而是根据书籍的信息来进行的设置,保存到对应的book的model中 // 搜索模式,退出时保存设置 @objc enum QSReaderSearchMode:Int { case zhuishu case baidu case shenma case tieba } // 阅读器能设置的最小字体 let QSReaderFontSizeMin:NSInteger = 15; // 阅读器能设置的最大字体 let QSReaderFontSizeMax:NSInteger = 30; let QSReaderBrightnessMin = 0 let QSReaderBrightnessMax = 1 // 阅读器显示文字区域的frame,要适配新的设备 let QSReaderFrame = CGRect(x: 10, y: STATEBARHEIGHT, width: UIScreen.main.bounds.width - 20, height: UIScreen.main.bounds.height - STATEBARHEIGHT - 30) @objcMembers class QSReaderSetting: NSObject { var fontSize:NSInteger = QSReaderFontSizeMin { didSet { fontSizeAction() } } var fontStyle:QSReaderFontStyle = .system { didSet { fontStyleAction() } } var pageStyle:ZSReaderAnimationStyle = .none { didSet { pageStyleAction() } } var brightness:Float = 1 { didSet { brightnessAction() } } var chineseFontStyle:QSReaderChineseFontStyle = .simpleChinese { didSet { chineseFontStyleAction() } } var lineSpace:CGFloat = QSReaderLineSpace var paragraphSpace:CGFloat = QSReaderParagraphSpace var background:QSReaderBackgroundStyle = .white { didSet { backgroundStyleAction() } } static var shared = QSReaderSetting(dict: nil) public func attributes() -> [NSAttributedString.Key:Any]{ let paragraphStyle = NSMutableParagraphStyle() paragraphStyle.lineSpacing = QSReaderLineSpace paragraphStyle.paragraphSpacing = QSReaderParagraphSpace let font = readerFont() let attributes = [NSAttributedString.Key.font:font, NSAttributedString.Key.paragraphStyle:paragraphStyle] as [NSAttributedString.Key : Any] return attributes } public func readerFont() -> UIFont{ // 系统字体经常变化,因此固定一个字体 var font:UIFont = UIFont(name: "ArialMT", size: CGFloat(fontSize))! switch fontStyle { case .system: font = UIFont(name: "ArialMT", size: CGFloat(fontSize))! break case .lantingBalck: font = UIFont(name: "ArialMT", size: CGFloat(fontSize))! break default: font = UIFont(name: "ArialMT", size: CGFloat(fontSize))! break } return font } fileprivate init(dict:Any?) { super.init() let reader:[String:Any]? = UserDefaults.standard.value(forKey: QSReaderSettingKey) as? [String : Any] if let tmp = reader { setValuesForKeys(tmp) } } fileprivate override init() { super.init() let reader:[String:Any]? = UserDefaults.standard.value(forKey: QSReaderSettingKey) as? [String : Any] if let tmp = reader { setValuesForKeys(tmp) } } fileprivate func fontSizeAction(){ if fontSize > QSReaderFontSizeMax { QSLog("fontSize:\(fontSize)\n 超出了限制") fontSize = QSReaderFontSizeMax return } if fontSize < QSReaderFontSizeMin { QSLog("fontSize:\(fontSize)\n 超出了限制") fontSize = QSReaderFontSizeMin return } save() // 通知chapter重新计算page NotificationCenter.qs_postNotification(name: QSReaderPagesClearNotificationKey,obj:nil) } fileprivate func fontStyleAction(){ save() } fileprivate func pageStyleAction(){ NotificationCenter.qs_postNotification(name: ZSReaderAnimationStyleChangeNotification, obj: nil) save() } fileprivate func brightnessAction(){ save() } fileprivate func backgroundStyleAction(){ save() } fileprivate func chineseFontStyleAction(){ save() } fileprivate func save(){ let reader = fetchProperties() UserDefaults.standard.set(reader, forKey: QSReaderSettingKey) } } ================================================ FILE: zhuishushenqi/TXTReader/BookDetail/Models/QSReaderViewFlowLayout.swift ================================================ // // QSReaderViewFlowLayout.swift // zhuishushenqi // // Created by yung on 2018/2/11. // Copyright © 2018年 QS. All rights reserved. // import UIKit class QSReaderViewFlowLayout: UICollectionViewFlowLayout { override init() { super.init() } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } } ================================================ FILE: zhuishushenqi/TXTReader/BookDetail/Models/QSRecomment.swift ================================================ // // QSRecomment.swift // zhuishushenqi // // Created by yung on 2017/4/21. // Copyright © 2017年 QS. All rights reserved. // import UIKit @objc(QSRecomment) class QSRecomment: NSObject { var id:String = "" var title:String = "" var author:String = "" var site:String = "" var cover:String = "" var shortIntro:String = "" var lastChapter:String = "" var retentionRatio:CGFloat = 0 var latelyFollower:Int = 0 var cat:String = "" var majorCate:String = "" var minorCate:String = "" } ================================================ FILE: zhuishushenqi/TXTReader/BookDetail/Models/QSRecord.swift ================================================ // // QSRecord.swift // zhuishushenqi // // Created by yung on 2018/2/11. // Copyright © 2018年 QS. All rights reserved. // import UIKit class QSRecord: NSObject,NSCoding { // 阅读记录 var chapter: Int = 0 //最后阅读的章节 var page:Int = 0 //最后阅读的页数 var bookId:String = "" //阅读的bookid // 当前选择的来源 var source:ResourceModel? var animatedComplete:Bool? // 阅读动画完成与否 var chapterModel:QSChapter? // 当前阅读到的章节模型 // 暂未使用,后期做提前缓存用 var nextChapter:QSChapter? required init?(coder aDecoder: NSCoder) { super.init() self.chapter = aDecoder.decodeInteger(forKey: "chapter") self.page = aDecoder.decodeInteger(forKey: "page") self.bookId = aDecoder.decodeObject(forKey:"bookId") as! String self.chapterModel = aDecoder.decodeObject(forKey:"chapterModel") as? QSChapter self.source = aDecoder.decodeObject(forKey: "source") as? ResourceModel // self.animatedComplete = aDecoder.decodeBool(forKey: "animatedComplete") } func encode(with aCoder: NSCoder) { aCoder.encode(self.chapter, forKey: "chapter") aCoder.encode(self.page, forKey: "page") aCoder.encode(self.bookId, forKey: "bookId") aCoder.encode(self.chapterModel, forKey: "chapterModel") aCoder.encode(self.source, forKey: "source") // aCoder.encode(self.animatedComplete, forKey: "animatedComplete") } override init() { } } ================================================ FILE: zhuishushenqi/TXTReader/BookDetail/Models/ResourceModel.swift ================================================ // // Resource.swift // zhuishushenqi // // Created by Nory Cao on 2017/3/6. // Copyright © 2017年 QS. All rights reserved. // import UIKit import HandyJSON enum ResourceType { case vip //vip源,需登录购买 case free//免费源,有多个 } //某一章的源 @objc(ResourceModel) class ResourceModel: NSObject,NSCoding ,HandyJSON{ // {"_id": "570769fe326011945ee8612d", // "lastChapter": "第1154章 内心的喜悦", // "link": "http://book.my716.com/getBooks.aspx?method=chapterList&bookId=634203", // "source": "my176", // "name": "176小说", // "isCharge": false, // "chaptersCount": 1151, // "updated": "2017-03-06T01:10:48.432Z", // "starting": false, // "host": "book.my716.com"} //来源类型 var sourceType:ResourceType = .free var _id:String = "" //当前源的id var lastChapter = "" var link = "" var source = "" { didSet{ if source.contains("zhuishuvip") { sourceType = .vip } } } var name = "" var isCharge = false var chaptersCount = 0 var updated = "" var starting = false var host = "" required override init() { } required init?(coder aDecoder: NSCoder) { // self.sourceType = aDecoder.decodeObject(forKey: "sourceType") as? ResourceType ?? .free self._id = aDecoder.decodeObject(forKey: "id") as? String ?? "" self.lastChapter = aDecoder.decodeObject(forKey: "lastChapter") as? String ?? "" self.link = aDecoder.decodeObject(forKey: "link") as? String ?? "" self.source = aDecoder.decodeObject(forKey: "source") as? String ?? "" self.name = aDecoder.decodeObject(forKey: "name") as? String ?? "" self.isCharge = aDecoder.decodeBool(forKey: "isCharge") self.chaptersCount = aDecoder.decodeInteger(forKey: "chaptersCount") self.updated = aDecoder.decodeObject(forKey: "updated") as? String ?? "" self.starting = aDecoder.decodeBool(forKey: "starting") self.host = aDecoder.decodeObject(forKey: "host") as? String ?? "" } func encode(with aCoder: NSCoder) { // aCoder.encode(self.sourceType, forKey: "sourceType") aCoder.encode(self._id, forKey: "id") aCoder.encode(self.lastChapter, forKey: "lastChapter") aCoder.encode(self.link, forKey: "link") aCoder.encode(self.source, forKey: "source") aCoder.encode(self.name, forKey: "name") aCoder.encode(self.isCharge, forKey: "isCharge") aCoder.encode(self.chaptersCount, forKey: "chaptersCount") aCoder.encode(self.updated, forKey: "updated") aCoder.encode(self.starting, forKey: "starting") aCoder.encode(self.host, forKey: "host") } } ================================================ FILE: zhuishushenqi/TXTReader/BookDetail/Models/UpdateInfo.swift ================================================ // // UpdateInfo.swift // zhuishushenqi // // Created by Nory Cao on 2017/3/6. // Copyright © 2017年 QS. All rights reserved. // import UIKit import HandyJSON @objcMembers class UpdateInfo: NSObject,NSCoding ,HandyJSON{ // [{ // "_id": "51d11e782de6405c45000068", // "author": "天蚕土豆", // "referenceSource": "sogou", // "updated": "2017-03-04T11:34:43.143Z", // "chaptersCount": 1456, // "lastChapter": "第1495章 镇压" // }] var _id:String? var author:String? var referenceSource:String? var updated:String? var chaptersCount:Int? var lastChapter:String? required override init() { } required init?(coder aDecoder: NSCoder) { self._id = aDecoder.decodeObject(forKey: "id") as? String ?? "" self.author = aDecoder.decodeObject(forKey: "author") as? String ?? "" self.referenceSource = aDecoder.decodeObject(forKey: "referenceSource") as? String ?? "" self.updated = aDecoder.decodeObject(forKey: "updated") as? String ?? "" self.chaptersCount = aDecoder.decodeObject(forKey: "chaptersCount") as? Int ?? 0 self.lastChapter = aDecoder.decodeObject(forKey: "lastChapter") as? String ?? "" } func encode(with aCoder: NSCoder) { aCoder.encode(self._id, forKey: "id") aCoder.encode(self.author, forKey: "author") aCoder.encode(self.referenceSource, forKey: "referenceSource") aCoder.encode(self.updated, forKey: "updated") aCoder.encode(self.chaptersCount, forKey: "chaptersCount") aCoder.encode(self.lastChapter, forKey: "lastChapter") } } ================================================ FILE: zhuishushenqi/TXTReader/BookDetail/Models/User.swift ================================================ // // User.swift // zhuishushenqi // // Created by Nory Cao on 2017/3/6. // Copyright © 2017年 QS. All rights reserved. // import UIKit class User: NSObject { var isLogin:Bool = false static let user = User() private override init() { } } ================================================ FILE: zhuishushenqi/TXTReader/BookDetail/Models/ZSReadRecord.swift ================================================ // // ZSReadRecord.swift // zhuishushenqi // // Created by caony on 2018/11/20. // Copyright © 2018 QS. All rights reserved. // import UIKit import HandyJSON class ZSReadRecord: NSObject, HandyJSON, NSCoding { var tocId:String? var tocName:String? var title:String? var order:Int64? var wordIndex:Int64? var updated:String? var book:String? required override init() {} func encode(with aCoder: NSCoder) { aCoder.encode(self.tocId, forKey: "tocId") aCoder.encode(self.tocName, forKey: "tocName") aCoder.encode(self.title, forKey: "title") aCoder.encode(self.order, forKey: "order") aCoder.encode(self.wordIndex, forKey: "wordIndex") aCoder.encode(self.updated, forKey: "updated") aCoder.encode(self.book, forKey: "book") } required init?(coder aDecoder: NSCoder) { self.tocId = aDecoder.decodeObject(forKey: "tocId") as? String self.tocName = aDecoder.decodeObject(forKey: "tocName") as? String self.title = aDecoder.decodeObject(forKey: "title") as? String self.order = aDecoder.decodeInt64(forKey: "order") self.wordIndex = aDecoder.decodeInt64(forKey: "wordIndex") self.updated = aDecoder.decodeObject(forKey: "updated") as? String self.book = aDecoder.decodeObject(forKey: "book") as? String } } ================================================ FILE: zhuishushenqi/TXTReader/BookDetail/QSBookDetailInteractor.swift ================================================ // // QSBookDetailInteractor.swift // zhuishushenqi // // Created Nory Cao on 2017/4/13. // Copyright © 2017年 QS. All rights reserved. // // Template generated by Juanpe Catalán @JuanpeCMiOS // import UIKit import ZSAPI class QSBookDetailInteractor: QSBookDetailInteractorProtocol { var output: QSBookDetailInteractorOutputProtocol! var bookDetail:BookDetail! var hotComment:[BookComment] = [] var bookList:[QSBookList] = [] var recList:[Book] = [] func requestData(id:String){ let api = ZSAPI.book(key: id) zs_get(api.path, parameters: nil) { (response) in if let json = response as? [AnyHashable : Any]{ if let book = BookDetail.deserialize(from: json as NSDictionary) { self.bookDetail = book self.requestHot(id: id) }else{ self.output.fetchRankFailed() } }else{ self.output.fetchRankFailed() } } } func requestHot(id:String){ let api = ZSAPI.bookHot(key: id) zs_get(api.path) { (response) in if let json = response?["reviews"] as? [Any] { if let hot = [BookComment].deserialize(from: json) as? [BookComment] { self.hotComment = hot self.output.fetchBookSuccess(bookDetail:self.bookDetail,ranks: self.hotComment) }else{ self.output.fetchRankFailed() } }else{ self.output.fetchRankFailed() } } } func requestAllChapters(withUrl url:String,param:[String:Any]){ //先查询书籍来源,根据来源返回的id再查询所有章节 zs_getObj(url, parameters: param) { (response) in if let resources = response { if let res = [ResourceModel].deserialize(from: resources as? [Any]) as? [ResourceModel] { self.output.fetchAllChapterSuccess(bookDetail: self.bookDetail, res: res) }else{ self.output.fetchAllChapterFailed() } } else { self.output.fetchAllChapterFailed() } } } func requestRecommend(){ // http://api.zhuishushenqi.com/book/51d11e782de6405c45000068/recommend let url = "\(BASEURL)/book/\(bookDetail._id)/recommend" zs_get(url) { (response) in let books = response?["books"] as? NSArray if let bookList = books { if let booklist = [Book].deserialize(from: bookList as? [Any]) as? [Book] { self.recList = booklist self.output.fetchRecommendSuccess(books: booklist) }else{ self.output.fetchRecommendFailed() } }else{ self.output.fetchRecommendFailed() } } } func requestRecList(){ // http://api.zhuishushenqi.com/book-list/51d11e782de6405c45000068/recommend?limit=3 let url = "\(BASEURL)/book-list/\(bookDetail._id)/recommend?limit=3" zs_get(url) { (response) in let books = response?["booklists"] as? NSArray if let bookList = books { if let booklist = [QSBookList].deserialize(from: bookList as? [Any]) as? [QSBookList] { self.bookList = booklist self.output.fetchRecBookSuccess(books: booklist) }else{ self.output.fetchRecBookFailed() } }else{ self.output.fetchRecBookFailed() } } } func showTopic(index:Int){ if self.bookList.count > index { self.output.showTopic(model: self.bookList[index]) } } func showBookDetail(tag:Int){ if tag >= 4 { // 可能感兴趣 self.output.showInterestedView(recList: self.recList) return } if self.recList.count > tag{ self.output.showBookDetail(model: self.recList[tag]) } } func showCommunity(){ self.output.showCommunity(model: bookDetail) } } ================================================ FILE: zhuishushenqi/TXTReader/BookDetail/QSBookDetailPresenter.swift ================================================ // // QSBookDetailPresenter.swift // zhuishushenqi // // Created Nory Cao on 2017/4/13. // Copyright © 2017年 QS. All rights reserved. // // Template generated by Juanpe Catalán @JuanpeCMiOS // import UIKit class QSBookDetailPresenter: QSBookDetailPresenterProtocol { weak var view: QSBookDetailViewProtocol? let interactor: QSBookDetailInteractorProtocol let router: QSBookDetailWireframeProtocol var ranks:[BookComment] = [] var show:Bool = false init(interface: QSBookDetailViewProtocol, interactor: QSBookDetailInteractorProtocol, router: QSBookDetailWireframeProtocol) { self.view = interface self.interactor = interactor self.router = router } func viewDidLoad(id: String) { interactor.requestData(id: id) view?.showActivityView() } func didClickReadingBtn(model:BookDetail,select:Bool){ view?.showActivityView() let allChapterUrl = "\(BASEURL)/toc" interactor.requestAllChapters(withUrl: allChapterUrl,param:["view":"summary","book":model._id ]) } func didClickPersueBtn(model:BookDetail,select:Bool){ if select { NotificationCenter.qs_postNotification(name: BOOKSHELF_ADD,obj:model) } else { NotificationCenter.qs_postNotification(name: BOOKSHELF_DELETE,obj:model) } QSLog(NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)) } func didClickCacheAll(){ } func didSelectRow(indexPath:IndexPath){ if indexPath.section == 0 { if indexPath.row == 2 { self.show = !self.show fetchContent(show: self.show) } } if indexPath.section == 1 { router.presentComment(model: ranks[indexPath.row]) }else if indexPath.section == 2 { interactor.showCommunity() }else if indexPath.section == 4{ interactor.showTopic(index: indexPath.row) } } func didClickRecBtn(btn:UIView){ let tag = btn.tag - RecBtnTag interactor.showBookDetail(tag: tag) } } extension QSBookDetailPresenter:QSBookDetailInteractorOutputProtocol{ func fetchBookSuccess(bookDetail:BookDetail,ranks:[BookComment]){ self.ranks = ranks view?.hideActivityView() view?.showResult(bookDetail: bookDetail, comment: ranks) interactor.requestRecommend() interactor.requestRecList() } func fetchRankFailed() { view?.hideActivityView() } func fetchContent(show: Bool) { view?.showContent(show: show) } func fetchAllChapterSuccess(bookDetail:BookDetail,res:[ResourceModel]){ view?.hideActivityView() router.presentReading(model: res, booDetail: bookDetail) } func fetchAllChapterFailed(){ view?.hideActivityView() } func fetchRecommendSuccess(books: [Book]) { view?.hideActivityView() view?.showRecommend(list: books) } func fetchRecommendFailed() { view?.hideActivityView() } func fetchRecBookSuccess(books: [QSBookList]) { view?.hideActivityView() view?.showRecBookList(list: books) } func fetchRecBookFailed() { view?.hideActivityView() } func showTopic(model:QSBookList){ router.presentTopic(model: model) } func showBookDetail(model: Book) { router.presentSelf(model: model) } func showCommunity(model: BookDetail) { router.presentCommunity(model: model) } func showInterestedView(recList:[Book]){ router.presentInterestedView(recList: recList) } } ================================================ FILE: zhuishushenqi/TXTReader/BookDetail/QSBookDetailProtocols.swift ================================================ // // QSBookDetailProtocols.swift // zhuishushenqi // // Created Nory Cao on 2017/4/13. // Copyright © 2017年 QS. All rights reserved. // // Template generated by Juanpe Catalán @JuanpeCMiOS // import Foundation import UIKit //MARK: Wireframe - protocol QSBookDetailWireframeProtocol: class { func presentReading(model:[ResourceModel],booDetail:BookDetail) func presentComment(model:BookComment) // func presentComment(id:String) func presentTopic(model:QSBookList) func presentSelf(model:Book) func presentCommunity(model:BookDetail) func presentInterestedView(recList:[Book]) } //MARK: Presenter - protocol QSBookDetailPresenterProtocol: class { func viewDidLoad(id:String) func didClickReadingBtn(model:BookDetail,select:Bool) func didClickPersueBtn(model:BookDetail,select:Bool) func didClickCacheAll() func didSelectRow(indexPath:IndexPath) func didClickRecBtn(btn:UIView) } //MARK: Interactor - protocol QSBookDetailInteractorProtocol: class { func requestData(id:String) func requestAllChapters(withUrl url:String,param:[String:Any]) func requestRecommend() func requestRecList() func showTopic(index:Int) func showBookDetail(tag:Int) func showCommunity() } //MARK: Output - protocol QSBookDetailInteractorOutputProtocol: class { func fetchBookSuccess(bookDetail:BookDetail,ranks:[BookComment]) func fetchRankFailed() func fetchContent(show:Bool) func fetchAllChapterSuccess(bookDetail:BookDetail,res:[ResourceModel]) func fetchAllChapterFailed() func fetchRecommendSuccess(books:[Book]) func fetchRecommendFailed() func fetchRecBookSuccess(books:[QSBookList]) func fetchRecBookFailed() func showTopic(model:QSBookList) func showBookDetail(model:Book) func showCommunity(model:BookDetail) func showInterestedView(recList:[Book]) // func fetchMayIntrestedSuccess() } //MARK: View - protocol QSBookDetailViewProtocol: IndicatableView { var presenter: QSBookDetailPresenterProtocol? { get set } func showContent(show:Bool) func showResult(bookDetail:BookDetail,comment:[BookComment]) func showRecommend(list:[Book]) func showRecBookList(list:[QSBookList]) } ================================================ FILE: zhuishushenqi/TXTReader/BookDetail/QSBookDetailRouter.swift ================================================ // // QSBookDetailRouter.swift // zhuishushenqi // // Created Nory Cao on 2017/4/13. // Copyright © 2017年 QS. All rights reserved. // // Template generated by Juanpe Catalán @JuanpeCMiOS // import UIKit class QSBookDetailRouter: QSBookDetailWireframeProtocol { weak var viewController: UIViewController? static func createModule(id:String) -> UIViewController { // Change to get view from storyboard if not using progammatic UI let view = QSBookDetailViewController(nibName: nil, bundle: nil) view.id = id let interactor = QSBookDetailInteractor() let router = QSBookDetailRouter() let presenter = QSBookDetailPresenter(interface: view, interactor: interactor, router: router) view.presenter = presenter interactor.output = presenter router.viewController = view return view } func presentReading(model:[ResourceModel],booDetail:BookDetail){ // viewController?.navigationController?.pushViewController(QSTextRouter.createModule(bookDetail:booDetail,callback: {(book:BookDetail) in // // }), animated: true) viewController?.present(QSTextRouter.createModule(bookDetail:booDetail,callback: {(book:BookDetail) in }), animated: true, completion: nil) } func presentComment(model:BookComment){ let commentVC = ZSBookCommentViewController(style: .grouped) commentVC.viewModel.model = model viewController?.navigationController?.pushViewController(commentVC, animated: true) } func presentTopic(model:QSBookList){ viewController?.navigationController?.pushViewController(QSTopicDetailRouter.createModule(id: model.id), animated: true) } func presentSelf(model:Book){ let id = model._id ?? "" viewController?.navigationController?.pushViewController(QSBookDetailRouter.createModule(id: id), animated: true) } func presentCommunity(model: BookDetail) { viewController?.navigationController?.pushViewController(QSCommunityRouter.createModule(model: model), animated: true) } func presentInterestedView(recList:[Book]){ let interestedVC = QSInterestedViewController() interestedVC.books = recList viewController?.navigationController?.pushViewController(interestedVC, animated: true) } } ================================================ FILE: zhuishushenqi/TXTReader/BookDetail/QSBookDetailViewController.swift ================================================ // // QSBookDetailViewController.swift // zhuishushenqi // // Created Nory Cao on 2017/4/13. // Copyright © 2017年 QS. All rights reserved. // // Template generated by Juanpe Catalán @JuanpeCMiOS // import UIKit class QSBookDetailViewController: BaseViewController,UITableViewDataSource,UITableViewDelegate,QSBookDetailViewProtocol { var presenter: QSBookDetailPresenterProtocol? var rateView:QSBookDetailRateView! var tagsView:QSBookDetailTagsView! var contentView:QSBookDetailContentView! var commentHeaderView:UIView! var detailHeaderView:BookDetailHeader! var id:String = "" fileprivate var tagColor = [UIColor(red: 0.56, green: 0.77, blue: 0.94, alpha: 1.0), UIColor(red: 0.75, green: 0.41, blue: 0.82, alpha: 1.0), UIColor(red: 0.96, green: 0.74, blue: 0.49, alpha: 1.0), UIColor(red: 0.57, green: 0.81, blue: 0.84, alpha: 1.0), UIColor(red: 0.40, green: 0.80, blue: 0.72, alpha: 1.0), UIColor(red: 0.91, green: 0.56, blue: 0.56, alpha: 1.0), UIColor(red: 0.56, green: 0.77, blue: 0.94, alpha: 1.0), UIColor(red: 0.75, green: 0.41, blue: 0.82, alpha: 1.0)] fileprivate var sectionTwoY:CGFloat = 0 fileprivate var bookModel:BookDetail? fileprivate var hotComment:[BookComment]? fileprivate var reBooks:[Book]? fileprivate var recList:[QSBookList]? fileprivate let CONTENT_TAG = 11223 fileprivate var contentShow:Bool = false lazy var tableView:UITableView = { let tableView = UITableView(frame: CGRect(x: 0, y: 64, width: ScreenWidth, height: ScreenHeight - 64), style: .grouped) tableView.dataSource = self tableView.delegate = self tableView.separatorStyle = .singleLine tableView.qs_registerCellNib(HotCommentCell.self) tableView.qs_registerCellNib(QSRecommendCell.self) tableView.qs_registerCellNib(QSBookListViewCell.self) tableView.backgroundColor = UIColor(red: 0.95, green: 0.95, blue: 0.95, alpha: 1.0) return tableView }() override func viewDidLoad() { super.viewDidLoad() initSubview() presenter?.viewDidLoad(id: id) } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) navigationController?.isNavigationBarHidden = false } func initSubview(){ let titleView = UIView(frame: CGRect(x: 0,y: 0,width: 120,height: 30)) let titleLabel = UILabel(frame: CGRect(x: 0,y: 0,width: 90,height: 30)) titleLabel.textAlignment = .center titleLabel.text = "书籍详情" let titleShare = UIImageView(image: UIImage(named: "bd_share")) let width = (titleLabel.text! as NSString).boundingRect(with: CGSize(width: CGFloat.greatestFiniteMagnitude, height: 30), options: .usesLineFragmentOrigin, attributes: [NSAttributedString.Key.font:UIFont.systemFont(ofSize: 13)], context: nil) titleShare.frame = CGRect(x: width.size.width/2 + 120/2, y: 5, width: 20, height: 20) let ges = UITapGestureRecognizer(target: self, action: #selector(shareAction(_:))) titleShare.addGestureRecognizer(ges) titleView.addSubview(titleShare) titleView.addSubview(titleLabel) navigationItem.titleView = titleView let rightItem = UIBarButtonItem(title: "全本缓存", style: .plain, target: self, action: #selector(allCache(_:))) navigationItem.rightBarButtonItem = rightItem getCommentHeader() getDetailHeader() getContentView() rateView = QSBookDetailRateView(frame: CGRect(x: 0, y: 0, width: self.view.bounds.width, height: 80)) tagsView = QSBookDetailTagsView(frame: CGRect(x: 0, y: 0, width: self.view.bounds.width, height: 50)) contentView = QSBookDetailContentView(frame: CGRect(x: 0, y: 0, width: self.view.bounds.width, height: 50)) view.addSubview(self.tableView) } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { let counts = [3,self.hotComment?.count ?? 0,1,1,recList?.count ?? 3] return counts[section] } func numberOfSections(in tableView: UITableView) -> Int { return 5 } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let iden = "CellIden" var cell = tableView.dequeueReusableCell(withIdentifier: iden) if cell == nil { cell = UITableViewCell(style: .subtitle, reuseIdentifier: iden) cell?.backgroundColor = UIColor.clear cell?.selectionStyle = .none cell?.contentView.layer.masksToBounds = true } if indexPath.section == 0 { cell?.accessoryType = .none let views:[UIView] = [rateView,tagsView,contentView] views[indexPath.row].removeFromSuperview() cell?.contentView.addSubview(views[indexPath.row]) }else if indexPath.section == 1{ let hotCell:HotCommentCell = tableView.qs_dequeueReusableCell(HotCommentCell.self) hotCell.model = self.hotComment?[indexPath.row] return hotCell }else if indexPath.section == 2 { cell = UITableViewCell(style: .subtitle, reuseIdentifier: iden) cell?.textLabel?.text = "\(bookModel?.title ?? "")的社区" cell?.textLabel?.font = UIFont.systemFont(ofSize: 15) cell?.detailTextLabel?.text = "共有\(bookModel?.postCount ?? 0)个帖子" cell?.detailTextLabel?.textColor = UIColor.darkGray cell?.accessoryType = .disclosureIndicator return cell! }else if indexPath.section == 3 { let reCell = tableView.qs_dequeueReusableCell(QSRecommendCell.self) reCell?.clickAction = { (btn:Any) in self.presenter?.didClickRecBtn(btn:btn as! UIView) } reCell?.books = reBooks return reCell! }else if indexPath.section == 4{ let listCell = tableView.qs_dequeueReusableCell(QSBookListViewCell.self) if let list = recList{ listCell?.book = list[indexPath.row] } return listCell! } return cell! } func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { let headerView = UIView(frame: CGRect(x: 0, y: 0, width: self.view.bounds.width, height: 50)) headerView.backgroundColor = UIColor.white let label = UILabel(frame: CGRect(x: 15, y: 0, width: self.view.bounds.width - 30, height: 50)) label.text = "推荐书单" headerView.addSubview(label) let headers:[UIView] = [detailHeaderView,commentHeaderView,UIView(),UIView(),headerView] return headers[section] } func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { let heights:[CGFloat] = [165.0,30.0,0.001,0.001,50] return heights[section] } func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { return 10 } func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { let heights:[CGFloat] = [127,60,180,100] if indexPath.section == 0 { let height = QSBookDetailContentView.height(intro: self.bookModel?.longIntro ?? "",show:self.contentShow) contentView.frame = CGRect(x: 0, y: 0, width: self.view.bounds.width, height: height) let tagHeight = QSBookDetailTagsView.height(tags: self.bookModel?.tags as? [String] ?? []) tagsView.frame = CGRect(x: 0, y: 0, width: self.view.bounds.width, height: tagHeight) let sectionOne:[CGFloat] = [80,tagHeight,height] return sectionOne[indexPath.row] }else { return heights[indexPath.section - 1] } } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) presenter?.didSelectRow(indexPath: indexPath) } func showContent(show: Bool) { contentShow = show self.contentView.contentShow = contentShow self.tableView.reloadData() } @objc func moreCommentAction(btn:UIButton){ let indexPath = IndexPath(row: 0, section: 2) presenter?.didSelectRow(indexPath: indexPath) } @objc func allCache(_ item:UIBarButtonItem){ } @objc func shareAction(_ tap:UITapGestureRecognizer){ } func getContentView(){ } func getCommentHeader(){ commentHeaderView = UIView() commentHeaderView.backgroundColor = UIColor.white let label = UILabel() label.text = "热门评论" label.font = UIFont.systemFont(ofSize: 13) label.textColor = UIColor.darkGray label.frame = CGRect(x: 15, y: 0, width: 100, height: 30) commentHeaderView.addSubview(label) let moreBtn = UIButton(type: .custom) moreBtn.frame = CGRect(x: self.view.bounds.width - 135, y: 0, width: 120, height: 30) moreBtn.setTitle("更多", for: .normal) moreBtn.setTitleColor(UIColor.darkGray, for: .normal) moreBtn.titleLabel?.font = UIFont.systemFont(ofSize: 13) moreBtn.addTarget(self, action: #selector(moreCommentAction(btn:)), for: .touchUpInside) moreBtn.contentHorizontalAlignment = .right commentHeaderView.addSubview(moreBtn) } func getDetailHeader(){ detailHeaderView = (UINib(nibName: "BookDetailHeader", bundle: nil).instantiate(withOwner: self, options: nil) as NSArray).object(at: 0) as? BookDetailHeader detailHeaderView?.model = bookModel detailHeaderView?.addBtnAction = { (isSelected:Bool,model:BookDetail) in self.presenter?.didClickPersueBtn(model: model, select: isSelected) } detailHeaderView?.startReading = {(isSelected:Bool,model:BookDetail) in self.presenter?.didClickReadingBtn(model: model, select: isSelected) } } func showResult(bookDetail:BookDetail,comment:[BookComment]){ self.bookModel = bookDetail self.hotComment = comment self.reloadData() } func showRecommend(list: [Book]) { self.reBooks = list self.tableView.reloadData() } func showRecBookList(list: [QSBookList]) { self.recList = list self.tableView.reloadData() } func reloadData(){ tagsView.tags = self.bookModel?.tags as? [String] ?? [] detailHeaderView.model = self.bookModel contentView.longIntro = self.bookModel?.longIntro ?? "" let sText = [bookModel?.latelyFollower ?? "0","\(bookModel?.retentionRatio ?? "0")%","\(bookModel?.serializeWordCount ?? "未知")"] rateView.rate = sText self.tableView.reloadData() } } ================================================ FILE: zhuishushenqi/TXTReader/BookDetail/QSInterestedViewController.swift ================================================ // // QSInterestedViewController.swift // zhuishushenqi // // Created by yung on 2017/6/26. // Copyright © 2017年 QS. All rights reserved. // import UIKit class QSInterestedViewController: BaseViewController,UITableViewDataSource,UITableViewDelegate { var books:[Book] = [] lazy var tableView:UITableView = { let tableView = UITableView(frame: CGRect(x: 0, y: 64, width: ScreenWidth, height: ScreenHeight - 64), style: .grouped) tableView.dataSource = self tableView.delegate = self tableView.sectionHeaderHeight = CGFloat.leastNormalMagnitude tableView.sectionFooterHeight = CGFloat.leastNormalMagnitude tableView.rowHeight = 93 tableView.qs_registerCellNib(TopDetailCell.self) return tableView }() override func viewDidLoad() { super.viewDidLoad() initSubview() } func initSubview(){ self.title = "你可能感兴趣" view.addSubview(self.tableView) } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return books.count } func numberOfSections(in tableView: UITableView) -> Int { return 1 } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell:TopDetailCell? = tableView.qs_dequeueReusableCell(TopDetailCell.self) cell?.backgroundColor = UIColor.white cell?.selectionStyle = .none cell!.model = books[indexPath.row] return cell! } func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { return tableView.sectionHeaderHeight } func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { return tableView.sectionHeaderHeight } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) let id = books[indexPath.row]._id ?? "" self.navigationController?.pushViewController(QSBookDetailRouter.createModule(id: id), animated: true) } } ================================================ FILE: zhuishushenqi/TXTReader/BookDetail/Views/BookCommentCell.swift ================================================ // // BookCommentCell.swift // zhuishushenqi // // Created by Nory Cao on 2017/3/13. // Copyright © 2017年 QS. All rights reserved. // import UIKit @available(iOS 9.0, *) class BookCommentCell: UITableViewCell { @IBOutlet weak var readerIcon: UIImageView! @IBOutlet weak var readerName: UILabel! @IBOutlet weak var createTime: UILabel! @IBOutlet weak var title: UILabel! @IBOutlet weak var bookCover: UIImageView! @IBOutlet weak var bookName: UILabel! @IBOutlet weak var readerRate: UILabel! @IBOutlet weak var bgView: UIView! @IBOutlet weak var titleHeight: NSLayoutConstraint! @IBOutlet weak var displayHeight: NSLayoutConstraint! @IBOutlet weak var displayView:CTDisplayView! static var totalCellHeight:CGFloat = 0 var model:BookComment? { didSet{ modelSetAction(model: model) } } var handler:CTDisplayHandler? @IBOutlet weak var stackView:UIStackView! @IBOutlet weak var approveBtn:QSToolButton! @IBOutlet weak var shareBtn:QSToolButton! @IBOutlet weak var moreBtn:QSToolButton! var rateView:RateView! @IBOutlet weak var stackViewTop: NSLayoutConstraint! var data:CoreTextData! let defaultTime = "2014-02-23T16:48:18.179Z" func layout(model:BookComment?, layout:ZSBookCTLayoutModel?) { let created = model?.created ?? defaultTime self.createTime.qs_setCreateTime(createTime: created,append: "") readerName.text = "\(model?.author.nickname ?? "") lv.\(model?.author.lv ?? 0)" title.text = "\(model?.title ?? "")" titleHeight.constant = layout?.titleHeight ?? 20 displayHeight.constant = layout?.contentHeight ?? 20 bookName.text = "\(model?.book.title ?? "")" if model?.book.cover != "" { let cover = "\(model?.book.cover ?? "")" self.bookCover.qs_setBookCoverWithURLString(urlString: cover) rateView.rate = model?.rating ?? 0 let lightRect = CGRect(x: readerRate.frame.maxX , y: readerRate.frame.minY + readerRate.frame.height/2 - 10/2, width: 60, height: 10) rateView.frame = lightRect } else { bgView.isHidden = true rateView.isHidden = true stackViewTop.constant = -bgView.frame.size.height } BookCommentCell.totalCellHeight = stackView.bottom let urlString = "\(model?.author.avatar ?? "qqqqqqqq")" self.readerIcon.qs_setAvatarWithURLString(urlString: urlString) } func modelSetAction(model:BookComment?){ let created = model?.created ?? defaultTime self.createTime.qs_setCreateTime(createTime: created,append: "") readerName.text = "\(model?.author.nickname ?? "") lv.\(model?.author.lv ?? 0)" title.text = "\(model?.title ?? "")" title.sizeToFit() setupDisplayView(model: model) displayHeight.constant = data?.height ?? 0 bookName.text = "\(model?.book.title ?? "")" if model?.book.cover != "" { let cover = "\(model?.book.cover ?? "")" self.bookCover.qs_setBookCoverWithURLString(urlString: cover) rateView.rate = model?.rating ?? 0 let lightRect = CGRect(x: readerRate.frame.maxX , y: readerRate.frame.minY + readerRate.frame.height/2 - 10/2, width: 60, height: 10) rateView.frame = lightRect } else { bgView.isHidden = true rateView.isHidden = true stackViewTop.constant = -bgView.frame.size.height } BookCommentCell.totalCellHeight = stackView.bottom let urlString = "\(model?.author.avatar ?? "qqqqqqqq")" self.readerIcon.qs_setAvatarWithURLString(urlString: urlString) } @IBAction func rightAction(_ sender: Any) { } override func awakeFromNib() { super.awakeFromNib() makeStarView() // config.width = displayView.size.width } private func setupDisplayView(model:BookComment?) { // data = CTFrameParser.parseString(model?.content ?? "", config: config) displayView.data = data displayHeight.constant = data?.height ?? 21 displayView.backgroundColor = UIColor.white displayView.handler = handler } func makeStarView(){ let lightRect = CGRect(x: readerRate.frame.maxX , y: readerRate.frame.minY + readerRate.frame.height/2 - 10/2, width: 60, height: 10) rateView = RateView(frame: lightRect, darkImage: UIImage(named: "forum_gray_star"), lightImage: UIImage(named: "forum_red_star")) rateView.rate = model?.rating ?? 0 bgView.addSubview(rateView) } override func layoutSubviews() { super.layoutSubviews() } } class QSToolButton:UIButton { override func layoutSubviews() { super.layoutSubviews() self.imageView?.frame = CGRect(x: 0, y: 0, width: ScreenWidth/3, height: 70) self.imageView?.contentMode = .center self.titleLabel?.frame = CGRect(x: 0, y: 49, width: ScreenWidth/3, height: 21) self.titleLabel?.textAlignment = .center self.contentHorizontalAlignment = .center } } ================================================ FILE: zhuishushenqi/TXTReader/BookDetail/Views/BookCommentCell.xib ================================================ ================================================ FILE: zhuishushenqi/TXTReader/BookDetail/Views/BookCommentViewCell.swift ================================================ // // BookCommentViewCell.swift // zhuishushenqi // // Created by Nory Cao on 2017/3/13. // Copyright © 2017年 QS. All rights reserved. // import UIKit import SnapKit enum CommentType { case normal case magical } class BookCommentViewCell: UITableViewCell { @IBOutlet weak var readerIcon: UIImageView! @IBOutlet weak var floor: UILabel! @IBOutlet weak var readerName: UILabel! @IBOutlet weak var createTime: UILabel! @IBOutlet weak var content: UILabel! @IBOutlet weak var reply: UILabel! @IBOutlet weak var floorWidth: NSLayoutConstraint! var contentPane:UIView = { let view = UIView() view.backgroundColor = UIColor(red: 0.95, green: 0.95, blue: 0.95, alpha: 1.0) return view }() var cellHeight:CGFloat = 44 var type:CommentType = .normal var model:BookCommentDetail? override func prepareForReuse() { super.prepareForReuse() self.floor.text = "" self.readerName.text = "" self.createTime.text = "" self.content.text = "" self.reply.text = "" } static func cellHeight(model:BookCommentDetail)->CGFloat{ let contentHeight = model.textLayout?.textBoundingRect.size.height ?? 0 var height = contentHeight + 15 + 21 + 15 if contentHeight < 19 { height = CGFloat(15 + 15 + 40) } if model.replyTo._id != "" { height += (21 + 5) } return height } func setLayout(){ self.contentPane.snp.makeConstraints{ (make) -> Void in make.top.left.right.equalTo(self.contentView) } self.readerIcon.snp.makeConstraints { (make) in make.top.left.equalTo(self.contentView).offset(15) make.width.height.equalTo(40) } self.createTime.snp.makeConstraints { (make) in make.right.equalTo(self.contentView).offset(-15) make.top.equalTo(self.floor.snp.top) make.width.equalTo(60) } self.floor.snp.makeConstraints { (make) in make.top.equalTo(self.readerIcon) make.left.equalTo(self.readerIcon.snp.right).offset(5) } self.readerName.snp.makeConstraints { (make) in make.left.equalTo(self.floor.snp.right).offset(10) make.top.equalTo(self.floor.snp.top) make.right.equalTo(self.createTime.snp.left) } self.reply.snp.makeConstraints { (make) in make.left.equalTo(self.floor.snp.left) make.bottom.equalTo(self.contentPane.snp.bottom).offset(-10) make.right.equalTo(self.contentView).offset(-15) } self.content.snp.makeConstraints { (make) in make.left.equalTo(self.floor.snp.left) make.top.equalTo(self.floor.snp.bottom).offset(5) make.right.equalTo(self.contentView).offset(-15) make.bottom.equalTo(self.reply.snp.top).offset(-10) } self.contentPane.snp.makeConstraints { (make) in make.bottom.equalTo(self.contentView.snp.bottom) } } func setupSubviews(){ self.contentView.addSubview(self.contentPane) contentPane.addSubview(readerIcon) contentPane.addSubview(floor) contentPane.addSubview(readerName) contentPane.addSubview(createTime) contentPane.addSubview(content) contentPane.addSubview(reply) setLayout() } func bind(book:BookCommentDetail){ model = book floor.text = "\(model?.floor ?? 0)楼" readerName.text = "\(model?.author.nickname ?? "") lv.\(model?.author.lv ?? 0)" let created = model?.created ?? "" self.createTime.qs_setCreateTime(createTime: created, append: "") if type == .magical { createTime.text = "\(model?.likeCount ?? 0)同感" } if let replyTo = model?.replyTo { reply.isHidden = false reply.text = "回复\(replyTo.author.nickname) (\(replyTo.floor)楼)" }else{ reply.text = "" } floorWidth.constant = model?.floorWidth ?? 0 content.text = "\(model?.content ?? "")" let urlString = "\(model?.author.avatar ?? "")" self.readerIcon.qs_setAvatarWithURLString(urlString: urlString) } override func awakeFromNib() { super.awakeFromNib() readerIcon.qs_addCornerRadius(cornerRadius: 5) } override func setSelected(_ selected: Bool, animated: Bool) { super.setSelected(selected, animated: animated) // Configure the view for the selected state } } ================================================ FILE: zhuishushenqi/TXTReader/BookDetail/Views/BookCommentViewCell.xib ================================================ ================================================ FILE: zhuishushenqi/TXTReader/BookDetail/Views/BookDetailHeader.swift ================================================ // // BookDetailHeader.swift // zhuishushenqi // // Created by Nory Cao on 16/10/4. // Copyright © 2016年 QS. All rights reserved. // import UIKit typealias AddButtonAction = (_ isSelected:Bool,_ model:BookDetail)->Void class BookDetailHeader: UIView { var addBtnAction:AddButtonAction? var startReading:AddButtonAction? var model:BookDetail?{ didSet{ name.text = model?.title if let book = model { let exist = ZSBookManager.shared.existBookId(bookId: book._id) if exist{ addButton.isSelected = true } } authorWidth.text = model?.author let widthttt = (authorWidth.text ?? "").qs_width(UIFont.boldSystemFont(ofSize: 13), height: 21) authorWidthh.constant = widthttt typeWidth.text = model?.minorCate == "" ? model?.majorCate : model?.minorCate let typeConst = (typeWidth.text ?? "").qs_width(UIFont.systemFont(ofSize: 13), height: 21) typeWidthConst.constant = typeConst + 5 words.text = "\(Int(model?.wordCount ?? "0")!/10000)万字" let created = model?.updated ?? "2014-02-23T16:48:18.179Z" self.new.qs_setCreateTime(createTime: created,append: "") self.icon.qs_setBookCoverWithURLString(urlString: self.model?.cover ?? "qqqqqqqq") } } @IBOutlet weak var name: UILabel! @IBOutlet weak var icon: UIImageView! @IBOutlet weak var authorWidthh: NSLayoutConstraint! @IBOutlet weak var new: UILabel! @IBOutlet weak var words: UILabel! @IBOutlet weak var authorWidth: UILabel! @IBOutlet weak var typeWidthConst: NSLayoutConstraint! @IBOutlet weak var typeWidth: UILabel! @IBOutlet weak var addButton: UIButton! @IBAction func readingStart(_ sender: UIButton) { if let action = startReading { if let model = self.model { action(sender.isSelected,model) } } } @IBAction func addBook(_ sender: UIButton) { sender.isSelected = !sender.isSelected if let action = addBtnAction { if let model = self.model { action(sender.isSelected,model) } } } } ================================================ FILE: zhuishushenqi/TXTReader/BookDetail/Views/BookDetailHeader.xib ================================================ ================================================ FILE: zhuishushenqi/TXTReader/BookDetail/Views/CategoryButton.swift ================================================ // // CategoryButton.swift // zhuishushenqi // // Created by Nory Cao on 2017/4/17. // Copyright © 2017年 QS. All rights reserved. // import UIKit class CategoryButton: UIButton { override func layoutSubviews() { super.layoutSubviews() self.imageView?.frame = CGRect(x: 0, y: 0, width: 30, height: 20) self.imageView?.contentMode = .scaleAspectFill self.titleLabel?.frame = CGRect(x: 0, y: 24, width: 30, height: 10) self.titleLabel?.textAlignment = .center } } ================================================ FILE: zhuishushenqi/TXTReader/BookDetail/Views/CategoryTableViewCell.swift ================================================ // // CategoryTableViewCell.swift // PageViewController // // Created by Nory Cao on 16/10/11. // Copyright © 2016年 QS. All rights reserved. // import UIKit protocol CategoryCellDelegate { func downloadBtnClicked(sender:Any) -> Void; } class CategoryTableViewCell: UITableViewCell { var cellDelegate:CategoryCellDelegate? @IBOutlet weak var head: UIView! @IBOutlet weak var count: UILabel! @IBOutlet weak var tittle: UILabel! @IBOutlet weak var downloadBtn: UIButton! @IBAction func downloadAction(_ sender: Any) { cellDelegate?.downloadBtnClicked(sender: sender) } override func awakeFromNib() { super.awakeFromNib() // Initialization code } func bind(model:QSChapter?){ if let _ = model { downloadBtn.isEnabled = false downloadBtn.setTitle("已缓存", for: .normal) if (model?.isVip ?? false) == true { downloadBtn.setTitle("", for: .normal) downloadBtn.setImage(UIImage(named: "directory_vip_chapter"), for: .normal) } } else { downloadBtn.isEnabled = true downloadBtn.setTitle("下载", for: .normal) } } override func prepareForReuse() { tittle.textColor = UIColor.black downloadBtn.setImage(nil, for: .normal) } override func setSelected(_ selected: Bool, animated: Bool) { super.setSelected(selected, animated: animated) // Configure the view for the selected state } } ================================================ FILE: zhuishushenqi/TXTReader/BookDetail/Views/CategoryTableViewCell.xib ================================================ ================================================ FILE: zhuishushenqi/TXTReader/BookDetail/Views/ChangeSourceCell.swift ================================================ // // ChangeSourceCell.swift // zhuishushenqi // // Created by Nory Cao on 2017/3/16. // Copyright © 2017年 QS. All rights reserved. // import UIKit class ChangeSourceCell: UITableViewCell { @IBOutlet weak var icon: UIImageView! @IBOutlet weak var website: UILabel! @IBOutlet weak var novelInfo: UILabel! @IBOutlet weak var currentSelect: UILabel! @IBOutlet weak var sourceWidth: NSLayoutConstraint! @IBOutlet weak var updated: UILabel! var higherCase:UILabel! var isCurrentSelected:Bool = false var model:ResourceModel? { didSet{ self.website.text = "\(model?.source ?? "")" let width = (website.text ?? "").qs_width(UIFont.systemFont(ofSize: 11), height: 21) self.sourceWidth.constant = width + 10 let created = model?.updated ?? "" self.updated.qs_setCreateTime(createTime: created, append: "") self.novelInfo.text = "\(model?.lastChapter ?? "")" self.currentSelect.isHidden = !self.isCurrentSelected higherCase.text = (model?.source ?? "zz").qs_subStr(to: 1).uppercased() } } override func awakeFromNib() { super.awakeFromNib() // Initialization code icon.layer.cornerRadius = 16.5 icon.layer.masksToBounds = true higherCase = UILabel(frame: CGRect(x: 0, y: 0, width: 33, height: 33)) higherCase.textAlignment = .center higherCase.textColor = UIColor.white higherCase.font = UIFont.systemFont(ofSize: 20) higherCase.backgroundColor = UIColor.gray icon.addSubview(higherCase) } override func prepareForReuse() { self.isCurrentSelected = false self.currentSelect.isHidden = true } override func setSelected(_ selected: Bool, animated: Bool) { super.setSelected(selected, animated: animated) // Configure the view for the selected state } } ================================================ FILE: zhuishushenqi/TXTReader/BookDetail/Views/ChangeSourceCell.xib ================================================ ================================================ FILE: zhuishushenqi/TXTReader/BookDetail/Views/HotCommentCell.swift ================================================ // // HotCommentCell.swift // zhuishushenqi // // Created by Nory Cao on 2017/3/9. // Copyright © 2017 QS. All rights reserved. // import UIKit class HotCommentCell: UITableViewCell { @IBOutlet weak var iconView: UIImageView! @IBOutlet weak var userNameLabel: UILabel! @IBOutlet weak var publishTimeLabel: UILabel! @IBOutlet weak var titleLabel: UILabel! @IBOutlet weak var contentLabel: UILabel! @IBOutlet weak var userfulBtn: UIButton! var darkView:DarkView? var lightView:LightView? var model:BookComment?{ didSet{ self.iconView.qs_setAvatarWithURLString(urlString: model?.author.avatar ?? "") self.userNameLabel.text = "\(model?.author.nickname ?? "")" self.titleLabel.text = "\(model?.title ?? "")" //文本过长会有卡顿现象 if (model?.content.count ?? 0) > 40 { self.contentLabel.text = "\(model?.content.qs_subStr(to: 40) ?? "")" }else{ self.contentLabel.text = "\(model?.content ?? "")" } self.userfulBtn.setTitle("\(model?.likeCount ?? 0)", for: .normal) let ratingValue = model?.rating ?? 60 let width = (1 + 10*ratingValue + (ratingValue - 1)*2) self.lightView?.frame = CGRect(x: 65 , y: 53, width: width, height: 10) self.publishTimeLabel.text = "" let created = model?.created ?? "2014-02-23T16:48:18.179Z" self.publishTimeLabel.qs_setCreateTime(createTime: created,append:"") } } override func awakeFromNib() { super.awakeFromNib() // Initialization code initSubview() } func initSubview(){ darkView = DarkView(frame: CGRect(x: 65, y: 53, width: 60, height: 10), image: UIImage(named: "bd_star_empty")) lightView = LightView(frame: CGRect(x: 65, y: 53, width: 60, height: 10), image: UIImage(named: "bd_star_filled")) contentView.addSubview(darkView!) contentView.addSubview(lightView!) } override func setSelected(_ selected: Bool, animated: Bool) { super.setSelected(selected, animated: animated) } } ================================================ FILE: zhuishushenqi/TXTReader/BookDetail/Views/HotCommentCell.xib ================================================ ================================================ FILE: zhuishushenqi/TXTReader/BookDetail/Views/PageView.swift ================================================ // // PageView.swift // PageViewController // // Created by Nory Cao on 16/10/9. // Copyright © 2016年 QS. All rights reserved. // import UIKit class PageView: ZSDisplayView { // size color var color:UIColor = UIColor.black var attributedText:String = "" { didSet{ refresh() } } func refresh() { let fontSize = ZSReader.share.theme.fontSize.size let lineSpace = ZSReader.share.theme.lineSpace let config = CTFrameParserConfig() config.fontSize = CGFloat(fontSize) config.textColor = AppStyle.shared.reader.textColor config.width = ZSReader.share.contentFrame.width config.lineSpace = lineSpace config.paragraphSpace = ZSReader.share.theme.paragraphSpace config.textFont = UIFont.systemFont(ofSize: fontSize) // attribute = CTFrameParser.attributes(with: config) // let data:CoreTextData = CTFrameParser.parseContent(attributedText, config: config) // self.data = data let settings = CTSettings.shared settings.margin = ZSReader.share.contentFrame.origin.x let parser = MarkupParser() parser.font = UIFont.systemFont(ofSize: fontSize) parser.color = AppStyle.shared.reader.textColor parser.lineSpace = lineSpace parser.fontSize = fontSize parser.paragraphSpace = ZSReader.share.theme.paragraphSpace parser.parseContent(attributedText, settings: settings) self.buildContent(attr: parser.attrString, andImages: [], settings: settings) setNeedsDisplay() } override func layoutSubviews() { super.layoutSubviews() refresh() } } ================================================ FILE: zhuishushenqi/TXTReader/BookDetail/Views/ProgressView.swift ================================================ // // ProgressView.swift // zhuishushenqi // // Created by yung on 2017/4/28. // Copyright © 2017年 QS. All rights reserved. // /* { "desc":"正在缓存中", "total":100, "now":0 } */ import UIKit class ProgressView: UIView { var dict:[String:Any]? { didSet{ if let tip = dict { setTips(dict: tip) } } } private var tipLabel:UILabel! override init(frame: CGRect) { super.init(frame: frame) setupSubview() } func setupSubview(){ tipLabel = UILabel() tipLabel.frame = CGRect(x: 15, y: 0, width: self.bounds.width, height: 20) tipLabel.textColor = UIColor.white tipLabel.font = UIFont.systemFont(ofSize: 11) addSubview(tipLabel) } func setTips(dict:[String:Any]){ let desc = dict["desc"] ?? "" let total:Int = dict["total"] as? Int ?? -1 let now:Int = dict["now"] as? Int ?? 0 var tip = "" if total == -1 { tip = "\(desc)" }else{ tip = "\(desc)(\(now + 1)/\(total))" } if now >= total { tip = "缓存完成...(\(now)/\(total))" } DispatchQueue.main.async { self.tipLabel.text = tip } } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } } ================================================ FILE: zhuishushenqi/TXTReader/BookDetail/Views/QSBatteryView.swift ================================================ // // QSBatteryView.swift // zhuishushenqi // // Created by Nory Cao on 2017/4/19. // Copyright © 2017年 QS. All rights reserved. // import UIKit class QSBatteryView: UIView { var batteryLevel:CGFloat = 1.0 { didSet{ setNeedsLayout() } } var batteryColor:UIColor = UIColor.darkGray { didSet { header.backgroundColor = batteryColor external.layer.borderColor = batteryColor.cgColor `internal`.backgroundColor = batteryColor } } private var `internal`:UIView! private var external:UIView! private var header:UIView! private var headerWidth:CGFloat = 0 override init(frame: CGRect) { super.init(frame: frame) setupSubviews() } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } func setupSubviews(){ self.backgroundColor = UIColor.clear external = UIView() external.backgroundColor = UIColor.clear external.layer.borderColor = UIColor.darkGray.cgColor external.layer.borderWidth = 1 `internal` = UIView() `internal`.backgroundColor = UIColor.darkGray header = UIView() header.backgroundColor = UIColor.darkGray self.addSubview(external) self.addSubview(`internal`) self.addSubview(header) } override func layoutSubviews() { super.layoutSubviews() let spaceX:CGFloat = 2 let spaceY:CGFloat = 2 let headerHeight = self.bounds.height/2 let headerWidth = self.bounds.height/3 if batteryLevel < 0 { batteryLevel = 1.0 } let width = (self.bounds.width - 4 - headerWidth)*batteryLevel `internal`.frame = CGRect(x: spaceX, y: spaceY, width: width, height: self.bounds.height - 4) external.frame = CGRect(x: 0, y: 0, width: self.bounds.width - headerWidth, height: self.bounds.height) header.frame = CGRect(x: self.bounds.width - headerWidth, y: self.bounds.height/2 - headerHeight/2, width: headerWidth, height: headerHeight) } } ================================================ FILE: zhuishushenqi/TXTReader/BookDetail/Views/QSBookDetailContentView.swift ================================================ // // QSBookDetailContentView.swift // zhuishushenqi // // Created by Nory Cao on 2017/4/13. // Copyright © 2017年 QS. All rights reserved. // import UIKit class QSBookDetailContentView: UIView { var longIntro:String? { didSet{ if let intro = longIntro { setupSubviews(longIntro: intro) } } } var contentShow:Bool = false { didSet{ setNeedsLayout() } } private let defaultHeight:CGFloat = 100 var contentLabel:UILabel! override init(frame: CGRect) { super.init(frame: frame) setupSubviews(longIntro: "") } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } func setupSubviews(longIntro:String){ let height = contentHeight(longIntro: self.longIntro ?? "") self.backgroundColor = UIColor.white self.layer.masksToBounds = true let subviews = self.subviews for item in subviews { item.removeFromSuperview() } contentLabel = UILabel(frame: CGRect(x: 20,y: 10,width: ScreenWidth - 40,height: height)) contentLabel.textAlignment = .left contentLabel.font = UIFont.systemFont(ofSize: 15) contentLabel.numberOfLines = 0 contentLabel.text = longIntro contentLabel.textColor = UIColor.black self.addSubview(contentLabel) } private func contentHeight(longIntro:String)->CGFloat{ if contentShow { var height:CGFloat = longIntro.qs_height(15, width: ScreenWidth - 40) if height == 0 { height = defaultHeight }else{ height += 30 } return height } else{ return defaultHeight } } static func height(intro:String,show:Bool)->CGFloat{ let content = QSBookDetailContentView() content.contentShow = show let height = content.contentHeight(longIntro: intro) return height } override func layoutSubviews() { super.layoutSubviews() if contentShow { let height = contentHeight(longIntro: self.longIntro ?? "") contentLabel.frame = CGRect(x: 20,y: 10,width: ScreenWidth - 40,height: height - 30) }else{ contentLabel.frame = CGRect(x: 20,y: 10,width: ScreenWidth - 40,height: 89) } } } ================================================ FILE: zhuishushenqi/TXTReader/BookDetail/Views/QSBookDetailRateView.swift ================================================ // // QSBookDetailRateView.swift // zhuishushenqi // // Created by Nory Cao on 2017/4/13. // Copyright © 2017年 QS. All rights reserved. // import UIKit class QSBookDetailRateView: UIView { var rate:[String]?{ didSet{ setRate() } } override init(frame: CGRect) { super.init(frame: frame) setupSubviews() } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } func setupSubviews(){ self.backgroundColor = UIColor.white let text = ["追书人数","读者留存率","更新字数/天"] let x:CGFloat = 0 let y:CGFloat = 20 let width = ScreenWidth/3 let height:CGFloat = 21.0 for index in 0..<3 { let label = UILabel(frame: CGRect(x: x + width*CGFloat(index),y: y,width: width,height: height)) label.text = text[index] label.textAlignment = .center label.textColor = UIColor.gray label.font = UIFont.systemFont(ofSize: 13) self.addSubview(label) let slabel = UILabel(frame: CGRect(x: x + width*CGFloat(index),y: y + 21,width: width,height: height)) slabel.textAlignment = .center slabel.textColor = UIColor.gray slabel.tag = 123 + index slabel.font = UIFont.systemFont(ofSize: 13) self.addSubview(slabel) } } func setRate(){ for index in 0..<(rate?.count ?? 0) { let label:UILabel? = self.viewWithTag(123 + index) as? UILabel label?.text = rate?[index] } } } ================================================ FILE: zhuishushenqi/TXTReader/BookDetail/Views/QSBookDetailTagsView.swift ================================================ // // QSBookDetailTagsView.swift // zhuishushenqi // // Created by Nory Cao on 2017/4/13. // Copyright © 2017年 QS. All rights reserved. // import UIKit class QSBookDetailTagsView: UIView { var tagColor = [UIColor(red: 0.56, green: 0.77, blue: 0.94, alpha: 1.0), UIColor(red: 0.75, green: 0.41, blue: 0.82, alpha: 1.0), UIColor(red: 0.96, green: 0.74, blue: 0.49, alpha: 1.0), UIColor(red: 0.57, green: 0.81, blue: 0.84, alpha: 1.0), UIColor(red: 0.40, green: 0.80, blue: 0.72, alpha: 1.0), UIColor(red: 0.91, green: 0.56, blue: 0.56, alpha: 1.0), UIColor(red: 0.56, green: 0.77, blue: 0.94, alpha: 1.0), UIColor(red: 0.75, green: 0.41, blue: 0.82, alpha: 1.0)] var tags:[String]!{ didSet{ setupSubviews(tags: tags) } } override init(frame: CGRect) { super.init(frame: frame) } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } @discardableResult func setupSubviews(tags:[String])->CGFloat{ self.backgroundColor = UIColor.white let subviews = self.subviews for item in subviews { item.removeFromSuperview() } var x:CGFloat = 20 var y:CGFloat = 10 let spacex:CGFloat = 10 let spacey:CGFloat = 10 let height:CGFloat = 30 for index in 0.. ScreenWidth { x = 20 y = y + spacey + height } let btn = UIButton(type: .custom) btn.frame = CGRect(x: x, y: y, width: width, height: height) btn.setTitle(tags[index], for: UIControl.State()) btn.titleLabel?.font = UIFont.systemFont(ofSize: 15) btn.setTitleColor(UIColor.white, for: UIControl.State()) btn.backgroundColor = tagColor[index%tagColor.count] btn.layer.cornerRadius = 2 self.addSubview(btn) x = x + width + spacex } return (y + height + 10) } static func height(tags:[String])->CGFloat{ let view:QSBookDetailTagsView = QSBookDetailTagsView() return view.setupSubviews(tags: tags) } } ================================================ FILE: zhuishushenqi/TXTReader/BookDetail/Views/QSBookListViewCell.swift ================================================ // // QSBookListViewCell.swift // zhuishushenqi // // Created by yung on 2017/4/21. // Copyright © 2017年 QS. All rights reserved. // import UIKit class QSBookListViewCell: UITableViewCell { @IBOutlet weak var icon: UIImageView! @IBOutlet weak var totalWidth: NSLayoutConstraint! @IBOutlet weak var collectLabel: UILabel! @IBOutlet weak var totalLabel: UILabel! @IBOutlet weak var contentLabel: UILabel! @IBOutlet weak var authorLabel: UILabel! @IBOutlet weak var titleLabel: UILabel! var book:QSBookList? { didSet{ self.icon.qs_setBookCoverWithURLString(urlString: book?.cover ?? "") titleLabel.text = book?.title authorLabel.text = book?.author contentLabel.text = book?.desc totalLabel.text = "共\(book?.bookCount ?? 0)本书" collectLabel.text = "\(book?.collectorCount ?? 0)人收藏" let widht = (totalLabel.text ?? "").qs_width(UIFont.systemFont(ofSize: 11), height: 21) + 10 totalWidth.constant = widht } } override func awakeFromNib() { super.awakeFromNib() // Initialization code } override func setSelected(_ selected: Bool, animated: Bool) { super.setSelected(selected, animated: animated) // Configure the view for the selected state } } ================================================ FILE: zhuishushenqi/TXTReader/BookDetail/Views/QSBookListViewCell.xib ================================================ ================================================ FILE: zhuishushenqi/TXTReader/BookDetail/Views/QSDiscussCell.swift ================================================ // // QSDiscussCell.swift // zhuishushenqi // // Created by yung on 2017/4/24. // Copyright © 2017年 QS. All rights reserved. // import UIKit class QSDiscussCell: UITableViewCell { @IBOutlet weak var icon: UIImageView! @IBOutlet weak var name: UILabel! @IBOutlet weak var contentLabel: UILabel! @IBOutlet weak var commentLabel: UILabel! @IBOutlet weak var likeLabel: UILabel! @IBOutlet weak var timeLabel: UILabel! var model:BookComment? { didSet{ icon.qs_setAvatarWithURLString(urlString: model?.author.avatar ?? "") name.text = "\(model?.author.nickname ?? "") lv.\(model?.author.lv ?? 0)" contentLabel.text = "\(model?.title ?? "")" commentLabel.text = "\(model?.commentCount ?? 0)" likeLabel.text = "\(model?.likeCount ?? 0)" timeLabel.qs_setCreateTime(createTime: model?.created ?? "", append: "") } } override func awakeFromNib() { super.awakeFromNib() // Initialization code icon.layer.cornerRadius = 5 backgroundColor = UIColor(red: 0.95, green: 0.95, blue: 0.95, alpha: 1.0) } override func setSelected(_ selected: Bool, animated: Bool) { super.setSelected(selected, animated: animated) // Configure the view for the selected state } } ================================================ FILE: zhuishushenqi/TXTReader/BookDetail/Views/QSDiscussCell.xib ================================================ ================================================ FILE: zhuishushenqi/TXTReader/BookDetail/Views/QSRecommendCell.swift ================================================ // // QSRecommendCell.swift // zhuishushenqi // // Created by yung on 2017/4/21. // Copyright © 2017年 QS. All rights reserved. // import UIKit //import YYCategories typealias BtnClickAction = (_ btn:Any)->Void let RecBtnTag = 12345 //146 class QSRecommendCell: UITableViewCell,InterestdViewDelegate { var books:[Book]? { didSet{ if let bookss = books { cacheImage(books: bookss) } } } let QSRecommendCellItemCount = 4 let QSRecommendCellSpaceX:CGFloat = 15 let QSRecommendCellSpaceY:CGFloat = 43 let QSRecommendCellInterestdWidth:CGFloat = 69 let QSRecommendCellInterestdHeight:CGFloat = 137 var clickAction:BtnClickAction? @IBOutlet weak var more: UIButton! override func awakeFromNib() { super.awakeFromNib() // Initialization code setupSubviews() } @IBAction func moreAct(_ sender: UIView) { sender.tag = RecBtnTag + 4 touchAction(btn: sender) } override func setSelected(_ selected: Bool, animated: Bool) { super.setSelected(selected, animated: animated) // Configure the view for the selected state } func cacheImage(books:[Book]){ var index = 0 for item in books { let title = item.title let cover = item.cover if let interView:QSInterestdView = self.viewWithTag(RecBtnTag + index) as? QSInterestdView { interView.imageView.qs_setBookCoverWithURLString(urlString: cover ?? "") interView.titleLabel.text = title } index = index + 1 } } func didClick(_ interestdView: QSInterestdView) { self.touchAction(btn: interestdView) } func setupSubviews(){ for item in 0.. Void } class QSInterestdView : UIView { var imageView: UIImageView! var titleLabel: UILabel! var interestdViewDelegate:InterestdViewDelegate? override init(frame: CGRect) { super.init(frame: frame) setupSubviews() self.imageView.contentMode = .scaleToFill let tap = UITapGestureRecognizer(target: self, action: #selector(tapAction(sender:))) self.addGestureRecognizer(tap) } required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) } func setupSubviews(){ imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 69, height: 102)) titleLabel = UILabel(frame: CGRect(x: 0, y: imageView.size.height, width: imageView.size.width, height: 137 - imageView.size.height)) titleLabel.font = UIFont.systemFont(ofSize: 11) titleLabel.textColor = UIColor.gray titleLabel.numberOfLines = 0 titleLabel.textAlignment = .center addSubview(imageView) addSubview(titleLabel) let tap = UITapGestureRecognizer(target: self, action: #selector(tapAction(sender:))) self.addGestureRecognizer(tap) } @objc public func tapAction(sender:UITapGestureRecognizer){ interestdViewDelegate?.didClick(self) } } ================================================ FILE: zhuishushenqi/TXTReader/BookDetail/Views/QSRecommendCell.xib ================================================ ================================================ FILE: zhuishushenqi/TXTReader/BookDetail/Views/ToolBar.swift ================================================ // // ToolBar.swift // PageViewController // // Created by Nory Cao on 16/10/10. // Copyright © 2016年 QS. All rights reserved. // import UIKit typealias ZSBgViewHandler = ()->Void enum ToolBarFontChangeAction { case plus case minimus } protocol ToolBarDelegate{ func backButtonDidClicked() func catagoryClicked() func changeSourceClicked() func toolBarDidShow() func toolBarDidHidden() func readBg(type:Reader) func fontChange(action:ToolBarFontChangeAction) func brightnessChange(value:CGFloat) func cacheAll() func toolbar(toolbar:ToolBar, clickMoreSetting:UIView) func listen() } class ToolBar: UIView { private let kBottomBtnTag = 12345 private let TopBarHeight:CGFloat = kNavgationBarHeight private let BottomBarHeight:CGFloat = 49 + CGFloat(kTabbarBlankHeight) var toolBarDelegate:ToolBarDelegate? var topBar:UIView? var bottomBar:UIView? var midBar:UIView? var isShow:Bool = false var showMid:Bool = false var whiteBtn:UIButton! var yellowBtn:UIButton! var greenBtn:UIButton! var mode_bgView:UIScrollView! var fontSize:Int = QSReaderSetting.shared.fontSize var titleLabel:UILabel! var title:String = "" { didSet{ titleLabel.text = self.title } } var bgItemViews:[ZSModeBgItemView] = [] var progressView:ProgressView! override init(frame: CGRect) { super.init(frame: frame) fontSize = QSReaderSetting.shared.fontSize initSubview() } private func initSubview(){ topBar = UIView(frame: CGRect(x: 0, y: -TopBarHeight, width: UIScreen.main.bounds.size.width, height: TopBarHeight)) topBar?.backgroundColor = UIColor(white: 0.0, alpha: 1.0) addSubview(topBar!) bottomBar = UIView(frame: CGRect(x:0,y:UIScreen.main.bounds.size.height,width:UIScreen.main.bounds.size.width,height:BottomBarHeight)) bottomBar?.backgroundColor = UIColor(white: 0.0, alpha: 1.0) addSubview(bottomBar!) midBar = UIView(frame: CGRect(x:0,y:UIScreen.main.bounds.size.height - 180 - BottomBarHeight,width:UIScreen.main.bounds.size.width,height:180)) midBar?.backgroundColor = UIColor(white: 0.0, alpha: 0.7) midBarSubviews() bottomSubviews() let backBtn = UIButton(type: .custom) backBtn.setImage(UIImage(named: "sm_exit"), for: .normal) backBtn.addTarget(self, action: #selector(backAction(btn:)), for: .touchUpInside) backBtn.frame = CGRect(x:self.bounds.width - 55, y: STATEBARHEIGHT,width: 49,height: 49) topBar?.addSubview(backBtn) let changeSourceBtn = UIButton(type: .custom) changeSourceBtn.setTitle("换源", for: .normal) changeSourceBtn.setTitleColor(UIColor.white, for: .normal) changeSourceBtn.addTarget(self, action: #selector(changeSourceAction(btn:)), for: .touchUpInside) changeSourceBtn.frame = CGRect(x:self.bounds.width - 65, y: 27,width: 50,height: 30) changeSourceBtn.frame = CGRect(x:10, y:STATEBARHEIGHT + 7,width: 50,height: 30) topBar?.addSubview(changeSourceBtn) let listenBtn = UIButton(type: .custom) listenBtn.setImage(UIImage(named: "readAloud"), for: .normal) listenBtn.addTarget(self, action: #selector(listenAction(btn:)), for: .touchUpInside) listenBtn.frame = CGRect(x:self.bounds.width - 104, y: STATEBARHEIGHT,width: 49,height: 49) topBar?.addSubview(listenBtn) titleLabel = UILabel(frame: CGRect(x: self.bounds.width/2 - 100, y: STATEBARHEIGHT+7, width: 200, height: 30)) titleLabel.textColor = UIColor.white titleLabel.textAlignment = .center topBar?.addSubview(titleLabel) let tap = UITapGestureRecognizer(target: self, action:#selector(hideWithAnimations(animation:)) ) addGestureRecognizer(tap) } func midBarSubviews(){ let progressBar = UISlider(frame: CGRect(x: 50, y: 25, width: self.bounds.width - 100, height: 15)) progressBar.minimumTrackTintColor = UIColor.orange progressBar.value = Float(UIScreen.main.brightness) progressBar.addTarget(self, action: #selector(brightnessChange(btn:)), for: .valueChanged) let leftImg = UIImageView(frame: CGRect(x: 25, y: 25, width: 15, height: 15)) leftImg.image = UIImage(named: "brightess_white_low") let rightImg = UIImageView(frame: CGRect(x: self.bounds.width - 40, y: 23, width: 25, height: 25)) rightImg.image = UIImage(named: "brightess_white_high") let fontminus = button(with: UIImage(named:"font_decrease"), selectedImage: nil, title: nil, frame: CGRect(x: 13, y: 55, width: 60, height: 60), selector: #selector(fontMinusAction(btn:)), font: nil) let fontPlus = button(with: UIImage(named:"font_increase"), selectedImage: nil, title: nil, frame: CGRect(x: fontminus.frame.maxX + 13, y: fontminus.frame.minY, width: 60, height: 60), selector: #selector(fontPlusAction(btn:)), font: nil) let landscape = button(with: UIImage(named:"landscape"), selectedImage: nil, title: nil, frame: CGRect(x: fontPlus.frame.maxX, y: fontminus.frame.minY + 5, width: 60, height: 50), selector: #selector(landscape(btn:)), font: nil) let autoReading = button(with: UIImage(named:"autoreading_start"), selectedImage: nil, title: "自动阅读", frame: CGRect(x: landscape.frame.maxX, y: landscape.frame.minY + 10, width: 115, height: 30), selector: #selector(autoReading(btn:)), font: UIFont.systemFont(ofSize: 15)) mode_bgView = UIScrollView(frame: CGRect(x: 0, y: 115, width: self.bounds.width - 140, height: 60)) mode_bgView.isUserInteractionEnabled = true // mode_bgView.backgroundColor = UIColor.orange mode_bgView.showsHorizontalScrollIndicator = true midBar?.addSubview(mode_bgView) let itemImages = ["white_mode_bg","yellow_mode_bg","green_mode_bg","blackGreen_mode_bg","pink_mode_bg","sheepskin_mode_bg","violet_mode_bg","water_mode_bg","weekGreen_mode_bg","weekPink_mode_bg","coffee_mode_bg"] var index = 0 for image in itemImages { let bgView = ZSModeBgItemView(frame: CGRect(x: 25*(index + 1) + 60 * index, y: 0, width: 60, height: 60)) bgView.setImage(image: UIImage(named: image)) bgView.index = index bgView.selectHandler = { self.itemAction(index: bgView.index) } mode_bgView.addSubview(bgView) bgItemViews.append(bgView) index += 1 } mode_bgView.contentSize = CGSize(width: itemImages.count * (60 + 25), height: 60) let senior = button(with: UIImage(named:"reading_more_setting"), selectedImage: nil, title: "高级设置", frame: CGRect(x: self.bounds.width - 140, y: 130, width: 115, height: 30), selector: #selector(seniorSettingAction(btn:)), font: UIFont.systemFont(ofSize: 15)) progressView = ProgressView(frame: CGRect(x: 0, y: self.bounds.height - 49 - 20, width: self.bounds.width, height: 20)) progressView.backgroundColor = UIColor.black progressView.isHidden = true progressView.alpha = 0.7 addSubview(progressView) midBar?.addSubview(leftImg) midBar?.addSubview(rightImg) midBar?.addSubview(progressBar) midBar?.addSubview(fontminus) midBar?.addSubview(fontPlus) midBar?.addSubview(landscape) midBar?.addSubview(autoReading) midBar?.addSubview(senior) let type = AppStyle.shared.reader if type.rawValue >= 0 && type.rawValue < bgItemViews.count { var index = 0 for bgView in bgItemViews { if type.rawValue == index { bgView.select(select: true) } else { bgView.select(select: false) } index += 1 } } } func bottomSubviews(){ let width = UIScreen.main.bounds.width/5 let btnWidth:CGFloat = 30.0 let btnHeight:CGFloat = 34.0 let images = ["night_mode","feedback","directory","preview_btn","reading_setting"] let titles = ["夜间","反馈","目录","缓存","设置"] for item in 0..<5 { let x = (width - btnWidth)/2*CGFloat(item*2 + 1) + btnWidth*CGFloat(item) let y = 49/2 - btnHeight/2 let btn = CategoryButton(type: .custom) btn.setImage(UIImage(named: images[item]), for: .normal) btn.setTitle(titles[item], for: .normal) btn.titleLabel?.font = UIFont.systemFont(ofSize: 9) btn.frame = CGRect(x:x,y: y,width: btnWidth,height: btnHeight) btn.addTarget(self, action: #selector(bottomBtnAction(btn:)), for: .touchUpInside) btn.tag = kBottomBtnTag + item bottomBar?.addSubview(btn) } } func button(with image:UIImage?,selectedImage:UIImage?,title:String?,frame:CGRect,selector:Selector,font:UIFont?)->UIButton{ let button = UIButton(frame: frame) button.setImage(selectedImage, for: .selected) button.setImage(image, for: .normal) button.setTitle(title, for: .normal) button.addTarget(self, action: selector, for: .touchUpInside) button.titleLabel?.font = font return button } func showWithAnimations(animation:Bool,inView:UIView){ self.isShow = true inView.addSubview(self) UIView.animate(withDuration: 0.35, animations: { self.topBar?.frame = CGRect(x:0, y:0,width: self.bounds.size.width,height: self.TopBarHeight) self.bottomBar?.frame = CGRect(x:0,y: self.bounds.size.height - self.BottomBarHeight,width: self.bounds.size.width,height: self.BottomBarHeight) self.progressView.frame = CGRect(x: 0, y: self.bounds.height - 49 - 20, width: self.bounds.width, height: 20) }) { (finished) in } toolBarDelegate?.toolBarDidShow() } @objc func hideWithAnimations(animation:Bool){ midBar?.removeFromSuperview() showMid = false UIView.animate(withDuration: 0.35, animations: { self.topBar?.frame = CGRect(x:0,y: -self.TopBarHeight,width: self.bounds.size.width,height: self.TopBarHeight) self.bottomBar?.frame = CGRect(x:0, y:self.bounds.size.height + 20, width:self.bounds.size.width, height:self.BottomBarHeight) self.progressView.frame = CGRect(x: 0, y: self.bounds.height, width: self.bounds.width, height: 20) }) { (finished) in self.isShow = false self.removeFromSuperview() } toolBarDelegate?.toolBarDidHidden() } @objc private func brightnessChange(btn:UISlider){ //调节屏幕亮度 self.toolBarDelegate?.brightnessChange(value: CGFloat(btn.value)) } @objc private func fontMinusAction(btn:UIButton){ self.toolBarDelegate?.fontChange(action: .minimus) } @objc private func fontPlusAction(btn:UIButton){ self.toolBarDelegate?.fontChange(action: .plus) } @objc private func landscape(btn:UIButton){ } @objc private func autoReading(btn:UIButton){ } @objc private func whiteAction(btn:UIButton){ btn.isSelected = true yellowBtn.isSelected = false greenBtn.isSelected = false self.toolBarDelegate?.readBg(type: .white) } @objc private func yellowAction(btn:UIButton){ btn.isSelected = true whiteBtn.isSelected = false greenBtn.isSelected = false self.toolBarDelegate?.readBg(type: .yellow) } @objc private func greenAction(btn:UIButton){ btn.isSelected = true whiteBtn.isSelected = false yellowBtn.isSelected = false self.toolBarDelegate?.readBg(type: .green) } @objc private func itemAction(index:Int) { var viewIndex = 0 for bgView in bgItemViews { if viewIndex != index { bgView.select(select: false) } viewIndex += 1 } self.toolBarDelegate?.readBg(type: Reader(rawValue: index) ?? .white) } @objc private func seniorSettingAction(btn:UIButton){ self.toolBarDelegate?.toolbar(toolbar: self, clickMoreSetting: btn) } @objc private func bottomBtnAction(btn:UIButton){ let tag = btn.tag - kBottomBtnTag switch tag { case 0: darkNight(btn: btn) break case 1: feedback(btn: btn) break case 2: catalogAction(btn: btn) break case 3: cache(btn: btn) break case 4: setting(btn: btn) break default: darkNight(btn: btn) } } @objc private func darkNight(btn:UIButton){ } @objc private func feedback(btn:UIButton){ } @objc private func catalogAction(btn:UIButton){ toolBarDelegate?.catagoryClicked() } @objc private func cache(btn:UIButton){ toolBarDelegate?.cacheAll() } @objc private func setting(btn:UIButton){ showMid = !showMid if showMid { self.addSubview(midBar!) }else{ midBar?.removeFromSuperview() } } @objc private func backAction(btn:UIButton){ toolBarDelegate?.backButtonDidClicked() } @objc private func listenAction(btn:UIButton){ toolBarDelegate?.listen() } @objc private func changeSourceAction(btn:UIButton){ toolBarDelegate?.changeSourceClicked() } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } } class ZSModeBgItemView: UIView { var backgroundImage:UIImageView! private var selectedImage:UIImageView! var selectHandler:ZSBgViewHandler? var index:Int = 0 override init(frame: CGRect) { super.init(frame: frame) setupSubviews() } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } private func setupSubviews() { isUserInteractionEnabled = true backgroundImage = UIImageView(frame: self.bounds) backgroundImage.isUserInteractionEnabled = true backgroundImage.layer.cornerRadius = self.bounds.width/2 backgroundImage.layer.masksToBounds = true selectedImage = UIImageView(frame: CGRect(x: self.bounds.width/2 - 18, y: self.bounds.width/2 - 18, width: 36, height: 36)) selectedImage.isUserInteractionEnabled = true selectedImage.image = UIImage(named: "cell_selected_tip") selectedImage.isHidden = true addSubview(backgroundImage) addSubview(selectedImage) let tap = UITapGestureRecognizer(target: self, action: #selector(selectAction)) backgroundImage.addGestureRecognizer(tap) } @objc private func selectAction() { selectedImage.isHidden = false selectHandler?() } func select(select:Bool) { selectedImage.isHidden = !select } func setImage(image:UIImage?) { backgroundImage.image = image } } ================================================ FILE: zhuishushenqi/TXTReader/BookDetail/Views/UserfulCell.swift ================================================ // // UserfulCell.swift // zhuishushenqi // // Created by Nory Cao on 2017/3/14. // Copyright © 2017年 QS. All rights reserved. // import UIKit class UserfulCell: UITableViewCell { @IBOutlet weak var userful: UILabel! @IBOutlet weak var unuserful: UILabel! var model:BookComment? { didSet{ userful.text = "\(model?.helpful.yes ?? 0)" unuserful.text = "\(model?.helpful.no ?? 0)" } } override func awakeFromNib() { super.awakeFromNib() // Initialization code } override func setSelected(_ selected: Bool, animated: Bool) { super.setSelected(selected, animated: animated) // Configure the view for the selected state } } ================================================ FILE: zhuishushenqi/TXTReader/BookDetail/Views/UserfulCell.xib ================================================ ================================================ FILE: zhuishushenqi/TXTReader/BookDetail/ZSDetailInfoCell.swift ================================================ // // ZSDetailInfoCell.swift // zhuishushenqi // // Created by caony on 2019/5/9. // Copyright © 2019年 QS. All rights reserved. // import UIKit class ZSDetailInfoCell: UITableViewCell { override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) setupSubviews() } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } private func setupSubviews() { authorLabel.textColor = UIColor.red authorLabel.font = UIFont.systemFont(ofSize: 11) majorLabel.textColor = UIColor.gray majorLabel.font = UIFont.systemFont(ofSize: 11) wordCountLabel.textColor = UIColor.gray wordCountLabel.font = UIFont.systemFont(ofSize: 11) updatedLabel.textColor = UIColor.gray updatedLabel.font = UIFont.systemFont(ofSize: 11) pursueButton.setBackgroundImage(UIImage(named: "bd_add"), for: .normal) pursueButton.setBackgroundImage(UIImage(named: "bd_cancel_selected"), for: .selected) pursueButton.layer.cornerRadius = 5 pursueButton.layer.masksToBounds = true startReadingButton.backgroundColor = UIColor.red startReadingButton.setTitle("开始阅读", for: .normal) startReadingButton.layer.cornerRadius = 5 startReadingButton.layer.masksToBounds = true contentView.addSubview(authorLabel) contentView.addSubview(majorLabel) contentView.addSubview(wordCountLabel) contentView.addSubview(updatedLabel) contentView.addSubview(pursueButton) contentView.addSubview(startReadingButton) } override func layoutSubviews() { super.layoutSubviews() imageView?.frame = CGRect(x: 20, y: 20, width: 36, height: 45) let marginX = imageView?.frame.maxX ?? 0 textLabel?.frame = CGRect(x: marginX + 10, y: 20, width: bounds.width - marginX - 10, height: 15) let authorOriginY = textLabel?.frame.maxY ?? 0 let authorWidth = authorLabel.sizeThatFits(CGSize(width: CGFloat.greatestFiniteMagnitude, height: 15)).width authorLabel.frame = CGRect(x: marginX, y: authorOriginY, width: authorWidth, height: 15) let majorWidth = majorLabel.sizeThatFits(CGSize(width: CGFloat.greatestFiniteMagnitude, height: 15)).width let majorOriginX = authorLabel.frame.maxX + 20 majorLabel.frame = CGRect(x: majorOriginX, y: 15, width: majorWidth, height: 15) let wordCountOriginX = majorLabel.frame.maxX let wordCountWidth = wordCountLabel.sizeThatFits(CGSize(width: CGFloat.greatestFiniteMagnitude, height: 15)).width wordCountLabel.frame = CGRect(x: wordCountOriginX, y: 15, width: wordCountWidth, height: 15) updatedLabel.frame = CGRect(x: 20, y: 30, width: bounds.width - marginX - 10 - 20, height: 15) } override func awakeFromNib() { super.awakeFromNib() } override func setSelected(_ selected: Bool, animated: Bool) { super.setSelected(selected, animated: animated) } var authorLabel:UILabel = UILabel(frame: .zero) var majorLabel:UILabel = UILabel(frame: .zero) var wordCountLabel:UILabel = UILabel(frame: .zero) var updatedLabel:UILabel = UILabel(frame: .zero) var pursueButton:UIButton = UIButton(type: .custom) var startReadingButton:UIButton = UIButton(type: .custom) } ================================================ FILE: zhuishushenqi/TXTReader/BookDetail/ZSDetailSection.swift ================================================ // // ZSDetailSection.swift // zhuishushenqi // // Created by caony on 2019/5/9. // Copyright © 2019年 QS. All rights reserved. // import Foundation enum ZSDetailSection { case info(ZSDetailInfo) case people(ZSDetailPeople) case tag(ZSDetailTag) case intro(ZSDetailIntro) } // section struct ZSDetailSectionModel { var secions:[ZSDetailSection] = [] init(_ sections:[ZSDetailSection] = []) { self.secions = sections } } struct ZSDetailInfo:ZSDetailSectionProtocol { var rows:[ZSDetailInfoModel] = [] } struct ZSDetailPeople:ZSDetailSectionProtocol { } struct ZSDetailTag:ZSDetailSectionProtocol { } struct ZSDetailIntro:ZSDetailSectionProtocol { } protocol ZSDetailSectionProtocol { } struct ZSDetailInfoModel { var id:String = "" var title:String = "" var updated:String = "" var wordCount:Int64 = 0 var author:String = "" var majorCateV2:String = "" var cover:String = "" } ================================================ FILE: zhuishushenqi/TXTReader/BookDetail/ZSDetailViewController.swift ================================================ // // ZSDetailViewController.swift // zhuishushenqi // // Created by Nory Cao on 2017/3/10. // Copyright © 2017年 QS. All rights reserved. // class ZSDetailViewController:BaseViewController { override func viewDidLoad() { super.viewDidLoad() setupSubviews() } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) } lazy var tableView:UITableView = { let tableView = UITableView(frame: CGRect(x: 0, y: 64, width: ScreenWidth, height: ScreenHeight - 64), style: .grouped) tableView.dataSource = self tableView.delegate = self tableView.separatorStyle = .singleLine // tableView.qs_registerCellClass(<#T##aClass: T.Type##T.Type#>) tableView.backgroundColor = UIColor(red: 0.95, green: 0.95, blue: 0.95, alpha: 1.0) return tableView }() } extension ZSDetailViewController { fileprivate func setupSubviews() { let titleView = UIView(frame: CGRect(x: 0,y: 0,width: 120,height: 30)) let titleLabel = UILabel(frame: CGRect(x: 0,y: 0,width: 90,height: 30)) titleLabel.textAlignment = .center titleLabel.text = "书籍详情" let titleShare = UIImageView(image: UIImage(named: "bd_share")) let width = (titleLabel.text! as NSString).boundingRect(with: CGSize(width: CGFloat.greatestFiniteMagnitude, height: 30), options: .usesLineFragmentOrigin, attributes: [NSAttributedString.Key.font:UIFont.systemFont(ofSize: 13)], context: nil) titleShare.frame = CGRect(x: width.size.width/2 + 120/2, y: 5, width: 20, height: 20) let ges = UITapGestureRecognizer(target: self, action: #selector(shareAction(_:))) titleShare.addGestureRecognizer(ges) titleView.addSubview(titleShare) titleView.addSubview(titleLabel) navigationItem.titleView = titleView let rightItem = UIBarButtonItem(title: "全本缓存", style: .plain, target: self, action: #selector(allCache(_:))) navigationItem.rightBarButtonItem = rightItem } @objc fileprivate func allCache(_ item:UIBarButtonItem){ } @objc fileprivate func shareAction(_ tap:UITapGestureRecognizer){ } } extension ZSDetailViewController: UITableViewDataSource, UITableViewDelegate { func numberOfSections(in tableView: UITableView) -> Int { return 1 } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return 0 } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { return UITableViewCell() } } ================================================ FILE: zhuishushenqi/TXTReader/Category/CategoryController.swift ================================================ // // ModalViewController.swift // TXTReader // // Created by Nory Cao on 16/11/27. // Copyright © 2016年 QS. All rights reserved. // import UIKit //import Alamofire protocol CategoryDelegate { func categoryDidSelectAtIndex(index:Int) } class CategoryController: BaseViewController,UITableViewDataSource,UITableViewDelegate,CategoryCellDelegate { var categoryDelegate:CategoryDelegate? var titles = [NSDictionary]() var selectedIndex:Int = 0 var id:String = "" var resource:ResourceModel? lazy var tableView:UITableView = { let tableView = UITableView(frame: CGRect(x:0,y:64,width:self.view.bounds.width,height:self.view.bounds.height - 64), style: .plain) tableView.dataSource = self tableView.delegate = self return tableView }() override func viewDidLoad() { super.viewDidLoad() self.view.backgroundColor = UIColor.black view.addSubview(self.tableView) tableView.separatorStyle = .singleLineEtched tableView.separatorColor = UIColor.gray navigationController?.navigationBar.barTintColor = UIColor.black tableView.register(UINib(nibName: "CategoryTableViewCell", bundle: nil), forCellReuseIdentifier: "Category") let leftItem = UIBarButtonItem(image: UIImage(named: "bg_back_white"), style: .plain, target: self, action: #selector(dismiss(sender:))) navigationItem.leftBarButtonItem = leftItem let statusView = UIView(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.size.width, height: 30)) statusView.backgroundColor = UIColor.gray //添加阴影也能达到相同的目的 // let layer = statusView.layer // let path = UIBezierPath(rect: layer.bounds) // layer.shadowPath = path.cgPath // layer.shadowColor = UIColor.black.cgColor // layer.shadowOffset = CGSize.zero // layer.shadowOpacity = 1.0 // layer.shadowRadius = 10 // self.view.addSubview(statusView) // setmask(statusBarBackgroundView: statusView) // self.tableView.backgroundView = UIImageView(image: UIImage(named: "space")) } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) let indexPATH = IndexPath(row: selectedIndex, section: 0) if titles.count > indexPATH.row { self.tableView.scrollToRow(at: indexPATH , at: .middle, animated: false) } // setNeedsStatusBarAppearanceUpdate() } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) } func setmask(statusBarBackgroundView:UIView){ let gradientLayer = CAGradientLayer() let colors = [UIColor(white: 0.0, alpha: 0.0).cgColor,UIColor(white: 0.0, alpha: 0.9).cgColor] gradientLayer.colors = colors gradientLayer.startPoint = CGPoint(x: 0.5, y: 1.0) gradientLayer.endPoint = CGPoint(x:0.5,y:0.7) gradientLayer.frame = statusBarBackgroundView.bounds statusBarBackgroundView.layer.mask = gradientLayer } func numberOfSections(in tableView: UITableView) -> Int { return 1 } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return titles.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "Category",for: indexPath as IndexPath) as! CategoryTableViewCell cell.count.text = "\(indexPath.row)." cell.cellDelegate = self if titles.count > indexPath.row { cell.tittle.text = titles[indexPath.row].object(forKey: "title") as? String } cell.tittle.textColor = (selectedIndex == indexPath.row ? UIColor.green:UIColor.black) return cell } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { categoryDelegate?.categoryDidSelectAtIndex(index: indexPath.row) dismiss(animated: true, completion: nil) } @objc private func dismiss(sender:AnyObject){ dismiss(animated: true, completion: nil) } override var prefersStatusBarHidden: Bool{ return false } override var preferredStatusBarStyle: UIStatusBarStyle{ return .lightContent } override var preferredStatusBarUpdateAnimation: UIStatusBarAnimation{ return .slide } func downloadBtnClicked(sender:Any) -> Void{ let btn = sender as! UIButton btn.isEnabled = false let cell = btn.superview?.superview as! UITableViewCell let indexPath = tableView.indexPath(for: cell) requestChapter(atIndex: indexPath?.row ?? 0) // tableView(self.tableView, didSelectRowAt: indexPath!) } //网络请求某一章节的信息 func requestChapter(atIndex index:Int){ if index >= titles.count { return; } let _:String = "\(index)\(self.resource?.link ?? "")" _ = "\(CHAPTERURL)/\(titles[index].object(forKey: "link") ?? "")?k=19ec78553ec3a169&t=1476188085" // Alamofire.request(url).responseJSON { (response) in // if let json = response.result.value as? Dictionary { // QSLog("JSON:\(json)") // if let chapter = json["chapter"] as? Dictionary { // let chapterInfo = ChapterInfo(JSON: chapter) // QSLog(chapterInfo?.body) // chapterInfo?.currentIndex = index // chapterInfo?.resource = self.resource // let chapterInfo = QSChapter(JSON: chapter) // chapterInfo?.link = self.titles[index]["link"] as? String ?? "" // chapterInfo?.title = self.titles[index]["title"] as? String ?? "" // chapterInfo?.content = chapter["body"] as? String ?? "" // chapterInfo?.curChapter = index // QSChapter.updateLocalModel(localModel: chapterInfo!, link: self.resource?.link ?? "") // DispatchQueue.main.async { // self.tableView.reloadData() // } // } // } // } } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } } ================================================ FILE: zhuishushenqi/TXTReader/Category/CategoryViewController.swift ================================================ // // CategoryViewController.swift // PageViewController // // Created by Nory Cao on 16/10/11. // Copyright © 2016年 QS. All rights reserved. // import UIKit protocol CategoryViewDelegate { func categoryDidSelectAtIndex(index:Int) } class CategoryViewController: UIViewController,UITableViewDataSource,UITableViewDelegate { var categoryDelegate:CategoryViewDelegate? var titles:[String] = [] var selectedIndex:Int = 0 lazy var tableView:UITableView = { let tableView = UITableView(frame: UIScreen.main.bounds, style: .plain) tableView.dataSource = self tableView.delegate = self return tableView }() override func viewDidLoad() { super.viewDidLoad() self.view.backgroundColor = UIColor.black view.addSubview(self.tableView) tableView.separatorStyle = .singleLineEtched tableView.separatorColor = UIColor.gray tableView.backgroundColor = UIColor.red navigationController?.navigationBar.barTintColor = UIColor.black tableView.register(UINib(nibName: "CategoryTableViewCell", bundle: nil), forCellReuseIdentifier: "Category") let leftItem = UIBarButtonItem(image: UIImage(named: "bg_back_white"), style: .plain, target: self, action: #selector(dismiss(sender:))) navigationItem.leftBarButtonItem = leftItem navigationController?.setNavigationBarHidden(true, animated: false) let statusView = UIView(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.size.width, height: 30)) statusView.backgroundColor = UIColor.red //添加阴影也能达到相同的目的 // let layer = statusView.layer // let path = UIBezierPath(rect: layer.bounds) // layer.shadowPath = path.cgPath // layer.shadowColor = UIColor.black.cgColor // layer.shadowOffset = CGSize.zero // layer.shadowOpacity = 1.0 // layer.shadowRadius = 10 self.view.addSubview(statusView) setmask(statusBarBackgroundView: statusView) self.tableView.backgroundView = UIImageView(image: UIImage(named: "space")) } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) let indexPATH = IndexPath(row: selectedIndex, section: 0) self.tableView.scrollToRow(at: indexPATH , at: .middle, animated: false) setNeedsStatusBarAppearanceUpdate() } func setmask(statusBarBackgroundView:UIView){ let gradientLayer = CAGradientLayer() let colors = [UIColor(white: 0.0, alpha: 0.0).cgColor,UIColor(white: 0.0, alpha: 0.9).cgColor] gradientLayer.colors = colors gradientLayer.startPoint = CGPoint(x: 0.5, y: 1.0) gradientLayer.endPoint = CGPoint(x:0.5,y:0.7) gradientLayer.frame = statusBarBackgroundView.bounds statusBarBackgroundView.layer.mask = gradientLayer } func numberOfSections(in tableView: UITableView) -> Int { return 1 } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return titles.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "Category",for: indexPath as IndexPath) as! CategoryTableViewCell cell.count.text = "\(indexPath.row)." cell.backgroundColor = UIColor.red if titles.count > indexPath.row { cell.tittle.text = titles[indexPath.row] } cell.tittle.textColor = (selectedIndex == indexPath.row ? UIColor.green:UIColor.black) return cell } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { categoryDelegate?.categoryDidSelectAtIndex(index: indexPath.row) dismiss(animated: true, completion: nil) } @objc private func dismiss(sender:AnyObject){ dismiss(animated: true, completion: nil) } // override var prefersStatusBarHidden: Bool{ // return false // } override var preferredStatusBarStyle: UIStatusBarStyle{ return .lightContent } override var preferredStatusBarUpdateAnimation: UIStatusBarAnimation{ return .slide } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } } ================================================ FILE: zhuishushenqi/TXTReader/Category/ChangeSourceViewController.swift ================================================ // // ChangeSourceViewController.swift // zhuishushenqi // // Created by Nory Cao on 2017/3/16. // Copyright © 2017年 QS. All rights reserved. // import UIKit typealias SelectAction = (_ index:Int,_ resources:[ResourceModel]?)->Void class ChangeSourceViewController: BaseViewController ,UITableViewDataSource,UITableViewDelegate{ var selectAction:SelectAction? var viewModel:ZSReaderViewModel! fileprivate lazy var tableView:UITableView = { let tableView = UITableView(frame: CGRect(x: 0, y: 64, width: ScreenWidth, height: ScreenHeight - 64), style: .grouped) tableView.dataSource = self tableView.delegate = self tableView.sectionHeaderHeight = 60 tableView.sectionFooterHeight = CGFloat.leastNonzeroMagnitude tableView.estimatedRowHeight = 80 tableView.rowHeight = 80 tableView.qs_registerCellNib(ChangeSourceCell.self) return tableView }() override func viewDidLoad() { super.viewDidLoad() title = "选择来源" self.automaticallyAdjustsScrollViewInsets = false let rightBtn = UIButton(type: .custom) rightBtn.addTarget(self, action: #selector(close(btn:)), for: .touchUpInside) rightBtn.setImage(UIImage(named:"actionbar_close"), for: .normal) rightBtn.frame = CGRect(x: self.view.bounds.width - 45, y: 7, width: 30, height: 30) let rightBar = UIBarButtonItem(customView: rightBtn) self.navigationItem.rightBarButtonItem = rightBar view.addSubview(self.tableView) if let id = viewModel.book?._id { requestData(id: id) } } @objc func close(btn:UIButton){ dismiss(animated: true, completion: nil) } func requestData(id:String){ // http://api.zhuishushenqi.com/toc?view=summary&book=57e0dac5de88e4e83c6c5297 let urlString = "\(BASEURL)/toc" let param = ["view":"summary","book":"\(id)"] zs_get(urlString, parameters: param) { (response) in QSLog(response) if let resources:[Any] = response as? [Any] { if let models = [ResourceModel].deserialize(from: resources) as? [ResourceModel] { self.viewModel.book?.resources = models } DispatchQueue.main.async { self.tableView.reloadData() } } } } func numberOfSections(in tableView: UITableView) -> Int { return 2 } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { if section == 1 { if let resources = self.viewModel.book?.resources { return resources.count } } return 1 } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { if indexPath.section == 1 { let cell:ChangeSourceCell? = tableView.qs_dequeueReusableCell(ChangeSourceCell.self) if indexPath.row == self.viewModel.sourceIndex { cell?.isCurrentSelected = true } if let resources = self.viewModel.book?.resources { cell?.model = resources[indexPath.row] } return cell! }else{ let cell = UITableViewCell(style: .subtitle, reuseIdentifier: "Cell") cell.textLabel?.text = "自动选择" cell.accessoryType = .disclosureIndicator return cell } } func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { if section > 0 { let headerView = UIView() let headerLabel = UILabel() headerLabel.frame = CGRect(x: 15, y: 30, width: self.view.bounds.width - 30, height: 30) headerLabel.text = "共搜索到\(self.viewModel.book?.resources?.count ?? 0)个网站" headerLabel.textColor = UIColor.darkGray headerLabel.font = UIFont.systemFont(ofSize: 13) headerView.addSubview(headerLabel) return headerView } return nil } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { if let action = selectAction { self.viewModel.sourceIndex = indexPath.row action(indexPath.row,self.viewModel.book?.resources) } dismiss(animated: true, completion: nil) } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } } ================================================ FILE: zhuishushenqi/TXTReader/Category/QSCategoryInteractor.swift ================================================ // // QSCategoryInteractor.swift // zhuishushenqi // // Created Nory Cao on 2017/4/13. // Copyright © 2017年 QS. All rights reserved. // // Template generated by Juanpe Catalán @JuanpeCMiOS // import UIKit class QSCategoryInteractor: QSCategoryInteractorProtocol { var output: QSCategoryInteractorOutputProtocol! var bookDetail:BookDetail! func show(){ if let chapters = bookDetail.chapters { var titles:[String] = [] for item in chapters { if let title = item["title"] as? String { titles.append(title) } } self.output.show(titles: titles) } } func showDetail() { self.output.showDetail(book: bookDetail) } } ================================================ FILE: zhuishushenqi/TXTReader/Category/QSCategoryPresenter.swift ================================================ // // QSCategoryPresenter.swift // zhuishushenqi // // Created Nory Cao on 2017/4/13. // Copyright © 2017年 QS. All rights reserved. // // Template generated by Juanpe Catalán @JuanpeCMiOS // import UIKit class QSCategoryPresenter: QSCategoryPresenterProtocol { weak var view: QSCategoryViewProtocol? private let interactor: QSCategoryInteractorProtocol let router: QSCategoryWireframeProtocol var ranks:[QSHotComment] = [] var show:Bool = false init(interface: QSCategoryViewProtocol, interactor: QSCategoryInteractorProtocol, router: QSCategoryWireframeProtocol) { self.view = interface self.interactor = interactor self.router = router } func viewDidLoad() { view?.showActivityView() interactor.showDetail() interactor.show() } } extension QSCategoryPresenter:QSCategoryInteractorOutputProtocol{ func show(titles:[String]){ view?.hideActivityView() view?.show(titles: titles) } func showDetail(book: BookDetail) { view?.showDetail(book: book) // func fetchBookSuccess(bookDetail:BookDetail,ranks:[QSHotComment]){ // self.ranks = ranks view?.hideActivityView() } func fetchRankFailed() { view?.hideActivityView() } func fetchContent(show: Bool) { } func fetchAllChapterSuccess(bookDetail:BookDetail,res:[ResourceModel]){ router.presentReading(model: res, booDetail: bookDetail) view?.hideActivityView() } func fetchAllChapterFailed(){ view?.hideActivityView() } } ================================================ FILE: zhuishushenqi/TXTReader/Category/QSCategoryProtocols.swift ================================================ // // QSCategoryProtocols.swift // zhuishushenqi // // Created Nory Cao on 2017/4/13. // Copyright © 2017年 QS. All rights reserved. // // Template generated by Juanpe Catalán @JuanpeCMiOS // import Foundation //MARK: Wireframe - protocol QSCategoryWireframeProtocol: class { func presentReading(model:[ResourceModel],booDetail:BookDetail) } //MARK: Presenter - protocol QSCategoryPresenterProtocol: class { func viewDidLoad() } //MARK: Interactor - protocol QSCategoryInteractorProtocol: class { func show() func showDetail() } //MARK: Output - protocol QSCategoryInteractorOutputProtocol: class { func show(titles:[String]) func showDetail(book:BookDetail) } //MARK: View - protocol QSCategoryViewProtocol: IndicatableView { var presenter: QSCategoryPresenterProtocol? { get set } func show(titles:[String]) func showDetail(book:BookDetail) } ================================================ FILE: zhuishushenqi/TXTReader/Category/QSCategoryReaderViewController.swift ================================================ // // QSCategoryViewController.swift // zhuishushenqi // // Created by Nory Cao on 2017/4/17. // Copyright © 2017年 QS. All rights reserved. // import UIKit //import Alamofire protocol QSCategoryDelegate { func categoryDidSelectAtIndex(index:Int) } class QSCategoryReaderViewController: BaseViewController,UITableViewDataSource,UITableViewDelegate,CategoryCellDelegate ,QSCategoryViewProtocol{ var presenter: QSCategoryPresenterProtocol? var bookDetail:BookDetail? var viewModel = ZSReaderViewModel() var categoryDelegate:QSCategoryDelegate? var titles:[String] = [] var selectedIndex = 0 // key为章节的link,value为QSChapter模型 var chapterDict:[String:QSChapter] = [:] lazy var tableView:UITableView = { let tableView = UITableView(frame: CGRect(x:0,y:0,width:0,height:0), style: .grouped) tableView.dataSource = self tableView.delegate = self tableView.separatorStyle = .singleLineEtched tableView.separatorColor = UIColor.gray tableView.register(UINib(nibName: "CategoryTableViewCell", bundle: nil), forCellReuseIdentifier: "Category") return tableView }() override func viewDidLoad() { super.viewDidLoad() self.view.backgroundColor = UIColor.black view.addSubview(self.tableView) let leftItem = UIBarButtonItem(image: UIImage(named: "bg_back_white"), style: .plain, target: self, action: #selector(dismiss(sender:))) navigationItem.leftBarButtonItem = leftItem self.title = viewModel.book?.title if #available(iOS 11.0, *) { self.tableView.contentInsetAdjustmentBehavior = .never } } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) self.tableView.frame = CGRect(x: 0, y: kNavgationBarHeight, width: self.view.bounds.width, height: self.view.bounds.height - kNavgationBarHeight) let indexPATH = IndexPath(row: viewModel.book?.record?.chapter ?? 0, section: 0) if (viewModel.book?.chaptersInfo?.count ?? 0) > 0 && (viewModel.book?.chaptersInfo?.count ?? 0) > indexPATH.row { self.tableView.scrollToRow(at: indexPATH , at: .middle, animated: false) } } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) } func setmask(statusBarBackgroundView:UIView){ let gradientLayer = CAGradientLayer() let colors = [UIColor(white: 0.0, alpha: 0.0).cgColor,UIColor(white: 0.0, alpha: 0.9).cgColor] gradientLayer.colors = colors gradientLayer.startPoint = CGPoint(x: 0.5, y: 1.0) gradientLayer.endPoint = CGPoint(x:0.5,y:0.7) gradientLayer.frame = statusBarBackgroundView.bounds statusBarBackgroundView.layer.mask = gradientLayer } func numberOfSections(in tableView: UITableView) -> Int { return 1 } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { if viewModel.exsitLocal() { return viewModel.book?.book.localChapters.count ?? 0 } else { if let chaptersInfo = self.viewModel.book?.chaptersInfo { return chaptersInfo.count } return 0 } } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "Category",for: indexPath as IndexPath) as! CategoryTableViewCell cell.cellDelegate = self if viewModel.exsitLocal() { if let model = viewModel.book?.book.localChapters[indexPath.row] { cell.tittle.text = model.title cell.count.text = "\(indexPath.row)" cell.bind(model: model) if viewModel.book?.record?.chapter == indexPath.row { cell.tittle.textColor = UIColor.red } else { cell.tittle.textColor = UIColor.black } } } else { if let chaptersInfo = self.viewModel.book?.chaptersInfo { let link = chaptersInfo[indexPath.row].link let model = chapterDict[link] cell.bind(model: model) cell.tittle.text = chaptersInfo[indexPath.row].title cell.count.text = "\(indexPath.row)" if (viewModel.book?.record?.chapter == indexPath.row) { cell.tittle.textColor = UIColor.red } else { cell.tittle.textColor = UIColor.black } } } return cell } func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { return UIView() } func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { return 0.01 } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { categoryDelegate?.categoryDidSelectAtIndex(index: indexPath.row) dismiss(animated: true, completion: nil) } @objc private func dismiss(sender:AnyObject){ dismiss(animated: true, completion: nil) } override var prefersStatusBarHidden: Bool{ return false } override var preferredStatusBarStyle: UIStatusBarStyle{ return .`default` } override var preferredStatusBarUpdateAnimation: UIStatusBarAnimation{ return .slide } func downloadBtnClicked(sender:Any) -> Void{ let btn = sender as! UIButton btn.isEnabled = false let cell = btn.superview?.superview as! UITableViewCell let indexPath = tableView.indexPath(for: cell) requestChapter(atIndex: indexPath?.row ?? 0) } //网络请求某一章节的信息 func requestChapter(atIndex index:Int){ if index >= titles.count { return; } if !BookManager.shared.bookExist(book:self.bookDetail) { return } let chapter = bookDetail?.chapters?[index] var link:NSString = "\(chapter?["link"] ?? "")" as NSString link = link.urlEncode() as NSString let url = "\(CHAPTERURL)/\(link)?k=19ec78553ec3a169&t=1476188085" zs_get(url) { (response) in if let json = response as? Dictionary { QSLog("JSON:\(json)") if let chapter = json["chapter"] as? Dictionary { let chapterModel = self.bookDetail?.book.chapters[index] chapterModel?.content = chapter["body"] as? String ?? "" if let model = chapterModel { self.bookDetail?.book.chapters[index] = model } if let book = self.bookDetail { BookManager.shared.modifyBookshelf(book: book) } self.tableView.reloadData() } } } } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } func show(titles: [String]) { self.titles = titles self.tableView.reloadData() } func showDetail(book: BookDetail) { self.bookDetail = book selectedIndex = self.bookDetail?.record?.chapter ?? 0 self.tableView.reloadData() } } extension UINavigationController { //http://stackoverflow.com/questions/19022210/preferredstatusbarstyle-isnt-called //The UINavigationController does not forward on preferredStatusBarStyle calls to its child view controllers. Instead it manages its own state - as it should, it is drawing at the top of the screen where the status bar lives and so should be responsible for it. Therefor implementing preferredStatusBarStyle in your VCs within a nav controller will do nothing - they will never be called. open override var childForStatusBarStyle: UIViewController?{ return self.topViewController } open override var childForStatusBarHidden: UIViewController?{ return self.topViewController } } ================================================ FILE: zhuishushenqi/TXTReader/Category/QSCategoryRouter.swift ================================================ // // QSCategoryRouter.swift // zhuishushenqi // // Created Nory Cao on 2017/4/13. // Copyright © 2017年 QS. All rights reserved. // // Template generated by Juanpe Catalán @JuanpeCMiOS // import UIKit class QSCategoryRouter: QSCategoryWireframeProtocol { weak var viewController: UIViewController? static func createModule(book:BookDetail) -> UIViewController { // Change to get view from storyboard if not using progammatic UI let view = QSCategoryReaderViewController(nibName: nil, bundle: nil) let interactor = QSCategoryInteractor() let router = QSCategoryRouter() let presenter = QSCategoryPresenter(interface: view, interactor: interactor, router: router) view.presenter = presenter interactor.output = presenter router.viewController = view interactor.bookDetail = book return view } func presentReading(model:[ResourceModel],booDetail:BookDetail){ viewController?.present(QSTextRouter.createModule(bookDetail:booDetail,callback: {(book:BookDetail) in }), animated: true, completion: nil) } func presentComment(id:String){ let bookCommentVC = BookCommentViewController() bookCommentVC.id = id viewController?.navigationController?.pushViewController(bookCommentVC, animated: true) } } ================================================ FILE: zhuishushenqi/TXTReader/Community/QSCommunityInteractor.swift ================================================ // // QSCommunityInteractor.swift // zhuishushenqi // // Created yung on 2017/4/24. // Copyright © 2017年 QS. All rights reserved. // // Template generated by Juanpe Catalán @JuanpeCMiOS // import UIKit class QSCommunityInteractor: QSCommunityInteractorProtocol { var output: QSCommunityInteractorOutputProtocol? var model:BookDetail! var posts:[BookComment] = [] var comments:[BookComment] = [] var postStart:Int = 0 var commentStart:Int = 0 var selectedIndex = 0 func requestPost(){ selectedIndex = 0 postStart = 0 request() } func request(){ // http://api.zhuishushenqi.com/post/by-book?book=51d11e782de6405c45000068&sort=updated&type=normal,vote&start=0&limit=20 let url = "\(BASEURL)/post/by-book?book=\(model._id)&sort=updated&type=normal,vote&start=\(postStart)&limit=20" zs_get(url) { (response) in if let json = response?["posts"] as? NSArray { if let posts = [BookComment].deserialize(from: json as? [Any]) as? [BookComment] { if self.postStart == 0{ self.posts = posts }else{ self.posts.append(contentsOf: posts) } self.output?.fetchPostsSuccess(posts: self.posts) }else { self.output?.fetchPostsFailed() } }else { self.output?.fetchPostsFailed() } } } func requestPostMore(){ selectedIndex = 0 postStart += 20 request() } func requestBaseComments(){ selectedIndex = 1 commentStart = 0 requestComments() } func requestMoreComments(){ selectedIndex = 1 commentStart += 20 requestComments() } func requestComments(){ // http://api.zhuishushenqi.com/post/review/by-book?book=51d11e782de6405c45000068&sort=updated&start=0&limit=20 let url = "\(BASEURL)/post/review/by-book?book=\(model._id)&sort=updated&start=\(commentStart)&limit=20" zs_get(url) { (response) in if let json = response?["reviews"] as? NSArray { if let posts = [BookComment].deserialize(from: json as? [Any]) as? [BookComment] { if self.commentStart == 0{ self.comments = posts }else{ self.comments.append(contentsOf: posts) } self.output?.fetchCommentsSuccess(comments: self.comments) }else { self.output?.fetchCommentsFailed() } }else { self.output?.fetchCommentsFailed() } } } func showTitle(){ self.output?.showTitle(title: model.title) } func showDetail(indexPath: IndexPath) { if selectedIndex == 0 { self.output?.showDetail(detail: posts[indexPath.row]) }else{ self.output?.showDetail(detail: comments[indexPath.row]) } } } ================================================ FILE: zhuishushenqi/TXTReader/Community/QSCommunityPresenter.swift ================================================ // // QSCommunityPresenter.swift // zhuishushenqi // // Created yung on 2017/4/24. // Copyright © 2017年 QS. All rights reserved. // // Template generated by Juanpe Catalán @JuanpeCMiOS // import UIKit class QSCommunityPresenter: QSCommunityPresenterProtocol { weak var view: QSCommunityViewProtocol? let interactor: QSCommunityInteractorProtocol let router: QSCommunityWireframeProtocol init(interface: QSCommunityViewProtocol, interactor: QSCommunityInteractorProtocol, router: QSCommunityWireframeProtocol) { self.view = interface self.interactor = interactor self.router = router } func viewDidLoad() { interactor.showTitle() interactor.requestPost() view?.showActivityView() } func refreshData(){ view?.showActivityView() interactor.requestPost() } func requestMore(){ view?.showActivityView() interactor.requestPostMore() } func refreshComments(){ view?.showActivityView() interactor.requestBaseComments() } func requestMoreComments(){ view?.showActivityView() interactor.requestMoreComments() } func didSelectCell(indexPath: IndexPath) { interactor.showDetail(indexPath: indexPath) } } extension QSCommunityPresenter:QSCommunityInteractorOutputProtocol { func fetchPostsSuccess(posts: [BookComment]) { view?.hideActivityView() view?.showPosts(posts: posts) } func fetchPostsFailed() { view?.hideActivityView() view?.showEmpty() } func fetchCommentsSuccess(comments: [BookComment]) { view?.hideActivityView() view?.showComments(comments: comments) } func fetchCommentsFailed() { view?.hideActivityView() view?.showEmpty() } func showTitle(title: String) { view?.showTitle(title: title) } func showDetail(detail:BookComment){ router.presentDetail(detail: detail) } } ================================================ FILE: zhuishushenqi/TXTReader/Community/QSCommunityProtocols.swift ================================================ // // QSCommunityProtocols.swift // zhuishushenqi // // Created yung on 2017/4/24. // Copyright © 2017年 QS. All rights reserved. // // Template generated by Juanpe Catalán @JuanpeCMiOS // import Foundation //MARK: Wireframe - protocol QSCommunityWireframeProtocol: class { func presentDetail(detail:BookComment) } //MARK: Presenter - protocol QSCommunityPresenterProtocol: class { func viewDidLoad() func refreshData() func requestMore() func refreshComments() func requestMoreComments() func didSelectCell(indexPath:IndexPath) } //MARK: Interactor - protocol QSCommunityInteractorProtocol: class { func request() func requestPost() func requestPostMore() func requestComments() func requestBaseComments() func requestMoreComments() func showTitle() func showDetail(indexPath:IndexPath) } //MARK: Output - protocol QSCommunityInteractorOutputProtocol: class { func fetchPostsSuccess(posts:[BookComment]) func fetchPostsFailed() func fetchCommentsSuccess(comments:[BookComment]) func fetchCommentsFailed() func showTitle(title:String) func showDetail(detail:BookComment) } //MARK: View - protocol QSCommunityViewProtocol: IndicatableView { var presenter: QSCommunityPresenterProtocol? { get set } func showPosts(posts:[BookComment]) func showEmpty() func showComments(comments:[BookComment]) func showTitle(title:String) } ================================================ FILE: zhuishushenqi/TXTReader/Community/QSCommunityRouter.swift ================================================ // // QSCommunityRouter.swift // zhuishushenqi // // Created yung on 2017/4/24. // Copyright © 2017年 QS. All rights reserved. // // Template generated by Juanpe Catalán @JuanpeCMiOS // import UIKit class QSCommunityRouter: QSCommunityWireframeProtocol { weak var viewController: UIViewController? static func createModule(model:BookDetail) -> UIViewController { // Change to get view from storyboard if not using progammatic UI let view = QSCommunityViewController(nibName: nil, bundle: nil) let interactor = QSCommunityInteractor() let router = QSCommunityRouter() let presenter = QSCommunityPresenter(interface: view, interactor: interactor, router: router) view.presenter = presenter interactor.output = presenter router.viewController = view interactor.model = model return view } func presentDetail(detail: BookComment) { let commentVC = ZSBookCommentViewController(style: .grouped) commentVC.viewModel.model = detail viewController?.navigationController?.pushViewController(commentVC, animated: true) } } ================================================ FILE: zhuishushenqi/TXTReader/Community/QSCommunityViewController.swift ================================================ // // QSCommunityViewController.swift // zhuishushenqi // // Created yung on 2017/4/24. // Copyright © 2017年 QS. All rights reserved. // // Template generated by Juanpe Catalán @JuanpeCMiOS // import UIKit class QSCommunityViewController: UIViewController, QSCommunityViewProtocol,UITableViewDataSource,UITableViewDelegate ,Refreshable{ var presenter: QSCommunityPresenterProtocol? var discusses:[BookComment] = [] var comments:[BookComment] = [] var selectedIndex = 0 lazy var tableView:UITableView = { let tableView = UITableView(frame: CGRect(x: 0, y: kNavgationBarHeight + 40, width: ScreenWidth, height: ScreenHeight - kNavgationBarHeight - 40), style: .grouped) tableView.dataSource = self tableView.delegate = self tableView.separatorStyle = .singleLine tableView.qs_registerCellNib(QSDiscussCell.self) tableView.qs_registerCellNib(HotCommentCell.self) tableView.backgroundColor = UIColor(red: 0.95, green: 0.95, blue: 0.95, alpha: 1.0) return tableView }() override func viewDidLoad() { super.viewDidLoad() initRefreshHeader(tableView) { if self.selectedIndex == 0{ self.presenter?.refreshData() }else { self.presenter?.refreshComments() } } initRefreshFooter(tableView) { if self.selectedIndex == 0{ self.presenter?.requestMore() }else{ self.presenter?.requestMoreComments() } } setupSubviews() presenter?.viewDidLoad() } func setupSubviews(){ let segment = UISegmentedControl(frame: CGRect(x: self.view.bounds.width/4, y: kNavgationBarHeight + 5, width: self.view.bounds.width/2, height: 30)) segment.insertSegment(withTitle: "讨论", at: 0, animated: false) segment.insertSegment(withTitle: "书评", at: 1, animated: false) segment.selectedSegmentIndex = 0 segment.tintColor = UIColor.red segment.backgroundColor = UIColor.white segment.addTarget(self, action: #selector(segmentAction(seg:)), for: .valueChanged) self.view.backgroundColor = UIColor(red: 0.95, green: 0.95, blue: 0.95, alpha: 1.0) self.view.addSubview(segment) self.view.addSubview(tableView) self.automaticallyAdjustsScrollViewInsets = false } func numberOfSections(in tableView: UITableView) -> Int { return 1 } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { if selectedIndex == 0{ return discusses.count } return comments.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { if selectedIndex == 0{ let cell = tableView.qs_dequeueReusableCell(QSDiscussCell.self) cell?.model = discusses[indexPath.row] return cell! }else{ let cell = tableView.qs_dequeueReusableCell(HotCommentCell.self) cell?.model = comments[indexPath.row] cell?.backgroundColor = UIColor(red: 0.95, green: 0.95, blue: 0.95, alpha: 1.0) return cell! } } func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { if selectedIndex == 0{ return 70 }else { return 127 } } func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { return 0.001 } func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { return 0.001 } @objc func segmentAction(seg:UISegmentedControl){ if selectedIndex == seg.selectedSegmentIndex { return } selectedIndex = seg.selectedSegmentIndex if selectedIndex == 0{ if discusses.count == 0 { self.presenter?.refreshData() } }else if selectedIndex == 1 { if comments.count == 0 { self.presenter?.refreshComments() } } tableView.reloadData() } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) presenter?.didSelectCell(indexPath: indexPath) } func showPosts(posts: [BookComment]) { self.discusses = posts self.tableView.mj_header.endRefreshing() self.tableView.mj_footer.endRefreshing() self.tableView.reloadData() } func showComments(comments: [BookComment]) { self.comments = comments self.tableView.mj_header.endRefreshing() self.tableView.mj_footer.endRefreshing() self.tableView.reloadData() } func showEmpty() { self.tableView.mj_header.endRefreshing() self.tableView.mj_footer.endRefreshing() } func showTitle(title: String) { self.title = title } } ================================================ FILE: zhuishushenqi/TXTReader/Reader/Model/MonitorFileChangeHelp.h ================================================ // // MonitorFileChangeHelp.h // MonitorFilesChange // // Created by lingyohunl on 2016/11/17. // Copyright © 2016年 yohunl. All rights reserved. // #import @interface MonitorFileChangeHelp : NSObject - (void)watcherForPath:(NSString *)aPath block:(void (^)(NSInteger type))block; @end ================================================ FILE: zhuishushenqi/TXTReader/Reader/Model/MonitorFileChangeHelp.m ================================================ // // MonitorFileCChangeHelp.m // MonitorFilesChange // // Created by lingyohunl on 2016/11/17. // Copyright © 2016年 yohunl. All rights reserved. // #import "MonitorFileChangeHelp.h" @interface MonitorFileChangeHelp () { @private NSURL *_fileURL; dispatch_source_t _source; int _fileDescriptor; BOOL _keepMonitoringFile; } @property (nonatomic,copy) void (^fileChangeBlock)(NSInteger type); @end @implementation MonitorFileChangeHelp - (void)watcherForPath:(NSString *)aPath block:(void (^)(NSInteger type))block;{ self.fileChangeBlock = block; NSURL *url = [NSURL URLWithString:aPath]; _fileURL = url; _keepMonitoringFile = NO; [self __beginMonitoringFile]; } - (void)dealloc { dispatch_source_cancel(_source); } - (void)__beginMonitoringFile { _fileDescriptor = open([[_fileURL path] fileSystemRepresentation], O_EVTONLY); dispatch_queue_t defaultQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); _source = dispatch_source_create(DISPATCH_SOURCE_TYPE_VNODE, _fileDescriptor, DISPATCH_VNODE_ATTRIB | DISPATCH_VNODE_DELETE | DISPATCH_VNODE_EXTEND | DISPATCH_VNODE_LINK | DISPATCH_VNODE_RENAME | DISPATCH_VNODE_REVOKE | DISPATCH_VNODE_WRITE, defaultQueue); dispatch_source_set_event_handler(_source, ^ { unsigned long eventTypes = dispatch_source_get_data(_source); [self __alertDelegateOfEvents:eventTypes]; }); dispatch_source_set_cancel_handler(_source, ^{ close(_fileDescriptor); _fileDescriptor = 0; _source = nil; // If this dispatch source was canceled because of a rename or delete notification, recreate it if (_keepMonitoringFile) { _keepMonitoringFile = NO; [self __beginMonitoringFile]; } }); dispatch_resume(_source); } - (void)__recreateDispatchSource { _keepMonitoringFile = YES; dispatch_source_cancel(_source); } - (void)__alertDelegateOfEvents:(unsigned long)eventTypes { dispatch_async(dispatch_get_main_queue(), ^ { BOOL recreateDispatchSource = NO; NSMutableSet *eventSet = [[NSMutableSet alloc] initWithCapacity:7]; if (eventTypes & DISPATCH_VNODE_ATTRIB) { [eventSet addObject:@(DISPATCH_VNODE_ATTRIB)]; } if (eventTypes & DISPATCH_VNODE_DELETE) { [eventSet addObject:@(DISPATCH_VNODE_DELETE)]; recreateDispatchSource = YES; } if (eventTypes & DISPATCH_VNODE_EXTEND) { [eventSet addObject:@(DISPATCH_VNODE_EXTEND)]; } if (eventTypes & DISPATCH_VNODE_LINK) { [eventSet addObject:@(DISPATCH_VNODE_LINK)]; } if (eventTypes & DISPATCH_VNODE_RENAME){ [eventSet addObject:@(DISPATCH_VNODE_RENAME)]; recreateDispatchSource = YES; } if (eventTypes & DISPATCH_VNODE_REVOKE) { [eventSet addObject:@(DISPATCH_VNODE_REVOKE)]; } if (eventTypes & DISPATCH_VNODE_WRITE) { [eventSet addObject:@(DISPATCH_VNODE_WRITE)]; } for (NSNumber *eventType in eventSet) { if (self.fileChangeBlock) { self.fileChangeBlock([eventType unsignedIntegerValue]); } } if (recreateDispatchSource) { [self __recreateDispatchSource]; } }); } @end ================================================ FILE: zhuishushenqi/TXTReader/Reader/Model/ZSBoughtInfo.swift ================================================ // // ZSBoughtInfo.swift // zhuishushenqi // // Created by caony on 2018/11/15. // Copyright © 2018 QS. All rights reserved. // import UIKit import HandyJSON class ZSBoughtItem: NSObject, HandyJSON { var order:Int = 0 var key:String = "" required override init(){} } class ZSBoughtInfo: NSObject, HandyJSON { var keys:[ZSBoughtItem] = [] var ok = false required override init(){} } ================================================ FILE: zhuishushenqi/TXTReader/Reader/Model/ZSChapterBody.swift ================================================ // // ZSChapterBody.swift // zhuishushenqi // // Created by caony on 2018/7/3. // Copyright © 2018年 QS. All rights reserved. // import UIKit import HandyJSON class ZSChapterBody :NSObject, HandyJSON, NSCoding { var body:String = "" var title:String = "" var cpContent:String = "" var images:String = "" var created:String = "" var updated:String = "" var id:String = "" required override init() {} func encode(with aCoder: NSCoder) { aCoder.encode(self.body, forKey: "body") aCoder.encode(self.title, forKey: "title") aCoder.encode(self.cpContent, forKey: "cpContent") aCoder.encode(self.images, forKey: "images") aCoder.encode(self.created, forKey: "created") aCoder.encode(self.updated, forKey: "updated") aCoder.encode(self.id, forKey: "id") } required init?(coder aDecoder: NSCoder) { self.body = aDecoder.decodeObject(forKey: "body") as? String ?? "" self.title = aDecoder.decodeObject(forKey: "title") as? String ?? "" self.cpContent = aDecoder.decodeObject(forKey: "cpContent") as? String ?? "" self.images = aDecoder.decodeObject(forKey: "images") as? String ?? "" self.created = aDecoder.decodeObject(forKey: "created") as? String ?? "" self.updated = aDecoder.decodeObject(forKey: "updated") as? String ?? "" self.id = aDecoder.decodeObject(forKey: "id") as? String ?? "" } } ================================================ FILE: zhuishushenqi/TXTReader/Reader/Model/ZSChapterInfo.swift ================================================ // // ZSChapterInfo.swift // zhuishushenqi // // Created by caony on 2018/7/3. // Copyright © 2018年 QS. All rights reserved. // import Foundation import HandyJSON class ZSChapterInfo:NSObject,HandyJSON,NSCoding { var chapterCover:String = "" var currency:Int = 0 var isVip:Bool = false var link:String = "" var order:Int = 0 var partsize:Int = 0 var title:String = "" var totalpage:Int = 0 var unreadble:Int = 0 required override init() {} required init?(coder aDecoder: NSCoder) { super.init() self.chapterCover = aDecoder.decodeObject(forKey: "chapterCover") as? String ?? "" self.currency = aDecoder.decodeInteger(forKey: "currency") self.isVip = aDecoder.decodeBool(forKey: "isVip") self.link = aDecoder.decodeObject(forKey: "link") as? String ?? "" self.order = aDecoder.decodeInteger(forKey: "order") self.partsize = aDecoder.decodeInteger(forKey: "partsize") self.title = aDecoder.decodeObject(forKey: "title") as? String ?? "" self.totalpage = aDecoder.decodeInteger(forKey: "totalpage") self.unreadble = aDecoder.decodeInteger(forKey: "unreadble") } func encode(with aCoder: NSCoder) { aCoder.encode(self.chapterCover, forKey: "chapterCover") aCoder.encode(self.currency, forKey: "currency") aCoder.encode(self.isVip, forKey: "isVip") aCoder.encode(self.link, forKey: "link") aCoder.encode(self.order, forKey: "order") aCoder.encode(self.partsize, forKey: "partsize") aCoder.encode(self.title, forKey: "title") aCoder.encode(self.totalpage, forKey: "totalpage") aCoder.encode(self.unreadble, forKey: "unreadble") } } ================================================ FILE: zhuishushenqi/TXTReader/Reader/Model/ZSChapterSelectModel.swift ================================================ // // ZSChapterSelectModel.swift // zhuishushenqi // // Created by caony on 2018/11/13. // Copyright © 2018 QS. All rights reserved. // //{ // "levels": [ // { // "num": 10, // "discount": 0.9 // }, // { // "num": 40, // "discount": 0.85 // }, // { // "num": 100, // "discount": 0.75 // } // ], // "allowInputChapterNum": true, // "allowShowOtherSource": false, // "ok": true //} import UIKit import HandyJSON @objc(ZSChapterSelectItemModel) class ZSChapterSelectItemModel: NSObject, HandyJSON { var num:Int = 0 var discount:CGFloat = 1.0 required override init() {} } @objc(ZSChapterSelectModel) class ZSChapterSelectModel: NSObject, HandyJSON { var levels:[ZSChapterSelectItemModel] = [] var allowInputChapterNum:Bool = true var allowShowOtherSource:Bool = false var ok:Bool = false required override init() {} } ================================================ FILE: zhuishushenqi/TXTReader/Reader/Model/ZSHTTPConnection.h ================================================ // // ZSHTTPConnection.h // zhuishushenqi // // Created by yung on 2018/7/30. // Copyright © 2018年 QS. All rights reserved. // #import "HTTPConnection.h" @class MultipartFormDataParser; @interface ZSHTTPConnection : HTTPConnection{ MultipartFormDataParser *parser; NSFileHandle *storeFile; NSMutableArray *uploadedFiles; } @end FOUNDATION_EXTERN NSString *const ZSHTTPConnectionUploadFileFinished; ================================================ FILE: zhuishushenqi/TXTReader/Reader/Model/ZSHTTPConnection.m ================================================ // // ZSHTTPConnection.m // zhuishushenqi // // Created by yung on 2018/7/30. // Copyright © 2018年 QS. All rights reserved. // #import "ZSHTTPConnection.h" #import "HTTPLogging.h" #import "HTTPMessage.h" #import "HTTPDataResponse.h" #import "DDNumber.h" #import "HTTPDynamicFileResponse.h" #import "HTTPFileResponse.h" #import "MultipartMessageHeaderField.h" #import "MultipartFormDataParser.h" // Log levels : off, error, warn, info, verbose // Other flags : trace static const int httpLogLevel = HTTP_LOG_LEVEL_VERBOSE; // | HTTP_LOG_FLAG_TRACE NSString *const ZSHTTPConnectionUploadFileFinished = @"kUploadFileFinished"; /** * All we have to do is voerride appropriate methods in HTTPConnection. */ @interface ZSHTTPConnection() @end @implementation ZSHTTPConnection - (BOOL)supportsMethod:(NSString *)method atPath:(NSString *)path { HTTPLogTrace(); if ([method isEqualToString:@"POST"]) { if ([path isEqualToString:@"/upload.html"]) { return YES; } } return [super supportsMethod:method atPath:path]; } - (BOOL)expectsRequestBodyFromMethod:(NSString *)method atPath:(NSString *)path { HTTPLogTrace(); // Inform HTTP server that we expect a body to accompany a POST request if ([method isEqualToString:@"POST"] && [path isEqualToString:@"/upload.html"]) { // here we need to make sure, boundary is set in header NSString *contentType = [request headerField:@"Content-Type"]; NSUInteger paramsSeparator = [contentType rangeOfString:@";"].location; if (NSNotFound == paramsSeparator) { return NO; } if (paramsSeparator >= contentType.length - 1) { return NO; } NSString *type = [contentType substringToIndex:paramsSeparator]; if (![type isEqualToString:@"multipart/form-data"]) { // we expect multipart/form-data content type return NO; } // enumerate all params in content-type, and find boundary there NSArray *params = [[contentType substringFromIndex:paramsSeparator + 1] componentsSeparatedByString:@";"]; for (NSString *param in params) { paramsSeparator = [param rangeOfString:@"="].location; if (NSNotFound == paramsSeparator || paramsSeparator >= param.length - 1) { continue; } NSString *paramName = [param substringWithRange:NSMakeRange(1, paramsSeparator - 1)]; NSString *paramValue = [param substringFromIndex:paramsSeparator + 1]; if ([paramName isEqualToString:@"boundary"]) { // let's separate the boundary from content-type, to make it more handy to handle [request setHeaderField:@"boundary" value:paramValue]; } } // check if boundary specified if ([request headerField:@"boundary"] == nil) { return NO; } return YES; } return [super expectsRequestBodyFromMethod:method atPath:path]; } - (NSObject *)httpResponseForMethod:(NSString *)method URI:(NSString *)path { HTTPLogTrace(); if ([method isEqualToString:@"POST"] && [path isEqualToString:@"/upload.html"]) { // this method will generate response with links to uploaded file NSMutableString *filesStr = [[NSMutableString alloc] init]; for (NSString *filePath in uploadedFiles) { // generate links [filesStr appendFormat:@" %@
", filePath, [filePath lastPathComponent]]; } NSString *templatePath = [[config documentRoot] stringByAppendingPathComponent:@"upload.html"]; NSDictionary *replacementDict = [NSDictionary dictionaryWithObject:filesStr forKey:@"MyFiles"]; // use dynamic file response to apply our links to response template return [[HTTPDynamicFileResponse alloc] initWithFilePath:templatePath forConnection:self separator:@"%" replacementDictionary:replacementDict]; } if ([method isEqualToString:@"GET"] && [path hasPrefix:@"/upload/"]) { // let download the uploaded files return [[HTTPFileResponse alloc] initWithFilePath:[[config documentRoot] stringByAppendingString:path] forConnection:self]; } return [super httpResponseForMethod:method URI:path]; } - (void)prepareForBodyWithSize:(UInt64)contentLength { HTTPLogTrace(); // set up mine parser NSString *boundary = [request headerField:@"boundary"]; parser = [[MultipartFormDataParser alloc] initWithBoundary:boundary formEncoding:NSUTF8StringEncoding]; parser.delegate = self; uploadedFiles = [[NSMutableArray alloc] init]; } - (void)processBodyData:(NSData *)postDataChunk { HTTPLogTrace(); // append data to the parser. It will invoke callbacks to let us handle // parsed data. [parser appendData:postDataChunk]; } #pragma mark -- multipart form data parser delegate - (void)processStartOfPartWithHeader:(MultipartMessageHeader *)header { // in this sample, we are not interested in parts, other then file parts. // check content disposition to find out filename MultipartMessageHeaderField *disposition = [header.fields objectForKey:@"Content-Disposition"]; NSString *filename = [[disposition.params objectForKey:@"filename"] lastPathComponent]; if (filename == nil || [filename isEqualToString:@""]) { // it's either not a file part, or // an empty form sent. we won't handle it. return; } // NSString* uploadDirPath = [[config documentRoot] stringByAppendingPathComponent:@"upload"]; // BOOL isDir = YES; // if (![[NSFileManager defaultManager]fileExistsAtPath:uploadDirPath isDirectory:&isDir ]) { // [[NSFileManager defaultManager]createDirectoryAtPath:uploadDirPath withIntermediateDirectories:YES attributes:nil error:nil]; // } // Inbox 创建文件失败 NSString *uploadDirPath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject] stringByAppendingPathComponent:@"LocalBooks"]; NSString *filePath = [uploadDirPath stringByAppendingPathComponent:filename]; if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) { storeFile = nil; }else { HTTPLogVerbose(@"Saving file to %@", filePath); if (![[NSFileManager defaultManager] createDirectoryAtPath:uploadDirPath withIntermediateDirectories:YES attributes:nil error:nil]) { HTTPLogError(@"Could not create directory at path: %@", filePath); } if (![[NSFileManager defaultManager] createFileAtPath:filePath contents:nil attributes:nil]) { HTTPLogError(@"Cound not create file at path: %@", filePath); } storeFile = [NSFileHandle fileHandleForWritingAtPath:filePath]; [uploadedFiles addObject:[NSString stringWithFormat:@"/upload/%@", filePath]]; } } - (void)processContent:(NSData *)data WithHeader:(MultipartMessageHeader *)header { // here we just write the output from parser to the file MultipartMessageHeaderField *field = header.fields[@"Content-Disposition"]; NSString *fileName = field.params[@"filename"]; NSLog(@"fileName:%@",fileName); [[NSNotificationCenter defaultCenter] postNotificationName:ZSHTTPConnectionUploadFileFinished object:nil userInfo:field.params]; if (storeFile) { [storeFile writeData:data]; NSLog(@"????"); } } - (void)processEndOfPartWithHeader:(MultipartMessageHeader *)header { // as the file part is over, we close the file. [storeFile closeFile]; storeFile = nil; } - (void)processPreambleData:(NSData *)data { // if we are interested in preamble data, we could process it here. } - (void)processEpilogueData:(NSData *)data { // if we are interested in epilogue data, we could process it here } @end ================================================ FILE: zhuishushenqi/TXTReader/Reader/Model/ZSHTTPTool.h ================================================ // // ZSHTTPTool.h // zhuishushenqi // // Created by yung on 2018/7/30. // Copyright © 2018年 QS. All rights reserved. // #import @interface ZSHTTPTool : NSObject + (NSString *)getIPAddress:(BOOL)preferIPv4; + (BOOL)isValidatIP:(NSString *)ipAddress; + (NSDictionary *)getIPAddresses; @end ================================================ FILE: zhuishushenqi/TXTReader/Reader/Model/ZSHTTPTool.m ================================================ // // ZSHTTPTool.m // zhuishushenqi // // Created by yung on 2018/7/30. // Copyright © 2018年 QS. All rights reserved. // #import "ZSHTTPTool.h" #import #import #import #define IOS_CELLULAR @"pdp_ip0" #define IOS_WIFI @"en0" #define IOS_VPN @"utun0" #define IP_ADDR_IPv4 @"ipv4" #define IP_ADDR_IPv6 @"ipv6" @implementation ZSHTTPTool #pragma mark - 获取设备当前网络IP地址 + (NSString *)getIPAddress:(BOOL)preferIPv4 { NSArray *searchArray = preferIPv4 ? @[ IOS_VPN @"/" IP_ADDR_IPv4, IOS_VPN @"/" IP_ADDR_IPv6, IOS_WIFI @"/" IP_ADDR_IPv4, IOS_WIFI @"/" IP_ADDR_IPv6, IOS_CELLULAR @"/" IP_ADDR_IPv4, IOS_CELLULAR @"/" IP_ADDR_IPv6 ] : @[ IOS_VPN @"/" IP_ADDR_IPv6, IOS_VPN @"/" IP_ADDR_IPv4, IOS_WIFI @"/" IP_ADDR_IPv6, IOS_WIFI @"/" IP_ADDR_IPv4, IOS_CELLULAR @"/" IP_ADDR_IPv6, IOS_CELLULAR @"/" IP_ADDR_IPv4 ] ; NSDictionary *addresses = [self getIPAddresses]; NSLog(@"addresses: %@", addresses); __block NSString *address; [searchArray enumerateObjectsUsingBlock:^(NSString *key, NSUInteger idx, BOOL *stop) { address = addresses[key]; //筛选出IP地址格式 if([self isValidatIP:address]) *stop = YES; }]; return address ? address : @"0.0.0.0"; } + (BOOL)isValidatIP:(NSString *)ipAddress { if (ipAddress.length == 0) { return NO; } NSString *urlRegEx = @"^([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\." "([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\." "([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\." "([01]?\\d\\d?|2[0-4]\\d|25[0-5])$"; NSError *error; NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:urlRegEx options:0 error:&error]; if (regex != nil) { NSTextCheckingResult *firstMatch=[regex firstMatchInString:ipAddress options:0 range:NSMakeRange(0, [ipAddress length])]; if (firstMatch) { NSRange resultRange = [firstMatch rangeAtIndex:0]; NSString *result=[ipAddress substringWithRange:resultRange]; //输出结果 NSLog(@"%@",result); return YES; } } return NO; } + (NSDictionary *)getIPAddresses { NSMutableDictionary *addresses = [NSMutableDictionary dictionaryWithCapacity:8]; // retrieve the current interfaces - returns 0 on success struct ifaddrs *interfaces; if(!getifaddrs(&interfaces)) { // Loop through linked list of interfaces struct ifaddrs *interface; for(interface=interfaces; interface; interface=interface->ifa_next) { if(!(interface->ifa_flags & IFF_UP) /* || (interface->ifa_flags & IFF_LOOPBACK) */ ) { continue; // deeply nested code harder to read } const struct sockaddr_in *addr = (const struct sockaddr_in*)interface->ifa_addr; char addrBuf[ MAX(INET_ADDRSTRLEN, INET6_ADDRSTRLEN) ]; if(addr && (addr->sin_family==AF_INET || addr->sin_family==AF_INET6)) { NSString *name = [NSString stringWithUTF8String:interface->ifa_name]; NSString *type; if(addr->sin_family == AF_INET) { if(inet_ntop(AF_INET, &addr->sin_addr, addrBuf, INET_ADDRSTRLEN)) { type = IP_ADDR_IPv4; } } else { const struct sockaddr_in6 *addr6 = (const struct sockaddr_in6*)interface->ifa_addr; if(inet_ntop(AF_INET6, &addr6->sin6_addr, addrBuf, INET6_ADDRSTRLEN)) { type = IP_ADDR_IPv6; } } if(type) { NSString *key = [NSString stringWithFormat:@"%@/%@", name, type]; addresses[key] = [NSString stringWithUTF8String:addrBuf]; } } } // Free memory freeifaddrs(interfaces); } return [addresses count] ? addresses : nil; } @end ================================================ FILE: zhuishushenqi/TXTReader/Reader/Model/ZSReaderProtocol.swift ================================================ // // ZSReaderProtocol.swift // zhuishushenqi // // Created by caony on 2018/7/3. // Copyright © 2018年 QS. All rights reserved. // import Foundation // 点击 protocol ZSReaderTap { func showNextPage(page:QSPage) func showLastPage(page:QSPage) } ================================================ FILE: zhuishushenqi/TXTReader/Reader/Model/web/index.html ================================================ 追书神器
================================================ FILE: zhuishushenqi/TXTReader/Reader/Model/web/s.css ================================================ * {padding:0;margin:0;} body {margin:0;font: normal 12px Arial, Verdana, Tahoma; color:#333;background: #F0F0F0 url("bg.png") 50% 0 fixed repeat;} #main {width:800px;margin:0 auto;} #header {background: transparent url(titlebg.png) repeat-x;border-top: 1px solid #dcdcdc;border-bottom: 1px solid #404040;height:50px;} form.upload {margin-left:12px;padding-left:48px;padding-top:8px;margin-top:10px;height:32px;} table {width:100%;padding:0;border-bottom: 1px solid #fff;} thead {margin:0;padding:0;} th {height:15px;text-align: left;padding-left:20px;border-left:1px solid #999;border-bottom:1px solid #999;color:#303;font-size:.9em;font-weight: normal;background: transparent url(theadbg.png) repeat-x;} td.del, th.del {width:8em;} tbody td {padding-left:20px;background-color:#fff;height:20px;border-bottom: 1px solid #ccc;} tr.shadow td{background-color:#ecf3fe;} a.file {text-decoration:none;color:#333;} #footer {height:50px;border-top:1px solid #ccc;margin:0 auto;position:absolute;bottom:0px;width:800px;text-align: center;} #footer .content {border-top: 1px solid #fff;} ================================================ FILE: zhuishushenqi/TXTReader/Reader/Model/web/upload.html ================================================ %MyFiles% ================================================ FILE: zhuishushenqi/TXTReader/Reader/PageViewController.swift ================================================ // // PageViewController.swift // PageViewController // // Created by Nory Cao on 16/10/9. // Copyright © 2016年 QS. All rights reserved. // import UIKit let PageViewDidTap = "PageViewDidTap" class PageViewController: UIViewController { lazy var titleLabel:UILabel = { let titleLabel = UILabel() titleLabel.textAlignment = .center titleLabel.frame = CGRect(x:0,y:CGFloat(kQSReaderTopMargin),width:self.view.bounds.size.width,height:20) titleLabel.backgroundColor = UIColor.clear titleLabel.font = UIFont.systemFont(ofSize: 11) titleLabel.text = "" titleLabel.textColor = AppStyle.shared.reader.textColor return titleLabel }() lazy var pageLabel:UILabel = { let pageLabel = UILabel() pageLabel.frame = CGRect(x:0,y:self.view.bounds.size.height - 30,width:self.view.bounds.size.width,height:30) pageLabel.font = UIFont.systemFont(ofSize: 13) pageLabel.textAlignment = .center pageLabel.backgroundColor = UIColor.clear pageLabel.textColor = AppStyle.shared.reader.textColor pageLabel.text = "1/1" return pageLabel }() var page:QSPage? { didSet { refreshView() } } var newPage:ZSBookPage? { didSet { self.view.alpha = 1.0 refreshView() } } lazy var batteryView:QSBatteryView = { let batteryView = QSBatteryView(frame: CGRect(x: 15, y: self.view.bounds.height - 20, width: 25, height: 10)) batteryView.batteryLevel = CGFloat(UIDevice.current.batteryLevel) return batteryView }() lazy var timeLabel:UILabel = { let timeLabel = UILabel() timeLabel.font = UIFont.systemFont(ofSize: 11) timeLabel.textAlignment = .center timeLabel.frame = CGRect(x:self.view.bounds.width - 40 - 15 , y: self.view.bounds.height - 30, width: 40, height: 30) timeLabel.text = getCurrentTime() return timeLabel }() var timer:Timer? // 正版购买信息 var payView:ZSChapterPayView! var isShowPayView:Bool = false var isPageEditing:Bool { return pageView.isEditing } var startEditingHandler:ZSDisplayHandler? var endEditingHandler:ZSDisplayHandler? override func viewDidLoad() { super.viewDidLoad() setSubviews() } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) NotificationCenter.default.removeObserver(self, name: UIDevice.batteryLevelDidChangeNotification, object: nil) } override func viewDidDisappear(_ animated: Bool) { super.viewDidDisappear(animated) NotificationCenter.default.removeObserver(self, name: UIDevice.batteryLevelDidChangeNotification, object: nil) NotificationCenter.default.removeObserver(self) } override func viewWillLayoutSubviews() { layoutSubview() } private func layoutSubview() { titleLabel.snp.remakeConstraints { (make) in var top = 0 let orientation = UIApplication.shared.statusBarOrientation if orientation.isPortrait && IPHONEX { top = 30 } make.left.right.equalToSuperview() make.top.equalTo(top) make.height.equalTo(20) } pageLabel.snp.remakeConstraints { (make) in make.left.right.bottom.equalToSuperview() make.height.equalTo(30) } batteryView.snp.remakeConstraints { (make) in make.bottom.equalToSuperview().offset(-10) make.left.equalTo(15) make.width.equalTo(25) make.height.equalTo(10) } timeLabel.snp.remakeConstraints { (make) in make.right.equalToSuperview().offset(-15) make.bottom.equalToSuperview() make.width.equalTo(40) make.height.equalTo(30) } payView.snp.remakeConstraints { (make) in make.edges.equalToSuperview() } if let _ = pageView.superview { pageView.snp.remakeConstraints { (make) in make.left.equalToSuperview().offset(10) make.right.equalToSuperview().offset(-10) make.top.equalTo(titleLabel.snp.bottom).offset(10) make.bottom.equalToSuperview().offset(-30) } } bgView.snp.remakeConstraints { (make) in make.edges.equalToSuperview() } } func setSubviews() -> Void { view.addSubview(bgView) view.addSubview(titleLabel) view.addSubview(pageLabel) view.addSubview(timeLabel) view.addSubview(batteryView) view.addSubview(pageView) payView = ZSChapterPayView(frame: CGRect(x: 0, y: view.bounds.height, width: view.bounds.width, height: 0)) payView.isHidden = true view.addSubview(payView) UIDevice.current.isBatteryMonitoringEnabled = true NotificationCenter.default.addObserver(forName: UIDevice.batteryLevelDidChangeNotification, object: nil, queue: OperationQueue.main) { [weak self] (notification) in let level = UIDevice.current.batteryLevel QSLog("电池电量:\(level))") self?.batteryView.batteryLevel = CGFloat(level) } // Fallback on earlier versions timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(getTime(time:)), userInfo: nil, repeats: true) } func showPayView() { isShowPayView = true payView.isHidden = false payView.titleLabel.text = titleLabel.text view.bringSubviewToFront(payView) UIView.animate(withDuration: 0.25, animations: { self.payView.frame = CGRect(x: 0, y: 0, width: self.view.bounds.width, height: self.view.bounds.height) }) { (finished) in } } func hidePayView() { isShowPayView = false payView.isHidden = true } @objc func getTime(time:Timer){ self.timeLabel.text = self.getCurrentTime() } @objc func getCurrentTime()->String{ let calendar = NSCalendar.current as NSCalendar let components = calendar.components([NSCalendar.Unit.hour,NSCalendar.Unit.minute], from: Date()) let hour = components.hour let minute = components.minute return "\(toBit(bit: hour ?? 00)):\(toBit(bit: minute ?? 00))" } func toBit(bit:Int) ->String{ var bitString = String(describing: bit) if bit < 10 { bitString = String(format: "%02d", arguments: [bit]) } return bitString } public func setPage(tmpPage:QSPage?){ if let realPage = tmpPage { page = realPage } else { page = QSPage() } } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) view.backgroundColor = UIColor.clear view.addSubview(pageView) layoutSubview() } func refreshView(){ if let page = self.page { pageView.attributedText = page.content pageLabel.text = "第\((page.curPage + 1))/\(page.totalPages)页" titleLabel.text = page.title titleLabel.textColor = AppStyle.shared.reader.textColor pageLabel.textColor = AppStyle.shared.reader.textColor timeLabel.textColor = AppStyle.shared.reader.textColor batteryView.batteryColor = AppStyle.shared.reader.batteryColor } else if let page = self.newPage { pageView.attributedText = page.content pageLabel.text = "第\((page.pageIndex + 1))/\(page.totalPages)页" titleLabel.text = page.chapterName titleLabel.textColor = AppStyle.shared.reader.textColor pageLabel.textColor = AppStyle.shared.reader.textColor timeLabel.textColor = AppStyle.shared.reader.textColor batteryView.batteryColor = AppStyle.shared.reader.batteryColor } } func clearEditing() { pageView.clearEditing() } func nextParagraph(string:String) -> String { return pageView.nextParagraph(string: string) } func startEdit(for string:String) { pageView.startEdit(for: string) } lazy var pageView:PageView = { let pageView = PageView() pageView.frame = ZSReader.share.contentFrame pageView.backgroundColor = UIColor.clear pageView.startEditingHandler = { [weak self] in self?.startEditingHandler?() } pageView.endEditingHandler = { [weak self] in self?.endEditingHandler?() } return pageView }() lazy var bgView:UIImageView = { let bgView:UIImageView = UIImageView(frame: CGRect(x: 0, y: 0, width: self.view.bounds.width, height: self.view.bounds.height)) bgView.image = ZSReader.share.theme.readerStyle.backgroundImage bgView.backgroundColor = UIColor.white bgView.isUserInteractionEnabled = true return bgView }() override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } override func touchesEnded(_ touches: Set, with event: UIEvent?) { if touches.first?.tapCount == 1 { // 发送tap事件给其它的在意者 NotificationCenter.qs_postNotification(name: PageViewDidTap, obj: nil) } } func destroy() { timer?.invalidate() timer = nil } deinit { QSLog("PageVC释放了") } } ================================================ FILE: zhuishushenqi/TXTReader/Reader/QSMoreSettingController.swift ================================================ // // QSMoreSettingController.swift // zhuishushenqi // // Created by yung on 2018/2/8. // Copyright © 2018年 QS. All rights reserved. // typealias QSMoreSettingCallbackAction = (_ obj:Int)->Void import UIKit class QSMoreSettingController: UIViewController { private let settings = [["title":"翻页方式","list":["拟真","简洁","滑动"],"tip":"选择要使用的翻页方式"], ["title":"简繁转换","list":["简体","繁体"],"tip":"选择使用简体或繁体"], ["title":"字体设置","list":["默认","兰亭黑","楷体","魏碑","雅痞","翩翩体","隶书","日文字体"]], ["title":"自动购买","switch":"off"], ["title":"发音人","viewController":ZSSpeakerViewController.self]] override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. view.addSubview(self.tableView) setupNavigationItem() automaticallyAdjustsScrollViewInsets = false if #available(iOS 11, *) { tableView.contentInsetAdjustmentBehavior = .never } } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) tableView.frame = CGRect(x: 0, y: kNavgationBarHeight, width: ScreenWidth, height: ScreenHeight - kNavgationBarHeight) } private func setupNavigationItem(){ self.title = "更多设置" let leftItem = UIBarButtonItem(image: UIImage(named: "nav_back_white"), style: .plain, target: self, action: #selector(dismissVC)) navigationItem.leftBarButtonItem = leftItem } @objc private func dismissVC(){ dismiss(animated: true, completion: nil) } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } lazy var tableView:UITableView = { let tableView = UITableView(frame: CGRect(x: 0, y: 0, width: 0, height: 0), style: .grouped) tableView.dataSource = self tableView.delegate = self tableView.separatorStyle = .singleLine tableView.qs_registerCellClass(QSMoreSettingCell.self) tableView.backgroundColor = UIColor(red: 0.95, green: 0.95, blue: 0.95, alpha: 1.0) return tableView }() } extension QSMoreSettingController:UITableViewDataSource,UITableViewDelegate{ func addSwitch() -> UIControl{ let autoSwitch = UISwitch() autoSwitch.onTintColor = UIColor.red autoSwitch.isOn = false return autoSwitch } func numberOfSections(in tableView: UITableView) -> Int { return 1 } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return settings.count; } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.qs_dequeueReusableCell(QSMoreSettingCell.self) let title = settings[indexPath.row]["title"] as? String cell?.textLabel?.text = title cell?.accessoryType = .disclosureIndicator if indexPath.row == settings.count - 2 { cell?.accessoryType = .none } let autoSwitch = settings[indexPath.row]["switch"] if let _ = autoSwitch { let switchView = addSwitch() switchView.center = CGPoint(x: ScreenWidth - 34, y: 22) cell?.contentView.addSubview(switchView) } let detail = [QSReaderSetting.shared.pageStyle.rawValue, QSReaderSetting.shared.chineseFontStyle.rawValue, QSReaderSetting.shared.fontStyle.rawValue] let list = settings[indexPath.row]["list"] as? [String] if indexPath.row < detail.count { cell?.detailTextLabel?.text = list?[detail[indexPath.row]] } return cell! } func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { return UIView() } func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { return 10 } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) if indexPath.row < settings.count { let list = settings[indexPath.row]["list"] as? [String] let tip = settings[indexPath.row]["tip"] as? String if indexPath.row == 2 { let fontVC = ZSFontViewController(style: .grouped) fontVC.title = "字体设置" self.navigationController?.pushViewController(fontVC, animated: true) } if let speakerVC = settings[indexPath.row]["viewController"] as? ZSSpeakerViewController.Type { let vc = speakerVC.init() vc.title = settings[indexPath.row]["title"] as? String self.navigationController?.pushViewController(vc, animated: true) } if let ttip = tip,let llist = list { actionSheet(title: ttip, message: "", list: llist, callback: { (obj) in QSLog("点击了第\(obj)个") if (indexPath.row == 0) { QSReaderSetting.shared.pageStyle = ZSReaderAnimationStyle(rawValue: obj) ?? .curlPage } else if indexPath.row == 1 { QSReaderSetting.shared.chineseFontStyle = QSReaderChineseFontStyle(rawValue: obj) ?? .simpleChinese } else if indexPath.row == 2 { QSReaderSetting.shared.fontStyle = QSReaderFontStyle(rawValue: obj) ?? .system } else if indexPath.row == 3 { } tableView.reloadRow(at: indexPath, with: .automatic) }) } } } //MARK: - ActionSheet func actionSheet(title:String,message:String,list:[String],callback: QSMoreSettingCallbackAction?){ let alertController = UIAlertController(title: title, message: message, preferredStyle: .actionSheet) var index = 0 for li in list { let callbackIndex = index let action = UIAlertAction(title: li, style: .default, handler: { (action) in if let handler = callback { handler(callbackIndex) } }) alertController.addAction(action) index += 1 } let action = UIAlertAction(title: "取消", style: .cancel, handler: nil) alertController.addAction(action) present(alertController, animated: true, completion: nil) } } //fileprivate extension UITableViewCell { // convenience init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { // self.init(style: .value1, reuseIdentifier: reuseIdentifier) // } //} @objcMembers class QSMoreSettingCell: UITableViewCell { override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: .value1, reuseIdentifier: reuseIdentifier) } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } } ================================================ FILE: zhuishushenqi/TXTReader/Reader/QSReaderBackgroundViewController.swift ================================================ // // QSReaderBackgroundViewController.swift // zhuishushenqi // // Created by Nory Cao on 2017/4/17. // Copyright © 2017年 QS. All rights reserved. // import UIKit class QSReaderBackgroundViewController: UIViewController { var backgroundImg:UIImage? override func viewDidLoad() { super.viewDidLoad() backImage.image = backgroundImg view.addSubview(backImage) } lazy var backImage:UIImageView = { let img = UIImageView() img.backgroundColor = UIColor.red img.frame = self.view.bounds return img }() override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } func setBackground(viewController:PageViewController){ let image = viewController.view.screenShot() self.backgroundImg = image } } ================================================ FILE: zhuishushenqi/TXTReader/Reader/QSReaderViewController.swift ================================================ // // QSReaderViewController.swift // zhuishushenqi // // Created by yung on 2018/2/11. // Copyright © 2018年 QS. All rights reserved. // import UIKit private let reuseIdentifier = "Cell" class QSReaderViewController: UICollectionViewController { override func viewDidLoad() { super.viewDidLoad() // Uncomment the following line to preserve selection between presentations // self.clearsSelectionOnViewWillAppear = false // Register cell classes self.collectionView!.register(UICollectionViewCell.self, forCellWithReuseIdentifier: reuseIdentifier) // Do any additional setup after loading the view. } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } /* // MARK: - Navigation // In a storyboard-based application, you will often want to do a little preparation before navigation override func prepare(for segue: UIStoryboardSegue, sender: Any?) { // Get the new view controller using [segue destinationViewController]. // Pass the selected object to the new view controller. } */ // MARK: UICollectionViewDataSource override func numberOfSections(in collectionView: UICollectionView) -> Int { // #warning Incomplete implementation, return the number of sections return 0 } override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { // #warning Incomplete implementation, return the number of items return 0 } override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) // Configure the cell return cell } // MARK: UICollectionViewDelegate /* // Uncomment this method to specify if the specified item should be highlighted during tracking override func collectionView(_ collectionView: UICollectionView, shouldHighlightItemAt indexPath: IndexPath) -> Bool { return true } */ /* // Uncomment this method to specify if the specified item should be selected override func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool { return true } */ /* // Uncomment these methods to specify if an action menu should be displayed for the specified item, and react to actions performed on the item override func collectionView(_ collectionView: UICollectionView, shouldShowMenuForItemAt indexPath: IndexPath) -> Bool { return false } override func collectionView(_ collectionView: UICollectionView, canPerformAction action: Selector, forItemAt indexPath: IndexPath, withSender sender: Any?) -> Bool { return false } override func collectionView(_ collectionView: UICollectionView, performAction action: Selector, forItemAt indexPath: IndexPath, withSender sender: Any?) { } */ } ================================================ FILE: zhuishushenqi/TXTReader/Reader/QSTextProtocols.swift ================================================ // // QSTextProtocols.swift // zhuishushenqi // // Created by Nory Cao on 2017/4/14. // Copyright © 2017年 QS. All rights reserved. // import Foundation import UIKit //MARK: Wireframe - protocol QSTextWireframeProtocol: class { var viewController: UIViewController? { get set } func presentDetails(_ novel:QSRankModel) func presentCategory(book:BookDetail,books:[String:Any]) } //MARK: Presenter - protocol QSTextPresenterProtocol: class { var view: QSTextViewProtocol?{ get set } var interactor: QSTextInteractorProtocol{ get set } var router: QSTextWireframeProtocol{ get set } func viewDidLoad(bookDetail:BookDetail) func didClickContent() func didClickChangeSource() func didClickCategory(book:BookDetail,books:[String:Any]) func didClickBack() func didClickCache() func requestChapter(index:Int) func requestAllChapter(index:Int) } //MARK: Output - protocol QSTextInteractorOutputProtocol: class { func fetchAllChaptersSuccess(chapters:[NSDictionary]) func fetchAllChaptersFailed() func fetchChapterSuccess(chapter:Dictionary,index:Int) func fetchChapterFailed() func fetchAllResourceSuccess(resource:[ResourceModel]) func fetchAllResourceFailed() func showActivity() func showBook(book:QSBook) func downloadFinish(book:QSBook) func showProgress(dict:[String:Any]) } //MARK: Interactor - protocol QSTextInteractorProtocol: class { var output: QSTextInteractorOutputProtocol! { get set } func commonInit(model:BookDetail) func requestAllResource(bookDetail:BookDetail) func requestAllChapters(selectedIndex:Int) func requestChapter(atIndex chapterIndex:Int) func getChapter(chapterIndex:Int,pageIndex:Int) // func book(bookDetail:BookDetail?,chapters:[NSDictionary]?,resources:[ResourceModel]?)->QSBook//更换书籍来源则需要更新book.chapters信息,请求某一章节成功后也需要刷新chapters的content及其他信息 func setChapters(chapterParam:NSDictionary?,index:Int,chapters:[QSChapter])->[QSChapter] func cacheAllChapter() } //MARK: View - protocol QSTextViewProtocol: IndicatableView { var presenter: QSTextPresenterProtocol? { get set } func showBook(book:QSBook) func showResources(resources:[ResourceModel]) func showAllChapter(chapters:[NSDictionary]) func showChapter(chapter:Dictionary,index:Int) func showEmpty() func downloadFinish(book:QSBook) func showProgress(dict:[String:Any]) } ================================================ FILE: zhuishushenqi/TXTReader/Reader/QSTextReaderController.swift ================================================ // QSTextReaderController.swift // TXTReader // // Created by Nory Cao on 16/11/24. // Copyright © 2016年 QS. All rights reserved. // enum QSTextCurlPageStyle { case none //表示跳转到的章节,没有动画产生 case forwards //前 ,下一页 case backwards } import UIKit class QSTextReaderController: ZSReaderBaseViewController, IndicatableView { var isToolBarHidden:Bool = true var sideNum = 1 var curlPageStyle:QSTextCurlPageStyle = .forwards // 拟真阅读 var pageController:UIPageViewController? // 当前阅读视图控制器,处于动画中时,则表示要切换到的控制器,非动画中则表示当前的阅读控制器 var currentReaderVC:PageViewController! var window:UIWindow? var callback:QSTextCallBack? var style:ZSReaderAnimationStyle = .curlPage var record:QSRecord = QSRecord() var shouldReactPanGesture:Bool = true var pagePanGesture:UIGestureRecognizer! override func viewDidLoad() { super.viewDidLoad() // self.navigationController?.isNavigationBarHidden = true // 第一次进入没有record,初始化一个record setupRecord() // 初始化根控制器 createRootController() // 初始化请求 initial() // 变更书籍的更新信息 viewModel.book?.isUpdated = false if !viewModel.exsitLocal() { if let book = viewModel.book { BookManager.shared.modifyBookshelf(book: book) } } // 替换了delegate之后需要自己管理viewController,不建议替换 // for gesture in self.pageController?.gestureRecognizers ?? [] { // if gesture.isKind(of: UIPanGestureRecognizer.self) { // gesture.delegate = self // } // } for gesture in self.pageController?.gestureRecognizers ?? [] { if gesture.isKind(of: UIPanGestureRecognizer.self) { pagePanGesture = gesture } } } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) setNeedsStatusBarAppearanceUpdate() // 保存浏览记录 if let book = viewModel.book { BookManager.shared.addReadHistory(book: book) } } //MARK: - initial action private func createRootController(){ let pageStyle = QSReaderSetting.shared.pageStyle switch pageStyle { case .curlPage: guard let _ = pageController else { setupPageController() return } view.addSubview(pageController!.view) addChild(pageController!) pageController?.setViewControllers([currentReaderVC], direction: .forward, animated: true, completion: nil) if let model = viewModel.book?.record?.chapterModel { if let chapterIndex = viewModel.book?.record?.chapter { if chapterIndex < model.pages.count { currentReaderVC.page = model.pages[chapterIndex] } } } break case .none: break case .horMove: break default: break } } private func setupPageController(){ var transitionStyle:UIPageViewController.TransitionStyle = .pageCurl if style == .horMove { transitionStyle = .scroll } pageController = UIPageViewController(transitionStyle: transitionStyle, navigationOrientation: .horizontal, options: nil) pageController?.dataSource = self pageController?.delegate = self pageController?.isDoubleSided = true view.addSubview(pageController!.view) addChild(pageController!) pageController?.setViewControllers([initialPageViewController()], direction: .forward, animated: true, completion: nil) } func initialPageViewController()->PageViewController{ let pageVC = PageViewController() if let record = viewModel.book?.record { if let chapterModel = record.chapterModel { let pageIndex = record.page if pageIndex < chapterModel.pages.count { pageVC.page = chapterModel.pages[pageIndex] } } else if (viewModel.book?.book.localChapters.count)! > 0 { if let chapters = viewModel.book?.book.localChapters { let chapterIndex = record.chapter let pageIndex = record.page if chapterIndex < chapters.count { let chapterModel = chapters[chapterIndex] if pageIndex < chapterModel.pages.count { pageVC.page = chapterModel.pages[pageIndex] } } } } } currentReaderVC = pageVC return pageVC } func initial(){ //如果是本地书籍,则不清求 if viewModel.exsitLocal() { return } if ZSLogin.share.hasLogin() { // 获取所有已购章节的key,用于解密章节文字 viewModel.boughtInfo( token: ZSLogin.share.token) { (info) in } } viewModel.fetchAllResource { resources in self.viewModel.fetchAllChapters({ (chapters) in self.viewModel.fetchInitialChapter({ (page) in self.currentReaderVC.page = page }) }) } } func setupRecord(){ // 第一次进入没有record,初始化一个record if let book = viewModel.book { record.bookId = book._id if book.record == nil { book.record = record } } } } extension QSTextReaderController:UIPageViewControllerDataSource,UIPageViewControllerDelegate{ //MARK: - UIPageViewControllerDataSource,UIPageViewControllerDelegate public func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController?{ //UIPageViewController的 isDoubleSided 设置为true后,会调用两次代理方法,第一次为背面的viewcontroller,第二次为正面 print("before") sideNum -= 1 curlPageStyle = .backwards if let curPageViewController = viewController as? PageViewController { if let page = curPageViewController.page { // page 存在则根据page的值来获取下一章节 } else { // page 不存在说明是新的章节 } } func lastPage() ->UIViewController? { let existLast = viewModel.existLastPage() if existLast { let pageVC = PageViewController() self.showActivityView() viewModel.fetchLastPage { (page) in pageVC.page = page self.hideActivityView() } currentReaderVC = pageVC return pageVC } shouldReactPanGesture = false return nil } if style == .horMove { return lastPage() } else { if abs(sideNum)%2 == 0 { let backgroundVC = QSReaderBackgroundViewController() //这里获取的是当前页的背面,实际应该是获取上一页的背面 backgroundVC.setBackground(viewController: self.currentReaderVC!) return backgroundVC } else { return lastPage() } } } func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController?{ print("after") curlPageStyle = .forwards sideNum += 1 func nextPage() ->UIViewController? { let existNext = viewModel.existNextPage() if existNext { let pageVC = PageViewController() self.showActivityView() viewModel.fetchNextPage { (page) in pageVC.page = page self.hideActivityView() if pageVC.page?.isDecrypted == false && self.viewModel.sourceIndex == 0 { pageVC.showPayView() } else { pageVC.hidePayView() } } currentReaderVC = pageVC return pageVC } // 没有下一页 shouldReactPanGesture = false return nil } if style == .horMove { return nextPage() } else { if abs(sideNum)%2 == 0 { //背面 let backgroundVC = QSReaderBackgroundViewController() backgroundVC.setBackground(viewController: self.currentReaderVC!) return backgroundVC } else { return nextPage() } } } func pageViewController(_ pageViewController: UIPageViewController, willTransitionTo pendingViewControllers: [UIViewController]){ // 改变代理,在动画结束前不再响应滑动手势,防止翻页过快 pagePanGesture.delegate = self } func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool){ pagePanGesture.delegate = pageViewController as? UIGestureRecognizerDelegate if !completed { currentReaderVC = previousViewControllers.first as? PageViewController } else { // 更新阅读记录 if curlPageStyle == .forwards { viewModel.updateNextRecord { (page) in // 如果当前展示的不是record中的,需要刷新下 if self.currentReaderVC.page?.curPage != self.viewModel.book?.record?.page { self.currentReaderVC.page = page } } } else if curlPageStyle == .backwards { viewModel.updateLastRecord { (page) in if self.currentReaderVC.page?.curPage != self.viewModel.book?.record?.page { self.currentReaderVC.page = page } } } } QSLog("ZSReaderChapter:\(viewModel.book?.record?.chapter ?? 0) \npage:\(viewModel.book?.record?.page ?? 0)") } // reader style change func readBg(type: Reader) { self.currentReaderVC?.bgView.image = AppStyle.shared.reader.backgroundImage self.currentReaderVC.refreshView() } func fontChange(action:ToolBarFontChangeAction){ viewModel.fontChange(action: action) { (page) in self.currentReaderVC.page = page } } func cacheAll() { let root:RootViewController = SideViewController.shared.contentViewController as! RootViewController let type = root.reachability?.networkType //移动网络,进行提示 if type == .WWAN2G || type == .WWAN3G || type == .WWAN4G { //提示当前正在使用移动网络,是否继续 alert(with: "提示", message: "当前正在使用移动网络,是否继续", okTitle: "继续", cancelTitle: "取消", okAction: { (action) in // self.presenter?.didClickCache() }, cancelAction: { (action) in }) }else { // presenter?.didClickCache() } } func brightnessChange(value: CGFloat) { // 修改page的background与foreground } func toolbar(toolbar:ToolBar, clickMoreSetting:UIView) { let moreSettingVC = QSMoreSettingController() let nav = UINavigationController(rootViewController: moreSettingVC) present(nav, animated: true, completion: nil) } func changeSourceClicked() { let sourceVC = ChangeSourceViewController() sourceVC.viewModel = self.viewModel sourceVC.selectAction = { (index:Int,sources:[ResourceModel]?) in self.viewModel = sourceVC.viewModel self.viewModel.cachedChapter.removeAll() self.viewModel.changeSource(index: index) self.viewModel.fetchAllChapters({ (info) in self.viewModel.fetchCurrentPage({ (page) in self.currentReaderVC.page = page }) }) } let nav = UINavigationController(rootViewController: sourceVC) present(nav, animated: true) { } } @objc func updateStatusBarStyle(){ window = UIWindow(frame: CGRect(x: 0, y: UIScreen.main.bounds.size.height - 20, width: UIScreen.main.bounds.size.width, height: 20)) window?.windowLevel = UIWindow.Level.statusBar window?.makeKeyAndVisible() window?.backgroundColor = UIColor.red setNeedsStatusBarAppearanceUpdate() } //MARK: - CategoryDelegate func categoryDidSelectAtIndex(index:Int){ curlPageStyle = .none viewModel.book?.record?.chapter = index viewModel.book?.record?.page = 0 viewModel.book?.record?.chapterModel = nil if let link = viewModel.book?.chaptersInfo?[index].link { if let chapterModel = viewModel.cachedChapter[link] { viewModel.book?.record?.chapterModel = chapterModel } } else { if index < (viewModel.book?.book.localChapters.count ?? 0) { viewModel.book?.record?.chapterModel = viewModel.book?.book.localChapters[index] } } let pageVC = PageViewController() if let model = viewModel.book?.record?.chapterModel { pageVC.page = model.pages[0] } else { viewModel.fetchCurrentPage { (page) in pageVC.page = page } } let backgroundVC = QSReaderBackgroundViewController() backgroundVC.setBackground(viewController: pageVC) pageController?.setViewControllers([pageVC,backgroundVC], direction: .forward, animated: true) { (finished) in } currentReaderVC = pageVC } } extension QSTextReaderController:UIGestureRecognizerDelegate { func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { return false } } ================================================ FILE: zhuishushenqi/TXTReader/Reader/QSTextRouter.swift ================================================ // // QSTextRouter.swift // zhuishushenqi // // Created by Nory Cao on 2017/4/14. // Copyright © 2017年 QS. All rights reserved. // import Foundation import UIKit typealias QSTextCallBack = (_ book:BookDetail)->Void class QSTextRouter: QSTextWireframeProtocol { weak var viewController: UIViewController? static func createModule(bookDetail:BookDetail,callback:@escaping QSTextCallBack) -> UIViewController { // Change to get view from storyboard if not using progammatic UI let view = ZSReaderViewController() view.viewModel.book = bookDetail return view } func presentDetails(_ novel: QSRankModel) { // viewController?.navigationController?.pushViewController(QSRankDetailRouter.createModule(novel:novel), animated: true) } func presentCategory(book:BookDetail,books:[String:Any]){ let vc:QSCategoryReaderViewController = QSCategoryRouter.createModule(book: book) as! QSCategoryReaderViewController let txtVC:QSTextReaderController = viewController as! QSTextReaderController // vc.categoryDelegate = txtVC vc.bookDetail = book // vc.chapterDict = let nav = UINavigationController(rootViewController: vc) viewController?.present(nav, animated: true, completion: nil) } } ================================================ FILE: zhuishushenqi/TXTReader/Reader/Service/ZSFontService.swift ================================================ // // ZSFontService.swift // zhuishushenqi // // Created by caony on 2018/9/13. // Copyright © 2018年 QS. All rights reserved. // import Foundation class ZSFontService { func fetchFont(url:String,_ handler:ZSBaseCallback<[String:Any]>?) { zs_download(url: url) { (json) in handler?(json) } } } ================================================ FILE: zhuishushenqi/TXTReader/Reader/Service/ZSReaderManager.swift ================================================ // // ZSReaderManager.swift // zhuishushenqi // // Created by yung on 2018/7/9. // Copyright © 2018年 QS. All rights reserved. // import UIKit final class ZSReaderManager { var cachedChapter:[String:QSChapter] = [:] // 获取下一个页面 func getNextPage(record:QSRecord,chaptersInfo:[ZSChapterInfo]?,callback:ZSSearchWebAnyCallback?){ //2.获取当前阅读的章节与页码,这里由于是网络小说,有几种情况 //(1).当前章节信息为空,那么这一章只有一页,翻页直接进入了下一页,章节加一 //(2).当前章节信息不为空,说明已经请求成功了,那么计算当前页码是否为该章节的最后一页 //这是2(2) if let chapterModel = record.chapterModel { let page = record.page if page < chapterModel.pages.count - 1 { let pageIndex = page + 1 let pageModel = chapterModel.pages[pageIndex] callback?(pageModel) } else { // 新的章节 getNewChapter(chapterOffset: 1,record: record,chaptersInfo: chaptersInfo,callback: callback) } } else { getNewChapter(chapterOffset: 1,record: record,chaptersInfo: chaptersInfo,callback: callback) } } func getLastPage(record:QSRecord,chaptersInfo:[ZSChapterInfo]?,callback:ZSSearchWebAnyCallback?){ if let chapterModel = record.chapterModel { let page = record.page if page > 0 { // 当前页存在 let pageIndex = page - 1 let pageModel = chapterModel.pages[pageIndex] callback?(pageModel) } else {// 当前章节信息不存在,必然是新的章节 getNewChapter(chapterOffset: -1,record: record,chaptersInfo: chaptersInfo,callback: callback) } } else { getNewChapter(chapterOffset: -1,record: record,chaptersInfo: chaptersInfo,callback: callback) } } func getNewChapter(chapterOffset:Int,record:QSRecord,chaptersInfo:[ZSChapterInfo]?,callback:ZSSearchWebAnyCallback?){ let chapter = record.chapter + chapterOffset if chapter > 0 && chapter < (chaptersInfo?.count ?? 0) { if let chapterInfo = chaptersInfo?[chapter] { let link = chapterInfo.link // 内存缓存 if let model = cachedChapter[link] { callback?(model.pages.first) } else { callback?(nil) // viewModel.fetchChapter(key: link, { (body) in // if let bodyInfo = body { // if let network = self.cacheChapter(body: bodyInfo, index: chapter) { // callback?(network.pages.first) // } // } // }) } } } } } ================================================ FILE: zhuishushenqi/TXTReader/Reader/Service/ZSReaderWebService.swift ================================================ // // QSTextInteractor.swift // zhuishushenqi // // Created by Nory Cao on 2017/4/14. // Copyright © 2017年 QS. All rights reserved. // /* chapters: { chapterCover = ""; currency = 0; isVip = 0; link = "http://book.my716.com/getBooks.aspx?method=content&bookId=2201994&chapterFile=U_2201994_201806182043464927_0420_600.txt"; order = 0; partsize = 0; title = "\U7b2c\U4e94\U4e5d\U516b\U7ae0 \U8d8a\U5357\U5144\U5f1f\Uff08\U8865\U66f44\Uff09"; totalpage = 0; unreadble = 0; } */ import Foundation import HandyJSON import ZSAPI class ZSReaderWebService:ZSBaseService { //MARK: - 请求所有的来源,key为book的id func fetchAllResource(key:String,_ callback:ZSBaseCallback<[ResourceModel]>?){ let api = ZSAPI.allResource(key: key) zs_get(api.path, parameters: api.parameters) { (response) in if let resources = [ResourceModel].deserialize(from: response as? [Any]) as? [ResourceModel] { callback?(resources) }else{ callback?(nil) } } } //MARK: - 请求所有的章节,key为book的id func fetchAllChapters(key:String,_ callback:ZSBaseCallback<[ZSChapterInfo]>?){ let api = ZSAPI.allChapters(key: key) let url:String = api.path zs_get(url, parameters: api.parameters) { (response) in if let chapters = [ZSChapterInfo].deserialize(from: response?["chapters"] as? [Any]) as? [ZSChapterInfo]{ QSLog("Chapters:\(chapters)") callback?(chapters) }else{ callback?(nil) } } } //MARK: - 请求当前章节的信息,key为当前章节的link,追书正版则为id func fetchChapter(key:String,_ callback:ZSBaseCallback?){ var link:NSString = key as NSString link = link.urlEncode() as NSString let api = ZSAPI.chapter(key: link as String, type: .chapter) zs_get(api.path) { (response) in QSLog("JSON:\(String(describing: response))") if let body = ZSChapterBody.deserialize(from: response?["chapter"] as? NSDictionary) { callback?(body) } else { callback?(nil) } } } } ================================================ FILE: zhuishushenqi/TXTReader/Reader/TXTReaderViewController.swift ================================================ // // TXTReaderViewController.swift // TXTReader // // Created by Nory Chao on 16/11/24. // Copyright © 2016年 QS. All rights reserved. // import UIKit class TXTReaderViewController: UIViewController { var viewModel = ZSReaderViewModel() var isToolBarHidden:Bool = true var sideNum = 1 var curlPageStyle:QSTextCurlPageStyle = .forwards // 拟真阅读 var pageController:UIPageViewController? // 当前阅读视图控制器,处于动画中时,则表示要切换到的控制器,非动画中则表示当前的阅读控制器 var currentReaderVC:PageViewController! var window:UIWindow? var callback:QSTextCallBack? var style:ZSReaderAnimationStyle = .horMove var viewControllers:[PageViewController] = [] var record:QSRecord = QSRecord() override func viewDidLoad() { super.viewDidLoad() self.navigationController?.isNavigationBarHidden = true // 第一次进入没有record,初始化一个record setupRecord() // 初始化根控制器 createRootController() // 初始化请求 initial() // 变更书籍的更新信息 viewModel.book?.isUpdated = false if let book = viewModel.book { BookManager.shared.modifyBookshelf(book: book) } } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) setNeedsStatusBarAppearanceUpdate() // 保存浏览记录 if let book = viewModel.book { BookManager.shared.addReadHistory(book: book) } } //MARK: - initial action private func createRootController(){ let pageStyle = QSReaderSetting.shared.pageStyle switch pageStyle { case .horMove: guard let _ = pageController else { setupPageController() return } view.addSubview(pageController!.view) addChild(pageController!) pageController?.setViewControllers([currentReaderVC], direction: .forward, animated: true, completion: nil) if let model = viewModel.book?.record?.chapterModel { if let chapterIndex = viewModel.book?.record?.chapter { if chapterIndex < model.pages.count { currentReaderVC.page = model.pages[chapterIndex] } } } break case .none: break case .curlPage: break default: break } } private func setupPageController(){ var transitionStyle:UIPageViewController.TransitionStyle = .pageCurl if style == .horMove { transitionStyle = .scroll } pageController = UIPageViewController(transitionStyle: transitionStyle, navigationOrientation: .horizontal, options: nil) pageController?.dataSource = self pageController?.delegate = self pageController?.isDoubleSided = (style == .horMove ? false:true) view.addSubview(pageController!.view) addChild(pageController!) pageController?.setViewControllers([initialPageViewController()], direction: .forward, animated: true, completion: nil) } func initialPageViewController()->PageViewController{ let pageVC = PageViewController() if let record = viewModel.book?.record { if let chapterModel = record.chapterModel { let pageIndex = record.page if pageIndex < chapterModel.pages.count { pageVC.page = chapterModel.pages[pageIndex] } } else if (viewModel.book?.book.localChapters.count)! > 0 { if let chapters = viewModel.book?.book.localChapters { let chapterIndex = record.chapter let pageIndex = record.page if chapterIndex < chapters.count { let chapterModel = chapters[chapterIndex] if pageIndex < chapterModel.pages.count { pageVC.page = chapterModel.pages[pageIndex] } } } } else { viewModel.fetchInitialChapter { (page) in pageVC.page = page } } } currentReaderVC = pageVC return pageVC } func initial(){ //如果是本地书籍,则不清求 if viewModel.exsitLocal() { return } viewModel.fetchAllResource { resources in self.viewModel.fetchAllChapters({ (chapters) in self.viewModel.fetchInitialChapter({ (page) in self.currentReaderVC.page = page }) }) } } func setupRecord(){ // 第一次进入没有record,初始化一个record if let book = viewModel.book { record.bookId = book._id if book.record == nil { book.record = record } } } } extension TXTReaderViewController:UIPageViewControllerDataSource,UIPageViewControllerDelegate{ //MARK: - UIPageViewControllerDataSource,UIPageViewControllerDelegate public func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController?{ //UIPageViewController的 isDoubleSided 设置为true后,会调用两次代理方法,第一次为背面的viewcontroller,第二次为正面 print("before") sideNum -= 1 curlPageStyle = .backwards let curPageViewController = viewController as! PageViewController var pageVC:PageViewController? // if viewControllers.count > 1 { // if let reusePageVC = viewControllers.first { // pageVC = reusePageVC // viewControllers.remove(at: 0) // } // } else { pageVC = PageViewController() // } let existLast = viewModel.hm_existLast(page: curPageViewController.page) if existLast { currentReaderVC = pageVC viewModel.fetchBackwardPage(page: curPageViewController.page) { (page) in pageVC?.page = page } return pageVC } return nil } func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController?{ print("after") curlPageStyle = .forwards sideNum += 1 let curPageViewController = viewController as! PageViewController var pageVC:PageViewController? if viewControllers.count > 1 { if let reusePageVC = viewControllers.first { pageVC = reusePageVC viewControllers.remove(at: 0) } } else { pageVC = PageViewController() } let existNext = viewModel.hm_existNext(page: curPageViewController.page) if existNext { pageVC?.view.alpha = 1.0 viewModel.fetchForwardPage(page: curPageViewController.page) { (page) in pageVC?.page = page } currentReaderVC = pageVC return pageVC } return nil } func pageViewController(_ pageViewController: UIPageViewController, willTransitionTo pendingViewControllers: [UIViewController]){ // currentReaderVC.qs_removeObserver() } func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool){ let preVC = previousViewControllers.first as! PageViewController if !completed { currentReaderVC = previousViewControllers.first as! PageViewController } else { // 更新阅读记录 if curlPageStyle == .forwards { viewModel.updateForwardRecord(page: preVC.page) } else if curlPageStyle == .backwards { viewModel.updateBackwardRecord(page: preVC.page) } // pageVC的重复利用 if !viewControllers.contains(preVC) { viewControllers.append(preVC) } } QSLog("ZSReaderChapter:\(viewModel.book?.record?.chapter ?? 0) \npage:\(viewModel.book?.record?.page ?? 0)") // preVC.qs_removeObserver() } // reader style change func readBg(type: Reader) { self.currentReaderVC?.bgView.image = AppStyle.shared.reader.backgroundImage } func fontChange(action:ToolBarFontChangeAction){ viewModel.fontChange(action: action) { (page) in self.currentReaderVC.page = page } } func cacheAll() { let root:RootViewController = SideViewController.shared.contentViewController as! RootViewController let type = root.reachability?.networkType //移动网络,进行提示 if type == .WWAN2G || type == .WWAN3G || type == .WWAN4G { //提示当前正在使用移动网络,是否继续 alert(with: "提示", message: "当前正在使用移动网络,是否继续", okTitle: "继续", cancelTitle: "取消", okAction: { (action) in // self.presenter?.didClickCache() }, cancelAction: { (action) in }) }else { // presenter?.didClickCache() } } func brightnessChange(value: CGFloat) { // 修改page的background与foreground } func toolbar(toolbar:ToolBar, clickMoreSetting:UIView) { let moreSettingVC = QSMoreSettingController() let nav = UINavigationController(rootViewController: moreSettingVC) present(nav, animated: true, completion: nil) } func changeSourceClicked() { let sourceVC = ChangeSourceViewController() sourceVC.viewModel = self.viewModel sourceVC.selectAction = { (index:Int,sources:[ResourceModel]?) in self.viewModel = sourceVC.viewModel self.viewModel.cachedChapter.removeAll() self.viewModel.fetchAllChapters({ (info) in self.viewModel.fetchCurrentPage({ (page) in self.currentReaderVC.page = page }) }) } let nav = UINavigationController(rootViewController: sourceVC) present(nav, animated: true) { } } @objc func updateStatusBarStyle(){ window = UIWindow(frame: CGRect(x: 0, y: UIScreen.main.bounds.size.height - 20, width: UIScreen.main.bounds.size.width, height: 20)) window?.windowLevel = UIWindow.Level.statusBar window?.makeKeyAndVisible() window?.backgroundColor = UIColor.red setNeedsStatusBarAppearanceUpdate() } //MARK: - CategoryDelegate func categoryDidSelectAtIndex(index:Int){ curlPageStyle = .none viewModel.book?.record?.chapter = index viewModel.book?.record?.page = 0 viewModel.book?.record?.chapterModel = nil if let link = viewModel.book?.chaptersInfo?[index].link { if let chapterModel = viewModel.cachedChapter[link] { viewModel.book?.record?.chapterModel = chapterModel } } let pageVC = PageViewController() if let model = viewModel.book?.record?.chapterModel { pageVC.page = model.pages[0] } else { viewModel.fetchCurrentPage { (page) in pageVC.page = page } } let backgroundVC = QSReaderBackgroundViewController() backgroundVC.setBackground(viewController: pageVC) pageController?.setViewControllers([pageVC], direction: .forward, animated: true) { (finished) in } currentReaderVC = pageVC } } ================================================ FILE: zhuishushenqi/TXTReader/Reader/View/ZSChapterPayView.swift ================================================ // // ZSChapterPayView.swift // zhuishushenqi // // Created by caony on 2019/2/15. // Copyright © 2019年 QS. All rights reserved. // import Foundation class ZSChapterPayView: UIView { var titleLabel:UILabel! var tipLabel:UILabel! var payButton:UIButton! var leftLineView:UIView! var rightLineView:UIView! override init(frame: CGRect) { super.init(frame: frame) setupSubviews() } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } private func setupSubviews() { titleLabel = UILabel(frame: CGRect.zero) titleLabel.textAlignment = .center titleLabel.font = UIFont.systemFont(ofSize: 17) titleLabel.textColor = UIColor.gray addSubview(titleLabel) tipLabel = UILabel(frame: CGRect.zero) tipLabel.textAlignment = .center tipLabel.font = UIFont.systemFont(ofSize: 13) tipLabel.text = "* 本章需购买后阅读 *" tipLabel.textColor = UIColor.gray addSubview(tipLabel) payButton = UIButton(type: .custom) payButton.setTitle("购买章节", for: .normal) payButton.setTitleColor(UIColor.gray, for: .normal) payButton.layer.cornerRadius = 5 payButton.layer.borderWidth = 1 payButton.layer.borderColor = UIColor.gray.cgColor addSubview(payButton) leftLineView = UIView(frame: CGRect.zero) leftLineView.backgroundColor = UIColor.gray addSubview(leftLineView) rightLineView = UIView(frame: CGRect.zero) rightLineView.backgroundColor = UIColor.gray addSubview(rightLineView) } override func layoutSubviews() { super.layoutSubviews() titleLabel.frame = CGRect(x: 20, y: 200, width: bounds.width - 40, height: 60) tipLabel.frame = CGRect(x: 0, y: 0, width: 130, height: 15) tipLabel.center = CGPoint(x: bounds.width/2, y: bounds.height/2) leftLineView.frame = CGRect(x: 0, y: 0, width: bounds.width/2 - tipLabel.bounds.width/2, height: 1) leftLineView.center = CGPoint(x: bounds.width/4 - tipLabel.bounds.width/4, y: tipLabel.centerY) rightLineView.frame = CGRect(x: bounds.width/2 + tipLabel.bounds.width/2, y: tipLabel.centerY, width: bounds.width/2 - tipLabel.bounds.width/2, height: 1) // rightLineView.center = CGPoint(x:bounds.width + tipLabel.bounds.width + bounds.width/4 - tipLabel.bounds.width/4, y: tipLabel.centerY) payButton.frame = CGRect(x: 0, y: 0, width: 200, height: 40) payButton.center = CGPoint(x: bounds.width/2, y: tipLabel.frame.maxY + 60) } } ================================================ FILE: zhuishushenqi/TXTReader/Reader/View/ZSChapterSelectView.swift ================================================ // // ZSChapterSelectView.swift // zhuishushenqi // // Created by caony on 2018/11/9. // Copyright © 2018 QS. All rights reserved. // import UIKit import YYText import YYCategories class ZSChapterSelectView: UICollectionView { var items:[ZSChapterSelectItemModel] = [] override init(frame: CGRect, collectionViewLayout layout: UICollectionViewLayout) { let flowLayout = UICollectionViewFlowLayout() flowLayout.minimumLineSpacing = 10 flowLayout.minimumInteritemSpacing = 10 flowLayout.itemSize = CGSize(width: UIScreen.main.bounds.width/2 - 10*3/2, height: 40) super.init(frame: frame, collectionViewLayout: flowLayout) self.qs_registerCellClass(ZSChapterSelectItem.self) } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override var numberOfSections: Int { return 1 } override func numberOfItems(inSection section: Int) -> Int { return items.count } override func cellForItem(at indexPath: IndexPath) -> UICollectionViewCell? { let cell = self.qs_dequeueReusableCell(ZSChapterSelectItem.self, for: indexPath) if indexPath.item == 0 { cell.setTitle(title: "本章") } else if indexPath.item == items.count - 1 { cell.setTitle(title: "后xx章") cell.setDiscount(discount: "\(0.75 * 10)") } else { cell.setTitle(title: "后\(items[indexPath.item].num)章") cell.setDiscount(discount: "\(items[indexPath.item].discount * 10.0)") } return cell } } class ZSChapterSelectItem: UICollectionViewCell { private var titleLabel:YYLabel! var selectedItem:Bool = false override init(frame: CGRect) { super.init(frame: frame) setupSubviews() } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } private func setupSubviews() { titleLabel = YYLabel(frame: CGRect(x: 0, y: 0, width: self.bounds.width, height: self.bounds.height)) contentView.addSubview(titleLabel) } override func prepareForReuse() { titleLabel.attributedText = nil titleLabel.text = nil } func setTitle(title:String?) { if (titleLabel.attributedText ?? NSAttributedString(string: "")).string == title { let titleText = title ?? "" let attr = NSMutableAttributedString(string: titleText) attr.yy_font = UIFont.systemFont(ofSize: 13) attr.yy_alignment = .center titleLabel.attributedText = attr } else { // 可能已经设置了discount let discount = titleLabel.attributedText?.string ?? "" setDiscount(discount: discount) } } func setDiscount(discount:String) { let title = titleLabel.attributedText ?? NSMutableAttributedString(string: "") let attr = NSMutableAttributedString(string: "\(title)\(discount)折") attr.yy_font = UIFont.systemFont(ofSize: 13) attr.yy_alignment = .center attr.yy_setTextHighlight(NSMakeRange(0, title.length), color: UIColor.gray, backgroundColor: UIColor.clear, userInfo: nil) attr.yy_setTextHighlight(NSMakeRange(title.length, (discount as NSString).length), color: UIColor.white, backgroundColor: UIColor.orange, userInfo: nil) titleLabel.attributedText = attr } } ================================================ FILE: zhuishushenqi/TXTReader/Reader/View/ZSFontViewController.swift ================================================ // // ZSFontViewController.swift // zhuishushenqi // // Created by caony on 2018/9/13. // Copyright © 2018年 QS. All rights reserved. // import UIKit typealias ZSClickHandler = ()->Void class ZSFontViewController: ZSBaseTableViewController { var viewModel = ZSFontViewModel() override func viewDidLoad() { super.viewDidLoad() viewModel.copyFont() } override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { return 0.01 } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return viewModel.fonts.count } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.qs_dequeueReusableCell(ZSFontViewCell.self) let image = viewModel.fonts[indexPath.row]["image"] ?? "" if image == "" { cell?.textLabel?.text = viewModel.fonts[indexPath.row]["name"] } else { cell?.imageView?.image = UIImage(named: image) } let exist = viewModel.fileExist(indexPath: indexPath) if exist { cell?.setDownloadType(type: .finished) } else { cell?.setDownloadType(type: .notdown) } if indexPath == viewModel.selectedIndexPath { cell?.setDownloadType(type: .inuse) } cell?.downloadHandler = { if cell?.type == .notdown { self.viewModel.fetchFont(indexPath: indexPath, handler: { (finished) in if finished! { cell?.setDownloadType(type: .finished) } }) } else if cell?.type == .finished { self.viewModel.selectedIndexPath = indexPath cell?.setDownloadType(type: .inuse) self.tableView.reloadData() } } return cell! } override func registerCellClasses() -> Array { return [ZSFontViewCell.self] } } class ZSFontViewCell: UITableViewCell { enum ZSDownloadType { case notdown case finished case inuse } var download:UIButton = UIButton(type: .custom) var downloadHandler:ZSClickHandler? var type:ZSDownloadType = .notdown override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) self.setDownloadType(type: .notdown) download.titleLabel?.font = UIFont.systemFont(ofSize: 11) download.setTitleColor(UIColor.red, for: .normal) download.layer.borderColor = UIColor.red.cgColor download.layer.borderWidth = 0.5 download.frame = CGRect(x: 0, y: 0, width: 60, height: 30) download.addTarget(self, action: #selector(downloadAction(sender:)), for: .touchUpInside) self.accessoryView = download self.separatorInset = UIEdgeInsets(top: 0, left: 15, bottom: 0, right: 0) self.selectionStyle = .none } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } @objc private func downloadAction(sender:UIButton) { downloadHandler?() } func setDownloadType(type:ZSDownloadType) { self.type = type if type == .finished { self.download.setTitle("启用", for: .normal) self.download.setTitleColor(UIColor.white, for: .normal) self.download.backgroundColor = UIColor.red self.download.isEnabled = true self.download.layer.borderColor = UIColor.red.cgColor } else if type == .inuse { self.download.setTitle("使用中", for: .normal) self.download.isEnabled = false self.download.setTitleColor(UIColor.black, for: .normal) self.download.backgroundColor = UIColor.white self.download.layer.borderColor = UIColor.clear.cgColor } else if type == .notdown { self.download.setTitle("下载", for: .normal) self.download.isEnabled = true self.download.setTitleColor(UIColor.red, for: .normal) self.download.backgroundColor = UIColor.white self.download.layer.borderColor = UIColor.red.cgColor } } override func prepareForReuse() { self.setDownloadType(type: .notdown) } } ================================================ FILE: zhuishushenqi/TXTReader/Reader/View/ZSHorizonalMoveCell.swift ================================================ // // ZSHorizonalMoveCell.swift // zhuishushenqi // // Created by yung on 2018/7/9. // Copyright © 2018年 QS. All rights reserved. // import UIKit class ZSHorizonalMoveCell: UICollectionViewCell { fileprivate var _pageViewController = PageViewController() var pageViewController:PageViewController { get { return _pageViewController } } var page:QSPage? { didSet { pageViewController.page = page } } var newpage:ZSBookPage? { didSet { pageViewController.newPage = newpage } } override init(frame: CGRect) { super.init(frame: frame) contentView.addSubview(pageViewController.view) contentView.backgroundColor = UIColor.white } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func prepareForReuse() { super.prepareForReuse() pageViewController.page = nil pageViewController.newPage = nil } } ================================================ FILE: zhuishushenqi/TXTReader/Reader/View/ZSHorizonalMoveViewController.swift ================================================ // // ZSHorizonalMoveViewController.swift // zhuishushenqi // // Created by yung on 2018/7/9. // Copyright © 2018年 QS. All rights reserved. // import UIKit enum ZSHorizonalMovePage { case none case next case last } class ZSHorizonalMoveViewController: BaseViewController { var viewModel:ZSReaderViewModel = ZSReaderViewModel() var pageViewController:PageViewController = PageViewController() var record:QSRecord = QSRecord() var contentOffsetX:CGFloat = -1.0; var pageType:ZSHorizonalMovePage = .none var pageController:UIPageViewController? var curlPageStyle:QSTextCurlPageStyle = .forwards fileprivate lazy var collectionView:UICollectionView = { let layout = UICollectionViewFlowLayout() layout.scrollDirection = .horizontal layout.itemSize = CGSize(width: ScreenWidth, height: ScreenHeight) layout.minimumLineSpacing = 0.0 layout.minimumInteritemSpacing = 0 let collection = UICollectionView(frame: CGRect.zero, collectionViewLayout: layout) collection.qs_registerCellClass(ZSHorizonalMoveCell.self) collection.dataSource = self collection.delegate = self collection.isPagingEnabled = true return collection }() override func viewDidLoad() { super.viewDidLoad() initial() setupRecord() // view.addSubview(collectionView) // Do any additional setup after loading the view. } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) // collectionView.frame = view.bounds } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } func initial(){ viewModel.fetchAllResource { resources in self.viewModel.fetchAllChapters({ (chapters) in if let chapter = self.viewModel.book?.record?.chapterModel,let record = self.viewModel.book?.record { self.viewModel.cachedChapter[chapter.link] = chapter // self.collectionView.reloadData() // let indexPath = IndexPath(item: record.page, section: record.chapter) // self.collectionView.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: false) } else { self.viewModel.fetchInitialChapter({ (page) in if let record = self.viewModel.book?.record { // self.collectionView.reloadData() // let indexPath = IndexPath(item: record.page, section: record.chapter) // self.collectionView.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: false) } }) } }) } } private func setupPageController(){ pageController = UIPageViewController(transitionStyle: .pageCurl, navigationOrientation: .horizontal, options: nil) pageController?.dataSource = self pageController?.delegate = self pageController?.isDoubleSided = true view.addSubview(pageController!.view) addChild(pageController!) // pageController?.setViewControllers([initialPageViewController()], direction: .forward, animated: true, completion: nil) } func setupRecord(){ // 第一次进入没有record,初始化一个record if let book = viewModel.book { record.bookId = book._id if book.record == nil { book.record = record } } } } extension ZSHorizonalMoveViewController:UIPageViewControllerDataSource,UIPageViewControllerDelegate { func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? { return nil } func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? { return nil } func pageViewController(_ pageViewController: UIPageViewController, willTransitionTo pendingViewControllers: [UIViewController]){ } func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool){ if !completed { } else { // 更新阅读记录 if curlPageStyle == .forwards { } else if curlPageStyle == .backwards { } } //如果翻页完成,则completed=true,否则为false // isAnimatedFinished = completed//请求数据时根据此字段更新当前页 } } extension ZSHorizonalMoveViewController:UICollectionViewDataSource,UICollectionViewDelegate { public func numberOfSections(in collectionView: UICollectionView) -> Int { return viewModel.book?.chaptersInfo?.count ?? 1 } public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { if let key = viewModel.book?.chaptersInfo?[section].link { if let model = viewModel.cachedChapter[key] { return model.pages.count } } return 1 } // The cell that is returned must be retrieved from a call to -dequeueReusableCellWithReuseIdentifier:forIndexPath: public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.qs_dequeueReusableCell(ZSHorizonalMoveCell.self, for: indexPath) // 一次滑动切换cell可能导致几个cell同时请求,所以这个方法中不要改变record的值 viewModel.fetchNextPage(indexPath: indexPath, callback: { (page) in cell.pageViewController.page = page }) { (page) in cell.pageViewController.page = page self.collectionView.reloadSections([indexPath.section]) } // if pageType == .next { // record = viewModel.book?.record ?? record // pageType = .none // viewModel.fetchNextPage(indexPath: indexPath) { (page) in // cell.pageViewController.page = page // } // } // else if pageType == .last { // pageType = .none // viewModel.fetchLastPage(indexPath: indexPath) { (page) in // cell.pageViewController.page = page // } // } // else if pageType == .none { // // 一次滑动切换cell可能导致几个cell同时请求,这个时候第一次的请求可以改变record,后面的请求则不能改变record // if indexPath.section > record.chapter || indexPath.item > record.page { // viewModel.fetchNextPage(indexPath: indexPath) { (page) in // cell.pageViewController.page = page // } // return cell // } else if (indexPath.section < record.chapter || indexPath.item < record.page) { // viewModel.fetchLastPage(indexPath: indexPath) { (page) in // cell.pageViewController.page = page // } // return cell // } // pageViewController = cell.pageViewController // if let record = self.viewModel.book?.record { // if let chapterModel = record.chapterModel { // if record.page < chapterModel.pages.count { // pageViewController.page = chapterModel.pages[record.page] // } // } // } // } return cell } func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { // } } extension ZSHorizonalMoveViewController:UIScrollViewDelegate { func scrollViewDidScroll(_ scrollView: UIScrollView) { // let offsetX = scrollView.contentOffset.x } func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { let velocity = scrollView.panGestureRecognizer.velocity(in: scrollView) if velocity.x > 0 { // 上一页 pageType = .last } else { pageType = .next } } func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { let indexPath = collectionView.indexPathForItem(at: CGPoint(x: scrollView.contentOffset.x + 1, y: scrollView.contentOffset.y + 1) ) ?? IndexPath(item: 0, section: 0) let cell:ZSHorizonalMoveCell = collectionView.cellForItem(at: indexPath) as! ZSHorizonalMoveCell pageViewController = cell.pageViewController let page = pageViewController.page let pageIndex = page?.curPage let chapterIndex = page?.curChapter record.page = pageIndex ?? indexPath.item record.chapter = chapterIndex ?? indexPath.section if let key = viewModel.book?.chaptersInfo?[indexPath.section].link { if let model = viewModel.cachedChapter[key] { record.chapterModel = model } } viewModel.book?.record = record } } extension ZSHorizonalMoveViewController:ZSReaderControllerProtocol { typealias Item = Book } ================================================ FILE: zhuishushenqi/TXTReader/Reader/View/ZSMultiplePayView.swift ================================================ // // ZSMultiplePayView.swift // zhuishushenqi // // Created by caony on 2018/11/9. // Copyright © 2018 QS. All rights reserved. // import UIKit class ZSMultiplePayView: UIView { var maskingView:UIView! var backgroundView:UIView! var collapseBtn:UIButton! var startChapterTipTitleLabel:UILabel! var purchaseInfoBtn:UIButton! var chapterSelectionView:ZSChapterSelectView! override init(frame: CGRect) { super.init(frame: frame) } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } private func setupSubviews() { isUserInteractionEnabled = true maskingView = UIView(frame: self.bounds) maskingView.backgroundColor = UIColor(white: 0.2, alpha: 1.0) maskingView.isUserInteractionEnabled = true addSubview(maskingView) backgroundView = UIView(frame: CGRect(x: 0, y: 100, width: self.bounds.width, height: self.bounds.height - 100)) backgroundView.backgroundColor = UIColor.white backgroundView.isUserInteractionEnabled = true addSubview(backgroundView) collapseBtn = UIButton(type: .custom) collapseBtn.setTitle("折叠", for: .normal) collapseBtn.setTitleColor(UIColor.gray, for: .normal) collapseBtn.titleLabel?.font = UIFont.systemFont(ofSize: 12) collapseBtn.frame = CGRect(x: 20, y: 15, width: 60, height: 30) addSubview(collapseBtn) startChapterTipTitleLabel = UILabel(frame: CGRect(x: 0, y: 15, width: self.bounds.width, height: 30)) startChapterTipTitleLabel.textAlignment = .center startChapterTipTitleLabel.font = UIFont.systemFont(ofSize: 17) startChapterTipTitleLabel.textColor = UIColor.black addSubview(startChapterTipTitleLabel) purchaseInfoBtn = UIButton(type: .custom) purchaseInfoBtn.setTitle("购买说明", for: .normal) purchaseInfoBtn.setTitleColor(UIColor.gray, for: .normal) purchaseInfoBtn.titleLabel?.font = UIFont.systemFont(ofSize: 12) purchaseInfoBtn.frame = CGRect(x: 20, y: 15, width: 60, height: 30) addSubview(purchaseInfoBtn) chapterSelectionView = ZSChapterSelectView(frame: CGRect(x: 0, y: 60, width: self.bounds.width, height: 180), collectionViewLayout: UICollectionViewLayout()) addSubview(chapterSelectionView) } } ================================================ FILE: zhuishushenqi/TXTReader/Reader/View/ZSNoneAnimationViewController.swift ================================================ // // ZSNoneAnimationViewController.swift // zhuishushenqi // // Created by caony on 2018/7/3. // Copyright © 2018年 QS. All rights reserved. // 无动画阅读控制器,管理手势,左滑手势显示下一页,右滑手势显示上一页,或者点击左侧右侧屏幕显示下一页,点击左侧屏幕显示上一页 import UIKit class ZSNoneAnimationViewController: BaseViewController { var viewModel:ZSReaderViewModel = ZSReaderViewModel() var pageViewController:PageViewController = PageViewController() var record:QSRecord = QSRecord() var cachedChapter:[String:QSChapter] = [:] fileprivate var changedPage = false override func viewDidLoad() { super.viewDidLoad() setupRecord() initial() setupSubviews() setupGesture() } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) } func initial(){ if let record = viewModel.book?.record { if let chapterModel = record.chapterModel { let pageIndex = record.page if pageIndex < chapterModel.pages.count { pageViewController.page = chapterModel.pages[pageIndex] } } else if (viewModel.book?.book.localChapters.count)! > 0 { if let chapters = viewModel.book?.book.localChapters { let chapterIndex = record.chapter let pageIndex = record.page if chapterIndex < chapters.count { let chapterModel = chapters[chapterIndex] if pageIndex < chapterModel.pages.count { pageViewController.page = chapterModel.pages[pageIndex] } } } } } self.viewModel.fetchAllResource { resources in self.viewModel.fetchAllChapters({ (chapters) in self.viewModel.fetchInitialChapter({ (page) in self.pageViewController.page = page }) }) } } func setupRecord(){ // 第一次进入没有record,初始化一个record if let book = viewModel.book { record.bookId = book._id if book.record == nil { book.record = record } } } func setupSubviews(){ view.addSubview(pageViewController.view) } func setupGesture(){ let pan = UIPanGestureRecognizer(target: self, action: #selector(panAction)) view.addGestureRecognizer(pan) } @objc func panAction(pan:UIPanGestureRecognizer){ // x方向滑动超过20就翻页 let translation:CGPoint = pan.translation(in: self.view) if pan.state == .changed && !changedPage { let offsetX = translation.x if offsetX < -20 { // 在本次手势结束前都不再响应 changedPage = true getNextPage() } else if (offsetX > 20) { // 在本次手势结束前都不再响应 changedPage = true getLastPage() } } else if pan.state == .ended { changedPage = false } } // 获取下一个页面 func getNextPage(){ self.viewModel.fetchNextPage { (page) in // 更新record self.pageViewController.page = page self.viewModel.updateNextRecord(callback: { (page) in if self.pageViewController.page?.curPage != self.viewModel.book?.record?.page { self.pageViewController.page = page } }) } } func getLastPage(){ viewModel.fetchLastPage { (page) in self.pageViewController.page = page self.viewModel.updateLastRecord(callback: { (page) in if self.pageViewController.page?.curPage != self.viewModel.book?.record?.page { self.pageViewController.page = page } }) } } func changeSourceClicked() { let sourceVC = ChangeSourceViewController() sourceVC.viewModel = self.viewModel sourceVC.selectAction = { (index:Int,sources:[ResourceModel]?) in self.viewModel = sourceVC.viewModel self.viewModel.cachedChapter.removeAll() self.viewModel.fetchAllChapters({ (info) in self.viewModel.fetchCurrentPage({ (page) in self.pageViewController.page = page }) }) } let nav = UINavigationController(rootViewController: sourceVC) present(nav, animated: true) { } } } extension ZSNoneAnimationViewController:ZSReaderTap{ func showLastPage(page:QSPage) { pageViewController.page = page } func showNextPage(page:QSPage) { pageViewController.page = page } } extension ZSNoneAnimationViewController:ZSReaderControllerProtocol { typealias Item = Book } ================================================ FILE: zhuishushenqi/TXTReader/Reader/View/ZSReaderBaseViewController.swift ================================================ // // ZSReaderBaseViewController.swift // zhuishushenqi // // Created by caony on 2018/10/11. // Copyright © 2018 QS. All rights reserved. // import UIKit class ZSReaderBaseViewController: UIViewController, ZSReaderControllerProtocol { var viewModel = ZSReaderViewModel() var pageViewController = PageViewController() typealias Item = Book override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. } } ================================================ FILE: zhuishushenqi/TXTReader/Reader/View/ZSReaderViewController.swift ================================================ // // ZSReaderViewController.swift // zhuishushenqi // // Created by caony on 2018/7/3. // Copyright © 2018年 QS. All rights reserved. // import UIKit import RxSwift protocol ZSReaderControllerProtocol { associatedtype Item var viewModel:ZSReaderViewModel { get set } var pageViewController:PageViewController { get set } } extension ZSReaderControllerProtocol where Self:UIViewController { } let changeAnimationStyle = "changeAnimationStyle" class ZSReaderViewController: BaseViewController { var animationStyle:ZSReaderAnimationStyle = QSReaderSetting.shared.pageStyle var noneAnimationViewController = ZSNoneAnimationViewController() var horMoveViewController = ZSHorizonalMoveViewController() var curlPageViewController = QSTextReaderController() var horMoveController = TXTReaderViewController() var viewModel:ZSReaderViewModel = ZSReaderViewModel() let dispose = DisposeBag() var isToolBarHiden = true var callback:QSTextCallBack? var book:BookDetail? { didSet{ viewModel.book = book } } var voiceBook:VoiceBook = VoiceBook() lazy var toolBar:ToolBar = { let toolBar:ToolBar = ToolBar(frame: CGRect(x: 0, y: 0, width: self.view.bounds.size.width, height: self.view.bounds.size.height)) toolBar.isHidden = true toolBar.toolBarDelegate = self return toolBar; }() lazy var speechView:ZSSpeechView = { let speechView = ZSSpeechView(frame: CGRect(x: 0, y: 0, width: self.view.bounds.size.width, height: self.view.bounds.size.height)) return speechView }() override func viewDidLoad() { super.viewDidLoad() toolBar.title = viewModel.book?.title ?? "" if animationStyle == .none { setupNoneAnimationController() } else if animationStyle == .horMove { setupHorMoveAnimationController() } else if animationStyle == .curlPage { setupCurlPageViewController() } NotificationCenter.default.rx.notification(Notification.Name(rawValue: changeAnimationStyle)).subscribe { (noti) in // let style = noti.element?.userInfo }.disposed(by: dispose) NotificationCenter.zs_addObserver(oberver: self, name: PageViewDidTap) { self.toolBar.isHidden = false self.toolBar.showWithAnimations(animation: true, inView: self.view) } NotificationCenter.qs_addObserver(observer: self, selector: #selector(changePageStyle), name: ZSReaderAnimationStyleChangeNotification, object: nil) NotificationCenter.default.post(Notification(name: Notification.Name(rawValue:RootDisappearNotificationName))) speechView.startHandler = { selected in if selected! { if self.voiceBook.isSpeaking() { self.voiceBook.stop() } let speaker = self.speechView.speaker if speaker.engineType == .local { // let appid = "5ba0b197" // let xfyj = "5445f87d" // // let xfyj2 = "591a4d99" // let initString = "appid=\(xfyj)" // IFlySpeechUtility.createUtility(initString) let speakerPath = "\(filePath)\(speaker.name).jet" self.voiceBook.config.speakerPath = speakerPath self.voiceBook.config.voiceID = "\(speaker.speakerId)" self.voiceBook.engineLocal() if let vc = self.curViewController() { if let readerVC = vc as? QSTextReaderController { self.voiceBook.start(sentence: readerVC.currentReaderVC.page?.content ?? "") } } } else { let appid = "5ba0b197" // let xfyj = "5445f87d" // let xfyj2 = "591a4d99" let initString = "appid=\(appid)" IFlySpeechUtility.createUtility(initString) self.voiceBook.engineCloud() self.voiceBook.start(sentence: "") // if let vc = self.curViewController() { // if let readerVC = vc as? QSTextReaderController { // } // } } } else { self.voiceBook.pause() } } speechView.stopHandler = { _ in self.voiceBook.stop() self.speechView.removeFromSuperview() } voiceBook.completeHandler = { error in QSLog(error) if error?.errorCode == 0 { // 读完了,自动翻下一页 if let vc = self.curViewController() as? QSTextReaderController { vc.viewModel.fetchNextPage { (page) in vc.currentReaderVC.page = page // 这里需要更新阅读记录,否则退出自动阅读后,阅读进度还在开始阅读前 vc.viewModel.updateNextRecord { (page) in } self.voiceBook.start(sentence: vc.currentReaderVC.page?.content ?? "") } } } } } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) // 全局设置 if let book = viewModel.book { ZSBookManager.shared.addHistory(book: book) } layoutSubview() } override func viewWillLayoutSubviews() { layoutSubview() } private func layoutSubview() { if let _ = toolBar.superview { toolBar.snp.remakeConstraints { (make) in make.edges.equalToSuperview() } } if let _ = speechView.superview { speechView.snp.remakeConstraints { (make) in make.edges.equalToSuperview() } } } @objc func changePageStyle() { if animationStyle == .none { viewModel = noneAnimationViewController.viewModel } else if animationStyle == .horMove { viewModel = horMoveController.viewModel } else if animationStyle == .curlPage { viewModel = curlPageViewController.viewModel } animationStyle = QSReaderSetting.shared.pageStyle if animationStyle == .none { removeHorMoveAnimationView() removeCurlPageViewController() setupNoneAnimationController() } else if animationStyle == .horMove { removeNoneAnimationView() removeCurlPageViewController() setupHorMoveAnimationController() } else if animationStyle == .curlPage { removeNoneAnimationView() removeHorMoveAnimationView() setupCurlPageViewController() } } func setupNoneAnimationController(){ noneAnimationViewController.viewModel = viewModel view.addSubview(noneAnimationViewController.view) addChild(noneAnimationViewController) } func setupHorMoveAnimationController(){ horMoveController.viewModel = viewModel view.addSubview(horMoveController.view) addChild(horMoveController) } func setupCurlPageViewController(){ curlPageViewController.viewModel = viewModel view.addSubview(curlPageViewController.view) addChild(curlPageViewController) } func removeNoneAnimationView(){ if let _ = noneAnimationViewController.view.superview { noneAnimationViewController.view.removeFromSuperview() } } func removeHorMoveAnimationView(){ if let _ = horMoveViewController.view.superview { horMoveViewController.view.removeFromSuperview() } } func removeCurlPageViewController() { if let _ = curlPageViewController.view.superview { curlPageViewController.view.removeFromSuperview() } } func curBook() ->BookDetail? { return curViewModel().book } func curViewController() -> ZSReaderBaseViewController? { if QSReaderSetting.shared.pageStyle == .curlPage { return curlPageViewController } return nil } func curViewModel() ->ZSReaderViewModel { var viewModel:ZSReaderViewModel! if QSReaderSetting.shared.pageStyle == .none { viewModel = noneAnimationViewController.viewModel } else if QSReaderSetting.shared.pageStyle == .horMove{ viewModel = horMoveController.viewModel } else if QSReaderSetting.shared.pageStyle == .curlPage { viewModel = curlPageViewController.viewModel } return viewModel } //MARK: - statusBar override var prefersStatusBarHidden: Bool { return isToolBarHiden } override var preferredStatusBarStyle: UIStatusBarStyle { return .lightContent } override var preferredStatusBarUpdateAnimation: UIStatusBarAnimation{ return .slide } } extension ZSReaderViewController:ToolBarDelegate ,QSCategoryDelegate{ func listen() { toolBar.hideWithAnimations(animation: false) speechView.show() } func backButtonDidClicked() { if let book = curBook() { // 退出时,通常保存阅读记录就行,其它的不需要保存 let exist = ZSBookManager.shared.existBookId(bookId: book._id) if !exist { self.alert(with: "追书提示", message: "是否将本书加入我的收藏", okTitle: "好的", cancelTitle: "不了", okAction: { (action) in ZSBookManager.shared.addBook(book: book) self.dismiss(animated: true, completion: nil) }, cancelAction: { (action) in self.dismiss(animated: true, completion: nil) }) } else{ ZSBookManager.shared.updateBook(book: book) self.dismiss(animated: true, completion: nil) } if let back = callback { back(book) } } else { self.dismiss(animated: true, completion: nil) } } func catagoryClicked() { self.toolBar.hideWithAnimations(animation: true) let book = curBook() ?? BookDetail() let vc:QSCategoryReaderViewController = QSCategoryRouter.createModule(book: book) as! QSCategoryReaderViewController vc.categoryDelegate = self vc.viewModel = curViewModel() // vc.bookDetail = book vc.chapterDict = curViewModel().cachedChapter let nav = UINavigationController(rootViewController: vc) present(nav, animated: true, completion: nil) } func changeSourceClicked() { toolBar.hideWithAnimations(animation: false) if QSReaderSetting.shared.pageStyle == .curlPage { curlPageViewController.changeSourceClicked() } else if QSReaderSetting.shared.pageStyle == .horMove { horMoveController.changeSourceClicked() } else if QSReaderSetting.shared.pageStyle == .none { noneAnimationViewController.changeSourceClicked() } } func toolBarDidShow() { self.isToolBarHiden = false UIView.animate(withDuration: 0.35, animations: { self.setNeedsStatusBarAppearanceUpdate() }) } func toolBarDidHidden() { self.isToolBarHiden = true UIView.animate(withDuration: 0.35, animations: { self.setNeedsStatusBarAppearanceUpdate() }) } func readBg(type:Reader) { AppStyle.shared.reader = type let image = AppStyle.shared.reader.backgroundImage if QSReaderSetting.shared.pageStyle == .none { self.noneAnimationViewController.pageViewController.bgView.image = image } else if QSReaderSetting.shared.pageStyle == .horMove { self.horMoveController.readBg(type: type) } else if QSReaderSetting.shared.pageStyle == .curlPage { self.curlPageViewController.readBg(type: type) } } func fontChange(action:ToolBarFontChangeAction) { if QSReaderSetting.shared.pageStyle == .curlPage { self.curlPageViewController.fontChange(action: action) } else if QSReaderSetting.shared.pageStyle == .horMove { self.horMoveController.fontChange(action: action) } } func brightnessChange(value:CGFloat) { } func cacheAll() { } func toolbar(toolbar:ToolBar, clickMoreSetting:UIView) { let moreVC = QSMoreSettingController() toolbar.hideWithAnimations(animation: true) let nav = UINavigationController(rootViewController: moreVC) present(nav, animated: true, completion: nil) } //MARK: - QSCategoryDelegate func categoryDidSelectAtIndex(index:Int) { if QSReaderSetting.shared.pageStyle == .curlPage { curlPageViewController.categoryDidSelectAtIndex(index: index) } else if QSReaderSetting.shared.pageStyle == .horMove { horMoveController.categoryDidSelectAtIndex(index: index) } } } extension ZSReaderViewController:QSTextViewProtocol{ var presenter: QSTextPresenterProtocol? { get { return nil } set { } } func showBook(book: QSBook) { } func showResources(resources: [ResourceModel]) { } func showAllChapter(chapters: [NSDictionary]) { } func showChapter(chapter: Dictionary, index: Int) { } func showEmpty() { } func downloadFinish(book: QSBook) { } func showProgress(dict: [String : Any]) { } } ================================================ FILE: zhuishushenqi/TXTReader/Reader/ViewModel/ZSBookBoughtViewModel.swift ================================================ // // ZSBookBoughtViewModel.swift // zhuishushenqi // // Created by caony on 2018/11/15. // Copyright © 2018 QS. All rights reserved. // import UIKit import ZSAPI class ZSBookBoughtViewModel: NSObject { func boughtInfo(id:String, token:String, _ callback:ZSBaseCallback?) { let api = ZSAPI.boughtChapters(id: id, token: token) zs_get(api.path, parameters: api.parameters) { (json) in if let info = ZSBoughtInfo.deserialize(from: json) { callback?(info) } } } } ================================================ FILE: zhuishushenqi/TXTReader/Reader/ViewModel/ZSFontViewModel.swift ================================================ // // ZSFontViewModel.swift // zhuishushenqi // // Created by caony on 2018/9/13. // Copyright © 2018年 QS. All rights reserved. // import Foundation class ZSFontViewModel { var webService = ZSFontService() // http://statics.zhuishushenqi.com/fonts/fz-qkbys.TTF // http://statics.zhuishushenqi.com/fonts/fz-yhzx.TTF // http://statics.zhuishushenqi.com/fonts/fz-xjlt-new.ttf // http://statics.zhuishushenqi.com/fonts/fz-sxslkt.ttf // http://statics.zhuishushenqi.com/fonts/fz-mwt.ttf var fonts = [["key":"system", "name":"系统默认", "image":""], ["key":"fz-yhzx.TTF", "name":"方正悠黑", "image":"fzyouhei"], ["key":"fz-qkbys.TTF", "name":"方正清刻本悦宋", "image":"fzqingke"], ["key":"fz-xjlt-new.ttf", "name":"方正金陵细", "image":"fzxijinling"], ["key":"fz-sxslkt.ttf", "name":"方正苏新诗柳楷", "image":"fzsuxinshil"], ["key":"fz-mwt.ttf", "name":"方正喵呜", "image":"fzmiaowu"], ["key":"ltt.ttf", "name":"兰亭黑", "image":"lantingh"], ["key":"fz-kt.ttf", "name":"楷体", "image":"kai"], ["key":"fz-wbt.ttf", "name":"魏碑", "image":"weibei"], ["key":"ypt.ttf", "name":"雅痞", "image":"yapi"], ["key":"hkppt.ttf", "name":"翩翩体", "image":"pianpian"], ["key":"hylst.ttf", "name":"隶书", "image":"li"], ["key":"Redocn.ttf", "name":"日文字体", "image":"riwen"] ] var selectedIndexPath = IndexPath(item: 0, section: 0) func fetchFont(indexPath:IndexPath, handler:ZSBaseCallback?) { var dict = fonts[indexPath.row] let path:String = "\(NSHomeDirectory())/Documents/\(dict["key"] ?? "")" if FileManager.default.fileExists(atPath: path) { handler?(true) } else { let url = "http://statics.zhuishushenqi.com/fonts/\(dict["key"] ?? "")" webService.fetchFont(url: url) { (json) in if let _ = json?["url"] { handler?(true) } else { handler?(false) } } } } func fileExist(indexPath:IndexPath) -> Bool { var dict = fonts[indexPath.row] let path:String = "\(NSHomeDirectory())/Documents/\(dict["key"] ?? "")" let exist = FileManager.default.fileExists(atPath: path) return exist } func copyFont() { for font in fonts { let file = font["key"] ?? "" let path = "\(NSHomeDirectory())/Documents/\(file)" let bundlePath = Bundle.main.path(forResource: file, ofType: nil) ?? "" let exist = FileManager.default.fileExists(atPath: bundlePath) if exist { try? FileManager.default.copyItem(atPath: bundlePath, toPath: path) } } } func fontArray(path:String, size:CGFloat) { let fontPath = CFStringCreateWithCString(nil, path.cString(using: .utf8), CFStringBuiltInEncodings.UTF8.rawValue) if let fontURL = CFURLCreateWithFileSystemPath(nil, fontPath, CFURLPathStyle.cfurlposixPathStyle, false) { let fontArray = CTFontManagerCreateFontDescriptorsFromURL(fontURL) CTFontManagerRegisterFontsForURL(fontURL, CTFontManagerScope.none, nil) var customAttay:[UIFont] = [] for item in 0..?) { var size = QSReaderSetting.shared.fontSize if action == .plus { size += 1 } else { size -= 1 } if size > QSReaderFontSizeMax { QSLog("fontSize:\(size)\n 超出了限制") return } if size < QSReaderFontSizeMin { QSLog("fontSize:\(size)\n 超出了限制") return } QSReaderSetting.shared.fontSize = size guard let record = book?.record else { resetCached() return } let chapterIndex = record.chapter > 0 ? record.chapter:0 let pageIndex = record.page > 0 ? record.page:0 guard let link = book?.chaptersInfo?[chapterIndex].link else { resetCached() return } guard let chapter = cachedChapter[link] else { resetCached() return } // 如果缓存的章节量较大,为了防止影响用户体验,当前章节处理后直接返回,其他章节继续处理 chapter.getPages() if pageIndex >= 0 && pageIndex < chapter.pages.count { callback?(chapter.pages[pageIndex]) } else { callback?(chapter.pages.last) } resetCached() } func resetCached() { //字体变小,页数变少,这里应该对所有的已缓存章节变更 for (_, chapter) in cachedChapter { chapter.getPages() } } //MARK: - boughtINfo func boughtInfo( token:String, _ callback:ZSBaseCallback?) { let api = ZSAPI.boughtChapters(id: book?._id ?? "", token: token) zs_get(api.path, parameters: api.parameters) { (json) in if let info = ZSBoughtInfo.deserialize(from: json) { self.boughtInfo = info callback?(info) } } } //MARK: - fetch network resource func fetchAllResource(_ callback:ZSBaseCallback<[ResourceModel]>?){ let key = book?._id ?? "" webService.fetchAllResource(key: key) { (resources) in self.book?.resources = resources callback?(resources) } } func fetchSourceIndex() ->Int? { guard let source = self.book?.record?.source else { return nil } guard let resources = self.book?.resources else { return nil } var index = 0 for model in resources { if model._id.isEqual(to: source._id) { break } index += 1 } return index } func changeSource(index:Int) { if index < (self.book?.resources?.count ?? 0) { self.book?.record?.source = self.book?.resources?[safe: index] } } func fetchAllChapters(_ callback:ZSBaseCallback<[ZSChapterInfo]>?){ if let index = fetchSourceIndex() { let key = self.book?.resources?[safe: index]?._id ?? "" webService.fetchAllChapters(key: key) { (chapters) in self.book?.chaptersInfo = chapters callback?(chapters) } } else { if sourceIndex < (self.book?.resources?.count ?? 0) { changeSource(index: sourceIndex) let key = self.book?.resources?[safe: sourceIndex]?._id ?? "" webService.fetchAllChapters(key: key) { (chapters) in self.book?.chaptersInfo = chapters callback?(chapters) } } else { sourceIndex = (self.book?.resources?.count ?? 0) - 1 changeSource(index: sourceIndex) let key = self.book?.resources?[safe: sourceIndex]?._id ?? "" webService.fetchAllChapters(key: key) { (chapters) in self.book?.chaptersInfo = chapters callback?(chapters) } } } } func fetchChapter(key:String,_ callback:ZSBaseCallback?){ webService.fetchChapter(key: key, callback) } fileprivate func fetchForwardPage(link:String,chapterIndex:Int,callback:ZSSearchWebAnyCallback?){ // 内存缓存 if let model = cachedChapter[link] { callback?(model.pages.first) } else { fetchChapter(key: link, { (body) in guard let bodyInfo = body else { return } if let network = self.cacheChapter(body: bodyInfo, index: chapterIndex) { // 请求新章节成功后不一定是当前的章节 callback?(network.pages.first) } }) } } // 此方法仅获取page,不改变record func fetchNextPage(indexPath:IndexPath,callback:ZSSearchWebAnyCallback?, networkCallback:ZSSearchWebAnyCallback?){ if let chapters = book?.chaptersInfo?[safe: indexPath.section] { // 从cachedChapter中获取model if let model = cachedChapter[chapters.link] { callback?(model.pages[safe: indexPath.row]) } else { // cachedChapter中不存在则网络请求,先返回一个空的 callback?(nil) fetchNewChapter(indexPath: indexPath,callback: networkCallback) } } else { callback?(nil) fetchAllResource { (resources) in self.fetchAllChapters({ (chaptersInfo) in self.fetchNewChapter(indexPath: indexPath,callback: networkCallback) }) } } } // 此方法仅获取page,不改变record func fetchLastPage(indexPath:IndexPath,callback:ZSSearchWebAnyCallback?){ if let chapters = book?.chaptersInfo?[safe: indexPath.section] { // 从cachedChapter中获取model if let model = cachedChapter[chapters.link] { callback?(model.pages.first) } else { // cachedChapter中不存在则网络请求,先返回一个空的 callback?(nil) fetchNewChapter(indexPath: indexPath,callback: callback) } } else { callback?(nil) fetchAllResource { (resources) in self.fetchAllChapters({ (chaptersInfo) in self.fetchNewChapter(indexPath: indexPath,callback: callback) }) } } } func updateLastRecord(callback:ZSSearchWebAnyCallback?) { if let record = book?.record { if let chapters = book?.chaptersInfo?[safe: record.chapter] { // 从cachedChapter中获取model if let model = cachedChapter[chapters.link] { let pageIndex = record.page if pageIndex > 0 { record.page -= 1 callback?(model.pages[safe: record.page]) } else {// 新章节 record.page = 0 record.chapter -= 1 record.chapterModel = nil if record.chapter >= 0 { if let link = book?.chaptersInfo?[safe: record.chapter]?.link { if let chapter = cachedChapter[link] { record.page = chapter.pages.count - 1 record.chapterModel = chapter callback?(chapter.pages[safe: record.page]) } } } } } else { record.page = 0 record.chapter -= 1 record.chapterModel = nil if let link = book?.chaptersInfo?[safe: record.chapter]?.link { if let chapter = cachedChapter[link] { record.page = chapter.pages.count - 1 record.chapterModel = chapter callback?(chapter.pages[safe: record.page]) } } } } book?.record = record } } // 先更新 func updateNextRecord(callback:ZSSearchWebAnyCallback?) { // 向前章节,完成后从内存中获取当前章节,更新阅读记录中的model // 判断是否为新的章节 guard let record = book?.record else { return } guard let info = book?.chaptersInfo else { return } if record.chapter >= info.count { if info.count > 0 { record.chapter = info.count - 1 } else { record.chapter = 0 } } // 1. 网络书籍 if let link = info[safe: record.chapter]?.link { let page = record.page var totalPage = 0 var chapter:QSChapter? func back() { record.page += 1 record.chapterModel = chapter callback?(chapter?.pages[safe: record.page]) } if let chapterModel = cachedChapter[link] { totalPage = chapterModel.pages.count chapter = chapterModel if totalPage > 0 && (page < totalPage - 1) { back() return } } else if let model = ZSBookDownloader.shared.content(for: link) { if let chapterModel = cacheChapter(body: model, index: record.chapter) { totalPage = chapterModel.pages.count chapter = chapterModel } if totalPage > 0 && (page < totalPage - 1) { back() return } } // 新章节 record.chapter += 1 record.page = 0 record.chapterModel = nil guard let tmpLink = book?.chaptersInfo?[safe: record.chapter]?.link else { return } if let chapterModel = cachedChapter[tmpLink] { record.chapterModel = chapterModel callback?(chapterModel.pages[safe: record.page]) } else if let model = ZSBookDownloader.shared.content(for: tmpLink) { if let chapterModel = cacheChapter(body: model, index: record.chapter) { record.chapterModel = chapterModel callback?(chapterModel.pages[safe: record.page]) } } } else if let localChapters = self.book?.book.localChapters { guard let chapterModel = localChapters[safe: record.chapter] else { return } let pageIndex = record.page if pageIndex < (chapterModel.pages.count - 1) { record.page += 1 record.chapterModel = chapterModel callback?(chapterModel.pages[safe: record.page]) } else { record.page = 0 record.chapter += 1 record.chapterModel = nil if record.chapter < localChapters.count { record.chapterModel = localChapters[safe: record.chapter] callback?(localChapters[safe: record.chapter]?.pages[safe: record.page]) } } } book?.record = record } // 获取下一个页面 func fetchNextPage(callback:ZSSearchWebAnyCallback?){ if exsitLocal() { fetchNextLocalPage(page: nil, callback) } else { guard let record = book?.record else { return } guard let info = book?.chaptersInfo else { return } var chapterIndex = record.chapter if chapterIndex >= info.count { chapterIndex = info.count - 1 } func back(with model:QSChapter, page:Int) { let tmpPage = page + 1 let tmpModel = model.pages[safe: tmpPage] callback?(tmpModel) } guard let link = book?.chaptersInfo?[safe: chapterIndex]?.link else { return } let page = record.page if let chapterModel = cachedChapter[link] { if page < chapterModel.pages.count - 1 { back(with: chapterModel, page: page) return } } else if let model = ZSBookDownloader.shared.content(for: link) { if let chapterModel = cacheChapter(body: model, index: chapterIndex) { if page < chapterModel.pages.count - 1 { back(with: chapterModel, page: page) return } } } fetchNewChapter(chapterOffset: 1,record: record,chaptersInfo: self.book?.chaptersInfo,callback: callback) fetchPreChapter(record: record, chapterOffset: 1) } } func fetchLastPage(callback:ZSSearchWebAnyCallback?){ if exsitLocal() { fetchLastLocalPage(page:nil,callback) } else { guard let record = book?.record else { return } var chapterIndex = record.chapter func back(with model:QSChapter, page:Int) { // 当前页存在 let pageIndex = page - 1 let pageModel = model.pages[safe: pageIndex] callback?(pageModel) } guard let link = book?.chaptersInfo?[safe: chapterIndex]?.link else { return } if chapterIndex < 0 { chapterIndex = 0 } // 获取当前章节的page let page = record.page // 1. 内存缓存 if let chapterModel = cachedChapter[link] { if page > 0 { back(with: chapterModel, page: page) return } } else if let model = ZSBookDownloader.shared.content(for: link) { // 2. 磁盘缓存 if let chapterModel = cacheChapter(body: model, index: chapterIndex) { if page > 0 { back(with: chapterModel, page: page) return } } } // 获取新的章节信息 fetchNewChapter(chapterOffset: -1,record: record,chaptersInfo: self.book?.chaptersInfo,callback: callback) fetchPreChapter(record: record, chapterOffset: -1) } } func existNextPage() -> Bool { if let record = book?.record { if let chapterModel = record.chapterModel { let chapter = record.chapter let page = record.page if chapter == ((book?.chaptersInfo?.count ?? 0) - 1) { if page == chapterModel.pages.count - 1 { return false } } } else { let chapter = record.chapter if let chaptersCount = book?.chaptersInfo?.count { // 如果是最后一章,要考虑是否为最后一页,一般第一次进入不存在chapterModel,网络请求成功后默认展示第一章 if chapter == chaptersCount - 1 { if let chapterLink = book?.chaptersInfo?[safe: chapter]?.link { let chapterModel = cachedChapter[chapterLink] if chapterModel?.pages.count == 1 || record.page == ((chapterModel?.pages.count ?? 1) - 1) { return false } } } } else if let chapterCount = book?.book.localChapters.count { if chapter >= chapterCount { return false } } } return true } return false } func existLastPage() -> Bool { if let record = book?.record { if let _ = record.chapterModel { let chapter = record.chapter let page = record.page if chapter == 0 { if page == 0 { return false } } } else { let chapter = record.chapter let page = record.page if chapter == 0 { if page == 0 { return false } } } return true } return false } func fetchCurrentPage(_ callback:ZSSearchWebAnyCallback?){ if let record = book?.record { fetchPreChapter(record: record, chapterOffset: 0) var chapter = record.chapter // 如果当前章节超出限制,取最大值 if chapter >= (book?.chaptersInfo?.count ?? 0) { chapter = (book?.chaptersInfo?.count ?? 0) - 1 } if let link = book?.chaptersInfo?[safe: chapter]?.link { fetchChapter(key: link) { (body) in if let bodyInfo = body { if let network = self.cacheChapter(body: bodyInfo, index: chapter) { callback?(network.pages.first) } } } } } } func fetchInitialChapter(_ callback:ZSSearchWebAnyCallback?){ if let record = book?.record { fetchPreChapter(record: record, chapterOffset: 0) if let chapter = record.chapterModel { let chapterIndex = record.chapter // 换源时有可能超出章节 if let chapters = book?.chaptersInfo { var linkIndex = chapterIndex if chapterIndex >= chapters.count { linkIndex = chapters.count - 1 } guard let link = chapters[safe: linkIndex]?.link else { return } cachedChapter[link] = chapter } } else { fetchNewChapter(chapterOffset: 0, record: record, chaptersInfo: book?.chaptersInfo) { (page) in callback?(page) } } } } fileprivate func fetchNewChapter(indexPath:IndexPath,callback:ZSSearchWebAnyCallback?) { if let chapterInfo = self.book?.chaptersInfo?[safe: indexPath.section] { let link = chapterInfo.link // 内存缓存 self.fetchChapter(key: link, { (body) in if let bodyInfo = body { if let network = self.cacheChapter(body: bodyInfo, index: indexPath.section) { callback?(network.pages.first) } } }) } } /// 默认请求该章节的前后X章内容 func fetchPreChapter(record:QSRecord, chapterOffset:Int) { if chapterOffset <= 0 { /// 请求当前章节的前X章节 for index in (-preCacheCount + chapterOffset)..<0 { fetchNewChapter(chapterOffset: index, record: record, chaptersInfo: book?.chaptersInfo) { (page) in // 缓存 } } } if chapterOffset >= 0 { /// 请求当前章节的后X章节 for index in 1..<(preCacheCount + 1 + chapterOffset) { fetchNewChapter(chapterOffset: index, record: record, chaptersInfo: book?.chaptersInfo) { (page) in // 缓存 } } } } func fetchNewChapter(chapterOffset:Int,record:QSRecord,chaptersInfo:[ZSChapterInfo]?,callback:ZSSearchWebAnyCallback?){ let chapter = record.chapter + chapterOffset if chapter >= 0 && chapter < (chaptersInfo?.count ?? 0) { if let chapterInfo = chaptersInfo?[safe: chapter] { let link = chapterInfo.link // 内存缓存 if let model = cachedChapter[link] { let page = chapterOffset > 0 ? 0: model.pages.count - 1 callback?(model.pages[safe: page]) } else { self.fetchChapter(key: link, { (body) in if let bodyInfo = body { if let network = self.cacheChapter(body: bodyInfo, index: chapter) { // 请求新章节成功后不一定是当前的章节 callback?(network.pages.first) } } }) } } } } // 将新获取的章节信息存入chapterDict中 @discardableResult func cacheChapter(body:ZSChapterBody,index:Int)->QSChapter? { let chapterModel = self.book?.chaptersInfo?[safe: index] let qsChapter = QSChapter() if let link = chapterModel?.link { qsChapter.link = link // 如果使用追书正版书源,取的字段应该是cpContent,需要根据当前选择的源进行判断 qsChapter.isVip = chapterModel?.isVip ?? false qsChapter.order = chapterModel?.order ?? 0 qsChapter.currency = chapterModel?.currency ?? 0 qsChapter.cpContent = body.cpContent qsChapter.id = body.id qsChapter.content = body.body if let title = chapterModel?.title { qsChapter.title = title } qsChapter.curChapter = index qsChapter.getPages() // 直接计算page cachedChapter[link] = qsChapter return qsChapter } return nil } } //MARK: - horMove extension ZSReaderViewModel { // 这个方法包含本地与网络小说的判断 func hm_existNext(page:QSPage?) ->Bool { if exsitLocal() { return existNextLocalPage() } else { if let tmpPage = page { let chapterIndex = tmpPage.curChapter let pageIndex = tmpPage.curPage let totalPages = tmpPage.totalPages if pageIndex == totalPages - 1 { if let chaptersInfo = book?.chaptersInfo { if chapterIndex == chaptersInfo.count - 1 { return false } } } } return true } } func hm_existLast(page:QSPage?) ->Bool { if exsitLocal() { return existLastLocalPage() } else { if let tmpPage = page { let chapterIndex = tmpPage.curChapter let pageIndex = tmpPage.curPage if pageIndex == 0 { if chapterIndex == 0 { return false } } } return true } } fileprivate func fetchBackwardPage(link:String,chapterIndex:Int,callback:ZSSearchWebAnyCallback?){ // 内存缓存 if let model = cachedChapter[link] { callback?(model.pages.last) } else { fetchChapter(key: link, { (body) in if let bodyInfo = body { if let network = self.cacheChapter(body: bodyInfo, index: chapterIndex) { callback?(network.pages.last) self.book?.record?.page = network.pages.count - 1 } } }) } } func fetchBackwardPage(page:QSPage?,callback:ZSSearchWebAnyCallback?) { if exsitLocal() { fetchLastLocalPage(page:page,callback) } else { if let tmpPage = page { let chapterIndex = tmpPage.curChapter let pageIndex = tmpPage.curPage if pageIndex > 0 { if let link = book?.chaptersInfo?[chapterIndex].link { if let chapterModel = cachedChapter[link] { callback?(chapterModel.pages[pageIndex - 1]) } } } else if (pageIndex == 0) { if let chapterInfo = book?.chaptersInfo?[chapterIndex - 1] { let link = chapterInfo.link if chapterIndex > 0 { fetchBackwardPage(link: link, chapterIndex: chapterIndex - 1, callback: callback) } } } } else { if let chapter = book?.record?.chapter { if chapter > 0 { if let chapterInfo = book?.chaptersInfo?[chapter - 1] { let link = chapterInfo.link fetchBackwardPage(link: link, chapterIndex: chapter - 1, callback: callback) } } } } } } func fetchForwardPage(page:QSPage?,callback:ZSSearchWebAnyCallback?){ if exsitLocal() { fetchNextLocalPage(page:page, callback) } else { if let tmpPage = page { // page 存在则根据page的值来获取下一章节 let chapterIndex = tmpPage.curChapter let pageIndex = tmpPage.curPage let totalPages = tmpPage.totalPages if pageIndex < totalPages - 1 { // 还在当前章节 if let link = book?.chaptersInfo?[chapterIndex].link { if let chapterModel = cachedChapter[link] { callback?(chapterModel.pages[pageIndex + 1]) } } } else if pageIndex == totalPages - 1 { // 新的章节 if chapterIndex < (book?.chaptersInfo?.count ?? 0 - 1) { if let chapterInfo = book?.chaptersInfo?[chapterIndex + 1] { let link = chapterInfo.link fetchForwardPage(link: link, chapterIndex: chapterIndex + 1, callback: callback) } } } } else { // page 不存在说明是新的章节,请求新的章节 if let chapter = book?.record?.chapter { if chapter >= 0 && chapter < (book?.chaptersInfo?.count ?? 0 - 1) { if let chapterInfo = book?.chaptersInfo?[chapter + 1] { let link = chapterInfo.link fetchForwardPage(link: link, chapterIndex: chapter + 1, callback: callback) } } } } } } func updateBackwardRecord(page:QSPage?){ if exsitLocal() { if let tmpPage = page { let pageIndex = tmpPage.curPage let chapterIndex = tmpPage.curChapter if let _ = book?.book.localChapters[chapterIndex] { if pageIndex > 0 { book?.record?.page = pageIndex - 1 } else if pageIndex == 0 { if chapterIndex > 0 { book?.record?.chapter = chapterIndex - 1 book?.record?.page = book!.book.localChapters[chapterIndex - 1].pages.count - 1 } } } } } else { if let tmpPage = page { let pageIndex = tmpPage.curPage let chapterIndex = tmpPage.curChapter if let link = book?.chaptersInfo?[chapterIndex].link { if let _ = cachedChapter[link] { if pageIndex > 0 { book?.record?.page = pageIndex - 1 } else if pageIndex == 0 { book?.record?.chapter = chapterIndex - 1 book?.record?.page = 0 book?.record?.chapterModel = nil } } else { book?.record?.chapter = chapterIndex - 1 book?.record?.page = 0 book?.record?.chapterModel = nil } } } else { book?.record?.chapter -= 1 book?.record?.page = 0 book?.record?.chapterModel = nil } } } func updateForwardRecord(page:QSPage?){ if exsitLocal() { if let tmpPage = page { let pageIndex = tmpPage.curPage let totalPages = tmpPage.totalPages let chapterIndex = tmpPage.curChapter if let chapterModel = book?.book.localChapters[chapterIndex] { if pageIndex < totalPages - 1 { book?.record?.page = pageIndex + 1 } else if pageIndex == totalPages - 1 { if chapterIndex < book!.book.localChapters.count - 1 { book?.record?.chapter = chapterIndex + 1 book?.record?.page = 0 } } } } } else { if let tmpPage = page { let pageIndex = tmpPage.curPage let totalPages = tmpPage.totalPages let chapterIndex = tmpPage.curChapter if let link = book?.chaptersInfo?[chapterIndex].link { if let _ = cachedChapter[link] { if pageIndex < totalPages - 1 { // 当前章节 book?.record?.page = pageIndex + 1 } else if pageIndex == totalPages - 1 { book?.record?.chapter = chapterIndex + 1 book?.record?.page = 0 book?.record?.chapterModel = nil } } else { book?.record?.chapter = chapterIndex + 1 book?.record?.page = 0 book?.record?.chapterModel = nil } } } else { // page不存在,那么下一页依然是新的章节 book?.record?.chapter += 1 book?.record?.page = 0 book?.record?.chapterModel = nil } } } } //MARK: - local book extension ZSReaderViewModel { func exsitLocal()->Bool{ if let chapters = book?.book.localChapters { if chapters.count > 0 { return true } } return false } func existNextLocalPage() -> Bool{ if exsitLocal() { if let chapters = book?.book.localChapters { if let record = book?.record { let pageIndex = record.page let chapterIndex = record.chapter if chapterIndex == chapters.count - 1 { // 最后一章 if pageIndex == chapters[chapterIndex].pages.count - 1 { return false } } } } else { return false } } return true } func existLastLocalPage() -> Bool { if exsitLocal() { if let chapters = book?.book.localChapters { if let record = book?.record { let pageIndex = record.page let chapterIndex = record.chapter if chapterIndex == 0 { if pageIndex == 0 { return false } } } } else { return false } } return true } func fetchLocalPage(_ callback:ZSBaseCallback?){ if (book?.book.localChapters.count ?? 0) > 0 { let localChapters = book?.book.localChapters ?? [] if let record = book?.record { let pageIndex = record.page let chapterIndex = record.chapter if chapterIndex < localChapters.count { if pageIndex < localChapters[chapterIndex].pages.count { callback?(localChapters[chapterIndex].pages[pageIndex]) } } } } else { callback?(nil) } } func fetchNextLocalPage(page:QSPage?,_ callback:ZSBaseCallback?){ if let chapters = book?.book.localChapters { let pageIndex = book?.record?.page ?? 0 let chapterIndex = book?.record?.chapter ?? 0 if chapterIndex < chapters.count - 1 { if pageIndex < chapters[chapterIndex].pages.count - 1 { callback?(chapters[chapterIndex].pages[pageIndex + 1]) } else { callback?(chapters[chapterIndex + 1].pages.first) } } else if chapterIndex == chapters.count - 1 { if pageIndex < chapters[chapterIndex].pages.count - 1 { callback?(chapters[chapterIndex].pages[pageIndex + 1]) } else { callback?(nil) } } } else { callback?(nil) } } func fetchLastLocalPage(page:QSPage?,_ callback:ZSBaseCallback?){ if let chapters = book?.book.localChapters { let pageIndex = book?.record?.page ?? 0 let chapterIndex = book?.record?.chapter ?? 0 if chapterIndex > 0 && chapterIndex < chapters.count { if pageIndex > 0 { callback?(chapters[chapterIndex].pages[pageIndex - 1]) } else { callback?(chapters[chapterIndex - 1].pages.last) } } else if chapterIndex == 0 { if pageIndex > 0 { callback?(chapters[chapterIndex].pages[pageIndex - 1]) } else { callback?(nil) } } } else { callback?(nil) } } } ================================================ FILE: zhuishushenqi/TXTReader/Reader/ZSSpeakerCell.swift ================================================ // // ZSSpeakerCell.swift // zhuishushenqi // // Created by caony on 2018/10/11. // Copyright © 2018 QS. All rights reserved. // import UIKit class ZSSpeakerCell: UITableViewCell { lazy var download:UIButton = { let btn = UIButton(type: .custom) btn.frame = CGRect(x: 0, y: 0, width: 60, height: 30) btn.setTitle("下载", for: .normal) btn.setTitle("已下载", for: .selected) btn.setTitleColor(UIColor.blue, for: .normal) btn.addTarget(self, action: #selector(downloadAction), for: .touchUpInside) return btn }() var downloadHandler:ZSBaseCallback? @objc private func downloadAction() { downloadHandler?(nil) } override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: UITableViewCell.CellStyle.subtitle, reuseIdentifier: reuseIdentifier) self.accessoryView = self.download self.download.imageView?.startAnimating() } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func awakeFromNib() { super.awakeFromNib() // Initialization code } override func setSelected(_ selected: Bool, animated: Bool) { super.setSelected(selected, animated: animated) } override func layoutSubviews() { super.layoutSubviews() self.imageView?.frame = CGRect(x: 15, y: 10, width: 50, height: 50) self.imageView?.qs_addCorner(radius: 25) self.textLabel?.frame = CGRect(x: 80, y: 15, width: 100, height: 20.34) self.detailTextLabel?.frame = CGRect(x: 80, y: 37, width: 100, height: 14.34) } } ================================================ FILE: zhuishushenqi/TXTReader/Reader/ZSSpeakerViewController.swift ================================================ // // ZSSpeakerViewController.swift // zhuishushenqi // // Created by caony on 2018/10/11. // Copyright © 2018 QS. All rights reserved. // import UIKit import Zip import Kingfisher class ZSSpeakerViewController: UIViewController { lazy var tableView:UITableView = { let tableView = UITableView(frame: CGRect(x: 0, y: 0, width: 0, height: 0), style: .grouped) tableView.dataSource = self tableView.delegate = self tableView.separatorStyle = .singleLine tableView.qs_registerCellClass(QSMoreSettingCell.self) tableView.backgroundColor = UIColor(red: 0.95, green: 0.95, blue: 0.95, alpha: 1.0) return tableView }() override func viewDidLoad() { super.viewDidLoad() self.tableView.qs_registerCellClass(ZSSpeakerCell.self) self.view.addSubview(self.tableView) } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) self.tableView.frame = self.view.bounds } } extension ZSSpeakerViewController:UITableViewDataSource, UITableViewDelegate { func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return TTSConfig.share.allSpeakers.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.qs_dequeueReusableCell(ZSSpeakerCell.self) cell?.accessoryType = .disclosureIndicator let speaker = TTSConfig.share.allSpeakers[indexPath.row] cell?.imageView?.setImage(imageUrl: speaker.largeIcon) cell?.textLabel?.text = speaker.nickname cell?.detailTextLabel?.text = speaker.accent let fileName = "\(speaker.name).jet" let jet = "\(filePath)\(fileName)" let exist = FileManager.default.fileExists(atPath: jet) cell?.download.isSelected = exist cell?.downloadHandler = { _ in downloadFile(urlString: speaker.downloadUrl, handler: { (response) in if let url = response as? URL { self.unzip(fileURL: url) self.tableView.reloadData() } }) } return cell! } func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { return 70 } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) } func unzip(fileURL:URL) { do { let documentsDirectory = FileManager.default.urls(for:.documentDirectory, in: .userDomainMask)[0].appendingPathComponent("/speakerres/3589709422/", isDirectory: true) try Zip.unzipFile(fileURL, destination: documentsDirectory, overwrite: true, password: nil, progress: { (progress) -> () in print(progress) }) // Unzip } catch { print("Something went wrong") } } } ================================================ FILE: zhuishushenqi/TXTReader/Speech/Model/Network.swift ================================================ // // Network.swift // iflyDemo // // Created by caony on 2018/9/19. // Copyright © 2018年 QSH. All rights reserved. // import Foundation import Alamofire import Zip public typealias NetworkHandler = (T?)->Void //let filePath = "\(NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first ?? "")/speakerres/3589709422/" func download(urlString:String, handler:NetworkHandler?) { let pathURL = URL(fileURLWithPath: filePath, isDirectory: true) do { try FileManager.default.createDirectory(at: pathURL, withIntermediateDirectories: true, attributes: nil) } catch { print(error) } let fileName = (urlString as NSString).lastPathComponent let fileURL = pathURL.appendingPathComponent(fileName) let destination: DownloadRequest.DownloadFileDestination = { temporaryURL, _ in return (fileURL, [.createIntermediateDirectories, .removePreviousFile]) } download(urlString, to: destination).response { (response) in if let error = response.error { handler?(error) } else { handler?(response.destinationURL) } } } func unzip(fileURL:URL) { do { let documentsDirectory = FileManager.default.urls(for:.documentDirectory, in: .userDomainMask)[0].appendingPathComponent("/speakerres/3589709422/", isDirectory: true) try Zip.unzipFile(fileURL, destination: documentsDirectory, overwrite: true, password: nil, progress: { (progress) -> () in print(progress) }) // Unzip } catch { print("Something went wrong") } } ================================================ FILE: zhuishushenqi/TXTReader/Speech/Model/Speaker.swift ================================================ // // Speaker.swift // iflyDemo // // Created by caony on 2018/9/19. // Copyright © 2018年 QSH. All rights reserved. // import Foundation import HandyJSON //https://ttsh5.openspeech.cn/tts-h5/speaker/list enum EngineType:String { case auto = "auto" case local = "local" case cloud = "cloud" } class Speaker: HandyJSON { var accent = "普通话" var age = 25 var appid = "5445f87d" var commonExpirationDate = "" var desc = "" var downloads = 20806 var engineType = EngineType.local var engineVersion = 1 var ent = "" var experienceExpirationDate = "" var field = "恐怖灵异" var isActive = 1 var isDefault = 0 var isNew = 0 var isRecommend = 0 var isVip = 0 var largeIcon = "https://bj.openstorage.cn/v1/iflytek/tts/common/icon/xiaoxi_big.png" var level = 3 var listenPath = "aHR0cHM6Ly9iai5vcGVuc3RvcmFnZS5jbi92MS9pZmx5dGVrL3R0cy9jb21tb24vbGlzdGVuL3hpYW94aS5tcDM=" var name = "xiaoxi" var nickname = "方木" var price = 0 var resId = 618 var resPath = "aHR0cDovL2lmbHl0ZWsuYmpkbi5vcGVuc3RvcmFnZS5jbi90dHMvODdkL3Jlc291cmNlL3hpYW94aS56aXA=" var resSize = 4295 var sex = "male" var smallIcon = "https://bj.openstorage.cn/v1/iflytek/tts/common/icon/xiaoxi_small.png" var sortId = 2 var speakerId = 51210 var updateTime = "2018-09-19 19:21:05" var version = "1" var downloadUrl = "http://iflytek.bjdn.openstorage.cn/tts/87d/resource/xiaoxi.zip" var prelisten = "https://bj.openstorage.cn/v1/iflytek/tts/common/listen/xiaoxi.mp3" required init() {} } ================================================ FILE: zhuishushenqi/TXTReader/Speech/Model/TTSConfig.swift ================================================ // // TTSConfig.swift // iflyDemo // // Created by caony on 2018/9/18. // Copyright © 2018年 QSH. All rights reserved. // import Foundation let filePath = "\(NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first ?? "")/speakerres/3589709422/" public class TTSConfig { var speed:String = "50" var volume:String = "50" var pitch:String = "50" var sampleRate:String = "16000" var vcnName:String = "xiaoyu" // the engine type of Text-to-Speech:"auto","local","cloud" var engineType:String = "cloud" // 在线发音人 var cloudVcns:[[String:String]] = [] var voiceID:String = "51200" var ent:String = "" var ttsPath:String = "" var next_text_len:Int = 0 var next_text:String = "" var speakerPath = "\(NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0])/speakerres/3589709422/xiaoyan.jet" var commonPath = "\(Bundle.main.resourcePath ?? "")/TTSResource/common.jet" var fileName:String = "tts.pcm" let tts_res_path = "tts_res_path" let unicode = "unicode" private init() { cloudVcns = [ ["name":"小燕","vcn":"xiaoyan"], ["name":"小宇","vcn":"xiaoyu"], ["name":"凯瑟琳","vcn":"catherine"], ["name":"亨利","vcn":"henry"], ["name":"玛丽","vcn":"vimary"], ["name":"小研","vcn":"vixy"], ["name":"小琪","vcn":"vixq"], ["name":"小峰","vcn":"vixf"], ["name":"小梅","vcn":"vixl"], ["name":"小莉","vcn":"vixq"], ["name":"小蓉","vcn":"vixr"], ["name":"小芸","vcn":"vixyun"], ["name":"小坤","vcn":"vixk"], ["name":"小强","vcn":"vixqa"], ["name":"小莹","vcn":"vixyin"], ["name":"小新","vcn":"vixx"], ["name":"楠楠","vcn":"vinn"], ["name":"老孙","vcn":"vils"], ] } static var share = TTSConfig() var allSpeakers:[Speaker] = [] func availableSpeakers() ->[Speaker] { var speakers:[Speaker] = [] for vcn in cloudVcns { let speaker = Speaker() if let vcnName = vcn["vcn"] { speaker.name = vcnName } if let name = vcn["name"] { speaker.nickname = name } speaker.accent = "普通话" speaker.engineType = .cloud speakers.append(speaker) } let files = (try? FileManager.default.contentsOfDirectory(atPath: filePath)) ?? [] var jets:[String] = [] for file in files { let jet = (file as NSString).lastPathComponent.contains(".jet") if jet { jets.append(file) } } for jet in jets { let file = (jet as NSString).lastPathComponent let fileArr = file.components(separatedBy: ".") if fileArr.count > 1 { let name = fileArr[0] for speak in allSpeakers { if speak.name == name { speakers.append(speak) } } } } return speakers } func getSpeakers() { ZSBookManager.calTime { let filePath = Bundle.main.path(forResource: "speakers.plist", ofType: nil) ?? "" if let dict = NSDictionary(contentsOfFile: filePath) { if let speakers = dict["speakers"] as? [Any] { if let models = [Speaker].deserialize(from: speakers) as? [Speaker] { self.allSpeakers = models } } } } print("") } func parseJSONFile(path:String) ->[Speaker] { var models:[Speaker] = [] ZSBookManager.calTime { let url = URL(fileURLWithPath: path) if let data = try? Data(contentsOf: url, options: Data.ReadingOptions.mappedIfSafe) { let str = String(data: data, encoding: .utf8) ?? "" if let obj = try? JSONSerialization.jsonObject(with: data, options: .allowFragments) { if let dict = obj as? [String:Any] { // let filePath = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.userDomainMask, true)[0] // // dict.write(toFile: "\(filePath)/speakers.plist", atomically: true) if let speakers = dict["speakers"] as? [Any] { if let speakersModel = [Speaker].deserialize(from: speakers) as? [Speaker] { models = speakersModel } } } } } } return models } } ================================================ FILE: zhuishushenqi/TXTReader/Speech/Model/TTSResource/speakers.plist ================================================ speakers accent 普通话 age 25 appid 5445f87d commonExpirationDate desc downloadUrl http://iflytek.bjdn.openstorage.cn/tts/87d/resource/xiaoxi.zip downloads 20806 engineType local engineVersion 1 ent experienceExpirationDate field 恐怖灵异 isActive 1 isDefault 0 isNew 0 isRecommend 0 isVip 0 largeIcon https://bj.openstorage.cn/v1/iflytek/tts/common/icon/xiaoxi_big.png level 3 listenPath aHR0cHM6Ly9iai5vcGVuc3RvcmFnZS5jbi92MS9pZmx5dGVrL3R0cy9jb21tb24vbGlzdGVuL3hpYW94aS5tcDM= name xiaoxi nickname 方木 prelisten https://bj.openstorage.cn/v1/iflytek/tts/common/listen/xiaoxi.mp3 price 0 resId 618 resPath aHR0cDovL2lmbHl0ZWsuYmpkbi5vcGVuc3RvcmFnZS5jbi90dHMvODdkL3Jlc291cmNlL3hpYW94aS56aXA= resSize 4295 sex male smallIcon https://bj.openstorage.cn/v1/iflytek/tts/common/icon/xiaoxi_small.png sortId 2 speakerId 51210 updateTime 2018-09-19 19:21:05 version 1 accent 普通话 age 18 appid 5445f87d commonExpirationDate desc downloadUrl http://iflytek.bjdn.openstorage.cn/tts/87d/resource/xiaoyin.zip downloads 29025 engineType local engineVersion 1 ent experienceExpirationDate field 都市言情 isActive 1 isDefault 0 isNew 0 isRecommend 0 isVip 0 largeIcon https://bj.openstorage.cn/v1/iflytek/tts/common/icon/xiaoyin_big.png level 3 listenPath aHR0cHM6Ly9iai5vcGVuc3RvcmFnZS5jbi92MS9pZmx5dGVrL3R0cy9jb21tb24vbGlzdGVuL3hpYW95aW4ubXAz name xiaoyin nickname 方茴 prelisten https://bj.openstorage.cn/v1/iflytek/tts/common/listen/xiaoyin.mp3 price 0 resId 616 resPath aHR0cDovL2lmbHl0ZWsuYmpkbi5vcGVuc3RvcmFnZS5jbi90dHMvODdkL3Jlc291cmNlL3hpYW95aW4uemlw resSize 4281 sex female smallIcon https://bj.openstorage.cn/v1/iflytek/tts/common/icon/xiaoyin_small.png sortId 4 speakerId 51115 updateTime 2018-09-19 20:21:07 version 1 accent 普通话 age 30 appid 5445f87d commonExpirationDate desc downloadUrl http://iflytek.bjdn.openstorage.cn/tts/87d/resource/xiaohou.zip downloads 25445 engineType local engineVersion 1 ent experienceExpirationDate field 原创出版 isActive 1 isDefault 0 isNew 0 isRecommend 0 isVip 0 largeIcon https://bj.openstorage.cn/v1/iflytek/tts/common/icon/xiaohou_big.png level 3 listenPath aHR0cHM6Ly9iai5vcGVuc3RvcmFnZS5jbi92MS9pZmx5dGVrL3R0cy9jb21tb24vbGlzdGVuL3hpYW9ob3UubXAz name xiaohou nickname 郭嘉 prelisten https://bj.openstorage.cn/v1/iflytek/tts/common/listen/xiaohou.mp3 price 0 resId 620 resPath aHR0cDovL2lmbHl0ZWsuYmpkbi5vcGVuc3RvcmFnZS5jbi90dHMvODdkL3Jlc291cmNlL3hpYW9ob3Uuemlw resSize 5188 sex male smallIcon https://bj.openstorage.cn/v1/iflytek/tts/common/icon/xiaohou_small.png sortId 4 speakerId 51200 updateTime 2018-09-19 15:20:57 version 1 accent 普通话 age 10 appid 5445f87d commonExpirationDate desc downloadUrl http://iflytek.bjdn.openstorage.cn/tts/87d/resource/nannan.zip downloads 20587 engineType local engineVersion 1 ent experienceExpirationDate field 普通发音人 isActive 1 isDefault 0 isNew 0 isRecommend 0 isVip 0 largeIcon https://bj.openstorage.cn/v1/iflytek/tts/common/icon/nannan_big.png level 0 listenPath aHR0cHM6Ly9iai5vcGVuc3RvcmFnZS5jbi92MS9pZmx5dGVrL3R0cy9jb21tb24vbGlzdGVuL25hbm5hbi5tcDM= name nannan nickname 小萝莉 prelisten https://bj.openstorage.cn/v1/iflytek/tts/common/listen/nannan.mp3 price 0 resId 624 resPath aHR0cDovL2lmbHl0ZWsuYmpkbi5vcGVuc3RvcmFnZS5jbi90dHMvODdkL3Jlc291cmNlL25hbm5hbi56aXA= resSize 1150 sex female smallIcon https://bj.openstorage.cn/v1/iflytek/tts/common/icon/nannan_small.png sortId 7 speakerId 7 updateTime 2018-09-19 20:21:07 version 1 accent 普通话 age 34 appid 5445f87d commonExpirationDate desc downloadUrl http://iflytek.bjdn.openstorage.cn/tts/87d/resource/xiaofeng.zip downloads 23476 engineType local engineVersion 1 ent experienceExpirationDate field 普通发音人 isActive 1 isDefault 0 isNew 0 isRecommend 0 isVip 0 largeIcon https://bj.openstorage.cn/v1/iflytek/tts/common/icon/xiaofeng_big.png level 0 listenPath aHR0cHM6Ly9iai5vcGVuc3RvcmFnZS5jbi92MS9pZmx5dGVrL3R0cy9jb21tb24vbGlzdGVuL3hpYW9mZW5nLm1wMw== name xiaofeng nickname 男主播 prelisten https://bj.openstorage.cn/v1/iflytek/tts/common/listen/xiaofeng.mp3 price 0 resId 608 resPath aHR0cDovL2lmbHl0ZWsuYmpkbi5vcGVuc3RvcmFnZS5jbi90dHMvODdkL3Jlc291cmNlL3hpYW9mZW5nLnppcA== resSize 1125 sex male smallIcon https://bj.openstorage.cn/v1/iflytek/tts/common/icon/xiaofeng_small.png sortId 8 speakerId 4 updateTime 2018-09-19 20:21:07 version 1 accent 普通话 age 22 appid 5445f87d commonExpirationDate desc downloadUrl http://iflytek.bjdn.openstorage.cn/tts/87d/resource/xiaojing.zip downloads 22708 engineType local engineVersion 1 ent experienceExpirationDate field 普通发音人 isActive 1 isDefault 0 isNew 0 isRecommend 0 isVip 0 largeIcon https://bj.openstorage.cn/v1/iflytek/tts/common/icon/xiaojing_big.png level 0 listenPath aHR0cHM6Ly9iai5vcGVuc3RvcmFnZS5jbi92MS9pZmx5dGVrL3R0cy9jb21tb24vbGlzdGVuL3hpYW9qaW5nLm1wMw== name xiaojing nickname 邻家姐姐 prelisten https://bj.openstorage.cn/v1/iflytek/tts/common/listen/xiaojing.mp3 price 0 resId 606 resPath aHR0cDovL2lmbHl0ZWsuYmpkbi5vcGVuc3RvcmFnZS5jbi90dHMvODdkL3Jlc291cmNlL3hpYW9qaW5nLnppcA== resSize 1226 sex female smallIcon https://bj.openstorage.cn/v1/iflytek/tts/common/icon/xiaojing_small.png sortId 9 speakerId 8 updateTime 2018-09-19 20:21:07 version 1 accent 普通话 age 20 appid 5445f87d commonExpirationDate desc downloadUrl http://iflytek.bjdn.openstorage.cn/tts/87d/resource/jiajia.zip downloads 23302 engineType local engineVersion 1 ent experienceExpirationDate field 普通发音人 isActive 1 isDefault 0 isNew 0 isRecommend 0 isVip 0 largeIcon https://bj.openstorage.cn/v1/iflytek/tts/common/icon/jiajia_big.png level 0 listenPath aHR0cHM6Ly9iai5vcGVuc3RvcmFnZS5jbi92MS9pZmx5dGVrL3R0cy9jb21tb24vbGlzdGVuL2ppYWppYS5tcDM= name jiajia nickname 嘉嘉老师 prelisten https://bj.openstorage.cn/v1/iflytek/tts/common/listen/jiajia.mp3 price 0 resId 626 resPath aHR0cDovL2lmbHl0ZWsuYmpkbi5vcGVuc3RvcmFnZS5jbi90dHMvODdkL3Jlc291cmNlL2ppYWppYS56aXA= resSize 1714 sex female smallIcon https://bj.openstorage.cn/v1/iflytek/tts/common/icon/jiajia_small.png sortId 11 speakerId 9 updateTime 2018-09-19 20:21:07 version 1 accent 粤语 age 26 appid 5445f87d commonExpirationDate desc downloadUrl http://iflytek.bjdn.openstorage.cn/tts/87d/resource/xiaomei.zip downloads 13046 engineType local engineVersion 1 ent experienceExpirationDate field 粤语 isActive 1 isDefault 0 isNew 0 isRecommend 0 isVip 0 largeIcon https://bj.openstorage.cn/v1/iflytek/tts/common/icon/xiaomei_big.png level 0 listenPath aHR0cHM6Ly9iai5vcGVuc3RvcmFnZS5jbi92MS9pZmx5dGVrL3R0cy9jb21tb24vbGlzdGVuL3hpYW9tZWlfbG9jYWwubXAz name xiaomei nickname 港姐 prelisten https://bj.openstorage.cn/v1/iflytek/tts/common/listen/xiaomei.mp3 price 0 resId 634 resPath aHR0cDovL2lmbHl0ZWsuYmpkbi5vcGVuc3RvcmFnZS5jbi90dHMvODdkL3Jlc291cmNlL3hpYW9tZWkuemlw resSize 1831 sex female smallIcon https://bj.openstorage.cn/v1/iflytek/tts/common/icon/xiaomei_small.png sortId 12 speakerId 15 updateTime 2018-09-19 20:51:08 version 1 accent 台湾话 age 32 appid 5445f87d commonExpirationDate desc downloadUrl http://iflytek.bjdn.openstorage.cn/tts/87d/resource/xiaolin.zip downloads 10551 engineType local engineVersion 1 ent experienceExpirationDate field 台湾话 isActive 1 isDefault 0 isNew 0 isRecommend 0 isVip 0 largeIcon https://bj.openstorage.cn/v1/iflytek/tts/common/icon/xiaolin_big.png level 0 listenPath aHR0cHM6Ly9iai5vcGVuc3RvcmFnZS5jbi92MS9pZmx5dGVrL3R0cy9jb21tb24vbGlzdGVuL3hpYW9saW4ubXAz name xiaolin nickname 佳宜 prelisten https://bj.openstorage.cn/v1/iflytek/tts/common/listen/xiaolin.mp3 price 0 resId 638 resPath aHR0cDovL2lmbHl0ZWsuYmpkbi5vcGVuc3RvcmFnZS5jbi90dHMvODdkL3Jlc291cmNlL3hpYW9saW4uemlw resSize 1616 sex female smallIcon https://bj.openstorage.cn/v1/iflytek/tts/common/icon/xiaolin_small.png sortId 13 speakerId 22 updateTime 2018-09-19 20:51:08 version 1 accent 湖南话 age 32 appid 5445f87d commonExpirationDate desc downloadUrl http://iflytek.bjdn.openstorage.cn/tts/87d/resource/xiaoqiang.zip downloads 8089 engineType local engineVersion 1 ent experienceExpirationDate field 湖南话 isActive 1 isDefault 0 isNew 0 isRecommend 0 isVip 0 largeIcon https://bj.openstorage.cn/v1/iflytek/tts/common/icon/xiaoqiang_big.png level 0 listenPath aHR0cHM6Ly9iai5vcGVuc3RvcmFnZS5jbi92MS9pZmx5dGVrL3R0cy9jb21tb24vbGlzdGVuL3hpYW9xaWFuZy5tcDM= name xiaoqiang nickname 涵涵 prelisten https://bj.openstorage.cn/v1/iflytek/tts/common/listen/xiaoqiang.mp3 price 0 resId 636 resPath aHR0cDovL2lmbHl0ZWsuYmpkbi5vcGVuc3RvcmFnZS5jbi90dHMvODdkL3Jlc291cmNlL3hpYW9xaWFuZy56aXA= resSize 600 sex male smallIcon https://bj.openstorage.cn/v1/iflytek/tts/common/icon/xiaoqiang_small.png sortId 14 speakerId 24 updateTime 2018-09-19 20:51:08 version 1 accent 四川话 age 32 appid 5445f87d commonExpirationDate desc downloadUrl http://iflytek.bjdn.openstorage.cn/tts/87d/resource/xiaorong.zip downloads 9843 engineType local engineVersion 1 ent experienceExpirationDate field 四川话 isActive 1 isDefault 0 isNew 0 isRecommend 0 isVip 0 largeIcon https://bj.openstorage.cn/v1/iflytek/tts/common/icon/xiaorong_big.png level 0 listenPath aHR0cHM6Ly9iai5vcGVuc3RvcmFnZS5jbi92MS9pZmx5dGVrL3R0cy9jb21tb24vbGlzdGVuL3hpYW9yb25nLm1wMw== name xiaorong nickname 辣妹子 prelisten https://bj.openstorage.cn/v1/iflytek/tts/common/listen/xiaorong.mp3 price 0 resId 630 resPath aHR0cDovL2lmbHl0ZWsuYmpkbi5vcGVuc3RvcmFnZS5jbi90dHMvODdkL3Jlc291cmNlL3hpYW9yb25nLnppcA== resSize 489 sex female smallIcon https://bj.openstorage.cn/v1/iflytek/tts/common/icon/xiaorong_small.png sortId 15 speakerId 14 updateTime 2018-09-19 20:51:08 version 1 accent 河南话 age 28 appid 5445f87d commonExpirationDate desc downloadUrl http://iflytek.bjdn.openstorage.cn/tts/87d/resource/xiaokun.zip downloads 7722 engineType local engineVersion 1 ent experienceExpirationDate field 河南话 isActive 1 isDefault 0 isNew 0 isRecommend 0 isVip 0 largeIcon https://bj.openstorage.cn/v1/iflytek/tts/common/icon/xiaokun_big.png level 0 listenPath aHR0cHM6Ly9iai5vcGVuc3RvcmFnZS5jbi92MS9pZmx5dGVrL3R0cy9jb21tb24vbGlzdGVuL3hpYW9rdW4ubXAzCg== name xiaokun nickname 傻根 prelisten https://bj.openstorage.cn/v1/iflytek/tts/common/listen/xiaokun.mp3 price 0 resId 632 resPath aHR0cDovL2lmbHl0ZWsuYmpkbi5vcGVuc3RvcmFnZS5jbi90dHMvODdkL3Jlc291cmNlL3hpYW9rdW4uemlw resSize 580 sex male smallIcon https://bj.openstorage.cn/v1/iflytek/tts/common/icon/xiaokun_small.png sortId 16 speakerId 25 updateTime 2018-09-19 20:51:08 version 1 accent 东北话 age 28 appid 5445f87d commonExpirationDate desc downloadUrl http://iflytek.bjdn.openstorage.cn/tts/87d/resource/xiaoqian.zip downloads 9844 engineType local engineVersion 1 ent experienceExpirationDate field 东北话 isActive 1 isDefault 0 isNew 0 isRecommend 0 isVip 0 largeIcon https://bj.openstorage.cn/v1/iflytek/tts/common/icon/xiaoqian_big.png level 0 listenPath aHR0cHM6Ly9iai5vcGVuc3RvcmFnZS5jbi92MS9pZmx5dGVrL3R0cy9jb21tb24vbGlzdGVuL3hpYW9xaWFuLm1wMw== name xiaoqian nickname 女汉子 prelisten https://bj.openstorage.cn/v1/iflytek/tts/common/listen/xiaoqian.mp3 price 0 resId 628 resPath aHR0cDovL2lmbHl0ZWsuYmpkbi5vcGVuc3RvcmFnZS5jbi90dHMvODdkL3Jlc291cmNlL3hpYW9xaWFuLnppcA== resSize 735 sex female smallIcon https://bj.openstorage.cn/v1/iflytek/tts/common/icon/xiaoqian_small.png sortId 17 speakerId 11 updateTime 2018-09-19 20:51:08 version 1 accent 英文 age 40 appid 5445f87d commonExpirationDate desc downloadUrl http://iflytek.bjdn.openstorage.cn/tts/87d/resource/catherine.zip downloads 13246 engineType local engineVersion 1 ent experienceExpirationDate field 英文 isActive 1 isDefault 0 isNew 0 isRecommend 0 isVip 0 largeIcon https://bj.openstorage.cn/v1/iflytek/tts/common/icon/catherine_big.png level 0 listenPath aHR0cHM6Ly9iai5vcGVuc3RvcmFnZS5jbi92MS9pZmx5dGVrL3R0cy9jb21tb24vbGlzdGVuL2NhdGhlcmluZS5tcDM= name catherine nickname 希拉里 prelisten https://bj.openstorage.cn/v1/iflytek/tts/common/listen/catherine.mp3 price 0 resId 640 resPath aHR0cDovL2lmbHl0ZWsuYmpkbi5vcGVuc3RvcmFnZS5jbi90dHMvODdkL3Jlc291cmNlL2NhdGhlcmluZS56aXA= resSize 3424 sex female smallIcon https://bj.openstorage.cn/v1/iflytek/tts/common/icon/catherine_small.png sortId 17 speakerId 20 updateTime 2018-09-19 20:51:08 version 1 ================================================ FILE: zhuishushenqi/TXTReader/Speech/Model/VoiceBook.swift ================================================ // // VoiceBook.swift // iflyDemo // // Created by caony on 2018/9/18. // Copyright © 2018年 QSH. All rights reserved. // import Foundation typealias VoiceBookHandler = (T?)->Void class VoiceBook:NSObject { private var iflySpeechSynthesizer = IFlySpeechSynthesizer.sharedInstance() private var audioPlayer = PcmPlayer() public var config:TTSConfig = TTSConfig.share public var beginHandler:VoiceBookHandler? public var completeHandler:VoiceBookHandler? public var bufferProgress:VoiceBookHandler? public var speakProgress:VoiceBookHandler? public var speakCancel:VoiceBookHandler? public var speakResume:VoiceBookHandler? public var speakPause:VoiceBookHandler? private let zhuishushenqi = "566551f4" public var speakText:String = "" public var text:String = "科大讯飞作为中国最大的智能语音技术提供商,在智能语音技术领域有着长期的研究积累、并在中文语音合成、语音识别、口语评测等多项技术上拥有国际领先的成果。科大讯飞是我国唯一以语音技术为产业化方向的“国家863计划成果产业化基地”、“国家规划布局内重点软件企业”、“国家火炬计划重点高新技术企业”、“国家高技术产业化示范工程”,并被信息产业部确定为中文语音交互技术标准工作组组长单位,牵头制定中文语音技术标准。2003年,科大讯飞获迄今中国语音产业唯一的“国家科技进步奖(二等)”,2005年获中国信息产业自主创新最高荣誉“信息产业重大技术发明奖”。2006年至2009年,连续四届英文语音合成国际大赛(Blizzard Challenge)荣获第一名。2008年获国际说话人识别评测大赛(美国国家标准技术研究院—NIST 2008)桂冠,2009年获得国际语种识别评测大赛(NIST 2009)高难度混淆方言测试指标冠军、通用测试指标亚军。" override init() { super.init() iflySpeechSynthesizer?.delegate = self } private func setupSpeech() { iflySpeechSynthesizer?.setParameter(config.engineType, forKey: IFlySpeechConstant.engine_TYPE()) iflySpeechSynthesizer?.setParameter(config.speed, forKey: IFlySpeechConstant.speed()) iflySpeechSynthesizer?.setParameter(config.fileName, forKey: IFlySpeechConstant.tts_AUDIO_PATH()) iflySpeechSynthesizer?.setParameter(config.volume, forKey: IFlySpeechConstant.volume()) iflySpeechSynthesizer?.setParameter(config.pitch, forKey: IFlySpeechConstant.pitch()) iflySpeechSynthesizer?.setParameter(config.sampleRate, forKey: IFlySpeechConstant.sample_RATE()) iflySpeechSynthesizer?.setParameter(config.vcnName, forKey: IFlySpeechConstant.voice_NAME()) iflySpeechSynthesizer?.setParameter("unicode", forKey: IFlySpeechConstant.text_ENCODING()) } private func setupLocalSpeech() { //设置协议委托对象 //设置语音合成的启动参数 // IFlySpeechUtility.getUtility()?.setParameter("tts", forKey: IFlyResourceUtil.engine_START()) iflySpeechSynthesizer?.setParameter("\(config.commonPath);\(config.speakerPath)", forKey: config.tts_res_path) let xfyj2 = "591a4d99" iflySpeechSynthesizer?.setParameter(xfyj2, forKey: "caller.appid") // domain=iat,language=en_us // iflySpeechSynthesizer?.setParameter("iat", forKey: "domain") // iflySpeechSynthesizer?.setParameter("zh_cn", forKey: "language") // iflySpeechSynthesizer?.setParameter("cantonese", forKey: IFlySpeechConstant.accent()) iflySpeechSynthesizer?.setParameter(config.ent, forKey: "ent") iflySpeechSynthesizer?.setParameter("http://yuji.xf-yun.com/msp.do", forKey: "server_url") //设置本地引擎类型 iflySpeechSynthesizer?.setParameter(config.voiceID, forKey:IFlySpeechConstant.voice_ID()) // iflySpeechSynthesizer?.setParameter("http://yuji.xf-yun.com/msp.do", forKey: "server_url") iflySpeechSynthesizer?.setParameter(IFlySpeechConstant.type_LOCAL(), forKey: IFlySpeechConstant.engine_TYPE()) iflySpeechSynthesizer?.setParameter(config.speed, forKey: IFlySpeechConstant.speed()) iflySpeechSynthesizer?.setParameter(config.fileName, forKey: IFlySpeechConstant.tts_AUDIO_PATH()) iflySpeechSynthesizer?.setParameter(config.volume, forKey: IFlySpeechConstant.volume()) iflySpeechSynthesizer?.setParameter(config.pitch, forKey: IFlySpeechConstant.pitch()) iflySpeechSynthesizer?.setParameter(config.sampleRate, forKey: IFlySpeechConstant.sample_RATE()) iflySpeechSynthesizer?.setParameter(config.vcnName, forKey: IFlySpeechConstant.voice_NAME()) iflySpeechSynthesizer?.setParameter(config.unicode, forKey: IFlySpeechConstant.text_ENCODING()) // 设置发音人 iflySpeechSynthesizer?.setParameter(config.vcnName, forKey: IFlySpeechConstant.voice_NAME()) // NSString *newResPath = [[NSString alloc] initWithFormat:@"%@/aisound/common.jet;%@/aisound/xiaoyan.jet",resPath,resPath]; iflySpeechSynthesizer?.setParameter("\(config.next_text_len)", forKey: "next_text_len") iflySpeechSynthesizer?.setParameter("\(config.next_text)", forKey: "next_text") } /// 开始朗读 public func start(sentence:String) { let isSpeaking = iflySpeechSynthesizer?.isSpeaking if isSpeaking! { iflySpeechSynthesizer?.stopSpeaking() } // start之前设置的config有效 // setupSpeech() if sentence.count > 0 { speakText = sentence iflySpeechSynthesizer?.startSpeaking(sentence) } else { speakText = text iflySpeechSynthesizer?.startSpeaking(text) } } public func stop() { iflySpeechSynthesizer?.stopSpeaking() } /// 恢复朗读 public func resume() { iflySpeechSynthesizer?.resumeSpeaking() } /// 暂停朗读 public func pause() { iflySpeechSynthesizer?.pauseSpeaking() } /// 保存音频文件 public func saveUri() { let path = uriPath() iflySpeechSynthesizer?.delegate = self iflySpeechSynthesizer?.synthesize(text, toUri: path) } /// 是否正在语音 public func isSpeaking() ->Bool { return iflySpeechSynthesizer?.isSpeaking ?? false } /// 播放本地保存的音频文件 public func playUri() { let path = uriPath() let exist = FileManager.default.fileExists(atPath: path) if exist { // try? AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.playback) // try? AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.playback) if audioPlayer.isPlaying { audioPlayer.stop() } audioPlayer = PcmPlayer(filePath: path, sampleRate: Int(config.sampleRate) ?? 16000) audioPlayer.play() } } /// 返回当前保存音频文件的路径 public func uriPath() ->String { let path = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first ?? "" let uriPath = "\(path)/\(config.fileName)" return uriPath } /// 返回所有的发音人 // public func speaker() ->[String] { // return config.vcnNickNameArray // } /// 设置发音人 // public func voiceName(name:String) { // let names = speaker() // for speaker in names { // if speaker == name { // config.vcnName = name // setupSpeech() // break // } // } // } /// 设置发音人 public func setVcn(name:String) { config.vcnName = name } /// 语音合成引擎变更为离线 public func engineLocal() { config.engineType = "local" setupLocalSpeech() } /// 语音合成引擎变更为云端 public func engineCloud() { config.engineType = "cloud" setupSpeech() } /// 音量范围 0-100 public func volumeChange( volume:Int) { if volume >= 0 && volume <= 100 { config.volume = "\(volume)" setupSpeech() } } /// 语速范围 0-100 public func speedChange( speed:Int) { if speed >= 0 && speed <= 100 { config.speed = "\(speed)" setupSpeech() } } } extension VoiceBook:IFlySpeechSynthesizerDelegate { func onCompleted(_ error: IFlySpeechError!) { completeHandler?(error) } func onSpeakBegin() { beginHandler?(nil) } func onBufferProgress(_ progress: Int32, message msg: String!) { bufferProgress?(progress) } func onSpeakProgress(_ progress: Int32, beginPos: Int32, endPos: Int32) { speakProgress?(progress) } func onSpeakCancel() { speakCancel?(nil) } func onSpeakPaused() { speakPause?(nil) } func onSpeakResumed() { speakResume?(nil) } func onEvent(_ eventType: Int32, arg0: Int32, arg1: Int32, data eventData: Data!) { } } ================================================ FILE: zhuishushenqi/TXTReader/Speech/Model/iflyMSC.framework/Headers/IFlyAudioSession.h ================================================ // // IFlyAudioSession.h // MSCDemo // // Created by AlexHHC on 1/9/14. // // #import /** * 音频环境初始化,设置AVAudioSession的Category属性。 */ @interface IFlyAudioSession : NSObject /** * 初始化播音环境,主要用于合成播放器。 * * 此接口主要根据原来的音频环境,重新优化设置AVAudioSession的Category属性值。
* 若原来的Category属性值为AVAudioSessionCategoryPlayAndRecord,则添加AVAudioSessionCategoryOptionDefaultToSpeaker|AVAudioSessionCategoryOptionAllowBluetooth选项;若为其他Category属性值且isMPCenter为NO,则设置Category属性值为AVAudioSessionCategoryPlayback,选项为AVAudioSessionCategoryOptionMixWithOthers;若为其他Category属性值且isMPCenter为YES,则保持原来的设置,不做任何更改。 * * @param isMPCenter 是否初始化MPPlayerCenter:0不初始化,1初始化。此参数只在AVAudioSession的Category属性值不为AVAudioSessionCategoryPlayAndRecord时设置有效。 */ +(void) initPlayingAudioSession:(BOOL)isMPCenter; /** * 初始化录音环境,主要用于识别录音器。 * * 设置AVAudioSession的Category属性值为AVAudioSessionCategoryPlayAndRecord,选项为AVAudioSessionCategoryOptionDefaultToSpeaker|AVAudioSessionCategoryOptionAllowBluetooth。 * * @return 成功返回YES,失败返回NO */ +(BOOL) initRecordingAudioSession; @end ================================================ FILE: zhuishushenqi/TXTReader/Speech/Model/iflyMSC.framework/Headers/IFlyDataUploader.h ================================================ // // IFlyDataUploader.h // MSC // // Created by ypzhao on 13-4-8. // Copyright (c) 2013年 iflytek. All rights reserved. // #import @class IFlySpeechError; /*! * 数据上传类,主要用于上传语法文件或上传联系人、词表等个性化数据。 */ @interface IFlyDataUploader : NSObject /*! * 数据名称 */ @property(nonatomic,copy) NSString *dataName; /*! * 数据 */ @property(nonatomic,copy) NSString *data; /*! * 上传完成回调 * * @param result 结果 * @param error 错误码 */ typedef void(^IFlyUploadDataCompletionHandler)(NSString* result,IFlySpeechError * error); /*! * 上传数据 * 此函数用于上传数据,下载的过程是**异步**的。 * * @param completionHandler -[in] 上传完成回调 * @param name -[in] 上传的内容名称,名称最好和你要上传的数据内容相关,不可以为nil * @param data -[in] 上传的数据,以utf8编码,不可以为nil */ - (void) uploadDataWithCompletionHandler:(IFlyUploadDataCompletionHandler)completionHandler name:(NSString *)name data:(NSString *)data; /*! * 设置上传数据参数 * * @param parameter 参数值 * @param key 参数名 */ -(void) setParameter:(NSString*) parameter forKey:(NSString*) key; @end ================================================ FILE: zhuishushenqi/TXTReader/Speech/Model/iflyMSC.framework/Headers/IFlyDebugLog.h ================================================ // // IFlyDebugLog.h // MSC // description: 程序中的log处理类 // Created by ypzhao on 12-11-22. // Copyright (c) 2012年 iflytek. All rights reserved. // #import /*! * 调试信息 */ @interface IFlyDebugLog : NSObject /*! * 打印调试信息 * * @param format -[in] 要打印的内容格式 * @param ... -[in] 要打印的内容 */ + (void) showLog:(NSString *)format, ...; /*! * 将log写入文件中 */ + (void) writeLog; /*! * 设置是否显示log * * @param showLog YES:显示;NO:不显示 */ + (void) setShowLog:(BOOL) showLog; @end ================================================ FILE: zhuishushenqi/TXTReader/Speech/Model/iflyMSC.framework/Headers/IFlyISVDelegate.h ================================================ // // IFlyISVDelegate.h // msc_UI // // Created by admin on 14-9-15. // Copyright (c) 2014年 iflytek. All rights reserved. // #import @class IFlySpeechError; /*! * 声纹回调协议 */ @protocol IFlyISVDelegate /*! * 声纹结果回调 * * @param dic 结果 */ -(void) onResult:(NSDictionary *)dic; /*! * 错误码回调 * * @param errorCode 错误码 */ -(void) onCompleted:(IFlySpeechError *) errorCode; @optional /*! * 等待结果 */ -(void) onRecognition; /*! * 音量改变回调 * * @param volume 音量值 */ -(void) onVolumeChanged: (int)volume; @end ================================================ FILE: zhuishushenqi/TXTReader/Speech/Model/iflyMSC.framework/Headers/IFlyISVRecognizer.h ================================================ // // IFlyISVRecognizer.h // ISV // // Created by wangdan on 14-9-6. // Copyright (c) 2014年 IFlyTEK. All rights reserved. // #import #import "IFlyISVDelegate.h" /** * 声纹接口类 */ @interface IFlyISVRecognizer : NSObject { } /*! * The delegate of FlyISVRecognizer responsing to IFlyISVDelegate. */ @property (assign) id delegate; /*! * FlyISVRecognizer is a kind of Singleton calss.The function can be used as below:
* IFLyISVRecognizer *recognizer=[IFlyISVRecognizer creteRecognizer: self]; */ +(instancetype) sharedInstance; /*! * Genrerate a serial number password
* Princeple:
* 1.Number serial has no 1 in itself;
* 2.The nuber serial has no same number("98765432"is right while "99876543" is wrong) * * @param length the serial number's length,length of "98765432" is 8,generally length is 8 and other value is forbidden */ -(NSString*) generatePassword:(int)length; /*! * Used to get password from server * * @param pwdt when pwdt is 1,the function will return chinese text;while pwdt is 2, the funciton will return number serial */ -(NSArray*) getPasswordList:(int)pwdt; /*! * Used to judge if the engine is running in listenning * * @return YES: the engine is listenning;
No : the engine is not listenning */ -(BOOL) isListening; /*! * Used to query or delete the voiceprint model in server * * @param cmd "del": delete model;
"que": query model; * @param authid: user id ,can be @"tianxia" or other; * @param pwdt voiceprint type
* 1: fixed txt voiceprint code ,like @"我的地盘我做主";
* 2: free voiceprint code , user can speek anything,but 5 times trainning the speech shall be same;
* 3: number serial voiceprint code ,like @"98765432" and so on. * @param ptxt voiceprint txt,only fixed voiceprint and number serial have this,in free voiceprint model this param shall be set nil. * @param vid another voiceprint type model,user can use this to query or delete model in server can be @"jakillasdfasdjjjlajlsdfhdfdsadff",totally 32 bits;
* NOTES:
* when vid is not nil,then the server will judge the vid first; while the vid is nil, server can still query or delete the voiceprint model by other params. */ -(BOOL) sendRequest:(NSString*)cmd authid:(NSString *)auth_id pwdt:(int)pwdt ptxt:(NSString *)ptxt vid:(NSString *)vid err:(int *)err; /*! * Set the voiceprint params * * | key | value | * |:---------------:|:-------------------------------------------------:| * | sst | @"train" or @"verify" | * | auth_id | @"tianxia" or other | * | sub | @"ivp" | * | ptxt | | * | rgn | @"5" | * | pwdt | @"1",or @"2", or @"3" | * | auf | @"audio/L16;rate=16000" or @"audio/L16;rate=8000" | * | vad_enable | @"1" or @"0" | * | vad_timeout | @"3000" | * | vad_speech_tail | @"100" | * * @param value 参数值 * @param key 参数类型 * * @return 设置成功返回YES,失败返回NO */ -(BOOL) setParameter:(NSString *)value forKey:(NSString *)key; /*! * Get the voiceprint params used the same as function of setParameter */ -(NSString*) getParameter:(NSString *)key; /*! * Start recording */ -(void) startListening; /*! * Stop recording */ -(void) stopListening; /*! * Cancel recording,like function stopListening */ -(void) cancel; /* cancel recognization */ @end ================================================ FILE: zhuishushenqi/TXTReader/Speech/Model/iflyMSC.framework/Headers/IFlyMSC.h ================================================ // // IFlyMSC.h // msc // // Created by 张剑 on 15/1/14. // Copyright (c) 2015年 iflytek. All rights reserved. // #ifndef MSC_IFlyMSC_h #define MSC_IFlyMSC_h #import "IFlyAudioSession.h" #import "IFlyDataUploader.h" #import "IFlyDebugLog.h" #import "IFlyISVDelegate.h" #import "IFlyISVRecognizer.h" #import "IFlyRecognizerView.h" #import "IFlyRecognizerViewDelegate.h" #import "IFlyResourceUtil.h" #import "IFlySetting.h" #import "IFlySpeechConstant.h" #import "IFlySpeechError.h" #import "IFlySpeechEvaluator.h" #import "IFlySpeechEvaluatorDelegate.h" #import "IFlySpeechEvent.h" #import "IFlySpeechRecognizer.h" #import "IFlySpeechRecognizerDelegate.h" #import "IFlySpeechSynthesizer.h" #import "IFlySpeechSynthesizerDelegate.h" #import "IFlySpeechUtility.h" #import "IFlyUserWords.h" #import "IFlyPcmRecorder.h" #import "IFlyVoiceWakeuper.h" #import "IFlyVoiceWakeuperDelegate.h" #endif ================================================ FILE: zhuishushenqi/TXTReader/Speech/Model/iflyMSC.framework/Headers/IFlyPcmRecorder.h ================================================ // // IFlyPcmRecorder.h // MSC // description: // Created by ypzhao on 12-11-15. // Copyright (c) 2012年 iflytek. All rights reserved. // #import #import #import #import #import #import @class IFlyPcmRecorder; /*! * 录音协议 */ @protocol IFlyPcmRecorderDelegate /*! * 回调音频数据 * * @param buffer 音频数据 * @param size 表示音频的长度 */ - (void) onIFlyRecorderBuffer: (const void *)buffer bufferSize:(int)size; /*! * 回调音频的错误码 * * @param recoder 录音器 * @param error 错误码 */ - (void) onIFlyRecorderError:(IFlyPcmRecorder*)recoder theError:(int) error; @optional /*! * 回调录音音量 * * @param power 音量值 */ - (void) onIFlyRecorderVolumeChanged:(int) power; @end /*! * 录音器控件 */ @interface IFlyPcmRecorder : NSObject /*! * 录音委托对象 */ @property (nonatomic,assign) id delegate; /*! * 用于设置是否在录音结束后发送Deactive通知,默认是YES:发送 */ @property (nonatomic,assign) BOOL isNeedDeActive; /*! * 单例模式 * * @return 返回录音对象单例 */ + (instancetype) sharedInstance; /*! * 开始录音 * * @return 开启录音成功返回YES,否则返回NO */ - (BOOL) start; /*! * 停止录音 */ - (void) stop; /*! * 设置音频采样率 * * @param rate -[in] 采样率,8k/16k */ - (void) setSample:(NSString *) rate; /*! * 设置录音音量回调时间间隔参数 */ - (void) setPowerCycle:(float) cycle; /*! * 保存录音 * * @param savePath 音频保存路径 */ -(void) setSaveAudioPath:(NSString *)savePath; /*! * 录音器是否完成 * * @return 录音器完全结束返回YES,否则返回NO */ -(BOOL) isCompleted; @end ================================================ FILE: zhuishushenqi/TXTReader/Speech/Model/iflyMSC.framework/Headers/IFlyRecognizerView.h ================================================ // // IFlyRecognizerView.h // MSC // // Created by admin on 13-4-16. // Copyright (c) 2013年 iflytek. All rights reserved. // #import @protocol IFlyRecognizerViewDelegate ; /*! * 语音识别控件
* 录音时触摸控件结束录音,开始识别(相当于旧版的停止);触摸其他位置,取消录音,结束会话(取消)
* 出错时触摸控件,重新开启会话(相当于旧版的再说一次);触摸其他位置,取消录音,结束会话(取消) * */ @interface IFlyRecognizerView : UIView /*! * 设置委托对象 */ @property(nonatomic,assign)id delegate; /*! * 初始化控件 * * @param origin 控件左上角的坐标 * * @return IFlyRecognizerView 对象 */ - (id)initWithOrigin:(CGPoint)origin; /*! * 初始化控件 * * @param center 控件中心的坐标 * * @return IFlyRecognizerView 对象 */ - (id) initWithCenter:(CGPoint)center; /*! * 设置横竖屏自适应 * * @param autoRotate 默认值YES,横竖屏自适应 */ - (void) setAutoRotate:(BOOL)autoRotate; /* * | ------------- |----------------------------------------------------------- * | 参数 | 描述 * | ------------- |----------------------------------------------------------- * | domain |应用的领域: 取值为:iat、search、video、poi、music、asr; * | | iat:普通文本听写; * | | search:热词搜索; * | | video:视频音乐搜索; * | | asr:关键词识别; * | ------------- |----------------------------------------------------------- * | vad_bos |前端点检测: 静音超时时间,即用户多长时间不说话则当做超时处理; 单位:ms; * | | engine指定iat识别默认值为5000; * | | 其他情况默认值为 4000,范围 0-10000。 * | ------------- |----------------------------------------------------------- * | vad_eos |后断点检测: 后端点静音检测时间,即用户停止说话多长时间内即认为不再输入, * | | 自动停止录音;单位:ms; * | | sms 识别默认值为 1800; * | | 其他默认值为 700,范围 0-10000。 * | ------------- |----------------------------------------------------------- * | sample_rate |采样率:目前支持的采样率设置有 16000 和 8000。 * | ------------- |----------------------------------------------------------- * | asr_ptt |标点符号设置: 默认为 1,当设置为 0 时,将返回无标点符号文本。 * | ------------- |----------------------------------------------------------- * | result_type |返回结果的数据格式: 可设置为json,xml,plain,默认为json。 * | ------------- |----------------------------------------------------------- * | grammarID |识别的语法id: 只针对 domain 设置为”asr”的应用。 * | ------------- |----------------------------------------------------------- * | asr_audio_path|音频文件名: 设置此参数后,将会自动保存识别的录音文件。 * | | 路径为Documents/(指定值)。 * | | 不设置或者设置为nil,则不保存音频。 * | ------------- |----------------------------------------------------------- * | params |扩展参数: 对于一些特殊的参数可在此设置,一般用于设置语义。 * | ------------- |----------------------------------------------------------- * */ /*! * 设置识别引擎的参数 * * 识别的引擎参数(key)取值如下:
* * | 参数 | 描述 | * |-----------------|-------------------------------------------------------| * | domain | 应用的领域: 取值为:iat、search、video、poi、music、asr;
iat:普通文本听写;
search:热词搜索;
video:视频音乐搜索;
asr:关键词识别;| * | vad_bos | 前端点检测: 静音超时时间,即用户多长时间不说话则当做超时处理; 单位:ms;
engine指定iat识别默认值为5000;
其他情况默认值为 4000,范围 0-10000。| * | vad_eos | 后断点检测: 后端点静音检测时间,即用户停止说话多长时间内即认为不再输入,自动停止录音;单位:ms;
sms 识别默认值为 1800;
其他默认值为 700,范围 0-10000。| * | sample_rate | 采样率:目前支持的采样率设置有 16000 和 8000。| * | asr_ptt | 标点符号设置: 默认为 1,当设置为 0 时,将返回无标点符号文本。| * | result_type | 返回结果的数据格式: 可设置为json,xml,plain,默认为json。| * | grammarID | 识别的语法id: 只针对 domain 设置为”asr”的应用。| * | asr_audio_path | 音频文件名: 设置此参数后,将会自动保存识别的录音文件。
路径为Documents/(指定值)。
不设置或者设置为nil,则不保存音频。| * | params | 扩展参数: 对于一些特殊的参数可在此设置,一般用于设置语义。| * * @param value 参数对应的取值 * @param key 识别引擎参数 * * @return 成功返回YES;失败返回NO */ -(BOOL) setParameter:(NSString *) value forKey:(NSString*)key; /*! * 获取识别引擎参数 * * @param key 参数key * * @return 参数值 */ -(NSString*) parameterForKey:(NSString *)key; /*! * 开始识别 * * @return 成功返回YES;失败返回NO */ - (BOOL)start; /*! * 取消本次识别 */ - (void)cancel; @end ================================================ FILE: zhuishushenqi/TXTReader/Speech/Model/iflyMSC.framework/Headers/IFlyRecognizerViewDelegate.h ================================================ // // IFlyRecognizerDelegate.h // MSC // // Created by admin on 13-4-16. // Copyright (c) 2013年 iflytek. All rights reserved. // #import @class IFlyRecognizerView; @class IFlySpeechError; /*! * 识别回调委托 */ @protocol IFlyRecognizerViewDelegate /*! * 回调返回识别结果 * * @param resultArray 识别结果,NSArray的第一个元素为NSDictionary,NSDictionary的key为识别结果,sc为识别结果的置信度 * @param isLast -[out] 是否最后一个结果 */ - (void)onResult:(NSArray *)resultArray isLast:(BOOL) isLast; /*! * 识别结束回调 * * @param error 识别结束错误码 */ - (void)onCompleted: (IFlySpeechError *) error; @optional @end ================================================ FILE: zhuishushenqi/TXTReader/Speech/Model/iflyMSC.framework/Headers/IFlyResourceUtil.h ================================================ // // IFlyResourceUtil.h // MSCDemo // // Created by admin on 14-6-20. // Copyright (c) 2014年 iflytek. All rights reserved. // #import /*! * 资源工具类 */ @interface IFlyResourceUtil : NSObject /*! * 获取通过MSPSetParam,启动引擎的标识 * * @return 通过MSPSetParam,启动引擎的标识 */ +(NSString*) ENGINE_START; /*! * 获取通过MSPSetParam,销毁引擎的标识 * * @return 通过MSPSetParam,销毁引擎的标识 */ +(NSString*) ENGINE_DESTROY; /*! * 获取识别引擎的资源目录标识 * * @return 识别引擎的资源目录标识 */ +(NSString*) ASR_RES_PATH; /*! * 得到语法构建目录 * * @return 语法构建目录 */ +(NSString*) GRM_BUILD_PATH; /*! * 获取合成引擎的资源目录标识,同时需要先传入voice_name方可生效 * * @return 合成引擎的资源目录标识,同时需要先传入voice_name方可生效 */ +(NSString*) TTS_RES_PATH; /*! * 获取唤醒资源的资源目录标识 * * @return 唤醒资源的资源目录标识 */ +(NSString*) IVW_RES_PATH; /*! * 语法类型 * * @return 语法类型 */ +(NSString*) GRAMMARTYPE; /*! * 语记SDK专用参数,用于设置本地默认资源路径 * * @return 本地默认资源路径key字符串 */ +(NSString*) PLUS_LOCAL_DEFAULT_RES_PATH; #pragma mark - /*! * 资源存放路径 * * @param path 设置的路径 * * @return 资源目录 */ +(NSString*) generateResourcePath:(NSString *)path; /** * 获得离线发音人对应的id * * @param voiceName 发音人名称 * * @return 有,发音人对应的id;无,返回nil */ +(NSString*) identifierForVoiceName:(NSString*)voiceName; @end ================================================ FILE: zhuishushenqi/TXTReader/Speech/Model/iflyMSC.framework/Headers/IFlySetting.h ================================================ // // IFlySetting.h // MSC // // Created by iflytek on 13-4-12. // Copyright (c) 2013年 iflytek. All rights reserved. // #import /*! * 日志打印等级 */ typedef NS_OPTIONS(NSInteger, LOG_LEVEL){ /*! * 全部打印 */ LVL_ALL = -1, /*! * 高,异常分析需要的级别 */ LVL_DETAIL = 31, /*! * 中,打印基本日志信息 */ LVL_NORMAL = 15, /*! * 低,只打印主要日志信息 */ LVL_LOW = 7, /*! * 不打印 */ LVL_NONE = 0 }; /*! * 此接口为iflyMSC sdk 配置接口。
* 可以获取版本号,设置日志打印等级等 */ @interface IFlySetting : NSObject /*! * 获取版本号 * * @return 版本号 */ + (NSString *) getVersion; /*! * 获取日志等级 * * @return 返回日志等级 */ + (LOG_LEVEL) logLvl; /*! * 是否打印控制台log
* 在软件发布时,建议关闭此log。 * * @param showLog -[in] YES,打印log;NO,不打印 */ + (void) showLogcat:(BOOL) showLog; /*! * 设置日志msc.log生成路径以及日志等级 * * | 日志打印等级 | 描述 | * |------------------------|-----------------------------------| * | LVL_ALL | 全部打印 | * | LVL_DETAIL | 高,异常分析需要的级别 | * | LVL_NORMAL | 中,打印基本日志信息 | * | LVL_LOW | 低,只打印主要日志信息 | * | LVL_NONE | 不打印 | * * @param level -[in] 日志打印等级 */ + (void) setLogFile:(LOG_LEVEL) level; /*! * 设置日志文件的路径
* 日志文件默认存放在Documents目录。 * * @param path -[in] 日志文件的全路径 */ + (void) setLogFilePath:(NSString*) path; @end ================================================ FILE: zhuishushenqi/TXTReader/Speech/Model/iflyMSC.framework/Headers/IFlySpeechConstant.h ================================================ // // IFlySpeechConstant.h // MSCDemo // // Created by iflytek on 5/9/14. // Copyright (c) 2014 iflytek. All rights reserved. // #import /*! * 公共常量类
* 主要定义参数的key value值 */ @interface IFlySpeechConstant : NSObject #pragma mark - 通用参数key /*! * 语音应用ID
* 通过开发者网站申请 * * @return 语音应用IDkey */ +(NSString*)APPID; /*! * 语言区域。 * * @return 语言区域key。 */ +(NSString*)ACCENT; /*! * 语言区域。 * * @return 普通话value。 */ +(NSString*)ACCENT_MANDARIN; /*! * 语言区域。 * * @return 河南话value。 */ +(NSString*)ACCENT_HENANESE; /*! * 语言区域。 * * @return 四川话value。 */ +(NSString*)ACCENT_SICHUANESE; /*! * 语言区域。 * * @return 粤语value。 */ +(NSString*)ACCENT_CANTONESE; /*! * 语言
* 支持:zh_cn,zh_tw,en_us
* * @return 语言key */ +(NSString*)LANGUAGE; /*! * 语言 * * @return 中文value */ +(NSString*)LANGUAGE_CHINESE; /*! * 语言 * * @return 中文台湾value */ +(NSString*)LANGUAGE_CHINESE_TW; /*! * 语言 * * @return 英文value */ +(NSString*)LANGUAGE_ENGLISH; /*! * 返回结果的数据格式,可设置为json,xml,plain,默认为json。 * * @return 返回结果的数据格式key */ +(NSString*)RESULT_TYPE; /*! * 应用领域。 * * @return 应用领域key */ +(NSString*)IFLY_DOMAIN; /*! * 个性化数据上传类型 * * @return 个性化数据上传类型key */ +(NSString*)DATA_TYPE; /*! * 语音输入超时时间
* 单位:ms,默认30000 * * @return 语音输入超时时间key */ +(NSString*)SPEECH_TIMEOUT; /*! * 网络连接超时时间
* 单位:ms,默认20000 * * @return 网络连接超时时间key */ +(NSString*)NET_TIMEOUT; /*! * 业务类型。 * * @return 业务类型key。 */ +(NSString*)SUBJECT; /*! * 扩展参数。 * * @return 扩展参数key。 */ +(NSString*)PARAMS; /*! * 加密参数 * * 支持类型:ssl 加密 tcp 非加密 默认:tcp
* 建议对安全性要求较高时使用ssl。 * * @return 加密参数key */ +(NSString*)PROT_TYPE; /*! * ssl证书内容 * * @return ssl证书内容key */ +(NSString*)SSL_CERT; /*! * 录音音量返回时间间隔。 * * @return 间隔key。 */ +(NSString*)POWER_CYCLE; /*! * 合成、识别、唤醒、评测、声纹等业务采样率。 * * @return 合成及识别采样率key。 */ +(NSString*)SAMPLE_RATE; /*! * 合成、识别、唤醒、声纹等业务采样率。 * * @return 合成及识别采样率8K Value。 */ +(NSString*)SAMPLE_RATE_8K; /*! * 合成、识别、唤醒、评测、声纹等业务采样率。 * * @return 合成及识别采样率16K Value。 */ +(NSString*)SAMPLE_RATE_16K; /*! * 引擎类型。
* 可选:local,cloud,auto
* 默认:auto * * @return 引擎类型key。 */ +(NSString*)ENGINE_TYPE; /*! * 本地识别引擎。 * * @return 本地识别引擎value。 */ +(NSString*)TYPE_LOCAL; /*! * 云端识别引擎。 * * @return 云端识别引擎value。 */ +(NSString*)TYPE_CLOUD; /*! * 混合识别引擎。 * * @return 混合识别引擎value。 */ +(NSString*)TYPE_MIX; /*! * 引擎根据当前配置进行选择。 * * @return 引擎根据当前配置进行选择value。 */ +(NSString*)TYPE_AUTO; /*! * 输入文本编码格式。 * * @return 编码格式key。 */ +(NSString*)TEXT_ENCODING; /*! * 结果编码格式。 * * @return 结果编码格式key。 */ +(NSString*)RESULT_ENCODING; /*! * 是否初始化播放器
* SDK内部播放器采用音频队列实现,有部分外部需求需要自定义音频队列,可以通过此开关控制
* 0:不初始化,非0或者参数为空:初始化,默认初始化 * * @return 是否初始化播放器参数key */ +(NSString*)PLAYER_INIT; /*! * 是否播放器结束后发送deactive系统通知
* SDK内部播放器结束后可通过此开关发送deactive系统通知,使其他被中断的音频应用解除中断
* 0:不发送,非0或者参数为空:发送,默认发送 * * @return 是否播放器结束后发送deactive系统通知参数key */ +(NSString*)PLAYER_DEACTIVE; /** * 是否初始化录音器
* SDK内部录音器采用音频队列实现,有部分外部需求需要自定义音频队列,可以通过此开关控制
* 0:不初始化,非0或者参数为空:初始化,默认初始化 * * @return 是否初始化录音器参数key */ +(NSString*)RECORDER_INIT; /** * 是否录音器结束后发送deactive系统通知
* SDK内部录音器结束后可通过此开关发送deactive系统通知,使其他被中断的音频应用解除中断
* 0:不发送,非0或者参数为空:发送,默认发送 * * @return 是否录音器结束后发送deactive系统通知参数key */ +(NSString*)RECORDER_DEACTIVE; #pragma mark - 合成相关设置key /*! * 语速
* 范围 (0~100) 默认值:50 * * @return 语速key */ +(NSString*)SPEED; /*! * 音调
* 范围(0~100)默认值:50 * * @return 音调key */ +(NSString*)PITCH; /*! * 合成录音保存路径 * * 注意:只需要设置文件名则可,会自动拼接到[IFlySetting setLogFilePath]接口设置的目录后 * * @return 合成录音保存路径key */ +(NSString*)TTS_AUDIO_PATH; /** * 启用VAD功能 * * @return 启用VAD功能key */ +(NSString*)VAD_ENABLE; /*! * VAD前端点超时
* 范围:0-10000(单位ms) * * @return VAD前端点超时key */ +(NSString*)VAD_BOS; /*! * VAD后端点超时。
* 可选范围:0-10000(单位ms) * * @return VAD后端点超时key */ +(NSString*)VAD_EOS; /* * 云端支持如下发音人: * 对于网络TTS的发音人角色,不同引擎类型支持的发音人不同,使用中请注意选择。 * * |--------|----------------| * | 发音人 | 参数 | * |--------|----------------| * | 小燕 | xiaoyan | * |--------|----------------| * | 小宇 | xiaoyu | * |--------|----------------| * | 凯瑟琳 | catherine | * |--------|----------------| * | 亨利 | henry | * |--------|----------------| * | 玛丽 | vimary | * |--------|----------------| * | 小研 | vixy | * |--------|----------------| * | 小琪 | vixq | * |--------|----------------| * | 小峰 | vixf | * |--------|----------------| * | 小梅 | vixl | * |--------|----------------| * | 小莉 | vixq | * |--------|----------------| * | 小蓉 | vixr | * |--------|----------------| * | 小芸 | vixyun | * |--------|----------------| * | 小坤 | vixk | * |--------|----------------| * | 小强 | vixqa | * |--------|----------------| * | 小莹 | vixyin | * |--------|----------------| * | 小新 | vixx | * |--------|----------------| * | 楠楠 | vinn | * |--------|----------------| * | 老孙 | vils | * |--------|----------------| */ /*! * 发音人 * * 云端支持如下发音人:
* 对于网络TTS的发音人角色,不同引擎类型支持的发音人不同,使用中请注意选择。
* * | 发音人 | 参数 | * |:--------:|:----------------:| * | 小燕 | xiaoyan | * | 小宇 | xiaoyu | * | 凯瑟琳 | catherine | * | 亨利 | henry | * | 玛丽 | vimary | * | 小研 | vixy | * | 小琪 | vixq | * | 小峰 | vixf | * | 小梅 | vixl | * | 小莉 | vixq | * | 小蓉 | vixr | * | 小芸 | vixyun | * | 小坤 | vixk | * | 小强 | vixqa | * | 小莹 | vixyin | * | 小新 | vixx | * | 楠楠 | vinn | * | 老孙 | vils | * * @return 发音人key */ +(NSString*)VOICE_NAME; /*! * 发音人ID key。 * * @return 发音人ID key */ +(NSString*)VOICE_ID; /*! * 发音人语种 key。 * * 参数值:0:Auto 1:中文 2英文 ,默认 0. * * @return 发音人ID key */ +(NSString*)VOICE_LANG; /*! * 音量
* 范围(0~100) 默认值:50 * * @return 音量key */ +(NSString*)VOLUME ; /*! * 合成音频播放缓冲时间
* 即缓冲多少秒音频后开始播放,如tts_buffer_time=1000;
* 默认缓冲1000ms毫秒后播放。 * * @return 合成音频播放缓冲时间缓冲时间key */ +(NSString*)TTS_BUFFER_TIME ; /*! * 合成数据是否即时返回 * * 是否需要数据回调,为1时,当合成一段音频会通过onEvent回调返回,直接合成结束;
* 设置为1为即时返回;0为非即时返回;默认值为0; * * @return 合成数据即时返回key */ +(NSString*)TTS_DATA_NOTIFY; /*! * 预合成文本 * * @return 预合成文本参数key */ +(NSString*)NEXT_TEXT; /*! * 是否需要打开MPPlayingInfocenter
* 是否需要初始化MPPlayerCenter的属性;0:需要初始化,1:不初始化 * * @return 是否需要打开MPPlayingInfocenter 参数key */ +(NSString*)MPPLAYINGINFOCENTER; #pragma mark - 识别、听写、语义相关设置key /*! * 录音源
* 录音时的录音方式,默认为麦克风,设置为1;
* 如果需要外部送入音频,设置为-1,通过WriteAudio接口送入音频。 * * @return 录音源key */ +(NSString*)AUDIO_SOURCE; /*! * 识别录音保存路径 * * @return 识别录音保存路径key */ +(NSString*) ASR_AUDIO_PATH; /*! * 设置是否开启语义 * * @return 设置是否开启语义key */ +(NSString*)ASR_SCH; /*! * 设置是否有标点符号 * * @return 设置是否有标点符号key */ +(NSString*)ASR_PTT; /*! * ASR_PTT 参数值:设置带标点符号 * * @return 设置是有标点符号Value */ +(NSString*)ASR_PTT_HAVEDOT; /*! * ASR_PTT 参数值:设置不带标点符号 * * @return 设置是无标点符号Value */ +(NSString*)ASR_PTT_NODOT; /*! * 本地语法名称。
* 本地语法名称,对应云端的有CLOUD_GRAMMAR * * @return 本地语法名称key。 */ +(NSString*)LOCAL_GRAMMAR; /*! * 云端语法ID。
* 云端编译语法返回的表示,早期版本使用GRAMMAR_ID,仍然兼容,但建议使用新的。 * * @return 云端语法ID key。 */ +(NSString*)CLOUD_GRAMMAR; /*! * 语法类型 * * @return 语法类型key */ +(NSString*)GRAMMAR_TYPE; /*! * 语法内容。 * * @return 语法内容key。 */ +(NSString*)GRAMMAR_CONTENT; /*! * 字典内容。 * * @return 字典内容key。 */ +(NSString*)LEXICON_CONTENT; /*! * 字典名字。 * * @return 字典名字key。 */ +(NSString*)LEXICON_NAME; /*! * 语法名称列表。 * * @return 语法名称列表key。 */ +(NSString*)GRAMMAR_LIST; /*! * 开放语义协议版本号。
* 如需使用请在http://osp.voicecloud.cn/上进行业务配置 * * @return 开放语义协议版本号key。 */ +(NSString*)NLP_VERSION; #pragma mark - 唤醒相关设置key /*! * 唤醒门限值。 * * @return 唤醒门限值key。 */ +(NSString*)IVW_THRESHOLD; /*! * 唤醒服务类型。 * * @return 唤醒服务类型key。 */ +(NSString*)IVW_SST; /*! * 唤醒+识别。 * * @return 唤醒+识别key。 */ +(NSString*)IVW_ONESHOT; /*! * 唤醒工作方式
* 1:表示唤醒成功后继续录音,0:表示唤醒成功后停止录音。 * * @return 唤醒工作方式key */ +(NSString*)KEEP_ALIVE; /*! * 唤醒录音保存路径 * * @return 唤醒录音保存路径key */ +(NSString*) IVW_AUDIO_PATH; #pragma mark - 评测相关设置key /*! * 评测类型
* 可选值:read_syllable(英文评测不支持):单字;read_word:词语;read_sentence:句子;read_chapter(待开放):篇章。 * * @return 评测类型 key */ +(NSString*)ISE_CATEGORY; /*! * 评测结果等级
* 可选值:complete:完整 ;plain:简单 * * @return 评测结果等级 key */ +(NSString*)ISE_RESULT_LEVEL; /*! * 评测结果格式
* 可选值:xml;plain * * @return 评测结果格式 key */ +(NSString*)ISE_RESULT_TYPE; /*! * 评测录音保存路径 * * @return 评测录音保存路径key */ +(NSString*) ISE_AUDIO_PATH; /*! * 朗读跟踪,只对句子和篇章有效
* 可选值:enable:开启;disable:关闭。 * * @return 朗读跟踪 key */ +(NSString*)ISE_AUTO_TRACKING; /*! * 跟踪模式
* 可选值:easy:简单;hard:复杂。 * * @return 跟踪模式 key */ +(NSString*)ISE_TRACK_TYPE; #pragma mark - 语记SDK业务key /*! * 本地所有资源 * * @return 本地所有资源key */ + (NSString *)PLUS_LOCAL_ALL; /*! * 本地合成资源 * * @return 本地合成资源key */ + (NSString *)PLUS_LOCAL_TTS; /*! * 本地识别资源 * * @return 本地识别资源key */ + (NSString *)PLUS_LOCAL_ASR; /*! * 本地唤醒资源 * * @return 本地唤醒资源key */ + (NSString *)PLUS_LOCAL_IVW; #pragma mark - 身份验证业务key /*! * auth_id
* 用于用户注册和登录、查询、删除等业务时标识用户身份 * * @return 用户标识 */ + (NSString*)MFV_AUTH_ID; /*! * 请求业务类型,可选值:mfv(默认,融合验证),ivp(声纹),ifr(人脸) * * @return 请求业务类型key */ + (NSString*)MFV_SUB; /*! * 会话类型,不同sub有不同的sst取值。
* ifr:enroll,verify,identify,reenroll,query,delete
* ivp:enroll(train),verify,reenroll,query,delete,download * * @return 会话类型key */ + (NSString*)MFV_SST; /*! * 融合验证模式,仅在融合验证场景下使用。可选值:sin(单一生物特征数据验证),mix(混合生物特征数据验证),agi(灵活生物特征数据验证) * * @return 融合验证模式key */ + (NSString*)MFV_VCM; /*! * 特征场景,用来说明本次验证将涉及的业务。可选值:ivp,ifr,ivp|ifr * * @return 特征场景 key */ + (NSString*)MFV_SCENES; /*! * 确认周期(affirmance cycle,单位:s),用户设置的确认超时时间(生命周期),仅在灵活融合验证场景下使用 * * @return 确认周期key */ + (NSString*)MFV_AFC; /*! * 数据保存路径 * * @return 数据保存路径key */ + (NSString*)MFV_DATA_PATH; /*! * 训练次数:取值2~9.无默认值,必须明确指定。 * * @return 训练次数key */ + (NSString*)MFV_RGN; /*! * 声纹确认门限值,验证得分>=tsd验证通过,否则验证失败(该参数目前不支持,作为保留参数。)却只范围:0~100. * * @return 声纹确认门限值key */ + (NSString*)MFV_TSD; /*! * 密码文本。从服务端下载,比如数字密码所需要的数字串。 * * @return 密码文本key */ + (NSString*)MFV_PTXT; /*! * 密码类型。取值:1(文本密码),2(自由说),3(数字密码). * * @return 密码类型key */ + (NSString*)MFV_PWDT; /*! * 取消注册。取值:0(不取消,即不生效),1(取消本次注册). * * @return 取消注册key */ + (NSString*)MFV_FIN; /*! * 等待超时时间:描述客户端等待结果的超时时间 * * @return 等待超时时间:key */ + (NSString*)MFV_WTT; /*! * 数据格式
* 声纹为音频采样率支持:16000和8000;人脸为图片格式,支持jpg和gif * * @return 数据格式key */ + (NSString*)MFV_DATA_FORMAT; /*! * 数据压缩编码
* 声纹为;人脸支持raw,不对图片压缩 * * @return 数据压缩编码key */ + (NSString*)MFV_DATA_ENCODING; #pragma mark - 人脸业务key //1. sub 取值: wfr 用途: 用于区分业务类型,web访问方式中,nginx配置不用使用,但是在结构化日志和染色日志记录中使用。 //2. sst 取值: reg、verify、detect、align 用途: 指定本路会话是属于何种性质 // + 人脸图像注册(reg):上传图像,验证图像的有效性,然后存储起来,作为数据源。 // + 人脸图像验证(verify):通过与指定源图像比较,验证人脸相似性。 // + 人脸图像检测(detect):能够检测出不同姿态方位的人脸在图中的位置。 // + 人脸图像聚焦(align):在给定人脸框下自动标定出两眼、鼻尖、嘴角的坐标。 //3. aue 取值: raw 用途: 图像压缩格式,现在引擎不支持图像压缩,aue只能取值raw //4. pset 取值: 整数 用途: 人脸识别验证阈值,取值可以是负数也可以是整数。 //5. skip 取值: true/false 用途: 后台图片处理是否进行过滤。true表示不过滤,false表示过滤 //6. gid 取值: *********** 用途: 图像模型id,如:4a6c124ed6b78436ee5aac4563f13eb5 //7. appid 取值:用户申请的appid 用途: 验证用户 /*! * sub 默认值:wfr
* 用于区分业务类型,web访问方式中,nginx配置不用使用,但是在结构化日志和染色日志记录中使用。 */ + (NSString*) FACE_SUB; /*! * WFR
* sub参数的默认值 */ + (NSString*) FACE_WFR; /*! * sst
* 指定本路会话是属于何种性质 */ + (NSString*) FACE_SST; /*! * REG
* 人脸图像注册(reg):上传图像,验证图像的有效性,然后存储起来,作为数据源。 */ + (NSString*) FACE_REG; /*! * VERIFY
* 人脸图像验证(verify):通过与指定源图像比较,验证人脸相似性。 */ + (NSString*) FACE_VERIFY; /*! * DETECT
* 人脸图像检测(detect):能够检测出不同姿态方位的人脸在图中的位置。 */ + (NSString*) FACE_DETECT; /*! * ALIGN
* 人脸图像聚焦(align):在给定人脸框下自动标定出两眼、鼻尖、嘴角的坐标。 */ + (NSString*) FACE_ALIGN; /*! * ATTR
* 面部属性识别(attr):对面部属性进行识别:例如秃顶、刘海、大嘴、模糊、眼镜等。 */ + (NSString*) FACE_ATTR; /*! * AUE
* 图像压缩格式,现在引擎不支持图像压缩,aue只能取值raw */ + (NSString*) FACE_AUE; /*! * RAW
* AUE参数的值 */ + (NSString*) FACE_RAW; /*! * PSET
* 人脸识别验证阈值,取值可以是负数也可以是整数。 */ + (NSString*) FACE_PSET; /*! * SKIP
* 后台图片处理是否进行过滤。true表示不过滤,false表示过滤,传入字符串@“true”或@“false” */ + (NSString*) FACE_SKIP; /*! * GID
* 图像模型id,如:4a6c124ed6b78436ee5aac4563f13eb5 */ + (NSString*) FACE_GID; /*! * auth_id
* 用于用户注册和登录、查询、删除等业务时标识用户身份 * * @return 用户标识 */ + (NSString*)FACE_AUTH_ID; /*! * DVC
* 用户设备编号,用于验证用户 */ + (NSString*) FACE_DVC; @end ================================================ FILE: zhuishushenqi/TXTReader/Speech/Model/iflyMSC.framework/Headers/IFlySpeechError.h ================================================ // // IFlySpeechError.h // MSC // // Created by iflytek on 13-3-19. // Copyright (c) 2013年 iflytek. All rights reserved. // #ifndef __IFlySpeechError__ #define __IFlySpeechError__ #import /*! * 错误描述类 */ @interface IFlySpeechError : NSObject /*! * 错误码 */ @property(nonatomic,assign) int errorCode; /*! * 错误码类型 */ @property(nonatomic,assign) int errorType; /*! * 错误描述 */ @property(nonatomic,retain) NSString* errorDesc; /*! * 初始化 * * @param errorCode -[in] 错误码 * * @return IFlySpeechError对象 */ + (instancetype) initWithError:(int) errorCode; /*! * 获取错误码 * * @return 错误码 */ -(int) errorCode; /*! * 获取错误描述 * * @return 错误描述 */ - (NSString *) errorDesc; @end #endif ================================================ FILE: zhuishushenqi/TXTReader/Speech/Model/iflyMSC.framework/Headers/IFlySpeechEvaluator.h ================================================ // // IFlySpeechEvaluator.h // msc // // Created by jianzhang on 14-1-13 // Copyright (c) 2013年 iflytek. All rights reserved. // #import #import "IFlySpeechEvaluatorDelegate.h" #define IFLY_AUDIO_SOURCE_MIC @"1" #define IFLY_AUDIO_SOURCE_STREAM @"-1" /*! * 语音评测类 */ @interface IFlySpeechEvaluator : NSObject /*! * 设置委托对象 */ @property (assign) id delegate; /*! * 返回评测对象的单例 * * @return 别对象的单例 */ + (instancetype)sharedInstance; /*! * 销毁评测对象。 * * @return 成功返回YES,失败返回NO。 */ - (BOOL)destroy; /*! * 设置评测引擎的参数 * * @param value 评测引擎参数值 * @param key 评测引擎参数 * * @return 设置的参数和取值正确返回YES,失败返回NO */ - (BOOL)setParameter:(NSString *)value forKey:(NSString *)key; /*! * 获得评测引擎的参数 * * @param key 评测引擎参数 * * @return key对应的参数值 */ - (NSString*)parameterForKey:(NSString *)key; /*! * 开始评测
* 同时只能进行一路会话,这次会话没有结束不能进行下一路会话,否则会报错 * * @param data 评测的试题 * @param params 评测的参数 * @return 成功返回YES,失败返回NO */ - (BOOL)startListening:(NSData *)data params:(NSString *)params; /*! * 停止录音
* 调用此函数会停止录音,并开始进行语音识别 */ - (void)stopListening; /*! * 取消本次会话 */ - (void)cancel; @end /*! * 音频流评测
* 音频流评测可以将文件分段写入 */ @interface IFlySpeechEvaluator(IFlyStreamISERecognizer) /*! * 写入音频流 * * @param audioData 音频数据 * * @return 写入成功返回YES,写入失败返回NO */ - (BOOL) writeAudio:(NSData *) audioData; @end ================================================ FILE: zhuishushenqi/TXTReader/Speech/Model/iflyMSC.framework/Headers/IFlySpeechEvaluatorDelegate.h ================================================ // // IFlySpeechEvaluatorDelegate.h // msc // // Created by admin on 13-6-19. // Copyright (c) 2013年 iflytek. All rights reserved. // #import @class IFlySpeechError; /*! * 评测协议 */ @protocol IFlySpeechEvaluatorDelegate /*! * 音量和数据回调 * * @param volume 音量 * @param buffer 音频数据 */ - (void)onVolumeChanged:(int)volume buffer:(NSData *)buffer; /*! * 开始录音回调
* 当调用了`startListening`函数之后,如果没有发生错误则会回调此函数。如果发生错误则回调onCompleted:函数 */ - (void)onBeginOfSpeech; /*! * 停止录音回调
* 当调用了`stopListening`函数或者引擎内部自动检测到断点,如果没有发生错误则回调此函数。
* 如果发生错误则回调onCompleted:函数 */ - (void)onEndOfSpeech; /*! * 正在取消 */ - (void)onCancel; /*! * 评测错误回调 * * 在进行语音评测过程中的任何时刻都有可能回调此函数,你可以根据errorCode进行相应的处理.当errorCode没有错误时,表示此次会话正常结束,否则,表示此次会话有错误发生。特别的当调用`cancel`函数时,引擎不会自动结束,需要等到回调此函数,才表示此次会话结束。在没有回调此函数之前如果重新调用了`startListenging`函数则会报错误。 * * @param errorCode 错误描述类 */ - (void)onCompleted:(IFlySpeechError *)errorCode; /*! * 评测结果回调
* 在评测过程中可能会多次回调此函数,你最好不要在此回调函数中进行界面的更改等操作,只需要将回调的结果保存起来。 * * @param results -[out] 评测结果。 * @param isLast -[out] 是否最后一条结果 */ - (void)onResults:(NSData *)results isLast:(BOOL)isLast; @end ================================================ FILE: zhuishushenqi/TXTReader/Speech/Model/iflyMSC.framework/Headers/IFlySpeechEvent.h ================================================ // // IFlySpeechEvent.h // MSCDemo // // Created by admin on 14-8-12. // Copyright (c) 2014年 iflytek. All rights reserved. // #import /*! * 事件类型 */ typedef NS_ENUM(NSUInteger,IFlySpeechEventType){ /*! * 网络状态消息
* 在消息到达时,可通过onEvent的第2个参数arg1,获取当前网络连接状态值 */ IFlySpeechEventTypeNetPref = 10001, /*! * 转写音频文件消息
* 在录音模式下,成功创建音频文件时返回。可通过onEvent第4个参数data,指定Key为[IFlySpeechConstant IST_AUDIO_PATH],获取音频文件绝对路径.或通过[IFlySpeechTranscripter getParameter:[IFlySpeechConstant IST_AUDIO_PATH]],获取音频文件绝对路径. */ IFlySpeechEventTypeISTAudioFile = 10004, /*! * 转写已上传字节消息
* 在消息到达时,通过onEvent的第二个参数arg1,获取已确认上传到服务器的字节数.若当前音频源为非写音频模式,还可通过onEvent * 的第三个参数arg2,获取当前所有音频的字节大小.录音模式时,由于所有音频字节大小会变。当停止音频输入后(等待录音时间超时[IFlySpeechConstant SPEECH_TIMEOUT],或调用[IFlySpeechTranscripter stopTranscripting]),且服务器收到所有音频时,第四个参数data,将包含完成标记的布尔值(true),可通过data调用指定KEY为KCIFlySpeechEventKeyISTUploadComplete获取。此消息可能多次返回. */ IFlySpeechEventTypeISTUploadBytes = 10006, /*! * 转写缓存剩余
* 此消息仅在音频源为-1时需要关注,在调用[IFlySpeechTranscripter writeAudio]写音频时,应该关注此事件。
* 此事件在调用写音频接口、及音频最后被写入底库库时分别回调一次。当事件回调时,通过onEvent的第二个参数arg1,获取当前剩余的缓存大小,当缓存小于要写入的音频时,应该先暂停写音频数据,直到下次缓存大小大于要写入的音频时.最大缓存为128KByte。 */ IFlySpeechEventTypeISTCacheLeft = 10007, /*! * 转写结果等待时间消息
* 在消息到达时,通过 onEvent的第二个参数arg1,获取当前结果需要的时间.
* 此消息可能多次返回,返回时间不定,且不一定会返回. */ IFlySpeechEventTypeISTResultTime= 10008, /*! * 转写转写音频同步ID消息
* 在消息到达时,通过 onEvent的第二个参数arg1,获取当前写音频同步ID.
* 此消息可能多次返回. */ IFlySpeechEventTypeISTSyncID= 10009, /*! * 会话开始消息
* 在会话开始成功后返回 */ IFlySpeechEventTypeSessionBegin = 10010, /*! * 会话结束消息
* 在会话结束前返回 */ IFlySpeechEventTypeSessionEnd = 10011, /*! * 音量消息,在得到音量时抛出,暂时只有身份验证的声纹业务用到 */ IFlySpeechEventTypeVolume = 10012, /*! * VAD后端点消息,在检测到VAD后端点时抛出,暂时只有身份验证的声纹业务用到 */ IFlySpeechEventTypeVadEOS = 10013, /*! * 服务端会话id
* 在消息到达时,可通过onEvent的第4个参数data(字典类型),指定key KCIFlySpeechEventKeySessionID,获取服务端会话id. */ IFlySpeechEventTypeSessionID = 20001, /*! * TTS合成数据消息
* -(void)onEvent:(int)eventType arg0:(int)arg0 arg1:(int)arg1 data:(NSData *)eventData
* 其中eventData中包含数据 * */ IFlySpeechEventTypeTTSBuffer = 21001, /*! * 通知cancel方法被调用的回调 * */ IFlySpeechEventTypeTTSCancel = 21002, /*! * IVW onshot 听写 or 识别结果
* 在消息到达时,第2个参数arg1包含是否为最后一个结果:1为是,0为否;
* 第4个参数data中包含数据,通过指定KEY为KCIFlySpeechEventKeyIVWResult获取. */ IFlySpeechEventTypeIVWResult = 22001, /*! * 开始处理录音数据 * */ IFlySpeechEventTypeSpeechStart= 22002, /*! * 录音停止 * */ IFlySpeechEventTypeRecordStop= 22003, /*! * 服务端音频url
* 在消息到达时,第4个参数data,包含数据,通过指定KEY为KCIFlySpeechEventKeyAudioUrl获取. */ IFlySpeechEventTypeAudioUrl = 23001, /*! * 变声数据结果返回
* 设置voice_change参数获取结果. */ IFlySpeechEventTypeVoiceChangeResult = 24001 }; #pragma mark - keys for event data /** * 转写是否已上传完标记key */ extern NSString* const KCIFlySpeechEventKeyISTUploadComplete; /** * 服务端会话key */ extern NSString* const KCIFlySpeechEventKeySessionID; /** * TTS取音频数据key */ extern NSString* const KCIFlySpeechEventKeyTTSBuffer; /** * IVW oneshot 听写 or 识别结果 key */ extern NSString* const KCIFlySpeechEventKeyIVWResult; /** * 服务端音频url key */ extern NSString* const KCIFlySpeechEventKeyAudioUrl; ================================================ FILE: zhuishushenqi/TXTReader/Speech/Model/iflyMSC.framework/Headers/IFlySpeechRecognizer.h ================================================ // // IFlySpeechRecognizer.h // MSC // // Created by iflytek on 13-3-19. // Copyright (c) 2013年 iflytek. All rights reserved. // #import #import "IFlySpeechRecognizerDelegate.h" #define IFLY_AUDIO_SOURCE_MIC @"1" #define IFLY_AUDIO_SOURCE_STREAM @"-1" /*! * 语音识别类
* 此类现在设计为单例,你在使用中只需要创建此对象,不能调用release/dealloc函数去释放此对象。所有关于语音识别的操作都在此类中。 */ @interface IFlySpeechRecognizer : NSObject /*! * 设置委托对象 */ @property(nonatomic,assign) id delegate ; /*! * 返回识别对象的单例 * * @return 识别对象的单例 */ + (instancetype) sharedInstance; /*! * 销毁识别对象。 * * @return 成功返回YES,失败返回NO */ - (BOOL) destroy; /* * | ------------- |----------------------------------------------------------- * | 参数 | 描述 * | ------------- |----------------------------------------------------------- * | domain |应用的领域: 取值为:iat、search、video、poi、music、asr; * | | iat:普通文本听写; * | | search:热词搜索; * | | video:视频音乐搜索; * | | asr:关键词识别; * | ------------- |----------------------------------------------------------- * | vad_bos |前端点检测: 静音超时时间,即用户多长时间不说话则当做超时处理; 单位:ms; * | | engine指定iat识别默认值为5000; * | | 其他情况默认值为 4000,范围 0-10000。 * | ------------- |----------------------------------------------------------- * | vad_eos |后断点检测: 后端点静音检测时间,即用户停止说话多长时间内即认为不再输入, * | | 自动停止录音;单位:ms; * | | sms 识别默认值为 1800; * | | 其他默认值为 700,范围 0-10000。 * | ------------- |----------------------------------------------------------- * | sample_rate |采样率:目前支持的采样率设置有 16000 和 8000。 * | ------------- |----------------------------------------------------------- * | asr_ptt |标点符号设置: 默认为 1,当设置为 0 时,将返回无标点符号文本。 * | ------------- |----------------------------------------------------------- * | result_type |返回结果的数据格式: 可设置为json,xml,plain,默认为json。 * | ------------- |----------------------------------------------------------- * | grammarID |识别的语法id: 只针对 domain 设置为”asr”的应用。 * | ------------- |----------------------------------------------------------- * | asr_audio_path|音频文件名: 设置此参数后,将会自动保存识别的录音文件。 * | | 路径为Documents/(指定值)。 * | | 不设置或者设置为nil,则不保存音频。 * | ------------- |----------------------------------------------------------- * | params |扩展参数: 对于一些特殊的参数可在此设置,一般用于设置语义。 * | ------------- |----------------------------------------------------------- * */ /*! * 设置识别引擎的参数 * * 识别的引擎参数(key)取值如下: * * | 参数 | 描述 | * |-----------------|-------------------------------------------------------| * | domain | 应用的领域: 取值为:iat、search、video、poi、music、asr;
iat:普通文本听写;
search:热词搜索;
video:视频音乐搜索;
asr:关键词识别;| * | vad_bos | 前端点检测: 静音超时时间,即用户多长时间不说话则当做超时处理; 单位:ms;
engine指定iat识别默认值为5000;
其他情况默认值为 4000,范围 0-10000。| * | vad_eos | 后断点检测: 后端点静音检测时间,即用户停止说话多长时间内即认为不再输入,自动停止录音;单位:ms;
sms 识别默认值为 1800;
其他默认值为 700,范围 0-10000。| * | sample_rate | 采样率:目前支持的采样率设置有 16000 和 8000。| * | asr_ptt | 标点符号设置: 默认为 1,当设置为 0 时,将返回无标点符号文本。| * | result_type | 返回结果的数据格式: 可设置为json,xml,plain,默认为json。| * | grammarID | 识别的语法id: 只针对 domain 设置为”asr”的应用。| * | asr_audio_path | 音频文件名: 设置此参数后,将会自动保存识别的录音文件。
路径为Documents/(指定值)。
不设置或者设置为nil,则不保存音频。| * | params | 扩展参数: 对于一些特殊的参数可在此设置,一般用于设置语义。| * * @param value 参数对应的取值 * @param key 识别引擎参数 * * @return 成功返回YES;失败返回NO */ -(BOOL) setParameter:(NSString *) value forKey:(NSString*)key; /*! * 获取识别引擎参数 * * @param key 参数key * * @return 参数值 */ -(NSString*) parameterForKey:(NSString *)key; /*! * 开始识别 * * 同时只能进行一路会话,这次会话没有结束不能进行下一路会话,否则会报错。若有需要多次回话,请在onCompleted回调返回后请求下一路回话。 * * @return 成功返回YES;失败返回NO */ - (BOOL) startListening; /*! * 停止录音
* 调用此函数会停止录音,并开始进行语音识别 */ - (void) stopListening; /*! * 取消本次会话 */ - (void) cancel; /*! * 上传语法 * * @param completionHandler 上传语法完成回调 * @param grammarType 语法类型 * @param grammarContent 语法内容 * * @return 错误码 */ - (int) buildGrammarCompletionHandler:(IFlyOnBuildFinishCompletionHandler)completionHandler grammarType:(NSString *)grammarType grammarContent:(NSString *)grammarContent; /*! * 是否正在识别 */ @property (nonatomic, readonly) BOOL isListening; @end /*! * 音频流识别
* 音频流识别可以将文件分段写入 */ @interface IFlySpeechRecognizer(IFlyStreamRecognizer) /*! * 写入音频流 * * 此方法的使用示例如下: *
[_iFlySpeechRecognizer setParameter:@"-1" value:@"audio_source"];
* [_iFlySpeechRecognizer startListening];
* [_iFlySpeechRecognizer writeAudio:audioData1];
* [_iFlySpeechRecognizer writeAudio:audioData2];
* ...
* [_iFlySpeechRecognizer stopListening];
* 
* * @param audioData 音频数据 * * @return 写入成功返回YES,写入失败返回NO */ - (BOOL) writeAudio:(NSData *) audioData; @end ================================================ FILE: zhuishushenqi/TXTReader/Speech/Model/iflyMSC.framework/Headers/IFlySpeechRecognizerDelegate.h ================================================ // // IFlySpeechRecognizerDelegate.h // MSC // // Created by ypzhao on 13-3-27. // Copyright (c) 2013年 iflytek. All rights reserved. // #import @class IFlySpeechError; /*! * 构建语法结束回调 * * @param grammarId 语法id * @param error 错误描述 */ typedef void(^IFlyOnBuildFinishCompletionHandler)(NSString* grammarId,IFlySpeechError * error); /*! * 语音识别协议
* 在使用语音识别时,需要实现这个协议中的方法. */ @protocol IFlySpeechRecognizerDelegate @required /*! * 识别结果回调 * * 在进行语音识别过程中的任何时刻都有可能回调此函数,你可以根据errorCode进行相应的处理,当errorCode没有错误时,表示此次会话正常结束;否则,表示此次会话有错误发生。特别的当调用`cancel`函数时,引擎不会自动结束,需要等到回调此函数,才表示此次会话结束。在没有回调此函数之前如果重新调用了`startListenging`函数则会报错误。 * * @param errorCode 错误描述 */ - (void) onCompleted:(IFlySpeechError *) errorCode; /*! * 识别结果回调 * * 在识别过程中可能会多次回调此函数,你最好不要在此回调函数中进行界面的更改等操作,只需要将回调的结果保存起来。
* 使用results的示例如下: *

 *  - (void) onResults:(NSArray *) results{
 *     NSMutableString *result = [[NSMutableString alloc] init];
 *     NSDictionary *dic = [results objectAtIndex:0];
 *     for (NSString *key in dic){
 *        [result appendFormat:@"%@",key];//合并结果
 *     }
 *   }
 *  
* * @param results -[out] 识别结果,NSArray的第一个元素为NSDictionary,NSDictionary的key为识别结果,sc为识别结果的置信度。 * @param isLast -[out] 是否最后一个结果 */ - (void) onResults:(NSArray *) results isLast:(BOOL)isLast; @optional /*! * 音量变化回调
* 在录音过程中,回调音频的音量。 * * @param volume -[out] 音量,范围从0-30 */ - (void) onVolumeChanged: (int)volume; /*! * 开始录音回调
* 当调用了`startListening`函数之后,如果没有发生错误则会回调此函数。
* 如果发生错误则回调onCompleted:函数 */ - (void) onBeginOfSpeech; /*! * 停止录音回调
* 当调用了`stopListening`函数或者引擎内部自动检测到断点,如果没有发生错误则回调此函数。
* 如果发生错误则回调onCompleted:函数 */ - (void) onEndOfSpeech; /*! * 取消识别回调
* 当调用了`cancel`函数之后,会回调此函数,在调用了cancel函数和回调onCompleted之前会有一个
* 短暂时间,您可以在此函数中实现对这段时间的界面显示。 */ - (void) onCancel; #ifdef _EDUCATION_ /*! * 返回音频Key * * @param key 音频Key */ - (void) getAudioKey:(NSString *)key; #endif /*! * 扩展事件回调
* 根据事件类型返回额外的数据 * * @param eventType 事件类型,具体参见IFlySpeechEventType的IFlySpeechEventTypeVoiceChangeResult枚举。 * @param arg0 arg0 * @param arg1 arg1 * @param eventData 事件数据 */ - (void) onEvent:(int)eventType arg0:(int)arg0 arg1:(int)arg1 data:(NSData *)eventData; @end ================================================ FILE: zhuishushenqi/TXTReader/Speech/Model/iflyMSC.framework/Headers/IFlySpeechSynthesizer.h ================================================ // // IFlySpeechSynthesizer.h // MSC // // Created by 侯效林 on 16-4-22. // Copyright (c) 2016年 iflytek. All rights reserved. // #import #import "IFlySpeechSynthesizerDelegate.h" /*! * 语音合成 */ @interface IFlySpeechSynthesizer : NSObject /*! * 设置识别的委托对象 */ @property(nonatomic,assign) id delegate; /*! * 返回合成对象的单例 * * @return 合成对象 */ + (instancetype) sharedInstance; /*! * 销毁合成对象。 * * @return 成功返回YES,失败返回NO. */ + (BOOL) destroy; /* * | ------------- |----------------------------------------------------------- * | 参数 | 描述 * | ------------- |----------------------------------------------------------- * | speed |合成语速,取值范围 0~100 * | ------------- |----------------------------------------------------------- * | volume |合成的音量,取值范围 0~100 * | ------------- |----------------------------------------------------------- * | voice_name |默认为”xiaoyan”;可以设置的参数列表可参考个性化发音人列表 * | ------------- |----------------------------------------------------------- * | sample_rate |采样率:目前支持的采样率设置有 16000 和 8000。 * | ------------- |----------------------------------------------------------- * | tts_audio_path|音频文件名 设置此参数后,将会自动保存合成的音频文件。 * | |路径为Documents/(指定值)。不设置或者设置为nil,则不保存音频。 * | ------------- |----------------------------------------------------------- * | params |扩展参数: 对于一些特殊的参数可在此设置。 * | ------------- |----------------------------------------------------------- * */ /*! * 设置合成参数 * * | 参数 | 描述 | * |-----------------|----------------------------------------------------| * | speed | 合成语速,取值范围 0~100 | * | volume | 合成的音量,取值范围 0~100 | * | voice_name | 默认为”xiaoyan”;可以设置的参数列表可参考个性化发音人列表 | * | sample_rate | 采样率:目前支持的采样率设置有 16000 和 8000。 | * | tts_audio_path | 音频文件名 设置此参数后,将会自动保存合成的音频文件。
路径为Documents/(指定值)。不设置或者设置为nil,则不保存音频。| * | params | 扩展参数: 对于一些特殊的参数可在此设置。 | * * @param value 参数取值 * @param key 合成参数 * * @return 设置成功返回YES,失败返回NO */ -(BOOL) setParameter:(NSString *) value forKey:(NSString*)key; /*! * 获取合成参数 * * @param key 参数key * * @return 参数值 */ -(NSString*) parameterForKey:(NSString *)key; /*! * 开始合成(播放)
* 调用此函数进行合成,如果发生错误会回调错误`onCompleted` * * @param text 合成的文本,最大的字节数为1k */ - (void) startSpeaking:(NSString *)text; /*! * 开始合成(不播放)
* 调用此函数进行合成,如果发生错误会回调错误`onCompleted` * * @param text 合成的文本,最大的字节数为1k * @param uri 合成后,保存再本地的音频路径 */ -(void)synthesize:(NSString *)text toUri:(NSString*)uri; /*! * 暂停播放
* 暂停播放之后,合成不会暂停,仍会继续,如果发生错误则会回调错误`onCompleted` */ - (void) pauseSpeaking; /*! * 恢复播放 */ - (void) resumeSpeaking; /*! * 停止播放并停止合成 */ - (void) stopSpeaking; /*! * 是否正在播放 */ @property (nonatomic, readonly) BOOL isSpeaking; @end ================================================ FILE: zhuishushenqi/TXTReader/Speech/Model/iflyMSC.framework/Headers/IFlySpeechSynthesizerDelegate.h ================================================ // // IFlySpeechSynthesizerDelegate.h // MSC // // Created by ypzhao on 13-3-20. // Copyright (c) 2013年 iflytek. All rights reserved. // #import #import "IFlySpeechEvent.h" @class IFlySpeechError; /*! * 语音合成回调 */ @protocol IFlySpeechSynthesizerDelegate @required /*! * 结束回调
* 当整个合成结束之后会回调此函数 * * @param error 错误码 */ - (void) onCompleted:(IFlySpeechError*) error; @optional /*! * 开始合成回调 */ - (void) onSpeakBegin; /*! * 缓冲进度回调 * * @param progress 缓冲进度,0-100 * @param msg 附件信息,此版本为nil */ - (void) onBufferProgress:(int) progress message:(NSString *)msg; /*! * 播放进度回调 * * @param progress 当前播放进度,0-100 * @param beginPos 当前播放文本的起始位置(按照字节计算),对于汉字(2字节)需/2处理 * @param endPos 当前播放文本的结束位置(按照字节计算),对于汉字(2字节)需/2处理 */ - (void) onSpeakProgress:(int) progress beginPos:(int)beginPos endPos:(int)endPos; /*! * 暂停播放回调 */ - (void) onSpeakPaused; /*! * 恢复播放回调
* 注意:此回调方法SDK内部不执行,播放恢复全部在onSpeakBegin中执行 */ - (void) onSpeakResumed; /*! * 正在取消回调
* 注意:此回调方法SDK内部不执行 */ - (void) onSpeakCancel; /*! * 扩展事件回调
* 根据事件类型返回额外的数据 * * @param eventType 事件类型,具体参见IFlySpeechEventType枚举。目前只支持EVENT_TTS_BUFFER也就是实时返回合成音频。 * @param arg0 arg0 * @param arg1 arg1 * @param eventData 事件数据 */ - (void) onEvent:(int)eventType arg0:(int)arg0 arg1:(int)arg1 data:(NSData *)eventData; @end ================================================ FILE: zhuishushenqi/TXTReader/Speech/Model/iflyMSC.framework/Headers/IFlySpeechUtility.h ================================================ // // IFlySpeechUtility.h // MSCDemo // // Created by admin on 14-5-7. // Copyright (c) 2014年 iflytek. All rights reserved. // #import #define iOS_EXCLUSIVE //iOS平台独占API @class IFlySpeechError; /*! * 引擎模式 */ typedef NS_ENUM(NSUInteger,IFlyEngineMode){ /*! * 云端使用MSC,本地优先使用语记 */ IFlyEngineModeAuto = 0, /*! * 只使用MSC */ IFlyEngineModeMsc, /*! * 本地只使用语记(受平台限制,云端无法使用语记) */ IFlyEngineModePlus, }; /*! * 服务类型 */ typedef NS_ENUM(NSUInteger,IFlySpeechPlusServiceType){ /*! * 打开语记主界面 */ IFlySpeechPlusServiceTypeNone=0, /*! * 获取合成资源 */ IFlySpeechPlusServiceTypeTTS, /*! * 获取识别资源(未开放) */ IFlySpeechPlusServiceTypeISR, /*! * 获取唤醒资源(未开放) */ IFlySpeechPlusServiceTypeIVW, } ; /*! 语记返回回调 */ @protocol IFlySpeechplusDelegate /*! * 发生错误 * * @param errorCode 错误码 */ - (void)onCompleted:(int)errorCode; /*! * 服务正常结束 */ - (void)onCompleted; @end /*! * 用户配置 */ @interface IFlySpeechUtility : NSObject /*! * 创建用户语音配置
* 注册应用请前往语音云开发者网站。
* 网站:http://www.xfyun.cn * * @param params 启动参数,必须保证appid参数传入,示例:appid=123456 * * @return 语音配置对象 */ + (IFlySpeechUtility*) createUtility:(NSString *) params; /*! * 销毁用户配置对象 * * @return 成功返回YES,失败返回NO */ +(BOOL) destroy; /*! * 获取用户配置对象 * * @return 用户配置对象 */ +(IFlySpeechUtility *) getUtility; /*! * 设置MSC引擎的状态参数 * * @param value 参数值 * @param key 参数名称 * * @return 成功返回YES,失败返回NO */ -(BOOL) setParameter:(NSString *) value forKey:(NSString*)key; /*! * 获取MSC引擎状态参数 * * @param key 参数名 * * @return 参数值 */ - (NSString *)parameterForKey:(NSString *)key; /*! * 引擎类型 */ @property (nonatomic, readonly) IFlyEngineMode engineMode; /*! * 语记协议委托 */ @property (nonatomic, assign) id delegate; @end /*! * 讯飞语记类别 */ @interface IFlySpeechUtility (SpeechPlus) /*! * 检查讯飞语记是否安装 * * @return 已安装返回YES,否则返回NO */ + (BOOL)checkServiceInstalled; /*! * 获取讯飞语记下载地址进行下载,安装完成后即可使用服务。
* 下载地址需要通过[[UIApplication sharedApplication] openUrl:]打开 * * @return 讯飞语记在App Store下载地址 */ + (NSString *)componentUrl; /*! * 注意:此接口废弃,不再需要使用
* 处理语记使用URL启动第三方应用程序时传递的数据
* 需要在 application:openURL:sourceApplication:annotation:或者application:handleOpenURL中调用。 * * @param url 语记启动第三方应用程序时传递过来的URL * * @return 成功返回YES,失败返回NO。 */ - (BOOL)handleOpenURL:(NSURL *)url iOS_EXCLUSIVE; /*! * 打开讯飞语记获取相应类型服务,0表示打开主界面 * * @param serviceType 服务类型 * * @return 成功打开返回YES,否则返回NO */ - (BOOL)openSpeechPlus:(IFlySpeechPlusServiceType)serviceType iOS_EXCLUSIVE; @end ================================================ FILE: zhuishushenqi/TXTReader/Speech/Model/iflyMSC.framework/Headers/IFlyUserWords.h ================================================ // // IFlyUserWords.h // MSC // // Created by ypzhao on 13-2-26. // Copyright (c) 2013年 iflytek. All rights reserved. // #import /*! * 用户词表类 * 获取用户词表是为了更好的语音识别(iat),用户词表也属于个性化的一部分. */ @interface IFlyUserWords : NSObject /*! * 初始化对象 * * 在进行初始化时,需要传入的格式如下: *
{\"userword\":[{\"name\":\"iflytek\",\"words\":[\"科大讯飞\",
 *  \"云平台\",\"用户词条\",\"开始上传词条\"]}]}
* * @param json 初始化时传入的数据 * * @return IFlyUserWords对象 */ - (id) initWithJson:(NSString *)json; /*! * 将数据转化为上传的数据格式 * * @return 没有数据或者格式不对时返回nil */ - (NSString *) toString; /*! * 返回key对应的数据 * * @param key 在putword:value中设置的key * * @return key对应的数组 */ - (NSArray *) getWords: (NSString *) key; /*! * 添加一条用户词数据 * * @param key 用户词对应的key * @param value 上传的用户词数据 * * @return 成功返回YES,失败返回NO */ - (BOOL) putWord: (NSString *) key value:(NSString *)value; /*! * 添加一组数据 * * @param key 用户词对应的key * @param words 上传的用户词数据 * * @return 成功返回YES,失败返回NO */ - (BOOL) putwords: (NSString *) key words:(NSArray *)words; /*! * 是否包含key对应的用户词数据 * * @param key 用户词对应的key * * @return 成功返回YES,失败返回NO */ - (BOOL) containsKey: (NSString *) key; @end ================================================ FILE: zhuishushenqi/TXTReader/Speech/Model/iflyMSC.framework/Headers/IFlyVoiceWakeuper.h ================================================ // // IFlyVoiceWakeuper.h // wakeup // // Created by admin on 14-3-18. // Copyright (c) 2014年 iflytek. All rights reserved. // #import #import "IFlyVoiceWakeuperDelegate.h" #define IFLY_AUDIO_SOURCE_MIC @"1" #define IFLY_AUDIO_SOURCE_STREAM @"-1" /*! * 语音唤醒 */ @interface IFlyVoiceWakeuper : NSObject /*! * 代理 */ @property (nonatomic, assign) id delegate; /*! * 是否正在唤醒 */ @property (nonatomic, readonly) BOOL isListening; /*! * 创建唤醒实例,采用单例模式 */ + (instancetype) sharedInstance; /*! * 启动唤醒 * 返回值:YES 成功,NO:失败 */ -(BOOL) startListening; /*! * 停止录音 */ -(BOOL) stopListening; /*! * 取消唤醒会话 */ -(BOOL) cancel; /*! * 获取工作参数 */ -(NSString*) getParameter:(NSString *)key; /*! * 设置工作参数
* 注意服务正在运行中,不能设置参数 */ -(BOOL) setParameter:(NSString *) value forKey:(NSString*)key; @end /*! * 音频流唤醒
* 音频流唤醒可以将文件分段写入 */ @interface IFlyVoiceWakeuper(IFlyStreamVoiceWakeuper) /*! * 写入音频流 * * @param audioData 音频数据 * * @return 写入成功返回YES,写入失败返回NO */ - (BOOL) writeAudio:(NSData *) audioData; @end ================================================ FILE: zhuishushenqi/TXTReader/Speech/Model/iflyMSC.framework/Headers/IFlyVoiceWakeuperDelegate.h ================================================ // // IFlyVoiceWakeuperDel.h // wakeup // // Created by admin on 14-3-18. // Copyright (c) 2014年 iflytek. All rights reserved. // #import @class IFlySpeechError; @protocol IFlyVoiceWakeuperDelegate @optional /*! * 录音开始 */ -(void) onBeginOfSpeech; /*! * 录音结束 */ -(void) onEndOfSpeech; /*! * 会话错误 * * @param errorCode 错误描述类, */ - (void) onCompleted:(IFlySpeechError *) error; /*! * 唤醒结果 * * @param resultDic 唤醒结果字典 */ -(void) onResult:(NSMutableDictionary *)resultDic; /*! * 音量反馈,返回频率与录音数据返回回调频率一致 * * @param volume 音量值 */ - (void) onVolumeChanged: (int)volume; /*! * 扩展事件回调
* 根据事件类型返回额外的数据 * @param eventType 事件类型,具体参见IFlySpeechEvent枚举。 */ - (void) onEvent:(int)eventType isLast:(BOOL)isLast arg1:(int)arg1 data:(NSMutableDictionary *)eventData; @end ================================================ FILE: zhuishushenqi/TXTReader/Speech/Model/iflyMSC.framework/iflyMSC ================================================ [File too large to display: 39.4 MB] ================================================ FILE: zhuishushenqi/TXTReader/Speech/Model/pcmPlayerCode/PcmPlayer.h ================================================ // // pcmPlayer.h // MSCDemo // // Created by wangdan on 14-11-4. // // #import #import @interface PcmPlayer : NSObject /** * initialize player and set the loacl path of audio file * * path the loacl path of audio file * sample sample rate of audio,only for 8000 and 16000 **/ -(id)initWithFilePath:(NSString *)path sampleRate:(long)sample; /** * initialize player and set audio data * * data audio data * sample sample rate of audio,only for 8000 and 16000 **/ -(id)initWithData:(NSData *)data sampleRate:(long)sample; /** start playing ****/ - (void)play; /** stop playing **/ - (void)stop; /** whether or not it's playing ****/ @property (nonatomic,assign) BOOL isPlaying; @end ================================================ FILE: zhuishushenqi/TXTReader/Speech/Model/pcmPlayerCode/PcmPlayer.m ================================================ // // pcmPlayer.m // MSCDemo // // Created by wangdan on 14-11-4. // // #import "PcmPlayer.h" typedef struct Wavehead { /****RIFF WAVE CHUNK*/ unsigned char a[4]; //Four bytes: 'R','I','F','F' long int b; //Size of Chunk unsigned char c[4]; //Four bytes: 'W','A','V','E' /****RIFF WAVE CHUNK*/ /****Format CHUNK*/ unsigned char d[4]; //Four bytes: 'f','m','t','' long int e; //16: no additional information,18: additional information; short int f; //encoding mode,default,0x0001; short int g; //channel,1:mono,2:stereo; int h; //sample rate; unsigned int i; //bytes per second; short int j; //bytes per sample; short int k; //bitDepth /****Format CHUNK*/ /***Data Chunk**/ unsigned char p[4]; //Four bytes: 'd','a','t','a' long int q; //length of audio data, not include WAV head } WaveHead;//Structure of WAV head @interface PcmPlayer () @property (nonatomic,strong) AVAudioPlayer *player; @property (nonatomic,strong) NSMutableData *pcmData; @property (nonatomic,strong) NSTimer *timer; @end @implementation PcmPlayer -(id)initWithFilePath:(NSString *)path sampleRate:(long)sample { self = [super init]; if (self) { NSData *audioData = [NSData dataWithContentsOfFile:path]; [self writeWaveHead:audioData sampleRate:sample]; NSLog(@"nihao"); } return self; } -(id)initWithData:(NSData *)data sampleRate:(long)sample { if (data == nil) { return nil; } self = [super init]; if (self) { [self writeWaveHead:data sampleRate:sample]; NSLog(@"nihao"); } return self; } /** * * write WAV head for audio data * */ - (void)writeWaveHead:(NSData *)audioData sampleRate:(long)sampleRate{ Byte waveHead[44]; waveHead[0] = 'R'; waveHead[1] = 'I'; waveHead[2] = 'F'; waveHead[3] = 'F'; long totalDatalength = [audioData length] + 44; waveHead[4] = (Byte)(totalDatalength & 0xff); waveHead[5] = (Byte)((totalDatalength >> 8) & 0xff); waveHead[6] = (Byte)((totalDatalength >> 16) & 0xff); waveHead[7] = (Byte)((totalDatalength >> 24) & 0xff); waveHead[8] = 'W'; waveHead[9] = 'A'; waveHead[10] = 'V'; waveHead[11] = 'E'; waveHead[12] = 'f'; waveHead[13] = 'm'; waveHead[14] = 't'; waveHead[15] = ' '; waveHead[16] = 16; //size of 'fmt ' waveHead[17] = 0; waveHead[18] = 0; waveHead[19] = 0; waveHead[20] = 1; //format waveHead[21] = 0; waveHead[22] = 1; //chanel waveHead[23] = 0; waveHead[24] = (Byte)(sampleRate & 0xff); waveHead[25] = (Byte)((sampleRate >> 8) & 0xff); waveHead[26] = (Byte)((sampleRate >> 16) & 0xff); waveHead[27] = (Byte)((sampleRate >> 24) & 0xff); long byteRate = sampleRate * 2 * (16 >> 3);; waveHead[28] = (Byte)(byteRate & 0xff); waveHead[29] = (Byte)((byteRate >> 8) & 0xff); waveHead[30] = (Byte)((byteRate >> 16) & 0xff); waveHead[31] = (Byte)((byteRate >> 24) & 0xff); waveHead[32] = 2*(16 >> 3); waveHead[33] = 0; waveHead[34] = 16; waveHead[35] = 0; waveHead[36] = 'd'; waveHead[37] = 'a'; waveHead[38] = 't'; waveHead[39] = 'a'; long totalAudiolength = [audioData length]; waveHead[40] = (Byte)(totalAudiolength & 0xff); waveHead[41] = (Byte)((totalAudiolength >> 8) & 0xff); waveHead[42] = (Byte)((totalAudiolength >> 16) & 0xff); waveHead[43] = (Byte)((totalAudiolength >> 24) & 0xff); self.pcmData = [[NSMutableData alloc]initWithBytes:&waveHead length:sizeof(waveHead)]; [self.pcmData appendData:audioData]; NSError *err = nil; self.player = [[AVAudioPlayer alloc]initWithData:self.pcmData error:&err]; if (err) { NSLog(@"%@",err.localizedDescription); } self.player.delegate = self; [self.player prepareToPlay]; } - (void)play { if (self.isPlaying) { NSLog(@"pcmPlayer isPlaying"); return; } self.isPlaying = YES; self.player.volume=1; if ([self.pcmData length] > 44) { self.player.meteringEnabled = YES; NSLog(@"Audio Duration:%f",self.player.duration); BOOL ret = [self.player play]; NSLog(@"play ret=%d",ret); } else { self.isPlaying = NO; NSLog(@"empty audio data"); } } - (void)stop { if (self.isPlaying) { self.isPlaying = NO; [self.player stop]; self.player.currentTime = 0; } } #pragma mark speechRecordDelegate - (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag { NSLog(@"in pcmPlayer audioPlayerDidFinishPlaying"); self.isPlaying=NO; } @end ================================================ FILE: zhuishushenqi/TXTReader/Speech/Model/pcmPlayerCode/PcmPlayerDelegate.h ================================================ // // speechRecordDelegate.h // MSCDemo // // Created by wangdan on 14-11-4. // // #import @protocol PcmPlayerDelegate @optional //playback completion -(void)onPlayCompleted; @end ================================================ FILE: zhuishushenqi/TXTReader/Speech/Model/speaker.json ================================================ { "speakers": [ { "accent": "普通话", "age": 25, "appid": "5445f87d", "commonExpirationDate": "", "desc": "", "downloads": 20806, "engineType": "local", "engineVersion": 1, "ent": "", "experienceExpirationDate": "", "field": "恐怖灵异", "isActive": 1, "isDefault": 0, "isNew": 0, "isRecommend": 0, "isVip": 0, "largeIcon": "https://bj.openstorage.cn/v1/iflytek/tts/common/icon/xiaoxi_big.png", "level": 3, "listenPath": "aHR0cHM6Ly9iai5vcGVuc3RvcmFnZS5jbi92MS9pZmx5dGVrL3R0cy9jb21tb24vbGlzdGVuL3hpYW94aS5tcDM=", "name": "xiaoxi", "nickname": "方木", "price": 0, "resId": 618, "resPath": "aHR0cDovL2lmbHl0ZWsuYmpkbi5vcGVuc3RvcmFnZS5jbi90dHMvODdkL3Jlc291cmNlL3hpYW94aS56aXA=", "resSize": 4295, "sex": "male", "smallIcon": "https://bj.openstorage.cn/v1/iflytek/tts/common/icon/xiaoxi_small.png", "sortId": 2, "speakerId": 51210, "updateTime": "2018-09-19 19:21:05", "version": "1", "downloadUrl":"http://iflytek.bjdn.openstorage.cn/tts/87d/resource/xiaoxi.zip", "prelisten":"https://bj.openstorage.cn/v1/iflytek/tts/common/listen/xiaoxi.mp3" }, { "accent": "普通话", "age": 18, "appid": "5445f87d", "commonExpirationDate": "", "desc": "", "downloads": 29025, "engineType": "local", "engineVersion": 1, "ent": "", "experienceExpirationDate": "", "field": "都市言情", "isActive": 1, "isDefault": 0, "isNew": 0, "isRecommend": 0, "isVip": 0, "largeIcon": "https://bj.openstorage.cn/v1/iflytek/tts/common/icon/xiaoyin_big.png", "level": 3, "listenPath": "aHR0cHM6Ly9iai5vcGVuc3RvcmFnZS5jbi92MS9pZmx5dGVrL3R0cy9jb21tb24vbGlzdGVuL3hpYW95aW4ubXAz", "name": "xiaoyin", "nickname": "方茴", "price": 0, "resId": 616, "resPath": "aHR0cDovL2lmbHl0ZWsuYmpkbi5vcGVuc3RvcmFnZS5jbi90dHMvODdkL3Jlc291cmNlL3hpYW95aW4uemlw", "resSize": 4281, "sex": "female", "smallIcon": "https://bj.openstorage.cn/v1/iflytek/tts/common/icon/xiaoyin_small.png", "sortId": 4, "speakerId": 51115, "updateTime": "2018-09-19 20:21:07", "version": "1", "downloadUrl":"http://iflytek.bjdn.openstorage.cn/tts/87d/resource/xiaoyin.zip", "prelisten":"https://bj.openstorage.cn/v1/iflytek/tts/common/listen/xiaoyin.mp3" }, { "accent": "普通话", "age": 30, "appid": "5445f87d", "commonExpirationDate": "", "desc": "", "downloads": 25445, "engineType": "local", "engineVersion": 1, "ent": "", "experienceExpirationDate": "", "field": "原创出版", "isActive": 1, "isDefault": 0, "isNew": 0, "isRecommend": 0, "isVip": 0, "largeIcon": "https://bj.openstorage.cn/v1/iflytek/tts/common/icon/xiaohou_big.png", "level": 3, "listenPath": "aHR0cHM6Ly9iai5vcGVuc3RvcmFnZS5jbi92MS9pZmx5dGVrL3R0cy9jb21tb24vbGlzdGVuL3hpYW9ob3UubXAz", "name": "xiaohou", "nickname": "郭嘉", "price": 0, "resId": 620, "resPath": "aHR0cDovL2lmbHl0ZWsuYmpkbi5vcGVuc3RvcmFnZS5jbi90dHMvODdkL3Jlc291cmNlL3hpYW9ob3Uuemlw", "resSize": 5188, "sex": "male", "smallIcon": "https://bj.openstorage.cn/v1/iflytek/tts/common/icon/xiaohou_small.png", "sortId": 4, "speakerId": 51200, "updateTime": "2018-09-19 15:20:57", "version": "1", "downloadUrl":"http://iflytek.bjdn.openstorage.cn/tts/87d/resource/xiaohou.zip", "prelisten":"https://bj.openstorage.cn/v1/iflytek/tts/common/listen/xiaohou.mp3" }, { "accent": "普通话", "age": 10, "appid": "5445f87d", "commonExpirationDate": "", "desc": "", "downloads": 20587, "engineType": "local", "engineVersion": 1, "ent": "", "experienceExpirationDate": "", "field": "普通发音人", "isActive": 1, "isDefault": 0, "isNew": 0, "isRecommend": 0, "isVip": 0, "largeIcon": "https://bj.openstorage.cn/v1/iflytek/tts/common/icon/nannan_big.png", "level": 0, "listenPath": "aHR0cHM6Ly9iai5vcGVuc3RvcmFnZS5jbi92MS9pZmx5dGVrL3R0cy9jb21tb24vbGlzdGVuL25hbm5hbi5tcDM=", "name": "nannan", "nickname": "小萝莉", "price": 0, "resId": 624, "resPath": "aHR0cDovL2lmbHl0ZWsuYmpkbi5vcGVuc3RvcmFnZS5jbi90dHMvODdkL3Jlc291cmNlL25hbm5hbi56aXA=", "resSize": 1150, "sex": "female", "smallIcon": "https://bj.openstorage.cn/v1/iflytek/tts/common/icon/nannan_small.png", "sortId": 7, "speakerId": 7, "updateTime": "2018-09-19 20:21:07", "version": 1, "downloadUrl":"http://iflytek.bjdn.openstorage.cn/tts/87d/resource/nannan.zip", "prelisten":"https://bj.openstorage.cn/v1/iflytek/tts/common/listen/nannan.mp3" }, { "accent": "普通话", "age": 34, "appid": "5445f87d", "commonExpirationDate": "", "desc": "", "downloads": 23476, "engineType": "local", "engineVersion": 1, "ent": "", "experienceExpirationDate": "", "field": "普通发音人", "isActive": 1, "isDefault": 0, "isNew": 0, "isRecommend": 0, "isVip": 0, "largeIcon": "https://bj.openstorage.cn/v1/iflytek/tts/common/icon/xiaofeng_big.png", "level": 0, "listenPath": "aHR0cHM6Ly9iai5vcGVuc3RvcmFnZS5jbi92MS9pZmx5dGVrL3R0cy9jb21tb24vbGlzdGVuL3hpYW9mZW5nLm1wMw==", "name": "xiaofeng", "nickname": "男主播", "price": 0, "resId": 608, "resPath": "aHR0cDovL2lmbHl0ZWsuYmpkbi5vcGVuc3RvcmFnZS5jbi90dHMvODdkL3Jlc291cmNlL3hpYW9mZW5nLnppcA==", "resSize": 1125, "sex": "male", "smallIcon": "https://bj.openstorage.cn/v1/iflytek/tts/common/icon/xiaofeng_small.png", "sortId": 8, "speakerId": 4, "updateTime": "2018-09-19 20:21:07", "version": 1, "downloadUrl":"http://iflytek.bjdn.openstorage.cn/tts/87d/resource/xiaofeng.zip", "prelisten":"https://bj.openstorage.cn/v1/iflytek/tts/common/listen/xiaofeng.mp3" }, { "accent": "普通话", "age": 22, "appid": "5445f87d", "commonExpirationDate": "", "desc": "", "downloads": 22708, "engineType": "local", "engineVersion": 1, "ent": "", "experienceExpirationDate": "", "field": "普通发音人", "isActive": 1, "isDefault": 0, "isNew": 0, "isRecommend": 0, "isVip": 0, "largeIcon": "https://bj.openstorage.cn/v1/iflytek/tts/common/icon/xiaojing_big.png", "level": 0, "listenPath": "aHR0cHM6Ly9iai5vcGVuc3RvcmFnZS5jbi92MS9pZmx5dGVrL3R0cy9jb21tb24vbGlzdGVuL3hpYW9qaW5nLm1wMw==", "name": "xiaojing", "nickname": "邻家姐姐", "price": 0, "resId": 606, "resPath": "aHR0cDovL2lmbHl0ZWsuYmpkbi5vcGVuc3RvcmFnZS5jbi90dHMvODdkL3Jlc291cmNlL3hpYW9qaW5nLnppcA==", "resSize": 1226, "sex": "female", "smallIcon": "https://bj.openstorage.cn/v1/iflytek/tts/common/icon/xiaojing_small.png", "sortId": 9, "speakerId": 8, "updateTime": "2018-09-19 20:21:07", "version": 1, "downloadUrl":"http://iflytek.bjdn.openstorage.cn/tts/87d/resource/xiaojing.zip", "prelisten":"https://bj.openstorage.cn/v1/iflytek/tts/common/listen/xiaojing.mp3" }, { "accent": "普通话", "age": 20, "appid": "5445f87d", "commonExpirationDate": "", "desc": "", "downloads": 23302, "engineType": "local", "engineVersion": 1, "ent": "", "experienceExpirationDate": "", "field": "普通发音人", "isActive": 1, "isDefault": 0, "isNew": 0, "isRecommend": 0, "isVip": 0, "largeIcon": "https://bj.openstorage.cn/v1/iflytek/tts/common/icon/jiajia_big.png", "level": 0, "listenPath": "aHR0cHM6Ly9iai5vcGVuc3RvcmFnZS5jbi92MS9pZmx5dGVrL3R0cy9jb21tb24vbGlzdGVuL2ppYWppYS5tcDM=", "name": "jiajia", "nickname": "嘉嘉老师", "price": 0, "resId": 626, "resPath": "aHR0cDovL2lmbHl0ZWsuYmpkbi5vcGVuc3RvcmFnZS5jbi90dHMvODdkL3Jlc291cmNlL2ppYWppYS56aXA=", "resSize": 1714, "sex": "female", "smallIcon": "https://bj.openstorage.cn/v1/iflytek/tts/common/icon/jiajia_small.png", "sortId": 11, "speakerId": 9, "updateTime": "2018-09-19 20:21:07", "version": 1, "downloadUrl":"http://iflytek.bjdn.openstorage.cn/tts/87d/resource/jiajia.zip", "prelisten":"https://bj.openstorage.cn/v1/iflytek/tts/common/listen/jiajia.mp3" }, { "accent": "粤语", "age": 26, "appid": "5445f87d", "commonExpirationDate": "", "desc": "", "downloads": 13046, "engineType": "local", "engineVersion": 1, "ent": "", "experienceExpirationDate": "", "field": "粤语", "isActive": 1, "isDefault": 0, "isNew": 0, "isRecommend": 0, "isVip": 0, "largeIcon": "https://bj.openstorage.cn/v1/iflytek/tts/common/icon/xiaomei_big.png", "level": 0, "listenPath": "aHR0cHM6Ly9iai5vcGVuc3RvcmFnZS5jbi92MS9pZmx5dGVrL3R0cy9jb21tb24vbGlzdGVuL3hpYW9tZWlfbG9jYWwubXAz", "name": "xiaomei", "nickname": "港姐", "price": 0, "resId": 634, "resPath": "aHR0cDovL2lmbHl0ZWsuYmpkbi5vcGVuc3RvcmFnZS5jbi90dHMvODdkL3Jlc291cmNlL3hpYW9tZWkuemlw", "resSize": 1831, "sex": "female", "smallIcon": "https://bj.openstorage.cn/v1/iflytek/tts/common/icon/xiaomei_small.png", "sortId": 12, "speakerId": 15, "updateTime": "2018-09-19 20:51:08", "version": 1, "downloadUrl":"http://iflytek.bjdn.openstorage.cn/tts/87d/resource/xiaomei.zip", "prelisten":"https://bj.openstorage.cn/v1/iflytek/tts/common/listen/xiaomei.mp3" }, { "accent": "台湾话", "age": 32, "appid": "5445f87d", "commonExpirationDate": "", "desc": "", "downloads": 10551, "engineType": "local", "engineVersion": 1, "ent": "", "experienceExpirationDate": "", "field": "台湾话", "isActive": 1, "isDefault": 0, "isNew": 0, "isRecommend": 0, "isVip": 0, "largeIcon": "https://bj.openstorage.cn/v1/iflytek/tts/common/icon/xiaolin_big.png", "level": 0, "listenPath": "aHR0cHM6Ly9iai5vcGVuc3RvcmFnZS5jbi92MS9pZmx5dGVrL3R0cy9jb21tb24vbGlzdGVuL3hpYW9saW4ubXAz", "name": "xiaolin", "nickname": "佳宜", "price": 0, "resId": 638, "resPath": "aHR0cDovL2lmbHl0ZWsuYmpkbi5vcGVuc3RvcmFnZS5jbi90dHMvODdkL3Jlc291cmNlL3hpYW9saW4uemlw", "resSize": 1616, "sex": "female", "smallIcon": "https://bj.openstorage.cn/v1/iflytek/tts/common/icon/xiaolin_small.png", "sortId": 13, "speakerId": 22, "updateTime": "2018-09-19 20:51:08", "version": 1, "downloadUrl":"http://iflytek.bjdn.openstorage.cn/tts/87d/resource/xiaolin.zip", "prelisten":"https://bj.openstorage.cn/v1/iflytek/tts/common/listen/xiaolin.mp3" }, { "accent": "湖南话", "age": 32, "appid": "5445f87d", "commonExpirationDate": "", "desc": "", "downloads": 8089, "engineType": "local", "engineVersion": 1, "ent": "", "experienceExpirationDate": "", "field": "湖南话", "isActive": 1, "isDefault": 0, "isNew": 0, "isRecommend": 0, "isVip": 0, "largeIcon": "https://bj.openstorage.cn/v1/iflytek/tts/common/icon/xiaoqiang_big.png", "level": 0, "listenPath": "aHR0cHM6Ly9iai5vcGVuc3RvcmFnZS5jbi92MS9pZmx5dGVrL3R0cy9jb21tb24vbGlzdGVuL3hpYW9xaWFuZy5tcDM=", "name": "xiaoqiang", "nickname": "涵涵", "price": 0, "resId": 636, "resPath": "aHR0cDovL2lmbHl0ZWsuYmpkbi5vcGVuc3RvcmFnZS5jbi90dHMvODdkL3Jlc291cmNlL3hpYW9xaWFuZy56aXA=", "resSize": 600, "sex": "male", "smallIcon": "https://bj.openstorage.cn/v1/iflytek/tts/common/icon/xiaoqiang_small.png", "sortId": 14, "speakerId": 24, "updateTime": "2018-09-19 20:51:08", "version": 1, "downloadUrl":"http://iflytek.bjdn.openstorage.cn/tts/87d/resource/xiaoqiang.zip", "prelisten":"https://bj.openstorage.cn/v1/iflytek/tts/common/listen/xiaoqiang.mp3" }, { "accent": "四川话", "age": 32, "appid": "5445f87d", "commonExpirationDate": "", "desc": "", "downloads": 9843, "engineType": "local", "engineVersion": 1, "ent": "", "experienceExpirationDate": "", "field": "四川话", "isActive": 1, "isDefault": 0, "isNew": 0, "isRecommend": 0, "isVip": 0, "largeIcon": "https://bj.openstorage.cn/v1/iflytek/tts/common/icon/xiaorong_big.png", "level": 0, "listenPath": "aHR0cHM6Ly9iai5vcGVuc3RvcmFnZS5jbi92MS9pZmx5dGVrL3R0cy9jb21tb24vbGlzdGVuL3hpYW9yb25nLm1wMw==", "name": "xiaorong", "nickname": "辣妹子", "price": 0, "resId": 630, "resPath": "aHR0cDovL2lmbHl0ZWsuYmpkbi5vcGVuc3RvcmFnZS5jbi90dHMvODdkL3Jlc291cmNlL3hpYW9yb25nLnppcA==", "resSize": 489, "sex": "female", "smallIcon": "https://bj.openstorage.cn/v1/iflytek/tts/common/icon/xiaorong_small.png", "sortId": 15, "speakerId": 14, "updateTime": "2018-09-19 20:51:08", "version": 1, "downloadUrl":"http://iflytek.bjdn.openstorage.cn/tts/87d/resource/xiaorong.zip", "prelisten":"https://bj.openstorage.cn/v1/iflytek/tts/common/listen/xiaorong.mp3" }, { "accent": "河南话", "age": 28, "appid": "5445f87d", "commonExpirationDate": "", "desc": "", "downloads": 7722, "engineType": "local", "engineVersion": 1, "ent": "", "experienceExpirationDate": "", "field": "河南话", "isActive": 1, "isDefault": 0, "isNew": 0, "isRecommend": 0, "isVip": 0, "largeIcon": "https://bj.openstorage.cn/v1/iflytek/tts/common/icon/xiaokun_big.png", "level": 0, "listenPath": "aHR0cHM6Ly9iai5vcGVuc3RvcmFnZS5jbi92MS9pZmx5dGVrL3R0cy9jb21tb24vbGlzdGVuL3hpYW9rdW4ubXAzCg==", "name": "xiaokun", "nickname": "傻根", "price": 0, "resId": 632, "resPath": "aHR0cDovL2lmbHl0ZWsuYmpkbi5vcGVuc3RvcmFnZS5jbi90dHMvODdkL3Jlc291cmNlL3hpYW9rdW4uemlw", "resSize": 580, "sex": "male", "smallIcon": "https://bj.openstorage.cn/v1/iflytek/tts/common/icon/xiaokun_small.png", "sortId": 16, "speakerId": 25, "updateTime": "2018-09-19 20:51:08", "version": 1, "downloadUrl":"http://iflytek.bjdn.openstorage.cn/tts/87d/resource/xiaokun.zip", "prelisten":"https://bj.openstorage.cn/v1/iflytek/tts/common/listen/xiaokun.mp3" }, { "accent": "东北话", "age": 28, "appid": "5445f87d", "commonExpirationDate": "", "desc": "", "downloads": 9844, "engineType": "local", "engineVersion": 1, "ent": "", "experienceExpirationDate": "", "field": "东北话", "isActive": 1, "isDefault": 0, "isNew": 0, "isRecommend": 0, "isVip": 0, "largeIcon": "https://bj.openstorage.cn/v1/iflytek/tts/common/icon/xiaoqian_big.png", "level": 0, "listenPath": "aHR0cHM6Ly9iai5vcGVuc3RvcmFnZS5jbi92MS9pZmx5dGVrL3R0cy9jb21tb24vbGlzdGVuL3hpYW9xaWFuLm1wMw==", "name": "xiaoqian", "nickname": "女汉子", "price": 0, "resId": 628, "resPath": "aHR0cDovL2lmbHl0ZWsuYmpkbi5vcGVuc3RvcmFnZS5jbi90dHMvODdkL3Jlc291cmNlL3hpYW9xaWFuLnppcA==", "resSize": 735, "sex": "female", "smallIcon": "https://bj.openstorage.cn/v1/iflytek/tts/common/icon/xiaoqian_small.png", "sortId": 17, "speakerId": 11, "updateTime": "2018-09-19 20:51:08", "version": 1, "downloadUrl":"http://iflytek.bjdn.openstorage.cn/tts/87d/resource/xiaoqian.zip", "prelisten":"https://bj.openstorage.cn/v1/iflytek/tts/common/listen/xiaoqian.mp3" }, { "accent": "英文", "age": 40, "appid": "5445f87d", "commonExpirationDate": "", "desc": "", "downloads": 13246, "engineType": "local", "engineVersion": 1, "ent": "", "experienceExpirationDate": "", "field": "英文", "isActive": 1, "isDefault": 0, "isNew": 0, "isRecommend": 0, "isVip": 0, "largeIcon": "https://bj.openstorage.cn/v1/iflytek/tts/common/icon/catherine_big.png", "level": 0, "listenPath": "aHR0cHM6Ly9iai5vcGVuc3RvcmFnZS5jbi92MS9pZmx5dGVrL3R0cy9jb21tb24vbGlzdGVuL2NhdGhlcmluZS5tcDM=", "name": "catherine", "nickname": "希拉里", "price": 0, "resId": 640, "resPath": "aHR0cDovL2lmbHl0ZWsuYmpkbi5vcGVuc3RvcmFnZS5jbi90dHMvODdkL3Jlc291cmNlL2NhdGhlcmluZS56aXA=", "resSize": 3424, "sex": "female", "smallIcon": "https://bj.openstorage.cn/v1/iflytek/tts/common/icon/catherine_small.png", "sortId": 17, "speakerId": 20, "updateTime": "2018-09-19 20:51:08", "version": 1, "downloadUrl":"http://iflytek.bjdn.openstorage.cn/tts/87d/resource/catherine.zip", "prelisten":"https://bj.openstorage.cn/v1/iflytek/tts/common/listen/catherine.mp3" } ] } ================================================ FILE: zhuishushenqi/TXTReader/Speech/Model/speakers.plist ================================================ speakers accent 普通话 age 25 appid 5445f87d commonExpirationDate desc downloadUrl http://iflytek.bjdn.openstorage.cn/tts/87d/resource/xiaoxi.zip downloads 20806 engineType local engineVersion 1 ent experienceExpirationDate field 恐怖灵异 isActive 1 isDefault 0 isNew 0 isRecommend 0 isVip 0 largeIcon https://bj.openstorage.cn/v1/iflytek/tts/common/icon/xiaoxi_big.png level 3 listenPath aHR0cHM6Ly9iai5vcGVuc3RvcmFnZS5jbi92MS9pZmx5dGVrL3R0cy9jb21tb24vbGlzdGVuL3hpYW94aS5tcDM= name xiaoxi nickname 方木 prelisten https://bj.openstorage.cn/v1/iflytek/tts/common/listen/xiaoxi.mp3 price 0 resId 618 resPath aHR0cDovL2lmbHl0ZWsuYmpkbi5vcGVuc3RvcmFnZS5jbi90dHMvODdkL3Jlc291cmNlL3hpYW94aS56aXA= resSize 4295 sex male smallIcon https://bj.openstorage.cn/v1/iflytek/tts/common/icon/xiaoxi_small.png sortId 2 speakerId 51210 updateTime 2018-09-19 19:21:05 version 1 accent 普通话 age 18 appid 5445f87d commonExpirationDate desc downloadUrl http://iflytek.bjdn.openstorage.cn/tts/87d/resource/xiaoyin.zip downloads 29025 engineType local engineVersion 1 ent experienceExpirationDate field 都市言情 isActive 1 isDefault 0 isNew 0 isRecommend 0 isVip 0 largeIcon https://bj.openstorage.cn/v1/iflytek/tts/common/icon/xiaoyin_big.png level 3 listenPath aHR0cHM6Ly9iai5vcGVuc3RvcmFnZS5jbi92MS9pZmx5dGVrL3R0cy9jb21tb24vbGlzdGVuL3hpYW95aW4ubXAz name xiaoyin nickname 方茴 prelisten https://bj.openstorage.cn/v1/iflytek/tts/common/listen/xiaoyin.mp3 price 0 resId 616 resPath aHR0cDovL2lmbHl0ZWsuYmpkbi5vcGVuc3RvcmFnZS5jbi90dHMvODdkL3Jlc291cmNlL3hpYW95aW4uemlw resSize 4281 sex female smallIcon https://bj.openstorage.cn/v1/iflytek/tts/common/icon/xiaoyin_small.png sortId 4 speakerId 51115 updateTime 2018-09-19 20:21:07 version 1 accent 普通话 age 30 appid 5445f87d commonExpirationDate desc downloadUrl http://iflytek.bjdn.openstorage.cn/tts/87d/resource/xiaohou.zip downloads 25445 engineType local engineVersion 1 ent experienceExpirationDate field 原创出版 isActive 1 isDefault 0 isNew 0 isRecommend 0 isVip 0 largeIcon https://bj.openstorage.cn/v1/iflytek/tts/common/icon/xiaohou_big.png level 3 listenPath aHR0cHM6Ly9iai5vcGVuc3RvcmFnZS5jbi92MS9pZmx5dGVrL3R0cy9jb21tb24vbGlzdGVuL3hpYW9ob3UubXAz name xiaohou nickname 郭嘉 prelisten https://bj.openstorage.cn/v1/iflytek/tts/common/listen/xiaohou.mp3 price 0 resId 620 resPath aHR0cDovL2lmbHl0ZWsuYmpkbi5vcGVuc3RvcmFnZS5jbi90dHMvODdkL3Jlc291cmNlL3hpYW9ob3Uuemlw resSize 5188 sex male smallIcon https://bj.openstorage.cn/v1/iflytek/tts/common/icon/xiaohou_small.png sortId 4 speakerId 51200 updateTime 2018-09-19 15:20:57 version 1 accent 普通话 age 10 appid 5445f87d commonExpirationDate desc downloadUrl http://iflytek.bjdn.openstorage.cn/tts/87d/resource/nannan.zip downloads 20587 engineType local engineVersion 1 ent experienceExpirationDate field 普通发音人 isActive 1 isDefault 0 isNew 0 isRecommend 0 isVip 0 largeIcon https://bj.openstorage.cn/v1/iflytek/tts/common/icon/nannan_big.png level 0 listenPath aHR0cHM6Ly9iai5vcGVuc3RvcmFnZS5jbi92MS9pZmx5dGVrL3R0cy9jb21tb24vbGlzdGVuL25hbm5hbi5tcDM= name nannan nickname 小萝莉 prelisten https://bj.openstorage.cn/v1/iflytek/tts/common/listen/nannan.mp3 price 0 resId 624 resPath aHR0cDovL2lmbHl0ZWsuYmpkbi5vcGVuc3RvcmFnZS5jbi90dHMvODdkL3Jlc291cmNlL25hbm5hbi56aXA= resSize 1150 sex female smallIcon https://bj.openstorage.cn/v1/iflytek/tts/common/icon/nannan_small.png sortId 7 speakerId 7 updateTime 2018-09-19 20:21:07 version 1 accent 普通话 age 34 appid 5445f87d commonExpirationDate desc downloadUrl http://iflytek.bjdn.openstorage.cn/tts/87d/resource/xiaofeng.zip downloads 23476 engineType local engineVersion 1 ent experienceExpirationDate field 普通发音人 isActive 1 isDefault 0 isNew 0 isRecommend 0 isVip 0 largeIcon https://bj.openstorage.cn/v1/iflytek/tts/common/icon/xiaofeng_big.png level 0 listenPath aHR0cHM6Ly9iai5vcGVuc3RvcmFnZS5jbi92MS9pZmx5dGVrL3R0cy9jb21tb24vbGlzdGVuL3hpYW9mZW5nLm1wMw== name xiaofeng nickname 男主播 prelisten https://bj.openstorage.cn/v1/iflytek/tts/common/listen/xiaofeng.mp3 price 0 resId 608 resPath aHR0cDovL2lmbHl0ZWsuYmpkbi5vcGVuc3RvcmFnZS5jbi90dHMvODdkL3Jlc291cmNlL3hpYW9mZW5nLnppcA== resSize 1125 sex male smallIcon https://bj.openstorage.cn/v1/iflytek/tts/common/icon/xiaofeng_small.png sortId 8 speakerId 4 updateTime 2018-09-19 20:21:07 version 1 accent 普通话 age 22 appid 5445f87d commonExpirationDate desc downloadUrl http://iflytek.bjdn.openstorage.cn/tts/87d/resource/xiaojing.zip downloads 22708 engineType local engineVersion 1 ent experienceExpirationDate field 普通发音人 isActive 1 isDefault 0 isNew 0 isRecommend 0 isVip 0 largeIcon https://bj.openstorage.cn/v1/iflytek/tts/common/icon/xiaojing_big.png level 0 listenPath aHR0cHM6Ly9iai5vcGVuc3RvcmFnZS5jbi92MS9pZmx5dGVrL3R0cy9jb21tb24vbGlzdGVuL3hpYW9qaW5nLm1wMw== name xiaojing nickname 邻家姐姐 prelisten https://bj.openstorage.cn/v1/iflytek/tts/common/listen/xiaojing.mp3 price 0 resId 606 resPath aHR0cDovL2lmbHl0ZWsuYmpkbi5vcGVuc3RvcmFnZS5jbi90dHMvODdkL3Jlc291cmNlL3hpYW9qaW5nLnppcA== resSize 1226 sex female smallIcon https://bj.openstorage.cn/v1/iflytek/tts/common/icon/xiaojing_small.png sortId 9 speakerId 8 updateTime 2018-09-19 20:21:07 version 1 accent 普通话 age 20 appid 5445f87d commonExpirationDate desc downloadUrl http://iflytek.bjdn.openstorage.cn/tts/87d/resource/jiajia.zip downloads 23302 engineType local engineVersion 1 ent experienceExpirationDate field 普通发音人 isActive 1 isDefault 0 isNew 0 isRecommend 0 isVip 0 largeIcon https://bj.openstorage.cn/v1/iflytek/tts/common/icon/jiajia_big.png level 0 listenPath aHR0cHM6Ly9iai5vcGVuc3RvcmFnZS5jbi92MS9pZmx5dGVrL3R0cy9jb21tb24vbGlzdGVuL2ppYWppYS5tcDM= name jiajia nickname 嘉嘉老师 prelisten https://bj.openstorage.cn/v1/iflytek/tts/common/listen/jiajia.mp3 price 0 resId 626 resPath aHR0cDovL2lmbHl0ZWsuYmpkbi5vcGVuc3RvcmFnZS5jbi90dHMvODdkL3Jlc291cmNlL2ppYWppYS56aXA= resSize 1714 sex female smallIcon https://bj.openstorage.cn/v1/iflytek/tts/common/icon/jiajia_small.png sortId 11 speakerId 9 updateTime 2018-09-19 20:21:07 version 1 accent 粤语 age 26 appid 5445f87d commonExpirationDate desc downloadUrl http://iflytek.bjdn.openstorage.cn/tts/87d/resource/xiaomei.zip downloads 13046 engineType local engineVersion 1 ent experienceExpirationDate field 粤语 isActive 1 isDefault 0 isNew 0 isRecommend 0 isVip 0 largeIcon https://bj.openstorage.cn/v1/iflytek/tts/common/icon/xiaomei_big.png level 0 listenPath aHR0cHM6Ly9iai5vcGVuc3RvcmFnZS5jbi92MS9pZmx5dGVrL3R0cy9jb21tb24vbGlzdGVuL3hpYW9tZWlfbG9jYWwubXAz name xiaomei nickname 港姐 prelisten https://bj.openstorage.cn/v1/iflytek/tts/common/listen/xiaomei.mp3 price 0 resId 634 resPath aHR0cDovL2lmbHl0ZWsuYmpkbi5vcGVuc3RvcmFnZS5jbi90dHMvODdkL3Jlc291cmNlL3hpYW9tZWkuemlw resSize 1831 sex female smallIcon https://bj.openstorage.cn/v1/iflytek/tts/common/icon/xiaomei_small.png sortId 12 speakerId 15 updateTime 2018-09-19 20:51:08 version 1 accent 台湾话 age 32 appid 5445f87d commonExpirationDate desc downloadUrl http://iflytek.bjdn.openstorage.cn/tts/87d/resource/xiaolin.zip downloads 10551 engineType local engineVersion 1 ent experienceExpirationDate field 台湾话 isActive 1 isDefault 0 isNew 0 isRecommend 0 isVip 0 largeIcon https://bj.openstorage.cn/v1/iflytek/tts/common/icon/xiaolin_big.png level 0 listenPath aHR0cHM6Ly9iai5vcGVuc3RvcmFnZS5jbi92MS9pZmx5dGVrL3R0cy9jb21tb24vbGlzdGVuL3hpYW9saW4ubXAz name xiaolin nickname 佳宜 prelisten https://bj.openstorage.cn/v1/iflytek/tts/common/listen/xiaolin.mp3 price 0 resId 638 resPath aHR0cDovL2lmbHl0ZWsuYmpkbi5vcGVuc3RvcmFnZS5jbi90dHMvODdkL3Jlc291cmNlL3hpYW9saW4uemlw resSize 1616 sex female smallIcon https://bj.openstorage.cn/v1/iflytek/tts/common/icon/xiaolin_small.png sortId 13 speakerId 22 updateTime 2018-09-19 20:51:08 version 1 accent 湖南话 age 32 appid 5445f87d commonExpirationDate desc downloadUrl http://iflytek.bjdn.openstorage.cn/tts/87d/resource/xiaoqiang.zip downloads 8089 engineType local engineVersion 1 ent experienceExpirationDate field 湖南话 isActive 1 isDefault 0 isNew 0 isRecommend 0 isVip 0 largeIcon https://bj.openstorage.cn/v1/iflytek/tts/common/icon/xiaoqiang_big.png level 0 listenPath aHR0cHM6Ly9iai5vcGVuc3RvcmFnZS5jbi92MS9pZmx5dGVrL3R0cy9jb21tb24vbGlzdGVuL3hpYW9xaWFuZy5tcDM= name xiaoqiang nickname 涵涵 prelisten https://bj.openstorage.cn/v1/iflytek/tts/common/listen/xiaoqiang.mp3 price 0 resId 636 resPath aHR0cDovL2lmbHl0ZWsuYmpkbi5vcGVuc3RvcmFnZS5jbi90dHMvODdkL3Jlc291cmNlL3hpYW9xaWFuZy56aXA= resSize 600 sex male smallIcon https://bj.openstorage.cn/v1/iflytek/tts/common/icon/xiaoqiang_small.png sortId 14 speakerId 24 updateTime 2018-09-19 20:51:08 version 1 accent 四川话 age 32 appid 5445f87d commonExpirationDate desc downloadUrl http://iflytek.bjdn.openstorage.cn/tts/87d/resource/xiaorong.zip downloads 9843 engineType local engineVersion 1 ent experienceExpirationDate field 四川话 isActive 1 isDefault 0 isNew 0 isRecommend 0 isVip 0 largeIcon https://bj.openstorage.cn/v1/iflytek/tts/common/icon/xiaorong_big.png level 0 listenPath aHR0cHM6Ly9iai5vcGVuc3RvcmFnZS5jbi92MS9pZmx5dGVrL3R0cy9jb21tb24vbGlzdGVuL3hpYW9yb25nLm1wMw== name xiaorong nickname 辣妹子 prelisten https://bj.openstorage.cn/v1/iflytek/tts/common/listen/xiaorong.mp3 price 0 resId 630 resPath aHR0cDovL2lmbHl0ZWsuYmpkbi5vcGVuc3RvcmFnZS5jbi90dHMvODdkL3Jlc291cmNlL3hpYW9yb25nLnppcA== resSize 489 sex female smallIcon https://bj.openstorage.cn/v1/iflytek/tts/common/icon/xiaorong_small.png sortId 15 speakerId 14 updateTime 2018-09-19 20:51:08 version 1 accent 河南话 age 28 appid 5445f87d commonExpirationDate desc downloadUrl http://iflytek.bjdn.openstorage.cn/tts/87d/resource/xiaokun.zip downloads 7722 engineType local engineVersion 1 ent experienceExpirationDate field 河南话 isActive 1 isDefault 0 isNew 0 isRecommend 0 isVip 0 largeIcon https://bj.openstorage.cn/v1/iflytek/tts/common/icon/xiaokun_big.png level 0 listenPath aHR0cHM6Ly9iai5vcGVuc3RvcmFnZS5jbi92MS9pZmx5dGVrL3R0cy9jb21tb24vbGlzdGVuL3hpYW9rdW4ubXAzCg== name xiaokun nickname 傻根 prelisten https://bj.openstorage.cn/v1/iflytek/tts/common/listen/xiaokun.mp3 price 0 resId 632 resPath aHR0cDovL2lmbHl0ZWsuYmpkbi5vcGVuc3RvcmFnZS5jbi90dHMvODdkL3Jlc291cmNlL3hpYW9rdW4uemlw resSize 580 sex male smallIcon https://bj.openstorage.cn/v1/iflytek/tts/common/icon/xiaokun_small.png sortId 16 speakerId 25 updateTime 2018-09-19 20:51:08 version 1 accent 东北话 age 28 appid 5445f87d commonExpirationDate desc downloadUrl http://iflytek.bjdn.openstorage.cn/tts/87d/resource/xiaoqian.zip downloads 9844 engineType local engineVersion 1 ent experienceExpirationDate field 东北话 isActive 1 isDefault 0 isNew 0 isRecommend 0 isVip 0 largeIcon https://bj.openstorage.cn/v1/iflytek/tts/common/icon/xiaoqian_big.png level 0 listenPath aHR0cHM6Ly9iai5vcGVuc3RvcmFnZS5jbi92MS9pZmx5dGVrL3R0cy9jb21tb24vbGlzdGVuL3hpYW9xaWFuLm1wMw== name xiaoqian nickname 女汉子 prelisten https://bj.openstorage.cn/v1/iflytek/tts/common/listen/xiaoqian.mp3 price 0 resId 628 resPath aHR0cDovL2lmbHl0ZWsuYmpkbi5vcGVuc3RvcmFnZS5jbi90dHMvODdkL3Jlc291cmNlL3hpYW9xaWFuLnppcA== resSize 735 sex female smallIcon https://bj.openstorage.cn/v1/iflytek/tts/common/icon/xiaoqian_small.png sortId 17 speakerId 11 updateTime 2018-09-19 20:51:08 version 1 accent 英文 age 40 appid 5445f87d commonExpirationDate desc downloadUrl http://iflytek.bjdn.openstorage.cn/tts/87d/resource/catherine.zip downloads 13246 engineType local engineVersion 1 ent experienceExpirationDate field 英文 isActive 1 isDefault 0 isNew 0 isRecommend 0 isVip 0 largeIcon https://bj.openstorage.cn/v1/iflytek/tts/common/icon/catherine_big.png level 0 listenPath aHR0cHM6Ly9iai5vcGVuc3RvcmFnZS5jbi92MS9pZmx5dGVrL3R0cy9jb21tb24vbGlzdGVuL2NhdGhlcmluZS5tcDM= name catherine nickname 希拉里 prelisten https://bj.openstorage.cn/v1/iflytek/tts/common/listen/catherine.mp3 price 0 resId 640 resPath aHR0cDovL2lmbHl0ZWsuYmpkbi5vcGVuc3RvcmFnZS5jbi90dHMvODdkL3Jlc291cmNlL2NhdGhlcmluZS56aXA= resSize 3424 sex female smallIcon https://bj.openstorage.cn/v1/iflytek/tts/common/icon/catherine_small.png sortId 17 speakerId 20 updateTime 2018-09-19 20:51:08 version 1 ================================================ FILE: zhuishushenqi/TXTReader/Speech/View/AKPickerView.h ================================================ // // AKPickerView.h // AKPickerViewSample // // Created by Akio Yasui on 3/29/14. // Copyright (c) 2014 Akio Yasui. All rights reserved. // #import typedef NS_ENUM(NSInteger, AKPickerViewStyle) { AKPickerViewStyle3D = 1, AKPickerViewStyleFlat }; @class AKPickerView; @protocol AKPickerViewDataSource @required - (NSUInteger)numberOfItemsInPickerView:(AKPickerView *)pickerView; @optional - (NSString *)pickerView:(AKPickerView *)pickerView titleForItem:(NSInteger)item; - (UIImage *)pickerView:(AKPickerView *)pickerView imageForItem:(NSInteger)item; @end @protocol AKPickerViewDelegate @optional - (void)pickerView:(AKPickerView *)pickerView didSelectItem:(NSInteger)item; - (CGSize)pickerView:(AKPickerView *)pickerView marginForItem:(NSInteger)item; - (void)pickerView:(AKPickerView *)pickerView configureLabel:(UILabel * const)label forItem:(NSInteger)item; @end @interface AKPickerView : UIView @property (nonatomic, weak) id dataSource; @property (nonatomic, weak) id delegate; @property (nonatomic, strong) UIFont *font; @property (nonatomic, strong) UIFont *highlightedFont; @property (nonatomic, strong) UIColor *textColor; @property (nonatomic, strong) UIColor *highlightedTextColor; @property (nonatomic, assign) CGFloat interitemSpacing; @property (nonatomic, assign) CGFloat fisheyeFactor; // 0...1; slight value recommended such as 0.0001 @property (nonatomic, assign, getter=isMaskDisabled) BOOL maskDisabled; @property (nonatomic, assign) AKPickerViewStyle pickerViewStyle; @property (nonatomic, assign, readonly) NSUInteger selectedItem; @property (nonatomic, assign, readonly) CGPoint contentOffset; - (void)reloadData; - (void)scrollToItem:(NSUInteger)item animated:(BOOL)animated; - (void)selectItem:(NSUInteger)item animated:(BOOL)animated notifySelection:(BOOL)notifySelection; - (void)selectItem:(NSUInteger)item animated:(BOOL)animated; @end ================================================ FILE: zhuishushenqi/TXTReader/Speech/View/AKPickerView.m ================================================ // // AKPickerView.m // AKPickerViewSample // // Created by Akio Yasui on 3/29/14. // Copyright (c) 2014 Akio Yasui. All rights reserved. // #import "AKPickerView.h" #import @class AKCollectionViewLayout; @protocol AKCollectionViewLayoutDelegate - (AKPickerViewStyle)pickerViewStyleForCollectionViewLayout:(AKCollectionViewLayout *)layout; @end @interface AKCollectionViewCell : UICollectionViewCell @property (nonatomic, strong) UILabel *label; @property (nonatomic, strong) UIImageView *imageView; @property (nonatomic, strong) UIFont *font; @property (nonatomic, strong) UIFont *highlightedFont; @end @interface AKCollectionViewLayout : UICollectionViewFlowLayout @property (nonatomic, assign) id delegate; @end @interface AKPickerViewDelegateIntercepter : NSObject @property (nonatomic, weak) AKPickerView *pickerView; @property (nonatomic, weak) id delegate; @end @interface AKPickerView () @property (nonatomic, strong) UICollectionView *collectionView; @property (nonatomic, assign) NSUInteger selectedItem; @property (nonatomic, strong) AKPickerViewDelegateIntercepter *intercepter; - (CGFloat)offsetForItem:(NSUInteger)item; - (void)didEndScrolling; - (CGSize)sizeForString:(NSString *)string; @end @implementation AKPickerView - (void)initialize { self.font = self.font ?: [UIFont fontWithName:@"HelveticaNeue-Light" size:20]; self.highlightedFont = self.highlightedFont ?: [UIFont fontWithName:@"HelveticaNeue" size:20]; self.textColor = self.textColor ?: [UIColor darkGrayColor]; self.highlightedTextColor = self.highlightedTextColor ?: [UIColor blackColor]; self.pickerViewStyle = self.pickerViewStyle ?: AKPickerViewStyle3D; self.maskDisabled = self.maskDisabled; [self.collectionView removeFromSuperview]; self.collectionView = [[UICollectionView alloc] initWithFrame:self.bounds collectionViewLayout:[self collectionViewLayout]]; self.collectionView.showsHorizontalScrollIndicator = NO; self.collectionView.backgroundColor = [UIColor clearColor]; self.collectionView.decelerationRate = UIScrollViewDecelerationRateFast; self.collectionView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; self.collectionView.dataSource = self; [self.collectionView registerClass:[AKCollectionViewCell class] forCellWithReuseIdentifier:NSStringFromClass([AKCollectionViewCell class])]; [self addSubview:self.collectionView]; self.intercepter = [AKPickerViewDelegateIntercepter new]; self.intercepter.pickerView = self; self.intercepter.delegate = self.delegate; self.collectionView.delegate = self.intercepter; } - (id)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { [self initialize]; } return self; } - (id)initWithCoder:(NSCoder *)aDecoder { self = [super initWithCoder:aDecoder]; if (self) { [self initialize]; } return self; } - (void)dealloc { self.collectionView.delegate = nil; } #pragma mark - - (void)layoutSubviews { [super layoutSubviews]; self.collectionView.collectionViewLayout = [self collectionViewLayout]; [self scrollToItem:self.selectedItem animated:NO]; self.collectionView.layer.mask.frame = self.collectionView.bounds; } - (CGSize)intrinsicContentSize { return CGSizeMake(UIViewNoIntrinsicMetric, MAX(self.font.lineHeight, self.highlightedFont.lineHeight)); } - (CGPoint)contentOffset { return self.collectionView.contentOffset; } #pragma mark - - (void)setDelegate:(id)delegate { if (![_delegate isEqual:delegate]) { _delegate = delegate; self.intercepter.delegate = delegate; } } - (void)setFisheyeFactor:(CGFloat)fisheyeFactor { _fisheyeFactor = fisheyeFactor; CATransform3D transform = CATransform3DIdentity; transform.m34 = -MAX(MIN(self.fisheyeFactor, 1.0), 0.0); self.collectionView.layer.sublayerTransform = transform; } - (void)setMaskDisabled:(BOOL)maskDisabled { _maskDisabled = maskDisabled; self.collectionView.layer.mask = maskDisabled ? nil : ({ CAGradientLayer *maskLayer = [CAGradientLayer layer]; maskLayer.frame = self.collectionView.bounds; maskLayer.colors = @[(id)[[UIColor clearColor] CGColor], (id)[[UIColor blackColor] CGColor], (id)[[UIColor blackColor] CGColor], (id)[[UIColor clearColor] CGColor],]; maskLayer.locations = @[@0.0, @0.33, @0.66, @1.0]; maskLayer.startPoint = CGPointMake(0.0, 0.0); maskLayer.endPoint = CGPointMake(1.0, 0.0); maskLayer; }); } #pragma mark - - (AKCollectionViewLayout *)collectionViewLayout { AKCollectionViewLayout *layout = [AKCollectionViewLayout new]; layout.delegate = self; return layout; } - (CGSize)sizeForString:(NSString *)string { CGSize size; CGSize highlightedSize; #ifdef __IPHONE_7_0 size = [string sizeWithAttributes:@{NSFontAttributeName: self.font}]; highlightedSize = [string sizeWithAttributes:@{NSFontAttributeName: self.highlightedFont}]; #else size = [string sizeWithFont:self.font]; highlightedSize = [string sizeWithFont:self.highlightedFont]; #endif return CGSizeMake(ceilf(MAX(size.width, highlightedSize.width)), ceilf(MAX(size.height, highlightedSize.height))); } #pragma mark - - (void)reloadData { [self invalidateIntrinsicContentSize]; [self.collectionView.collectionViewLayout invalidateLayout]; [self.collectionView reloadData]; [self selectItem:self.selectedItem animated:NO notifySelection:NO]; } - (CGFloat)offsetForItem:(NSUInteger)item { NSAssert(item < [self.collectionView numberOfItemsInSection:0], @"item out of range; '%lu' passed, but the maximum is '%lu'",(unsigned long)item, (long)[self.collectionView numberOfItemsInSection:0]); CGFloat offset = 0.0; for (NSInteger i = 0; i < item; i++) { NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0]; CGSize cellSize = [self collectionView:self.collectionView layout:self.collectionView.collectionViewLayout sizeForItemAtIndexPath:indexPath]; offset += cellSize.width; } NSIndexPath *firstIndexPath = [NSIndexPath indexPathForItem:0 inSection:0]; CGSize firstSize = [self collectionView:self.collectionView layout:self.collectionView.collectionViewLayout sizeForItemAtIndexPath:firstIndexPath]; NSIndexPath *selectedIndexPath = [NSIndexPath indexPathForItem:item inSection:0]; CGSize selectedSize = [self collectionView:self.collectionView layout:self.collectionView.collectionViewLayout sizeForItemAtIndexPath:selectedIndexPath]; offset -= (firstSize.width - selectedSize.width) / 2; return offset; } - (void)scrollToItem:(NSUInteger)item animated:(BOOL)animated { switch (self.pickerViewStyle) { case AKPickerViewStyleFlat: { [self.collectionView scrollToItemAtIndexPath:[NSIndexPath indexPathForItem:item inSection:0] atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally animated:animated]; break; } case AKPickerViewStyle3D: { [self.collectionView setContentOffset:CGPointMake([self offsetForItem:item], self.collectionView.contentOffset.y) animated:animated]; break; } default: break; } } - (void)selectItem:(NSUInteger)item animated:(BOOL)animated { [self selectItem:item animated:animated notifySelection:YES]; } - (void)selectItem:(NSUInteger)item animated:(BOOL)animated notifySelection:(BOOL)notifySelection { [self.collectionView selectItemAtIndexPath:[NSIndexPath indexPathForItem:item inSection:0] animated:animated scrollPosition:UICollectionViewScrollPositionNone]; [self scrollToItem:item animated:animated]; self.selectedItem = item; if (notifySelection && [self.delegate respondsToSelector:@selector(pickerView:didSelectItem:)]) [self.delegate pickerView:self didSelectItem:item]; } - (void)didEndScrolling { switch (self.pickerViewStyle) { case AKPickerViewStyleFlat: { CGPoint center = [self convertPoint:self.collectionView.center toView:self.collectionView]; NSIndexPath *indexPath = [self.collectionView indexPathForItemAtPoint:center]; [self selectItem:indexPath.item animated:YES]; break; } case AKPickerViewStyle3D: { if ([self.dataSource numberOfItemsInPickerView:self]) { for (NSUInteger i = 0; i < [self collectionView:self.collectionView numberOfItemsInSection:0]; i++) { NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0]; AKCollectionViewCell *cell = (AKCollectionViewCell *)[self.collectionView cellForItemAtIndexPath:indexPath]; if ([self offsetForItem:i] + cell.bounds.size.width / 2 > self.collectionView.contentOffset.x) { [self selectItem:i animated:YES]; break; } } } break; } default: break; } } #pragma mark - - (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView { return ([self.dataSource numberOfItemsInPickerView:self] > 0); } - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { return [self.dataSource numberOfItemsInPickerView:self]; } - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { AKCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:NSStringFromClass([AKCollectionViewCell class]) forIndexPath:indexPath]; if ([self.dataSource respondsToSelector:@selector(pickerView:titleForItem:)]) { NSString *title = [self.dataSource pickerView:self titleForItem:indexPath.item]; cell.label.text = title; cell.label.textColor = self.textColor; cell.label.highlightedTextColor = self.highlightedTextColor; cell.label.font = self.font; cell.font = self.font; cell.highlightedFont = self.highlightedFont; cell.label.bounds = (CGRect){CGPointZero, [self sizeForString:title]}; if ([self.delegate respondsToSelector:@selector(pickerView:marginForItem:)]) { CGSize margin = [self.delegate pickerView:self marginForItem:indexPath.item]; cell.label.frame = CGRectInset(cell.label.frame, -margin.width, -margin.height); } if ([self.delegate respondsToSelector:@selector(pickerView:configureLabel:forItem:)]) { [self.delegate pickerView:self configureLabel:cell.label forItem:indexPath.item]; } } else if ([self.dataSource respondsToSelector:@selector(pickerView:imageForItem:)]) { cell.imageView.image = [self.dataSource pickerView:self imageForItem:indexPath.item]; } cell.selected = (indexPath.item == self.selectedItem); return cell; } - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath { CGSize size = CGSizeMake(self.interitemSpacing, collectionView.bounds.size.height); if ([self.dataSource respondsToSelector:@selector(pickerView:titleForItem:)]) { NSString *title = [self.dataSource pickerView:self titleForItem:indexPath.item]; size.width += [self sizeForString:title].width; if ([self.delegate respondsToSelector:@selector(pickerView:marginForItem:)]) { CGSize margin = [self.delegate pickerView:self marginForItem:indexPath.item]; size.width += margin.width * 2; } } else if ([self.dataSource respondsToSelector:@selector(pickerView:imageForItem:)]) { UIImage *image = [self.dataSource pickerView:self imageForItem:indexPath.item]; size.width += image.size.width; } return size; } - (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section { return 0.0; } - (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section { return 0.0; } - (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section { NSInteger number = [self collectionView:collectionView numberOfItemsInSection:section]; NSIndexPath *firstIndexPath = [NSIndexPath indexPathForItem:0 inSection:section]; CGSize firstSize = [self collectionView:collectionView layout:collectionViewLayout sizeForItemAtIndexPath:firstIndexPath]; NSIndexPath *lastIndexPath = [NSIndexPath indexPathForItem:number - 1 inSection:section]; CGSize lastSize = [self collectionView:collectionView layout:collectionViewLayout sizeForItemAtIndexPath:lastIndexPath]; return UIEdgeInsetsMake(0, (collectionView.bounds.size.width - firstSize.width) / 2, 0, (collectionView.bounds.size.width - lastSize.width) / 2); } - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath { [self selectItem:indexPath.item animated:YES]; } #pragma mark - - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView { if ([self.delegate respondsToSelector:@selector(scrollViewDidEndDecelerating:)]) [self.delegate scrollViewDidEndDecelerating:scrollView]; if (!scrollView.isTracking) [self didEndScrolling]; } - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate { if ([self.delegate respondsToSelector:@selector(scrollViewDidEndDragging:willDecelerate:)]) [self.delegate scrollViewDidEndDragging:scrollView willDecelerate:decelerate]; if (!decelerate) [self didEndScrolling]; } - (void)scrollViewDidScroll:(UIScrollView *)scrollView { if ([self.delegate respondsToSelector:@selector(scrollViewDidScroll:)]) [self.delegate scrollViewDidScroll:scrollView]; [CATransaction begin]; [CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions]; self.collectionView.layer.mask.frame = self.collectionView.bounds; [CATransaction commit]; } #pragma mark - - (AKPickerViewStyle)pickerViewStyleForCollectionViewLayout:(AKCollectionViewLayout *)layout { return self.pickerViewStyle; } @end @implementation AKCollectionViewCell - (void)initialize { self.layer.doubleSided = NO; self.label = [[UILabel alloc] initWithFrame:self.contentView.bounds]; self.label.backgroundColor = [UIColor clearColor]; self.label.textAlignment = NSTextAlignmentCenter; self.label.textColor = [UIColor grayColor]; self.label.numberOfLines = 1; self.label.lineBreakMode = NSLineBreakByTruncatingTail; self.label.highlightedTextColor = [UIColor blackColor]; self.label.font = [UIFont systemFontOfSize:[UIFont systemFontSize]]; self.label.autoresizingMask = (UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleRightMargin); [self.contentView addSubview:self.label]; self.imageView = [[UIImageView alloc] initWithFrame:self.contentView.bounds]; self.imageView.backgroundColor = [UIColor clearColor]; self.imageView.contentMode = UIViewContentModeCenter; self.imageView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; [self.contentView addSubview:self.imageView]; } - (id)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { [self initialize]; } return self; } - (id)initWithCoder:(NSCoder *)aDecoder { self = [super initWithCoder:aDecoder]; if (self) { [self initialize]; } return self; } - (void)setSelected:(BOOL)selected { [super setSelected:selected]; CATransition *transition = [CATransition animation]; [transition setType:kCATransitionFade]; [transition setDuration:0.15]; [self.label.layer addAnimation:transition forKey:nil]; self.label.font = self.selected ? self.highlightedFont : self.font; } @end @interface AKCollectionViewLayout () @property (nonatomic, assign) CGFloat width; @property (nonatomic, assign) CGFloat midX; @property (nonatomic, assign) CGFloat maxAngle; @end @implementation AKCollectionViewLayout - (id)init { self = [super init]; if (self) { self.sectionInset = UIEdgeInsetsMake(0.0, 0.0, 0.0, 0.0); self.scrollDirection = UICollectionViewScrollDirectionHorizontal; self.minimumLineSpacing = 0.0; } return self; } - (void)prepareLayout { CGRect visibleRect = (CGRect){self.collectionView.contentOffset, self.collectionView.bounds.size}; self.midX = CGRectGetMidX(visibleRect); self.width = CGRectGetWidth(visibleRect) / 2; self.maxAngle = M_PI_2; } - (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds { return YES; } - (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath { UICollectionViewLayoutAttributes *attributes = [super layoutAttributesForItemAtIndexPath:indexPath]; switch ([self.delegate pickerViewStyleForCollectionViewLayout:self]) { case AKPickerViewStyleFlat: { return attributes; break; } case AKPickerViewStyle3D: { CGFloat distance = CGRectGetMidX(attributes.frame) - self.midX; CGFloat currentAngle = self.maxAngle * distance / self.width / M_PI_2; CATransform3D transform = CATransform3DIdentity; transform = CATransform3DTranslate(transform, -distance, 0, -self.width); transform = CATransform3DRotate(transform, currentAngle, 0, 1, 0); transform = CATransform3DTranslate(transform, 0, 0, self.width); attributes.transform3D = transform; attributes.alpha = (ABS(currentAngle) < self.maxAngle); return attributes; break; } default: return nil; break; } } - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect { switch ([self.delegate pickerViewStyleForCollectionViewLayout:self]) { case AKPickerViewStyleFlat: { return [super layoutAttributesForElementsInRect:rect]; break; } case AKPickerViewStyle3D: { NSMutableArray *attributes = [NSMutableArray array]; if ([self.collectionView numberOfSections]) { for (NSInteger i = 0; i < [self.collectionView numberOfItemsInSection:0]; i++) { NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0]; [attributes addObject:[self layoutAttributesForItemAtIndexPath:indexPath]]; } } return attributes; break; } default: return nil; break; } } @end @implementation AKPickerViewDelegateIntercepter - (id)forwardingTargetForSelector:(SEL)aSelector { if ([self.pickerView respondsToSelector:aSelector]) return self.pickerView; if ([self.delegate respondsToSelector:aSelector]) return self.delegate; return [super forwardingTargetForSelector:aSelector]; } - (BOOL)respondsToSelector:(SEL)aSelector { if ([self.pickerView respondsToSelector:aSelector]) return YES; if ([self.delegate respondsToSelector:aSelector]) return YES; return [super respondsToSelector:aSelector]; } @end ================================================ FILE: zhuishushenqi/TXTReader/Speech/View/ZSSpeechView.swift ================================================ // // ZSSpeechView.swift // zhuishushenqi // // Created by caony on 2018/9/23. // Copyright © 2018年 QS. All rights reserved. // import UIKit protocol ZSSpeechDelegate:class { // 改变朗读速度 0-100 func speechView(speechView:ZSSpeechView, change speed:Float) // 变更发音人 func speechView(speechView:ZSSpeechView, change speaker:Speaker) // 变更定时时间 func speechView(speechView:ZSSpeechView, change time:TimeInterval) } class ZSSpeechView: UIView { fileprivate var speakers:[Speaker] = [] fileprivate var timers:[[String:Any]] = [ ["title":"5分钟","time":300.00], ["title":"15分钟","time":900.00], ["title":"30分钟","time":1800.00], ["title":"60分钟","time":3600.00] ] var startHandler:ZSBaseCallback? var stopHandler:ZSBaseCallback? var speaker:Speaker { return speakers[Int(speakerPicker.selectedItem)] } weak var delegate:ZSSpeechDelegate? fileprivate lazy var backgroundView:UIView = { let view = UIView(frame: CGRect(x: 0, y: 0, width: ScreenWidth, height: ScreenHeight)) view.isUserInteractionEnabled = true return view }() fileprivate lazy var readerView:UIView = { let view = UIView(frame: CGRect.zero) view.backgroundColor = UIColor.black view.alpha = 0.8 view.isUserInteractionEnabled = true return view }() fileprivate lazy var speedSlider:UISlider = { let slider = UISlider(frame: CGRect.zero) slider.minimumTrackTintColor = UIColor.red slider.maximumValue = 100 slider.minimumValue = 0 slider.value = 50 slider.addTarget(self, action: #selector(speedChange(slider:)), for: .valueChanged) return slider }() fileprivate lazy var timerPicker:AKPickerView = { let picker = AKPickerView(frame: CGRect.zero) picker.delegate = self; picker.dataSource = self; picker.autoresizingMask = [UIView.AutoresizingMask.flexibleWidth, UIView.AutoresizingMask.flexibleHeight] picker.textColor = UIColor.white; picker.font = UIFont(name: "HelveticaNeue-Light", size: 17) picker.highlightedFont = UIFont(name: "HelveticaNeue", size: 17) picker.highlightedTextColor = UIColor(red: 1.0, green: 168.0/255.0, blue: 0.0, alpha: 1.0) picker.interitemSpacing = 20.0 picker.fisheyeFactor = 0.001 picker.pickerViewStyle = AKPickerViewStyle.styleFlat picker.isMaskDisabled = false return picker }() fileprivate lazy var speakerPicker:AKPickerView = { let picker = AKPickerView(frame: CGRect.zero) picker.delegate = self; picker.dataSource = self; picker.autoresizingMask = [UIView.AutoresizingMask.flexibleWidth, UIView.AutoresizingMask.flexibleHeight] picker.textColor = UIColor.white; picker.font = UIFont(name: "HelveticaNeue-Light", size: 17) picker.highlightedFont = UIFont(name: "HelveticaNeue", size: 17) picker.highlightedTextColor = UIColor(red: 1.0, green: 168.0/255.0, blue: 0.0, alpha: 1.0) picker.interitemSpacing = 20.0 picker.fisheyeFactor = 0.001 picker.pickerViewStyle = AKPickerViewStyle.styleFlat picker.isMaskDisabled = false return picker }() fileprivate lazy var speedView:ZSSpeechLine = { let speedLine = ZSSpeechLine(frame: CGRect(x: 0, y: 0, width: ScreenWidth, height: 60)) speedLine.titleLabel.text = "语速:" return speedLine }() fileprivate lazy var speakerView:ZSSpeechLine = { let speedLine = ZSSpeechLine(frame: CGRect(x: 0, y: 0, width: ScreenWidth, height: 60)) speedLine.titleLabel.text = "发音:" return speedLine }() fileprivate lazy var timerView:ZSSpeechLine = { let speedLine = ZSSpeechLine(frame: CGRect(x: 0, y: 0, width: ScreenWidth, height: 60)) speedLine.titleLabel.text = "定时:" return speedLine }() fileprivate lazy var stopButton:UIButton = { let button = UIButton(type: .custom) button.layer.borderWidth = 1 button.layer.borderColor = UIColor.white.cgColor button.setTitle("退出朗读", for: .normal) button.setTitleColor(UIColor.white, for: .normal) button.layer.cornerRadius = 3 return button }() fileprivate lazy var startButton:UIButton = { let button = UIButton(type: .custom) button.layer.borderWidth = 1 button.layer.borderColor = UIColor.white.cgColor button.setTitle("开始", for: .normal) button.setTitle("暂停", for: .selected) button.setTitleColor(UIColor.white, for: .normal) button.layer.cornerRadius = 3 return button }() func show() { speakers = TTSConfig.share.availableSpeakers() speakerPicker.reloadData() KeyWindow?.addSubview(self) } func hiden() { removeFromSuperview() } @objc fileprivate func speedChange(slider:UISlider) { delegate?.speechView(speechView: self, change: slider.value) } override init(frame: CGRect) { super.init(frame: frame) self.addSubview(backgroundView) backgroundView.addSubview(readerView) readerView.addSubview(speedView) readerView.addSubview(speakerView) readerView.addSubview(timerView) readerView.addSubview(startButton) readerView.addSubview(stopButton) speedView.addSubview(speedSlider) speakerView.addSubview(speakerPicker) timerView.addSubview(timerPicker) speakers = TTSConfig.share.availableSpeakers() isUserInteractionEnabled = true stopButton.addTarget(self, action: #selector(stopAction(stop:)), for: .touchUpInside) startButton.addTarget(self, action: #selector(startAction(start:)), for: .touchUpInside) } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func layoutSubviews() { super.layoutSubviews() readerView.frame = CGRect(x: 0, y: ScreenHeight - 300, width: ScreenWidth, height: 300) speedView.frame = CGRect(x: 60, y: 0, width: readerView.frame.width - 120, height: 60) speakerView.frame = CGRect(x: 60, y: 60, width: speedView.frame.width, height: 60) timerView.frame = CGRect(x: 60, y: 120, width: speakerView.frame.width, height: 60) stopButton.frame = CGRect(x: 60, y: 180 + 30, width: ScreenWidth/2 - 70, height: 50) startButton.frame = CGRect(x: ScreenWidth - 60 - (ScreenWidth/2 - 70), y: timerView.bottom + 30, width: ScreenWidth/2 - 70, height: 50) speedSlider.frame = CGRect(x: 60, y: 0, width: speedView.frame.width - 100, height: speedView.frame.height) speakerPicker.frame = CGRect(x: 60, y: 0, width: speakerView.frame.width - 60, height: speakerView.frame.height) timerPicker.frame = CGRect(x: 60, y: 0, width: timerView.frame.width - 60, height: timerView.frame.height) } @objc func stopAction(stop:UIButton) { stopHandler?(nil) } @objc func startAction(start:UIButton) { start.isSelected = !start.isSelected startHandler?(start.isSelected) } } extension ZSSpeechView:AKPickerViewDataSource,AKPickerViewDelegate { func numberOfItems(in pickerView: AKPickerView!) -> UInt { if pickerView == speakerPicker { if speakers.count > 0 { return UInt(speakers.count) } return 1 } return UInt(timers.count) } func pickerView(_ pickerView: AKPickerView!, titleForItem item: Int) -> String! { if pickerView == speakerPicker { if speakers.count > 0 { return speakers[item].nickname } return "在线优声" } return timers[item]["title"] as? String ?? "" } func pickerView(_ pickerView: AKPickerView!, didSelectItem item: Int) { if pickerView == speakerPicker { delegate?.speechView(speechView: self, change: speakers[item]) } else { delegate?.speechView(speechView: self, change: timers[item]["time"] as? TimeInterval ?? 0.0) } } } class ZSSpeechLine: UIView { lazy var titleLabel:UILabel = { let label = UILabel(frame: CGRect(x: 0, y: 15, width: 50, height: 30)) label.textColor = UIColor.white label.font = UIFont.systemFont(ofSize: 17) return label }() override init(frame: CGRect) { super.init(frame: frame) self.isUserInteractionEnabled = true self.addSubview(self.titleLabel) } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } } ================================================ FILE: zhuishushenqi/Vendor/CTDisplayText/Source/CTDisplayText.h ================================================ // // CTDisplayText.h // zhuishushenqi // // Created by yung on 2018/1/9. // Copyright © 2018年 QS. All rights reserved. // #ifndef CTDisplayText_h #define CTDisplayText_h #import "CTDisplayView.h" #import "CoreTextData.h" #import "CoreTextImageData.h" #import "CoreTextLinkData.h" #import "CoreTextUtils.h" #import "CTFrameParser.h" #import "CTFrameParserConfig.h" //#import "UIView+frameAdjust.h" #endif /* CTDisplayText_h */ ================================================ FILE: zhuishushenqi/Vendor/CTDisplayText/Source/CTFrameParser.h ================================================ // // CTFrameParser.h // CoreTextDemo // // Created by yung on 2017/7/25. // Copyright (c) 2017年 yung. All rights reserved. // #import #import "CoreTextData.h" #import "CTFrameParserConfig.h" @interface CTFrameParser : NSObject + (NSMutableDictionary *)attributesWithConfig:(CTFrameParserConfig *)config; + (CoreTextData *)parseContent:(NSString *)content config:(CTFrameParserConfig*)config; + (CoreTextData *)parseAttributedContent:(NSAttributedString *)content config:(CTFrameParserConfig*)config; + (CoreTextData *)parseTemplateFile:(NSString *)path config:(CTFrameParserConfig*)config; + (CoreTextData *)parseString:(NSString *)string config:(CTFrameParserConfig *)config; @end ================================================ FILE: zhuishushenqi/Vendor/CTDisplayText/Source/CTFrameParser.m ================================================ // // CTFrameParser.m // CoreTextDemo // // Created by yung on 2017/7/25. // Copyright (c) 2017年 yung. All rights reserved. // #import "CTFrameParser.h" #import "CTFrameParserConfig.h" #import "CoreTextImageData.h" #import "CoreTextLinkData.h" #import "RegexKitLite.h" //《❤温馨小贴士》 static NSString *const kImageViewPattern = @"(《.*?》)"; //static NSString *const kImageViewPattern = @"(?《.*?》)"; //[[post:5b50550d6f788aef59667822 【传送门】告别燥热?这样玩转书单还有惊喜大礼!]] static NSString *const kTextLinkPattern = @"(\\[\\[.*?\\]\\])"; //{{type:image,url:http%3A%2F%2Fstatics.zhuishushenqi.com%2Fpost%2F151678369762541,size:420-422}} static NSString *const kBookPattern = @"(\\{\\{.*?\\}\\})"; // 环视 //static NSString *const kBookPattern = @"(?《.*?》)"; static NSMutableArray *imageInfoArr; @implementation CTFrameParser static CGFloat ascentCallback(void *ref){ NSDictionary *dict = [(__bridge NSDictionary*)ref copy]; return [(NSNumber*)[dict objectForKey:@"height"] floatValue]; } static CGFloat descentCallback(void *ref){ return 0; } static CGFloat widthCallback(void* ref){ return [(NSNumber*)[(__bridge NSDictionary*)ref objectForKey:@"width"] floatValue]; } + (NSMutableDictionary *)attributesWithConfig:(CTFrameParserConfig *)config { CGFloat fontSize = config.fontSize; // CTFontCreateUIFontForLanguage(kCTFontUIFontSystem, fontSize, <#CFStringRef _Nullable language#>) // CTFontRef fontRef = (__bridge CTFontRef)config.textFont; // CTFontRef fontRef = CTFontCreateWithName((__bridge CFStringRef)@"", fontSize, NULL); CTFontRef fontRef = CTFontCreateWithName((CFStringRef)@"ArialMT", fontSize, NULL); CGFloat lineSpacing = config.lineSpace; const CFIndex kNumberOfSettings = 3; CTParagraphStyleSetting theSettings[kNumberOfSettings] = { { kCTParagraphStyleSpecifierLineSpacingAdjustment, sizeof(CGFloat), &lineSpacing }, { kCTParagraphStyleSpecifierMaximumLineSpacing, sizeof(CGFloat), &lineSpacing }, { kCTParagraphStyleSpecifierMinimumLineSpacing, sizeof(CGFloat), &lineSpacing } }; CTParagraphStyleRef theParagraphRef = CTParagraphStyleCreate(theSettings, kNumberOfSettings); UIColor * textColor = config.textColor; NSMutableDictionary * dict = [NSMutableDictionary dictionary]; dict[(id)kCTForegroundColorAttributeName] = (id)textColor.CGColor; dict[(id)kCTFontAttributeName] = (__bridge id)fontRef; dict[(id)kCTParagraphStyleAttributeName] = (__bridge id)theParagraphRef; CFRelease(theParagraphRef); CFRelease(fontRef); return dict; } + (CoreTextData *)parseString:(NSString *)string config:(CTFrameParserConfig *)config{ NSMutableArray *imageArray = [NSMutableArray array]; NSMutableArray *linkArray = [NSMutableArray array]; NSAttributedString *content = [self zs_loadString:string config:config imageArray:imageArray linkArray:linkArray]; CoreTextData *data = [self parseAttributedContent:content config:config]; data.imageArray = imageArray; data.linkArray = linkArray; return data; } + (NSAttributedString *)zs_loadString:(NSString *)string config:(CTFrameParserConfig*)config imageArray:(NSMutableArray *)imageArray linkArray:(NSMutableArray *)linkArray { NSString *pattern = [NSString stringWithFormat:@"%@|%@|%@",kImageViewPattern,kTextLinkPattern,kBookPattern]; NSMutableAttributedString *attributeString = [[NSMutableAttributedString alloc] init]; imageInfoArr = @[].mutableCopy; __block NSRange lastRange = NSMakeRange(0, 0); __block BOOL exist = NO; [string enumerateStringsMatchedByRegex:pattern usingBlock:^(NSInteger captureCount, NSString *const __unsafe_unretained *capturedStrings, const NSRange *capturedRanges, volatile BOOL *const stop) { exist = YES; // NSLog(@"%@ %@", *capturedStrings, NSStringFromRange(*capturedRanges)); NSString *text = [string substringWithRange:NSMakeRange(lastRange.location +lastRange.length, (*capturedRanges).location - lastRange.location - lastRange.length)]; NSLog(@"text:%@",text); // 1.处理文字 if (text.length > 0) { NSDictionary *textDict = @{@"size":@"13", @"type":@"txt", @"color":@"black", @"content":text }; NSAttributedString *as = [self parseOnlyContentFromNSDictionary:textDict config:config]; [attributeString appendAttributedString:as]; } //2.处理图片 if ([*capturedStrings rangeOfString:@"{{"].location != NSNotFound) { NSString *string = [*capturedStrings stringByReplacingOccurrencesOfString:@"{{" withString:@""]; string = [*capturedStrings stringByReplacingOccurrencesOfString:@"}}" withString:@""]; NSDictionary *imageDict = [self parseImageStr:string]; NSString *size = imageDict[@"size"]; NSArray *sizeInfo = [size componentsSeparatedByString:@"-"]; CoreTextImageData *imageData = [[CoreTextImageData alloc] init]; imageData.url = imageDict[@"url"]; imageData.size = CGSizeMake([sizeInfo.firstObject floatValue], [sizeInfo.lastObject floatValue]); imageData.position = [attributeString length]; [imageArray addObject:imageData]; // 图片宽度如果大于config的宽度,按比例缩放 CGFloat imageWidth = [sizeInfo.firstObject floatValue]; CGFloat imageHeight = [sizeInfo.lastObject floatValue]; if (imageWidth > config.width) { CGFloat scale = config.width/imageWidth; imageWidth = config.width; imageHeight = imageHeight * scale; } NSDictionary *imageInfo = @{@"width": @(imageWidth), @"height":@(imageHeight), @"type":[NSString stringWithFormat:@"%@",imageDict[@"type"]], @"url":[NSString stringWithFormat:@"%@",imageDict[@"url"] ], @"name":@"1234" }; [imageInfoArr addObject:imageInfo]; NSAttributedString *as = [self parseImageDataFromNSDictionary:imageInfo config:config]; [attributeString appendAttributedString:as]; } else //3.处理链接 if ([*capturedStrings rangeOfString:@"[["].location != NSNotFound) { //[[post:5b45ac11137888850bb1c69e 【传送门】报名阶段:7月11日~8月1日]] NSRange maohaoRange = [*capturedStrings rangeOfString:@":"]; NSRange range = [*capturedStrings rangeOfString:@"[["]; NSString *linkKey = [*capturedStrings substringWithRange:NSMakeRange(range.location + 2, maohaoRange.location - range.location - 2)]; NSString *key = [*capturedStrings substringWithRange:NSMakeRange(maohaoRange.location + 1, 24)]; NSString *content = [*capturedStrings substringFromIndex:maohaoRange.location + 26]; content = [content stringByReplacingOccurrencesOfString:@"]]" withString:@""]; NSUInteger startPos = attributeString.length; NSDictionary *linkInfo = @{@"type":@"link", @"key":key, @"color":@"orange", @"content":content, }; NSAttributedString *as = [self parseAttributedContentFromNSDictionary:linkInfo config:config]; [attributeString appendAttributedString:as]; // 创建 CoreTextLinkData NSUInteger length = attributeString.length - startPos; NSRange linkRange = NSMakeRange(startPos, length); CoreTextLinkData *linkData = [[CoreTextLinkData alloc] init]; linkData.title = [NSString stringWithFormat:@"%@",linkInfo[@"content"]]; linkData.url = [NSString stringWithFormat:@"%@",linkInfo[@"url"]]; linkData.key = [NSString stringWithFormat:@"%@",linkInfo[@"key"]]; linkData.linkTo = [NSString stringWithFormat:@"%@",linkKey]; linkData.range = linkRange; [linkArray addObject:linkData]; } else //4.处理书籍传送 if ([*capturedStrings rangeOfString:@"《"].location != NSNotFound) { NSUInteger startPos = attributeString.length; NSString *key = [*capturedStrings stringByReplacingOccurrencesOfString:@"《" withString:@""]; key = [key stringByReplacingOccurrencesOfString:@"》" withString:@""]; NSDictionary *linkInfo = @{@"type":@"link", @"key":key, @"color":@"orange", @"content":*capturedStrings }; NSAttributedString *as = [self parseAttributedContentFromNSDictionary:linkInfo config:config]; [attributeString appendAttributedString:as]; // 创建 CoreTextLinkData NSUInteger length = attributeString.length - startPos; NSRange linkRange = NSMakeRange(startPos, length); CoreTextLinkData *linkData = [[CoreTextLinkData alloc] init]; linkData.title = [NSString stringWithFormat:@"%@",linkInfo[@"content"]]; linkData.url = [NSString stringWithFormat:@"%@",linkInfo[@"url"]]; linkData.key = [NSString stringWithFormat:@"%@",linkInfo[@"key"]]; linkData.range = linkRange; linkData.linkTo = @"search"; [linkArray addObject:linkData]; } // 如果后续 lastRange = *capturedRanges; }]; if (exist) { // 还需要添加最后的文本 if (lastRange.location + lastRange.length < string.length) { NSString *lastString = [string substringFromIndex:lastRange.location + lastRange.length]; NSDictionary *textDict = @{@"size":@"13", @"type":@"txt", @"color":@"black", @"content":lastString }; NSAttributedString *as = [self parseOnlyContentFromNSDictionary:textDict config:config]; [attributeString appendAttributedString:as]; } return attributeString; } else { UIColor *textColor = [UIColor blackColor]; UIFont *textFont = [UIFont systemFontOfSize:15]; if (config.textColor) { textColor = config.textColor; } if (config.textFont) { textFont = config.textFont; } NSMutableAttributedString *attr = [[NSMutableAttributedString alloc] initWithString:string]; [attr addAttributes:@{NSForegroundColorAttributeName:textColor, NSFontAttributeName:textFont } range:NSMakeRange(0, attr.length)]; [attributeString appendAttributedString:attr]; } return attributeString; } + (NSDictionary *)parseImageStr:(NSString *)imageStr{ NSMutableDictionary *dict = @{}.mutableCopy; NSArray *params = [imageStr componentsSeparatedByString:@","]; for (NSString *param in params) { NSArray *arr = [param componentsSeparatedByString:@":"]; if (arr.count > 1) { dict[arr[0]] = arr[1]; } } return [dict copy]; } + (CoreTextData *)parseTemplateFile:(NSString *)path config:(CTFrameParserConfig*)config { NSMutableArray *imageArray = [NSMutableArray array]; NSMutableArray *linkArray = [NSMutableArray array]; NSAttributedString *content = [self loadTemplateFile:path config:config imageArray:imageArray linkArray:linkArray]; CoreTextData *data = [self parseAttributedContent:content config:config]; data.imageArray = imageArray; data.linkArray = linkArray; return data; } + (NSAttributedString *)loadTemplateFile:(NSString *)path config:(CTFrameParserConfig*)config imageArray:(NSMutableArray *)imageArray linkArray:(NSMutableArray *)linkArray { NSData *data = [NSData dataWithContentsOfFile:path]; NSMutableAttributedString *result = [[NSMutableAttributedString alloc] init]; if (data) { NSArray *array = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:nil]; if ([array isKindOfClass:[NSArray class]]) { for (NSDictionary *dict in array) { NSString *type = dict[@"type"]; if ([type isEqualToString:@"txt"]) { NSAttributedString *as = [self parseAttributedContentFromNSDictionary:dict config:config]; [result appendAttributedString:as]; } else if ([type isEqualToString:@"img"]) { // 创建 CoreTextImageData CoreTextImageData *imageData = [[CoreTextImageData alloc] init]; imageData.name = dict[@"name"]; imageData.position = [result length]; [imageArray addObject:imageData]; // 创建空白占位符,并且设置它的CTRunDelegate信息 NSAttributedString *as = [self parseImageDataFromNSDictionary:dict config:config]; [result appendAttributedString:as]; } else if ([type isEqualToString:@"link"]) { NSUInteger startPos = result.length; NSAttributedString *as = [self parseAttributedContentFromNSDictionary:dict config:config]; [result appendAttributedString:as]; // 创建 CoreTextLinkData NSUInteger length = result.length - startPos; NSRange linkRange = NSMakeRange(startPos, length); CoreTextLinkData *linkData = [[CoreTextLinkData alloc] init]; linkData.title = dict[@"content"]; linkData.url = dict[@"url"]; linkData.range = linkRange; [linkArray addObject:linkData]; } } } } return result; } + (NSAttributedString *)parseImageDataFromNSDictionary:(NSDictionary *)dict config:(CTFrameParserConfig*)config { CTRunDelegateCallbacks callbacks; memset(&callbacks, 0, sizeof(CTRunDelegateCallbacks)); callbacks.version = kCTRunDelegateVersion1; callbacks.getAscent = ascentCallback; callbacks.getDescent = descentCallback; callbacks.getWidth = widthCallback; CTRunDelegateRef delegate = CTRunDelegateCreate(&callbacks, (__bridge void *)(dict)); // 使用0xFFFC作为空白的占位符 unichar objectReplacementChar = 0xFFFC; NSString * content = [NSString stringWithCharacters:&objectReplacementChar length:1]; NSDictionary * attributes = [self attributesWithConfig:config]; NSMutableAttributedString * space = [[NSMutableAttributedString alloc] initWithString:content attributes:attributes]; CFAttributedStringSetAttribute((CFMutableAttributedStringRef)space, CFRangeMake(0, 1), kCTRunDelegateAttributeName, delegate); CFRelease(delegate); return space; } + (NSAttributedString *)parseAttributedContentFromNSDictionary:(NSDictionary *)dict config:(CTFrameParserConfig*)config { NSMutableDictionary *attributes = [self attributesWithConfig:config]; // set color UIColor *color = [self colorFromTemplate:dict[@"color"]]; if (color) { attributes[(id)kCTForegroundColorAttributeName] = (id)color.CGColor; } // set font size CGFloat fontSize = [dict[@"size"] floatValue]; if (fontSize > 0) { CTFontRef fontRef = CTFontCreateWithName((CFStringRef)@"ArialMT", fontSize, NULL); attributes[(id)kCTFontAttributeName] = (__bridge id)fontRef; CFRelease(fontRef); } NSString *content = dict[@"content"]; return [[NSAttributedString alloc] initWithString:content attributes:attributes]; } + (NSAttributedString *)parseOnlyContentFromNSDictionary:(NSDictionary *)dict config:(CTFrameParserConfig*)config { NSMutableDictionary *attributes = [self attributesWithConfig:config]; // set color UIColor *color = [self colorFromTemplate:dict[@"color"]]; if (config.textColor) { color = config.textColor; } if (color) { attributes[(id)kCTForegroundColorAttributeName] = (id)color.CGColor; } // set font size CGFloat fontSize = [dict[@"size"] floatValue]; if (config.textFont) { CTFontRef fontRef = CTFontCreateWithName((CFStringRef)config.textFont.fontName, config.fontSize, NULL); attributes[(id)kCTFontAttributeName] = (__bridge id)fontRef; CFRelease(fontRef); } else if (fontSize > 0) { CTFontRef fontRef = CTFontCreateWithName((CFStringRef)@"ArialMT", fontSize, NULL); attributes[(id)kCTFontAttributeName] = (__bridge id)fontRef; CFRelease(fontRef); } NSString *content = dict[@"content"]; return [[NSAttributedString alloc] initWithString:content attributes:attributes]; } + (UIColor *)colorFromTemplate:(NSString *)name { if ([name isEqualToString:@"blue"]) { return [UIColor blueColor]; } else if ([name isEqualToString:@"red"]) { return [UIColor redColor]; } else if ([name isEqualToString:@"black"]) { return [UIColor blackColor]; } else if ([name isEqualToString:@"orange"]) { return [UIColor orangeColor]; } else { return nil; } } + (CoreTextData *)parseContent:(NSString *)content config:(CTFrameParserConfig*)config { NSDictionary *attributes = [self attributesWithConfig:config]; NSAttributedString *contentString = [[NSAttributedString alloc] initWithString:content attributes:attributes]; return [self parseAttributedContent:contentString config:config]; } + (CoreTextData *)parseAttributedContent:(NSAttributedString *)content config:(CTFrameParserConfig*)config { // 创建CTFramesetterRef实例 CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)content); // 获得要缓制的区域的高度 CGSize restrictSize = CGSizeMake(config.width, CGFLOAT_MAX); CGSize coreTextSize = CTFramesetterSuggestFrameSizeWithConstraints(framesetter, CFRangeMake(0,0), nil, restrictSize, nil); CGFloat textHeight = coreTextSize.height; // 生成CTFrameRef实例 CTFrameRef frame = [self createFrameWithFramesetter:framesetter config:config height:textHeight]; // 将生成好的CTFrameRef实例和计算好的缓制高度保存到CoreTextData实例中,最后返回CoreTextData实例 CoreTextData *data = [[CoreTextData alloc] init]; data.ctFrame = frame; data.height = textHeight; data.content = content; // 释放内存 CFRelease(frame); CFRelease(framesetter); return data; } + (CTFrameRef)createFrameWithFramesetter:(CTFramesetterRef)framesetter config:(CTFrameParserConfig *)config height:(CGFloat)height { CGMutablePathRef path = CGPathCreateMutable(); CGPathAddRect(path, NULL, CGRectMake(0, 0, config.width, height)); CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), path, NULL); CFRelease(path); return frame; } @end ================================================ FILE: zhuishushenqi/Vendor/CTDisplayText/Source/CTFrameParserConfig.h ================================================ // // CTFrameParserConfig.h // CoreTextDemo // // Created by yung on 2017/7/25. // Copyright (c) 2017年 yung. All rights reserved. // #import #import @interface CTFrameParserConfig : NSObject @property (nonatomic, assign) CGFloat width; @property (nonatomic, assign) CGFloat fontSize; @property (nonatomic, assign) CGFloat lineSpace; @property (nonatomic, assign) CGFloat paragraphSpace; @property (nonatomic, strong) UIColor *textColor; @property (nonatomic, strong) UIFont *textFont; @end ================================================ FILE: zhuishushenqi/Vendor/CTDisplayText/Source/CTFrameParserConfig.m ================================================ // // CTFrameParserConfig.m // CoreTextDemo // // Created by yung on 2017/7/25. // Copyright (c) 2017年 yung. All rights reserved. // #import "CTFrameParserConfig.h" #define RGB(A, B, C) [UIColor colorWithRed:A/255.0 green:B/255.0 blue:C/255.0 alpha:1.0] @implementation CTFrameParserConfig - (id)init { self = [super init]; if (self) { _width = 200.0f; _fontSize = 16.0f; _lineSpace = 8.0f; _textColor = RGB(108, 108, 108); } return self; } @end ================================================ FILE: zhuishushenqi/Vendor/CTDisplayText/Source/CoreTextData.h ================================================ // // CoreTextData.h // CoreTextDemo // // Created by yung on 2017/7/25. // Copyright (c) 2017年 yung. All rights reserved. // #import #import "CoreTextImageData.h" #import @interface CoreTextData : NSObject @property (assign, nonatomic) CTFrameRef ctFrame; @property (assign, nonatomic) CGFloat height; @property (strong, nonatomic) NSArray * imageArray; @property (strong, nonatomic) NSArray * linkArray; @property (strong, nonatomic) NSAttributedString *content; @end ================================================ FILE: zhuishushenqi/Vendor/CTDisplayText/Source/CoreTextData.m ================================================ // // CoreTextData.m // CoreTextDemo // // Created by yung on 2017/7/25. // Copyright (c) 2017年 yung. All rights reserved. // #import "CoreTextData.h" @implementation CoreTextData - (void)setCtFrame:(CTFrameRef)ctFrame { if (_ctFrame != ctFrame) { if (_ctFrame != nil) { CFRelease(_ctFrame); } CFRetain(ctFrame); _ctFrame = ctFrame; } } - (void)dealloc { if (_ctFrame != nil) { CFRelease(_ctFrame); _ctFrame = nil; } } - (void)setImageArray:(NSArray *)imageArray { _imageArray = imageArray; [self fillImagePosition]; } - (void)fillImagePosition { if (self.imageArray.count == 0) { return; } NSArray *lines = (NSArray *)CTFrameGetLines(self.ctFrame); NSUInteger lineCount = [lines count]; CGPoint lineOrigins[lineCount]; CTFrameGetLineOrigins(self.ctFrame, CFRangeMake(0, 0), lineOrigins); int imgIndex = 0; CoreTextImageData * imageData = self.imageArray[0]; for (int i = 0; i < lineCount; ++i) { if (imageData == nil) { break; } CTLineRef line = (__bridge CTLineRef)lines[i]; NSArray * runObjArray = (NSArray *)CTLineGetGlyphRuns(line); for (id runObj in runObjArray) { CTRunRef run = (__bridge CTRunRef)runObj; NSDictionary *runAttributes = (NSDictionary *)CTRunGetAttributes(run); CTRunDelegateRef delegate = (__bridge CTRunDelegateRef)[runAttributes valueForKey:(id)kCTRunDelegateAttributeName]; if (delegate == nil) { continue; } NSDictionary * metaDic = CTRunDelegateGetRefCon(delegate); if (![metaDic isKindOfClass:[NSDictionary class]]) { continue; } CGRect runBounds; CGFloat ascent; CGFloat descent; runBounds.size.width = CTRunGetTypographicBounds(run, CFRangeMake(0, 0), &ascent, &descent, NULL); runBounds.size.height = ascent + descent; CGFloat xOffset = CTLineGetOffsetForStringIndex(line, CTRunGetStringRange(run).location, NULL); runBounds.origin.x = lineOrigins[i].x + xOffset; runBounds.origin.y = lineOrigins[i].y; runBounds.origin.y -= descent; CGPathRef pathRef = CTFrameGetPath(self.ctFrame); CGRect colRect = CGPathGetBoundingBox(pathRef); CGRect delegateBounds = CGRectOffset(runBounds, colRect.origin.x, colRect.origin.y); imageData.imagePosition = delegateBounds; imgIndex++; if (imgIndex == self.imageArray.count) { imageData = nil; break; } else { imageData = self.imageArray[imgIndex]; } } } } @end ================================================ FILE: zhuishushenqi/Vendor/CTDisplayText/Source/CoreTextImageData.h ================================================ // // CoreTextImageData.h // CoreTextDemo // // Created by yung on 2017/7/25. // Copyright (c) 2017年 yung. All rights reserved. // #import #import @interface CoreTextImageData : NSObject @property (strong, nonatomic) NSString * name; @property (nonatomic, assign) CGSize size; @property (nonatomic, copy) NSString *url; @property (nonatomic) long position; // 此坐标是 CoreText 的坐标系,而不是UIKit的坐标系 @property (nonatomic, assign) CGRect imagePosition; @end ================================================ FILE: zhuishushenqi/Vendor/CTDisplayText/Source/CoreTextImageData.m ================================================ // // CoreTextImageData.m // CoreTextDemo // // Created by yung on 2017/7/25. // Copyright (c) 2017年 yung. All rights reserved. // #import "CoreTextImageData.h" @implementation CoreTextImageData @end ================================================ FILE: zhuishushenqi/Vendor/CTDisplayText/Source/CoreTextLinkData.h ================================================ // // CoreTextLinkData.h // CoreTextDemo // // Created by yung on 2017/7/25. // Copyright (c) 2017年 yung. All rights reserved. // #import @interface CoreTextLinkData : NSObject @property (strong, nonatomic) NSString * title; @property (strong, nonatomic) NSString * url; @property (assign, nonatomic) NSRange range; // post或者booklist @property (nonatomic, copy) NSString *linkTo; // key一般为24位的ID,可以手动拼接成URL @property (nonatomic, copy) NSString *key; @end ================================================ FILE: zhuishushenqi/Vendor/CTDisplayText/Source/CoreTextLinkData.m ================================================ // // CoreTextLinkData.m // CoreTextDemo // // Created by yung on 2017/7/25. // Copyright (c) 2017年 yung. All rights reserved. // #import "CoreTextLinkData.h" @implementation CoreTextLinkData @end ================================================ FILE: zhuishushenqi/Vendor/CTDisplayText/Source/CoreTextUtils.h ================================================ // // CoreTextUtils.h // CoreTextDemo // // Created by yung on 2017/7/25. // Copyright (c) 2017年 yung. All rights reserved. // #import #import "CoreTextLinkData.h" #import "CoreTextData.h" #import @interface CoreTextUtils : NSObject + (CoreTextLinkData *)touchLinkInView:(UIView *)view atPoint:(CGPoint)point data:(CoreTextData *)data; + (CFIndex)touchContentOffsetInView:(UIView *)view atPoint:(CGPoint)point data:(CoreTextData *)data; @end ================================================ FILE: zhuishushenqi/Vendor/CTDisplayText/Source/CoreTextUtils.m ================================================ // // CoreTextUtils.m // CoreTextDemo // // Created by yung on 2017/7/25. // Copyright (c) 2017年 yung. All rights reserved. // #import "CoreTextUtils.h" @implementation CoreTextUtils // 检测点击位置是否在链接上 + (CoreTextLinkData *)touchLinkInView:(UIView *)view atPoint:(CGPoint)point data:(CoreTextData *)data { CFIndex idx = [self touchContentOffsetInView:view atPoint:point data:data]; if (idx == -1) { return nil; } CoreTextLinkData * foundLink = [self linkAtIndex:idx linkArray:data.linkArray]; return foundLink; } // 将点击的位置转换成字符串的偏移量,如果没有找到,则返回-1 + (CFIndex)touchContentOffsetInView:(UIView *)view atPoint:(CGPoint)point data:(CoreTextData *)data { CTFrameRef textFrame = data.ctFrame; CFArrayRef lines = CTFrameGetLines(textFrame); if (!lines) { return -1; } CFIndex count = CFArrayGetCount(lines); // 获得每一行的origin坐标 CGPoint origins[count]; CTFrameGetLineOrigins(textFrame, CFRangeMake(0,0), origins); // 翻转坐标系 CGAffineTransform transform = CGAffineTransformMakeTranslation(0, view.bounds.size.height); transform = CGAffineTransformScale(transform, 1.f, -1.f); CFIndex idx = -1; for (int i = 0; i < count; i++) { CGPoint linePoint = origins[i]; CTLineRef line = CFArrayGetValueAtIndex(lines, i); // 获得每一行的CGRect信息 CGRect flippedRect = [self getLineBounds:line point:linePoint]; CGRect rect = CGRectApplyAffineTransform(flippedRect, transform); if (CGRectContainsPoint(rect, point)) { // 将点击的坐标转换成相对于当前行的坐标 CGPoint relativePoint = CGPointMake(point.x-CGRectGetMinX(rect), point.y-CGRectGetMinY(rect)); // 获得当前点击坐标对应的字符串偏移 idx = CTLineGetStringIndexForPosition(line, relativePoint); } } return idx; } + (CGRect)getLineBounds:(CTLineRef)line point:(CGPoint)point { CGFloat ascent = 0.0f; CGFloat descent = 0.0f; CGFloat leading = 0.0f; CGFloat width = (CGFloat)CTLineGetTypographicBounds(line, &ascent, &descent, &leading); CGFloat height = ascent + descent; return CGRectMake(point.x, point.y - descent, width, height); } + (CoreTextLinkData *)linkAtIndex:(CFIndex)i linkArray:(NSArray *)linkArray { CoreTextLinkData *link = nil; for (CoreTextLinkData *data in linkArray) { if (NSLocationInRange(i, data.range)) { link = data; break; } } return link; } @end ================================================ FILE: zhuishushenqi/Vendor/CTDisplayText/Source/RegexKitLite.h ================================================ // // RegexKitLite.h // http://regexkit.sourceforge.net/ // Licensed under the terms of the BSD License, as specified below. // /* Copyright (c) 2008-2010, John Engelhart All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the Zang Industries nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifdef __OBJC__ #import #import #import #import #import #endif // __OBJC__ #include #include #include #include #include #ifdef __cplusplus extern "C" { #endif #ifndef REGEXKITLITE_VERSION_DEFINED #define REGEXKITLITE_VERSION_DEFINED #define _RKL__STRINGIFY(b) #b #define _RKL_STRINGIFY(a) _RKL__STRINGIFY(a) #define _RKL_JOIN_VERSION(a,b) _RKL_STRINGIFY(a##.##b) #define _RKL_VERSION_STRING(a,b) _RKL_JOIN_VERSION(a,b) #define REGEXKITLITE_VERSION_MAJOR 4 #define REGEXKITLITE_VERSION_MINOR 0 #define REGEXKITLITE_VERSION_CSTRING _RKL_VERSION_STRING(REGEXKITLITE_VERSION_MAJOR, REGEXKITLITE_VERSION_MINOR) #define REGEXKITLITE_VERSION_NSSTRING @REGEXKITLITE_VERSION_CSTRING #endif // REGEXKITLITE_VERSION_DEFINED #if !defined(RKL_BLOCKS) && defined(NS_BLOCKS_AVAILABLE) && (NS_BLOCKS_AVAILABLE == 1) #define RKL_BLOCKS 1 #endif #if defined(RKL_BLOCKS) && (RKL_BLOCKS == 1) #define _RKL_BLOCKS_ENABLED 1 #endif // defined(RKL_BLOCKS) && (RKL_BLOCKS == 1) #if defined(_RKL_BLOCKS_ENABLED) && !defined(__BLOCKS__) #warning RegexKitLite support for Blocks is enabled, but __BLOCKS__ is not defined. This compiler may not support Blocks, in which case the behavior is undefined. This will probably cause numerous compiler errors. #endif // defined(_RKL_BLOCKS_ENABLED) && !defined(__BLOCKS__) // For Mac OS X < 10.5. #ifndef NSINTEGER_DEFINED #define NSINTEGER_DEFINED #if defined(__LP64__) || defined(NS_BUILD_32_LIKE_64) typedef long NSInteger; typedef unsigned long NSUInteger; #define NSIntegerMin LONG_MIN #define NSIntegerMax LONG_MAX #define NSUIntegerMax ULONG_MAX #else // defined(__LP64__) || defined(NS_BUILD_32_LIKE_64) typedef int NSInteger; typedef unsigned int NSUInteger; #define NSIntegerMin INT_MIN #define NSIntegerMax INT_MAX #define NSUIntegerMax UINT_MAX #endif // defined(__LP64__) || defined(NS_BUILD_32_LIKE_64) #endif // NSINTEGER_DEFINED #ifndef RKLREGEXOPTIONS_DEFINED #define RKLREGEXOPTIONS_DEFINED // These must be identical to their ICU regex counterparts. See http://www.icu-project.org/userguide/regexp.html enum { RKLNoOptions = 0, RKLCaseless = 2, RKLComments = 4, RKLDotAll = 32, RKLMultiline = 8, RKLUnicodeWordBoundaries = 256 }; typedef uint32_t RKLRegexOptions; // This must be identical to the ICU 'flags' argument type. #endif // RKLREGEXOPTIONS_DEFINED #ifndef RKLREGEXENUMERATIONOPTIONS_DEFINED #define RKLREGEXENUMERATIONOPTIONS_DEFINED enum { RKLRegexEnumerationNoOptions = 0UL, RKLRegexEnumerationCapturedStringsNotRequired = 1UL << 9, RKLRegexEnumerationReleaseStringReturnedByReplacementBlock = 1UL << 10, RKLRegexEnumerationFastCapturedStringsXXX = 1UL << 11, }; typedef NSUInteger RKLRegexEnumerationOptions; #endif // RKLREGEXENUMERATIONOPTIONS_DEFINED #ifndef _REGEXKITLITE_H_ #define _REGEXKITLITE_H_ #if defined(__GNUC__) && (__GNUC__ >= 4) && defined(__APPLE_CC__) && (__APPLE_CC__ >= 5465) #define RKL_DEPRECATED_ATTRIBUTE __attribute__((deprecated)) #else #define RKL_DEPRECATED_ATTRIBUTE #endif #if defined(NS_REQUIRES_NIL_TERMINATION) #define RKL_REQUIRES_NIL_TERMINATION NS_REQUIRES_NIL_TERMINATION #else // defined(NS_REQUIRES_NIL_TERMINATION) #define RKL_REQUIRES_NIL_TERMINATION #endif // defined(NS_REQUIRES_NIL_TERMINATION) // This requires a few levels of rewriting to get the desired results. #define _RKL_CONCAT_2(c,d) c ## d #define _RKL_CONCAT(a,b) _RKL_CONCAT_2(a,b) #ifdef RKL_PREPEND_TO_METHODS #define RKL_METHOD_PREPEND(x) _RKL_CONCAT(RKL_PREPEND_TO_METHODS, x) #else // RKL_PREPEND_TO_METHODS #define RKL_METHOD_PREPEND(x) x #endif // RKL_PREPEND_TO_METHODS // If it looks like low memory notifications might be available, add code to register and respond to them. // This is (should be) harmless if it turns out that this isn't the case, since the notification that we register for, // UIApplicationDidReceiveMemoryWarningNotification, is dynamically looked up via dlsym(). #if ((defined(TARGET_OS_EMBEDDED) && (TARGET_OS_EMBEDDED != 0)) || (defined(TARGET_OS_IPHONE) && (TARGET_OS_IPHONE != 0))) && (!defined(RKL_REGISTER_FOR_IPHONE_LOWMEM_NOTIFICATIONS) || (RKL_REGISTER_FOR_IPHONE_LOWMEM_NOTIFICATIONS != 0)) #define RKL_REGISTER_FOR_IPHONE_LOWMEM_NOTIFICATIONS 1 #endif #ifdef __OBJC__ // NSException exception name. extern NSString * const RKLICURegexException; // NSError error domains and user info keys. extern NSString * const RKLICURegexErrorDomain; extern NSString * const RKLICURegexEnumerationOptionsErrorKey; extern NSString * const RKLICURegexErrorCodeErrorKey; extern NSString * const RKLICURegexErrorNameErrorKey; extern NSString * const RKLICURegexLineErrorKey; extern NSString * const RKLICURegexOffsetErrorKey; extern NSString * const RKLICURegexPreContextErrorKey; extern NSString * const RKLICURegexPostContextErrorKey; extern NSString * const RKLICURegexRegexErrorKey; extern NSString * const RKLICURegexRegexOptionsErrorKey; extern NSString * const RKLICURegexReplacedCountErrorKey; extern NSString * const RKLICURegexReplacedStringErrorKey; extern NSString * const RKLICURegexReplacementStringErrorKey; extern NSString * const RKLICURegexSubjectRangeErrorKey; extern NSString * const RKLICURegexSubjectStringErrorKey; @interface NSString (RegexKitLiteAdditions) + (void)RKL_METHOD_PREPEND(clearStringCache); // Although these are marked as deprecated, a bug in GCC prevents a warning from being issues for + class methods. Filed bug with Apple, #6736857. + (NSInteger)RKL_METHOD_PREPEND(captureCountForRegex):(NSString *)regex RKL_DEPRECATED_ATTRIBUTE; + (NSInteger)RKL_METHOD_PREPEND(captureCountForRegex):(NSString *)regex options:(RKLRegexOptions)options error:(NSError **)error RKL_DEPRECATED_ATTRIBUTE; - (NSArray *)RKL_METHOD_PREPEND(componentsSeparatedByRegex):(NSString *)regex; - (NSArray *)RKL_METHOD_PREPEND(componentsSeparatedByRegex):(NSString *)regex range:(NSRange)range; - (NSArray *)RKL_METHOD_PREPEND(componentsSeparatedByRegex):(NSString *)regex options:(RKLRegexOptions)options range:(NSRange)range error:(NSError **)error; - (BOOL)RKL_METHOD_PREPEND(isMatchedByRegex):(NSString *)regex; - (BOOL)RKL_METHOD_PREPEND(isMatchedByRegex):(NSString *)regex inRange:(NSRange)range; - (BOOL)RKL_METHOD_PREPEND(isMatchedByRegex):(NSString *)regex options:(RKLRegexOptions)options inRange:(NSRange)range error:(NSError **)error; - (NSRange)RKL_METHOD_PREPEND(rangeOfRegex):(NSString *)regex; - (NSRange)RKL_METHOD_PREPEND(rangeOfRegex):(NSString *)regex capture:(NSInteger)capture; - (NSRange)RKL_METHOD_PREPEND(rangeOfRegex):(NSString *)regex inRange:(NSRange)range; - (NSRange)RKL_METHOD_PREPEND(rangeOfRegex):(NSString *)regex options:(RKLRegexOptions)options inRange:(NSRange)range capture:(NSInteger)capture error:(NSError **)error; - (NSString *)RKL_METHOD_PREPEND(stringByMatching):(NSString *)regex; - (NSString *)RKL_METHOD_PREPEND(stringByMatching):(NSString *)regex capture:(NSInteger)capture; - (NSString *)RKL_METHOD_PREPEND(stringByMatching):(NSString *)regex inRange:(NSRange)range; - (NSString *)RKL_METHOD_PREPEND(stringByMatching):(NSString *)regex options:(RKLRegexOptions)options inRange:(NSRange)range capture:(NSInteger)capture error:(NSError **)error; - (NSString *)RKL_METHOD_PREPEND(stringByReplacingOccurrencesOfRegex):(NSString *)regex withString:(NSString *)replacement; - (NSString *)RKL_METHOD_PREPEND(stringByReplacingOccurrencesOfRegex):(NSString *)regex withString:(NSString *)replacement range:(NSRange)searchRange; - (NSString *)RKL_METHOD_PREPEND(stringByReplacingOccurrencesOfRegex):(NSString *)regex withString:(NSString *)replacement options:(RKLRegexOptions)options range:(NSRange)searchRange error:(NSError **)error; //// >= 3.0 - (NSInteger)RKL_METHOD_PREPEND(captureCount); - (NSInteger)RKL_METHOD_PREPEND(captureCountWithOptions):(RKLRegexOptions)options error:(NSError **)error; - (BOOL)RKL_METHOD_PREPEND(isRegexValid); - (BOOL)RKL_METHOD_PREPEND(isRegexValidWithOptions):(RKLRegexOptions)options error:(NSError **)error; - (void)RKL_METHOD_PREPEND(flushCachedRegexData); - (NSArray *)RKL_METHOD_PREPEND(componentsMatchedByRegex):(NSString *)regex; - (NSArray *)RKL_METHOD_PREPEND(componentsMatchedByRegex):(NSString *)regex capture:(NSInteger)capture; - (NSArray *)RKL_METHOD_PREPEND(componentsMatchedByRegex):(NSString *)regex range:(NSRange)range; - (NSArray *)RKL_METHOD_PREPEND(componentsMatchedByRegex):(NSString *)regex options:(RKLRegexOptions)options range:(NSRange)range capture:(NSInteger)capture error:(NSError **)error; - (NSArray *)RKL_METHOD_PREPEND(captureComponentsMatchedByRegex):(NSString *)regex; - (NSArray *)RKL_METHOD_PREPEND(captureComponentsMatchedByRegex):(NSString *)regex range:(NSRange)range; - (NSArray *)RKL_METHOD_PREPEND(captureComponentsMatchedByRegex):(NSString *)regex options:(RKLRegexOptions)options range:(NSRange)range error:(NSError **)error; - (NSArray *)RKL_METHOD_PREPEND(arrayOfCaptureComponentsMatchedByRegex):(NSString *)regex; - (NSArray *)RKL_METHOD_PREPEND(arrayOfCaptureComponentsMatchedByRegex):(NSString *)regex range:(NSRange)range; - (NSArray *)RKL_METHOD_PREPEND(arrayOfCaptureComponentsMatchedByRegex):(NSString *)regex options:(RKLRegexOptions)options range:(NSRange)range error:(NSError **)error; //// >= 4.0 - (NSArray *)RKL_METHOD_PREPEND(arrayOfDictionariesByMatchingRegex):(NSString *)regex withKeysAndCaptures:(id)firstKey, ... RKL_REQUIRES_NIL_TERMINATION; - (NSArray *)RKL_METHOD_PREPEND(arrayOfDictionariesByMatchingRegex):(NSString *)regex range:(NSRange)range withKeysAndCaptures:(id)firstKey, ... RKL_REQUIRES_NIL_TERMINATION; - (NSArray *)RKL_METHOD_PREPEND(arrayOfDictionariesByMatchingRegex):(NSString *)regex options:(RKLRegexOptions)options range:(NSRange)range error:(NSError **)error withKeysAndCaptures:(id)firstKey, ... RKL_REQUIRES_NIL_TERMINATION; - (NSArray *)RKL_METHOD_PREPEND(arrayOfDictionariesByMatchingRegex):(NSString *)regex options:(RKLRegexOptions)options range:(NSRange)range error:(NSError **)error withFirstKey:(id)firstKey arguments:(va_list)varArgsList; - (NSArray *)RKL_METHOD_PREPEND(arrayOfDictionariesByMatchingRegex):(NSString *)regex options:(RKLRegexOptions)options range:(NSRange)range error:(NSError **)error withKeys:(id *)keys forCaptures:(int *)captures count:(NSUInteger)count; - (NSDictionary *)RKL_METHOD_PREPEND(dictionaryByMatchingRegex):(NSString *)regex withKeysAndCaptures:(id)firstKey, ... RKL_REQUIRES_NIL_TERMINATION; - (NSDictionary *)RKL_METHOD_PREPEND(dictionaryByMatchingRegex):(NSString *)regex range:(NSRange)range withKeysAndCaptures:(id)firstKey, ... RKL_REQUIRES_NIL_TERMINATION; - (NSDictionary *)RKL_METHOD_PREPEND(dictionaryByMatchingRegex):(NSString *)regex options:(RKLRegexOptions)options range:(NSRange)range error:(NSError **)error withKeysAndCaptures:(id)firstKey, ... RKL_REQUIRES_NIL_TERMINATION; - (NSDictionary *)RKL_METHOD_PREPEND(dictionaryByMatchingRegex):(NSString *)regex options:(RKLRegexOptions)options range:(NSRange)range error:(NSError **)error withFirstKey:(id)firstKey arguments:(va_list)varArgsList; - (NSDictionary *)RKL_METHOD_PREPEND(dictionaryByMatchingRegex):(NSString *)regex options:(RKLRegexOptions)options range:(NSRange)range error:(NSError **)error withKeys:(id *)keys forCaptures:(int *)captures count:(NSUInteger)count; #ifdef _RKL_BLOCKS_ENABLED - (BOOL)RKL_METHOD_PREPEND(enumerateStringsMatchedByRegex):(NSString *)regex usingBlock:(void (^)(NSInteger captureCount, NSString * const capturedStrings[captureCount], const NSRange capturedRanges[captureCount], volatile BOOL * const stop))block; - (BOOL)RKL_METHOD_PREPEND(enumerateStringsMatchedByRegex):(NSString *)regex options:(RKLRegexOptions)options inRange:(NSRange)range error:(NSError **)error enumerationOptions:(RKLRegexEnumerationOptions)enumerationOptions usingBlock:(void (^)(NSInteger captureCount, NSString * const capturedStrings[captureCount], const NSRange capturedRanges[captureCount], volatile BOOL * const stop))block; - (BOOL)RKL_METHOD_PREPEND(enumerateStringsSeparatedByRegex):(NSString *)regex usingBlock:(void (^)(NSInteger captureCount, NSString * const capturedStrings[captureCount], const NSRange capturedRanges[captureCount], volatile BOOL * const stop))block; - (BOOL)RKL_METHOD_PREPEND(enumerateStringsSeparatedByRegex):(NSString *)regex options:(RKLRegexOptions)options inRange:(NSRange)range error:(NSError **)error enumerationOptions:(RKLRegexEnumerationOptions)enumerationOptions usingBlock:(void (^)(NSInteger captureCount, NSString * const capturedStrings[captureCount], const NSRange capturedRanges[captureCount], volatile BOOL * const stop))block; - (NSString *)RKL_METHOD_PREPEND(stringByReplacingOccurrencesOfRegex):(NSString *)regex usingBlock:(NSString *(^)(NSInteger captureCount, NSString * const capturedStrings[captureCount], const NSRange capturedRanges[captureCount], volatile BOOL * const stop))block; - (NSString *)RKL_METHOD_PREPEND(stringByReplacingOccurrencesOfRegex):(NSString *)regex options:(RKLRegexOptions)options inRange:(NSRange)range error:(NSError **)error enumerationOptions:(RKLRegexEnumerationOptions)enumerationOptions usingBlock:(NSString *(^)(NSInteger captureCount, NSString * const capturedStrings[captureCount], const NSRange capturedRanges[captureCount], volatile BOOL * const stop))block; #endif // _RKL_BLOCKS_ENABLED @end @interface NSMutableString (RegexKitLiteAdditions) - (NSInteger)RKL_METHOD_PREPEND(replaceOccurrencesOfRegex):(NSString *)regex withString:(NSString *)replacement; - (NSInteger)RKL_METHOD_PREPEND(replaceOccurrencesOfRegex):(NSString *)regex withString:(NSString *)replacement range:(NSRange)searchRange; - (NSInteger)RKL_METHOD_PREPEND(replaceOccurrencesOfRegex):(NSString *)regex withString:(NSString *)replacement options:(RKLRegexOptions)options range:(NSRange)searchRange error:(NSError **)error; //// >= 4.0 #ifdef _RKL_BLOCKS_ENABLED - (NSInteger)RKL_METHOD_PREPEND(replaceOccurrencesOfRegex):(NSString *)regex usingBlock:(NSString *(^)(NSInteger captureCount, NSString * const capturedStrings[captureCount], const NSRange capturedRanges[captureCount], volatile BOOL * const stop))block; - (NSInteger)RKL_METHOD_PREPEND(replaceOccurrencesOfRegex):(NSString *)regex options:(RKLRegexOptions)options inRange:(NSRange)range error:(NSError **)error enumerationOptions:(RKLRegexEnumerationOptions)enumerationOptions usingBlock:(NSString *(^)(NSInteger captureCount, NSString * const capturedStrings[captureCount], const NSRange capturedRanges[captureCount], volatile BOOL * const stop))block; #endif // _RKL_BLOCKS_ENABLED @end #endif // __OBJC__ #endif // _REGEXKITLITE_H_ #ifdef __cplusplus } // extern "C" #endif ================================================ FILE: zhuishushenqi/Vendor/CTDisplayText/Source/RegexKitLite.m ================================================ // // RegexKitLite.m // http://regexkit.sourceforge.net/ // Licensed under the terms of the BSD License, as specified below. // /* Copyright (c) 2008-2010, John Engelhart All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the Zang Industries nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #import #import #import #import #import #import #ifdef __OBJC_GC__ #import #define RKL_STRONG_REF __strong #define RKL_GC_VOLATILE volatile #else // __OBJC_GC__ #define RKL_STRONG_REF #define RKL_GC_VOLATILE #endif // __OBJC_GC__ #if (defined(TARGET_OS_EMBEDDED) && (TARGET_OS_EMBEDDED != 0)) || (defined(TARGET_OS_IPHONE) && (TARGET_OS_IPHONE != 0)) || (defined(MAC_OS_X_VERSION_MIN_REQUIRED) && (MAC_OS_X_VERSION_MIN_REQUIRED >= 1050)) #include #else #include #endif #include #include #include #include #include #include #include #include #import "RegexKitLite.h" // If the gcc flag -mmacosx-version-min is used with, for example, '=10.2', give a warning that the libicucore.dylib is only available on >= 10.3. // If you are reading this comment because of this warning, this is to let you know that linking to /usr/lib/libicucore.dylib will cause your executable to fail on < 10.3. // You will need to build your own version of the ICU library and link to that in order for RegexKitLite to work successfully on < 10.3. This is not simple. #if MAC_OS_X_VERSION_MIN_REQUIRED < 1030 #warning The ICU dynamic shared library, /usr/lib/libicucore.dylib, is only available on Mac OS X 10.3 and later. #warning You will need to supply a version of the ICU library to use RegexKitLite on Mac OS X 10.2 and earlier. #endif //////////// #pragma mark Compile time tunables #ifndef RKL_CACHE_SIZE #define RKL_CACHE_SIZE (13UL) #endif #if RKL_CACHE_SIZE < 1 #error RKL_CACHE_SIZE must be a non-negative number greater than 0. #endif // RKL_CACHE_SIZE < 1 #ifndef RKL_FIXED_LENGTH #define RKL_FIXED_LENGTH (2048UL) #endif #if RKL_FIXED_LENGTH < 1 #error RKL_FIXED_LENGTH must be a non-negative number greater than 0. #endif // RKL_FIXED_LENGTH < 1 #ifndef RKL_STACK_LIMIT #define RKL_STACK_LIMIT (128UL * 1024UL) #endif #if RKL_STACK_LIMIT < 0 #error RKL_STACK_LIMIT must be a non-negative number. #endif // RKL_STACK_LIMIT < 0 #ifdef RKL_APPEND_TO_ICU_FUNCTIONS #define RKL_ICU_FUNCTION_APPEND(x) _RKL_CONCAT(x, RKL_APPEND_TO_ICU_FUNCTIONS) #else // RKL_APPEND_TO_ICU_FUNCTIONS #define RKL_ICU_FUNCTION_APPEND(x) x #endif // RKL_APPEND_TO_ICU_FUNCTIONS #if defined(RKL_DTRACE) && (RKL_DTRACE != 0) #define _RKL_DTRACE_ENABLED 1 #endif // defined(RKL_DTRACE) && (RKL_DTRACE != 0) // These are internal, non-public tunables. #define _RKL_FIXED_LENGTH ((NSUInteger)RKL_FIXED_LENGTH) #define _RKL_STACK_LIMIT ((NSUInteger)RKL_STACK_LIMIT) #define _RKL_SCRATCH_BUFFERS (5UL) #if _RKL_SCRATCH_BUFFERS != 5 #error _RKL_SCRATCH_BUFFERS is not tunable, it must be set to 5. #endif // _RKL_SCRATCH_BUFFERS != 5 #define _RKL_PREFETCH_SIZE (64UL) #define _RKL_DTRACE_REGEXUTF8_SIZE (64UL) // A LRU Cache Set holds 4 lines, and the LRU algorithm uses 4 bits per line. // A LRU Cache Set has a type of RKLLRUCacheSet_t and is 16 bits wide (4 lines * 4 bits per line). // RKLLRUCacheSet_t must be initialized to a value of 0x0137 in order to work correctly. typedef uint16_t RKLLRUCacheSet_t; #define _RKL_LRU_CACHE_SET_INIT ((RKLLRUCacheSet_t)0x0137U) #define _RKL_LRU_CACHE_SET_WAYS (4UL) #if _RKL_LRU_CACHE_SET_WAYS != 4 #error _RKL_LRU_CACHE_SET_WAYS is not tunable, it must be set to 4. #endif // _RKL_LRU_CACHE_SET_WAYS != 4 #define _RKL_REGEX_LRU_CACHE_SETS ((NSUInteger)(RKL_CACHE_SIZE)) #define _RKL_REGEX_CACHE_LINES ((NSUInteger)((NSUInteger)(_RKL_REGEX_LRU_CACHE_SETS) * (NSUInteger)(_RKL_LRU_CACHE_SET_WAYS))) // Regex String Lookaside Cache parameters. #define _RKL_REGEX_LOOKASIDE_CACHE_BITS (6UL) #if _RKL_REGEX_LOOKASIDE_CACHE_BITS < 0 #error _RKL_REGEX_LOOKASIDE_CACHE_BITS must be a non-negative number and is not intended to be user tunable. #endif // _RKL_REGEX_LOOKASIDE_CACHE_BITS < 0 #define _RKL_REGEX_LOOKASIDE_CACHE_SIZE (1LU << _RKL_REGEX_LOOKASIDE_CACHE_BITS) #define _RKL_REGEX_LOOKASIDE_CACHE_MASK ((1LU << _RKL_REGEX_LOOKASIDE_CACHE_BITS) - 1LU) // RKLLookasideCache_t should be large enough to to hold the maximum number of cached regexes, or (RKL_CACHE_SIZE * _RKL_LRU_CACHE_SET_WAYS). #if (RKL_CACHE_SIZE * _RKL_LRU_CACHE_SET_WAYS) <= (1 << 8) typedef uint8_t RKLLookasideCache_t; #elif (RKL_CACHE_SIZE * _RKL_LRU_CACHE_SET_WAYS) <= (1 << 16) typedef uint16_t RKLLookasideCache_t; #else // (RKL_CACHE_SIZE * _RKL_LRU_CACHE_SET_WAYS) > (1 << 16) typedef uint32_t RKLLookasideCache_t; #endif // (RKL_CACHE_SIZE * _RKL_LRU_CACHE_SET_WAYS) ////////////// #pragma mark - #pragma mark GCC / Compiler macros #if defined (__GNUC__) && (__GNUC__ >= 4) #define RKL_ATTRIBUTES(attr, ...) __attribute__((attr, ##__VA_ARGS__)) #define RKL_EXPECTED(cond, expect) __builtin_expect((long)(cond), (expect)) #define RKL_PREFETCH(ptr) __builtin_prefetch(ptr) #define RKL_PREFETCH_UNICHAR(ptr, off) { const char *p = ((const char *)(ptr)) + ((off) * sizeof(UniChar)) + _RKL_PREFETCH_SIZE; RKL_PREFETCH(p); RKL_PREFETCH(p + _RKL_PREFETCH_SIZE); } #define RKL_HAVE_CLEANUP #define RKL_CLEANUP(func) RKL_ATTRIBUTES(cleanup(func)) #else // defined (__GNUC__) && (__GNUC__ >= 4) #define RKL_ATTRIBUTES(attr, ...) #define RKL_EXPECTED(cond, expect) (cond) #define RKL_PREFETCH(ptr) #define RKL_PREFETCH_UNICHAR(ptr, off) #define RKL_CLEANUP(func) #endif // defined (__GNUC__) && (__GNUC__ >= 4) #define RKL_STATIC_INLINE static __inline__ RKL_ATTRIBUTES(always_inline) #define RKL_ALIGNED(arg) RKL_ATTRIBUTES(aligned(arg)) #define RKL_UNUSED_ARG RKL_ATTRIBUTES(unused) #define RKL_WARN_UNUSED RKL_ATTRIBUTES(warn_unused_result) #define RKL_WARN_UNUSED_CONST RKL_ATTRIBUTES(warn_unused_result, const) #define RKL_WARN_UNUSED_PURE RKL_ATTRIBUTES(warn_unused_result, pure) #define RKL_WARN_UNUSED_SENTINEL RKL_ATTRIBUTES(warn_unused_result, sentinel) #define RKL_NONNULL_ARGS(arg, ...) RKL_ATTRIBUTES(nonnull(arg, ##__VA_ARGS__)) #define RKL_WARN_UNUSED_NONNULL_ARGS(arg, ...) RKL_ATTRIBUTES(warn_unused_result, nonnull(arg, ##__VA_ARGS__)) #define RKL_WARN_UNUSED_CONST_NONNULL_ARGS(arg, ...) RKL_ATTRIBUTES(warn_unused_result, const, nonnull(arg, ##__VA_ARGS__)) #define RKL_WARN_UNUSED_PURE_NONNULL_ARGS(arg, ...) RKL_ATTRIBUTES(warn_unused_result, pure, nonnull(arg, ##__VA_ARGS__)) #if defined (__GNUC__) && (__GNUC__ >= 4) && (__GNUC_MINOR__ >= 3) #define RKL_ALLOC_SIZE_NON_NULL_ARGS_WARN_UNUSED(as, nn, ...) RKL_ATTRIBUTES(warn_unused_result, nonnull(nn, ##__VA_ARGS__), alloc_size(as)) #else // defined (__GNUC__) && (__GNUC__ >= 4) && (__GNUC_MINOR__ >= 3) #define RKL_ALLOC_SIZE_NON_NULL_ARGS_WARN_UNUSED(as, nn, ...) RKL_ATTRIBUTES(warn_unused_result, nonnull(nn, ##__VA_ARGS__)) #endif // defined (__GNUC__) && (__GNUC__ >= 4) && (__GNUC_MINOR__ >= 3) #ifdef _RKL_DTRACE_ENABLED #define RKL_UNUSED_DTRACE_ARG #else // _RKL_DTRACE_ENABLED #define RKL_UNUSED_DTRACE_ARG RKL_ATTRIBUTES(unused) #endif // _RKL_DTRACE_ENABLED //////////// #pragma mark - #pragma mark Assertion macros // These macros are nearly identical to their NSCParameterAssert siblings. // This is required because nearly everything is done while rkl_cacheSpinLock is locked. // We need to safely unlock before throwing any of these exceptions. // @try {} @finally {} significantly slows things down so it's not used. #define RKLCHardAbortAssert(c) do { int _c=(c); if(RKL_EXPECTED(!_c, 0L)) { NSLog(@"%@:%ld: Invalid parameter not satisfying: %s\n", [NSString stringWithUTF8String:__FILE__], (long)__LINE__, #c); abort(); } } while(0) #define RKLCAssertDictionary(d, ...) rkl_makeAssertDictionary(__PRETTY_FUNCTION__, __FILE__, __LINE__, (d), ##__VA_ARGS__) #define RKLCDelayedHardAssert(c, e, g) do { id *_e=(e); int _c=(c); if(RKL_EXPECTED(_e == NULL, 0L) || RKL_EXPECTED(*_e != NULL, 0L)) { goto g; } if(RKL_EXPECTED(!_c, 0L)) { *_e = RKLCAssertDictionary(@"Invalid parameter not satisfying: %s", #c); goto g; } } while(0) #ifdef NS_BLOCK_ASSERTIONS #define RKLCAbortAssert(c) #define RKLCDelayedAssert(c, e, g) #define RKL_UNUSED_ASSERTION_ARG RKL_ATTRIBUTES(unused) #else // NS_BLOCK_ASSERTIONS #define RKLCAbortAssert(c) RKLCHardAbortAssert(c) #define RKLCDelayedAssert(c, e, g) RKLCDelayedHardAssert(c, e, g) #define RKL_UNUSED_ASSERTION_ARG #endif // NS_BLOCK_ASSERTIONS #define RKL_EXCEPTION(e, f, ...) [NSException exceptionWithName:(e) reason:rkl_stringFromClassAndMethod((self), (_cmd), (f), ##__VA_ARGS__) userInfo:NULL] #define RKL_RAISE_EXCEPTION(e, f, ...) [RKL_EXCEPTION(e, f, ##__VA_ARGS__) raise] //////////// #pragma mark - #pragma mark Utility functions and macros RKL_STATIC_INLINE BOOL NSRangeInsideRange(NSRange cin, NSRange win) RKL_WARN_UNUSED; RKL_STATIC_INLINE BOOL NSRangeInsideRange(NSRange cin, NSRange win) { return((((cin.location - win.location) <= win.length) && ((NSMaxRange(cin) - win.location) <= win.length)) ? YES : NO); } #define NSMakeRange(loc, len) ((NSRange){.location=(NSUInteger)(loc), .length=(NSUInteger)(len)}) #define CFMakeRange(loc, len) ((CFRange){.location= (CFIndex)(loc), .length= (CFIndex)(len)}) #define NSNotFoundRange ((NSRange){.location=(NSUInteger)NSNotFound, .length= 0UL}) #define NSMaxiumRange ((NSRange){.location= 0UL, .length= NSUIntegerMax}) // These values are used to help tickle improper usage. #define RKLIllegalRange ((NSRange){.location= NSIntegerMax, .length= NSIntegerMax}) #define RKLIllegalPointer ((void * RKL_GC_VOLATILE)0xBAD0C0DE) //////////// #pragma mark - #pragma mark Exported NSString symbols for exception names, error domains, error keys, etc NSString * const RKLICURegexException = @"RKLICURegexException"; NSString * const RKLICURegexErrorDomain = @"RKLICURegexErrorDomain"; NSString * const RKLICURegexEnumerationOptionsErrorKey = @"RKLICURegexEnumerationOptions"; NSString * const RKLICURegexErrorCodeErrorKey = @"RKLICURegexErrorCode"; NSString * const RKLICURegexErrorNameErrorKey = @"RKLICURegexErrorName"; NSString * const RKLICURegexLineErrorKey = @"RKLICURegexLine"; NSString * const RKLICURegexOffsetErrorKey = @"RKLICURegexOffset"; NSString * const RKLICURegexPreContextErrorKey = @"RKLICURegexPreContext"; NSString * const RKLICURegexPostContextErrorKey = @"RKLICURegexPostContext"; NSString * const RKLICURegexRegexErrorKey = @"RKLICURegexRegex"; NSString * const RKLICURegexRegexOptionsErrorKey = @"RKLICURegexRegexOptions"; NSString * const RKLICURegexReplacedCountErrorKey = @"RKLICURegexReplacedCount"; NSString * const RKLICURegexReplacedStringErrorKey = @"RKLICURegexReplacedString"; NSString * const RKLICURegexReplacementStringErrorKey = @"RKLICURegexReplacementString"; NSString * const RKLICURegexSubjectRangeErrorKey = @"RKLICURegexSubjectRange"; NSString * const RKLICURegexSubjectStringErrorKey = @"RKLICURegexSubjectString"; // Used internally by rkl_userInfoDictionary to specify which arguments should be set in the NSError userInfo dictionary. enum { RKLUserInfoNone = 0UL, RKLUserInfoSubjectRange = 1UL << 0, RKLUserInfoReplacedCount = 1UL << 1, RKLUserInfoRegexEnumerationOptions = 1UL << 2, }; typedef NSUInteger RKLUserInfoOptions; //////////// #pragma mark - #pragma mark Type / struct definitions // In general, the ICU bits and pieces here must exactly match the definition in the ICU sources. #define U_STRING_NOT_TERMINATED_WARNING -124 #define U_ZERO_ERROR 0 #define U_INDEX_OUTOFBOUNDS_ERROR 8 #define U_BUFFER_OVERFLOW_ERROR 15 #define U_PARSE_CONTEXT_LEN 16 typedef struct uregex uregex; // Opaque ICU regex type. typedef struct UParseError { // This must be exactly the same as the 'real' ICU declaration. int32_t line; int32_t offset; UniChar preContext[U_PARSE_CONTEXT_LEN]; UniChar postContext[U_PARSE_CONTEXT_LEN]; } UParseError; // For use with GCC's cleanup() __attribute__. enum { RKLLockedCacheSpinLock = 1UL << 0, RKLUnlockedCacheSpinLock = 1UL << 1, }; enum { RKLSplitOp = 1UL, RKLReplaceOp = 2UL, RKLRangeOp = 3UL, RKLArrayOfStringsOp = 4UL, RKLArrayOfCapturesOp = 5UL, RKLCapturesArrayOp = 6UL, RKLDictionaryOfCapturesOp = 7UL, RKLArrayOfDictionariesOfCapturesOp = 8UL, RKLMaskOp = 0xFUL, RKLReplaceMutable = 1UL << 4, RKLSubcapturesArray = 1UL << 5, }; typedef NSUInteger RKLRegexOp; enum { RKLBlockEnumerationMatchOp = 1UL, RKLBlockEnumerationReplaceOp = 2UL, }; typedef NSUInteger RKLBlockEnumerationOp; typedef struct { RKL_STRONG_REF NSRange * RKL_GC_VOLATILE ranges; NSRange findInRange, remainingRange; NSInteger capacity, found, findUpTo, capture, addedSplitRanges; size_t size, stackUsed; RKL_STRONG_REF void ** RKL_GC_VOLATILE rangesScratchBuffer; RKL_STRONG_REF void ** RKL_GC_VOLATILE stringsScratchBuffer; RKL_STRONG_REF void ** RKL_GC_VOLATILE arraysScratchBuffer; RKL_STRONG_REF void ** RKL_GC_VOLATILE dictionariesScratchBuffer; RKL_STRONG_REF void ** RKL_GC_VOLATILE keysScratchBuffer; } RKLFindAll; typedef struct { CFStringRef string; CFHashCode hash; CFIndex length; RKL_STRONG_REF UniChar * RKL_GC_VOLATILE uniChar; } RKLBuffer; typedef struct { CFStringRef regexString; CFHashCode regexHash; RKLRegexOptions options; uregex *icu_regex; NSInteger captureCount; CFStringRef setToString; CFHashCode setToHash; CFIndex setToLength; NSUInteger setToIsImmutable:1; NSUInteger setToNeedsConversion:1; RKL_STRONG_REF const UniChar * RKL_GC_VOLATILE setToUniChar; NSRange setToRange, lastFindRange, lastMatchRange; RKLBuffer *buffer; } RKLCachedRegex; //////////// #pragma mark - #pragma mark Translation unit scope global variables static RKLLRUCacheSet_t rkl_lruFixedBufferCacheSet = _RKL_LRU_CACHE_SET_INIT, rkl_lruDynamicBufferCacheSet = _RKL_LRU_CACHE_SET_INIT; static RKLBuffer rkl_lruDynamicBuffer[_RKL_LRU_CACHE_SET_WAYS]; static UniChar rkl_lruFixedUniChar[_RKL_LRU_CACHE_SET_WAYS][_RKL_FIXED_LENGTH]; // This is the fixed sized UTF-16 conversion buffer. static RKLBuffer rkl_lruFixedBuffer[_RKL_LRU_CACHE_SET_WAYS] = {{NULL, 0UL, 0L, &rkl_lruFixedUniChar[0][0]}, {NULL, 0UL, 0L, &rkl_lruFixedUniChar[1][0]}, {NULL, 0UL, 0L, &rkl_lruFixedUniChar[2][0]}, {NULL, 0UL, 0L, &rkl_lruFixedUniChar[3][0]}}; static RKLCachedRegex rkl_cachedRegexes[_RKL_REGEX_CACHE_LINES]; #if defined(__GNUC__) && (__GNUC__ == 4) && defined(__GNUC_MINOR__) && (__GNUC_MINOR__ == 2) static RKLCachedRegex * volatile rkl_lastCachedRegex; // XXX This is a work around for what appears to be a optimizer code generation bug in GCC 4.2. #else static RKLCachedRegex *rkl_lastCachedRegex; #endif // defined(__GNUC__) && (__GNUC__ == 4) && defined(__GNUC_MINOR__) && (__GNUC_MINOR__ == 2) static RKLLRUCacheSet_t rkl_cachedRegexCacheSets[_RKL_REGEX_LRU_CACHE_SETS] = { [0 ... (_RKL_REGEX_LRU_CACHE_SETS - 1UL)] = _RKL_LRU_CACHE_SET_INIT }; static RKLLookasideCache_t rkl_regexLookasideCache[_RKL_REGEX_LOOKASIDE_CACHE_SIZE] RKL_ALIGNED(64); static OSSpinLock rkl_cacheSpinLock = OS_SPINLOCK_INIT; static const UniChar rkl_emptyUniCharString[1]; // For safety, icu_regexes are 'set' to this when the string they were searched is cleared. static RKL_STRONG_REF void * RKL_GC_VOLATILE rkl_scratchBuffer[_RKL_SCRATCH_BUFFERS]; // Used to hold temporary allocations that are allocated via reallocf(). //////////// #pragma mark - #pragma mark CFArray and CFDictionary call backs // These are used when running under manual memory management for the array that rkl_splitArray creates. // The split strings are created, but not autoreleased. The (immutable) array is created using these callbacks, which skips the CFRetain() call, effectively transferring ownership to the CFArray object. // For each split string this saves the overhead of an autorelease, then an array retain, then an NSAutoreleasePool release. This is good for a ~30% speed increase. static void rkl_CFCallbackRelease(CFAllocatorRef allocator RKL_UNUSED_ARG, const void *ptr) { CFRelease((CFTypeRef)ptr); } static const CFArrayCallBacks rkl_transferOwnershipArrayCallBacks = { (CFIndex)0L, NULL, rkl_CFCallbackRelease, CFCopyDescription, CFEqual }; static const CFDictionaryKeyCallBacks rkl_transferOwnershipDictionaryKeyCallBacks = { (CFIndex)0L, NULL, rkl_CFCallbackRelease, CFCopyDescription, CFEqual, CFHash }; static const CFDictionaryValueCallBacks rkl_transferOwnershipDictionaryValueCallBacks = { (CFIndex)0L, NULL, rkl_CFCallbackRelease, CFCopyDescription, CFEqual }; #ifdef __OBJC_GC__ //////////// #pragma mark - #pragma mark Low-level Garbage Collection aware memory/resource allocation utilities // If compiled with Garbage Collection, we need to be able to do a few things slightly differently. // The basic premiss is that under GC we use a trampoline function pointer which is set to a _start function to catch the first invocation. // The _start function checks if GC is running and then overwrites the function pointer with the appropriate routine. Think of it as 'lazy linking'. enum { RKLScannedOption = NSScannedOption }; // rkl_collectingEnabled uses objc_getClass() to get the NSGarbageCollector class, which doesn't exist on earlier systems. // This allows for graceful failure should we find ourselves running on an earlier version of the OS without NSGarbageCollector. static BOOL rkl_collectingEnabled_first (void); static BOOL rkl_collectingEnabled_yes (void) { return(YES); } static BOOL rkl_collectingEnabled_no (void) { return(NO); } static BOOL(*rkl_collectingEnabled) (void) = rkl_collectingEnabled_first; static BOOL rkl_collectingEnabled_first (void) { BOOL gcEnabled = ([objc_getClass("NSGarbageCollector") defaultCollector] != NULL) ? YES : NO; if(gcEnabled == YES) { // This section of code is required due to what I consider to be a fundamental design flaw in Cocoas GC system. // Earlier versions of "Garbage Collection Programming Guide" stated that (paraphrased) "all globals are automatically roots". // Current versions of the guide now include the following warning: // "You may pass addresses of strong globals or statics into routines expecting pointers to object pointers (such as id* or NSError**) // only if they have first been assigned to directly, rather than through a pointer dereference." // This is a surprisingly non-trivial condition to actually meet in practice and is a recipe for impossible to debug race condition bugs. // We just happen to be very, very, very lucky in the fact that we can initialize our root set before the first use. NSUInteger x = 0UL; for(x = 0UL; x < _RKL_SCRATCH_BUFFERS; x++) { rkl_scratchBuffer[x] = NSAllocateCollectable(16UL, 0UL); rkl_scratchBuffer[x] = NULL; } for(x = 0UL; x < _RKL_LRU_CACHE_SET_WAYS; x++) { rkl_lruDynamicBuffer[x].uniChar = NSAllocateCollectable(16UL, 0UL); rkl_lruDynamicBuffer[x].uniChar = NULL; } } return((rkl_collectingEnabled = (gcEnabled == YES) ? rkl_collectingEnabled_yes : rkl_collectingEnabled_no)()); } // rkl_realloc() static void *rkl_realloc_first (RKL_STRONG_REF void ** RKL_GC_VOLATILE ptr, size_t size, NSUInteger flags); static void *rkl_realloc_std (RKL_STRONG_REF void ** RKL_GC_VOLATILE ptr, size_t size, NSUInteger flags RKL_UNUSED_ARG) { return((*ptr = reallocf(*ptr, size))); } static void *rkl_realloc_gc (RKL_STRONG_REF void ** RKL_GC_VOLATILE ptr, size_t size, NSUInteger flags) { return((*ptr = NSReallocateCollectable(*ptr, (NSUInteger)size, flags))); } static void *(*rkl_realloc) (RKL_STRONG_REF void ** RKL_GC_VOLATILE ptr, size_t size, NSUInteger flags) RKL_ALLOC_SIZE_NON_NULL_ARGS_WARN_UNUSED(2,1) = rkl_realloc_first; static void *rkl_realloc_first (RKL_STRONG_REF void ** RKL_GC_VOLATILE ptr, size_t size, NSUInteger flags) { if(rkl_collectingEnabled()==YES) { rkl_realloc = rkl_realloc_gc; } else { rkl_realloc = rkl_realloc_std; } return(rkl_realloc(ptr, size, flags)); } // rkl_free() static void * rkl_free_first (RKL_STRONG_REF void ** RKL_GC_VOLATILE ptr); static void * rkl_free_std (RKL_STRONG_REF void ** RKL_GC_VOLATILE ptr) { if(*ptr != NULL) { free(*ptr); *ptr = NULL; } return(NULL); } static void * rkl_free_gc (RKL_STRONG_REF void ** RKL_GC_VOLATILE ptr) { if(*ptr != NULL) { *ptr = NULL; } return(NULL); } static void *(*rkl_free) (RKL_STRONG_REF void ** RKL_GC_VOLATILE ptr) RKL_NONNULL_ARGS(1) = rkl_free_first; static void *rkl_free_first (RKL_STRONG_REF void ** RKL_GC_VOLATILE ptr) { if(rkl_collectingEnabled()==YES) { rkl_free = rkl_free_gc; } else { rkl_free = rkl_free_std; } return(rkl_free(ptr)); } // rkl_CFAutorelease() static id rkl_CFAutorelease_first (CFTypeRef obj); static id rkl_CFAutorelease_std (CFTypeRef obj) { return([(id)obj autorelease]); } static id rkl_CFAutorelease_gc (CFTypeRef obj) { return(NSMakeCollectable(obj)); } static id(*rkl_CFAutorelease) (CFTypeRef obj) = rkl_CFAutorelease_first; static id rkl_CFAutorelease_first (CFTypeRef obj) { return((rkl_CFAutorelease = (rkl_collectingEnabled()==YES) ? rkl_CFAutorelease_gc : rkl_CFAutorelease_std)(obj)); } // rkl_CreateStringWithSubstring() static id rkl_CreateStringWithSubstring_first (id string, NSRange range); static id rkl_CreateStringWithSubstring_std (id string, NSRange range) { return((id)CFStringCreateWithSubstring(NULL, (CFStringRef)string, CFMakeRange((CFIndex)range.location, (CFIndex)range.length))); } static id rkl_CreateStringWithSubstring_gc (id string, NSRange range) { return([string substringWithRange:range]); } static id(*rkl_CreateStringWithSubstring) (id string, NSRange range) RKL_WARN_UNUSED_NONNULL_ARGS(1) = rkl_CreateStringWithSubstring_first; static id rkl_CreateStringWithSubstring_first (id string, NSRange range) { return((rkl_CreateStringWithSubstring = (rkl_collectingEnabled()==YES) ? rkl_CreateStringWithSubstring_gc : rkl_CreateStringWithSubstring_std)(string, range)); } // rkl_ReleaseObject() static id rkl_ReleaseObject_first (id obj); static id rkl_ReleaseObject_std (id obj) { CFRelease((CFTypeRef)obj); return(NULL); } static id rkl_ReleaseObject_gc (id obj RKL_UNUSED_ARG) { return(NULL); } static id (*rkl_ReleaseObject) (id obj) RKL_NONNULL_ARGS(1) = rkl_ReleaseObject_first; static id rkl_ReleaseObject_first (id obj) { return((rkl_ReleaseObject = (rkl_collectingEnabled()==YES) ? rkl_ReleaseObject_gc : rkl_ReleaseObject_std)(obj)); } // rkl_CreateArrayWithObjects() static id rkl_CreateArrayWithObjects_first (void **objects, NSUInteger count); static id rkl_CreateArrayWithObjects_std (void **objects, NSUInteger count) { return((id)CFArrayCreate(NULL, (const void **)objects, (CFIndex)count, &rkl_transferOwnershipArrayCallBacks)); } static id rkl_CreateArrayWithObjects_gc (void **objects, NSUInteger count) { return([NSArray arrayWithObjects:(const id *)objects count:count]); } static id(*rkl_CreateArrayWithObjects) (void **objects, NSUInteger count) RKL_WARN_UNUSED_NONNULL_ARGS(1) = rkl_CreateArrayWithObjects_first; static id rkl_CreateArrayWithObjects_first (void **objects, NSUInteger count) { return((rkl_CreateArrayWithObjects = (rkl_collectingEnabled()==YES) ? rkl_CreateArrayWithObjects_gc : rkl_CreateArrayWithObjects_std)(objects, count)); } // rkl_CreateAutoreleasedArray() static id rkl_CreateAutoreleasedArray_first (void **objects, NSUInteger count); static id rkl_CreateAutoreleasedArray_std (void **objects, NSUInteger count) { return((id)rkl_CFAutorelease(rkl_CreateArrayWithObjects(objects, count))); } static id rkl_CreateAutoreleasedArray_gc (void **objects, NSUInteger count) { return( rkl_CreateArrayWithObjects(objects, count) ); } static id(*rkl_CreateAutoreleasedArray) (void **objects, NSUInteger count) RKL_WARN_UNUSED_NONNULL_ARGS(1) = rkl_CreateAutoreleasedArray_first; static id rkl_CreateAutoreleasedArray_first (void **objects, NSUInteger count) { return((rkl_CreateAutoreleasedArray = (rkl_collectingEnabled()==YES) ? rkl_CreateAutoreleasedArray_gc : rkl_CreateAutoreleasedArray_std)(objects, count)); } #else // __OBJC_GC__ not defined //////////// #pragma mark - #pragma mark Low-level explicit memory/resource allocation utilities enum { RKLScannedOption = 0 }; #define rkl_collectingEnabled() (NO) RKL_STATIC_INLINE void *rkl_realloc (void **ptr, size_t size, NSUInteger flags) RKL_ALLOC_SIZE_NON_NULL_ARGS_WARN_UNUSED(2,1); RKL_STATIC_INLINE void *rkl_free (void **ptr) RKL_NONNULL_ARGS(1); RKL_STATIC_INLINE id rkl_CFAutorelease (CFTypeRef obj) RKL_WARN_UNUSED_NONNULL_ARGS(1); RKL_STATIC_INLINE id rkl_CreateAutoreleasedArray (void **objects, NSUInteger count) RKL_WARN_UNUSED_NONNULL_ARGS(1); RKL_STATIC_INLINE id rkl_CreateArrayWithObjects (void **objects, NSUInteger count) RKL_WARN_UNUSED_NONNULL_ARGS(1); RKL_STATIC_INLINE id rkl_CreateStringWithSubstring (id string, NSRange range) RKL_WARN_UNUSED_NONNULL_ARGS(1); RKL_STATIC_INLINE id rkl_ReleaseObject (id obj) RKL_NONNULL_ARGS(1); RKL_STATIC_INLINE void *rkl_realloc (void **ptr, size_t size, NSUInteger flags RKL_UNUSED_ARG) { return((*ptr = reallocf(*ptr, size))); } RKL_STATIC_INLINE void *rkl_free (void **ptr) { if(*ptr != NULL) { free(*ptr); *ptr = NULL; } return(NULL); } RKL_STATIC_INLINE id rkl_CFAutorelease (CFTypeRef obj) { return([(id)obj autorelease]); } RKL_STATIC_INLINE id rkl_CreateArrayWithObjects (void **objects, NSUInteger count) { return((id)CFArrayCreate(NULL, (const void **)objects, (CFIndex)count, &rkl_transferOwnershipArrayCallBacks)); } RKL_STATIC_INLINE id rkl_CreateAutoreleasedArray (void **objects, NSUInteger count) { return(rkl_CFAutorelease(rkl_CreateArrayWithObjects(objects, count))); } RKL_STATIC_INLINE id rkl_CreateStringWithSubstring (id string, NSRange range) { return((id)CFStringCreateWithSubstring(NULL, (CFStringRef)string, CFMakeRange((CFIndex)range.location, (CFIndex)range.length))); } RKL_STATIC_INLINE id rkl_ReleaseObject (id obj) { CFRelease((CFTypeRef)obj); return(NULL); } #endif // __OBJC_GC__ //////////// #pragma mark - #pragma mark ICU function prototypes // ICU functions. See http://www.icu-project.org/apiref/icu4c/uregex_8h.html Tweaked slightly from the originals, but functionally identical. const char *RKL_ICU_FUNCTION_APPEND(u_errorName) ( int32_t status) RKL_WARN_UNUSED_PURE; int32_t RKL_ICU_FUNCTION_APPEND(u_strlen) (const UniChar *s) RKL_WARN_UNUSED_PURE_NONNULL_ARGS(1); int32_t RKL_ICU_FUNCTION_APPEND(uregex_appendReplacement) ( uregex *regexp, const UniChar *replacementText, int32_t replacementLength, UniChar **destBuf, int32_t *destCapacity, int32_t *status) RKL_WARN_UNUSED_NONNULL_ARGS(1,2,4,5,6); int32_t RKL_ICU_FUNCTION_APPEND(uregex_appendTail) ( uregex *regexp, UniChar **destBuf, int32_t *destCapacity, int32_t *status) RKL_WARN_UNUSED_NONNULL_ARGS(1,2,3,4); void RKL_ICU_FUNCTION_APPEND(uregex_close) ( uregex *regexp) RKL_NONNULL_ARGS(1); int32_t RKL_ICU_FUNCTION_APPEND(uregex_end) ( uregex *regexp, int32_t groupNum, int32_t *status) RKL_WARN_UNUSED_NONNULL_ARGS(1,3); BOOL RKL_ICU_FUNCTION_APPEND(uregex_find) ( uregex *regexp, int32_t location, int32_t *status) RKL_WARN_UNUSED_NONNULL_ARGS(1,3); BOOL RKL_ICU_FUNCTION_APPEND(uregex_findNext) ( uregex *regexp, int32_t *status) RKL_WARN_UNUSED_NONNULL_ARGS(1,2); int32_t RKL_ICU_FUNCTION_APPEND(uregex_groupCount) ( uregex *regexp, int32_t *status) RKL_WARN_UNUSED_NONNULL_ARGS(1,2); uregex *RKL_ICU_FUNCTION_APPEND(uregex_open) (const UniChar *pattern, int32_t patternLength, RKLRegexOptions flags, UParseError *parseError, int32_t *status) RKL_WARN_UNUSED_NONNULL_ARGS(1,4,5); void RKL_ICU_FUNCTION_APPEND(uregex_reset) ( uregex *regexp, int32_t newIndex, int32_t *status) RKL_NONNULL_ARGS(1,3); void RKL_ICU_FUNCTION_APPEND(uregex_setText) ( uregex *regexp, const UniChar *text, int32_t textLength, int32_t *status) RKL_NONNULL_ARGS(1,2,4); int32_t RKL_ICU_FUNCTION_APPEND(uregex_start) ( uregex *regexp, int32_t groupNum, int32_t *status) RKL_WARN_UNUSED_NONNULL_ARGS(1,3); uregex *RKL_ICU_FUNCTION_APPEND(uregex_clone) (const uregex *regexp, int32_t *status) RKL_WARN_UNUSED_NONNULL_ARGS(1,2); //////////// #pragma mark - #pragma mark RegexKitLite internal, private function prototypes // Functions used for managing the 4-way set associative LRU cache and regex string hash lookaside cache. RKL_STATIC_INLINE NSUInteger rkl_leastRecentlyUsedWayInSet ( NSUInteger cacheSetsCount, const RKLLRUCacheSet_t cacheSetsArray[cacheSetsCount], NSUInteger set) RKL_WARN_UNUSED_NONNULL_ARGS(2); RKL_STATIC_INLINE void rkl_accessCacheSetWay ( NSUInteger cacheSetsCount, RKLLRUCacheSet_t cacheSetsArray[cacheSetsCount], NSUInteger set, NSUInteger way) RKL_NONNULL_ARGS(2); RKL_STATIC_INLINE NSUInteger rkl_regexLookasideCacheIndexForPointerAndOptions (const void *ptr, RKLRegexOptions options) RKL_WARN_UNUSED_NONNULL_ARGS(1); RKL_STATIC_INLINE void rkl_setRegexLookasideCacheToCachedRegexForPointer (const RKLCachedRegex *cachedRegex, const void *ptr) RKL_NONNULL_ARGS(1,2); RKL_STATIC_INLINE RKLCachedRegex *rkl_cachedRegexFromRegexLookasideCacheForString (const void *ptr, RKLRegexOptions options) RKL_WARN_UNUSED_NONNULL_ARGS(1); RKL_STATIC_INLINE NSUInteger rkl_makeCacheSetHash ( CFHashCode regexHash, RKLRegexOptions options) RKL_WARN_UNUSED; RKL_STATIC_INLINE NSUInteger rkl_cacheSetForRegexHashAndOptions ( CFHashCode regexHash, RKLRegexOptions options) RKL_WARN_UNUSED; RKL_STATIC_INLINE NSUInteger rkl_cacheWayForCachedRegex (const RKLCachedRegex *cachedRegex) RKL_WARN_UNUSED_NONNULL_ARGS(1); RKL_STATIC_INLINE NSUInteger rkl_cacheSetForCachedRegex (const RKLCachedRegex *cachedRegex) RKL_WARN_UNUSED_NONNULL_ARGS(1); RKL_STATIC_INLINE RKLCachedRegex *rkl_cachedRegexForCacheSetAndWay ( NSUInteger cacheSet, NSUInteger cacheWay) RKL_WARN_UNUSED; RKL_STATIC_INLINE RKLCachedRegex *rkl_cachedRegexForRegexHashAndOptionsAndWay ( CFHashCode regexHash, RKLRegexOptions options, NSUInteger cacheWay) RKL_WARN_UNUSED; RKL_STATIC_INLINE void rkl_updateCachesWithCachedRegex ( RKLCachedRegex *cachedRegex, const void *ptr, int hitOrMiss RKL_UNUSED_DTRACE_ARG, int status RKL_UNUSED_DTRACE_ARG) RKL_NONNULL_ARGS(1,2); RKL_STATIC_INLINE RKLCachedRegex *rkl_leastRecentlyUsedCachedRegexForRegexHashAndOptions ( CFHashCode regexHash, RKLRegexOptions options) RKL_WARN_UNUSED; static RKLCachedRegex *rkl_getCachedRegex (NSString *regexString, RKLRegexOptions options, NSError **error, id *exception) RKL_WARN_UNUSED_NONNULL_ARGS(1,4); static NSUInteger rkl_setCachedRegexToString (RKLCachedRegex *cachedRegex, const NSRange *range, int32_t *status, id *exception RKL_UNUSED_ASSERTION_ARG) RKL_WARN_UNUSED_NONNULL_ARGS(1,2,3,4); static RKLCachedRegex *rkl_getCachedRegexSetToString (NSString *regexString, RKLRegexOptions options, NSString *matchString, NSUInteger *matchLengthPtr, NSRange *matchRange, NSError **error, id *exception, int32_t *status) RKL_WARN_UNUSED_NONNULL_ARGS(1,3,4,5,7,8); static id rkl_performDictionaryVarArgsOp(id self, SEL _cmd, RKLRegexOp regexOp, NSString *regexString, RKLRegexOptions options, NSInteger capture, id matchString, NSRange *matchRange, NSString *replacementString, NSError **error, void *result, id firstKey, va_list varArgsList) RKL_NONNULL_ARGS(1,2); static id rkl_performRegexOp (id self, SEL _cmd, RKLRegexOp regexOp, NSString *regexString, RKLRegexOptions options, NSInteger capture, id matchString, NSRange *matchRange, NSString *replacementString, NSError **error, void *result, NSUInteger captureKeysCount, id captureKeys[captureKeysCount], const int captureKeyIndexes[captureKeysCount]) RKL_NONNULL_ARGS(1,2); static void rkl_handleDelayedAssert (id self, SEL _cmd, id exception) RKL_NONNULL_ARGS(3); static NSUInteger rkl_search (RKLCachedRegex *cachedRegex, NSRange *searchRange, NSUInteger updateSearchRange, id *exception RKL_UNUSED_ASSERTION_ARG, int32_t *status) RKL_WARN_UNUSED_NONNULL_ARGS(1,2,4,5); static BOOL rkl_findRanges (RKLCachedRegex *cachedRegex, RKLRegexOp regexOp, RKLFindAll *findAll, id *exception, int32_t *status) RKL_WARN_UNUSED_NONNULL_ARGS(1,3,4,5); static NSUInteger rkl_growFindRanges (RKLCachedRegex *cachedRegex, NSUInteger lastLocation, RKLFindAll *findAll, id *exception RKL_UNUSED_ASSERTION_ARG) RKL_WARN_UNUSED_NONNULL_ARGS(1,3,4); static NSArray *rkl_makeArray (RKLCachedRegex *cachedRegex, RKLRegexOp regexOp, RKLFindAll *findAll, id *exception RKL_UNUSED_ASSERTION_ARG) RKL_WARN_UNUSED_NONNULL_ARGS(1,3,4); static id rkl_makeDictionary (RKLCachedRegex *cachedRegex, RKLRegexOp regexOp, RKLFindAll *findAll, NSUInteger captureKeysCount, id captureKeys[captureKeysCount], const int captureKeyIndexes[captureKeysCount], id *exception RKL_UNUSED_ASSERTION_ARG) RKL_WARN_UNUSED_NONNULL_ARGS(1,3,5,6); static NSString *rkl_replaceString (RKLCachedRegex *cachedRegex, id searchString, NSUInteger searchU16Length, NSString *replacementString, NSUInteger replacementU16Length, NSInteger *replacedCount, NSUInteger replaceMutable, id *exception, int32_t *status) RKL_WARN_UNUSED_NONNULL_ARGS(1,2,4,8,9); static int32_t rkl_replaceAll (RKLCachedRegex *cachedRegex, RKL_STRONG_REF const UniChar * RKL_GC_VOLATILE replacementUniChar, int32_t replacementU16Length, UniChar *replacedUniChar, int32_t replacedU16Capacity, NSInteger *replacedCount, int32_t *needU16Capacity, id *exception RKL_UNUSED_ASSERTION_ARG, int32_t *status) RKL_WARN_UNUSED_NONNULL_ARGS(1,2,4,6,7,8,9); static NSUInteger rkl_isRegexValid (id self, SEL _cmd, NSString *regex, RKLRegexOptions options, NSInteger *captureCountPtr, NSError **error) RKL_NONNULL_ARGS(1,2); static void rkl_clearStringCache (void); static void rkl_clearBuffer (RKLBuffer *buffer, NSUInteger freeDynamicBuffer) RKL_NONNULL_ARGS(1); static void rkl_clearCachedRegex (RKLCachedRegex *cachedRegex) RKL_NONNULL_ARGS(1); static void rkl_clearCachedRegexSetTo (RKLCachedRegex *cachedRegex) RKL_NONNULL_ARGS(1); static NSDictionary *rkl_userInfoDictionary (RKLUserInfoOptions userInfoOptions, NSString *regexString, RKLRegexOptions options, const UParseError *parseError, int32_t status, NSString *matchString, NSRange matchRange, NSString *replacementString, NSString *replacedString, NSInteger replacedCount, RKLRegexEnumerationOptions enumerationOptions, ...) RKL_WARN_UNUSED_SENTINEL; static NSError *rkl_makeNSError (RKLUserInfoOptions userInfoOptions, NSString *regexString, RKLRegexOptions options, const UParseError *parseError, int32_t status, NSString *matchString, NSRange matchRange, NSString *replacementString, NSString *replacedString, NSInteger replacedCount, RKLRegexEnumerationOptions enumerationOptions, NSString *errorDescription) RKL_WARN_UNUSED; static NSException *rkl_NSExceptionForRegex (NSString *regexString, RKLRegexOptions options, const UParseError *parseError, int32_t status) RKL_WARN_UNUSED_NONNULL_ARGS(1); static NSDictionary *rkl_makeAssertDictionary (const char *function, const char *file, int line, NSString *format, ...) RKL_WARN_UNUSED_NONNULL_ARGS(1,2,4); static NSString *rkl_stringFromClassAndMethod (id object, SEL selector, NSString *format, ...) RKL_WARN_UNUSED_NONNULL_ARGS(3); RKL_STATIC_INLINE int32_t rkl_getRangeForCapture(RKLCachedRegex *cr, int32_t *s, int32_t c, NSRange *r) RKL_WARN_UNUSED_NONNULL_ARGS(1,2,4); RKL_STATIC_INLINE int32_t rkl_getRangeForCapture(RKLCachedRegex *cr, int32_t *s, int32_t c, NSRange *r) { uregex *re = cr->icu_regex; int32_t start = RKL_ICU_FUNCTION_APPEND(uregex_start)(re, c, s); if(RKL_EXPECTED((*s > U_ZERO_ERROR), 0L) || (start == -1)) { *r = NSNotFoundRange; } else { r->location = (NSUInteger)start; r->length = (NSUInteger)RKL_ICU_FUNCTION_APPEND(uregex_end)(re, c, s) - r->location; r->location += cr->setToRange.location; } return(*s); } RKL_STATIC_INLINE RKLFindAll rkl_makeFindAll(RKL_STRONG_REF NSRange * RKL_GC_VOLATILE r, NSRange fir, NSInteger c, size_t s, size_t su, RKL_STRONG_REF void ** RKL_GC_VOLATILE rsb, RKL_STRONG_REF void ** RKL_GC_VOLATILE ssb, RKL_STRONG_REF void ** RKL_GC_VOLATILE asb, RKL_STRONG_REF void ** RKL_GC_VOLATILE dsb, RKL_STRONG_REF void ** RKL_GC_VOLATILE ksb, NSInteger f, NSInteger cap, NSInteger fut) RKL_WARN_UNUSED_CONST; RKL_STATIC_INLINE RKLFindAll rkl_makeFindAll(RKL_STRONG_REF NSRange * RKL_GC_VOLATILE r, NSRange fir, NSInteger c, size_t s, size_t su, RKL_STRONG_REF void ** RKL_GC_VOLATILE rsb, RKL_STRONG_REF void ** RKL_GC_VOLATILE ssb, RKL_STRONG_REF void ** RKL_GC_VOLATILE asb, RKL_STRONG_REF void ** RKL_GC_VOLATILE dsb, RKL_STRONG_REF void ** RKL_GC_VOLATILE ksb, NSInteger f, NSInteger cap, NSInteger fut) { return(((RKLFindAll){ .ranges=r, .findInRange=fir, .remainingRange=fir, .capacity=c, .found=f, .findUpTo=fut, .capture=cap, .addedSplitRanges=0L, .size=s, .stackUsed=su, .rangesScratchBuffer=rsb, .stringsScratchBuffer=ssb, .arraysScratchBuffer=asb, .dictionariesScratchBuffer=dsb, .keysScratchBuffer=ksb})); } //////////// #pragma mark - #pragma mark RKL_FAST_MUTABLE_CHECK implementation #ifdef RKL_FAST_MUTABLE_CHECK // We use a trampoline function pointer to check at run time if the function __CFStringIsMutable is available. // If it is, the trampoline function pointer is replaced with the address of that function. // Otherwise, we assume the worst case that every string is mutable. // This hopefully helps to protect us since we're using an undocumented, non-public API call. // We will keep on working if it ever does go away, just with a bit less performance due to the overhead of mutable checks. static BOOL rkl_CFStringIsMutable_first (CFStringRef str); static BOOL rkl_CFStringIsMutable_yes (CFStringRef str RKL_UNUSED_ARG) { return(YES); } static BOOL(*rkl_CFStringIsMutable) (CFStringRef str) = rkl_CFStringIsMutable_first; static BOOL rkl_CFStringIsMutable_first (CFStringRef str) { if((rkl_CFStringIsMutable = (BOOL(*)(CFStringRef))dlsym(RTLD_DEFAULT, "__CFStringIsMutable")) == NULL) { rkl_CFStringIsMutable = rkl_CFStringIsMutable_yes; } return(rkl_CFStringIsMutable(str)); } #else // RKL_FAST_MUTABLE_CHECK is not defined. Assume that all strings are potentially mutable. #define rkl_CFStringIsMutable(s) (YES) #endif // RKL_FAST_MUTABLE_CHECK //////////// #pragma mark - #pragma mark iPhone / iPod touch low memory notification handler #if defined(RKL_REGISTER_FOR_IPHONE_LOWMEM_NOTIFICATIONS) && (RKL_REGISTER_FOR_IPHONE_LOWMEM_NOTIFICATIONS == 1) // The next few lines are specifically for the iPhone to catch low memory conditions. // The basic idea is that rkl_RegisterForLowMemoryNotifications() is set to be run once by the linker at load time via __attribute((constructor)). // rkl_RegisterForLowMemoryNotifications() tries to find the iPhone low memory notification symbol. If it can find it, // it registers with the default NSNotificationCenter to call the RKLLowMemoryWarningObserver class method +lowMemoryWarning:. // rkl_RegisterForLowMemoryNotifications() uses an atomic compare and swap to guarantee that it initializes exactly once. // +lowMemoryWarning tries to acquire the cache lock. If it gets the lock, it clears the cache. If it can't, it calls performSelector: // with a delay of half a second to try again. This will hopefully prevent any deadlocks, such as a RegexKitLite request for // memory triggering a notification while the lock is held. static void rkl_RegisterForLowMemoryNotifications(void) RKL_ATTRIBUTES(used); @interface RKLLowMemoryWarningObserver : NSObject +(void)lowMemoryWarning:(id)notification; @end @implementation RKLLowMemoryWarningObserver +(void)lowMemoryWarning:(id)notification { if(OSSpinLockTry(&rkl_cacheSpinLock)) { rkl_clearStringCache(); OSSpinLockUnlock(&rkl_cacheSpinLock); } else { [[RKLLowMemoryWarningObserver class] performSelector:@selector(lowMemoryWarning:) withObject:notification afterDelay:(NSTimeInterval)0.1]; } } @end static volatile int rkl_HaveRegisteredForLowMemoryNotifications = 0; __attribute__((constructor)) static void rkl_RegisterForLowMemoryNotifications(void) { _Bool didSwap = false; void **memoryWarningNotification = NULL; while((rkl_HaveRegisteredForLowMemoryNotifications == 0) && ((didSwap = OSAtomicCompareAndSwapIntBarrier(0, 1, &rkl_HaveRegisteredForLowMemoryNotifications)) == false)) { /* Allows for spurious CAS failures. */ } if(didSwap == true) { if((memoryWarningNotification = (void **)dlsym(RTLD_DEFAULT, "UIApplicationDidReceiveMemoryWarningNotification")) != NULL) { [[NSNotificationCenter defaultCenter] addObserver:[RKLLowMemoryWarningObserver class] selector:@selector(lowMemoryWarning:) name:(NSString *)*memoryWarningNotification object:NULL]; } } } #endif // defined(RKL_REGISTER_FOR_IPHONE_LOWMEM_NOTIFICATIONS) && (RKL_REGISTER_FOR_IPHONE_LOWMEM_NOTIFICATIONS == 1) //////////// #pragma mark - #pragma mark DTrace functionality #ifdef _RKL_DTRACE_ENABLED // compiledRegexCache(unsigned long eventID, const char *regexUTF8, int options, int captures, int hitMiss, int icuStatusCode, const char *icuErrorMessage, double *hitRate); // utf16ConversionCache(unsigned long eventID, unsigned int lookupResultFlags, double *hitRate, const void *string, unsigned long NSRange.location, unsigned long NSRange.length, long length); /* provider RegexKitLite { probe compiledRegexCache(unsigned long, const char *, unsigned int, int, int, int, const char *, double *); probe utf16ConversionCache(unsigned long, unsigned int, double *, const void *, unsigned long, unsigned long, long); }; #pragma D attributes Unstable/Unstable/Common provider RegexKitLite provider #pragma D attributes Private/Private/Common provider RegexKitLite module #pragma D attributes Private/Private/Common provider RegexKitLite function #pragma D attributes Unstable/Unstable/Common provider RegexKitLite name #pragma D attributes Unstable/Unstable/Common provider RegexKitLite args */ #define REGEXKITLITE_STABILITY "___dtrace_stability$RegexKitLite$v1$4_4_5_1_1_5_1_1_5_4_4_5_4_4_5" #define REGEXKITLITE_TYPEDEFS "___dtrace_typedefs$RegexKitLite$v1" #define REGEXKITLITE_COMPILEDREGEXCACHE(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7) { __asm__ volatile(".reference " REGEXKITLITE_TYPEDEFS); __dtrace_probe$RegexKitLite$compiledRegexCache$v1$756e7369676e6564206c6f6e67$63686172202a$756e7369676e656420696e74$696e74$696e74$696e74$63686172202a$646f75626c65202a(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7); __asm__ volatile(".reference " REGEXKITLITE_STABILITY); } #define REGEXKITLITE_COMPILEDREGEXCACHE_ENABLED() __dtrace_isenabled$RegexKitLite$compiledRegexCache$v1() #define REGEXKITLITE_CONVERTEDSTRINGU16CACHE(arg0, arg1, arg2, arg3, arg4, arg5, arg6) { __asm__ volatile(".reference " REGEXKITLITE_TYPEDEFS); __dtrace_probe$RegexKitLite$utf16ConversionCache$v1$756e7369676e6564206c6f6e67$756e7369676e656420696e74$646f75626c65202a$766f6964202a$756e7369676e6564206c6f6e67$756e7369676e6564206c6f6e67$6c6f6e67(arg0, arg1, arg2, arg3, arg4, arg5, arg6); __asm__ volatile(".reference " REGEXKITLITE_STABILITY); } #define REGEXKITLITE_CONVERTEDSTRINGU16CACHE_ENABLED() __dtrace_isenabled$RegexKitLite$utf16ConversionCache$v1() extern void __dtrace_probe$RegexKitLite$compiledRegexCache$v1$756e7369676e6564206c6f6e67$63686172202a$756e7369676e656420696e74$696e74$696e74$696e74$63686172202a$646f75626c65202a(unsigned long, const char *, unsigned int, int, int, int, const char *, double *); extern int __dtrace_isenabled$RegexKitLite$compiledRegexCache$v1(void); extern void __dtrace_probe$RegexKitLite$utf16ConversionCache$v1$756e7369676e6564206c6f6e67$756e7369676e656420696e74$646f75626c65202a$766f6964202a$756e7369676e6564206c6f6e67$756e7369676e6564206c6f6e67$6c6f6e67(unsigned long, unsigned int, double *, const void *, unsigned long, unsigned long, long); extern int __dtrace_isenabled$RegexKitLite$utf16ConversionCache$v1(void); //////////////////////////// enum { RKLCacheHitLookupFlag = 1 << 0, RKLConversionRequiredLookupFlag = 1 << 1, RKLSetTextLookupFlag = 1 << 2, RKLDynamicBufferLookupFlag = 1 << 3, RKLErrorLookupFlag = 1 << 4, RKLEnumerationBufferLookupFlag = 1 << 5, }; #define rkl_dtrace_addLookupFlag(a,b) do { a |= (unsigned int)(b); } while(0) static char rkl_dtrace_regexUTF8[_RKL_REGEX_CACHE_LINES + 1UL][_RKL_DTRACE_REGEXUTF8_SIZE]; static NSUInteger rkl_dtrace_eventID, rkl_dtrace_compiledCacheLookups, rkl_dtrace_compiledCacheHits, rkl_dtrace_conversionBufferLookups, rkl_dtrace_conversionBufferHits; #define rkl_dtrace_incrementEventID() do { rkl_dtrace_eventID++; } while(0) #define rkl_dtrace_incrementAndGetEventID(v) do { rkl_dtrace_eventID++; v = rkl_dtrace_eventID; } while(0) #define rkl_dtrace_compiledRegexCache(a0, a1, a2, a3, a4, a5) do { int _a3 = (a3); rkl_dtrace_compiledCacheLookups++; if(_a3 == 1) { rkl_dtrace_compiledCacheHits++; } if(RKL_EXPECTED(REGEXKITLITE_COMPILEDREGEXCACHE_ENABLED(), 0L)) { double hitRate = 0.0; if(rkl_dtrace_compiledCacheLookups > 0UL) { hitRate = ((double)rkl_dtrace_compiledCacheHits / (double)rkl_dtrace_compiledCacheLookups) * 100.0; } REGEXKITLITE_COMPILEDREGEXCACHE(rkl_dtrace_eventID, a0, a1, a2, _a3, a4, a5, &hitRate); } } while(0) #define rkl_dtrace_utf16ConversionCache(a0, a1, a2, a3, a4) do { unsigned int _a0 = (a0); if((_a0 & RKLConversionRequiredLookupFlag) != 0U) { rkl_dtrace_conversionBufferLookups++; if((_a0 & RKLCacheHitLookupFlag) != 0U) { rkl_dtrace_conversionBufferHits++; } } if(RKL_EXPECTED(REGEXKITLITE_CONVERTEDSTRINGU16CACHE_ENABLED(), 0L)) { double hitRate = 0.0; if(rkl_dtrace_conversionBufferLookups > 0UL) { hitRate = ((double)rkl_dtrace_conversionBufferHits / (double)rkl_dtrace_conversionBufferLookups) * 100.0; } REGEXKITLITE_CONVERTEDSTRINGU16CACHE(rkl_dtrace_eventID, _a0, &hitRate, a1, a2, a3, a4); } } while(0) #define rkl_dtrace_utf16ConversionCacheWithEventID(c0, a0, a1, a2, a3, a4) do { unsigned int _a0 = (a0); if((_a0 & RKLConversionRequiredLookupFlag) != 0U) { rkl_dtrace_conversionBufferLookups++; if((_a0 & RKLCacheHitLookupFlag) != 0U) { rkl_dtrace_conversionBufferHits++; } } if(RKL_EXPECTED(REGEXKITLITE_CONVERTEDSTRINGU16CACHE_ENABLED(), 0L)) { double hitRate = 0.0; if(rkl_dtrace_conversionBufferLookups > 0UL) { hitRate = ((double)rkl_dtrace_conversionBufferHits / (double)rkl_dtrace_conversionBufferLookups) * 100.0; } REGEXKITLITE_CONVERTEDSTRINGU16CACHE(c0, _a0, &hitRate, a1, a2, a3, a4); } } while(0) // \342\200\246 == UTF8 for HORIZONTAL ELLIPSIS, aka triple dots '...' #define RKL_UTF8_ELLIPSE "\342\200\246" // rkl_dtrace_getRegexUTF8 will copy the str argument to utf8Buffer using UTF8 as the string encoding. // If the utf8 encoding would take up more bytes than the utf8Buffers length, then the unicode character 'HORIZONTAL ELLIPSIS' ('...') is appended to indicate truncation occurred. static void rkl_dtrace_getRegexUTF8(CFStringRef str, char *utf8Buffer) RKL_NONNULL_ARGS(2); static void rkl_dtrace_getRegexUTF8(CFStringRef str, char *utf8Buffer) { if((str == NULL) || (utf8Buffer == NULL)) { return; } CFIndex maxLength = ((CFIndex)_RKL_DTRACE_REGEXUTF8_SIZE - 2L), maxBytes = (maxLength - (CFIndex)sizeof(RKL_UTF8_ELLIPSE) - 1L), stringU16Length = CFStringGetLength(str), usedBytes = 0L; CFStringGetBytes(str, CFMakeRange(0L, ((stringU16Length < maxLength) ? stringU16Length : maxLength)), kCFStringEncodingUTF8, (UInt8)'?', (Boolean)0, (UInt8 *)utf8Buffer, maxBytes, &usedBytes); if(usedBytes == maxBytes) { strncpy(utf8Buffer + usedBytes, RKL_UTF8_ELLIPSE, ((size_t)_RKL_DTRACE_REGEXUTF8_SIZE - (size_t)usedBytes) - 2UL); } else { utf8Buffer[usedBytes] = (char)0; } } #else // _RKL_DTRACE_ENABLED #define rkl_dtrace_incrementEventID() #define rkl_dtrace_incrementAndGetEventID(v) #define rkl_dtrace_compiledRegexCache(a0, a1, a2, a3, a4, a5) #define rkl_dtrace_utf16ConversionCache(a0, a1, a2, a3, a4) #define rkl_dtrace_utf16ConversionCacheWithEventID(c0, a0, a1, a2, a3, a4) #define rkl_dtrace_getRegexUTF8(str, buf) #define rkl_dtrace_addLookupFlag(a,b) #endif // _RKL_DTRACE_ENABLED //////////// #pragma mark - #pragma mark RegexKitLite low-level internal functions #pragma mark - // The 4-way set associative LRU logic comes from Henry S. Warren Jr.'s Hacker's Delight, "revisions", 7-7 An LRU Algorithm: // http://www.hackersdelight.org/revisions.pdf // The functions rkl_leastRecentlyUsedWayInSet() and rkl_accessCacheSetWay() implement the cache functionality and are used // from a number of different places that need to perform caching (i.e., cached regex, cached UTF16 conversions, etc) #pragma mark 4-way set associative LRU functions RKL_STATIC_INLINE NSUInteger rkl_leastRecentlyUsedWayInSet(NSUInteger cacheSetsCount, const RKLLRUCacheSet_t cacheSetsArray[cacheSetsCount], NSUInteger set) { RKLCAbortAssert((cacheSetsArray != NULL) && ((NSInteger)cacheSetsCount > 0L) && (set < cacheSetsCount) && ((cacheSetsArray == rkl_cachedRegexCacheSets) ? set < _RKL_REGEX_LRU_CACHE_SETS : 1) && (((sizeof(unsigned int) - sizeof(RKLLRUCacheSet_t)) * 8) < (sizeof(unsigned int) * 8))); unsigned int cacheSet = (((unsigned int)cacheSetsArray[set]) << ((sizeof(unsigned int) - sizeof(RKLLRUCacheSet_t)) * 8)); // __builtin_clz takes an 'unsigned int' argument. The rest is to ensure bit alignment regardless of 32/64/whatever. NSUInteger leastRecentlyUsed = ((NSUInteger)(3LU - (NSUInteger)((__builtin_clz((~(((cacheSet & 0x77777777U) + 0x77777777U) | cacheSet | 0x77777777U))) ) >> 2))); RKLCAbortAssert(leastRecentlyUsed < _RKL_LRU_CACHE_SET_WAYS); return(leastRecentlyUsed); } RKL_STATIC_INLINE void rkl_accessCacheSetWay(NSUInteger cacheSetsCount, RKLLRUCacheSet_t cacheSetsArray[cacheSetsCount], NSUInteger cacheSet, NSUInteger cacheWay) { RKLCAbortAssert((cacheSetsArray != NULL) && ((NSInteger)cacheSetsCount > 0L) && (cacheSet < cacheSetsCount) && (cacheWay < _RKL_LRU_CACHE_SET_WAYS) && ((cacheSetsArray == rkl_cachedRegexCacheSets) ? cacheSet < _RKL_REGEX_LRU_CACHE_SETS : 1)); cacheSetsArray[cacheSet] = (RKLLRUCacheSet_t)(((cacheSetsArray[cacheSet] & (RKLLRUCacheSet_t)0xFFFFU) | (((RKLLRUCacheSet_t)0xFU) << (cacheWay * 4U))) & (~(((RKLLRUCacheSet_t)0x1111U) << (3U - cacheWay)))); } #pragma mark Common, macro'ish compiled regular expression cache logic // These functions consolidate bits and pieces of code used to maintain, update, and access the 4-way set associative LRU cache and Regex Lookaside Cache. RKL_STATIC_INLINE NSUInteger rkl_regexLookasideCacheIndexForPointerAndOptions (const void *ptr, RKLRegexOptions options) { return(((((NSUInteger)(ptr)) >> 4) + options + (options >> 4)) & _RKL_REGEX_LOOKASIDE_CACHE_MASK); } RKL_STATIC_INLINE void rkl_setRegexLookasideCacheToCachedRegexForPointer (const RKLCachedRegex *cachedRegex, const void *ptr) { rkl_regexLookasideCache[rkl_regexLookasideCacheIndexForPointerAndOptions(ptr, cachedRegex->options)] = (cachedRegex - rkl_cachedRegexes); } RKL_STATIC_INLINE RKLCachedRegex *rkl_cachedRegexFromRegexLookasideCacheForString (const void *ptr, RKLRegexOptions options) { return(&rkl_cachedRegexes[rkl_regexLookasideCache[rkl_regexLookasideCacheIndexForPointerAndOptions(ptr, options)]]); } RKL_STATIC_INLINE NSUInteger rkl_makeCacheSetHash ( CFHashCode regexHash, RKLRegexOptions options) { return((NSUInteger)regexHash ^ (NSUInteger)options); } RKL_STATIC_INLINE NSUInteger rkl_cacheSetForRegexHashAndOptions ( CFHashCode regexHash, RKLRegexOptions options) { return((rkl_makeCacheSetHash(regexHash, options) % _RKL_REGEX_LRU_CACHE_SETS)); } RKL_STATIC_INLINE NSUInteger rkl_cacheWayForCachedRegex (const RKLCachedRegex *cachedRegex) { return((cachedRegex - rkl_cachedRegexes) % _RKL_LRU_CACHE_SET_WAYS); } RKL_STATIC_INLINE NSUInteger rkl_cacheSetForCachedRegex (const RKLCachedRegex *cachedRegex) { return(rkl_cacheSetForRegexHashAndOptions(cachedRegex->regexHash, cachedRegex->options)); } RKL_STATIC_INLINE RKLCachedRegex *rkl_cachedRegexForCacheSetAndWay ( NSUInteger cacheSet, NSUInteger cacheWay) { return(&rkl_cachedRegexes[((cacheSet * _RKL_LRU_CACHE_SET_WAYS) + cacheWay)]); } RKL_STATIC_INLINE RKLCachedRegex *rkl_cachedRegexForRegexHashAndOptionsAndWay ( CFHashCode regexHash, RKLRegexOptions options, NSUInteger cacheWay) { return(rkl_cachedRegexForCacheSetAndWay(rkl_cacheSetForRegexHashAndOptions(regexHash, options), cacheWay)); } RKL_STATIC_INLINE void rkl_updateCachesWithCachedRegex(RKLCachedRegex *cachedRegex, const void *ptr, int hitOrMiss RKL_UNUSED_DTRACE_ARG, int status RKL_UNUSED_DTRACE_ARG) { rkl_lastCachedRegex = cachedRegex; rkl_setRegexLookasideCacheToCachedRegexForPointer(cachedRegex, ptr); rkl_accessCacheSetWay(_RKL_REGEX_LRU_CACHE_SETS, rkl_cachedRegexCacheSets, rkl_cacheSetForCachedRegex(cachedRegex), rkl_cacheWayForCachedRegex(cachedRegex)); // Set the matching line as the most recently used. rkl_dtrace_compiledRegexCache(&rkl_dtrace_regexUTF8[(cachedRegex - rkl_cachedRegexes)][0], cachedRegex->options, (int)cachedRegex->captureCount, hitOrMiss, status, NULL); } RKL_STATIC_INLINE RKLCachedRegex *rkl_leastRecentlyUsedCachedRegexForRegexHashAndOptions(CFHashCode regexHash, RKLRegexOptions options) { NSUInteger cacheSet = rkl_cacheSetForRegexHashAndOptions(regexHash, options); return(rkl_cachedRegexForCacheSetAndWay(cacheSet, rkl_leastRecentlyUsedWayInSet(_RKL_REGEX_LRU_CACHE_SETS, rkl_cachedRegexCacheSets, cacheSet))); } #pragma mark Regular expression lookup function // IMPORTANT! This code is critical path code. Because of this, it has been written for speed, not clarity. // IMPORTANT! Should only be called with rkl_cacheSpinLock already locked! // ---------- static RKLCachedRegex *rkl_getCachedRegex(NSString *regexString, RKLRegexOptions options, NSError **error, id *exception) { // ---------- vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv // IMPORTANT! This section of code is called almost every single time that any RegexKitLite functionality is used! It /MUST/ be very fast! // ---------- vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv RKLCachedRegex *cachedRegex = NULL; CFHashCode regexHash = 0UL; int32_t status = 0; RKLCDelayedAssert((rkl_cacheSpinLock != (OSSpinLock)0) && (regexString != NULL), exception, exitNow); // Fast path the common case where this regex is exactly the same one used last time. // The pointer equality test is valid under these circumstances since the cachedRegex->regexString is an immutable copy. // If the regexString argument is mutable, this test will fail, and we'll use the the slow path cache check below. if(RKL_EXPECTED(rkl_lastCachedRegex != NULL, 1L) && RKL_EXPECTED(rkl_lastCachedRegex->regexString == (CFStringRef)regexString, 1L) && RKL_EXPECTED(rkl_lastCachedRegex->options == options, 1L) && RKL_EXPECTED(rkl_lastCachedRegex->icu_regex != NULL, 1L)) { rkl_dtrace_compiledRegexCache(&rkl_dtrace_regexUTF8[(rkl_lastCachedRegex - rkl_cachedRegexes)][0], rkl_lastCachedRegex->options, (int)rkl_lastCachedRegex->captureCount, 1, 0, NULL); return(rkl_lastCachedRegex); } rkl_lastCachedRegex = NULL; // Make sure that rkl_lastCachedRegex is NULL in case there is some kind of error. cachedRegex = rkl_cachedRegexFromRegexLookasideCacheForString(regexString, options); // Check the Regex Lookaside Cache to see if we can quickly find the correct Cached Regex for this regexString pointer + options. if((RKL_EXPECTED(cachedRegex->regexString == (CFStringRef)regexString, 1L) || (RKL_EXPECTED(cachedRegex->regexString != NULL, 1L) && RKL_EXPECTED(CFEqual((CFTypeRef)regexString, (CFTypeRef)cachedRegex->regexString) == YES, 1L))) && RKL_EXPECTED(cachedRegex->options == options, 1L) && RKL_EXPECTED(cachedRegex->icu_regex != NULL, 1L)) { goto foundMatch; } // There was a Regex Lookaside Cache hit, jump to foundMatch: to quickly return the result. A Regex Lookaside Cache hit allows us to bypass calling CFHash(), which is a decent performance win. else { cachedRegex = NULL; regexHash = CFHash((CFTypeRef)regexString); } // Regex Lookaside Cache miss. We need to call CFHash() to determine the cache set for this regex. NSInteger cacheWay = 0L; // Check each way of the set that this regex belongs to. for(cacheWay = ((NSInteger)_RKL_LRU_CACHE_SET_WAYS - 1L); cacheWay > 0L; cacheWay--) { // Checking the ways in reverse (3, 2, 1, 0) finds a match "sooner" on average. cachedRegex = rkl_cachedRegexForRegexHashAndOptionsAndWay(regexHash, options, (NSUInteger)cacheWay); // Return the cached entry if it's a match. If regexString is mutable, the pointer equality test will fail, and CFEqual() is used to determine true equality with the immutable cachedRegex copy. CFEqual() performs a slow character by character check. if(RKL_EXPECTED(cachedRegex->regexHash == regexHash, 0UL) && ((cachedRegex->regexString == (CFStringRef)regexString) || (RKL_EXPECTED(cachedRegex->regexString != NULL, 1L) && RKL_EXPECTED(CFEqual((CFTypeRef)regexString, (CFTypeRef)cachedRegex->regexString) == YES, 1L))) && RKL_EXPECTED(cachedRegex->options == options, 1L) && RKL_EXPECTED(cachedRegex->icu_regex != NULL, 1L)) { foundMatch: // Control can transfer here (from above) via a Regex Lookaside Cache hit. rkl_updateCachesWithCachedRegex(cachedRegex, regexString, 1, 0); return(cachedRegex); } } // ---------- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ // IMPORTANT! This section of code is called almost every single time that any RegexKitLite functionality is used! It /MUST/ be very fast! // ---------- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ // Code below this point is not as sensitive to speed since compiling a regular expression is an extremely expensive operation. // The regex was not found in the cache. Get the cached regex for the least recently used line in the set, then clear the cached regex and create a new ICU regex in its place. cachedRegex = rkl_leastRecentlyUsedCachedRegexForRegexHashAndOptions(regexHash, options); rkl_clearCachedRegex(cachedRegex); if(RKL_EXPECTED((cachedRegex->regexString = CFStringCreateCopy(NULL, (CFStringRef)regexString)) == NULL, 0L)) { goto exitNow; } ; // Get a cheap immutable copy. rkl_dtrace_getRegexUTF8(cachedRegex->regexString, &rkl_dtrace_regexUTF8[(cachedRegex - rkl_cachedRegexes)][0]); cachedRegex->regexHash = regexHash; cachedRegex->options = options; CFIndex regexStringU16Length = CFStringGetLength(cachedRegex->regexString); // In UTF16 code units. UParseError parseError = (UParseError){-1, -1, {0}, {0}}; RKL_STRONG_REF const UniChar * RKL_GC_VOLATILE regexUniChar = NULL; if(RKL_EXPECTED(regexStringU16Length >= (CFIndex)INT_MAX, 0L)) { *exception = [NSException exceptionWithName:NSRangeException reason:@"Regex string length exceeds INT_MAX" userInfo:NULL]; goto exitNow; } // Try to quickly obtain regexString in UTF16 format. if((regexUniChar = CFStringGetCharactersPtr(cachedRegex->regexString)) == NULL) { // We didn't get the UTF16 pointer quickly and need to perform a full conversion in a temp buffer. RKL_STRONG_REF UniChar * RKL_GC_VOLATILE uniCharBuffer = NULL; if(((size_t)regexStringU16Length * sizeof(UniChar)) < (size_t)_RKL_STACK_LIMIT) { if(RKL_EXPECTED((uniCharBuffer = (RKL_STRONG_REF UniChar * RKL_GC_VOLATILE)alloca( (size_t)regexStringU16Length * sizeof(UniChar) )) == NULL, 0L)) { goto exitNow; } } // Try to use the stack. else { if(RKL_EXPECTED((uniCharBuffer = (RKL_STRONG_REF UniChar * RKL_GC_VOLATILE)rkl_realloc(&rkl_scratchBuffer[0], (size_t)regexStringU16Length * sizeof(UniChar), 0UL)) == NULL, 0L)) { goto exitNow; } } // Otherwise use the heap. CFStringGetCharacters(cachedRegex->regexString, CFMakeRange(0L, regexStringU16Length), uniCharBuffer); // Convert regexString to UTF16. regexUniChar = uniCharBuffer; } // Create the ICU regex. if(RKL_EXPECTED((cachedRegex->icu_regex = RKL_ICU_FUNCTION_APPEND(uregex_open)(regexUniChar, (int32_t)regexStringU16Length, options, &parseError, &status)) == NULL, 0L)) { goto exitNow; } if(RKL_EXPECTED(status <= U_ZERO_ERROR, 1L)) { cachedRegex->captureCount = (NSInteger)RKL_ICU_FUNCTION_APPEND(uregex_groupCount)(cachedRegex->icu_regex, &status); } if(RKL_EXPECTED(status <= U_ZERO_ERROR, 1L)) { rkl_updateCachesWithCachedRegex(cachedRegex, regexString, 0, status); } exitNow: if(RKL_EXPECTED(rkl_scratchBuffer[0] != NULL, 0L)) { rkl_scratchBuffer[0] = rkl_free(&rkl_scratchBuffer[0]); } if(RKL_EXPECTED(status > U_ZERO_ERROR, 0L)) { rkl_clearCachedRegex(cachedRegex); cachedRegex = rkl_lastCachedRegex = NULL; if(error != NULL) { *error = rkl_makeNSError((RKLUserInfoOptions)RKLUserInfoNone, regexString, options, &parseError, status, NULL, NSNotFoundRange, NULL, NULL, 0L, (RKLRegexEnumerationOptions)RKLRegexEnumerationNoOptions, @"There was an error compiling the regular expression."); } } #ifdef _RKL_DTRACE_ENABLED if(RKL_EXPECTED(cachedRegex == NULL, 1L)) { char regexUTF8[_RKL_DTRACE_REGEXUTF8_SIZE]; const char *err = NULL; if(status != U_ZERO_ERROR) { err = RKL_ICU_FUNCTION_APPEND(u_errorName)(status); } rkl_dtrace_getRegexUTF8((CFStringRef)regexString, regexUTF8); rkl_dtrace_compiledRegexCache(regexUTF8, options, -1, -1, status, err); } #endif // _RKL_DTRACE_ENABLED return(cachedRegex); } // IMPORTANT! This code is critical path code. Because of this, it has been written for speed, not clarity. // IMPORTANT! Should only be called with rkl_cacheSpinLock already locked! // ---------- #pragma mark Set a cached regular expression to a NSStrings UTF-16 text static NSUInteger rkl_setCachedRegexToString(RKLCachedRegex *cachedRegex, const NSRange *range, int32_t *status, id *exception RKL_UNUSED_ASSERTION_ARG) { // ---------- vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv // IMPORTANT! This section of code is called almost every single time that any RegexKitLite functionality is used! It /MUST/ be very fast! // ---------- vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv RKLCDelayedAssert((cachedRegex != NULL) && (cachedRegex->setToString != NULL) && ((range != NULL) && (NSEqualRanges(*range, NSNotFoundRange) == NO)) && (status != NULL), exception, exitNow); RKL_STRONG_REF const UniChar * RKL_GC_VOLATILE stringUniChar = NULL; #ifdef _RKL_DTRACE_ENABLED unsigned int lookupResultFlags = 0U; #endif NSUInteger useFixedBuffer = (cachedRegex->setToLength < (CFIndex)_RKL_FIXED_LENGTH) ? 1UL : 0UL; RKLBuffer *buffer = NULL; if(cachedRegex->setToNeedsConversion == 0U) { RKLCDelayedAssert((cachedRegex->setToUniChar != NULL) && (cachedRegex->buffer == NULL), exception, exitNow); if(RKL_EXPECTED((stringUniChar = (RKL_STRONG_REF const UniChar * RKL_GC_VOLATILE)CFStringGetCharactersPtr(cachedRegex->setToString)) == NULL, 0L)) { cachedRegex->setToUniChar = NULL; cachedRegex->setToRange = NSNotFoundRange; cachedRegex->setToNeedsConversion = 1U; } else { if(RKL_EXPECTED(cachedRegex->setToUniChar != stringUniChar, 0L)) { cachedRegex->setToRange = NSNotFoundRange; cachedRegex->setToUniChar = stringUniChar; } goto setRegexText; } } buffer = cachedRegex->buffer; RKLCDelayedAssert((buffer == NULL) ? 1 : (((buffer == &rkl_lruFixedBuffer[0]) || (buffer == &rkl_lruFixedBuffer[1]) || (buffer == &rkl_lruFixedBuffer[2]) || (buffer == &rkl_lruFixedBuffer[3])) || ((buffer == &rkl_lruDynamicBuffer[0]) || (buffer == &rkl_lruDynamicBuffer[1]) || (buffer == &rkl_lruDynamicBuffer[2]) || (buffer == &rkl_lruDynamicBuffer[3]))), exception, exitNow); if((buffer != NULL) && RKL_EXPECTED(cachedRegex->setToString == buffer->string, 1L) && RKL_EXPECTED(cachedRegex->setToHash == buffer->hash, 1L) && RKL_EXPECTED(cachedRegex->setToLength == buffer->length, 1L)) { RKLCDelayedAssert((buffer->uniChar != NULL), exception, exitNow); rkl_dtrace_addLookupFlag(lookupResultFlags, RKLCacheHitLookupFlag | RKLConversionRequiredLookupFlag | (useFixedBuffer ? 0U : RKLDynamicBufferLookupFlag)); if(cachedRegex->setToUniChar != buffer->uniChar) { cachedRegex->setToRange = NSNotFoundRange; cachedRegex->setToUniChar = buffer->uniChar; } goto setRegexText; } buffer = NULL; cachedRegex->buffer = NULL; NSInteger cacheWay = 0L; for(cacheWay = ((NSInteger)_RKL_LRU_CACHE_SET_WAYS - 1L); cacheWay > 0L; cacheWay--) { if(useFixedBuffer) { buffer = &rkl_lruFixedBuffer[cacheWay]; } else { buffer = &rkl_lruDynamicBuffer[cacheWay]; } if(RKL_EXPECTED(cachedRegex->setToString == buffer->string, 1L) && RKL_EXPECTED(cachedRegex->setToHash == buffer->hash, 1L) && RKL_EXPECTED(cachedRegex->setToLength == buffer->length, 1L)) { RKLCDelayedAssert((buffer->uniChar != NULL), exception, exitNow); rkl_dtrace_addLookupFlag(lookupResultFlags, RKLCacheHitLookupFlag | RKLConversionRequiredLookupFlag | (useFixedBuffer ? 0U : RKLDynamicBufferLookupFlag)); if(cachedRegex->setToUniChar != buffer->uniChar) { cachedRegex->setToRange = NSNotFoundRange; cachedRegex->setToUniChar = buffer->uniChar; } cachedRegex->buffer = buffer; goto setRegexText; } } buffer = NULL; cachedRegex->setToUniChar = NULL; cachedRegex->setToRange = NSNotFoundRange; cachedRegex->buffer = NULL; RKLCDelayedAssert((cachedRegex->setToNeedsConversion == 1U) && (cachedRegex->buffer == NULL), exception, exitNow); if(RKL_EXPECTED(cachedRegex->setToNeedsConversion == 1U, 1L) && RKL_EXPECTED((cachedRegex->setToUniChar = (RKL_STRONG_REF const UniChar * RKL_GC_VOLATILE)CFStringGetCharactersPtr(cachedRegex->setToString)) != NULL, 0L)) { cachedRegex->setToNeedsConversion = 0U; cachedRegex->setToRange = NSNotFoundRange; goto setRegexText; } rkl_dtrace_addLookupFlag(lookupResultFlags, RKLConversionRequiredLookupFlag | (useFixedBuffer ? 0U : RKLDynamicBufferLookupFlag)); if(useFixedBuffer) { buffer = &rkl_lruFixedBuffer [rkl_leastRecentlyUsedWayInSet(1UL, &rkl_lruFixedBufferCacheSet, 0UL)]; } else { buffer = &rkl_lruDynamicBuffer[rkl_leastRecentlyUsedWayInSet(1UL, &rkl_lruDynamicBufferCacheSet, 0UL)]; } RKLCDelayedAssert((useFixedBuffer) ? ((buffer == &rkl_lruFixedBuffer[0]) || (buffer == &rkl_lruFixedBuffer[1]) || (buffer == &rkl_lruFixedBuffer[2]) || (buffer == &rkl_lruFixedBuffer[3])) : ((buffer == &rkl_lruDynamicBuffer[0]) || (buffer == &rkl_lruDynamicBuffer[1]) || (buffer == &rkl_lruDynamicBuffer[2]) || (buffer == &rkl_lruDynamicBuffer[3])), exception, exitNow); rkl_clearBuffer(buffer, 0UL); RKLCDelayedAssert((buffer->string == NULL) && (cachedRegex->setToString != NULL), exception, exitNow); if(RKL_EXPECTED((buffer->string = (CFStringRef)CFRetain((CFTypeRef)cachedRegex->setToString)) == NULL, 0L)) { goto exitNow; } buffer->hash = cachedRegex->setToHash; buffer->length = cachedRegex->setToLength; if(useFixedBuffer == 0UL) { RKL_STRONG_REF void * RKL_GC_VOLATILE p = (RKL_STRONG_REF void * RKL_GC_VOLATILE)buffer->uniChar; if(RKL_EXPECTED((buffer->uniChar = (RKL_STRONG_REF UniChar * RKL_GC_VOLATILE)rkl_realloc(&p, ((size_t)buffer->length * sizeof(UniChar)), 0UL)) == NULL, 0L)) { goto exitNow; } // Resize the buffer. } RKLCDelayedAssert((buffer->string != NULL) && (buffer->uniChar != NULL), exception, exitNow); CFStringGetCharacters(buffer->string, CFMakeRange(0L, buffer->length), (UniChar *)buffer->uniChar); // Convert to a UTF16 string. cachedRegex->setToUniChar = buffer->uniChar; cachedRegex->setToRange = NSNotFoundRange; cachedRegex->buffer = buffer; setRegexText: if(buffer != NULL) { if(useFixedBuffer == 1UL) { rkl_accessCacheSetWay(1UL, &rkl_lruFixedBufferCacheSet, 0UL, (NSUInteger)(buffer - rkl_lruFixedBuffer)); } else { rkl_accessCacheSetWay(1UL, &rkl_lruDynamicBufferCacheSet, 0UL, (NSUInteger)(buffer - rkl_lruDynamicBuffer)); } } if(NSEqualRanges(cachedRegex->setToRange, *range) == NO) { RKLCDelayedAssert((cachedRegex->icu_regex != NULL) && (cachedRegex->setToUniChar != NULL) && (NSMaxRange(*range) <= (NSUInteger)cachedRegex->setToLength) && (cachedRegex->setToRange.length <= INT_MAX), exception, exitNow); cachedRegex->lastFindRange = cachedRegex->lastMatchRange = NSNotFoundRange; cachedRegex->setToRange = *range; RKL_ICU_FUNCTION_APPEND(uregex_setText)(cachedRegex->icu_regex, cachedRegex->setToUniChar + cachedRegex->setToRange.location, (int32_t)cachedRegex->setToRange.length, status); rkl_dtrace_addLookupFlag(lookupResultFlags, RKLSetTextLookupFlag); if(RKL_EXPECTED(*status > U_ZERO_ERROR, 0L)) { rkl_dtrace_addLookupFlag(lookupResultFlags, RKLErrorLookupFlag); goto exitNow; } } rkl_dtrace_utf16ConversionCache(lookupResultFlags, cachedRegex->setToString, cachedRegex->setToRange.location, cachedRegex->setToRange.length, cachedRegex->setToLength); return(1UL); // ---------- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ // IMPORTANT! This section of code is called almost every single time that any RegexKitLite functionality is used! It /MUST/ be very fast! // ---------- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ exitNow: #ifdef _RKL_DTRACE_ENABLED rkl_dtrace_addLookupFlag(lookupResultFlags, RKLErrorLookupFlag); if(cachedRegex != NULL) { rkl_dtrace_utf16ConversionCache(lookupResultFlags, cachedRegex->setToString, cachedRegex->setToRange.location, cachedRegex->setToRange.length, cachedRegex->setToLength); } #endif // _RKL_DTRACE_ENABLED if(cachedRegex != NULL) { cachedRegex->buffer = NULL; cachedRegex->setToRange = NSNotFoundRange; cachedRegex->lastFindRange = NSNotFoundRange; cachedRegex->lastMatchRange = NSNotFoundRange; } return(0UL); } // IMPORTANT! This code is critical path code. Because of this, it has been written for speed, not clarity. // IMPORTANT! Should only be called with rkl_cacheSpinLock already locked! // ---------- #pragma mark Get a regular expression and set it to a NSStrings UTF-16 text static RKLCachedRegex *rkl_getCachedRegexSetToString(NSString *regexString, RKLRegexOptions options, NSString *matchString, NSUInteger *matchLengthPtr, NSRange *matchRange, NSError **error, id *exception, int32_t *status) { // ---------- vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv // IMPORTANT! This section of code is called almost every single time that any RegexKitLite functionality is used! It /MUST/ be very fast! // ---------- vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv RKLCachedRegex *cachedRegex = NULL; RKLCDelayedAssert((regexString != NULL) && (matchString != NULL) && (exception != NULL) && (status != NULL) && (matchLengthPtr != NULL), exception, exitNow); if(RKL_EXPECTED((cachedRegex = rkl_getCachedRegex(regexString, options, error, exception)) == NULL, 0L)) { goto exitNow; } RKLCDelayedAssert(((cachedRegex >= rkl_cachedRegexes) && ((cachedRegex - rkl_cachedRegexes) < (ssize_t)_RKL_REGEX_CACHE_LINES)) && (cachedRegex != NULL) && (cachedRegex->icu_regex != NULL) && (cachedRegex->regexString != NULL) && (cachedRegex->captureCount >= 0L) && (cachedRegex == rkl_lastCachedRegex), exception, exitNow); // Optimize the case where the string to search (matchString) is immutable and the setToString immutable copy is the same string with its reference count incremented. NSUInteger isSetTo = ((cachedRegex->setToString == (CFStringRef)matchString)) ? 1UL : 0UL; CFIndex matchLength = ((cachedRegex->setToIsImmutable == 1U) && (isSetTo == 1UL)) ? cachedRegex->setToLength : CFStringGetLength((CFStringRef)matchString); *matchLengthPtr = (NSUInteger)matchLength; if(matchRange->length == NSUIntegerMax) { matchRange->length = (NSUInteger)matchLength; } // For convenience, allow NSUIntegerMax == string length. if(RKL_EXPECTED((NSUInteger)matchLength < NSMaxRange(*matchRange), 0L)) { goto exitNow; } // The match range is out of bounds for the string. performRegexOp will catch and report the problem. RKLCDelayedAssert((isSetTo == 1UL) ? (cachedRegex->setToString != NULL) : 1, exception, exitNow); if(((cachedRegex->setToIsImmutable == 1U) ? isSetTo : (NSUInteger)((isSetTo == 1UL) && (cachedRegex->setToLength == matchLength) && (cachedRegex->setToHash == CFHash((CFTypeRef)matchString)))) == 0UL) { if(cachedRegex->setToString != NULL) { rkl_clearCachedRegexSetTo(cachedRegex); } cachedRegex->setToString = (CFStringRef)CFRetain((CFTypeRef)matchString); RKLCDelayedAssert(cachedRegex->setToString != NULL, exception, exitNow); cachedRegex->setToUniChar = CFStringGetCharactersPtr(cachedRegex->setToString); cachedRegex->setToNeedsConversion = (cachedRegex->setToUniChar == NULL) ? 1U : 0U; cachedRegex->setToIsImmutable = (rkl_CFStringIsMutable(cachedRegex->setToString) == YES) ? 0U : 1U; // If RKL_FAST_MUTABLE_CHECK is not defined then setToIsImmutable will always be set to '0', or in other words mutable.. cachedRegex->setToHash = CFHash((CFTypeRef)cachedRegex->setToString); cachedRegex->setToRange = NSNotFoundRange; cachedRegex->setToLength = matchLength; } if(RKL_EXPECTED(rkl_setCachedRegexToString(cachedRegex, matchRange, status, exception) == 0UL, 0L)) { cachedRegex = NULL; if(*exception == NULL) { *exception = (id)RKLCAssertDictionary(@"Failed to set up UTF16 buffer."); } goto exitNow; } exitNow: return(cachedRegex); // ---------- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ // IMPORTANT! This section of code is called almost every single time that any RegexKitLite functionality is used! It /MUST/ be very fast! // ---------- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ } #pragma mark GCC cleanup __attribute__ functions that ensure the global rkl_cacheSpinLock is properly unlocked #ifdef RKL_HAVE_CLEANUP // rkl_cleanup_cacheSpinLockStatus takes advantage of GCC's 'cleanup' variable attribute. When an 'auto' variable with the 'cleanup' attribute goes out of scope, // GCC arranges to have the designated function called. In this case, we make sure that if rkl_cacheSpinLock was locked that it was also unlocked. // If rkl_cacheSpinLock was locked, but the rkl_cacheSpinLockStatus unlocked flag was not set, we force rkl_cacheSpinLock unlocked with a call to OSSpinLockUnlock. // This is not a panacea for preventing mutex usage errors. Old style ObjC exceptions will bypass the cleanup call, but newer C++ style ObjC exceptions should cause the cleanup function to be called during the stack unwind. // We do not depend on this cleanup function being called. It is used only as an extra safety net. It is probably a bug in RegexKitLite if it is ever invoked and forced to take some kind of protective action. volatile NSUInteger rkl_debugCacheSpinLockCount = 0UL; void rkl_debugCacheSpinLock (void) RKL_ATTRIBUTES(used, noinline, visibility("default")); static void rkl_cleanup_cacheSpinLockStatus (volatile NSUInteger *rkl_cacheSpinLockStatusPtr) RKL_ATTRIBUTES(used); void rkl_debugCacheSpinLock(void) { rkl_debugCacheSpinLockCount++; // This is here primarily to prevent the optimizer from optimizing away the function. } static void rkl_cleanup_cacheSpinLockStatus(volatile NSUInteger *rkl_cacheSpinLockStatusPtr) { static NSUInteger didPrintForcedUnlockWarning = 0UL, didPrintNotLockedWarning = 0UL; NSUInteger rkl_cacheSpinLockStatus = *rkl_cacheSpinLockStatusPtr; if(RKL_EXPECTED((rkl_cacheSpinLockStatus & RKLUnlockedCacheSpinLock) == 0UL, 0L) && RKL_EXPECTED((rkl_cacheSpinLockStatus & RKLLockedCacheSpinLock) != 0UL, 1L)) { if(rkl_cacheSpinLock != (OSSpinLock)0) { if(didPrintForcedUnlockWarning == 0UL) { didPrintForcedUnlockWarning = 1UL; NSLog(@"[RegexKitLite] Unusual condition detected: Recorded that rkl_cacheSpinLock was locked, but for some reason it was not unlocked. Forcibly unlocking rkl_cacheSpinLock. Set a breakpoint at rkl_debugCacheSpinLock to debug. This warning is only printed once."); } rkl_debugCacheSpinLock(); // Since this is an unusual condition, offer an attempt to catch it before we unlock. OSSpinLockUnlock(&rkl_cacheSpinLock); } else { if(didPrintNotLockedWarning == 0UL) { didPrintNotLockedWarning = 1UL; NSLog(@"[RegexKitLite] Unusual condition detected: Recorded that rkl_cacheSpinLock was locked, but for some reason it was not unlocked, yet rkl_cacheSpinLock is currently not locked? Set a breakpoint at rkl_debugCacheSpinLock to debug. This warning is only printed once."); } rkl_debugCacheSpinLock(); } } } #endif // RKL_HAVE_CLEANUP // rkl_performDictionaryVarArgsOp is a front end to rkl_performRegexOp which converts a ', ...' varargs key/captures list and converts it in to a form that rkl_performRegexOp can use. // All error checking of arguments is handled by rkl_performRegexOp. #pragma mark Front end function that handles varargs and calls rkl_performRegexOp with the marshaled results static id rkl_performDictionaryVarArgsOp(id self, SEL _cmd, RKLRegexOp regexOp, NSString *regexString, RKLRegexOptions options, NSInteger capture, id matchString, NSRange *matchRange, NSString *replacementString, NSError **error, void *result, id firstKey, va_list varArgsList) { id captureKeys[64]; int captureKeyIndexes[64]; NSUInteger captureKeysCount = 0UL; if(varArgsList != NULL) { while(captureKeysCount < 62UL) { id thisCaptureKey = (captureKeysCount == 0) ? firstKey : va_arg(varArgsList, id); if(RKL_EXPECTED(thisCaptureKey == NULL, 0L)) { break; } int thisCaptureKeyIndex = va_arg(varArgsList, int); captureKeys[captureKeysCount] = thisCaptureKey; captureKeyIndexes[captureKeysCount] = thisCaptureKeyIndex; captureKeysCount++; } } return(rkl_performRegexOp(self, _cmd, regexOp, regexString, options, capture, matchString, matchRange, replacementString, error, result, captureKeysCount, captureKeys, captureKeyIndexes)); } // IMPORTANT! This code is critical path code. Because of this, it has been written for speed, not clarity. // ---------- #pragma mark Primary internal function that Objective-C methods call to perform regular expression operations static id rkl_performRegexOp(id self, SEL _cmd, RKLRegexOp regexOp, NSString *regexString, RKLRegexOptions options, NSInteger capture, id matchString, NSRange *matchRange, NSString *replacementString, NSError **error, void *result, NSUInteger captureKeysCount, id captureKeys[captureKeysCount], const int captureKeyIndexes[captureKeysCount]) { volatile NSUInteger RKL_CLEANUP(rkl_cleanup_cacheSpinLockStatus) rkl_cacheSpinLockStatus = 0UL; NSUInteger replaceMutable = 0UL; RKLRegexOp maskedRegexOp = (regexOp & RKLMaskOp); BOOL dictionaryOp = ((maskedRegexOp == RKLDictionaryOfCapturesOp) || (maskedRegexOp == RKLArrayOfDictionariesOfCapturesOp)) ? YES : NO; if((error != NULL) && (*error != NULL)) { *error = NULL; } if(RKL_EXPECTED(regexString == NULL, 0L)) { RKL_RAISE_EXCEPTION(NSInvalidArgumentException, @"The regular expression argument is NULL."); } if(RKL_EXPECTED(matchString == NULL, 0L)) { RKL_RAISE_EXCEPTION(NSInternalInconsistencyException, @"The match string argument is NULL."); } if(RKL_EXPECTED(matchRange == NULL, 0L)) { RKL_RAISE_EXCEPTION(NSInternalInconsistencyException, @"The match range argument is NULL."); } if((maskedRegexOp == RKLReplaceOp) && RKL_EXPECTED(replacementString == NULL, 0L)) { RKL_RAISE_EXCEPTION(NSInvalidArgumentException, @"The replacement string argument is NULL."); } if((dictionaryOp == YES) && RKL_EXPECTED(captureKeys == NULL, 0L)) { RKL_RAISE_EXCEPTION(NSInvalidArgumentException, @"The keys argument is NULL."); } if((dictionaryOp == YES) && RKL_EXPECTED(captureKeyIndexes == NULL, 0L)) { RKL_RAISE_EXCEPTION(NSInvalidArgumentException, @"The captures argument is NULL."); } id resultObject = NULL, exception = NULL; int32_t status = U_ZERO_ERROR; RKLCachedRegex *cachedRegex = NULL; NSUInteger stringU16Length = 0UL, tmpIdx = 0UL; NSRange stackRanges[2048]; RKLFindAll findAll; // IMPORTANT! Once we have obtained the lock, code MUST exit via 'goto exitNow;' to unlock the lock! NO EXCEPTIONS! // ---------- OSSpinLockLock(&rkl_cacheSpinLock); // Grab the lock and get cache entry. rkl_cacheSpinLockStatus |= RKLLockedCacheSpinLock; rkl_dtrace_incrementEventID(); if(RKL_EXPECTED((cachedRegex = rkl_getCachedRegexSetToString(regexString, options, matchString, &stringU16Length, matchRange, error, &exception, &status)) == NULL, 0L)) { stringU16Length = (NSUInteger)CFStringGetLength((CFStringRef)matchString); } if(RKL_EXPECTED(matchRange->length == NSUIntegerMax, 0L)) { matchRange->length = stringU16Length; } // For convenience. if(RKL_EXPECTED(stringU16Length < NSMaxRange(*matchRange), 0L) && RKL_EXPECTED(exception == NULL, 1L)) { exception = (id)RKL_EXCEPTION(NSRangeException, @"Range or index out of bounds."); goto exitNow; } if(RKL_EXPECTED(stringU16Length >= (NSUInteger)INT_MAX, 0L) && RKL_EXPECTED(exception == NULL, 1L)) { exception = (id)RKL_EXCEPTION(NSRangeException, @"String length exceeds INT_MAX."); goto exitNow; } if(((maskedRegexOp == RKLRangeOp) || (maskedRegexOp == RKLArrayOfStringsOp)) && RKL_EXPECTED(cachedRegex != NULL, 1L) && (RKL_EXPECTED(capture < 0L, 0L) || RKL_EXPECTED(capture > cachedRegex->captureCount, 0L)) && RKL_EXPECTED(exception == NULL, 1L)) { exception = (id)RKL_EXCEPTION(NSInvalidArgumentException, @"The capture argument is not valid."); goto exitNow; } if((dictionaryOp == YES) && RKL_EXPECTED(cachedRegex != NULL, 1L) && RKL_EXPECTED(exception == NULL, 1L)) { for(tmpIdx = 0UL; tmpIdx < captureKeysCount; tmpIdx++) { if(RKL_EXPECTED(captureKeys[tmpIdx] == NULL, 0L)) { exception = (id)RKL_EXCEPTION(NSInvalidArgumentException, @"The capture key (key %lu of %lu) is NULL.", (unsigned long)(tmpIdx + 1UL), (unsigned long)captureKeysCount); break; } if((RKL_EXPECTED(captureKeyIndexes[tmpIdx] < 0, 0L) || RKL_EXPECTED(captureKeyIndexes[tmpIdx] > cachedRegex->captureCount, 0L))) { exception = (id)RKL_EXCEPTION(NSInvalidArgumentException, @"The capture argument %d (capture %lu of %lu) for key '%@' is not valid.", captureKeyIndexes[tmpIdx], (unsigned long)(tmpIdx + 1UL), (unsigned long)captureKeysCount, captureKeys[tmpIdx]); break; } } } if(RKL_EXPECTED(cachedRegex == NULL, 0L) || RKL_EXPECTED(status > U_ZERO_ERROR, 0L) || RKL_EXPECTED(exception != NULL, 0L)) { goto exitNow; } RKLCDelayedAssert(((cachedRegex >= rkl_cachedRegexes) && ((cachedRegex - rkl_cachedRegexes) < (ssize_t)_RKL_REGEX_CACHE_LINES)) && (cachedRegex != NULL) && (cachedRegex->icu_regex != NULL) && (cachedRegex->regexString != NULL) && (cachedRegex->captureCount >= 0L) && (cachedRegex->setToString != NULL) && (cachedRegex->setToLength >= 0L) && (cachedRegex->setToUniChar != NULL) && ((CFIndex)NSMaxRange(cachedRegex->setToRange) <= cachedRegex->setToLength), &exception, exitNow); RKLCDelayedAssert((cachedRegex->setToNeedsConversion == 0U) ? ((cachedRegex->setToNeedsConversion == 0U) && (cachedRegex->setToUniChar == CFStringGetCharactersPtr(cachedRegex->setToString))) : ((cachedRegex->buffer != NULL) && (cachedRegex->setToHash == cachedRegex->buffer->hash) && (cachedRegex->setToLength == cachedRegex->buffer->length) && (cachedRegex->setToUniChar == cachedRegex->buffer->uniChar)), &exception, exitNow); switch(maskedRegexOp) { case RKLRangeOp: if((RKL_EXPECTED(rkl_search(cachedRegex, matchRange, 0UL, &exception, &status) == NO, 0L)) || (RKL_EXPECTED(status > U_ZERO_ERROR, 0L))) { *(NSRange *)result = NSNotFoundRange; goto exitNow; } if(RKL_EXPECTED(capture == 0L, 1L)) { *(NSRange *)result = cachedRegex->lastMatchRange; } else { if(RKL_EXPECTED(rkl_getRangeForCapture(cachedRegex, &status, (int32_t)capture, (NSRange *)result) > U_ZERO_ERROR, 0L)) { goto exitNow; } } break; case RKLSplitOp: // Fall-thru... case RKLArrayOfStringsOp: // Fall-thru... case RKLCapturesArrayOp: // Fall-thru... case RKLArrayOfCapturesOp: // Fall-thru... case RKLDictionaryOfCapturesOp: // Fall-thru... case RKLArrayOfDictionariesOfCapturesOp: findAll = rkl_makeFindAll(stackRanges, *matchRange, 2048L, (2048UL * sizeof(NSRange)), 0UL, &rkl_scratchBuffer[0], &rkl_scratchBuffer[1], &rkl_scratchBuffer[2], &rkl_scratchBuffer[3], &rkl_scratchBuffer[4], 0L, capture, (((maskedRegexOp == RKLCapturesArrayOp) || (maskedRegexOp == RKLDictionaryOfCapturesOp)) ? 1L : NSIntegerMax)); if(RKL_EXPECTED(rkl_findRanges(cachedRegex, regexOp, &findAll, &exception, &status) == NO, 1L)) { if(RKL_EXPECTED(findAll.found == 0L, 0L)) { resultObject = (maskedRegexOp == RKLDictionaryOfCapturesOp) ? [NSDictionary dictionary] : [NSArray array]; } else { if(dictionaryOp == YES) { resultObject = rkl_makeDictionary (cachedRegex, regexOp, &findAll, captureKeysCount, captureKeys, captureKeyIndexes, &exception); } else { resultObject = rkl_makeArray (cachedRegex, regexOp, &findAll, &exception); } } } for(tmpIdx = 0UL; tmpIdx < _RKL_SCRATCH_BUFFERS; tmpIdx++) { if(RKL_EXPECTED(rkl_scratchBuffer[tmpIdx] != NULL, 0L)) { rkl_scratchBuffer[tmpIdx] = rkl_free(&rkl_scratchBuffer[tmpIdx]); } } break; case RKLReplaceOp: resultObject = rkl_replaceString(cachedRegex, matchString, stringU16Length, replacementString, (NSUInteger)CFStringGetLength((CFStringRef)replacementString), (NSInteger *)result, (replaceMutable = (((regexOp & RKLReplaceMutable) != 0) ? 1UL : 0UL)), &exception, &status); break; default: exception = RKLCAssertDictionary(@"Unknown regexOp code."); break; } exitNow: OSSpinLockUnlock(&rkl_cacheSpinLock); rkl_cacheSpinLockStatus |= RKLUnlockedCacheSpinLock; // Warning about rkl_cacheSpinLockStatus never being read can be safely ignored. if(RKL_EXPECTED(status > U_ZERO_ERROR, 0L) && RKL_EXPECTED(exception == NULL, 0L)) { exception = rkl_NSExceptionForRegex(regexString, options, NULL, status); } // If we had a problem, prepare an exception to be thrown. if(RKL_EXPECTED(exception != NULL, 0L)) { rkl_handleDelayedAssert(self, _cmd, exception); } // If there is an exception, throw it at this point. // If we're working on a mutable string and there were successful matches/replacements, then we still have work to do. // This is done outside the cache lock and with the objc replaceCharactersInRange:withString: method because Core Foundation // does not assert that the string we are attempting to update is actually a mutable string, whereas Foundation ensures // the object receiving the message is a mutable string and throws an exception if we're attempting to modify an immutable string. if(RKL_EXPECTED(replaceMutable == 1UL, 0L) && RKL_EXPECTED(*((NSInteger *)result) > 0L, 1L) && RKL_EXPECTED(status == U_ZERO_ERROR, 1L) && RKL_EXPECTED(resultObject != NULL, 1L)) { [matchString replaceCharactersInRange:*matchRange withString:resultObject]; } // If status < U_ZERO_ERROR, consider it an error, even though status < U_ZERO_ERROR is a 'warning' in ICU nomenclature. // status > U_ZERO_ERROR are an exception and handled above. // http://sourceforge.net/tracker/?func=detail&atid=990188&aid=2890810&group_id=204582 if(RKL_EXPECTED(status < U_ZERO_ERROR, 0L) && RKL_EXPECTED(resultObject == NULL, 0L) && (error != NULL)) { NSString *replacedString = NULL; NSInteger replacedCount = 0L; RKLUserInfoOptions userInfoOptions = RKLUserInfoNone; if((maskedRegexOp == RKLReplaceOp) && (result != NULL)) { userInfoOptions |= RKLUserInfoReplacedCount; replacedString = resultObject; replacedCount = *((NSInteger *)result); } if(matchRange != NULL) { userInfoOptions |= RKLUserInfoSubjectRange; } *error = rkl_makeNSError(userInfoOptions, regexString, options, NULL, status, matchString, (matchRange != NULL) ? *matchRange : NSNotFoundRange, replacementString, replacedString, replacedCount, (RKLRegexEnumerationOptions)RKLRegexEnumerationNoOptions, @"The ICU library returned an unexpected error."); } return(resultObject); } static void rkl_handleDelayedAssert(id self, SEL _cmd, id exception) { if(RKL_EXPECTED(exception != NULL, 1L)) { if([exception isKindOfClass:[NSException class]]) { [[NSException exceptionWithName:[exception name] reason:rkl_stringFromClassAndMethod(self, _cmd, [exception reason]) userInfo:[exception userInfo]] raise]; } else { id functionString = [exception objectForKey:@"function"], fileString = [exception objectForKey:@"file"], descriptionString = [exception objectForKey:@"description"], lineNumber = [exception objectForKey:@"line"]; RKLCHardAbortAssert((functionString != NULL) && (fileString != NULL) && (descriptionString != NULL) && (lineNumber != NULL)); [[NSAssertionHandler currentHandler] handleFailureInFunction:functionString file:fileString lineNumber:(NSInteger)[lineNumber longValue] description:descriptionString]; } } } // IMPORTANT! This code is critical path code. Because of this, it has been written for speed, not clarity. // IMPORTANT! Should only be called from rkl_performRegexOp() or rkl_findRanges(). // ---------- #pragma mark Primary means of performing a search with a regular expression static NSUInteger rkl_search(RKLCachedRegex *cachedRegex, NSRange *searchRange, NSUInteger updateSearchRange, id *exception RKL_UNUSED_ASSERTION_ARG, int32_t *status) { NSUInteger foundMatch = 0UL; if((NSEqualRanges(*searchRange, cachedRegex->lastFindRange) == YES) && ((cachedRegex->lastMatchRange.length > 0UL) || (cachedRegex->lastMatchRange.location == (NSUInteger)NSNotFound))) { foundMatch = ((cachedRegex->lastMatchRange.location == (NSUInteger)NSNotFound) ? 0UL : 1UL);} else { // Only perform an expensive 'find' operation iff the current find range is different than the last find range. NSUInteger findLocation = (searchRange->location - cachedRegex->setToRange.location); RKLCDelayedAssert(((searchRange->location >= cachedRegex->setToRange.location)) && (NSRangeInsideRange(*searchRange, cachedRegex->setToRange) == YES) && (findLocation < INT_MAX) && (findLocation <= cachedRegex->setToRange.length), exception, exitNow); RKL_PREFETCH_UNICHAR(cachedRegex->setToUniChar, searchRange->location); // Spool up the CPU caches. // Using uregex_findNext can be a slight performance win. NSUInteger useFindNext = (RKL_EXPECTED(searchRange->location == (NSMaxRange(cachedRegex->lastMatchRange) + ((RKL_EXPECTED(cachedRegex->lastMatchRange.length == 0UL, 0L) && RKL_EXPECTED(cachedRegex->lastMatchRange.location < NSMaxRange(cachedRegex->setToRange), 0L)) ? 1UL : 0UL)), 1L) ? 1UL : 0UL); cachedRegex->lastFindRange = *searchRange; if(RKL_EXPECTED(useFindNext == 0UL, 0L)) { if(RKL_EXPECTED((RKL_ICU_FUNCTION_APPEND(uregex_find) (cachedRegex->icu_regex, (int32_t)findLocation, status) == NO), 0L) || RKL_EXPECTED(*status > U_ZERO_ERROR, 0L)) { goto finishedFind; } } else { if(RKL_EXPECTED((RKL_ICU_FUNCTION_APPEND(uregex_findNext)(cachedRegex->icu_regex, status) == NO), 0L) || RKL_EXPECTED(*status > U_ZERO_ERROR, 0L)) { goto finishedFind; } } foundMatch = 1UL; if(RKL_EXPECTED(rkl_getRangeForCapture(cachedRegex, status, 0, &cachedRegex->lastMatchRange) > U_ZERO_ERROR, 0L)) { goto finishedFind; } RKLCDelayedAssert(NSRangeInsideRange(cachedRegex->lastMatchRange, *searchRange) == YES, exception, exitNow); } finishedFind: if(RKL_EXPECTED(*status > U_ZERO_ERROR, 0L)) { foundMatch = 0UL; cachedRegex->lastFindRange = NSNotFoundRange; } if(RKL_EXPECTED(foundMatch == 0UL, 0L)) { cachedRegex->lastFindRange = NSNotFoundRange; cachedRegex->lastMatchRange = NSNotFoundRange; if(RKL_EXPECTED(updateSearchRange == 1UL, 1L)) { *searchRange = NSMakeRange(NSMaxRange(*searchRange), 0UL); } } else { RKLCDelayedAssert(NSRangeInsideRange(cachedRegex->lastMatchRange, *searchRange) == YES, exception, exitNow); if(RKL_EXPECTED(updateSearchRange == 1UL, 1L)) { NSUInteger nextLocation = (NSMaxRange(cachedRegex->lastMatchRange) + ((RKL_EXPECTED(cachedRegex->lastMatchRange.length == 0UL, 0L) && RKL_EXPECTED(cachedRegex->lastMatchRange.location < NSMaxRange(cachedRegex->setToRange), 1L)) ? 1UL : 0UL)), locationDiff = nextLocation - searchRange->location; RKLCDelayedAssert((((locationDiff > 0UL) || ((locationDiff == 0UL) && (cachedRegex->lastMatchRange.location == NSMaxRange(cachedRegex->setToRange)))) && (locationDiff <= searchRange->length)), exception, exitNow); searchRange->location = nextLocation; searchRange->length -= locationDiff; } } #ifndef NS_BLOCK_ASSERTIONS exitNow: #endif return(foundMatch); } // IMPORTANT! This code is critical path code. Because of this, it has been written for speed, not clarity. // IMPORTANT! Should only be called from rkl_performRegexOp() or rkl_performEnumerationUsingBlock(). // ---------- #pragma mark Used to perform multiple searches at once and return the NSRange results in bulk static BOOL rkl_findRanges(RKLCachedRegex *cachedRegex, RKLRegexOp regexOp, RKLFindAll *findAll, id *exception, int32_t *status) { BOOL returnWithError = YES; RKLCDelayedAssert((((cachedRegex != NULL) && (cachedRegex->icu_regex != NULL) && (cachedRegex->setToUniChar != NULL) && (cachedRegex->captureCount >= 0L) && (cachedRegex->setToRange.location != (NSUInteger)NSNotFound)) && (status != NULL) && ((findAll != NULL) && (findAll->found == 0L) && (findAll->addedSplitRanges == 0L) && ((findAll->capacity >= 0L) && (((findAll->capacity > 0L) || (findAll->size > 0UL)) ? ((findAll->ranges != NULL) && (findAll->capacity > 0L) && (findAll->size > 0UL)) : 1)) && ((findAll->capture >= 0L) && (findAll->capture <= cachedRegex->captureCount)))), exception, exitNow); NSInteger captureCount = cachedRegex->captureCount, findAllRangeIndexOfLastNonZeroLength = 0L; NSUInteger lastLocation = findAll->findInRange.location; RKLRegexOp maskedRegexOp = (regexOp & RKLMaskOp); NSRange searchRange = findAll->findInRange; for(findAll->found = 0L; (findAll->found < findAll->findUpTo) && ((findAll->found < findAll->capacity) || (findAll->found == 0L)); findAll->found++) { NSInteger loopCapture, shouldBreak = 0L; if(RKL_EXPECTED(findAll->found >= ((findAll->capacity - ((captureCount + 2L) * 4L)) - 4L), 0L)) { if(RKL_EXPECTED(rkl_growFindRanges(cachedRegex, lastLocation, findAll, exception) == 0UL, 0L)) { goto exitNow; } } RKLCDelayedAssert((searchRange.location != (NSUInteger)NSNotFound) && (NSRangeInsideRange(searchRange, cachedRegex->setToRange) == YES) && (NSRangeInsideRange(findAll->findInRange, cachedRegex->setToRange) == YES), exception, exitNow); // This fixes a 'bug' that is also present in ICU's uregex_split(). 'Bug', in this case, means that the results of a split operation can differ from those that perl's split() creates for the same input. // "I|at|ice I eat rice" split using the regex "\b\s*" demonstrates the problem. ICU bug http://bugs.icu-project.org/trac/ticket/6826 // ICU : "", "I", "|", "at", "|", "ice", "", "I", "", "eat", "", "rice" <- Results that RegexKitLite used to produce. // PERL: "I", "|", "at", "|", "ice", "I", "eat", "rice" <- Results that RegexKitLite now produces. do { if((rkl_search(cachedRegex, &searchRange, 1UL, exception, status) == NO) || (RKL_EXPECTED(*status > U_ZERO_ERROR, 0L))) { shouldBreak = 1L; } findAll->remainingRange = searchRange; } while(RKL_EXPECTED((cachedRegex->lastMatchRange.location - lastLocation) == 0UL, 0L) && RKL_EXPECTED(cachedRegex->lastMatchRange.length == 0UL, 0L) && (maskedRegexOp == RKLSplitOp) && RKL_EXPECTED(shouldBreak == 0L, 1L)); if(RKL_EXPECTED(shouldBreak == 1L, 0L)) { break; } RKLCDelayedAssert((searchRange.location != (NSUInteger)NSNotFound) && (NSRangeInsideRange(searchRange, cachedRegex->setToRange) == YES) && (NSRangeInsideRange(findAll->findInRange, cachedRegex->setToRange) == YES) && (NSRangeInsideRange(searchRange, findAll->findInRange) == YES), exception, exitNow); RKLCDelayedAssert((NSRangeInsideRange(cachedRegex->lastFindRange, cachedRegex->setToRange) == YES) && (NSRangeInsideRange(cachedRegex->lastMatchRange, cachedRegex->setToRange) == YES) && (NSRangeInsideRange(cachedRegex->lastMatchRange, findAll->findInRange) == YES), exception, exitNow); RKLCDelayedAssert((findAll->ranges != NULL) && (findAll->found >= 0L) && (findAll->capacity >= 0L) && ((findAll->found + (captureCount + 3L) + 1L) < (findAll->capacity - 2L)), exception, exitNow); NSInteger findAllRangesIndexForCapture0 = findAll->found; switch(maskedRegexOp) { case RKLArrayOfStringsOp: if(findAll->capture == 0L) { findAll->ranges[findAll->found] = cachedRegex->lastMatchRange; } else { if(RKL_EXPECTED(rkl_getRangeForCapture(cachedRegex, status, (int32_t)findAll->capture, &findAll->ranges[findAll->found]) > U_ZERO_ERROR, 0L)) { goto exitNow; } } break; case RKLSplitOp: // Fall-thru... case RKLCapturesArrayOp: // Fall-thru... case RKLDictionaryOfCapturesOp: // Fall-thru... case RKLArrayOfDictionariesOfCapturesOp: // Fall-thru... case RKLArrayOfCapturesOp: findAll->ranges[findAll->found] = ((maskedRegexOp == RKLSplitOp) ? NSMakeRange(lastLocation, cachedRegex->lastMatchRange.location - lastLocation) : cachedRegex->lastMatchRange); for(loopCapture = 1L; loopCapture <= captureCount; loopCapture++) { RKLCDelayedAssert((findAll->found >= 0L) && (findAll->found < (findAll->capacity - 2L)) && (loopCapture < INT_MAX), exception, exitNow); if(RKL_EXPECTED(rkl_getRangeForCapture(cachedRegex, status, (int32_t)loopCapture, &findAll->ranges[++findAll->found]) > U_ZERO_ERROR, 0L)) { goto exitNow; } } break; default: if(*exception == NULL) { *exception = RKLCAssertDictionary(@"Unknown regexOp."); } goto exitNow; break; } if(findAll->ranges[findAllRangesIndexForCapture0].length > 0UL) { findAllRangeIndexOfLastNonZeroLength = findAll->found + 1UL; } lastLocation = NSMaxRange(cachedRegex->lastMatchRange); } if(RKL_EXPECTED(*status > U_ZERO_ERROR, 0L)) { goto exitNow; } RKLCDelayedAssert((findAll->ranges != NULL) && (findAll->found >= 0L) && (findAll->found < (findAll->capacity - 2L)), exception, exitNow); if(maskedRegexOp == RKLSplitOp) { if(lastLocation != NSMaxRange(findAll->findInRange)) { findAll->addedSplitRanges++; findAll->ranges[findAll->found++] = NSMakeRange(lastLocation, NSMaxRange(findAll->findInRange) - lastLocation); findAllRangeIndexOfLastNonZeroLength = findAll->found; } findAll->found = findAllRangeIndexOfLastNonZeroLength; } RKLCDelayedAssert((findAll->ranges != NULL) && (findAll->found >= 0L) && (findAll->found < (findAll->capacity - 2L)), exception, exitNow); returnWithError = NO; exitNow: return(returnWithError); } // IMPORTANT! This code is critical path code. Because of this, it has been written for speed, not clarity. // IMPORTANT! Should only be called from rkl_findRanges(). // ---------- static NSUInteger rkl_growFindRanges(RKLCachedRegex *cachedRegex, NSUInteger lastLocation, RKLFindAll *findAll, id *exception RKL_UNUSED_ASSERTION_ARG) { NSUInteger didGrowRanges = 0UL; RKLCDelayedAssert((((cachedRegex != NULL) && (cachedRegex->captureCount >= 0L)) && ((findAll != NULL) && (findAll->capacity >= 0L) && (findAll->rangesScratchBuffer != NULL) && (findAll->found >= 0L) && (((findAll->capacity > 0L) || (findAll->size > 0UL) || (findAll->ranges != NULL)) ? ((findAll->capacity > 0L) && (findAll->size > 0UL) && (findAll->ranges != NULL) && (((size_t)findAll->capacity * sizeof(NSRange)) == findAll->size)) : 1))), exception, exitNow); // Attempt to guesstimate the required capacity based on: the total length needed to search / (length we've searched so far / ranges found so far). NSInteger newCapacity = (findAll->capacity + (findAll->capacity / 2L)), estimate = (NSInteger)((float)cachedRegex->setToLength / (((float)lastLocation + 1.0f) / ((float)findAll->found + 1.0f))); newCapacity = (((newCapacity + ((estimate > newCapacity) ? estimate : newCapacity)) / 2L) + ((cachedRegex->captureCount + 2L) * 4L) + 4L); NSUInteger needToCopy = ((*findAll->rangesScratchBuffer != findAll->ranges) && (findAll->ranges != NULL)) ? 1UL : 0UL; // If findAll->ranges is set to a stack allocation then we need to manually copy the data from the stack to the new heap allocation. size_t newSize = ((size_t)newCapacity * sizeof(NSRange)); RKL_STRONG_REF NSRange * RKL_GC_VOLATILE newRanges = NULL; if(RKL_EXPECTED((newRanges = (RKL_STRONG_REF NSRange * RKL_GC_VOLATILE)rkl_realloc((RKL_STRONG_REF void ** RKL_GC_VOLATILE)findAll->rangesScratchBuffer, newSize, 0UL)) == NULL, 0L)) { findAll->capacity = 0L; findAll->size = 0UL; findAll->ranges = NULL; *findAll->rangesScratchBuffer = rkl_free((RKL_STRONG_REF void ** RKL_GC_VOLATILE)findAll->rangesScratchBuffer); goto exitNow; } else { didGrowRanges = 1UL; } if(needToCopy == 1UL) { memcpy(newRanges, findAll->ranges, findAll->size); } // If necessary, copy the existing data to the new heap allocation. findAll->capacity = newCapacity; findAll->size = newSize; findAll->ranges = newRanges; exitNow: return(didGrowRanges); } // IMPORTANT! This code is critical path code. Because of this, it has been written for speed, not clarity. // IMPORTANT! Should only be called from rkl_performRegexOp(). // ---------- #pragma mark Convert bulk results from rkl_findRanges in to various NSArray types static NSArray *rkl_makeArray(RKLCachedRegex *cachedRegex, RKLRegexOp regexOp, RKLFindAll *findAll, id *exception RKL_UNUSED_ASSERTION_ARG) { NSUInteger createdStringsCount = 0UL, createdArraysCount = 0UL, transferredStringsCount = 0UL; id * RKL_GC_VOLATILE matchedStrings = NULL, * RKL_GC_VOLATILE subcaptureArrays = NULL, emptyString = @""; NSArray * RKL_GC_VOLATILE resultArray = NULL; RKLCDelayedAssert((cachedRegex != NULL) && ((findAll != NULL) && (findAll->found >= 0L) && (findAll->stringsScratchBuffer != NULL) && (findAll->arraysScratchBuffer != NULL)), exception, exitNow); size_t matchedStringsSize = ((size_t)findAll->found * sizeof(id)); CFStringRef setToString = cachedRegex->setToString; if((findAll->stackUsed + matchedStringsSize) < (size_t)_RKL_STACK_LIMIT) { if(RKL_EXPECTED((matchedStrings = (id * RKL_GC_VOLATILE)alloca(matchedStringsSize)) == NULL, 0L)) { goto exitNow; } findAll->stackUsed += matchedStringsSize; } else { if(RKL_EXPECTED((matchedStrings = (id * RKL_GC_VOLATILE)rkl_realloc(findAll->stringsScratchBuffer, matchedStringsSize, (NSUInteger)RKLScannedOption)) == NULL, 0L)) { goto exitNow; } } { // This sub-block (and its local variables) is here for the benefit of the optimizer. NSUInteger found = (NSUInteger)findAll->found; const NSRange *rangePtr = findAll->ranges; id *matchedStringsPtr = matchedStrings; for(createdStringsCount = 0UL; createdStringsCount < found; createdStringsCount++) { NSRange range = *rangePtr++; if(RKL_EXPECTED(((*matchedStringsPtr++ = RKL_EXPECTED(range.length == 0UL, 0L) ? emptyString : rkl_CreateStringWithSubstring((id)setToString, range)) == NULL), 0L)) { goto exitNow; } } } NSUInteger arrayCount = createdStringsCount; id * RKL_GC_VOLATILE arrayObjects = matchedStrings; if((regexOp & RKLSubcapturesArray) != 0UL) { RKLCDelayedAssert(((createdStringsCount % ((NSUInteger)cachedRegex->captureCount + 1UL)) == 0UL) && (createdArraysCount == 0UL), exception, exitNow); NSUInteger captureCount = ((NSUInteger)cachedRegex->captureCount + 1UL); NSUInteger subcaptureArraysCount = (createdStringsCount / captureCount); size_t subcaptureArraysSize = ((size_t)subcaptureArraysCount * sizeof(id)); if((findAll->stackUsed + subcaptureArraysSize) < (size_t)_RKL_STACK_LIMIT) { if(RKL_EXPECTED((subcaptureArrays = (id * RKL_GC_VOLATILE)alloca(subcaptureArraysSize)) == NULL, 0L)) { goto exitNow; } findAll->stackUsed += subcaptureArraysSize; } else { if(RKL_EXPECTED((subcaptureArrays = (id * RKL_GC_VOLATILE)rkl_realloc(findAll->arraysScratchBuffer, subcaptureArraysSize, (NSUInteger)RKLScannedOption)) == NULL, 0L)) { goto exitNow; } } { // This sub-block (and its local variables) is here for the benefit of the optimizer. id *subcaptureArraysPtr = subcaptureArrays; id *matchedStringsPtr = matchedStrings; for(createdArraysCount = 0UL; createdArraysCount < subcaptureArraysCount; createdArraysCount++) { if(RKL_EXPECTED((*subcaptureArraysPtr++ = rkl_CreateArrayWithObjects((void **)matchedStringsPtr, captureCount)) == NULL, 0L)) { goto exitNow; } matchedStringsPtr += captureCount; transferredStringsCount += captureCount; } } RKLCDelayedAssert((transferredStringsCount == createdStringsCount), exception, exitNow); arrayCount = createdArraysCount; arrayObjects = subcaptureArrays; } RKLCDelayedAssert((arrayObjects != NULL), exception, exitNow); resultArray = rkl_CreateAutoreleasedArray((void **)arrayObjects, (NSUInteger)arrayCount); exitNow: if(RKL_EXPECTED(resultArray == NULL, 0L) && (rkl_collectingEnabled() == NO)) { // If we did not create an array then we need to make sure that we release any objects we created. NSUInteger x; if(matchedStrings != NULL) { for(x = transferredStringsCount; x < createdStringsCount; x++) { if((matchedStrings[x] != NULL) && (matchedStrings[x] != emptyString)) { matchedStrings[x] = rkl_ReleaseObject(matchedStrings[x]); } } } if(subcaptureArrays != NULL) { for(x = 0UL; x < createdArraysCount; x++) { if(subcaptureArrays[x] != NULL) { subcaptureArrays[x] = rkl_ReleaseObject(subcaptureArrays[x]); } } } } return(resultArray); } // IMPORTANT! This code is critical path code. Because of this, it has been written for speed, not clarity. // IMPORTANT! Should only be called from rkl_performRegexOp(). // ---------- #pragma mark Convert bulk results from rkl_findRanges in to various NSDictionary types static id rkl_makeDictionary(RKLCachedRegex *cachedRegex, RKLRegexOp regexOp, RKLFindAll *findAll, NSUInteger captureKeysCount, id captureKeys[captureKeysCount], const int captureKeyIndexes[captureKeysCount], id *exception RKL_UNUSED_ASSERTION_ARG) { NSUInteger matchedStringIndex = 0UL, createdStringsCount = 0UL, createdDictionariesCount = 0UL, matchedDictionariesCount = (findAll->found / (cachedRegex->captureCount + 1UL)), transferredDictionariesCount = 0UL; id * RKL_GC_VOLATILE matchedStrings = NULL, * RKL_GC_VOLATILE matchedKeys = NULL, emptyString = @""; id RKL_GC_VOLATILE returnObject = NULL; NSDictionary ** RKL_GC_VOLATILE matchedDictionaries = NULL; RKLCDelayedAssert((cachedRegex != NULL) && ((findAll != NULL) && (findAll->found >= 0L) && (findAll->stringsScratchBuffer != NULL) && (findAll->dictionariesScratchBuffer != NULL) && (findAll->keysScratchBuffer != NULL) && (captureKeyIndexes != NULL)), exception, exitNow); CFStringRef setToString = cachedRegex->setToString; size_t matchedStringsSize = ((size_t)captureKeysCount * sizeof(void *)); if((findAll->stackUsed + matchedStringsSize) < (size_t)_RKL_STACK_LIMIT) { if(RKL_EXPECTED((matchedStrings = (id * RKL_GC_VOLATILE)alloca(matchedStringsSize)) == NULL, 0L)) { goto exitNow; } findAll->stackUsed += matchedStringsSize; } else { if(RKL_EXPECTED((matchedStrings = (id * RKL_GC_VOLATILE)rkl_realloc(findAll->stringsScratchBuffer, matchedStringsSize, (NSUInteger)RKLScannedOption)) == NULL, 0L)) { goto exitNow; } } size_t matchedKeysSize = ((size_t)captureKeysCount * sizeof(void *)); if((findAll->stackUsed + matchedKeysSize) < (size_t)_RKL_STACK_LIMIT) { if(RKL_EXPECTED((matchedKeys = (id * RKL_GC_VOLATILE)alloca(matchedKeysSize)) == NULL, 0L)) { goto exitNow; } findAll->stackUsed += matchedKeysSize; } else { if(RKL_EXPECTED((matchedKeys = (id * RKL_GC_VOLATILE)rkl_realloc(findAll->keysScratchBuffer, matchedKeysSize, (NSUInteger)RKLScannedOption)) == NULL, 0L)) { goto exitNow; } } size_t matchedDictionariesSize = ((size_t)matchedDictionariesCount * sizeof(NSDictionary *)); if((findAll->stackUsed + matchedDictionariesSize) < (size_t)_RKL_STACK_LIMIT) { if(RKL_EXPECTED((matchedDictionaries = (NSDictionary ** RKL_GC_VOLATILE)alloca(matchedDictionariesSize)) == NULL, 0L)) { goto exitNow; } findAll->stackUsed += matchedDictionariesSize; } else { if(RKL_EXPECTED((matchedDictionaries = (NSDictionary ** RKL_GC_VOLATILE)rkl_realloc(findAll->dictionariesScratchBuffer, matchedDictionariesSize, (NSUInteger)RKLScannedOption)) == NULL, 0L)) { goto exitNow; } } { // This sub-block (and its local variables) is here for the benefit of the optimizer. NSUInteger captureCount = cachedRegex->captureCount; NSDictionary **matchedDictionariesPtr = matchedDictionaries; for(createdDictionariesCount = 0UL; createdDictionariesCount < matchedDictionariesCount; createdDictionariesCount++) { RKLCDelayedAssert(((createdDictionariesCount * captureCount) < (NSUInteger)findAll->found), exception, exitNow); RKL_STRONG_REF const NSRange * RKL_GC_VOLATILE rangePtr = &findAll->ranges[(createdDictionariesCount * (captureCount + 1UL))]; for(matchedStringIndex = 0UL; matchedStringIndex < captureKeysCount; matchedStringIndex++) { NSRange range = rangePtr[captureKeyIndexes[matchedStringIndex]]; if(RKL_EXPECTED(range.location != NSNotFound, 0L)) { if(RKL_EXPECTED(((matchedStrings[createdStringsCount] = RKL_EXPECTED(range.length == 0UL, 0L) ? emptyString : rkl_CreateStringWithSubstring((id)setToString, range)) == NULL), 0L)) { goto exitNow; } matchedKeys[createdStringsCount] = captureKeys[createdStringsCount]; createdStringsCount++; } } RKLCDelayedAssert((matchedStringIndex <= captureCount), exception, exitNow); if(RKL_EXPECTED(((*matchedDictionariesPtr++ = (NSDictionary * RKL_GC_VOLATILE)CFDictionaryCreate(NULL, (const void **)matchedKeys, (const void **)matchedStrings, (CFIndex)createdStringsCount, &rkl_transferOwnershipDictionaryKeyCallBacks, &rkl_transferOwnershipDictionaryValueCallBacks)) == NULL), 0L)) { goto exitNow; } createdStringsCount = 0UL; } } if(createdDictionariesCount > 0UL) { if((regexOp & RKLMaskOp) == RKLArrayOfDictionariesOfCapturesOp) { RKLCDelayedAssert((matchedDictionaries != NULL) && (createdDictionariesCount > 0UL), exception, exitNow); if((returnObject = rkl_CreateAutoreleasedArray((void **)matchedDictionaries, createdDictionariesCount)) == NULL) { goto exitNow; } transferredDictionariesCount = createdDictionariesCount; } else { RKLCDelayedAssert((matchedDictionaries != NULL) && (createdDictionariesCount == 1UL), exception, exitNow); if((returnObject = rkl_CFAutorelease(matchedDictionaries[0])) == NULL) { goto exitNow; } transferredDictionariesCount = 1UL; } } exitNow: RKLCDelayedAssert((createdDictionariesCount <= transferredDictionariesCount) && ((transferredDictionariesCount > 0UL) ? (createdStringsCount == 0UL) : 1), exception, exitNow2); #ifndef NS_BLOCK_ASSERTIONS exitNow2: #endif if(rkl_collectingEnabled() == NO) { // Release any objects, if necessary. NSUInteger x; if(matchedStrings != NULL) { for(x = 0UL; x < createdStringsCount; x++) { if((matchedStrings[x] != NULL) && (matchedStrings[x] != emptyString)) { matchedStrings[x] = rkl_ReleaseObject(matchedStrings[x]); } } } if(matchedDictionaries != NULL) { for(x = transferredDictionariesCount; x < createdDictionariesCount; x++) { if((matchedDictionaries[x] != NULL)) { matchedDictionaries[x] = rkl_ReleaseObject(matchedDictionaries[x]); } } } } return(returnObject); } // IMPORTANT! This code is critical path code. Because of this, it has been written for speed, not clarity. // IMPORTANT! Should only be called from rkl_performRegexOp(). // ---------- #pragma mark Perform "search and replace" operations on strings using ICUs uregex_*replace* functions static NSString *rkl_replaceString(RKLCachedRegex *cachedRegex, id searchString, NSUInteger searchU16Length, NSString *replacementString, NSUInteger replacementU16Length, NSInteger *replacedCountPtr, NSUInteger replaceMutable, id *exception, int32_t *status) { RKL_STRONG_REF UniChar * RKL_GC_VOLATILE tempUniCharBuffer = NULL; RKL_STRONG_REF const UniChar * RKL_GC_VOLATILE replacementUniChar = NULL; uint64_t searchU16Length64 = (uint64_t)searchU16Length, replacementU16Length64 = (uint64_t)replacementU16Length; int32_t resultU16Length = 0, tempUniCharBufferU16Capacity = 0, needU16Capacity = 0; id RKL_GC_VOLATILE resultObject = NULL; NSInteger replacedCount = -1L; if((RKL_EXPECTED(replacementU16Length64 >= (uint64_t)INT_MAX, 0L) || RKL_EXPECTED(((searchU16Length64 / 2ULL) + (replacementU16Length64 * 2ULL)) >= (uint64_t)INT_MAX, 0L))) { *exception = [NSException exceptionWithName:NSRangeException reason:@"Replacement string length exceeds INT_MAX." userInfo:NULL]; goto exitNow; } RKLCDelayedAssert((searchU16Length64 < (uint64_t)INT_MAX) && (replacementU16Length64 < (uint64_t)INT_MAX) && (((searchU16Length64 / 2ULL) + (replacementU16Length64 * 2ULL)) < (uint64_t)INT_MAX), exception, exitNow); // Zero order approximation of the buffer sizes for holding the replaced string or split strings and split strings pointer offsets. As UTF16 code units. tempUniCharBufferU16Capacity = (int32_t)(16UL + (searchU16Length + (searchU16Length / 2UL)) + (replacementU16Length * 2UL)); RKLCDelayedAssert((tempUniCharBufferU16Capacity < INT_MAX) && (tempUniCharBufferU16Capacity > 0), exception, exitNow); // Buffer sizes converted from native units to bytes. size_t stackSize = 0UL, replacementSize = ((size_t)replacementU16Length * sizeof(UniChar)), tempUniCharBufferSize = ((size_t)tempUniCharBufferU16Capacity * sizeof(UniChar)); // For the various buffers we require, we first try to allocate from the stack if we're not over the RKL_STACK_LIMIT. If we are, switch to using the heap for the buffer. if((stackSize + tempUniCharBufferSize) < (size_t)_RKL_STACK_LIMIT) { if(RKL_EXPECTED((tempUniCharBuffer = (RKL_STRONG_REF UniChar * RKL_GC_VOLATILE)alloca(tempUniCharBufferSize)) == NULL, 0L)) { goto exitNow; } stackSize += tempUniCharBufferSize; } else { if(RKL_EXPECTED((tempUniCharBuffer = (RKL_STRONG_REF UniChar * RKL_GC_VOLATILE)rkl_realloc(&rkl_scratchBuffer[0], tempUniCharBufferSize, 0UL)) == NULL, 0L)) { goto exitNow; } } // Try to get the pointer to the replacement strings UTF16 data. If we can't, allocate some buffer space, then covert to UTF16. if((replacementUniChar = CFStringGetCharactersPtr((CFStringRef)replacementString)) == NULL) { RKL_STRONG_REF UniChar * RKL_GC_VOLATILE uniCharBuffer = NULL; if((stackSize + replacementSize) < (size_t)_RKL_STACK_LIMIT) { if(RKL_EXPECTED((uniCharBuffer = (RKL_STRONG_REF UniChar * RKL_GC_VOLATILE)alloca(replacementSize)) == NULL, 0L)) { goto exitNow; } stackSize += replacementSize; } else { if(RKL_EXPECTED((uniCharBuffer = (RKL_STRONG_REF UniChar * RKL_GC_VOLATILE)rkl_realloc(&rkl_scratchBuffer[1], replacementSize, 0UL)) == NULL, 0L)) { goto exitNow; } } CFStringGetCharacters((CFStringRef)replacementString, CFMakeRange(0L, replacementU16Length), uniCharBuffer); // Convert to a UTF16 string. replacementUniChar = uniCharBuffer; } resultU16Length = rkl_replaceAll(cachedRegex, replacementUniChar, (int32_t)replacementU16Length, tempUniCharBuffer, tempUniCharBufferU16Capacity, &replacedCount, &needU16Capacity, exception, status); RKLCDelayedAssert((resultU16Length <= tempUniCharBufferU16Capacity) && (needU16Capacity >= resultU16Length) && (needU16Capacity >= 0), exception, exitNow); if(RKL_EXPECTED((needU16Capacity + 4) >= INT_MAX, 0L)) { *exception = [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Replaced string length exceeds INT_MAX." userInfo:NULL]; goto exitNow; } if(RKL_EXPECTED(*status == U_BUFFER_OVERFLOW_ERROR, 0L)) { // Our buffer guess(es) were too small. Resize the buffers and try again. // rkl_replaceAll will turn a status of U_STRING_NOT_TERMINATED_WARNING in to a U_BUFFER_OVERFLOW_ERROR. // As an extra precaution, we pad out the amount needed by an extra four characters "just in case". // http://lists.apple.com/archives/Cocoa-dev/2010/Jan/msg01011.html needU16Capacity += 4; tempUniCharBufferSize = ((size_t)(tempUniCharBufferU16Capacity = needU16Capacity + 4) * sizeof(UniChar)); // Use needU16Capacity. Bug 2890810. if((stackSize + tempUniCharBufferSize) < (size_t)_RKL_STACK_LIMIT) { if(RKL_EXPECTED((tempUniCharBuffer = (RKL_STRONG_REF UniChar * RKL_GC_VOLATILE)alloca(tempUniCharBufferSize)) == NULL, 0L)) { goto exitNow; } stackSize += tempUniCharBufferSize; } // Warning about stackSize can be safely ignored. else { if(RKL_EXPECTED((tempUniCharBuffer = (RKL_STRONG_REF UniChar * RKL_GC_VOLATILE)rkl_realloc(&rkl_scratchBuffer[0], tempUniCharBufferSize, 0UL)) == NULL, 0L)) { goto exitNow; } } *status = U_ZERO_ERROR; // Make sure the status var is cleared and try again. resultU16Length = rkl_replaceAll(cachedRegex, replacementUniChar, (int32_t)replacementU16Length, tempUniCharBuffer, tempUniCharBufferU16Capacity, &replacedCount, &needU16Capacity, exception, status); RKLCDelayedAssert((resultU16Length <= tempUniCharBufferU16Capacity) && (needU16Capacity >= resultU16Length) && (needU16Capacity >= 0), exception, exitNow); } // If status != U_ZERO_ERROR, consider it an error, even though status < U_ZERO_ERROR is a 'warning' in ICU nomenclature. // http://sourceforge.net/tracker/?func=detail&atid=990188&aid=2890810&group_id=204582 if(RKL_EXPECTED(*status != U_ZERO_ERROR, 0L)) { goto exitNow; } // Something went wrong. RKLCDelayedAssert((replacedCount >= 0L), exception, exitNow); if(RKL_EXPECTED(resultU16Length == 0, 0L)) { resultObject = @""; } // Optimize the case where the replaced text length == 0 with a @"" string. else if(RKL_EXPECTED((NSUInteger)resultU16Length == searchU16Length, 0L) && RKL_EXPECTED(replacedCount == 0L, 1L)) { // Optimize the case where the replacement == original by creating a copy. Very fast if self is immutable. if(replaceMutable == 0UL) { resultObject = rkl_CFAutorelease(CFStringCreateCopy(NULL, (CFStringRef)searchString)); } // .. but only if this is not replacing a mutable self. Warning about potential leak can be safely ignored. } else { resultObject = rkl_CFAutorelease(CFStringCreateWithCharacters(NULL, tempUniCharBuffer, (CFIndex)resultU16Length)); } // otherwise, create a new string. Warning about potential leak can be safely ignored. // If replaceMutable == 1UL, we don't do the replacement here. We wait until after we return and unlock the cache lock. // This is because we may be trying to mutate an immutable string object. if((replaceMutable == 1UL) && RKL_EXPECTED(replacedCount > 0L, 1L)) { // We're working on a mutable string and there were successful matches with replaced text, so there's work to do. if(cachedRegex->buffer != NULL) { rkl_clearBuffer(cachedRegex->buffer, 0UL); cachedRegex->buffer = NULL; } NSUInteger idx = 0UL; for(idx = 0UL; idx < _RKL_LRU_CACHE_SET_WAYS; idx++) { RKLBuffer *buffer = ((NSUInteger)cachedRegex->setToLength < _RKL_FIXED_LENGTH) ? &rkl_lruFixedBuffer[idx] : &rkl_lruDynamicBuffer[idx]; if(RKL_EXPECTED(cachedRegex->setToString == buffer->string, 0L) && (cachedRegex->setToLength == buffer->length) && (cachedRegex->setToHash == buffer->hash)) { rkl_clearBuffer(buffer, 0UL); } } rkl_clearCachedRegexSetTo(cachedRegex); // Flush any cached information about this string since it will mutate. } exitNow: if(RKL_EXPECTED(status == NULL, 0L) || RKL_EXPECTED(*status != U_ZERO_ERROR, 0L) || RKL_EXPECTED(exception == NULL, 0L) || RKL_EXPECTED(*exception != NULL, 0L)) { replacedCount = -1L; } if(rkl_scratchBuffer[0] != NULL) { rkl_scratchBuffer[0] = rkl_free(&rkl_scratchBuffer[0]); } if(rkl_scratchBuffer[1] != NULL) { rkl_scratchBuffer[1] = rkl_free(&rkl_scratchBuffer[1]); } if(replacedCountPtr != NULL) { *replacedCountPtr = replacedCount; } return(resultObject); } // The two warnings about potential leaks can be safely ignored. // IMPORTANT! Should only be called from rkl_replaceString(). // ---------- // Modified version of the ICU libraries uregex_replaceAll() that keeps count of the number of replacements made. static int32_t rkl_replaceAll(RKLCachedRegex *cachedRegex, RKL_STRONG_REF const UniChar * RKL_GC_VOLATILE replacementUniChar, int32_t replacementU16Length, UniChar *replacedUniChar, int32_t replacedU16Capacity, NSInteger *replacedCount, int32_t *needU16Capacity, id *exception RKL_UNUSED_ASSERTION_ARG, int32_t *status) { int32_t u16Length = 0, initialReplacedU16Capacity = replacedU16Capacity; NSUInteger bufferOverflowed = 0UL; NSInteger replaced = -1L; RKLCDelayedAssert((cachedRegex != NULL) && (replacementUniChar != NULL) && (replacedUniChar != NULL) && (replacedCount != NULL) && (needU16Capacity != NULL) && (status != NULL) && (replacementU16Length >= 0) && (replacedU16Capacity >= 0), exception, exitNow); cachedRegex->lastFindRange = cachedRegex->lastMatchRange = NSNotFoundRange; // Clear the cached find information for this regex so a subsequent find works correctly. RKL_ICU_FUNCTION_APPEND(uregex_reset)(cachedRegex->icu_regex, 0, status); // Work around for ICU uregex_reset() bug, see http://bugs.icu-project.org/trac/ticket/6545 // http://sourceforge.net/tracker/index.php?func=detail&aid=2105213&group_id=204582&atid=990188 if(RKL_EXPECTED(cachedRegex->setToRange.length == 0UL, 0L) && (*status == U_INDEX_OUTOFBOUNDS_ERROR)) { *status = U_ZERO_ERROR; } replaced = 0L; // This loop originally came from ICU source/i18n/uregex.cpp, uregex_replaceAll. // There is a bug in that code which causes the size of the buffer required for the replaced text to not be calculated correctly. // This contains a work around using the variable bufferOverflowed. // ICU bug: http://bugs.icu-project.org/trac/ticket/6656 // http://sourceforge.net/tracker/index.php?func=detail&aid=2408447&group_id=204582&atid=990188 while(RKL_ICU_FUNCTION_APPEND(uregex_findNext)(cachedRegex->icu_regex, status)) { replaced++; u16Length += RKL_ICU_FUNCTION_APPEND(uregex_appendReplacement)(cachedRegex->icu_regex, replacementUniChar, replacementU16Length, &replacedUniChar, &replacedU16Capacity, status); if(RKL_EXPECTED(*status == U_BUFFER_OVERFLOW_ERROR, 0L)) { bufferOverflowed = 1UL; *status = U_ZERO_ERROR; } } if(RKL_EXPECTED(*status == U_BUFFER_OVERFLOW_ERROR, 0L)) { bufferOverflowed = 1UL; *status = U_ZERO_ERROR; } if(RKL_EXPECTED(*status <= U_ZERO_ERROR, 1L)) { u16Length += RKL_ICU_FUNCTION_APPEND(uregex_appendTail)(cachedRegex->icu_regex, &replacedUniChar, &replacedU16Capacity, status); } // Try to work around a status of U_STRING_NOT_TERMINATED_WARNING. For now, we treat it as a "Buffer Overflow" error. // As an extra precaution, in rkl_replaceString, we pad out the amount needed by an extra four characters "just in case". // http://lists.apple.com/archives/Cocoa-dev/2010/Jan/msg01011.html if(RKL_EXPECTED(*status == U_STRING_NOT_TERMINATED_WARNING, 0L)) { *status = U_BUFFER_OVERFLOW_ERROR; } // Check for status <= U_ZERO_ERROR (a 'warning' in ICU nomenclature) rather than just status == U_ZERO_ERROR. // Under just the right circumstances, status might be equal to U_STRING_NOT_TERMINATED_WARNING. When this occurred, // rkl_replaceString would never get the U_BUFFER_OVERFLOW_ERROR status, and thus never grow the buffer to the size needed. // http://sourceforge.net/tracker/?func=detail&atid=990188&aid=2890810&group_id=204582 if(RKL_EXPECTED(bufferOverflowed == 1UL, 0L) && RKL_EXPECTED(*status <= U_ZERO_ERROR, 1L)) { *status = U_BUFFER_OVERFLOW_ERROR; } #ifndef NS_BLOCK_ASSERTIONS exitNow: #endif // NS_BLOCK_ASSERTIONS if(RKL_EXPECTED(replacedCount != NULL, 1L)) { *replacedCount = replaced; } if(RKL_EXPECTED(needU16Capacity != NULL, 1L)) { *needU16Capacity = u16Length; } // Use needU16Capacity to return the number of characters that are needed for the completely replaced string. Bug 2890810. return(initialReplacedU16Capacity - replacedU16Capacity); // Return the number of characters of replacedUniChar that were used. } #pragma mark Internal function used to check if a regular expression is valid. static NSUInteger rkl_isRegexValid(id self, SEL _cmd, NSString *regex, RKLRegexOptions options, NSInteger *captureCountPtr, NSError **error) { volatile NSUInteger RKL_CLEANUP(rkl_cleanup_cacheSpinLockStatus) rkl_cacheSpinLockStatus = 0UL; RKLCachedRegex *cachedRegex = NULL; NSUInteger gotCachedRegex = 0UL; NSInteger captureCount = -1L; id exception = NULL; if((error != NULL) && (*error != NULL)) { *error = NULL; } if(RKL_EXPECTED(regex == NULL, 0L)) { RKL_RAISE_EXCEPTION(NSInvalidArgumentException, @"The regular expression argument is NULL."); } OSSpinLockLock(&rkl_cacheSpinLock); rkl_cacheSpinLockStatus |= RKLLockedCacheSpinLock; rkl_dtrace_incrementEventID(); if(RKL_EXPECTED((cachedRegex = rkl_getCachedRegex(regex, options, error, &exception)) != NULL, 1L)) { gotCachedRegex = 1UL; captureCount = cachedRegex->captureCount; } cachedRegex = NULL; OSSpinLockUnlock(&rkl_cacheSpinLock); rkl_cacheSpinLockStatus |= RKLUnlockedCacheSpinLock; // Warning about rkl_cacheSpinLockStatus never being read can be safely ignored. if(captureCountPtr != NULL) { *captureCountPtr = captureCount; } if(RKL_EXPECTED(exception != NULL, 0L)) { rkl_handleDelayedAssert(self, _cmd, exception); } return(gotCachedRegex); } #pragma mark Functions used for clearing and releasing resources for various internal data structures static void rkl_clearStringCache(void) { RKLCAbortAssert(rkl_cacheSpinLock != (OSSpinLock)0); rkl_lastCachedRegex = NULL; NSUInteger x = 0UL; for(x = 0UL; x < _RKL_SCRATCH_BUFFERS; x++) { if(rkl_scratchBuffer[x] != NULL) { rkl_scratchBuffer[x] = rkl_free(&rkl_scratchBuffer[x]); } } for(x = 0UL; x < _RKL_REGEX_CACHE_LINES; x++) { rkl_clearCachedRegex(&rkl_cachedRegexes[x]); } for(x = 0UL; x < _RKL_LRU_CACHE_SET_WAYS; x++) { rkl_clearBuffer(&rkl_lruFixedBuffer[x], 0UL); rkl_clearBuffer(&rkl_lruDynamicBuffer[x], 1UL); } } static void rkl_clearBuffer(RKLBuffer *buffer, NSUInteger freeDynamicBuffer) { RKLCAbortAssert(buffer != NULL); if(RKL_EXPECTED(buffer == NULL, 0L)) { return; } if(RKL_EXPECTED(freeDynamicBuffer == 1UL, 0L) && RKL_EXPECTED(buffer->uniChar != NULL, 1L)) { RKL_STRONG_REF void * RKL_GC_VOLATILE p = (RKL_STRONG_REF void * RKL_GC_VOLATILE)buffer->uniChar; buffer->uniChar = (RKL_STRONG_REF UniChar * RKL_GC_VOLATILE)rkl_free(&p); } if(RKL_EXPECTED(buffer->string != NULL, 1L)) { CFRelease((CFTypeRef)buffer->string); buffer->string = NULL; } buffer->length = 0L; buffer->hash = 0UL; } static void rkl_clearCachedRegex(RKLCachedRegex *cachedRegex) { RKLCAbortAssert(cachedRegex != NULL); if(RKL_EXPECTED(cachedRegex == NULL, 0L)) { return; } rkl_clearCachedRegexSetTo(cachedRegex); if(rkl_lastCachedRegex == cachedRegex) { rkl_lastCachedRegex = NULL; } if(cachedRegex->icu_regex != NULL) { RKL_ICU_FUNCTION_APPEND(uregex_close)(cachedRegex->icu_regex); cachedRegex->icu_regex = NULL; cachedRegex->captureCount = -1L; } if(cachedRegex->regexString != NULL) { CFRelease((CFTypeRef)cachedRegex->regexString); cachedRegex->regexString = NULL; cachedRegex->options = 0U; cachedRegex->regexHash = 0UL; } } static void rkl_clearCachedRegexSetTo(RKLCachedRegex *cachedRegex) { RKLCAbortAssert(cachedRegex != NULL); if(RKL_EXPECTED(cachedRegex == NULL, 0L)) { return; } if(RKL_EXPECTED(cachedRegex->icu_regex != NULL, 1L)) { int32_t status = 0; RKL_ICU_FUNCTION_APPEND(uregex_setText)(cachedRegex->icu_regex, &rkl_emptyUniCharString[0], 0, &status); } if(RKL_EXPECTED(cachedRegex->setToString != NULL, 1L)) { CFRelease((CFTypeRef)cachedRegex->setToString); cachedRegex->setToString = NULL; } cachedRegex->lastFindRange = cachedRegex->lastMatchRange = cachedRegex->setToRange = NSNotFoundRange; cachedRegex->setToIsImmutable = cachedRegex->setToNeedsConversion = 0U; cachedRegex->setToUniChar = NULL; cachedRegex->setToHash = 0UL; cachedRegex->setToLength = 0L; cachedRegex->buffer = NULL; } #pragma mark Internal functions used to implement NSException and NSError functionality and userInfo NSDictionaries // Helps to keep things tidy. #define addKeyAndObject(objs, keys, i, k, o) ({id _o=(o), _k=(k); if((_o != NULL) && (_k != NULL)) { objs[i] = _o; keys[i] = _k; i++; } }) static NSDictionary *rkl_userInfoDictionary(RKLUserInfoOptions userInfoOptions, NSString *regexString, RKLRegexOptions options, const UParseError *parseError, int32_t status, NSString *matchString, NSRange matchRange, NSString *replacementString, NSString *replacedString, NSInteger replacedCount, RKLRegexEnumerationOptions enumerationOptions, ...) { va_list varArgsList; va_start(varArgsList, enumerationOptions); if(regexString == NULL) { regexString = @""; } id objects[64], keys[64]; NSUInteger count = 0UL; NSString * RKL_GC_VOLATILE errorNameString = [NSString stringWithUTF8String:RKL_ICU_FUNCTION_APPEND(u_errorName)(status)]; addKeyAndObject(objects, keys, count, RKLICURegexRegexErrorKey, regexString); addKeyAndObject(objects, keys, count, RKLICURegexRegexOptionsErrorKey, [NSNumber numberWithUnsignedInt:options]); addKeyAndObject(objects, keys, count, RKLICURegexErrorCodeErrorKey, [NSNumber numberWithInt:status]); addKeyAndObject(objects, keys, count, RKLICURegexErrorNameErrorKey, errorNameString); if(matchString != NULL) { addKeyAndObject(objects, keys, count, RKLICURegexSubjectStringErrorKey, matchString); } if((userInfoOptions & RKLUserInfoSubjectRange) != 0UL) { addKeyAndObject(objects, keys, count, RKLICURegexSubjectRangeErrorKey, [NSValue valueWithRange:matchRange]); } if(replacementString != NULL) { addKeyAndObject(objects, keys, count, RKLICURegexReplacementStringErrorKey, replacementString); } if(replacedString != NULL) { addKeyAndObject(objects, keys, count, RKLICURegexReplacedStringErrorKey, replacedString); } if((userInfoOptions & RKLUserInfoReplacedCount) != 0UL) { addKeyAndObject(objects, keys, count, RKLICURegexReplacedCountErrorKey, [NSNumber numberWithInteger:replacedCount]); } if((userInfoOptions & RKLUserInfoRegexEnumerationOptions) != 0UL) { addKeyAndObject(objects, keys, count, RKLICURegexEnumerationOptionsErrorKey, [NSNumber numberWithUnsignedInteger:enumerationOptions]); } if((parseError != NULL) && (parseError->line != -1)) { NSString *preContextString = [NSString stringWithCharacters:&parseError->preContext[0] length:(NSUInteger)RKL_ICU_FUNCTION_APPEND(u_strlen)(&parseError->preContext[0])]; NSString *postContextString = [NSString stringWithCharacters:&parseError->postContext[0] length:(NSUInteger)RKL_ICU_FUNCTION_APPEND(u_strlen)(&parseError->postContext[0])]; addKeyAndObject(objects, keys, count, RKLICURegexLineErrorKey, [NSNumber numberWithInt:parseError->line]); addKeyAndObject(objects, keys, count, RKLICURegexOffsetErrorKey, [NSNumber numberWithInt:parseError->offset]); addKeyAndObject(objects, keys, count, RKLICURegexPreContextErrorKey, preContextString); addKeyAndObject(objects, keys, count, RKLICURegexPostContextErrorKey, postContextString); addKeyAndObject(objects, keys, count, @"NSLocalizedFailureReason", ([NSString stringWithFormat:@"The error %@ occurred at line %d, column %d: %@<>%@", errorNameString, parseError->line, parseError->offset, preContextString, postContextString])); } else { addKeyAndObject(objects, keys, count, @"NSLocalizedFailureReason", ([NSString stringWithFormat:@"The error %@ occurred.", errorNameString])); } while(count < 62UL) { id obj = va_arg(varArgsList, id), key = va_arg(varArgsList, id); if((obj != NULL) && (key != NULL)) { addKeyAndObject(objects, keys, count, key, obj); } else { break; } } va_end(varArgsList); return([NSDictionary dictionaryWithObjects:&objects[0] forKeys:&keys[0] count:count]); } static NSError *rkl_makeNSError(RKLUserInfoOptions userInfoOptions, NSString *regexString, RKLRegexOptions options, const UParseError *parseError, int32_t status, NSString *matchString, NSRange matchRange, NSString *replacementString, NSString *replacedString, NSInteger replacedCount, RKLRegexEnumerationOptions enumerationOptions, NSString *errorDescription) { if(errorDescription == NULL) { errorDescription = (status == U_ZERO_ERROR) ? @"No description of this error is available." : [NSString stringWithFormat:@"ICU regular expression error #%d, %s.", status, RKL_ICU_FUNCTION_APPEND(u_errorName)(status)]; } return([NSError errorWithDomain:RKLICURegexErrorDomain code:(NSInteger)status userInfo:rkl_userInfoDictionary(userInfoOptions, regexString, options, parseError, status, matchString, matchRange, replacementString, replacedString, replacedCount, enumerationOptions, errorDescription, @"NSLocalizedDescription", NULL)]); } static NSException *rkl_NSExceptionForRegex(NSString *regexString, RKLRegexOptions options, const UParseError *parseError, int32_t status) { return([NSException exceptionWithName:RKLICURegexException reason:[NSString stringWithFormat:@"ICU regular expression error #%d, %s.", status, RKL_ICU_FUNCTION_APPEND(u_errorName)(status)] userInfo:rkl_userInfoDictionary((RKLUserInfoOptions)RKLUserInfoNone, regexString, options, parseError, status, NULL, NSNotFoundRange, NULL, NULL, 0L, (RKLRegexEnumerationOptions)RKLRegexEnumerationNoOptions, NULL)]); } static NSDictionary *rkl_makeAssertDictionary(const char *function, const char *file, int line, NSString *format, ...) { va_list varArgsList; va_start(varArgsList, format); NSString * RKL_GC_VOLATILE formatString = [[[NSString alloc] initWithFormat:format arguments:varArgsList] autorelease]; va_end(varArgsList); NSString * RKL_GC_VOLATILE functionString = [NSString stringWithUTF8String:function], *fileString = [NSString stringWithUTF8String:file]; return([NSDictionary dictionaryWithObjectsAndKeys:formatString, @"description", functionString, @"function", fileString, @"file", [NSNumber numberWithInt:line], @"line", NSInternalInconsistencyException, @"exceptionName", NULL]); } static NSString *rkl_stringFromClassAndMethod(id object, SEL selector, NSString *format, ...) { va_list varArgsList; va_start(varArgsList, format); NSString * RKL_GC_VOLATILE formatString = [[[NSString alloc] initWithFormat:format arguments:varArgsList] autorelease]; va_end(varArgsList); Class objectsClass = (object == NULL) ? NULL : [object class]; return([NSString stringWithFormat:@"*** %c[%@ %@]: %@", (object == objectsClass) ? '+' : '-', (objectsClass == NULL) ? @"" : NSStringFromClass(objectsClass), (selector == NULL) ? @":NULL:" : NSStringFromSelector(selector), formatString]); } #ifdef _RKL_BLOCKS_ENABLED //////////// #pragma mark - #pragma mark Objective-C ^Blocks Support #pragma mark - //////////// // Prototypes static id rkl_performEnumerationUsingBlock(id self, SEL _cmd, RKLRegexOp regexOp, NSString *regexString, RKLRegexOptions options, id matchString, NSRange matchRange, RKLBlockEnumerationOp blockEnumerationOp, RKLRegexEnumerationOptions enumerationOptions, NSInteger *replacedCountPtr, NSUInteger *errorFreePtr, NSError **error, void (^stringsAndRangesBlock)(NSInteger capturedCount, NSString * const capturedStrings[capturedCount], const NSRange capturedStringRanges[capturedCount], volatile BOOL * const stop), NSString *(^replaceStringsAndRangesBlock)(NSInteger capturedCount, NSString * const capturedStrings[capturedCount], const NSRange capturedStringRanges[capturedCount], volatile BOOL * const stop) ) RKL_NONNULL_ARGS(1,2,4,6); // This is an object meant for internal use only. It wraps and abstracts various functionality to simplify ^Blocks support. @interface RKLBlockEnumerationHelper : NSObject { @public RKLCachedRegex cachedRegex; RKLBuffer buffer; RKL_STRONG_REF void * RKL_GC_VOLATILE scratchBuffer[_RKL_SCRATCH_BUFFERS]; NSUInteger needToFreeBufferUniChar:1; } - (id)initWithRegex:(NSString *)initRegexString options:(RKLRegexOptions)initOptions string:(NSString *)initString range:(NSRange)initRange error:(NSError **)initError; @end @implementation RKLBlockEnumerationHelper - (id)initWithRegex:(NSString *)initRegexString options:(RKLRegexOptions)initOptions string:(NSString *)initString range:(NSRange)initRange error:(NSError **)initError { volatile NSUInteger RKL_CLEANUP(rkl_cleanup_cacheSpinLockStatus) rkl_cacheSpinLockStatus = 0UL; int32_t status = U_ZERO_ERROR; id exception = NULL; RKLCachedRegex *retrievedCachedRegex = NULL; #ifdef _RKL_DTRACE_ENABLED NSUInteger thisDTraceEventID = 0UL; unsigned int lookupResultFlags = 0U; #endif if(RKL_EXPECTED((self = [super init]) == NULL, 0L)) { goto errorExit; } RKLCDelayedAssert((initRegexString != NULL) && (initString != NULL), &exception, errorExit); // IMPORTANT! Once we have obtained the lock, code MUST exit via 'goto exitNow;' to unlock the lock! NO EXCEPTIONS! // ---------- OSSpinLockLock(&rkl_cacheSpinLock); // Grab the lock and get cache entry. rkl_cacheSpinLockStatus |= RKLLockedCacheSpinLock; rkl_dtrace_incrementAndGetEventID(thisDTraceEventID); if(RKL_EXPECTED((retrievedCachedRegex = rkl_getCachedRegex(initRegexString, initOptions, initError, &exception)) == NULL, 0L)) { goto exitNow; } RKLCDelayedAssert(((retrievedCachedRegex >= rkl_cachedRegexes) && ((retrievedCachedRegex - &rkl_cachedRegexes[0]) < (ssize_t)_RKL_REGEX_CACHE_LINES)) && (retrievedCachedRegex != NULL) && (retrievedCachedRegex->icu_regex != NULL) && (retrievedCachedRegex->regexString != NULL) && (retrievedCachedRegex->captureCount >= 0L) && (retrievedCachedRegex == rkl_lastCachedRegex), &exception, exitNow); if(RKL_EXPECTED(retrievedCachedRegex == NULL, 0L) || RKL_EXPECTED(status > U_ZERO_ERROR, 0L) || RKL_EXPECTED(exception != NULL, 0L)) { goto exitNow; } if(RKL_EXPECTED((cachedRegex.icu_regex = RKL_ICU_FUNCTION_APPEND(uregex_clone)(retrievedCachedRegex->icu_regex, &status)) == NULL, 0L) || RKL_EXPECTED(status != U_ZERO_ERROR, 0L)) { goto exitNow; } if(RKL_EXPECTED((cachedRegex.regexString = (CFStringRef)CFRetain((CFTypeRef)retrievedCachedRegex->regexString)) == NULL, 0L)) { goto exitNow; } cachedRegex.options = initOptions; cachedRegex.captureCount = retrievedCachedRegex->captureCount; cachedRegex.regexHash = retrievedCachedRegex->regexHash; RKLCDelayedAssert((cachedRegex.icu_regex != NULL) && (cachedRegex.regexString != NULL) && (cachedRegex.captureCount >= 0L), &exception, exitNow); exitNow: if((rkl_cacheSpinLockStatus & RKLLockedCacheSpinLock) != 0UL) { // In case we arrive at exitNow: without obtaining the rkl_cacheSpinLock. OSSpinLockUnlock(&rkl_cacheSpinLock); rkl_cacheSpinLockStatus |= RKLUnlockedCacheSpinLock; // Warning about rkl_cacheSpinLockStatus never being read can be safely ignored. } if(RKL_EXPECTED(self == NULL, 0L) || RKL_EXPECTED(retrievedCachedRegex == NULL, 0L) || RKL_EXPECTED(cachedRegex.icu_regex == NULL, 0L) || RKL_EXPECTED(status != U_ZERO_ERROR, 0L) || RKL_EXPECTED(exception != NULL, 0L)) { goto errorExit; } retrievedCachedRegex = NULL; // Since we no longer hold the lock, ensure that nothing accesses the retrieved cache regex after this point. rkl_dtrace_addLookupFlag(lookupResultFlags, RKLEnumerationBufferLookupFlag); if(RKL_EXPECTED((buffer.string = CFStringCreateCopy(NULL, (CFStringRef)initString)) == NULL, 0L)) { goto errorExit; } buffer.hash = CFHash((CFTypeRef)buffer.string); buffer.length = CFStringGetLength(buffer.string); if((buffer.uniChar = (UniChar *)CFStringGetCharactersPtr(buffer.string)) == NULL) { rkl_dtrace_addLookupFlag(lookupResultFlags, RKLConversionRequiredLookupFlag); if(RKL_EXPECTED((buffer.uniChar = (RKL_STRONG_REF UniChar * RKL_GC_VOLATILE)rkl_realloc((RKL_STRONG_REF void ** RKL_GC_VOLATILE)&buffer.uniChar, ((size_t)buffer.length * sizeof(UniChar)), 0UL)) == NULL, 0L)) { goto errorExit; } // Resize the buffer. needToFreeBufferUniChar = rkl_collectingEnabled() ? 0U : 1U; CFStringGetCharacters(buffer.string, CFMakeRange(0L, buffer.length), (UniChar *)buffer.uniChar); // Convert to a UTF16 string. } if(RKL_EXPECTED((cachedRegex.setToString = (CFStringRef)CFRetain((CFTypeRef)buffer.string)) == NULL, 0L)) { goto errorExit; } cachedRegex.setToHash = buffer.hash; cachedRegex.setToLength = buffer.length; cachedRegex.setToUniChar = buffer.uniChar; cachedRegex.buffer = &buffer; RKLCDelayedAssert((cachedRegex.icu_regex != NULL) && (cachedRegex.setToUniChar != NULL) && (cachedRegex.setToLength < INT_MAX) && (NSMaxRange(initRange) <= (NSUInteger)cachedRegex.setToLength) && (NSMaxRange(initRange) < INT_MAX), &exception, errorExit); cachedRegex.lastFindRange = cachedRegex.lastMatchRange = NSNotFoundRange; cachedRegex.setToRange = initRange; RKL_ICU_FUNCTION_APPEND(uregex_setText)(cachedRegex.icu_regex, cachedRegex.setToUniChar + cachedRegex.setToRange.location, (int32_t)cachedRegex.setToRange.length, &status); if(RKL_EXPECTED(status > U_ZERO_ERROR, 0L)) { goto errorExit; } rkl_dtrace_addLookupFlag(lookupResultFlags, RKLSetTextLookupFlag); rkl_dtrace_utf16ConversionCacheWithEventID(thisDTraceEventID, lookupResultFlags, initString, cachedRegex.setToRange.location, cachedRegex.setToRange.length, cachedRegex.setToLength); return(self); errorExit: if(RKL_EXPECTED(self != NULL, 1L)) { [self autorelease]; } if(RKL_EXPECTED(status > U_ZERO_ERROR, 0L) && RKL_EXPECTED(exception == NULL, 0L)) { exception = rkl_NSExceptionForRegex(initRegexString, initOptions, NULL, status); } // If we had a problem, prepare an exception to be thrown. if(RKL_EXPECTED(status < U_ZERO_ERROR, 0L) && (initError != NULL)) { *initError = rkl_makeNSError((RKLUserInfoOptions)RKLUserInfoNone, initRegexString, initOptions, NULL, status, initString, initRange, NULL, NULL, 0L, (RKLRegexEnumerationOptions)RKLRegexEnumerationNoOptions, @"The ICU library returned an unexpected error."); } if(RKL_EXPECTED(exception != NULL, 0L)) { rkl_handleDelayedAssert(self, _cmd, exception); } return(NULL); } #ifdef __OBJC_GC__ - (void)finalize { rkl_clearCachedRegex(&cachedRegex); rkl_clearBuffer(&buffer, (needToFreeBufferUniChar != 0U) ? 1LU : 0LU); NSUInteger tmpIdx = 0UL; // The rkl_free() below is "probably" a no-op when GC is on, but better to be safe than sorry... for(tmpIdx = 0UL; tmpIdx < _RKL_SCRATCH_BUFFERS; tmpIdx++) { if(RKL_EXPECTED(scratchBuffer[tmpIdx] != NULL, 0L)) { scratchBuffer[tmpIdx] = rkl_free(&scratchBuffer[tmpIdx]); } } [super finalize]; } #endif // __OBJC_GC__ - (void)dealloc { rkl_clearCachedRegex(&cachedRegex); rkl_clearBuffer(&buffer, (needToFreeBufferUniChar != 0U) ? 1LU : 0LU); NSUInteger tmpIdx = 0UL; for(tmpIdx = 0UL; tmpIdx < _RKL_SCRATCH_BUFFERS; tmpIdx++) { if(RKL_EXPECTED(scratchBuffer[tmpIdx] != NULL, 0L)) { scratchBuffer[tmpIdx] = rkl_free(&scratchBuffer[tmpIdx]); } } [super dealloc]; } @end // IMPORTANT! This code is critical path code. Because of this, it has been written for speed, not clarity. // ---------- // // Return value: BOOL. Per "Error Handling Programming Guide" wrt/ NSError, return NO on error / failure, and set *error to an NSError object. // // rkl_performEnumerationUsingBlock reference counted / manual memory management notes: // // When running using reference counting, rkl_performEnumerationUsingBlock() creates a CFMutableArray called autoreleaseArray, which is -autoreleased. // autoreleaseArray uses the rkl_transferOwnershipArrayCallBacks CFArray callbacks which do not perform a -retain/CFRetain() when objects are added, but do perform a -release/CFRelease() when an object is removed. // // A special class, RKLBlockEnumerationHelper, is used to manage the details of creating a private instantiation of the ICU regex (via uregex_clone()) and setting up the details of the UTF-16 buffer required by the ICU regex engine. // The instantiated RKLBlockEnumerationHelper is not autoreleased, but added to autoreleaseArray. When rkl_performEnumerationUsingBlock() exits, it calls CFArrayRemoveAllValues(autoreleaseArray), which empties the array. // This has the effect of immediately -releasing the instantiated RKLBlockEnumerationHelper object, and all the memory used to hold the ICU regex and UTF-16 conversion buffer. // This means the memory is reclaimed immediately and we do not have to wait until the autorelease pool pops. // // If we are performing a "string replacement" operation, we create a temporary NSMutableString named mutableReplacementString to hold the replaced strings results. mutableReplacementString is also added to autoreleaseArray so that it // can be properly released on an error. // // Temporary strings that are created during the enumeration of matches are added to autoreleaseArray. // The strings are added by doing a CFArrayReplaceValues(), which simultaneously releases the previous iterations temporary strings while adding the current iterations temporary strings to the array. // // autoreleaseArray always has a reference to any "live" and in use objects. If anything "Goes Wrong", at any point, for any reason (ie, exception is thrown), autoreleaseArray is in the current NSAutoreleasePool // and will automatically be released when that pool pops. This ensures that we don't leak anything even when things go seriously sideways. This also allows us to keep the total amount of memory in use // down to a minimum, which can be substantial if the user is enumerating a large string, for example a regex of '\w+' on a 500K+ text file. // // The only 'caveat' is that the user needs to -retain any strings that they want to use past the point at which their ^block returns. Logically, it is as if the following takes place: // // for(eachMatchOfRegexInStringToSearch) { // NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; // callUsersBlock(capturedCount, capturedStrings, capturedStringRanges, stop); // [pool release]; // } // // But in reality, no NSAutoreleasePool is created, it's all slight of hand done via the CFMutableArray autoreleaseArray. // // rkl_performEnumerationUsingBlock garbage collected / automatic memory management notes: // // When RegexKitLite is built with -fobjc-gc or -fobjc-gc-only, and (in the case of -fobjc-gc) RegexKitLite determines that GC is active at execution time, then rkl_performEnumerationUsingBlock essentially // skips all of the above reference counted autoreleaseArray stuff. // // rkl_performEnumerationUsingBlock and RKLRegexEnumerationReleaseStringReturnedByReplacementBlock notes // // Under reference counting, this enumeration option allows the user to return a non-autoreleased string, and then have RegexKitLite send the object a -release message once it's done with it. // The primary reason to do this is to immediately reclaim the memory used by the string holding the replacement text. // Just in case the user returns one of the strings we passed via capturedStrings[], we check to see if the string return by the block is any of the strings we created and passed via capturedStrings[]. // If it is one of our strings, we do not send the string a -release since that would over release it. It is assumed that the user will /NOT/ add a -retain to our strings in this case. // Under GC, RKLRegexEnumerationReleaseStringReturnedByReplacementBlock is ignored and no -release messages are sent. // #pragma mark Primary internal function that Objective-C ^Blocks related methods call to perform regular expression operations static id rkl_performEnumerationUsingBlock(id self, SEL _cmd, RKLRegexOp regexOp, NSString *regexString, RKLRegexOptions options, id matchString, NSRange matchRange, RKLBlockEnumerationOp blockEnumerationOp, RKLRegexEnumerationOptions enumerationOptions, NSInteger *replacedCountPtr, NSUInteger *errorFreePtr, NSError **error, void (^stringsAndRangesBlock)(NSInteger capturedCount, NSString * const capturedStrings[capturedCount], const NSRange capturedStringRanges[capturedCount], volatile BOOL * const stop), NSString *(^replaceStringsAndRangesBlock)(NSInteger capturedCount, NSString * const capturedStrings[capturedCount], const NSRange capturedStringRanges[capturedCount], volatile BOOL * const stop)) { NSMutableArray * RKL_GC_VOLATILE autoreleaseArray = NULL; RKLBlockEnumerationHelper * RKL_GC_VOLATILE blockEnumerationHelper = NULL; NSMutableString * RKL_GC_VOLATILE mutableReplacementString = NULL; RKL_STRONG_REF UniChar * RKL_GC_VOLATILE blockEnumerationHelperUniChar = NULL; NSUInteger errorFree = NO; id exception = NULL, returnObject = NULL; CFRange autoreleaseReplaceRange = CFMakeRange(0L, 0L); int32_t status = U_ZERO_ERROR; RKLRegexOp maskedRegexOp = (regexOp & RKLMaskOp); volatile BOOL shouldStop = NO; NSInteger replacedCount = -1L; NSRange lastMatchedRange = NSNotFoundRange; NSUInteger stringU16Length = 0UL; BOOL performStringReplacement = (blockEnumerationOp == RKLBlockEnumerationReplaceOp) ? YES : NO; if((error != NULL) && (*error != NULL)) { *error = NULL; } if(RKL_EXPECTED(regexString == NULL, 0L)) { exception = (id)RKL_EXCEPTION(NSInvalidArgumentException, @"The regular expression argument is NULL."); goto exitNow; } if(RKL_EXPECTED(matchString == NULL, 0L)) { exception = (id)RKL_EXCEPTION(NSInternalInconsistencyException, @"The match string argument is NULL."); goto exitNow; } if((((enumerationOptions & RKLRegexEnumerationCapturedStringsNotRequired) != 0UL) && ((enumerationOptions & RKLRegexEnumerationFastCapturedStringsXXX) != 0UL)) || (((enumerationOptions & RKLRegexEnumerationReleaseStringReturnedByReplacementBlock) != 0UL) && (blockEnumerationOp != RKLBlockEnumerationReplaceOp)) || ((enumerationOptions & (~((RKLRegexEnumerationOptions)(RKLRegexEnumerationCapturedStringsNotRequired | RKLRegexEnumerationReleaseStringReturnedByReplacementBlock | RKLRegexEnumerationFastCapturedStringsXXX)))) != 0UL)) { exception = (id)RKL_EXCEPTION(NSInvalidArgumentException, @"The RKLRegexEnumerationOptions argument is not valid."); goto exitNow; } stringU16Length = (NSUInteger)CFStringGetLength((CFStringRef)matchString); if(RKL_EXPECTED(matchRange.length == NSUIntegerMax, 1L)) { matchRange.length = stringU16Length; } // For convenience. if(RKL_EXPECTED(stringU16Length < NSMaxRange(matchRange), 0L)) { exception = (id)RKL_EXCEPTION(NSRangeException, @"Range or index out of bounds."); goto exitNow; } if(RKL_EXPECTED(stringU16Length >= (NSUInteger)INT_MAX, 0L)) { exception = (id)RKL_EXCEPTION(NSRangeException, @"String length exceeds INT_MAX."); goto exitNow; } RKLCDelayedAssert((self != NULL) && (_cmd != NULL) && ((blockEnumerationOp == RKLBlockEnumerationMatchOp) ? (((regexOp == RKLCapturesArrayOp) || (regexOp == RKLSplitOp)) && (stringsAndRangesBlock != NULL) && (replaceStringsAndRangesBlock == NULL)) : 1) && ((blockEnumerationOp == RKLBlockEnumerationReplaceOp) ? ((regexOp == RKLCapturesArrayOp) && (stringsAndRangesBlock == NULL) && (replaceStringsAndRangesBlock != NULL)) : 1) , &exception, exitNow); if((rkl_collectingEnabled() == NO) && RKL_EXPECTED((autoreleaseArray = rkl_CFAutorelease(CFArrayCreateMutable(NULL, 0L, &rkl_transferOwnershipArrayCallBacks))) == NULL, 0L)) { goto exitNow; } // Warning about potential leak of Core Foundation object can be safely ignored. if(RKL_EXPECTED((blockEnumerationHelper = [[RKLBlockEnumerationHelper alloc] initWithRegex:regexString options:options string:matchString range:matchRange error:error]) == NULL, 0L)) { goto exitNow; } // Warning about potential leak of blockEnumerationHelper can be safely ignored. if(autoreleaseArray != NULL) { CFArrayAppendValue((CFMutableArrayRef)autoreleaseArray, blockEnumerationHelper); autoreleaseReplaceRange.location++; } // We do not autorelease blockEnumerationHelper, but instead add it to autoreleaseArray. if(performStringReplacement == YES) { if(RKL_EXPECTED((mutableReplacementString = [[NSMutableString alloc] init]) == NULL, 0L)) { goto exitNow; } // Warning about potential leak of mutableReplacementString can be safely ignored. if(autoreleaseArray != NULL) { CFArrayAppendValue((CFMutableArrayRef)autoreleaseArray, mutableReplacementString); autoreleaseReplaceRange.location++; } // We do not autorelease mutableReplacementString, but instead add it to autoreleaseArray. } // RKLBlockEnumerationHelper creates an immutable copy of the string to match (matchString) which we reference via blockEnumerationHelperString. We use blockEnumerationHelperString when creating the captured strings from a match. // This protects us against the user mutating matchString while we are in the middle of enumerating matches. NSString * RKL_GC_VOLATILE blockEnumerationHelperString = (NSString *)blockEnumerationHelper->buffer.string, ** RKL_GC_VOLATILE capturedStrings = NULL, *emptyString = @""; CFMutableStringRef * RKL_GC_VOLATILE fastCapturedStrings = NULL; NSInteger captureCountBlockArgument = (blockEnumerationHelper->cachedRegex.captureCount + 1L); size_t capturedStringsCapacity = ((size_t)captureCountBlockArgument + 4UL); size_t capturedRangesCapacity = (((size_t)captureCountBlockArgument + 4UL) * 5UL); NSRange *capturedRanges = NULL; lastMatchedRange = NSMakeRange(matchRange.location, 0UL); blockEnumerationHelperUniChar = blockEnumerationHelper->buffer.uniChar; RKLCDelayedAssert((blockEnumerationHelperString != NULL) && (blockEnumerationHelperUniChar != NULL) && (captureCountBlockArgument > 0L) && (capturedStringsCapacity > 0UL) && (capturedRangesCapacity > 0UL), &exception, exitNow); if((capturedStrings = (NSString ** RKL_GC_VOLATILE)alloca(sizeof(NSString *) * capturedStringsCapacity)) == NULL) { goto exitNow; } // Space to hold the captured strings from a match. if((capturedRanges = (NSRange *) alloca(sizeof(NSRange) * capturedRangesCapacity)) == NULL) { goto exitNow; } // Space to hold the NSRanges of the captured strings from a match. #ifdef NS_BLOCK_ASSERTIONS { // Initialize the padded capturedStrings and capturedRanges to values that should tickle a fault if they are ever used. size_t idx = 0UL; for(idx = captureCountBlockArgument; idx < capturedStringsCapacity; idx++) { capturedStrings[idx] = (NSString *)RKLIllegalPointer; } for(idx = captureCountBlockArgument; idx < capturedRangesCapacity; idx++) { capturedRanges[idx] = RKLIllegalRange; } } #else { // Initialize all of the capturedStrings and capturedRanges to values that should tickle a fault if they are ever used. size_t idx = 0UL; for(idx = 0UL; idx < capturedStringsCapacity; idx++) { capturedStrings[idx] = (NSString *)RKLIllegalPointer; } for(idx = 0UL; idx < capturedRangesCapacity; idx++) { capturedRanges[idx] = RKLIllegalRange; } } #endif if((enumerationOptions & RKLRegexEnumerationFastCapturedStringsXXX) != 0UL) { RKLCDelayedAssert(((enumerationOptions & RKLRegexEnumerationCapturedStringsNotRequired) == 0UL), &exception, exitNow); size_t idx = 0UL; if((fastCapturedStrings = (CFMutableStringRef * RKL_GC_VOLATILE)alloca(sizeof(NSString *) * capturedStringsCapacity)) == NULL) { goto exitNow; } // Space to hold the "fast" captured strings from a match. for(idx = 0UL; idx < (size_t)captureCountBlockArgument; idx++) { if((fastCapturedStrings[idx] = CFStringCreateMutableWithExternalCharactersNoCopy(NULL, NULL, 0L, 0L, kCFAllocatorNull)) == NULL) { goto exitNow; } if(autoreleaseArray != NULL) { CFArrayAppendValue((CFMutableArrayRef)autoreleaseArray, fastCapturedStrings[idx]); autoreleaseReplaceRange.location++; } // We do not autorelease mutableReplacementString, but instead add it to autoreleaseArray. capturedStrings[idx] = (NSString *)fastCapturedStrings[idx]; } } RKLFindAll findAll = rkl_makeFindAll(capturedRanges, matchRange, (NSInteger)capturedRangesCapacity, (capturedRangesCapacity * sizeof(NSRange)), 0UL, &blockEnumerationHelper->scratchBuffer[0], &blockEnumerationHelper->scratchBuffer[1], &blockEnumerationHelper->scratchBuffer[2], &blockEnumerationHelper->scratchBuffer[3], &blockEnumerationHelper->scratchBuffer[4], 0L, 0L, 1L); NSString ** RKL_GC_VOLATILE capturedStringsBlockArgument = NULL; // capturedStringsBlockArgument is what we pass to the 'capturedStrings[]' argument of the users ^block. Will pass NULL if the user doesn't want the captured strings created automatically. if((enumerationOptions & RKLRegexEnumerationCapturedStringsNotRequired) == 0UL) { capturedStringsBlockArgument = capturedStrings; } // If the user wants the captured strings automatically created, set to capturedStrings. replacedCount = 0L; while(RKL_EXPECTED(rkl_findRanges(&blockEnumerationHelper->cachedRegex, regexOp, &findAll, &exception, &status) == NO, 1L) && RKL_EXPECTED(findAll.found > 0L, 1L) && RKL_EXPECTED(exception == NULL, 1L) && RKL_EXPECTED(status == U_ZERO_ERROR, 1L)) { if(performStringReplacement == YES) { NSUInteger lastMatchedMaxLocation = (lastMatchedRange.location + lastMatchedRange.length); NSRange previousUnmatchedRange = NSMakeRange(lastMatchedMaxLocation, findAll.ranges[0].location - lastMatchedMaxLocation); RKLCDelayedAssert((NSMaxRange(previousUnmatchedRange) <= stringU16Length) && (NSRangeInsideRange(previousUnmatchedRange, matchRange) == YES), &exception, exitNow); if(RKL_EXPECTED(previousUnmatchedRange.length > 0UL, 1L)) { CFStringAppendCharacters((CFMutableStringRef)mutableReplacementString, blockEnumerationHelperUniChar + previousUnmatchedRange.location, (CFIndex)previousUnmatchedRange.length); } } findAll.found -= findAll.addedSplitRanges; NSInteger passCaptureCountBlockArgument = ((findAll.found == 0L) && (findAll.addedSplitRanges == 1L) && (maskedRegexOp == RKLSplitOp)) ? 1L : findAll.found, capturedStringsIdx = passCaptureCountBlockArgument; RKLCDelayedHardAssert(passCaptureCountBlockArgument <= captureCountBlockArgument, &exception, exitNow); if(capturedStringsBlockArgument != NULL) { // Only create the captured strings if the user has requested them. BOOL hadError = NO; // Loop over all the strings rkl_findRanges found. If rkl_CreateStringWithSubstring() returns NULL due to an error, set returnBool to NO, and break out of the for() loop. for(capturedStringsIdx = 0L; capturedStringsIdx < passCaptureCountBlockArgument; capturedStringsIdx++) { RKLCDelayedHardAssert(capturedStringsIdx < captureCountBlockArgument, &exception, exitNow); if((enumerationOptions & RKLRegexEnumerationFastCapturedStringsXXX) != 0UL) { // Analyzer report of "Dereference of null pointer" can be safely ignored for the next line. Bug filed: http://llvm.org/bugs/show_bug.cgi?id=6150 CFStringSetExternalCharactersNoCopy(fastCapturedStrings[capturedStringsIdx], &blockEnumerationHelperUniChar[findAll.ranges[capturedStringsIdx].location], (CFIndex)findAll.ranges[capturedStringsIdx].length, (CFIndex)findAll.ranges[capturedStringsIdx].length); } else { if((capturedStrings[capturedStringsIdx] = (findAll.ranges[capturedStringsIdx].length == 0UL) ? emptyString : rkl_CreateStringWithSubstring(blockEnumerationHelperString, findAll.ranges[capturedStringsIdx])) == NULL) { hadError = YES; break; } } } if(((enumerationOptions & RKLRegexEnumerationFastCapturedStringsXXX) == 0UL) && RKL_EXPECTED(autoreleaseArray != NULL, 1L)) { CFArrayReplaceValues((CFMutableArrayRef)autoreleaseArray, autoreleaseReplaceRange, (const void **)capturedStrings, capturedStringsIdx); autoreleaseReplaceRange.length = capturedStringsIdx; } // Add to autoreleaseArray all the strings the for() loop created. if(RKL_EXPECTED(hadError == YES, 0L)) { goto exitNow; } // hadError == YES will be set if rkl_CreateStringWithSubstring() returned NULL. } // For safety, set any capturedRanges and capturedStrings up to captureCountBlockArgument + 1 to values that indicate that they are not valid. // These values are chosen such that they should tickle any misuse by users. // capturedStringsIdx is initialized to passCaptureCountBlockArgument, but if capturedStringsBlockArgument != NULL, it is reset to 0 by the loop that creates strings. // If the loop that creates strings has an error, execution should transfer to exitNow and this will never get run. // Again, this is for safety for users that do not check the passed block argument 'captureCount' and instead depend on something like [regex captureCount]. for(; capturedStringsIdx < captureCountBlockArgument + 1L; capturedStringsIdx++) { RKLCDelayedAssert((capturedStringsIdx < (NSInteger)capturedStringsCapacity) && (capturedStringsIdx < (NSInteger)capturedRangesCapacity), &exception, exitNow); capturedRanges[capturedStringsIdx] = RKLIllegalRange; capturedStrings[capturedStringsIdx] = (NSString *)RKLIllegalPointer; } RKLCDelayedAssert((passCaptureCountBlockArgument > 0L) && (NSMaxRange(capturedRanges[0]) <= stringU16Length) && (capturedRanges[0].location < NSIntegerMax) && (capturedRanges[0].length < NSIntegerMax), &exception, exitNow); switch(blockEnumerationOp) { case RKLBlockEnumerationMatchOp: stringsAndRangesBlock(passCaptureCountBlockArgument, capturedStringsBlockArgument, capturedRanges, &shouldStop); break; case RKLBlockEnumerationReplaceOp: { NSString *blockReturnedReplacementString = replaceStringsAndRangesBlock(passCaptureCountBlockArgument, capturedStringsBlockArgument, capturedRanges, &shouldStop); if(RKL_EXPECTED(blockReturnedReplacementString != NULL, 1L)) { CFStringAppend((CFMutableStringRef)mutableReplacementString, (CFStringRef)blockReturnedReplacementString); BOOL shouldRelease = (((enumerationOptions & RKLRegexEnumerationReleaseStringReturnedByReplacementBlock) != 0UL) && (capturedStringsBlockArgument != NULL) && (rkl_collectingEnabled() == NO)) ? YES : NO; if(shouldRelease == YES) { NSInteger idx = 0L; for(idx = 0L; idx < passCaptureCountBlockArgument; idx++) { if(capturedStrings[idx] == blockReturnedReplacementString) { shouldRelease = NO; break; } } } if(shouldRelease == YES) { [blockReturnedReplacementString release]; } } } break; default: exception = RKLCAssertDictionary(@"Unknown blockEnumerationOp code."); goto exitNow; break; } replacedCount++; findAll.addedSplitRanges = 0L; // rkl_findRanges() expects findAll.addedSplitRanges to be 0 on entry. findAll.found = 0L; // rkl_findRanges() expects findAll.found to be 0 on entry. findAll.findInRange = findAll.remainingRange; // Ask rkl_findRanges() to search the part of the string after the current match. lastMatchedRange = findAll.ranges[0]; if(RKL_EXPECTED(shouldStop != NO, 0L)) { break; } } errorFree = YES; exitNow: if(RKL_EXPECTED(errorFree == NO, 0L)) { replacedCount = -1L; } if((blockEnumerationOp == RKLBlockEnumerationReplaceOp) && RKL_EXPECTED(errorFree == YES, 1L)) { RKLCDelayedAssert(replacedCount >= 0L, &exception, exitNow2); if(RKL_EXPECTED(replacedCount == 0UL, 0L)) { RKLCDelayedAssert((blockEnumerationHelper != NULL) && (blockEnumerationHelper->buffer.string != NULL), &exception, exitNow2); returnObject = rkl_CreateStringWithSubstring((id)blockEnumerationHelper->buffer.string, matchRange); if(rkl_collectingEnabled() == NO) { returnObject = rkl_CFAutorelease(returnObject); } } else { NSUInteger lastMatchedMaxLocation = (lastMatchedRange.location + lastMatchedRange.length); NSRange previousUnmatchedRange = NSMakeRange(lastMatchedMaxLocation, NSMaxRange(matchRange) - lastMatchedMaxLocation); RKLCDelayedAssert((NSMaxRange(previousUnmatchedRange) <= stringU16Length) && (NSRangeInsideRange(previousUnmatchedRange, matchRange) == YES), &exception, exitNow2); if(RKL_EXPECTED(previousUnmatchedRange.length > 0UL, 1L)) { CFStringAppendCharacters((CFMutableStringRef)mutableReplacementString, blockEnumerationHelperUniChar + previousUnmatchedRange.location, (CFIndex)previousUnmatchedRange.length); } returnObject = rkl_CFAutorelease(CFStringCreateCopy(NULL, (CFStringRef)mutableReplacementString)); // Warning about potential leak of Core Foundation object can be safely ignored. } } #ifndef NS_BLOCK_ASSERTIONS exitNow2: #endif // NS_BLOCK_ASSERTIONS if(RKL_EXPECTED(autoreleaseArray != NULL, 1L)) { CFArrayRemoveAllValues((CFMutableArrayRef)autoreleaseArray); } // Causes blockEnumerationHelper to be released immediately, freeing all of its resources (such as a large UTF-16 conversion buffer). if(RKL_EXPECTED(exception != NULL, 0L)) { rkl_handleDelayedAssert(self, _cmd, exception); } // If there is an exception, throw it at this point. if(((errorFree == NO) || ((errorFree == YES) && (returnObject == NULL))) && (error != NULL) && (*error == NULL)) { RKLUserInfoOptions userInfoOptions = (RKLUserInfoSubjectRange | RKLUserInfoRegexEnumerationOptions); NSString *replacedString = NULL; if(blockEnumerationOp == RKLBlockEnumerationReplaceOp) { userInfoOptions |= RKLUserInfoReplacedCount; if(RKL_EXPECTED(errorFree == YES, 1L)) { replacedString = returnObject; } } *error = rkl_makeNSError(userInfoOptions, regexString, options, NULL, status, (blockEnumerationHelper != NULL) ? (blockEnumerationHelper->buffer.string != NULL) ? (NSString *)blockEnumerationHelper->buffer.string : matchString : matchString, matchRange, NULL, replacedString, replacedCount, enumerationOptions, @"An unexpected error occurred."); } if(replacedCountPtr != NULL) { *replacedCountPtr = replacedCount; } if(errorFreePtr != NULL) { *errorFreePtr = errorFree; } return(returnObject); } // The two warnings about potential leaks can be safely ignored. #endif // _RKL_BLOCKS_ENABLED //////////// #pragma mark - #pragma mark Objective-C Public Interface #pragma mark - //////////// @implementation NSString (RegexKitLiteAdditions) #pragma mark +clearStringCache + (void)RKL_METHOD_PREPEND(clearStringCache) { volatile NSUInteger RKL_CLEANUP(rkl_cleanup_cacheSpinLockStatus) rkl_cacheSpinLockStatus = 0UL; OSSpinLockLock(&rkl_cacheSpinLock); rkl_cacheSpinLockStatus |= RKLLockedCacheSpinLock; rkl_clearStringCache(); OSSpinLockUnlock(&rkl_cacheSpinLock); rkl_cacheSpinLockStatus |= RKLUnlockedCacheSpinLock; // Warning about rkl_cacheSpinLockStatus never being read can be safely ignored. } #pragma mark +captureCountForRegex: + (NSInteger)RKL_METHOD_PREPEND(captureCountForRegex):(NSString *)regex { NSInteger captureCount = -1L; rkl_isRegexValid(self, _cmd, regex, RKLNoOptions, &captureCount, NULL); return(captureCount); } + (NSInteger)RKL_METHOD_PREPEND(captureCountForRegex):(NSString *)regex options:(RKLRegexOptions)options error:(NSError **)error { NSInteger captureCount = -1L; rkl_isRegexValid(self, _cmd, regex, options, &captureCount, error); return(captureCount); } #pragma mark -captureCount: - (NSInteger)RKL_METHOD_PREPEND(captureCount) { NSInteger captureCount = -1L; rkl_isRegexValid(self, _cmd, self, RKLNoOptions, &captureCount, NULL); return(captureCount); } - (NSInteger)RKL_METHOD_PREPEND(captureCountWithOptions):(RKLRegexOptions)options error:(NSError **)error { NSInteger captureCount = -1L; rkl_isRegexValid(self, _cmd, self, options, &captureCount, error); return(captureCount); } #pragma mark -componentsSeparatedByRegex: - (NSArray *)RKL_METHOD_PREPEND(componentsSeparatedByRegex):(NSString *)regex { NSRange range = NSMaxiumRange; return(rkl_performRegexOp(self, _cmd, (RKLRegexOp)RKLSplitOp, regex, RKLNoOptions, 0L, self, &range, NULL, NULL, NULL, 0UL, NULL, NULL)); } - (NSArray *)RKL_METHOD_PREPEND(componentsSeparatedByRegex):(NSString *)regex range:(NSRange)range { return(rkl_performRegexOp(self, _cmd, (RKLRegexOp)RKLSplitOp, regex, RKLNoOptions, 0L, self, &range, NULL, NULL, NULL, 0UL, NULL, NULL)); } - (NSArray *)RKL_METHOD_PREPEND(componentsSeparatedByRegex):(NSString *)regex options:(RKLRegexOptions)options range:(NSRange)range error:(NSError **)error { return(rkl_performRegexOp(self, _cmd, (RKLRegexOp)RKLSplitOp, regex, options, 0L, self, &range, NULL, error, NULL, 0UL, NULL, NULL)); } #pragma mark -isMatchedByRegex: - (BOOL)RKL_METHOD_PREPEND(isMatchedByRegex):(NSString *)regex { NSRange result = NSNotFoundRange, range = NSMaxiumRange; rkl_performRegexOp(self, _cmd, (RKLRegexOp)RKLRangeOp, regex, RKLNoOptions, 0L, self, &range, NULL, NULL, &result, 0UL, NULL, NULL); return((result.location == (NSUInteger)NSNotFound) ? NO : YES); } - (BOOL)RKL_METHOD_PREPEND(isMatchedByRegex):(NSString *)regex inRange:(NSRange)range { NSRange result = NSNotFoundRange; rkl_performRegexOp(self, _cmd, (RKLRegexOp)RKLRangeOp, regex, RKLNoOptions, 0L, self, &range, NULL, NULL, &result, 0UL, NULL, NULL); return((result.location == (NSUInteger)NSNotFound) ? NO : YES); } - (BOOL)RKL_METHOD_PREPEND(isMatchedByRegex):(NSString *)regex options:(RKLRegexOptions)options inRange:(NSRange)range error:(NSError **)error { NSRange result = NSNotFoundRange; rkl_performRegexOp(self, _cmd, (RKLRegexOp)RKLRangeOp, regex, options, 0L, self, &range, NULL, error, &result, 0UL, NULL, NULL); return((result.location == (NSUInteger)NSNotFound) ? NO : YES); } #pragma mark -isRegexValid - (BOOL)RKL_METHOD_PREPEND(isRegexValid) { return(rkl_isRegexValid(self, _cmd, self, RKLNoOptions, NULL, NULL) == 1UL ? YES : NO); } - (BOOL)RKL_METHOD_PREPEND(isRegexValidWithOptions):(RKLRegexOptions)options error:(NSError **)error { return(rkl_isRegexValid(self, _cmd, self, options, NULL, error) == 1UL ? YES : NO); } #pragma mark -flushCachedRegexData - (void)RKL_METHOD_PREPEND(flushCachedRegexData) { volatile NSUInteger RKL_CLEANUP(rkl_cleanup_cacheSpinLockStatus) rkl_cacheSpinLockStatus = 0UL; CFIndex selfLength = CFStringGetLength((CFStringRef)self); CFHashCode selfHash = CFHash((CFTypeRef)self); OSSpinLockLock(&rkl_cacheSpinLock); rkl_cacheSpinLockStatus |= RKLLockedCacheSpinLock; rkl_dtrace_incrementEventID(); NSUInteger idx; for(idx = 0UL; idx < _RKL_REGEX_CACHE_LINES; idx++) { RKLCachedRegex *cachedRegex = &rkl_cachedRegexes[idx]; if((cachedRegex->setToString != NULL) && ( (cachedRegex->setToString == (CFStringRef)self) || ((cachedRegex->setToLength == selfLength) && (cachedRegex->setToHash == selfHash)) ) ) { rkl_clearCachedRegexSetTo(cachedRegex); } } for(idx = 0UL; idx < _RKL_LRU_CACHE_SET_WAYS; idx++) { RKLBuffer *buffer = &rkl_lruFixedBuffer[idx]; if((buffer->string != NULL) && ((buffer->string == (CFStringRef)self) || ((buffer->length == selfLength) && (buffer->hash == selfHash)))) { rkl_clearBuffer(buffer, 0UL); } } for(idx = 0UL; idx < _RKL_LRU_CACHE_SET_WAYS; idx++) { RKLBuffer *buffer = &rkl_lruDynamicBuffer[idx]; if((buffer->string != NULL) && ((buffer->string == (CFStringRef)self) || ((buffer->length == selfLength) && (buffer->hash == selfHash)))) { rkl_clearBuffer(buffer, 0UL); } } OSSpinLockUnlock(&rkl_cacheSpinLock); rkl_cacheSpinLockStatus |= RKLUnlockedCacheSpinLock; // Warning about rkl_cacheSpinLockStatus never being read can be safely ignored. } #pragma mark -rangeOfRegex: - (NSRange)RKL_METHOD_PREPEND(rangeOfRegex):(NSString *)regex { NSRange result = NSNotFoundRange, range = NSMaxiumRange; rkl_performRegexOp(self, _cmd, (RKLRegexOp)RKLRangeOp, regex, RKLNoOptions, 0L, self, &range, NULL, NULL, &result, 0UL, NULL, NULL); return(result); } - (NSRange)RKL_METHOD_PREPEND(rangeOfRegex):(NSString *)regex capture:(NSInteger)capture { NSRange result = NSNotFoundRange, range = NSMaxiumRange; rkl_performRegexOp(self, _cmd, (RKLRegexOp)RKLRangeOp, regex, RKLNoOptions, capture, self, &range, NULL, NULL, &result, 0UL, NULL, NULL); return(result); } - (NSRange)RKL_METHOD_PREPEND(rangeOfRegex):(NSString *)regex inRange:(NSRange)range { NSRange result = NSNotFoundRange; rkl_performRegexOp(self, _cmd, (RKLRegexOp)RKLRangeOp, regex, RKLNoOptions, 0L, self, &range, NULL, NULL, &result, 0UL, NULL, NULL); return(result); } - (NSRange)RKL_METHOD_PREPEND(rangeOfRegex):(NSString *)regex options:(RKLRegexOptions)options inRange:(NSRange)range capture:(NSInteger)capture error:(NSError **)error { NSRange result = NSNotFoundRange; rkl_performRegexOp(self, _cmd, (RKLRegexOp)RKLRangeOp, regex, options, capture, self, &range, NULL, error, &result, 0UL, NULL, NULL); return(result); } #pragma mark -stringByMatching: - (NSString *)RKL_METHOD_PREPEND(stringByMatching):(NSString *)regex { NSRange matchedRange = NSNotFoundRange, range = NSMaxiumRange; rkl_performRegexOp(self, _cmd, (RKLRegexOp)RKLRangeOp, regex, RKLNoOptions, 0L, self, &range, NULL, NULL, &matchedRange, 0UL, NULL, NULL); return((matchedRange.location == (NSUInteger)NSNotFound) ? NULL : rkl_CFAutorelease(CFStringCreateWithSubstring(NULL, (CFStringRef)self, CFMakeRange(matchedRange.location, matchedRange.length)))); // Warning about potential leak can be safely ignored. } // Warning about potential leak can be safely ignored. - (NSString *)RKL_METHOD_PREPEND(stringByMatching):(NSString *)regex capture:(NSInteger)capture { NSRange matchedRange = NSNotFoundRange, range = NSMaxiumRange; rkl_performRegexOp(self, _cmd, (RKLRegexOp)RKLRangeOp, regex, RKLNoOptions, capture, self, &range, NULL, NULL, &matchedRange, 0UL, NULL, NULL); return((matchedRange.location == (NSUInteger)NSNotFound) ? NULL : rkl_CFAutorelease(CFStringCreateWithSubstring(NULL, (CFStringRef)self, CFMakeRange(matchedRange.location, matchedRange.length)))); // Warning about potential leak can be safely ignored. } // Warning about potential leak can be safely ignored. - (NSString *)RKL_METHOD_PREPEND(stringByMatching):(NSString *)regex inRange:(NSRange)range { NSRange matchedRange = NSNotFoundRange; rkl_performRegexOp(self, _cmd, (RKLRegexOp)RKLRangeOp, regex, RKLNoOptions, 0L, self, &range, NULL, NULL, &matchedRange, 0UL, NULL, NULL); return((matchedRange.location == (NSUInteger)NSNotFound) ? NULL : rkl_CFAutorelease(CFStringCreateWithSubstring(NULL, (CFStringRef)self, CFMakeRange(matchedRange.location, matchedRange.length)))); // Warning about potential leak can be safely ignored. } // Warning about potential leak can be safely ignored. - (NSString *)RKL_METHOD_PREPEND(stringByMatching):(NSString *)regex options:(RKLRegexOptions)options inRange:(NSRange)range capture:(NSInteger)capture error:(NSError **)error { NSRange matchedRange = NSNotFoundRange; rkl_performRegexOp(self, _cmd, (RKLRegexOp)RKLRangeOp, regex, options, capture, self, &range, NULL, error, &matchedRange, 0UL, NULL, NULL); return((matchedRange.location == (NSUInteger)NSNotFound) ? NULL : rkl_CFAutorelease(CFStringCreateWithSubstring(NULL, (CFStringRef)self, CFMakeRange(matchedRange.location, matchedRange.length)))); // Warning about potential leak can be safely ignored. } // Warning about potential leak can be safely ignored. #pragma mark -stringByReplacingOccurrencesOfRegex: - (NSString *)RKL_METHOD_PREPEND(stringByReplacingOccurrencesOfRegex):(NSString *)regex withString:(NSString *)replacement { NSRange searchRange = NSMaxiumRange; return(rkl_performRegexOp(self, _cmd, (RKLRegexOp)RKLReplaceOp, regex, RKLNoOptions, 0L, self, &searchRange, replacement, NULL, NULL, 0UL, NULL, NULL)); } - (NSString *)RKL_METHOD_PREPEND(stringByReplacingOccurrencesOfRegex):(NSString *)regex withString:(NSString *)replacement range:(NSRange)searchRange { return(rkl_performRegexOp(self, _cmd, (RKLRegexOp)RKLReplaceOp, regex, RKLNoOptions, 0L, self, &searchRange, replacement, NULL, NULL, 0UL, NULL, NULL)); } - (NSString *)RKL_METHOD_PREPEND(stringByReplacingOccurrencesOfRegex):(NSString *)regex withString:(NSString *)replacement options:(RKLRegexOptions)options range:(NSRange)searchRange error:(NSError **)error { return(rkl_performRegexOp(self, _cmd, (RKLRegexOp)RKLReplaceOp, regex, options, 0L, self, &searchRange, replacement, error, NULL, 0UL, NULL, NULL)); } #pragma mark -componentsMatchedByRegex: - (NSArray *)RKL_METHOD_PREPEND(componentsMatchedByRegex):(NSString *)regex { NSRange searchRange = NSMaxiumRange; return(rkl_performRegexOp(self, _cmd, (RKLRegexOp)RKLArrayOfStringsOp, regex, RKLNoOptions, 0L, self, &searchRange, NULL, NULL, NULL, 0UL, NULL, NULL)); } - (NSArray *)RKL_METHOD_PREPEND(componentsMatchedByRegex):(NSString *)regex capture:(NSInteger)capture { NSRange searchRange = NSMaxiumRange; return(rkl_performRegexOp(self, _cmd, (RKLRegexOp)RKLArrayOfStringsOp, regex, RKLNoOptions, capture, self, &searchRange, NULL, NULL, NULL, 0UL, NULL, NULL)); } - (NSArray *)RKL_METHOD_PREPEND(componentsMatchedByRegex):(NSString *)regex range:(NSRange)range { return(rkl_performRegexOp(self, _cmd, (RKLRegexOp)RKLArrayOfStringsOp, regex, RKLNoOptions, 0L, self, &range, NULL, NULL, NULL, 0UL, NULL, NULL)); } - (NSArray *)RKL_METHOD_PREPEND(componentsMatchedByRegex):(NSString *)regex options:(RKLRegexOptions)options range:(NSRange)range capture:(NSInteger)capture error:(NSError **)error { return(rkl_performRegexOp(self, _cmd, (RKLRegexOp)RKLArrayOfStringsOp, regex, options, capture, self, &range, NULL, error, NULL, 0UL, NULL, NULL)); } #pragma mark -captureComponentsMatchedByRegex: - (NSArray *)RKL_METHOD_PREPEND(captureComponentsMatchedByRegex):(NSString *)regex { NSRange searchRange = NSMaxiumRange; return(rkl_performRegexOp(self, _cmd, (RKLRegexOp)RKLCapturesArrayOp, regex, RKLNoOptions, 0L, self, &searchRange, NULL, NULL, NULL, 0UL, NULL, NULL)); } - (NSArray *)RKL_METHOD_PREPEND(captureComponentsMatchedByRegex):(NSString *)regex range:(NSRange)range { return(rkl_performRegexOp(self, _cmd, (RKLRegexOp)RKLCapturesArrayOp, regex, RKLNoOptions, 0L, self, &range, NULL, NULL, NULL, 0UL, NULL, NULL)); } - (NSArray *)RKL_METHOD_PREPEND(captureComponentsMatchedByRegex):(NSString *)regex options:(RKLRegexOptions)options range:(NSRange)range error:(NSError **)error { return(rkl_performRegexOp(self, _cmd, (RKLRegexOp)RKLCapturesArrayOp, regex, options, 0L, self, &range, NULL, error, NULL, 0UL, NULL, NULL)); } #pragma mark -arrayOfCaptureComponentsMatchedByRegex: - (NSArray *)RKL_METHOD_PREPEND(arrayOfCaptureComponentsMatchedByRegex):(NSString *)regex { NSRange searchRange = NSMaxiumRange; return(rkl_performRegexOp(self, _cmd, (RKLRegexOp)(RKLArrayOfCapturesOp | RKLSubcapturesArray), regex, RKLNoOptions, 0L, self, &searchRange, NULL, NULL, NULL, 0UL, NULL, NULL)); } - (NSArray *)RKL_METHOD_PREPEND(arrayOfCaptureComponentsMatchedByRegex):(NSString *)regex range:(NSRange)range { return(rkl_performRegexOp(self, _cmd, (RKLRegexOp)(RKLArrayOfCapturesOp | RKLSubcapturesArray), regex, RKLNoOptions, 0L, self, &range, NULL, NULL, NULL, 0UL, NULL, NULL)); } - (NSArray *)RKL_METHOD_PREPEND(arrayOfCaptureComponentsMatchedByRegex):(NSString *)regex options:(RKLRegexOptions)options range:(NSRange)range error:(NSError **)error { return(rkl_performRegexOp(self, _cmd, (RKLRegexOp)(RKLArrayOfCapturesOp | RKLSubcapturesArray), regex, options, 0L, self, &range, NULL, error, NULL, 0UL, NULL, NULL)); } #pragma mark -dictionaryByMatchingRegex: - (NSDictionary *)RKL_METHOD_PREPEND(dictionaryByMatchingRegex):(NSString *)regex withKeysAndCaptures:(id)firstKey, ... { NSRange searchRange = NSMaxiumRange; id returnObject = NULL; va_list varArgsList; va_start(varArgsList, firstKey); returnObject = rkl_performDictionaryVarArgsOp(self, _cmd, (RKLRegexOp)RKLDictionaryOfCapturesOp, regex, (RKLRegexOptions)RKLNoOptions, 0L, self, &searchRange, NULL, NULL, NULL, firstKey, varArgsList); va_end(varArgsList); return(returnObject); } - (NSDictionary *)RKL_METHOD_PREPEND(dictionaryByMatchingRegex):(NSString *)regex range:(NSRange)range withKeysAndCaptures:(id)firstKey, ... { id returnObject = NULL; va_list varArgsList; va_start(varArgsList, firstKey); returnObject = rkl_performDictionaryVarArgsOp(self, _cmd, (RKLRegexOp)RKLDictionaryOfCapturesOp, regex, (RKLRegexOptions)RKLNoOptions, 0L, self, &range, NULL, NULL, NULL, firstKey, varArgsList); va_end(varArgsList); return(returnObject); } - (NSDictionary *)RKL_METHOD_PREPEND(dictionaryByMatchingRegex):(NSString *)regex options:(RKLRegexOptions)options range:(NSRange)range error:(NSError **)error withKeysAndCaptures:(id)firstKey, ... { id returnObject = NULL; va_list varArgsList; va_start(varArgsList, firstKey); returnObject = rkl_performDictionaryVarArgsOp(self, _cmd, (RKLRegexOp)RKLDictionaryOfCapturesOp, regex, options, 0L, self, &range, NULL, error, NULL, firstKey, varArgsList); va_end(varArgsList); return(returnObject); } - (NSDictionary *)RKL_METHOD_PREPEND(dictionaryByMatchingRegex):(NSString *)regex options:(RKLRegexOptions)options range:(NSRange)range error:(NSError **)error withFirstKey:(id)firstKey arguments:(va_list)varArgsList { return(rkl_performDictionaryVarArgsOp(self, _cmd, (RKLRegexOp)RKLDictionaryOfCapturesOp, regex, options, 0L, self, &range, NULL, error, NULL, firstKey, varArgsList)); } - (NSDictionary *)RKL_METHOD_PREPEND(dictionaryByMatchingRegex):(NSString *)regex options:(RKLRegexOptions)options range:(NSRange)range error:(NSError **)error withKeys:(id *)keys forCaptures:(int *)captures count:(NSUInteger)count { return(rkl_performRegexOp(self, _cmd, (RKLRegexOp)RKLDictionaryOfCapturesOp, regex, options, 0L, self, &range, NULL, error, NULL, count, keys, captures)); } #pragma mark -arrayOfDictionariesByMatchingRegex: - (NSArray *)RKL_METHOD_PREPEND(arrayOfDictionariesByMatchingRegex):(NSString *)regex withKeysAndCaptures:(id)firstKey, ... { NSRange searchRange = NSMaxiumRange; id returnObject = NULL; va_list varArgsList; va_start(varArgsList, firstKey); returnObject = rkl_performDictionaryVarArgsOp(self, _cmd, (RKLRegexOp)RKLArrayOfDictionariesOfCapturesOp, regex, (RKLRegexOptions)RKLNoOptions, 0L, self, &searchRange, NULL, NULL, NULL, firstKey, varArgsList); va_end(varArgsList); return(returnObject); } - (NSArray *)RKL_METHOD_PREPEND(arrayOfDictionariesByMatchingRegex):(NSString *)regex range:(NSRange)range withKeysAndCaptures:(id)firstKey, ... { id returnObject = NULL; va_list varArgsList; va_start(varArgsList, firstKey); returnObject = rkl_performDictionaryVarArgsOp(self, _cmd, (RKLRegexOp)RKLArrayOfDictionariesOfCapturesOp, regex, (RKLRegexOptions)RKLNoOptions, 0L, self, &range, NULL, NULL, NULL, firstKey, varArgsList); va_end(varArgsList); return(returnObject); } - (NSArray *)RKL_METHOD_PREPEND(arrayOfDictionariesByMatchingRegex):(NSString *)regex options:(RKLRegexOptions)options range:(NSRange)range error:(NSError **)error withKeysAndCaptures:(id)firstKey, ... { id returnObject = NULL; va_list varArgsList; va_start(varArgsList, firstKey); returnObject = rkl_performDictionaryVarArgsOp(self, _cmd, (RKLRegexOp)RKLArrayOfDictionariesOfCapturesOp, regex, options, 0L, self, &range, NULL, error, NULL, firstKey, varArgsList); va_end(varArgsList); return(returnObject); } - (NSArray *)RKL_METHOD_PREPEND(arrayOfDictionariesByMatchingRegex):(NSString *)regex options:(RKLRegexOptions)options range:(NSRange)range error:(NSError **)error withFirstKey:(id)firstKey arguments:(va_list)varArgsList { return(rkl_performDictionaryVarArgsOp(self, _cmd, (RKLRegexOp)RKLArrayOfDictionariesOfCapturesOp, regex, options, 0L, self, &range, NULL, error, NULL, firstKey, varArgsList)); } - (NSArray *)RKL_METHOD_PREPEND(arrayOfDictionariesByMatchingRegex):(NSString *)regex options:(RKLRegexOptions)options range:(NSRange)range error:(NSError **)error withKeys:(id *)keys forCaptures:(int *)captures count:(NSUInteger)count { return(rkl_performRegexOp(self, _cmd, (RKLRegexOp)RKLArrayOfDictionariesOfCapturesOp, regex, options, 0L, self, &range, NULL, error, NULL, count, keys, captures)); } #ifdef _RKL_BLOCKS_ENABLED //////////// #pragma mark - #pragma mark ^Blocks Related NSString Methods #pragma mark -enumerateStringsMatchedByRegex:usingBlock: - (BOOL)RKL_METHOD_PREPEND(enumerateStringsMatchedByRegex):(NSString *)regex usingBlock:(void (^)(NSInteger captureCount, NSString * const capturedStrings[captureCount], const NSRange capturedRanges[captureCount], volatile BOOL * const stop))block { NSUInteger errorFree = NO; rkl_performEnumerationUsingBlock(self, _cmd, (RKLRegexOp)RKLCapturesArrayOp, regex, (RKLRegexOptions)RKLNoOptions, self, NSMaxiumRange, (RKLBlockEnumerationOp)RKLBlockEnumerationMatchOp, 0UL, NULL, &errorFree, NULL, block, NULL); return(errorFree == NO ? NO : YES); } - (BOOL)RKL_METHOD_PREPEND(enumerateStringsMatchedByRegex):(NSString *)regex options:(RKLRegexOptions)options inRange:(NSRange)range error:(NSError **)error enumerationOptions:(RKLRegexEnumerationOptions)enumerationOptions usingBlock:(void (^)(NSInteger captureCount, NSString * const capturedStrings[captureCount], const NSRange capturedRanges[captureCount], volatile BOOL * const stop))block { NSUInteger errorFree = NO; rkl_performEnumerationUsingBlock(self, _cmd, (RKLRegexOp)RKLCapturesArrayOp, regex, options, self, range, (RKLBlockEnumerationOp)RKLBlockEnumerationMatchOp, enumerationOptions, NULL, &errorFree, error, block, NULL); return(errorFree == NO ? NO : YES); } #pragma mark -enumerateStringsSeparatedByRegex:usingBlock: - (BOOL)RKL_METHOD_PREPEND(enumerateStringsSeparatedByRegex):(NSString *)regex usingBlock:(void (^)(NSInteger captureCount, NSString * const capturedStrings[captureCount], const NSRange capturedRanges[captureCount], volatile BOOL * const stop))block { NSUInteger errorFree = NO; rkl_performEnumerationUsingBlock(self, _cmd, (RKLRegexOp)RKLSplitOp, regex, (RKLRegexOptions)RKLNoOptions, self, NSMaxiumRange, (RKLBlockEnumerationOp)RKLBlockEnumerationMatchOp, 0UL, NULL, &errorFree, NULL, block, NULL); return(errorFree == NO ? NO : YES); } - (BOOL)RKL_METHOD_PREPEND(enumerateStringsSeparatedByRegex):(NSString *)regex options:(RKLRegexOptions)options inRange:(NSRange)range error:(NSError **)error enumerationOptions:(RKLRegexEnumerationOptions)enumerationOptions usingBlock:(void (^)(NSInteger captureCount, NSString * const capturedStrings[captureCount], const NSRange capturedRanges[captureCount], volatile BOOL * const stop))block { NSUInteger errorFree = NO; rkl_performEnumerationUsingBlock(self, _cmd, (RKLRegexOp)RKLSplitOp, regex, options, self, range, (RKLBlockEnumerationOp)RKLBlockEnumerationMatchOp, enumerationOptions, NULL, &errorFree, error, block, NULL); return(errorFree == NO ? NO : YES); } #pragma mark -stringByReplacingOccurrencesOfRegex:usingBlock: - (NSString *)RKL_METHOD_PREPEND(stringByReplacingOccurrencesOfRegex):(NSString *)regex usingBlock:(NSString *(^)(NSInteger captureCount, NSString * const capturedStrings[captureCount], const NSRange capturedRanges[captureCount], volatile BOOL * const stop))block { return(rkl_performEnumerationUsingBlock(self, _cmd, (RKLRegexOp)RKLCapturesArrayOp, regex, (RKLRegexOptions)RKLNoOptions, self, NSMaxiumRange, (RKLBlockEnumerationOp)RKLBlockEnumerationReplaceOp, 0UL, NULL, NULL, NULL, NULL, block)); } - (NSString *)RKL_METHOD_PREPEND(stringByReplacingOccurrencesOfRegex):(NSString *)regex options:(RKLRegexOptions)options inRange:(NSRange)range error:(NSError **)error enumerationOptions:(RKLRegexEnumerationOptions)enumerationOptions usingBlock:(NSString *(^)(NSInteger captureCount, NSString * const capturedStrings[captureCount], const NSRange capturedRanges[captureCount], volatile BOOL * const stop))block { return(rkl_performEnumerationUsingBlock(self, _cmd, (RKLRegexOp)RKLCapturesArrayOp, regex, options, self, range, (RKLBlockEnumerationOp)RKLBlockEnumerationReplaceOp, enumerationOptions, NULL, NULL, error, NULL, block)); } #endif // _RKL_BLOCKS_ENABLED @end //////////// #pragma mark - @implementation NSMutableString (RegexKitLiteAdditions) #pragma mark -replaceOccurrencesOfRegex: - (NSInteger)RKL_METHOD_PREPEND(replaceOccurrencesOfRegex):(NSString *)regex withString:(NSString *)replacement { NSRange searchRange = NSMaxiumRange; NSInteger replacedCount = -1L; rkl_performRegexOp(self, _cmd, (RKLRegexOp)(RKLReplaceOp | RKLReplaceMutable), regex, RKLNoOptions, 0L, self, &searchRange, replacement, NULL, (void **)((void *)&replacedCount), 0UL, NULL, NULL); return(replacedCount); } - (NSInteger)RKL_METHOD_PREPEND(replaceOccurrencesOfRegex):(NSString *)regex withString:(NSString *)replacement range:(NSRange)searchRange { NSInteger replacedCount = -1L; rkl_performRegexOp(self, _cmd, (RKLRegexOp)(RKLReplaceOp | RKLReplaceMutable), regex, RKLNoOptions, 0L, self, &searchRange, replacement, NULL, (void **)((void *)&replacedCount), 0UL, NULL, NULL); return(replacedCount); } - (NSInteger)RKL_METHOD_PREPEND(replaceOccurrencesOfRegex):(NSString *)regex withString:(NSString *)replacement options:(RKLRegexOptions)options range:(NSRange)searchRange error:(NSError **)error { NSInteger replacedCount = -1L; rkl_performRegexOp(self, _cmd, (RKLRegexOp)(RKLReplaceOp | RKLReplaceMutable), regex, options, 0L, self, &searchRange, replacement, error, (void **)((void *)&replacedCount), 0UL, NULL, NULL); return(replacedCount); } #ifdef _RKL_BLOCKS_ENABLED //////////// #pragma mark - #pragma mark ^Blocks Related NSMutableString Methods #pragma mark -replaceOccurrencesOfRegex:usingBlock: - (NSInteger)RKL_METHOD_PREPEND(replaceOccurrencesOfRegex):(NSString *)regex usingBlock:(NSString *(^)(NSInteger captureCount, NSString * const capturedStrings[captureCount], const NSRange capturedRanges[captureCount], volatile BOOL * const stop))block { NSUInteger errorFree = 0UL; NSInteger replacedCount = -1L; NSString *replacedString = rkl_performEnumerationUsingBlock(self, _cmd, (RKLRegexOp)RKLCapturesArrayOp, regex, RKLNoOptions, self, NSMaxiumRange, (RKLBlockEnumerationOp)RKLBlockEnumerationReplaceOp, 0UL, &replacedCount, &errorFree, NULL, NULL, block); if((errorFree == YES) && (replacedCount > 0L)) { [self replaceCharactersInRange:NSMakeRange(0UL, [self length]) withString:replacedString]; } return(replacedCount); } - (NSInteger)RKL_METHOD_PREPEND(replaceOccurrencesOfRegex):(NSString *)regex options:(RKLRegexOptions)options inRange:(NSRange)range error:(NSError **)error enumerationOptions:(RKLRegexEnumerationOptions)enumerationOptions usingBlock:(NSString *(^)(NSInteger captureCount, NSString * const capturedStrings[captureCount], const NSRange capturedRanges[captureCount], volatile BOOL * const stop))block { NSUInteger errorFree = 0UL; NSInteger replacedCount = -1L; NSString *replacedString = rkl_performEnumerationUsingBlock(self, _cmd, (RKLRegexOp)RKLCapturesArrayOp, regex, options, self, range, (RKLBlockEnumerationOp)RKLBlockEnumerationReplaceOp, enumerationOptions, &replacedCount, &errorFree, error, NULL, block); if((errorFree == YES) && (replacedCount > 0L)) { [self replaceCharactersInRange:range withString:replacedString]; } return(replacedCount); } #endif // _RKL_BLOCKS_ENABLED @end ================================================ FILE: zhuishushenqi/Vendor/CTDisplayText/Source/Views/CTDisplayView.h ================================================ // // CTDisplayView.h // CoreTextDemo // // Created by yung on 2017/7/25. // Copyright (c) 2017年 yung. All rights reserved. // #import #import "CoreTextData.h" #import extern NSString *const CTDisplayViewImagePressed; extern NSString *const CTDisplayViewLinkPressed; typedef void(^CTDisplayHandler)(NSDictionary *data); @interface CTDisplayView : UIView @property (strong, nonatomic) CoreTextData * data; @property (nonatomic, copy) CTDisplayHandler handler; @end ================================================ FILE: zhuishushenqi/Vendor/CTDisplayText/Source/Views/CTDisplayView.m ================================================ // // CTDisplayView.m // CoreTextDemo // // Created by yung on 2017/7/25. // Copyright (c) 2017年 yung. All rights reserved. // #import "CTDisplayView.h" #import "CoreTextUtils.h" #import "MagnifiterView.h" #import "UIView+frameAdjust.h" NSString *const CTDisplayViewImagePressed = @"CTDisplayViewImagePressedNotification"; NSString *const CTDisplayViewLinkPressed = @"CTDisplayViewLinkPressedNotification"; typedef enum CTDisplayViewState : NSInteger { CTDisplayViewStateNormal, // 普通状态 CTDisplayViewStateTouching, // 正在按下,需要弹出放大镜 CTDisplayViewStateSelecting // 选中了一些文本,需要弹出复制菜单 }CTDisplayViewState; #define ANCHOR_TARGET_TAG 1 #define FONT_HEIGHT 40 #define RGB(A, B, C) [UIColor colorWithRed:A/255.0 green:B/255.0 blue:C/255.0 alpha:1.0] @interface CTDisplayView() @property (nonatomic) NSInteger selectionStartPosition; @property (nonatomic) NSInteger selectionEndPosition; @property (nonatomic) CTDisplayViewState state; @property (strong, nonatomic) UIImageView *leftSelectionAnchor; @property (strong, nonatomic) UIImageView *rightSelectionAnchor; @property (strong, nonatomic) MagnifiterView *magnifierView; @property (strong, nonatomic) UIGestureRecognizer * tapRecognizer; @end @implementation CTDisplayView - (id)init { return [self initWithFrame:CGRectZero]; } - (id)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { [self setupEvents]; } return self; } - (id)initWithCoder:(NSCoder *)aDecoder { self = [super initWithCoder:aDecoder]; if (self) { [self setupEvents]; } return self; } - (void)setData:(CoreTextData *)data { _data = data; self.state = CTDisplayViewStateNormal; [self setNeedsDisplay]; } - (void)setupAnchors { _leftSelectionAnchor = [self createSelectionAnchorWithTop:YES]; _rightSelectionAnchor = [self createSelectionAnchorWithTop:NO]; [self addSubview:_leftSelectionAnchor]; [self addSubview:_rightSelectionAnchor]; } - (MagnifiterView *)magnifierView { if (_magnifierView == nil) { _magnifierView = [[MagnifiterView alloc] init]; _magnifierView.viewToMagnify = self; [self addSubview:_magnifierView]; } return _magnifierView; } - (UIImage *)cursorWithFontHeight:(CGFloat)height isTop:(BOOL)top { // 22 CGRect rect = CGRectMake(0, 0, 22, height * 2); UIColor *color = RGB(28, 107, 222); UIGraphicsBeginImageContext(rect.size); CGContextRef context = UIGraphicsGetCurrentContext(); // draw point if (top) { CGContextAddEllipseInRect(context, CGRectMake(0, 0, 22, 22)); } else { CGContextAddEllipseInRect(context, CGRectMake(0, height * 2 - 22, 22, 22)); } CGContextSetFillColorWithColor(context, color.CGColor); CGContextFillPath(context); // draw line [color set]; CGContextSetLineWidth(context, 4); CGContextMoveToPoint(context, 11, 22); CGContextAddLineToPoint(context, 11, height * 2 - 22); CGContextStrokePath(context); UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); return image; } - (UIImageView *)createSelectionAnchorWithTop:(BOOL)isTop { UIImage *image = [self cursorWithFontHeight:FONT_HEIGHT isTop:isTop]; UIImageView *imageView = [[UIImageView alloc] initWithImage:image]; imageView.frame = CGRectMake(0, 0, 11, FONT_HEIGHT); return imageView; } - (void)removeSelectionAnchor { if (_leftSelectionAnchor) { [_leftSelectionAnchor removeFromSuperview]; _leftSelectionAnchor = nil; } if (_rightSelectionAnchor) { [_rightSelectionAnchor removeFromSuperview]; _rightSelectionAnchor = nil; } } - (void)removeMaginfierView { if (_magnifierView) { [_magnifierView removeFromSuperview]; _magnifierView = nil; } } - (void)setState:(CTDisplayViewState)state { if (_state == state) { return; } _state = state; if (_state == CTDisplayViewStateNormal) { _selectionStartPosition = -1; _selectionEndPosition = -1; [self removeSelectionAnchor]; [self removeMaginfierView]; [self hideMenuController]; } else if (_state == CTDisplayViewStateTouching) { if (_leftSelectionAnchor == nil && _rightSelectionAnchor == nil) { [self setupAnchors]; } } else if (_state == CTDisplayViewStateSelecting) { if (_leftSelectionAnchor == nil && _rightSelectionAnchor == nil) { [self setupAnchors]; } if (_leftSelectionAnchor.tag != ANCHOR_TARGET_TAG && _rightSelectionAnchor.tag != ANCHOR_TARGET_TAG) { [self removeMaginfierView]; [self hideMenuController]; } } [self setNeedsDisplay]; } - (void)showMenuController { if ([self becomeFirstResponder]) { CGRect selectionRect = [self rectForMenuController]; // 翻转坐标系 CGAffineTransform transform = CGAffineTransformMakeTranslation(0, self.bounds.size.height); transform = CGAffineTransformScale(transform, 1.f, -1.f); selectionRect = CGRectApplyAffineTransform(selectionRect, transform); UIMenuController *theMenu = [UIMenuController sharedMenuController]; [theMenu setTargetRect:selectionRect inView:self]; [theMenu setMenuVisible:YES animated:YES]; } } - (void)hideMenuController { if ([self resignFirstResponder]) { UIMenuController *theMenu = [UIMenuController sharedMenuController]; [theMenu setMenuVisible:NO animated:YES]; } } - (void)setupEvents { _tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(userTapGestureDetected:)]; UIGestureRecognizer *longPressRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(userLongPressedGuestureDetected:)]; [self addGestureRecognizer:longPressRecognizer]; UIGestureRecognizer *panRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(userPanGuestureDetected:)]; // [self addGestureRecognizer:panRecognizer]; self.userInteractionEnabled = YES; } - (void)drawRect:(CGRect)rect { [super drawRect:rect]; if (self.data == nil) { return; } if (self.data.imageArray.count > 0 || self.data.linkArray.count > 0) { [self removeGestureRecognizer:_tapRecognizer]; [self addGestureRecognizer:_tapRecognizer]; } CGContextRef context = UIGraphicsGetCurrentContext(); CGContextSetTextMatrix(context, CGAffineTransformIdentity); CGContextTranslateCTM(context, 0, self.data.height); CGContextScaleCTM(context, 1.0, -1.0); if (self.state == CTDisplayViewStateTouching || self.state == CTDisplayViewStateSelecting) { [self drawSelectionArea]; [self drawAnchors]; } CTFrameDraw(self.data.ctFrame, context); for (CoreTextImageData * imageData in self.data.imageArray) { NSString *url = [imageData.url stringByRemovingPercentEncoding]; NSError *error; NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:url] options:(NSDataReadingMappedIfSafe) error:&error]; UIImage *image = [UIImage imageWithData:data]; if (image) { CGContextDrawImage(context, imageData.imagePosition, image.CGImage); } } } - (void)userTapGestureDetected:(UIGestureRecognizer *)recognizer { CGPoint point = [recognizer locationInView:self]; if (_state == CTDisplayViewStateNormal) { for (CoreTextImageData * imageData in self.data.imageArray) { // 翻转坐标系,因为imageData中的坐标是CoreText的坐标系 CGRect imageRect = imageData.imagePosition; CGPoint imagePosition = imageRect.origin; imagePosition.y = self.bounds.size.height - imageRect.origin.y - imageRect.size.height; CGRect rect = CGRectMake(imagePosition.x, imagePosition.y, imageRect.size.width, imageRect.size.height); // 检测点击位置 Point 是否在rect之内 if (CGRectContainsPoint(rect, point)) { NSLog(@"hint image"); // 在这里处理点击后的逻辑 NSDictionary *userInfo = @{ @"imageData": imageData }; if (_handler) { _handler(userInfo); } [[NSNotificationCenter defaultCenter] postNotificationName:CTDisplayViewImagePressed object:self userInfo:userInfo]; return; } } CoreTextLinkData *linkData = [CoreTextUtils touchLinkInView:self atPoint:point data:self.data]; if (linkData) { NSLog(@"hint link!"); NSDictionary *userInfo = @{ @"linkData": linkData }; if (_handler) { _handler(userInfo); } [[NSNotificationCenter defaultCenter] postNotificationName:CTDisplayViewLinkPressed object:self userInfo:userInfo]; return; } } else { self.state = CTDisplayViewStateNormal; } } - (void)userLongPressedGuestureDetected:(UILongPressGestureRecognizer *)recognizer { CGPoint point = [recognizer locationInView:self]; // debugMethod(); // debugLog(@"state = %d", recognizer.state); // debugLog(@"point = %@", NSStringFromCGPoint(point)); if (recognizer.state == UIGestureRecognizerStateBegan || recognizer.state == UIGestureRecognizerStateChanged) { CFIndex index = [CoreTextUtils touchContentOffsetInView:self atPoint:point data:self.data]; if (index != -1 && index < self.data.content.length) { _selectionStartPosition = index; _selectionEndPosition = index + 2; } self.magnifierView.touchPoint = point; self.state = CTDisplayViewStateTouching; } else { if (_selectionStartPosition >= 0 && _selectionEndPosition <= self.data.content.length) { self.state = CTDisplayViewStateSelecting; [self showMenuController]; } else { self.state = CTDisplayViewStateNormal; } } } - (BOOL)canPerformAction:(SEL)action withSender:(id)sender { // debugMethod(); if (action == @selector(copy:) || action == @selector(selectAll:)) { return YES; } // if (action == @selector(cut:) || action == @selector(copy:) || action == @selector(paste:) || action == @selector(selectAll:)) { // return YES; // } return NO; } - (void)userPanGuestureDetected:(UIGestureRecognizer *)recognizer { if (self.state == CTDisplayViewStateNormal) { return; } CGPoint point = [recognizer locationInView:self]; if (recognizer.state == UIGestureRecognizerStateBegan) { if (_leftSelectionAnchor && CGRectContainsPoint(CGRectInset(_leftSelectionAnchor.frame, -25, -6), point)) { // debugLog(@"try to move left anchor"); _leftSelectionAnchor.tag = ANCHOR_TARGET_TAG; [self hideMenuController]; } else if (_rightSelectionAnchor && CGRectContainsPoint(CGRectInset(_rightSelectionAnchor.frame, -25, -6), point)) { // debugLog(@"try to move right anchor"); _rightSelectionAnchor.tag = ANCHOR_TARGET_TAG; [self hideMenuController]; } } else if (recognizer.state == UIGestureRecognizerStateChanged) { CFIndex index = [CoreTextUtils touchContentOffsetInView:self atPoint:point data:self.data]; if (index == -1) { return; } if (_leftSelectionAnchor.tag == ANCHOR_TARGET_TAG && index < _selectionEndPosition) { // debugLog(@"change start position to %ld", index); _selectionStartPosition = index; self.magnifierView.touchPoint = point; [self hideMenuController]; } else if (_rightSelectionAnchor.tag == ANCHOR_TARGET_TAG && index > _selectionStartPosition) { // debugLog(@"change end position to %ld", index); _selectionEndPosition = index; self.magnifierView.touchPoint = point; [self hideMenuController]; } } else if (recognizer.state == UIGestureRecognizerStateEnded || recognizer.state == UIGestureRecognizerStateCancelled) { // debugLog(@"end move"); _leftSelectionAnchor.tag = 0; _rightSelectionAnchor.tag = 0; [self removeMaginfierView]; [self showMenuController]; } [self setNeedsDisplay]; } - (void)drawAnchors { if (_selectionStartPosition < 0 || _selectionEndPosition > self.data.content.length) { return; } CTFrameRef textFrame = self.data.ctFrame; CFArrayRef lines = CTFrameGetLines(self.data.ctFrame); if (!lines) { return; } // 翻转坐标系 CGAffineTransform transform = CGAffineTransformMakeTranslation(0, self.bounds.size.height); transform = CGAffineTransformScale(transform, 1.f, -1.f); CFIndex count = CFArrayGetCount(lines); // 获得每一行的origin坐标 CGPoint origins[count]; CTFrameGetLineOrigins(textFrame, CFRangeMake(0,0), origins); for (int i = 0; i < count; i++) { CGPoint linePoint = origins[i]; CTLineRef line = CFArrayGetValueAtIndex(lines, i); CFRange range = CTLineGetStringRange(line); if ([self isPosition:_selectionStartPosition inRange:range]) { CGFloat ascent, descent, leading, offset; offset = CTLineGetOffsetForStringIndex(line, _selectionStartPosition, NULL); CTLineGetTypographicBounds(line, &ascent, &descent, &leading); CGPoint origin = CGPointMake(linePoint.x + offset - 5, linePoint.y + ascent + 11); origin = CGPointApplyAffineTransform(origin, transform); _leftSelectionAnchor.origin = origin; } if ([self isPosition:_selectionEndPosition inRange:range]) { CGFloat ascent, descent, leading, offset; offset = CTLineGetOffsetForStringIndex(line, _selectionEndPosition, NULL); CTLineGetTypographicBounds(line, &ascent, &descent, &leading); CGPoint origin = CGPointMake(linePoint.x + offset - 5, linePoint.y + ascent + 11); origin = CGPointApplyAffineTransform(origin, transform); _rightSelectionAnchor.origin = origin; break; } } } - (void)drawSelectionArea { if (_selectionStartPosition < 0 || _selectionEndPosition > self.data.content.length) { return; } CTFrameRef textFrame = self.data.ctFrame; CFArrayRef lines = CTFrameGetLines(self.data.ctFrame); if (!lines) { return; } CFIndex count = CFArrayGetCount(lines); // 获得每一行的origin坐标 CGPoint origins[count]; CTFrameGetLineOrigins(textFrame, CFRangeMake(0,0), origins); for (int i = 0; i < count; i++) { CGPoint linePoint = origins[i]; CTLineRef line = CFArrayGetValueAtIndex(lines, i); CFRange range = CTLineGetStringRange(line); // 1. start和end在一个line,则直接弄完break if ([self isPosition:_selectionStartPosition inRange:range] && [self isPosition:_selectionEndPosition inRange:range]) { CGFloat ascent, descent, leading, offset, offset2; offset = CTLineGetOffsetForStringIndex(line, _selectionStartPosition, NULL); offset2 = CTLineGetOffsetForStringIndex(line, _selectionEndPosition, NULL); CTLineGetTypographicBounds(line, &ascent, &descent, &leading); CGRect lineRect = CGRectMake(linePoint.x + offset, linePoint.y - descent, offset2 - offset, ascent + descent); [self fillSelectionAreaInRect:lineRect]; break; } // 2. start和end不在一个line // 2.1 如果start在line中,则填充Start后面部分区域 if ([self isPosition:_selectionStartPosition inRange:range]) { CGFloat ascent, descent, leading, width, offset; offset = CTLineGetOffsetForStringIndex(line, _selectionStartPosition, NULL); width = CTLineGetTypographicBounds(line, &ascent, &descent, &leading); CGRect lineRect = CGRectMake(linePoint.x + offset, linePoint.y - descent, width - offset, ascent + descent); [self fillSelectionAreaInRect:lineRect]; } // 2.2 如果 start在line前,end在line后,则填充整个区域 else if (_selectionStartPosition < range.location && _selectionEndPosition >= range.location + range.length) { CGFloat ascent, descent, leading, width; width = CTLineGetTypographicBounds(line, &ascent, &descent, &leading); CGRect lineRect = CGRectMake(linePoint.x, linePoint.y - descent, width, ascent + descent); [self fillSelectionAreaInRect:lineRect]; } // 2.3 如果start在line前,end在line中,则填充end前面的区域,break else if (_selectionStartPosition < range.location && [self isPosition:_selectionEndPosition inRange:range]) { CGFloat ascent, descent, leading, width, offset; offset = CTLineGetOffsetForStringIndex(line, _selectionEndPosition, NULL); width = CTLineGetTypographicBounds(line, &ascent, &descent, &leading); CGRect lineRect = CGRectMake(linePoint.x, linePoint.y - descent, offset, ascent + descent); [self fillSelectionAreaInRect:lineRect]; } } } - (CGRect)rectForMenuController { if (_selectionStartPosition < 0 || _selectionEndPosition > self.data.content.length) { return CGRectZero; } CTFrameRef textFrame = self.data.ctFrame; CFArrayRef lines = CTFrameGetLines(self.data.ctFrame); if (!lines) { return CGRectZero; } CFIndex count = CFArrayGetCount(lines); // 获得每一行的origin坐标 CGPoint origins[count]; CTFrameGetLineOrigins(textFrame, CFRangeMake(0,0), origins); CGRect resultRect = CGRectZero; for (int i = 0; i < count; i++) { CGPoint linePoint = origins[i]; CTLineRef line = CFArrayGetValueAtIndex(lines, i); CFRange range = CTLineGetStringRange(line); // 1. start和end在一个line,则直接弄完break if ([self isPosition:_selectionStartPosition inRange:range] && [self isPosition:_selectionEndPosition inRange:range]) { CGFloat ascent, descent, leading, offset, offset2; offset = CTLineGetOffsetForStringIndex(line, _selectionStartPosition, NULL); offset2 = CTLineGetOffsetForStringIndex(line, _selectionEndPosition, NULL); CTLineGetTypographicBounds(line, &ascent, &descent, &leading); CGRect lineRect = CGRectMake(linePoint.x + offset, linePoint.y - descent, offset2 - offset, ascent + descent); resultRect = lineRect; break; } } if (!CGRectIsEmpty(resultRect)) { return resultRect; } // 2. start和end不在一个line for (int i = 0; i < count; i++) { CGPoint linePoint = origins[i]; CTLineRef line = CFArrayGetValueAtIndex(lines, i); CFRange range = CTLineGetStringRange(line); // 如果start在line中,则记录当前为起始行 if ([self isPosition:_selectionStartPosition inRange:range]) { CGFloat ascent, descent, leading, width, offset; offset = CTLineGetOffsetForStringIndex(line, _selectionStartPosition, NULL); width = CTLineGetTypographicBounds(line, &ascent, &descent, &leading); CGRect lineRect = CGRectMake(linePoint.x + offset, linePoint.y - descent, width - offset, ascent + descent); resultRect = lineRect; } } return resultRect; } - (BOOL)isPosition:(NSInteger)position inRange:(CFRange)range { if (position >= range.location && position < range.location + range.length) { return YES; } else { return NO; } } - (void)fillSelectionAreaInRect:(CGRect)rect { UIColor *bgColor = RGB(204, 221, 236); CGContextRef context = UIGraphicsGetCurrentContext(); CGContextSetFillColorWithColor(context, bgColor.CGColor); CGContextFillRect(context, rect); } - (BOOL)canBecomeFirstResponder { return YES; } - (void)copy:(id)sender{ UIPasteboard *pasteboard = [UIPasteboard generalPasteboard]; [pasteboard setString:@""]; NSRange range = NSMakeRange(self.selectionStartPosition, self.selectionEndPosition); NSString *selectedString = [self.data.content.string substringWithRange:range]; NSLog(@"selectedString:%@",selectedString); [pasteboard setImage:[UIImage imageNamed:@"coretext-image-1.jpg"]]; NSArray *images = [pasteboard images]; NSLog(@"images:%@",images); } - (void)selectAll:(id)sender{ } @end ================================================ FILE: zhuishushenqi/Vendor/CTDisplayText/Source/Views/MagnifiterView.h ================================================ // // MagnifiterView.h // CoreTextDemo // // Created by yung on 5/8/14. // Copyright (c) 2014 yung. All rights reserved. // #import @interface MagnifiterView : UIView @property (weak, nonatomic) UIView *viewToMagnify; @property (nonatomic) CGPoint touchPoint; @end ================================================ FILE: zhuishushenqi/Vendor/CTDisplayText/Source/Views/MagnifiterView.m ================================================ // // MagnifiterView.m // CoreTextDemo // // Created by yung on 5/8/14. // Copyright (c) 2014 yung. All rights reserved. // #import "MagnifiterView.h" #import "UIView+frameAdjust.h" @implementation MagnifiterView - (id)initWithFrame:(CGRect)frame { if (self = [super initWithFrame:CGRectMake(0, 0, 80, 80)]) { // make the circle-shape outline with a nice border. self.layer.borderColor = [[UIColor lightGrayColor] CGColor]; self.layer.borderWidth = 1; self.layer.cornerRadius = 40; self.layer.masksToBounds = YES; } return self; } - (void)setTouchPoint:(CGPoint)touchPoint { _touchPoint = touchPoint; // update the position of the magnifier (to just above what's being magnified) self.center = CGPointMake(touchPoint.x, touchPoint.y - 70); [self setNeedsDisplay]; } - (void)drawRect:(CGRect)rect { // here we're just doing some transforms on the view we're magnifying, // and rendering that view directly into this view, // rather than the previous method of copying an image. CGContextRef context = UIGraphicsGetCurrentContext(); CGContextTranslateCTM(context, self.width * 0.5, self.height * 0.5); CGContextScaleCTM(context, 1.5, 1.5); CGContextTranslateCTM(context, -1 * (_touchPoint.x), -1 * (_touchPoint.y)); [self.viewToMagnify.layer renderInContext:context]; } @end ================================================ FILE: zhuishushenqi/Vendor/CTDisplayText/Source/Views/UIView+frameAdjust.h ================================================ // // UIView+frameAdjust.h // fenbi // // Created by yung on 2017/7/25. // Copyright (c) 2012年 Fenbi.com . All rights reserved. // #import #import @interface UIView (frameAdjust) - (CGPoint)origin; - (void)setOrigin:(CGPoint) point; - (CGSize)size; - (void)setSize:(CGSize) size; - (CGFloat)x; - (void)setX:(CGFloat)x; - (CGFloat)y; - (void)setY:(CGFloat)y; - (CGFloat)height; - (void)setHeight:(CGFloat)height; - (CGFloat)width; - (void)setWidth:(CGFloat)width; - (CGFloat)tail; - (void)setTail:(CGFloat)tail; - (CGFloat)bottom; - (void)setBottom:(CGFloat)bottom; - (CGFloat)right; - (void)setRight:(CGFloat)right; @end ================================================ FILE: zhuishushenqi/Vendor/CTDisplayText/Source/Views/UIView+frameAdjust.m ================================================ // // UIView+frameAdjust.m // fenbi // // Created by yung on 2017/7/25. // Copyright (c) 2012年 Fenbi.com . All rights reserved. // #import "UIView+frameAdjust.h" #import @implementation UIView (frameAdjust) - (CGPoint) origin { return self.frame.origin; } - (void) setOrigin:(CGPoint) point { self.frame = CGRectMake(point.x, point.y, self.frame.size.width, self.frame.size.height); } - (CGSize) size { return self.frame.size; } - (void) setSize:(CGSize) size { self.frame = CGRectMake(self.x, self.y, size.width, size.height); } - (CGFloat) x { return self.frame.origin.x; } - (void) setX:(CGFloat)x { self.frame = CGRectMake(x, self.y, self.width, self.height); } - (CGFloat) y { return self.frame.origin.y; } - (void) setY:(CGFloat)y { self.frame = CGRectMake(self.x, y, self.width, self.height); } - (CGFloat) height { return self.frame.size.height; } - (void)setHeight:(CGFloat)height { self.frame = CGRectMake(self.x, self.y, self.width, height); } - (CGFloat)width { return self.frame.size.width; } - (void)setWidth:(CGFloat)width { self.frame = CGRectMake(self.x, self.y, width, self.height); } - (CGFloat)tail { return self.y + self.height; } - (void)setTail:(CGFloat)tail { self.frame = CGRectMake(self.x, tail - self.height, self.width, self.height); } - (CGFloat)bottom { return self.tail; } - (void)setBottom:(CGFloat)bottom { [self setTail:bottom]; } - (CGFloat)right { return self.x + self.width; } - (void)setRight:(CGFloat)right { self.frame = CGRectMake(right - self.width, self.y, self.width, self.height); } @end ================================================ FILE: zhuishushenqi/Vendor/CocoaHTTPServer/Core/Categories/DDData.h ================================================ #import @interface NSData (DDData) - (NSData *)md5Digest; - (NSData *)sha1Digest; - (NSString *)hexStringValue; - (NSString *)base64Encoded; - (NSData *)base64Decoded; @end ================================================ FILE: zhuishushenqi/Vendor/CocoaHTTPServer/Core/Categories/DDData.m ================================================ #import "DDData.h" #import @implementation NSData (DDData) static char encodingTable[64] = { 'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P', 'Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d','e','f', 'g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v', 'w','x','y','z','0','1','2','3','4','5','6','7','8','9','+','/' }; - (NSData *)md5Digest { unsigned char result[CC_MD5_DIGEST_LENGTH]; CC_MD5([self bytes], (CC_LONG)[self length], result); return [NSData dataWithBytes:result length:CC_MD5_DIGEST_LENGTH]; } - (NSData *)sha1Digest { unsigned char result[CC_SHA1_DIGEST_LENGTH]; CC_SHA1([self bytes], (CC_LONG)[self length], result); return [NSData dataWithBytes:result length:CC_SHA1_DIGEST_LENGTH]; } - (NSString *)hexStringValue { NSMutableString *stringBuffer = [NSMutableString stringWithCapacity:([self length] * 2)]; const unsigned char *dataBuffer = [self bytes]; int i; for (i = 0; i < [self length]; ++i) { [stringBuffer appendFormat:@"%02x", (unsigned int)dataBuffer[i]]; } return [stringBuffer copy]; } - (NSString *)base64Encoded { const unsigned char *bytes = [self bytes]; NSMutableString *result = [NSMutableString stringWithCapacity:[self length]]; unsigned long ixtext = 0; unsigned long lentext = [self length]; long ctremaining = 0; unsigned char inbuf[3], outbuf[4]; unsigned short i = 0; unsigned short charsonline = 0, ctcopy = 0; unsigned long ix = 0; while( YES ) { ctremaining = lentext - ixtext; if( ctremaining <= 0 ) break; for( i = 0; i < 3; i++ ) { ix = ixtext + i; if( ix < lentext ) inbuf[i] = bytes[ix]; else inbuf [i] = 0; } outbuf [0] = (inbuf [0] & 0xFC) >> 2; outbuf [1] = ((inbuf [0] & 0x03) << 4) | ((inbuf [1] & 0xF0) >> 4); outbuf [2] = ((inbuf [1] & 0x0F) << 2) | ((inbuf [2] & 0xC0) >> 6); outbuf [3] = inbuf [2] & 0x3F; ctcopy = 4; switch( ctremaining ) { case 1: ctcopy = 2; break; case 2: ctcopy = 3; break; } for( i = 0; i < ctcopy; i++ ) [result appendFormat:@"%c", encodingTable[outbuf[i]]]; for( i = ctcopy; i < 4; i++ ) [result appendString:@"="]; ixtext += 3; charsonline += 4; } return [NSString stringWithString:result]; } - (NSData *)base64Decoded { const unsigned char *bytes = [self bytes]; NSMutableData *result = [NSMutableData dataWithCapacity:[self length]]; unsigned long ixtext = 0; unsigned long lentext = [self length]; unsigned char ch = 0; unsigned char inbuf[4] = {0, 0, 0, 0}; unsigned char outbuf[3] = {0, 0, 0}; short i = 0, ixinbuf = 0; BOOL flignore = NO; BOOL flendtext = NO; while( YES ) { if( ixtext >= lentext ) break; ch = bytes[ixtext++]; flignore = NO; if( ( ch >= 'A' ) && ( ch <= 'Z' ) ) ch = ch - 'A'; else if( ( ch >= 'a' ) && ( ch <= 'z' ) ) ch = ch - 'a' + 26; else if( ( ch >= '0' ) && ( ch <= '9' ) ) ch = ch - '0' + 52; else if( ch == '+' ) ch = 62; else if( ch == '=' ) flendtext = YES; else if( ch == '/' ) ch = 63; else flignore = YES; if( ! flignore ) { short ctcharsinbuf = 3; BOOL flbreak = NO; if( flendtext ) { if( ! ixinbuf ) break; if( ( ixinbuf == 1 ) || ( ixinbuf == 2 ) ) ctcharsinbuf = 1; else ctcharsinbuf = 2; ixinbuf = 3; flbreak = YES; } inbuf [ixinbuf++] = ch; if( ixinbuf == 4 ) { ixinbuf = 0; outbuf [0] = ( inbuf[0] << 2 ) | ( ( inbuf[1] & 0x30) >> 4 ); outbuf [1] = ( ( inbuf[1] & 0x0F ) << 4 ) | ( ( inbuf[2] & 0x3C ) >> 2 ); outbuf [2] = ( ( inbuf[2] & 0x03 ) << 6 ) | ( inbuf[3] & 0x3F ); for( i = 0; i < ctcharsinbuf; i++ ) [result appendBytes:&outbuf[i] length:1]; } if( flbreak ) break; } } return [NSData dataWithData:result]; } @end ================================================ FILE: zhuishushenqi/Vendor/CocoaHTTPServer/Core/Categories/DDNumber.h ================================================ #import @interface NSNumber (DDNumber) + (BOOL)parseString:(NSString *)str intoSInt64:(SInt64 *)pNum; + (BOOL)parseString:(NSString *)str intoUInt64:(UInt64 *)pNum; + (BOOL)parseString:(NSString *)str intoNSInteger:(NSInteger *)pNum; + (BOOL)parseString:(NSString *)str intoNSUInteger:(NSUInteger *)pNum; @end ================================================ FILE: zhuishushenqi/Vendor/CocoaHTTPServer/Core/Categories/DDNumber.m ================================================ #import "DDNumber.h" @implementation NSNumber (DDNumber) + (BOOL)parseString:(NSString *)str intoSInt64:(SInt64 *)pNum { if(str == nil) { *pNum = 0; return NO; } errno = 0; // On both 32-bit and 64-bit machines, long long = 64 bit *pNum = strtoll([str UTF8String], NULL, 10); if(errno != 0) return NO; else return YES; } + (BOOL)parseString:(NSString *)str intoUInt64:(UInt64 *)pNum { if(str == nil) { *pNum = 0; return NO; } errno = 0; // On both 32-bit and 64-bit machines, unsigned long long = 64 bit *pNum = strtoull([str UTF8String], NULL, 10); if(errno != 0) return NO; else return YES; } + (BOOL)parseString:(NSString *)str intoNSInteger:(NSInteger *)pNum { if(str == nil) { *pNum = 0; return NO; } errno = 0; // On LP64, NSInteger = long = 64 bit // Otherwise, NSInteger = int = long = 32 bit *pNum = strtol([str UTF8String], NULL, 10); if(errno != 0) return NO; else return YES; } + (BOOL)parseString:(NSString *)str intoNSUInteger:(NSUInteger *)pNum { if(str == nil) { *pNum = 0; return NO; } errno = 0; // On LP64, NSUInteger = unsigned long = 64 bit // Otherwise, NSUInteger = unsigned int = unsigned long = 32 bit *pNum = strtoul([str UTF8String], NULL, 10); if(errno != 0) return NO; else return YES; } @end ================================================ FILE: zhuishushenqi/Vendor/CocoaHTTPServer/Core/Categories/DDRange.h ================================================ /** * DDRange is the functional equivalent of a 64 bit NSRange. * The HTTP Server is designed to support very large files. * On 32 bit architectures (ppc, i386) NSRange uses unsigned 32 bit integers. * This only supports a range of up to 4 gigabytes. * By defining our own variant, we can support a range up to 16 exabytes. * * All effort is given such that DDRange functions EXACTLY the same as NSRange. **/ #import #import @class NSString; typedef struct _DDRange { UInt64 location; UInt64 length; } DDRange; typedef DDRange *DDRangePointer; NS_INLINE DDRange DDMakeRange(UInt64 loc, UInt64 len) { DDRange r; r.location = loc; r.length = len; return r; } NS_INLINE UInt64 DDMaxRange(DDRange range) { return (range.location + range.length); } NS_INLINE BOOL DDLocationInRange(UInt64 loc, DDRange range) { return (loc - range.location < range.length); } NS_INLINE BOOL DDEqualRanges(DDRange range1, DDRange range2) { return ((range1.location == range2.location) && (range1.length == range2.length)); } FOUNDATION_EXPORT DDRange DDUnionRange(DDRange range1, DDRange range2); FOUNDATION_EXPORT DDRange DDIntersectionRange(DDRange range1, DDRange range2); FOUNDATION_EXPORT NSString *DDStringFromRange(DDRange range); FOUNDATION_EXPORT DDRange DDRangeFromString(NSString *aString); NSInteger DDRangeCompare(DDRangePointer pDDRange1, DDRangePointer pDDRange2); @interface NSValue (NSValueDDRangeExtensions) + (NSValue *)valueWithDDRange:(DDRange)range; - (DDRange)ddrangeValue; - (NSInteger)ddrangeCompare:(NSValue *)ddrangeValue; @end ================================================ FILE: zhuishushenqi/Vendor/CocoaHTTPServer/Core/Categories/DDRange.m ================================================ #import "DDRange.h" #import "DDNumber.h" DDRange DDUnionRange(DDRange range1, DDRange range2) { DDRange result; result.location = MIN(range1.location, range2.location); result.length = MAX(DDMaxRange(range1), DDMaxRange(range2)) - result.location; return result; } DDRange DDIntersectionRange(DDRange range1, DDRange range2) { DDRange result; if((DDMaxRange(range1) < range2.location) || (DDMaxRange(range2) < range1.location)) { return DDMakeRange(0, 0); } result.location = MAX(range1.location, range2.location); result.length = MIN(DDMaxRange(range1), DDMaxRange(range2)) - result.location; return result; } NSString *DDStringFromRange(DDRange range) { return [NSString stringWithFormat:@"{%qu, %qu}", range.location, range.length]; } DDRange DDRangeFromString(NSString *aString) { DDRange result = DDMakeRange(0, 0); // NSRange will ignore '-' characters, but not '+' characters NSCharacterSet *cset = [NSCharacterSet characterSetWithCharactersInString:@"+0123456789"]; NSScanner *scanner = [NSScanner scannerWithString:aString]; [scanner setCharactersToBeSkipped:[cset invertedSet]]; NSString *str1 = nil; NSString *str2 = nil; BOOL found1 = [scanner scanCharactersFromSet:cset intoString:&str1]; BOOL found2 = [scanner scanCharactersFromSet:cset intoString:&str2]; if(found1) [NSNumber parseString:str1 intoUInt64:&result.location]; if(found2) [NSNumber parseString:str2 intoUInt64:&result.length]; return result; } NSInteger DDRangeCompare(DDRangePointer pDDRange1, DDRangePointer pDDRange2) { // Comparison basis: // Which range would you encouter first if you started at zero, and began walking towards infinity. // If you encouter both ranges at the same time, which range would end first. if(pDDRange1->location < pDDRange2->location) { return NSOrderedAscending; } if(pDDRange1->location > pDDRange2->location) { return NSOrderedDescending; } if(pDDRange1->length < pDDRange2->length) { return NSOrderedAscending; } if(pDDRange1->length > pDDRange2->length) { return NSOrderedDescending; } return NSOrderedSame; } @implementation NSValue (NSValueDDRangeExtensions) + (NSValue *)valueWithDDRange:(DDRange)range { return [NSValue valueWithBytes:&range objCType:@encode(DDRange)]; } - (DDRange)ddrangeValue { DDRange result; [self getValue:&result]; return result; } - (NSInteger)ddrangeCompare:(NSValue *)other { DDRange r1 = [self ddrangeValue]; DDRange r2 = [other ddrangeValue]; return DDRangeCompare(&r1, &r2); } @end ================================================ FILE: zhuishushenqi/Vendor/CocoaHTTPServer/Core/HTTPAuthenticationRequest.h ================================================ #import #if TARGET_OS_IPHONE // Note: You may need to add the CFNetwork Framework to your project #import #endif @class HTTPMessage; @interface HTTPAuthenticationRequest : NSObject { BOOL isBasic; BOOL isDigest; NSString *base64Credentials; NSString *username; NSString *realm; NSString *nonce; NSString *uri; NSString *qop; NSString *nc; NSString *cnonce; NSString *response; } - (id)initWithRequest:(HTTPMessage *)request; - (BOOL)isBasic; - (BOOL)isDigest; // Basic - (NSString *)base64Credentials; // Digest - (NSString *)username; - (NSString *)realm; - (NSString *)nonce; - (NSString *)uri; - (NSString *)qop; - (NSString *)nc; - (NSString *)cnonce; - (NSString *)response; @end ================================================ FILE: zhuishushenqi/Vendor/CocoaHTTPServer/Core/HTTPAuthenticationRequest.m ================================================ #import "HTTPAuthenticationRequest.h" #import "HTTPMessage.h" #if ! __has_feature(objc_arc) #warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). #endif @interface HTTPAuthenticationRequest (PrivateAPI) - (NSString *)quotedSubHeaderFieldValue:(NSString *)param fromHeaderFieldValue:(NSString *)header; - (NSString *)nonquotedSubHeaderFieldValue:(NSString *)param fromHeaderFieldValue:(NSString *)header; @end @implementation HTTPAuthenticationRequest - (id)initWithRequest:(HTTPMessage *)request { if ((self = [super init])) { NSString *authInfo = [request headerField:@"Authorization"]; isBasic = NO; if ([authInfo length] >= 6) { isBasic = [[authInfo substringToIndex:6] caseInsensitiveCompare:@"Basic "] == NSOrderedSame; } isDigest = NO; if ([authInfo length] >= 7) { isDigest = [[authInfo substringToIndex:7] caseInsensitiveCompare:@"Digest "] == NSOrderedSame; } if (isBasic) { NSMutableString *temp = [[authInfo substringFromIndex:6] mutableCopy]; CFStringTrimWhitespace((__bridge CFMutableStringRef)temp); base64Credentials = [temp copy]; } if (isDigest) { username = [self quotedSubHeaderFieldValue:@"username" fromHeaderFieldValue:authInfo]; realm = [self quotedSubHeaderFieldValue:@"realm" fromHeaderFieldValue:authInfo]; nonce = [self quotedSubHeaderFieldValue:@"nonce" fromHeaderFieldValue:authInfo]; uri = [self quotedSubHeaderFieldValue:@"uri" fromHeaderFieldValue:authInfo]; // It appears from RFC 2617 that the qop is to be given unquoted // Tests show that Firefox performs this way, but Safari does not // Thus we'll attempt to retrieve the value as nonquoted, but we'll verify it doesn't start with a quote qop = [self nonquotedSubHeaderFieldValue:@"qop" fromHeaderFieldValue:authInfo]; if(qop && ([qop characterAtIndex:0] == '"')) { qop = [self quotedSubHeaderFieldValue:@"qop" fromHeaderFieldValue:authInfo]; } nc = [self nonquotedSubHeaderFieldValue:@"nc" fromHeaderFieldValue:authInfo]; cnonce = [self quotedSubHeaderFieldValue:@"cnonce" fromHeaderFieldValue:authInfo]; response = [self quotedSubHeaderFieldValue:@"response" fromHeaderFieldValue:authInfo]; } } return self; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark Accessors: //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - (BOOL)isBasic { return isBasic; } - (BOOL)isDigest { return isDigest; } - (NSString *)base64Credentials { return base64Credentials; } - (NSString *)username { return username; } - (NSString *)realm { return realm; } - (NSString *)nonce { return nonce; } - (NSString *)uri { return uri; } - (NSString *)qop { return qop; } - (NSString *)nc { return nc; } - (NSString *)cnonce { return cnonce; } - (NSString *)response { return response; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark Private API: //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /** * Retrieves a "Sub Header Field Value" from a given header field value. * The sub header field is expected to be quoted. * * In the following header field: * Authorization: Digest username="Mufasa", qop=auth, response="6629fae4939" * The sub header field titled 'username' is quoted, and this method would return the value @"Mufasa". **/ - (NSString *)quotedSubHeaderFieldValue:(NSString *)param fromHeaderFieldValue:(NSString *)header { NSRange startRange = [header rangeOfString:[NSString stringWithFormat:@"%@=\"", param]]; if(startRange.location == NSNotFound) { // The param was not found anywhere in the header return nil; } NSUInteger postStartRangeLocation = startRange.location + startRange.length; NSUInteger postStartRangeLength = [header length] - postStartRangeLocation; NSRange postStartRange = NSMakeRange(postStartRangeLocation, postStartRangeLength); NSRange endRange = [header rangeOfString:@"\"" options:0 range:postStartRange]; if(endRange.location == NSNotFound) { // The ending double-quote was not found anywhere in the header return nil; } NSRange subHeaderRange = NSMakeRange(postStartRangeLocation, endRange.location - postStartRangeLocation); return [header substringWithRange:subHeaderRange]; } /** * Retrieves a "Sub Header Field Value" from a given header field value. * The sub header field is expected to not be quoted. * * In the following header field: * Authorization: Digest username="Mufasa", qop=auth, response="6629fae4939" * The sub header field titled 'qop' is nonquoted, and this method would return the value @"auth". **/ - (NSString *)nonquotedSubHeaderFieldValue:(NSString *)param fromHeaderFieldValue:(NSString *)header { NSRange startRange = [header rangeOfString:[NSString stringWithFormat:@"%@=", param]]; if(startRange.location == NSNotFound) { // The param was not found anywhere in the header return nil; } NSUInteger postStartRangeLocation = startRange.location + startRange.length; NSUInteger postStartRangeLength = [header length] - postStartRangeLocation; NSRange postStartRange = NSMakeRange(postStartRangeLocation, postStartRangeLength); NSRange endRange = [header rangeOfString:@"," options:0 range:postStartRange]; if(endRange.location == NSNotFound) { // The ending comma was not found anywhere in the header // However, if the nonquoted param is at the end of the string, there would be no comma // This is only possible if there are no spaces anywhere NSRange endRange2 = [header rangeOfString:@" " options:0 range:postStartRange]; if(endRange2.location != NSNotFound) { return nil; } else { return [header substringWithRange:postStartRange]; } } else { NSRange subHeaderRange = NSMakeRange(postStartRangeLocation, endRange.location - postStartRangeLocation); return [header substringWithRange:subHeaderRange]; } } @end ================================================ FILE: zhuishushenqi/Vendor/CocoaHTTPServer/Core/HTTPConnection.h ================================================ #import @class GCDAsyncSocket; @class HTTPMessage; @class HTTPServer; @class WebSocket; @protocol HTTPResponse; #define HTTPConnectionDidDieNotification @"HTTPConnectionDidDie" //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @interface HTTPConfig : NSObject { HTTPServer __unsafe_unretained *server; NSString __strong *documentRoot; dispatch_queue_t queue; } - (id)initWithServer:(HTTPServer *)server documentRoot:(NSString *)documentRoot; - (id)initWithServer:(HTTPServer *)server documentRoot:(NSString *)documentRoot queue:(dispatch_queue_t)q; @property (nonatomic, unsafe_unretained, readonly) HTTPServer *server; @property (nonatomic, strong, readonly) NSString *documentRoot; @property (nonatomic, readonly) dispatch_queue_t queue; @end //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @interface HTTPConnection : NSObject { dispatch_queue_t connectionQueue; GCDAsyncSocket *asyncSocket; HTTPConfig *config; BOOL started; HTTPMessage *request; unsigned int numHeaderLines; BOOL sentResponseHeaders; NSString *nonce; long lastNC; NSObject *httpResponse; NSMutableArray *ranges; NSMutableArray *ranges_headers; NSString *ranges_boundry; int rangeIndex; UInt64 requestContentLength; UInt64 requestContentLengthReceived; UInt64 requestChunkSize; UInt64 requestChunkSizeReceived; NSMutableArray *responseDataSizes; } - (id)initWithAsyncSocket:(GCDAsyncSocket *)newSocket configuration:(HTTPConfig *)aConfig; - (void)start; - (void)stop; - (void)startConnection; - (BOOL)supportsMethod:(NSString *)method atPath:(NSString *)path; - (BOOL)expectsRequestBodyFromMethod:(NSString *)method atPath:(NSString *)path; - (BOOL)isSecureServer; - (NSArray *)sslIdentityAndCertificates; - (BOOL)isPasswordProtected:(NSString *)path; - (BOOL)useDigestAccessAuthentication; - (NSString *)realm; - (NSString *)passwordForUser:(NSString *)username; - (NSDictionary *)parseParams:(NSString *)query; - (NSDictionary *)parseGetParams; - (NSString *)requestURI; - (NSArray *)directoryIndexFileNames; - (NSString *)filePathForURI:(NSString *)path; - (NSString *)filePathForURI:(NSString *)path allowDirectory:(BOOL)allowDirectory; - (NSObject *)httpResponseForMethod:(NSString *)method URI:(NSString *)path; - (WebSocket *)webSocketForURI:(NSString *)path; - (void)prepareForBodyWithSize:(UInt64)contentLength; - (void)processBodyData:(NSData *)postDataChunk; - (void)finishBody; - (void)handleVersionNotSupported:(NSString *)version; - (void)handleAuthenticationFailed; - (void)handleResourceNotFound; - (void)handleInvalidRequest:(NSData *)data; - (void)handleUnknownMethod:(NSString *)method; - (NSData *)preprocessResponse:(HTTPMessage *)response; - (NSData *)preprocessErrorResponse:(HTTPMessage *)response; - (void)finishResponse; - (BOOL)shouldDie; - (void)die; @end @interface HTTPConnection (AsynchronousHTTPResponse) - (void)responseHasAvailableData:(NSObject *)sender; - (void)responseDidAbort:(NSObject *)sender; @end ================================================ FILE: zhuishushenqi/Vendor/CocoaHTTPServer/Core/HTTPConnection.m ================================================ #import "GCDAsyncSocket.h" #import "HTTPServer.h" #import "HTTPConnection.h" #import "HTTPMessage.h" #import "HTTPResponse.h" #import "HTTPAuthenticationRequest.h" #import "DDNumber.h" #import "DDRange.h" #import "DDData.h" #import "HTTPFileResponse.h" #import "HTTPAsyncFileResponse.h" #import "WebSocket.h" #import "HTTPLogging.h" #if ! __has_feature(objc_arc) #warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). #endif // Does ARC support support GCD objects? // It does if the minimum deployment target is iOS 6+ or Mac OS X 8+ #if TARGET_OS_IPHONE // Compiling for iOS #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 60000 // iOS 6.0 or later #define NEEDS_DISPATCH_RETAIN_RELEASE 0 #else // iOS 5.X or earlier #define NEEDS_DISPATCH_RETAIN_RELEASE 1 #endif #else // Compiling for Mac OS X #if MAC_OS_X_VERSION_MIN_REQUIRED >= 1080 // Mac OS X 10.8 or later #define NEEDS_DISPATCH_RETAIN_RELEASE 0 #else #define NEEDS_DISPATCH_RETAIN_RELEASE 1 // Mac OS X 10.7 or earlier #endif #endif // Log levels: off, error, warn, info, verbose // Other flags: trace static const int httpLogLevel = HTTP_LOG_LEVEL_WARN; // | HTTP_LOG_FLAG_TRACE; // Define chunk size used to read in data for responses // This is how much data will be read from disk into RAM at a time #if TARGET_OS_IPHONE #define READ_CHUNKSIZE (1024 * 128) #else #define READ_CHUNKSIZE (1024 * 512) #endif // Define chunk size used to read in POST upload data #if TARGET_OS_IPHONE #define POST_CHUNKSIZE (1024 * 32) #else #define POST_CHUNKSIZE (1024 * 128) #endif // Define the various timeouts (in seconds) for various parts of the HTTP process #define TIMEOUT_READ_FIRST_HEADER_LINE 30 #define TIMEOUT_READ_SUBSEQUENT_HEADER_LINE 30 #define TIMEOUT_READ_BODY -1 #define TIMEOUT_WRITE_HEAD 30 #define TIMEOUT_WRITE_BODY -1 #define TIMEOUT_WRITE_ERROR 30 #define TIMEOUT_NONCE 300 // Define the various limits // MAX_HEADER_LINE_LENGTH: Max length (in bytes) of any single line in a header (including \r\n) // MAX_HEADER_LINES : Max number of lines in a single header (including first GET line) #define MAX_HEADER_LINE_LENGTH 8190 #define MAX_HEADER_LINES 100 // MAX_CHUNK_LINE_LENGTH : For accepting chunked transfer uploads, max length of chunk size line (including \r\n) #define MAX_CHUNK_LINE_LENGTH 200 // Define the various tags we'll use to differentiate what it is we're currently doing #define HTTP_REQUEST_HEADER 10 #define HTTP_REQUEST_BODY 11 #define HTTP_REQUEST_CHUNK_SIZE 12 #define HTTP_REQUEST_CHUNK_DATA 13 #define HTTP_REQUEST_CHUNK_TRAILER 14 #define HTTP_REQUEST_CHUNK_FOOTER 15 #define HTTP_PARTIAL_RESPONSE 20 #define HTTP_PARTIAL_RESPONSE_HEADER 21 #define HTTP_PARTIAL_RESPONSE_BODY 22 #define HTTP_CHUNKED_RESPONSE_HEADER 30 #define HTTP_CHUNKED_RESPONSE_BODY 31 #define HTTP_CHUNKED_RESPONSE_FOOTER 32 #define HTTP_PARTIAL_RANGE_RESPONSE_BODY 40 #define HTTP_PARTIAL_RANGES_RESPONSE_BODY 50 #define HTTP_RESPONSE 90 #define HTTP_FINAL_RESPONSE 91 // A quick note about the tags: // // The HTTP_RESPONSE and HTTP_FINAL_RESPONSE are designated tags signalling that the response is completely sent. // That is, in the onSocket:didWriteDataWithTag: method, if the tag is HTTP_RESPONSE or HTTP_FINAL_RESPONSE, // it is assumed that the response is now completely sent. // Use HTTP_RESPONSE if it's the end of a response, and you want to start reading more requests afterwards. // Use HTTP_FINAL_RESPONSE if you wish to terminate the connection after sending the response. // // If you are sending multiple data segments in a custom response, make sure that only the last segment has // the HTTP_RESPONSE tag. For all other segments prior to the last segment use HTTP_PARTIAL_RESPONSE, or some other // tag of your own invention. @interface HTTPConnection (PrivateAPI) - (void)startReadingRequest; - (void)sendResponseHeadersAndBody; @end //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @implementation HTTPConnection static dispatch_queue_t recentNonceQueue; static NSMutableArray *recentNonces; /** * This method is automatically called (courtesy of Cocoa) before the first instantiation of this class. * We use it to initialize any static variables. **/ + (void)initialize { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ // Initialize class variables recentNonceQueue = dispatch_queue_create("HTTPConnection-Nonce", NULL); recentNonces = [[NSMutableArray alloc] initWithCapacity:5]; }); } /** * Generates and returns an authentication nonce. * A nonce is a server-specified string uniquely generated for each 401 response. * The default implementation uses a single nonce for each session. **/ + (NSString *)generateNonce { // We use the Core Foundation UUID class to generate a nonce value for us // UUIDs (Universally Unique Identifiers) are 128-bit values guaranteed to be unique. CFUUIDRef theUUID = CFUUIDCreate(NULL); NSString *newNonce = (__bridge_transfer NSString *)CFUUIDCreateString(NULL, theUUID); CFRelease(theUUID); // We have to remember that the HTTP protocol is stateless. // Even though with version 1.1 persistent connections are the norm, they are not guaranteed. // Thus if we generate a nonce for this connection, // it should be honored for other connections in the near future. // // In fact, this is absolutely necessary in order to support QuickTime. // When QuickTime makes it's initial connection, it will be unauthorized, and will receive a nonce. // It then disconnects, and creates a new connection with the nonce, and proper authentication. // If we don't honor the nonce for the second connection, QuickTime will repeat the process and never connect. dispatch_async(recentNonceQueue, ^{ @autoreleasepool { [recentNonces addObject:newNonce]; }}); double delayInSeconds = TIMEOUT_NONCE; dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC); dispatch_after(popTime, recentNonceQueue, ^{ @autoreleasepool { [recentNonces removeObject:newNonce]; }}); return newNonce; } /** * Returns whether or not the given nonce is in the list of recently generated nonce's. **/ + (BOOL)hasRecentNonce:(NSString *)recentNonce { __block BOOL result = NO; dispatch_sync(recentNonceQueue, ^{ @autoreleasepool { result = [recentNonces containsObject:recentNonce]; }}); return result; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark Init, Dealloc: //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /** * Sole Constructor. * Associates this new HTTP connection with the given AsyncSocket. * This HTTP connection object will become the socket's delegate and take over responsibility for the socket. **/ - (id)initWithAsyncSocket:(GCDAsyncSocket *)newSocket configuration:(HTTPConfig *)aConfig { if ((self = [super init])) { HTTPLogTrace(); if (aConfig.queue) { connectionQueue = aConfig.queue; #if NEEDS_DISPATCH_RETAIN_RELEASE dispatch_retain(connectionQueue); #endif } else { connectionQueue = dispatch_queue_create("HTTPConnection", NULL); } // Take over ownership of the socket asyncSocket = newSocket; [asyncSocket setDelegate:self delegateQueue:connectionQueue]; // Store configuration config = aConfig; // Initialize lastNC (last nonce count). // Used with digest access authentication. // These must increment for each request from the client. lastNC = 0; // Create a new HTTP message request = [[HTTPMessage alloc] initEmptyRequest]; numHeaderLines = 0; responseDataSizes = [[NSMutableArray alloc] initWithCapacity:5]; } return self; } /** * Standard Deconstructor. **/ - (void)dealloc { HTTPLogTrace(); #if NEEDS_DISPATCH_RETAIN_RELEASE dispatch_release(connectionQueue); #endif [asyncSocket setDelegate:nil delegateQueue:NULL]; [asyncSocket disconnect]; if ([httpResponse respondsToSelector:@selector(connectionDidClose)]) { [httpResponse connectionDidClose]; } } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark Method Support //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /** * Returns whether or not the server will accept messages of a given method * at a particular URI. **/ - (BOOL)supportsMethod:(NSString *)method atPath:(NSString *)path { HTTPLogTrace(); // Override me to support methods such as POST. // // Things you may want to consider: // - Does the given path represent a resource that is designed to accept this method? // - If accepting an upload, is the size of the data being uploaded too big? // To do this you can check the requestContentLength variable. // // For more information, you can always access the HTTPMessage request variable. // // You should fall through with a call to [super supportsMethod:method atPath:path] // // See also: expectsRequestBodyFromMethod:atPath: if ([method isEqualToString:@"GET"]) return YES; if ([method isEqualToString:@"HEAD"]) return YES; return NO; } /** * Returns whether or not the server expects a body from the given method. * * In other words, should the server expect a content-length header and associated body from this method. * This would be true in the case of a POST, where the client is sending data, * or for something like PUT where the client is supposed to be uploading a file. **/ - (BOOL)expectsRequestBodyFromMethod:(NSString *)method atPath:(NSString *)path { HTTPLogTrace(); // Override me to add support for other methods that expect the client // to send a body along with the request header. // // You should fall through with a call to [super expectsRequestBodyFromMethod:method atPath:path] // // See also: supportsMethod:atPath: if ([method isEqualToString:@"POST"]) return YES; if ([method isEqualToString:@"PUT"]) return YES; return NO; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark HTTPS //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /** * Returns whether or not the server is configured to be a secure server. * In other words, all connections to this server are immediately secured, thus only secure connections are allowed. * This is the equivalent of having an https server, where it is assumed that all connections must be secure. * If this is the case, then unsecure connections will not be allowed on this server, and a separate unsecure server * would need to be run on a separate port in order to support unsecure connections. * * Note: In order to support secure connections, the sslIdentityAndCertificates method must be implemented. **/ - (BOOL)isSecureServer { HTTPLogTrace(); // Override me to create an https server... return NO; } /** * This method is expected to returns an array appropriate for use in kCFStreamSSLCertificates SSL Settings. * It should be an array of SecCertificateRefs except for the first element in the array, which is a SecIdentityRef. **/ - (NSArray *)sslIdentityAndCertificates { HTTPLogTrace(); // Override me to provide the proper required SSL identity. return nil; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark Password Protection //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /** * Returns whether or not the requested resource is password protected. * In this generic implementation, nothing is password protected. **/ - (BOOL)isPasswordProtected:(NSString *)path { HTTPLogTrace(); // Override me to provide password protection... // You can configure it for the entire server, or based on the current request return NO; } /** * Returns whether or not the authentication challenge should use digest access authentication. * The alternative is basic authentication. * * If at all possible, digest access authentication should be used because it's more secure. * Basic authentication sends passwords in the clear and should be avoided unless using SSL/TLS. **/ - (BOOL)useDigestAccessAuthentication { HTTPLogTrace(); // Override me to customize the authentication scheme // Make sure you understand the security risks of using the weaker basic authentication return YES; } /** * Returns the authentication realm. * In this generic implmentation, a default realm is used for the entire server. **/ - (NSString *)realm { HTTPLogTrace(); // Override me to provide a custom realm... // You can configure it for the entire server, or based on the current request return @"defaultRealm@host.com"; } /** * Returns the password for the given username. **/ - (NSString *)passwordForUser:(NSString *)username { HTTPLogTrace(); // Override me to provide proper password authentication // You can configure a password for the entire server, or custom passwords for users and/or resources // Security Note: // A nil password means no access at all. (Such as for user doesn't exist) // An empty string password is allowed, and will be treated as any other password. (To support anonymous access) return nil; } /** * Returns whether or not the user is properly authenticated. **/ - (BOOL)isAuthenticated { HTTPLogTrace(); // Extract the authentication information from the Authorization header HTTPAuthenticationRequest *auth = [[HTTPAuthenticationRequest alloc] initWithRequest:request]; if ([self useDigestAccessAuthentication]) { // Digest Access Authentication (RFC 2617) if(![auth isDigest]) { // User didn't send proper digest access authentication credentials return NO; } if ([auth username] == nil) { // The client didn't provide a username // Most likely they didn't provide any authentication at all return NO; } NSString *password = [self passwordForUser:[auth username]]; if (password == nil) { // No access allowed (username doesn't exist in system) return NO; } NSString *url = [[request url] relativeString]; if (![url isEqualToString:[auth uri]]) { // Requested URL and Authorization URI do not match // This could be a replay attack // IE - attacker provides same authentication information, but requests a different resource return NO; } // The nonce the client provided will most commonly be stored in our local (cached) nonce variable if (![nonce isEqualToString:[auth nonce]]) { // The given nonce may be from another connection // We need to search our list of recent nonce strings that have been recently distributed if ([[self class] hasRecentNonce:[auth nonce]]) { // Store nonce in local (cached) nonce variable to prevent array searches in the future nonce = [[auth nonce] copy]; // The client has switched to using a different nonce value // This may happen if the client tries to get a file in a directory with different credentials. // The previous credentials wouldn't work, and the client would receive a 401 error // along with a new nonce value. The client then uses this new nonce value and requests the file again. // Whatever the case may be, we need to reset lastNC, since that variable is on a per nonce basis. lastNC = 0; } else { // We have no knowledge of ever distributing such a nonce. // This could be a replay attack from a previous connection in the past. return NO; } } long authNC = strtol([[auth nc] UTF8String], NULL, 16); if (authNC <= lastNC) { // The nc value (nonce count) hasn't been incremented since the last request. // This could be a replay attack. return NO; } lastNC = authNC; NSString *HA1str = [NSString stringWithFormat:@"%@:%@:%@", [auth username], [auth realm], password]; NSString *HA2str = [NSString stringWithFormat:@"%@:%@", [request method], [auth uri]]; NSString *HA1 = [[[HA1str dataUsingEncoding:NSUTF8StringEncoding] md5Digest] hexStringValue]; NSString *HA2 = [[[HA2str dataUsingEncoding:NSUTF8StringEncoding] md5Digest] hexStringValue]; NSString *responseStr = [NSString stringWithFormat:@"%@:%@:%@:%@:%@:%@", HA1, [auth nonce], [auth nc], [auth cnonce], [auth qop], HA2]; NSString *response = [[[responseStr dataUsingEncoding:NSUTF8StringEncoding] md5Digest] hexStringValue]; return [response isEqualToString:[auth response]]; } else { // Basic Authentication if (![auth isBasic]) { // User didn't send proper base authentication credentials return NO; } // Decode the base 64 encoded credentials NSString *base64Credentials = [auth base64Credentials]; NSData *temp = [[base64Credentials dataUsingEncoding:NSUTF8StringEncoding] base64Decoded]; NSString *credentials = [[NSString alloc] initWithData:temp encoding:NSUTF8StringEncoding]; // The credentials should be of the form "username:password" // The username is not allowed to contain a colon NSRange colonRange = [credentials rangeOfString:@":"]; if (colonRange.length == 0) { // Malformed credentials return NO; } NSString *credUsername = [credentials substringToIndex:colonRange.location]; NSString *credPassword = [credentials substringFromIndex:(colonRange.location + colonRange.length)]; NSString *password = [self passwordForUser:credUsername]; if (password == nil) { // No access allowed (username doesn't exist in system) return NO; } return [password isEqualToString:credPassword]; } } /** * Adds a digest access authentication challenge to the given response. **/ - (void)addDigestAuthChallenge:(HTTPMessage *)response { HTTPLogTrace(); NSString *authFormat = @"Digest realm=\"%@\", qop=\"auth\", nonce=\"%@\""; NSString *authInfo = [NSString stringWithFormat:authFormat, [self realm], [[self class] generateNonce]]; [response setHeaderField:@"WWW-Authenticate" value:authInfo]; } /** * Adds a basic authentication challenge to the given response. **/ - (void)addBasicAuthChallenge:(HTTPMessage *)response { HTTPLogTrace(); NSString *authFormat = @"Basic realm=\"%@\""; NSString *authInfo = [NSString stringWithFormat:authFormat, [self realm]]; [response setHeaderField:@"WWW-Authenticate" value:authInfo]; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark Core //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /** * Starting point for the HTTP connection after it has been fully initialized (including subclasses). * This method is called by the HTTP server. **/ - (void)start { dispatch_async(connectionQueue, ^{ @autoreleasepool { if (!started) { started = YES; [self startConnection]; } }}); } /** * This method is called by the HTTPServer if it is asked to stop. * The server, in turn, invokes stop on each HTTPConnection instance. **/ - (void)stop { dispatch_async(connectionQueue, ^{ @autoreleasepool { // Disconnect the socket. // The socketDidDisconnect delegate method will handle everything else. [asyncSocket disconnect]; }}); } /** * Starting point for the HTTP connection. **/ - (void)startConnection { // Override me to do any custom work before the connection starts. // // Be sure to invoke [super startConnection] when you're done. HTTPLogTrace(); if ([self isSecureServer]) { // We are configured to be an HTTPS server. // That is, we secure via SSL/TLS the connection prior to any communication. NSArray *certificates = [self sslIdentityAndCertificates]; if ([certificates count] > 0) { // All connections are assumed to be secure. Only secure connections are allowed on this server. NSMutableDictionary *settings = [NSMutableDictionary dictionaryWithCapacity:3]; // Configure this connection as the server [settings setObject:[NSNumber numberWithBool:YES] forKey:(NSString *)kCFStreamSSLIsServer]; [settings setObject:certificates forKey:(NSString *)kCFStreamSSLCertificates]; // Configure this connection to use the highest possible SSL level [settings setObject:(NSString *)kCFStreamSocketSecurityLevelNegotiatedSSL forKey:(NSString *)kCFStreamSSLLevel]; [asyncSocket startTLS:settings]; } } [self startReadingRequest]; } /** * Starts reading an HTTP request. **/ - (void)startReadingRequest { HTTPLogTrace(); [asyncSocket readDataToData:[GCDAsyncSocket CRLFData] withTimeout:TIMEOUT_READ_FIRST_HEADER_LINE maxLength:MAX_HEADER_LINE_LENGTH tag:HTTP_REQUEST_HEADER]; } /** * Parses the given query string. * * For example, if the query is "q=John%20Mayer%20Trio&num=50" * then this method would return the following dictionary: * { * q = "John Mayer Trio" * num = "50" * } **/ - (NSDictionary *)parseParams:(NSString *)query { NSArray *components = [query componentsSeparatedByString:@"&"]; NSMutableDictionary *result = [NSMutableDictionary dictionaryWithCapacity:[components count]]; NSUInteger i; for (i = 0; i < [components count]; i++) { NSString *component = [components objectAtIndex:i]; if ([component length] > 0) { NSRange range = [component rangeOfString:@"="]; if (range.location != NSNotFound) { NSString *escapedKey = [component substringToIndex:(range.location + 0)]; NSString *escapedValue = [component substringFromIndex:(range.location + 1)]; if ([escapedKey length] > 0) { CFStringRef k, v; k = CFURLCreateStringByReplacingPercentEscapes(NULL, (__bridge CFStringRef)escapedKey, CFSTR("")); v = CFURLCreateStringByReplacingPercentEscapes(NULL, (__bridge CFStringRef)escapedValue, CFSTR("")); NSString *key, *value; key = (__bridge_transfer NSString *)k; value = (__bridge_transfer NSString *)v; if (key) { if (value) [result setObject:value forKey:key]; else [result setObject:[NSNull null] forKey:key]; } } } } } return result; } /** * Parses the query variables in the request URI. * * For example, if the request URI was "/search.html?q=John%20Mayer%20Trio&num=50" * then this method would return the following dictionary: * { * q = "John Mayer Trio" * num = "50" * } **/ - (NSDictionary *)parseGetParams { if(![request isHeaderComplete]) return nil; NSDictionary *result = nil; NSURL *url = [request url]; if(url) { NSString *query = [url query]; if (query) { result = [self parseParams:query]; } } return result; } /** * Attempts to parse the given range header into a series of sequential non-overlapping ranges. * If successfull, the variables 'ranges' and 'rangeIndex' will be updated, and YES will be returned. * Otherwise, NO is returned, and the range request should be ignored. **/ - (BOOL)parseRangeRequest:(NSString *)rangeHeader withContentLength:(UInt64)contentLength { HTTPLogTrace(); // Examples of byte-ranges-specifier values (assuming an entity-body of length 10000): // // - The first 500 bytes (byte offsets 0-499, inclusive): bytes=0-499 // // - The second 500 bytes (byte offsets 500-999, inclusive): bytes=500-999 // // - The final 500 bytes (byte offsets 9500-9999, inclusive): bytes=-500 // // - Or bytes=9500- // // - The first and last bytes only (bytes 0 and 9999): bytes=0-0,-1 // // - Several legal but not canonical specifications of the second 500 bytes (byte offsets 500-999, inclusive): // bytes=500-600,601-999 // bytes=500-700,601-999 // NSRange eqsignRange = [rangeHeader rangeOfString:@"="]; if(eqsignRange.location == NSNotFound) return NO; NSUInteger tIndex = eqsignRange.location; NSUInteger fIndex = eqsignRange.location + eqsignRange.length; NSMutableString *rangeType = [[rangeHeader substringToIndex:tIndex] mutableCopy]; NSMutableString *rangeValue = [[rangeHeader substringFromIndex:fIndex] mutableCopy]; CFStringTrimWhitespace((__bridge CFMutableStringRef)rangeType); CFStringTrimWhitespace((__bridge CFMutableStringRef)rangeValue); if([rangeType caseInsensitiveCompare:@"bytes"] != NSOrderedSame) return NO; NSArray *rangeComponents = [rangeValue componentsSeparatedByString:@","]; if([rangeComponents count] == 0) return NO; ranges = [[NSMutableArray alloc] initWithCapacity:[rangeComponents count]]; rangeIndex = 0; // Note: We store all range values in the form of DDRange structs, wrapped in NSValue objects. // Since DDRange consists of UInt64 values, the range extends up to 16 exabytes. NSUInteger i; for (i = 0; i < [rangeComponents count]; i++) { NSString *rangeComponent = [rangeComponents objectAtIndex:i]; NSRange dashRange = [rangeComponent rangeOfString:@"-"]; if (dashRange.location == NSNotFound) { // We're dealing with an individual byte number UInt64 byteIndex; if(![NSNumber parseString:rangeComponent intoUInt64:&byteIndex]) return NO; if(byteIndex >= contentLength) return NO; [ranges addObject:[NSValue valueWithDDRange:DDMakeRange(byteIndex, 1)]]; } else { // We're dealing with a range of bytes tIndex = dashRange.location; fIndex = dashRange.location + dashRange.length; NSString *r1str = [rangeComponent substringToIndex:tIndex]; NSString *r2str = [rangeComponent substringFromIndex:fIndex]; UInt64 r1, r2; BOOL hasR1 = [NSNumber parseString:r1str intoUInt64:&r1]; BOOL hasR2 = [NSNumber parseString:r2str intoUInt64:&r2]; if (!hasR1) { // We're dealing with a "-[#]" range // // r2 is the number of ending bytes to include in the range if(!hasR2) return NO; if(r2 > contentLength) return NO; UInt64 startIndex = contentLength - r2; [ranges addObject:[NSValue valueWithDDRange:DDMakeRange(startIndex, r2)]]; } else if (!hasR2) { // We're dealing with a "[#]-" range // // r1 is the starting index of the range, which goes all the way to the end if(r1 >= contentLength) return NO; [ranges addObject:[NSValue valueWithDDRange:DDMakeRange(r1, contentLength - r1)]]; } else { // We're dealing with a normal "[#]-[#]" range // // Note: The range is inclusive. So 0-1 has a length of 2 bytes. if(r1 > r2) return NO; if(r2 >= contentLength) return NO; [ranges addObject:[NSValue valueWithDDRange:DDMakeRange(r1, r2 - r1 + 1)]]; } } } if([ranges count] == 0) return NO; // Now make sure none of the ranges overlap for (i = 0; i < [ranges count] - 1; i++) { DDRange range1 = [[ranges objectAtIndex:i] ddrangeValue]; NSUInteger j; for (j = i+1; j < [ranges count]; j++) { DDRange range2 = [[ranges objectAtIndex:j] ddrangeValue]; DDRange iRange = DDIntersectionRange(range1, range2); if(iRange.length != 0) { return NO; } } } // Sort the ranges [ranges sortUsingSelector:@selector(ddrangeCompare:)]; return YES; } - (NSString *)requestURI { if(request == nil) return nil; return [[request url] relativeString]; } /** * This method is called after a full HTTP request has been received. * The current request is in the HTTPMessage request variable. **/ - (void)replyToHTTPRequest { HTTPLogTrace(); if (HTTP_LOG_VERBOSE) { NSData *tempData = [request messageData]; NSString *tempStr = [[NSString alloc] initWithData:tempData encoding:NSUTF8StringEncoding]; HTTPLogVerbose(@"%@[%p]: Received HTTP request:\n%@", THIS_FILE, self, tempStr); } // Check the HTTP version // We only support version 1.0 and 1.1 NSString *version = [request version]; if (![version isEqualToString:HTTPVersion1_1] && ![version isEqualToString:HTTPVersion1_0]) { [self handleVersionNotSupported:version]; return; } // Extract requested URI NSString *uri = [self requestURI]; // Check for WebSocket request if ([WebSocket isWebSocketRequest:request]) { HTTPLogVerbose(@"isWebSocket"); WebSocket *ws = [self webSocketForURI:uri]; if (ws == nil) { [self handleResourceNotFound]; } else { [ws start]; [[config server] addWebSocket:ws]; // The WebSocket should now be the delegate of the underlying socket. // But gracefully handle the situation if it forgot. if ([asyncSocket delegate] == self) { HTTPLogWarn(@"%@[%p]: WebSocket forgot to set itself as socket delegate", THIS_FILE, self); // Disconnect the socket. // The socketDidDisconnect delegate method will handle everything else. [asyncSocket disconnect]; } else { // The WebSocket is using the socket, // so make sure we don't disconnect it in the dealloc method. asyncSocket = nil; [self die]; // Note: There is a timing issue here that should be pointed out. // // A bug that existed in previous versions happend like so: // - We invoked [self die] // - This caused us to get released, and our dealloc method to start executing // - Meanwhile, AsyncSocket noticed a disconnect, and began to dispatch a socketDidDisconnect at us // - The dealloc method finishes execution, and our instance gets freed // - The socketDidDisconnect gets run, and a crash occurs // // So the issue we want to avoid is releasing ourself when there is a possibility // that AsyncSocket might be gearing up to queue a socketDidDisconnect for us. // // In this particular situation notice that we invoke [asyncSocket delegate]. // This method is synchronous concerning AsyncSocket's internal socketQueue. // Which means we can be sure, when it returns, that AsyncSocket has already // queued any delegate methods for us if it was going to. // And if the delegate methods are queued, then we've been properly retained. // Meaning we won't get released / dealloc'd until the delegate method has finished executing. // // In this rare situation, the die method will get invoked twice. } } return; } // Check Authentication (if needed) // If not properly authenticated for resource, issue Unauthorized response if ([self isPasswordProtected:uri] && ![self isAuthenticated]) { [self handleAuthenticationFailed]; return; } // Extract the method NSString *method = [request method]; // Note: We already checked to ensure the method was supported in onSocket:didReadData:withTag: // Respond properly to HTTP 'GET' and 'HEAD' commands httpResponse = [self httpResponseForMethod:method URI:uri]; if (httpResponse == nil) { [self handleResourceNotFound]; return; } [self sendResponseHeadersAndBody]; } /** * Prepares a single-range response. * * Note: The returned HTTPMessage is owned by the sender, who is responsible for releasing it. **/ - (HTTPMessage *)newUniRangeResponse:(UInt64)contentLength { HTTPLogTrace(); // Status Code 206 - Partial Content HTTPMessage *response = [[HTTPMessage alloc] initResponseWithStatusCode:206 description:nil version:HTTPVersion1_1]; DDRange range = [[ranges objectAtIndex:0] ddrangeValue]; NSString *contentLengthStr = [NSString stringWithFormat:@"%qu", range.length]; [response setHeaderField:@"Content-Length" value:contentLengthStr]; NSString *rangeStr = [NSString stringWithFormat:@"%qu-%qu", range.location, DDMaxRange(range) - 1]; NSString *contentRangeStr = [NSString stringWithFormat:@"bytes %@/%qu", rangeStr, contentLength]; [response setHeaderField:@"Content-Range" value:contentRangeStr]; return response; } /** * Prepares a multi-range response. * * Note: The returned HTTPMessage is owned by the sender, who is responsible for releasing it. **/ - (HTTPMessage *)newMultiRangeResponse:(UInt64)contentLength { HTTPLogTrace(); // Status Code 206 - Partial Content HTTPMessage *response = [[HTTPMessage alloc] initResponseWithStatusCode:206 description:nil version:HTTPVersion1_1]; // We have to send each range using multipart/byteranges // So each byterange has to be prefix'd and suffix'd with the boundry // Example: // // HTTP/1.1 206 Partial Content // Content-Length: 220 // Content-Type: multipart/byteranges; boundary=4554d24e986f76dd6 // // // --4554d24e986f76dd6 // Content-Range: bytes 0-25/4025 // // [...] // --4554d24e986f76dd6 // Content-Range: bytes 3975-4024/4025 // // [...] // --4554d24e986f76dd6-- ranges_headers = [[NSMutableArray alloc] initWithCapacity:[ranges count]]; CFUUIDRef theUUID = CFUUIDCreate(NULL); ranges_boundry = (__bridge_transfer NSString *)CFUUIDCreateString(NULL, theUUID); CFRelease(theUUID); NSString *startingBoundryStr = [NSString stringWithFormat:@"\r\n--%@\r\n", ranges_boundry]; NSString *endingBoundryStr = [NSString stringWithFormat:@"\r\n--%@--\r\n", ranges_boundry]; UInt64 actualContentLength = 0; NSUInteger i; for (i = 0; i < [ranges count]; i++) { DDRange range = [[ranges objectAtIndex:i] ddrangeValue]; NSString *rangeStr = [NSString stringWithFormat:@"%qu-%qu", range.location, DDMaxRange(range) - 1]; NSString *contentRangeVal = [NSString stringWithFormat:@"bytes %@/%qu", rangeStr, contentLength]; NSString *contentRangeStr = [NSString stringWithFormat:@"Content-Range: %@\r\n\r\n", contentRangeVal]; NSString *fullHeader = [startingBoundryStr stringByAppendingString:contentRangeStr]; NSData *fullHeaderData = [fullHeader dataUsingEncoding:NSUTF8StringEncoding]; [ranges_headers addObject:fullHeaderData]; actualContentLength += [fullHeaderData length]; actualContentLength += range.length; } NSData *endingBoundryData = [endingBoundryStr dataUsingEncoding:NSUTF8StringEncoding]; actualContentLength += [endingBoundryData length]; NSString *contentLengthStr = [NSString stringWithFormat:@"%qu", actualContentLength]; [response setHeaderField:@"Content-Length" value:contentLengthStr]; NSString *contentTypeStr = [NSString stringWithFormat:@"multipart/byteranges; boundary=%@", ranges_boundry]; [response setHeaderField:@"Content-Type" value:contentTypeStr]; return response; } /** * Returns the chunk size line that must precede each chunk of data when using chunked transfer encoding. * This consists of the size of the data, in hexadecimal, followed by a CRLF. **/ - (NSData *)chunkedTransferSizeLineForLength:(NSUInteger)length { return [[NSString stringWithFormat:@"%lx\r\n", (unsigned long)length] dataUsingEncoding:NSUTF8StringEncoding]; } /** * Returns the data that signals the end of a chunked transfer. **/ - (NSData *)chunkedTransferFooter { // Each data chunk is preceded by a size line (in hex and including a CRLF), // followed by the data itself, followed by another CRLF. // After every data chunk has been sent, a zero size line is sent, // followed by optional footer (which are just more headers), // and followed by a CRLF on a line by itself. return [@"\r\n0\r\n\r\n" dataUsingEncoding:NSUTF8StringEncoding]; } - (void)sendResponseHeadersAndBody { if ([httpResponse respondsToSelector:@selector(delayResponseHeaders)]) { if ([httpResponse delayResponseHeaders]) { return; } } BOOL isChunked = NO; if ([httpResponse respondsToSelector:@selector(isChunked)]) { isChunked = [httpResponse isChunked]; } // If a response is "chunked", this simply means the HTTPResponse object // doesn't know the content-length in advance. UInt64 contentLength = 0; if (!isChunked) { contentLength = [httpResponse contentLength]; } // Check for specific range request NSString *rangeHeader = [request headerField:@"Range"]; BOOL isRangeRequest = NO; // If the response is "chunked" then we don't know the exact content-length. // This means we'll be unable to process any range requests. // This is because range requests might include a range like "give me the last 100 bytes" if (!isChunked && rangeHeader) { if ([self parseRangeRequest:rangeHeader withContentLength:contentLength]) { isRangeRequest = YES; } } HTTPMessage *response; if (!isRangeRequest) { // Create response // Default status code: 200 - OK NSInteger status = 200; if ([httpResponse respondsToSelector:@selector(status)]) { status = [httpResponse status]; } response = [[HTTPMessage alloc] initResponseWithStatusCode:status description:nil version:HTTPVersion1_1]; if (isChunked) { [response setHeaderField:@"Transfer-Encoding" value:@"chunked"]; } else { NSString *contentLengthStr = [NSString stringWithFormat:@"%qu", contentLength]; [response setHeaderField:@"Content-Length" value:contentLengthStr]; } } else { if ([ranges count] == 1) { response = [self newUniRangeResponse:contentLength]; } else { response = [self newMultiRangeResponse:contentLength]; } } BOOL isZeroLengthResponse = !isChunked && (contentLength == 0); // If they issue a 'HEAD' command, we don't have to include the file // If they issue a 'GET' command, we need to include the file if ([[request method] isEqualToString:@"HEAD"] || isZeroLengthResponse) { NSData *responseData = [self preprocessResponse:response]; [asyncSocket writeData:responseData withTimeout:TIMEOUT_WRITE_HEAD tag:HTTP_RESPONSE]; sentResponseHeaders = YES; } else { // Write the header response NSData *responseData = [self preprocessResponse:response]; [asyncSocket writeData:responseData withTimeout:TIMEOUT_WRITE_HEAD tag:HTTP_PARTIAL_RESPONSE_HEADER]; sentResponseHeaders = YES; // Now we need to send the body of the response if (!isRangeRequest) { // Regular request NSData *data = [httpResponse readDataOfLength:READ_CHUNKSIZE]; if ([data length] > 0) { [responseDataSizes addObject:[NSNumber numberWithUnsignedInteger:[data length]]]; if (isChunked) { NSData *chunkSize = [self chunkedTransferSizeLineForLength:[data length]]; [asyncSocket writeData:chunkSize withTimeout:TIMEOUT_WRITE_HEAD tag:HTTP_CHUNKED_RESPONSE_HEADER]; [asyncSocket writeData:data withTimeout:TIMEOUT_WRITE_BODY tag:HTTP_CHUNKED_RESPONSE_BODY]; if ([httpResponse isDone]) { NSData *footer = [self chunkedTransferFooter]; [asyncSocket writeData:footer withTimeout:TIMEOUT_WRITE_HEAD tag:HTTP_RESPONSE]; } else { NSData *footer = [GCDAsyncSocket CRLFData]; [asyncSocket writeData:footer withTimeout:TIMEOUT_WRITE_HEAD tag:HTTP_CHUNKED_RESPONSE_FOOTER]; } } else { long tag = [httpResponse isDone] ? HTTP_RESPONSE : HTTP_PARTIAL_RESPONSE_BODY; [asyncSocket writeData:data withTimeout:TIMEOUT_WRITE_BODY tag:tag]; } } } else { // Client specified a byte range in request if ([ranges count] == 1) { // Client is requesting a single range DDRange range = [[ranges objectAtIndex:0] ddrangeValue]; [httpResponse setOffset:range.location]; NSUInteger bytesToRead = range.length < READ_CHUNKSIZE ? (NSUInteger)range.length : READ_CHUNKSIZE; NSData *data = [httpResponse readDataOfLength:bytesToRead]; if ([data length] > 0) { [responseDataSizes addObject:[NSNumber numberWithUnsignedInteger:[data length]]]; long tag = [data length] == range.length ? HTTP_RESPONSE : HTTP_PARTIAL_RANGE_RESPONSE_BODY; [asyncSocket writeData:data withTimeout:TIMEOUT_WRITE_BODY tag:tag]; } } else { // Client is requesting multiple ranges // We have to send each range using multipart/byteranges // Write range header NSData *rangeHeaderData = [ranges_headers objectAtIndex:0]; [asyncSocket writeData:rangeHeaderData withTimeout:TIMEOUT_WRITE_HEAD tag:HTTP_PARTIAL_RESPONSE_HEADER]; // Start writing range body DDRange range = [[ranges objectAtIndex:0] ddrangeValue]; [httpResponse setOffset:range.location]; NSUInteger bytesToRead = range.length < READ_CHUNKSIZE ? (NSUInteger)range.length : READ_CHUNKSIZE; NSData *data = [httpResponse readDataOfLength:bytesToRead]; if ([data length] > 0) { [responseDataSizes addObject:[NSNumber numberWithUnsignedInteger:[data length]]]; [asyncSocket writeData:data withTimeout:TIMEOUT_WRITE_BODY tag:HTTP_PARTIAL_RANGES_RESPONSE_BODY]; } } } } } /** * Returns the number of bytes of the http response body that are sitting in asyncSocket's write queue. * * We keep track of this information in order to keep our memory footprint low while * working with asynchronous HTTPResponse objects. **/ - (NSUInteger)writeQueueSize { NSUInteger result = 0; NSUInteger i; for(i = 0; i < [responseDataSizes count]; i++) { result += [[responseDataSizes objectAtIndex:i] unsignedIntegerValue]; } return result; } /** * Sends more data, if needed, without growing the write queue over its approximate size limit. * The last chunk of the response body will be sent with a tag of HTTP_RESPONSE. * * This method should only be called for standard (non-range) responses. **/ - (void)continueSendingStandardResponseBody { HTTPLogTrace(); // This method is called when either asyncSocket has finished writing one of the response data chunks, // or when an asynchronous HTTPResponse object informs us that it has more available data for us to send. // In the case of the asynchronous HTTPResponse, we don't want to blindly grab the new data, // and shove it onto asyncSocket's write queue. // Doing so could negatively affect the memory footprint of the application. // Instead, we always ensure that we place no more than READ_CHUNKSIZE bytes onto the write queue. // // Note that this does not affect the rate at which the HTTPResponse object may generate data. // The HTTPResponse is free to do as it pleases, and this is up to the application's developer. // If the memory footprint is a concern, the developer creating the custom HTTPResponse object may freely // use the calls to readDataOfLength as an indication to start generating more data. // This provides an easy way for the HTTPResponse object to throttle its data allocation in step with the rate // at which the socket is able to send it. NSUInteger writeQueueSize = [self writeQueueSize]; if(writeQueueSize >= READ_CHUNKSIZE) return; NSUInteger available = READ_CHUNKSIZE - writeQueueSize; NSData *data = [httpResponse readDataOfLength:available]; if ([data length] > 0) { [responseDataSizes addObject:[NSNumber numberWithUnsignedInteger:[data length]]]; BOOL isChunked = NO; if ([httpResponse respondsToSelector:@selector(isChunked)]) { isChunked = [httpResponse isChunked]; } if (isChunked) { NSData *chunkSize = [self chunkedTransferSizeLineForLength:[data length]]; [asyncSocket writeData:chunkSize withTimeout:TIMEOUT_WRITE_HEAD tag:HTTP_CHUNKED_RESPONSE_HEADER]; [asyncSocket writeData:data withTimeout:TIMEOUT_WRITE_BODY tag:HTTP_CHUNKED_RESPONSE_BODY]; if([httpResponse isDone]) { NSData *footer = [self chunkedTransferFooter]; [asyncSocket writeData:footer withTimeout:TIMEOUT_WRITE_HEAD tag:HTTP_RESPONSE]; } else { NSData *footer = [GCDAsyncSocket CRLFData]; [asyncSocket writeData:footer withTimeout:TIMEOUT_WRITE_HEAD tag:HTTP_CHUNKED_RESPONSE_FOOTER]; } } else { long tag = [httpResponse isDone] ? HTTP_RESPONSE : HTTP_PARTIAL_RESPONSE_BODY; [asyncSocket writeData:data withTimeout:TIMEOUT_WRITE_BODY tag:tag]; } } } /** * Sends more data, if needed, without growing the write queue over its approximate size limit. * The last chunk of the response body will be sent with a tag of HTTP_RESPONSE. * * This method should only be called for single-range responses. **/ - (void)continueSendingSingleRangeResponseBody { HTTPLogTrace(); // This method is called when either asyncSocket has finished writing one of the response data chunks, // or when an asynchronous response informs us that is has more available data for us to send. // In the case of the asynchronous response, we don't want to blindly grab the new data, // and shove it onto asyncSocket's write queue. // Doing so could negatively affect the memory footprint of the application. // Instead, we always ensure that we place no more than READ_CHUNKSIZE bytes onto the write queue. // // Note that this does not affect the rate at which the HTTPResponse object may generate data. // The HTTPResponse is free to do as it pleases, and this is up to the application's developer. // If the memory footprint is a concern, the developer creating the custom HTTPResponse object may freely // use the calls to readDataOfLength as an indication to start generating more data. // This provides an easy way for the HTTPResponse object to throttle its data allocation in step with the rate // at which the socket is able to send it. NSUInteger writeQueueSize = [self writeQueueSize]; if(writeQueueSize >= READ_CHUNKSIZE) return; DDRange range = [[ranges objectAtIndex:0] ddrangeValue]; UInt64 offset = [httpResponse offset]; UInt64 bytesRead = offset - range.location; UInt64 bytesLeft = range.length - bytesRead; if (bytesLeft > 0) { NSUInteger available = READ_CHUNKSIZE - writeQueueSize; NSUInteger bytesToRead = bytesLeft < available ? (NSUInteger)bytesLeft : available; NSData *data = [httpResponse readDataOfLength:bytesToRead]; if ([data length] > 0) { [responseDataSizes addObject:[NSNumber numberWithUnsignedInteger:[data length]]]; long tag = [data length] == bytesLeft ? HTTP_RESPONSE : HTTP_PARTIAL_RANGE_RESPONSE_BODY; [asyncSocket writeData:data withTimeout:TIMEOUT_WRITE_BODY tag:tag]; } } } /** * Sends more data, if needed, without growing the write queue over its approximate size limit. * The last chunk of the response body will be sent with a tag of HTTP_RESPONSE. * * This method should only be called for multi-range responses. **/ - (void)continueSendingMultiRangeResponseBody { HTTPLogTrace(); // This method is called when either asyncSocket has finished writing one of the response data chunks, // or when an asynchronous HTTPResponse object informs us that is has more available data for us to send. // In the case of the asynchronous HTTPResponse, we don't want to blindly grab the new data, // and shove it onto asyncSocket's write queue. // Doing so could negatively affect the memory footprint of the application. // Instead, we always ensure that we place no more than READ_CHUNKSIZE bytes onto the write queue. // // Note that this does not affect the rate at which the HTTPResponse object may generate data. // The HTTPResponse is free to do as it pleases, and this is up to the application's developer. // If the memory footprint is a concern, the developer creating the custom HTTPResponse object may freely // use the calls to readDataOfLength as an indication to start generating more data. // This provides an easy way for the HTTPResponse object to throttle its data allocation in step with the rate // at which the socket is able to send it. NSUInteger writeQueueSize = [self writeQueueSize]; if(writeQueueSize >= READ_CHUNKSIZE) return; DDRange range = [[ranges objectAtIndex:rangeIndex] ddrangeValue]; UInt64 offset = [httpResponse offset]; UInt64 bytesRead = offset - range.location; UInt64 bytesLeft = range.length - bytesRead; if (bytesLeft > 0) { NSUInteger available = READ_CHUNKSIZE - writeQueueSize; NSUInteger bytesToRead = bytesLeft < available ? (NSUInteger)bytesLeft : available; NSData *data = [httpResponse readDataOfLength:bytesToRead]; if ([data length] > 0) { [responseDataSizes addObject:[NSNumber numberWithUnsignedInteger:[data length]]]; [asyncSocket writeData:data withTimeout:TIMEOUT_WRITE_BODY tag:HTTP_PARTIAL_RANGES_RESPONSE_BODY]; } } else { if (++rangeIndex < [ranges count]) { // Write range header NSData *rangeHeader = [ranges_headers objectAtIndex:rangeIndex]; [asyncSocket writeData:rangeHeader withTimeout:TIMEOUT_WRITE_HEAD tag:HTTP_PARTIAL_RESPONSE_HEADER]; // Start writing range body range = [[ranges objectAtIndex:rangeIndex] ddrangeValue]; [httpResponse setOffset:range.location]; NSUInteger available = READ_CHUNKSIZE - writeQueueSize; NSUInteger bytesToRead = range.length < available ? (NSUInteger)range.length : available; NSData *data = [httpResponse readDataOfLength:bytesToRead]; if ([data length] > 0) { [responseDataSizes addObject:[NSNumber numberWithUnsignedInteger:[data length]]]; [asyncSocket writeData:data withTimeout:TIMEOUT_WRITE_BODY tag:HTTP_PARTIAL_RANGES_RESPONSE_BODY]; } } else { // We're not done yet - we still have to send the closing boundry tag NSString *endingBoundryStr = [NSString stringWithFormat:@"\r\n--%@--\r\n", ranges_boundry]; NSData *endingBoundryData = [endingBoundryStr dataUsingEncoding:NSUTF8StringEncoding]; [asyncSocket writeData:endingBoundryData withTimeout:TIMEOUT_WRITE_HEAD tag:HTTP_RESPONSE]; } } } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark Responses //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /** * Returns an array of possible index pages. * For example: {"index.html", "index.htm"} **/ - (NSArray *)directoryIndexFileNames { HTTPLogTrace(); // Override me to support other index pages. return [NSArray arrayWithObjects:@"index.html", @"index.htm", nil]; } - (NSString *)filePathForURI:(NSString *)path { return [self filePathForURI:path allowDirectory:NO]; } /** * Converts relative URI path into full file-system path. **/ - (NSString *)filePathForURI:(NSString *)path allowDirectory:(BOOL)allowDirectory { HTTPLogTrace(); // Override me to perform custom path mapping. // For example you may want to use a default file other than index.html, or perhaps support multiple types. NSString *documentRoot = [config documentRoot]; // Part 0: Validate document root setting. // // If there is no configured documentRoot, // then it makes no sense to try to return anything. if (documentRoot == nil) { HTTPLogWarn(@"%@[%p]: No configured document root", THIS_FILE, self); return nil; } // Part 1: Strip parameters from the url // // E.g.: /page.html?q=22&var=abc -> /page.html NSURL *docRoot = [NSURL fileURLWithPath:documentRoot isDirectory:YES]; if (docRoot == nil) { HTTPLogWarn(@"%@[%p]: Document root is invalid file path", THIS_FILE, self); return nil; } NSString *relativePath = [[NSURL URLWithString:path relativeToURL:docRoot] relativePath]; // Part 2: Append relative path to document root (base path) // // E.g.: relativePath="/images/icon.png" // documentRoot="/Users/robbie/Sites" // fullPath="/Users/robbie/Sites/images/icon.png" // // We also standardize the path. // // E.g.: "Users/robbie/Sites/images/../index.html" -> "/Users/robbie/Sites/index.html" NSString *fullPath = [[documentRoot stringByAppendingPathComponent:relativePath] stringByStandardizingPath]; if ([relativePath isEqualToString:@"/"]) { fullPath = [fullPath stringByAppendingString:@"/"]; } // Part 3: Prevent serving files outside the document root. // // Sneaky requests may include ".." in the path. // // E.g.: relativePath="../Documents/TopSecret.doc" // documentRoot="/Users/robbie/Sites" // fullPath="/Users/robbie/Documents/TopSecret.doc" // // E.g.: relativePath="../Sites_Secret/TopSecret.doc" // documentRoot="/Users/robbie/Sites" // fullPath="/Users/robbie/Sites_Secret/TopSecret" if (![documentRoot hasSuffix:@"/"]) { documentRoot = [documentRoot stringByAppendingString:@"/"]; } if (![fullPath hasPrefix:documentRoot]) { HTTPLogWarn(@"%@[%p]: Request for file outside document root", THIS_FILE, self); return nil; } // Part 4: Search for index page if path is pointing to a directory if (!allowDirectory) { BOOL isDir = NO; if ([[NSFileManager defaultManager] fileExistsAtPath:fullPath isDirectory:&isDir] && isDir) { NSArray *indexFileNames = [self directoryIndexFileNames]; for (NSString *indexFileName in indexFileNames) { NSString *indexFilePath = [fullPath stringByAppendingPathComponent:indexFileName]; if ([[NSFileManager defaultManager] fileExistsAtPath:indexFilePath isDirectory:&isDir] && !isDir) { return indexFilePath; } } // No matching index files found in directory return nil; } } return fullPath; } /** * This method is called to get a response for a request. * You may return any object that adopts the HTTPResponse protocol. * The HTTPServer comes with two such classes: HTTPFileResponse and HTTPDataResponse. * HTTPFileResponse is a wrapper for an NSFileHandle object, and is the preferred way to send a file response. * HTTPDataResponse is a wrapper for an NSData object, and may be used to send a custom response. **/ - (NSObject *)httpResponseForMethod:(NSString *)method URI:(NSString *)path { HTTPLogTrace(); // Override me to provide custom responses. NSString *filePath = [self filePathForURI:path allowDirectory:NO]; BOOL isDir = NO; if (filePath && [[NSFileManager defaultManager] fileExistsAtPath:filePath isDirectory:&isDir] && !isDir) { return [[HTTPFileResponse alloc] initWithFilePath:filePath forConnection:self]; // Use me instead for asynchronous file IO. // Generally better for larger files. // return [[[HTTPAsyncFileResponse alloc] initWithFilePath:filePath forConnection:self] autorelease]; } return nil; } - (WebSocket *)webSocketForURI:(NSString *)path { HTTPLogTrace(); // Override me to provide custom WebSocket responses. // To do so, simply override the base WebSocket implementation, and add your custom functionality. // Then return an instance of your custom WebSocket here. // // For example: // // if ([path isEqualToString:@"/myAwesomeWebSocketStream"]) // { // return [[[MyWebSocket alloc] initWithRequest:request socket:asyncSocket] autorelease]; // } // // return [super webSocketForURI:path]; return nil; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark Uploads //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /** * This method is called after receiving all HTTP headers, but before reading any of the request body. **/ - (void)prepareForBodyWithSize:(UInt64)contentLength { // Override me to allocate buffers, file handles, etc. } /** * This method is called to handle data read from a POST / PUT. * The given data is part of the request body. **/ - (void)processBodyData:(NSData *)postDataChunk { // Override me to do something useful with a POST / PUT. // If the post is small, such as a simple form, you may want to simply append the data to the request. // If the post is big, such as a file upload, you may want to store the file to disk. // // Remember: In order to support LARGE POST uploads, the data is read in chunks. // This prevents a 50 MB upload from being stored in RAM. // The size of the chunks are limited by the POST_CHUNKSIZE definition. // Therefore, this method may be called multiple times for the same POST request. } /** * This method is called after the request body has been fully read but before the HTTP request is processed. **/ - (void)finishBody { // Override me to perform any final operations on an upload. // For example, if you were saving the upload to disk this would be // the hook to flush any pending data to disk and maybe close the file. } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark Errors //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /** * Called if the HTML version is other than what is supported **/ - (void)handleVersionNotSupported:(NSString *)version { // Override me for custom error handling of unsupported http version responses // If you simply want to add a few extra header fields, see the preprocessErrorResponse: method. // You can also use preprocessErrorResponse: to add an optional HTML body. HTTPLogWarn(@"HTTP Server: Error 505 - Version Not Supported: %@ (%@)", version, [self requestURI]); HTTPMessage *response = [[HTTPMessage alloc] initResponseWithStatusCode:505 description:nil version:HTTPVersion1_1]; [response setHeaderField:@"Content-Length" value:@"0"]; NSData *responseData = [self preprocessErrorResponse:response]; [asyncSocket writeData:responseData withTimeout:TIMEOUT_WRITE_ERROR tag:HTTP_RESPONSE]; } /** * Called if the authentication information was required and absent, or if authentication failed. **/ - (void)handleAuthenticationFailed { // Override me for custom handling of authentication challenges // If you simply want to add a few extra header fields, see the preprocessErrorResponse: method. // You can also use preprocessErrorResponse: to add an optional HTML body. HTTPLogInfo(@"HTTP Server: Error 401 - Unauthorized (%@)", [self requestURI]); // Status Code 401 - Unauthorized HTTPMessage *response = [[HTTPMessage alloc] initResponseWithStatusCode:401 description:nil version:HTTPVersion1_1]; [response setHeaderField:@"Content-Length" value:@"0"]; if ([self useDigestAccessAuthentication]) { [self addDigestAuthChallenge:response]; } else { [self addBasicAuthChallenge:response]; } NSData *responseData = [self preprocessErrorResponse:response]; [asyncSocket writeData:responseData withTimeout:TIMEOUT_WRITE_ERROR tag:HTTP_RESPONSE]; } /** * Called if we receive some sort of malformed HTTP request. * The data parameter is the invalid HTTP header line, including CRLF, as read from GCDAsyncSocket. * The data parameter may also be nil if the request as a whole was invalid, such as a POST with no Content-Length. **/ - (void)handleInvalidRequest:(NSData *)data { // Override me for custom error handling of invalid HTTP requests // If you simply want to add a few extra header fields, see the preprocessErrorResponse: method. // You can also use preprocessErrorResponse: to add an optional HTML body. HTTPLogWarn(@"HTTP Server: Error 400 - Bad Request (%@)", [self requestURI]); // Status Code 400 - Bad Request HTTPMessage *response = [[HTTPMessage alloc] initResponseWithStatusCode:400 description:nil version:HTTPVersion1_1]; [response setHeaderField:@"Content-Length" value:@"0"]; [response setHeaderField:@"Connection" value:@"close"]; NSData *responseData = [self preprocessErrorResponse:response]; [asyncSocket writeData:responseData withTimeout:TIMEOUT_WRITE_ERROR tag:HTTP_FINAL_RESPONSE]; // Note: We used the HTTP_FINAL_RESPONSE tag to disconnect after the response is sent. // We do this because we couldn't parse the request, // so we won't be able to recover and move on to another request afterwards. // In other words, we wouldn't know where the first request ends and the second request begins. } /** * Called if we receive a HTTP request with a method other than GET or HEAD. **/ - (void)handleUnknownMethod:(NSString *)method { // Override me for custom error handling of 405 method not allowed responses. // If you simply want to add a few extra header fields, see the preprocessErrorResponse: method. // You can also use preprocessErrorResponse: to add an optional HTML body. // // See also: supportsMethod:atPath: HTTPLogWarn(@"HTTP Server: Error 405 - Method Not Allowed: %@ (%@)", method, [self requestURI]); // Status code 405 - Method Not Allowed HTTPMessage *response = [[HTTPMessage alloc] initResponseWithStatusCode:405 description:nil version:HTTPVersion1_1]; [response setHeaderField:@"Content-Length" value:@"0"]; [response setHeaderField:@"Connection" value:@"close"]; NSData *responseData = [self preprocessErrorResponse:response]; [asyncSocket writeData:responseData withTimeout:TIMEOUT_WRITE_ERROR tag:HTTP_FINAL_RESPONSE]; // Note: We used the HTTP_FINAL_RESPONSE tag to disconnect after the response is sent. // We do this because the method may include an http body. // Since we can't be sure, we should close the connection. } /** * Called if we're unable to find the requested resource. **/ - (void)handleResourceNotFound { // Override me for custom error handling of 404 not found responses // If you simply want to add a few extra header fields, see the preprocessErrorResponse: method. // You can also use preprocessErrorResponse: to add an optional HTML body. HTTPLogInfo(@"HTTP Server: Error 404 - Not Found (%@)", [self requestURI]); // Status Code 404 - Not Found HTTPMessage *response = [[HTTPMessage alloc] initResponseWithStatusCode:404 description:nil version:HTTPVersion1_1]; [response setHeaderField:@"Content-Length" value:@"0"]; NSData *responseData = [self preprocessErrorResponse:response]; [asyncSocket writeData:responseData withTimeout:TIMEOUT_WRITE_ERROR tag:HTTP_RESPONSE]; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark Headers //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /** * Gets the current date and time, formatted properly (according to RFC) for insertion into an HTTP header. **/ - (NSString *)dateAsString:(NSDate *)date { // From Apple's Documentation (Data Formatting Guide -> Date Formatters -> Cache Formatters for Efficiency): // // "Creating a date formatter is not a cheap operation. If you are likely to use a formatter frequently, // it is typically more efficient to cache a single instance than to create and dispose of multiple instances. // One approach is to use a static variable." // // This was discovered to be true in massive form via issue #46: // // "Was doing some performance benchmarking using instruments and httperf. Using this single optimization // I got a 26% speed improvement - from 1000req/sec to 3800req/sec. Not insignificant. // The culprit? Why, NSDateFormatter, of course!" // // Thus, we are using a static NSDateFormatter here. static NSDateFormatter *df; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ // Example: Sun, 06 Nov 1994 08:49:37 GMT df = [[NSDateFormatter alloc] init]; [df setFormatterBehavior:NSDateFormatterBehavior10_4]; [df setTimeZone:[NSTimeZone timeZoneWithAbbreviation:@"GMT"]]; [df setDateFormat:@"EEE, dd MMM y HH:mm:ss 'GMT'"]; [df setLocale:[[NSLocale alloc] initWithLocaleIdentifier:@"en_US"]]; // For some reason, using zzz in the format string produces GMT+00:00 }); return [df stringFromDate:date]; } /** * This method is called immediately prior to sending the response headers. * This method adds standard header fields, and then converts the response to an NSData object. **/ - (NSData *)preprocessResponse:(HTTPMessage *)response { HTTPLogTrace(); // Override me to customize the response headers // You'll likely want to add your own custom headers, and then return [super preprocessResponse:response] // Add standard headers NSString *now = [self dateAsString:[NSDate date]]; [response setHeaderField:@"Date" value:now]; // Add server capability headers [response setHeaderField:@"Accept-Ranges" value:@"bytes"]; // Add optional response headers if ([httpResponse respondsToSelector:@selector(httpHeaders)]) { NSDictionary *responseHeaders = [httpResponse httpHeaders]; NSEnumerator *keyEnumerator = [responseHeaders keyEnumerator]; NSString *key; while ((key = [keyEnumerator nextObject])) { NSString *value = [responseHeaders objectForKey:key]; [response setHeaderField:key value:value]; } } return [response messageData]; } /** * This method is called immediately prior to sending the response headers (for an error). * This method adds standard header fields, and then converts the response to an NSData object. **/ - (NSData *)preprocessErrorResponse:(HTTPMessage *)response { HTTPLogTrace(); // Override me to customize the error response headers // You'll likely want to add your own custom headers, and then return [super preprocessErrorResponse:response] // // Notes: // You can use [response statusCode] to get the type of error. // You can use [response setBody:data] to add an optional HTML body. // If you add a body, don't forget to update the Content-Length. // // if ([response statusCode] == 404) // { // NSString *msg = @"Error 404 - Not Found"; // NSData *msgData = [msg dataUsingEncoding:NSUTF8StringEncoding]; // // [response setBody:msgData]; // // NSString *contentLengthStr = [NSString stringWithFormat:@"%lu", (unsigned long)[msgData length]]; // [response setHeaderField:@"Content-Length" value:contentLengthStr]; // } // Add standard headers NSString *now = [self dateAsString:[NSDate date]]; [response setHeaderField:@"Date" value:now]; // Add server capability headers [response setHeaderField:@"Accept-Ranges" value:@"bytes"]; // Add optional response headers if ([httpResponse respondsToSelector:@selector(httpHeaders)]) { NSDictionary *responseHeaders = [httpResponse httpHeaders]; NSEnumerator *keyEnumerator = [responseHeaders keyEnumerator]; NSString *key; while((key = [keyEnumerator nextObject])) { NSString *value = [responseHeaders objectForKey:key]; [response setHeaderField:key value:value]; } } return [response messageData]; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark GCDAsyncSocket Delegate //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /** * This method is called after the socket has successfully read data from the stream. * Remember that this method will only be called after the socket reaches a CRLF, or after it's read the proper length. **/ - (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData*)data withTag:(long)tag { if (tag == HTTP_REQUEST_HEADER) { // Append the header line to the http message BOOL result = [request appendData:data]; if (!result) { HTTPLogWarn(@"%@[%p]: Malformed request", THIS_FILE, self); [self handleInvalidRequest:data]; } else if (![request isHeaderComplete]) { // We don't have a complete header yet // That is, we haven't yet received a CRLF on a line by itself, indicating the end of the header if (++numHeaderLines > MAX_HEADER_LINES) { // Reached the maximum amount of header lines in a single HTTP request // This could be an attempted DOS attack [asyncSocket disconnect]; // Explictly return to ensure we don't do anything after the socket disconnect return; } else { [asyncSocket readDataToData:[GCDAsyncSocket CRLFData] withTimeout:TIMEOUT_READ_SUBSEQUENT_HEADER_LINE maxLength:MAX_HEADER_LINE_LENGTH tag:HTTP_REQUEST_HEADER]; } } else { // We have an entire HTTP request header from the client // Extract the method (such as GET, HEAD, POST, etc) NSString *method = [request method]; // Extract the uri (such as "/index.html") NSString *uri = [self requestURI]; // Check for a Transfer-Encoding field NSString *transferEncoding = [request headerField:@"Transfer-Encoding"]; // Check for a Content-Length field NSString *contentLength = [request headerField:@"Content-Length"]; // Content-Length MUST be present for upload methods (such as POST or PUT) // and MUST NOT be present for other methods. BOOL expectsUpload = [self expectsRequestBodyFromMethod:method atPath:uri]; if (expectsUpload) { if (transferEncoding && ![transferEncoding caseInsensitiveCompare:@"Chunked"]) { requestContentLength = -1; } else { if (contentLength == nil) { HTTPLogWarn(@"%@[%p]: Method expects request body, but had no specified Content-Length", THIS_FILE, self); [self handleInvalidRequest:nil]; return; } if (![NSNumber parseString:(NSString *)contentLength intoUInt64:&requestContentLength]) { HTTPLogWarn(@"%@[%p]: Unable to parse Content-Length header into a valid number", THIS_FILE, self); [self handleInvalidRequest:nil]; return; } } } else { if (contentLength != nil) { // Received Content-Length header for method not expecting an upload. // This better be zero... if (![NSNumber parseString:(NSString *)contentLength intoUInt64:&requestContentLength]) { HTTPLogWarn(@"%@[%p]: Unable to parse Content-Length header into a valid number", THIS_FILE, self); [self handleInvalidRequest:nil]; return; } if (requestContentLength > 0) { HTTPLogWarn(@"%@[%p]: Method not expecting request body had non-zero Content-Length", THIS_FILE, self); [self handleInvalidRequest:nil]; return; } } requestContentLength = 0; requestContentLengthReceived = 0; } // Check to make sure the given method is supported if (![self supportsMethod:method atPath:uri]) { // The method is unsupported - either in general, or for this specific request // Send a 405 - Method not allowed response [self handleUnknownMethod:method]; return; } if (expectsUpload) { // Reset the total amount of data received for the upload requestContentLengthReceived = 0; // Prepare for the upload [self prepareForBodyWithSize:requestContentLength]; if (requestContentLength > 0) { // Start reading the request body if (requestContentLength == -1) { // Chunked transfer [asyncSocket readDataToData:[GCDAsyncSocket CRLFData] withTimeout:TIMEOUT_READ_BODY maxLength:MAX_CHUNK_LINE_LENGTH tag:HTTP_REQUEST_CHUNK_SIZE]; } else { NSUInteger bytesToRead; if (requestContentLength < POST_CHUNKSIZE) bytesToRead = (NSUInteger)requestContentLength; else bytesToRead = POST_CHUNKSIZE; [asyncSocket readDataToLength:bytesToRead withTimeout:TIMEOUT_READ_BODY tag:HTTP_REQUEST_BODY]; } } else { // Empty upload [self finishBody]; [self replyToHTTPRequest]; } } else { // Now we need to reply to the request [self replyToHTTPRequest]; } } } else { BOOL doneReadingRequest = NO; // A chunked message body contains a series of chunks, // followed by a line with "0" (zero), // followed by optional footers (just like headers), // and a blank line. // // Each chunk consists of two parts: // // 1. A line with the size of the chunk data, in hex, // possibly followed by a semicolon and extra parameters you can ignore (none are currently standard), // and ending with CRLF. // 2. The data itself, followed by CRLF. // // Part 1 is represented by HTTP_REQUEST_CHUNK_SIZE // Part 2 is represented by HTTP_REQUEST_CHUNK_DATA and HTTP_REQUEST_CHUNK_TRAILER // where the trailer is the CRLF that follows the data. // // The optional footers and blank line are represented by HTTP_REQUEST_CHUNK_FOOTER. if (tag == HTTP_REQUEST_CHUNK_SIZE) { // We have just read in a line with the size of the chunk data, in hex, // possibly followed by a semicolon and extra parameters that can be ignored, // and ending with CRLF. NSString *sizeLine = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; errno = 0; // Reset errno before calling strtoull() to ensure it is always zero on success requestChunkSize = (UInt64)strtoull([sizeLine UTF8String], NULL, 16); requestChunkSizeReceived = 0; if (errno != 0) { HTTPLogWarn(@"%@[%p]: Method expects chunk size, but received something else", THIS_FILE, self); [self handleInvalidRequest:nil]; return; } if (requestChunkSize > 0) { NSUInteger bytesToRead; bytesToRead = (requestChunkSize < POST_CHUNKSIZE) ? (NSUInteger)requestChunkSize : POST_CHUNKSIZE; [asyncSocket readDataToLength:bytesToRead withTimeout:TIMEOUT_READ_BODY tag:HTTP_REQUEST_CHUNK_DATA]; } else { // This is the "0" (zero) line, // which is to be followed by optional footers (just like headers) and finally a blank line. [asyncSocket readDataToData:[GCDAsyncSocket CRLFData] withTimeout:TIMEOUT_READ_BODY maxLength:MAX_HEADER_LINE_LENGTH tag:HTTP_REQUEST_CHUNK_FOOTER]; } return; } else if (tag == HTTP_REQUEST_CHUNK_DATA) { // We just read part of the actual data. requestContentLengthReceived += [data length]; requestChunkSizeReceived += [data length]; [self processBodyData:data]; UInt64 bytesLeft = requestChunkSize - requestChunkSizeReceived; if (bytesLeft > 0) { NSUInteger bytesToRead = (bytesLeft < POST_CHUNKSIZE) ? (NSUInteger)bytesLeft : POST_CHUNKSIZE; [asyncSocket readDataToLength:bytesToRead withTimeout:TIMEOUT_READ_BODY tag:HTTP_REQUEST_CHUNK_DATA]; } else { // We've read in all the data for this chunk. // The data is followed by a CRLF, which we need to read (and basically ignore) [asyncSocket readDataToLength:2 withTimeout:TIMEOUT_READ_BODY tag:HTTP_REQUEST_CHUNK_TRAILER]; } return; } else if (tag == HTTP_REQUEST_CHUNK_TRAILER) { // This should be the CRLF following the data. // Just ensure it's a CRLF. if (![data isEqualToData:[GCDAsyncSocket CRLFData]]) { HTTPLogWarn(@"%@[%p]: Method expects chunk trailer, but is missing", THIS_FILE, self); [self handleInvalidRequest:nil]; return; } // Now continue with the next chunk [asyncSocket readDataToData:[GCDAsyncSocket CRLFData] withTimeout:TIMEOUT_READ_BODY maxLength:MAX_CHUNK_LINE_LENGTH tag:HTTP_REQUEST_CHUNK_SIZE]; } else if (tag == HTTP_REQUEST_CHUNK_FOOTER) { if (++numHeaderLines > MAX_HEADER_LINES) { // Reached the maximum amount of header lines in a single HTTP request // This could be an attempted DOS attack [asyncSocket disconnect]; // Explictly return to ensure we don't do anything after the socket disconnect return; } if ([data length] > 2) { // We read in a footer. // In the future we may want to append these to the request. // For now we ignore, and continue reading the footers, waiting for the final blank line. [asyncSocket readDataToData:[GCDAsyncSocket CRLFData] withTimeout:TIMEOUT_READ_BODY maxLength:MAX_HEADER_LINE_LENGTH tag:HTTP_REQUEST_CHUNK_FOOTER]; } else { doneReadingRequest = YES; } } else // HTTP_REQUEST_BODY { // Handle a chunk of data from the POST body requestContentLengthReceived += [data length]; [self processBodyData:data]; if (requestContentLengthReceived < requestContentLength) { // We're not done reading the post body yet... UInt64 bytesLeft = requestContentLength - requestContentLengthReceived; NSUInteger bytesToRead = bytesLeft < POST_CHUNKSIZE ? (NSUInteger)bytesLeft : POST_CHUNKSIZE; [asyncSocket readDataToLength:bytesToRead withTimeout:TIMEOUT_READ_BODY tag:HTTP_REQUEST_BODY]; } else { doneReadingRequest = YES; } } // Now that the entire body has been received, we need to reply to the request if (doneReadingRequest) { [self finishBody]; [self replyToHTTPRequest]; } } } /** * This method is called after the socket has successfully written data to the stream. **/ - (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag { BOOL doneSendingResponse = NO; if (tag == HTTP_PARTIAL_RESPONSE_BODY) { // Update the amount of data we have in asyncSocket's write queue [responseDataSizes removeObjectAtIndex:0]; // We only wrote a part of the response - there may be more [self continueSendingStandardResponseBody]; } else if (tag == HTTP_CHUNKED_RESPONSE_BODY) { // Update the amount of data we have in asyncSocket's write queue. // This will allow asynchronous responses to continue sending more data. [responseDataSizes removeObjectAtIndex:0]; // Don't continue sending the response yet. // The chunked footer that was sent after the body will tell us if we have more data to send. } else if (tag == HTTP_CHUNKED_RESPONSE_FOOTER) { // Normal chunked footer indicating we have more data to send (non final footer). [self continueSendingStandardResponseBody]; } else if (tag == HTTP_PARTIAL_RANGE_RESPONSE_BODY) { // Update the amount of data we have in asyncSocket's write queue [responseDataSizes removeObjectAtIndex:0]; // We only wrote a part of the range - there may be more [self continueSendingSingleRangeResponseBody]; } else if (tag == HTTP_PARTIAL_RANGES_RESPONSE_BODY) { // Update the amount of data we have in asyncSocket's write queue [responseDataSizes removeObjectAtIndex:0]; // We only wrote part of the range - there may be more, or there may be more ranges [self continueSendingMultiRangeResponseBody]; } else if (tag == HTTP_RESPONSE || tag == HTTP_FINAL_RESPONSE) { // Update the amount of data we have in asyncSocket's write queue if ([responseDataSizes count] > 0) { [responseDataSizes removeObjectAtIndex:0]; } doneSendingResponse = YES; } if (doneSendingResponse) { // Inform the http response that we're done if ([httpResponse respondsToSelector:@selector(connectionDidClose)]) { [httpResponse connectionDidClose]; } if (tag == HTTP_FINAL_RESPONSE) { // Cleanup after the last request [self finishResponse]; // Terminate the connection [asyncSocket disconnect]; // Explictly return to ensure we don't do anything after the socket disconnects return; } else { if ([self shouldDie]) { // Cleanup after the last request // Note: Don't do this before calling shouldDie, as it needs the request object still. [self finishResponse]; // The only time we should invoke [self die] is from socketDidDisconnect, // or if the socket gets taken over by someone else like a WebSocket. [asyncSocket disconnect]; } else { // Cleanup after the last request [self finishResponse]; // Prepare for the next request // If this assertion fails, it likely means you overrode the // finishBody method and forgot to call [super finishBody]. NSAssert(request == nil, @"Request not properly released in finishBody"); request = [[HTTPMessage alloc] initEmptyRequest]; numHeaderLines = 0; sentResponseHeaders = NO; // And start listening for more requests [self startReadingRequest]; } } } } /** * Sent after the socket has been disconnected. **/ - (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err { HTTPLogTrace(); asyncSocket = nil; [self die]; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark HTTPResponse Notifications //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /** * This method may be called by asynchronous HTTPResponse objects. * That is, HTTPResponse objects that return YES in their "- (BOOL)isAsynchronous" method. * * This informs us that the response object has generated more data that we may be able to send. **/ - (void)responseHasAvailableData:(NSObject *)sender { HTTPLogTrace(); // We always dispatch this asynchronously onto our connectionQueue, // even if the connectionQueue is the current queue. // // We do this to give the HTTPResponse classes the flexibility to call // this method whenever they want, even from within a readDataOfLength method. dispatch_async(connectionQueue, ^{ @autoreleasepool { if (sender != httpResponse) { HTTPLogWarn(@"%@[%p]: %@ - Sender is not current httpResponse", THIS_FILE, self, THIS_METHOD); return; } if (!sentResponseHeaders) { [self sendResponseHeadersAndBody]; } else { if (ranges == nil) { [self continueSendingStandardResponseBody]; } else { if ([ranges count] == 1) [self continueSendingSingleRangeResponseBody]; else [self continueSendingMultiRangeResponseBody]; } } }}); } /** * This method is called if the response encounters some critical error, * and it will be unable to fullfill the request. **/ - (void)responseDidAbort:(NSObject *)sender { HTTPLogTrace(); // We always dispatch this asynchronously onto our connectionQueue, // even if the connectionQueue is the current queue. // // We do this to give the HTTPResponse classes the flexibility to call // this method whenever they want, even from within a readDataOfLength method. dispatch_async(connectionQueue, ^{ @autoreleasepool { if (sender != httpResponse) { HTTPLogWarn(@"%@[%p]: %@ - Sender is not current httpResponse", THIS_FILE, self, THIS_METHOD); return; } [asyncSocket disconnectAfterWriting]; }}); } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark Post Request //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /** * This method is called after each response has been fully sent. * Since a single connection may handle multiple request/responses, this method may be called multiple times. * That is, it will be called after completion of each response. **/ - (void)finishResponse { HTTPLogTrace(); // Override me if you want to perform any custom actions after a response has been fully sent. // This is the place to release memory or resources associated with the last request. // // If you override this method, you should take care to invoke [super finishResponse] at some point. request = nil; httpResponse = nil; ranges = nil; ranges_headers = nil; ranges_boundry = nil; } /** * This method is called after each successful response has been fully sent. * It determines whether the connection should stay open and handle another request. **/ - (BOOL)shouldDie { HTTPLogTrace(); // Override me if you have any need to force close the connection. // You may do so by simply returning YES. // // If you override this method, you should take care to fall through with [super shouldDie] // instead of returning NO. BOOL shouldDie = NO; NSString *version = [request version]; if ([version isEqualToString:HTTPVersion1_1]) { // HTTP version 1.1 // Connection should only be closed if request included "Connection: close" header NSString *connection = [request headerField:@"Connection"]; shouldDie = (connection && ([connection caseInsensitiveCompare:@"close"] == NSOrderedSame)); } else if ([version isEqualToString:HTTPVersion1_0]) { // HTTP version 1.0 // Connection should be closed unless request included "Connection: Keep-Alive" header NSString *connection = [request headerField:@"Connection"]; if (connection == nil) shouldDie = YES; else shouldDie = [connection caseInsensitiveCompare:@"Keep-Alive"] != NSOrderedSame; } return shouldDie; } - (void)die { HTTPLogTrace(); // Override me if you want to perform any custom actions when a connection is closed. // Then call [super die] when you're done. // // See also the finishResponse method. // // Important: There is a rare timing condition where this method might get invoked twice. // If you override this method, you should be prepared for this situation. // Inform the http response that we're done if ([httpResponse respondsToSelector:@selector(connectionDidClose)]) { [httpResponse connectionDidClose]; } // Release the http response so we don't call it's connectionDidClose method again in our dealloc method httpResponse = nil; // Post notification of dead connection // This will allow our server to release us from its array of connections [[NSNotificationCenter defaultCenter] postNotificationName:HTTPConnectionDidDieNotification object:self]; } @end //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @implementation HTTPConfig @synthesize server; @synthesize documentRoot; @synthesize queue; - (id)initWithServer:(HTTPServer *)aServer documentRoot:(NSString *)aDocumentRoot { if ((self = [super init])) { server = aServer; documentRoot = aDocumentRoot; } return self; } - (id)initWithServer:(HTTPServer *)aServer documentRoot:(NSString *)aDocumentRoot queue:(dispatch_queue_t)q { if ((self = [super init])) { server = aServer; documentRoot = [aDocumentRoot stringByStandardizingPath]; if ([documentRoot hasSuffix:@"/"]) { documentRoot = [documentRoot stringByAppendingString:@"/"]; } if (q) { queue = q; #if NEEDS_DISPATCH_RETAIN_RELEASE dispatch_retain(queue); #endif } } return self; } - (void)dealloc { #if NEEDS_DISPATCH_RETAIN_RELEASE if (queue) dispatch_release(queue); #endif } @end ================================================ FILE: zhuishushenqi/Vendor/CocoaHTTPServer/Core/HTTPLogging.h ================================================ /** * In order to provide fast and flexible logging, this project uses Cocoa Lumberjack. * * The Google Code page has a wealth of documentation if you have any questions. * https://github.com/robbiehanson/CocoaLumberjack * * Here's what you need to know concerning how logging is setup for CocoaHTTPServer: * * There are 4 log levels: * - Error * - Warning * - Info * - Verbose * * In addition to this, there is a Trace flag that can be enabled. * When tracing is enabled, it spits out the methods that are being called. * * Please note that tracing is separate from the log levels. * For example, one could set the log level to warning, and enable tracing. * * All logging is asynchronous, except errors. * To use logging within your own custom files, follow the steps below. * * Step 1: * Import this header in your implementation file: * * #import "HTTPLogging.h" * * Step 2: * Define your logging level in your implementation file: * * // Log levels: off, error, warn, info, verbose * static const int httpLogLevel = HTTP_LOG_LEVEL_VERBOSE; * * If you wish to enable tracing, you could do something like this: * * // Debug levels: off, error, warn, info, verbose * static const int httpLogLevel = HTTP_LOG_LEVEL_INFO | HTTP_LOG_FLAG_TRACE; * * Step 3: * Replace your NSLog statements with HTTPLog statements according to the severity of the message. * * NSLog(@"Fatal error, no dohickey found!"); -> HTTPLogError(@"Fatal error, no dohickey found!"); * * HTTPLog works exactly the same as NSLog. * This means you can pass it multiple variables just like NSLog. **/ #import // Define logging context for every log message coming from the HTTP server. // The logging context can be extracted from the DDLogMessage from within the logging framework, // which gives loggers, formatters, and filters the ability to optionally process them differently. #define HTTP_LOG_OBJC_MAYBE(async, lvl, flg, ctx, frmt, ...) \ do{ if(HTTP_LOG_ASYNC_ENABLED) LOG_MAYBE(async, lvl, flg, ctx, nil, sel_getName(_cmd), frmt, ##__VA_ARGS__); } while(0) #define HTTP_HTTP_LOG_C_MAYBE(async, lvl, flg, ctx, frmt, ...) \ do{ if(HTTP_LOG_ASYNC_ENABLED) LOG_MAYBE(async, lvl, flg, ctx, nil, __FUNCTION__, frmt, ##__VA_ARGS__); } while(0) #define HTTP_LOG_CONTEXT 80 // Configure log levels. #define HTTP_LOG_FLAG_ERROR (1 << 0) // 0...00001 #define HTTP_LOG_FLAG_WARN (1 << 1) // 0...00010 #define HTTP_LOG_FLAG_INFO (1 << 2) // 0...00100 #define HTTP_LOG_FLAG_VERBOSE (1 << 3) // 0...01000 #define HTTP_LOG_LEVEL_OFF 0 // 0...00000 #define HTTP_LOG_LEVEL_ERROR (HTTP_LOG_LEVEL_OFF | HTTP_LOG_FLAG_ERROR) // 0...00001 #define HTTP_LOG_LEVEL_WARN (HTTP_LOG_LEVEL_ERROR | HTTP_LOG_FLAG_WARN) // 0...00011 #define HTTP_LOG_LEVEL_INFO (HTTP_LOG_LEVEL_WARN | HTTP_LOG_FLAG_INFO) // 0...00111 #define HTTP_LOG_LEVEL_VERBOSE (HTTP_LOG_LEVEL_INFO | HTTP_LOG_FLAG_VERBOSE) // 0...01111 // Setup fine grained logging. // The first 4 bits are being used by the standard log levels (0 - 3) // // We're going to add tracing, but NOT as a log level. // Tracing can be turned on and off independently of log level. #define HTTP_LOG_FLAG_TRACE (1 << 4) // 0...10000 // Setup the usual boolean macros. #define HTTP_LOG_ERROR (httpLogLevel & HTTP_LOG_FLAG_ERROR) #define HTTP_LOG_WARN (httpLogLevel & HTTP_LOG_FLAG_WARN) #define HTTP_LOG_INFO (httpLogLevel & HTTP_LOG_FLAG_INFO) #define HTTP_LOG_VERBOSE (httpLogLevel & HTTP_LOG_FLAG_VERBOSE) #define HTTP_LOG_TRACE (httpLogLevel & HTTP_LOG_FLAG_TRACE) // Configure asynchronous logging. // We follow the default configuration, // but we reserve a special macro to easily disable asynchronous logging for debugging purposes. #define HTTP_LOG_ASYNC_ENABLED YES #define HTTP_LOG_ASYNC_ERROR ( NO && HTTP_LOG_ASYNC_ENABLED) #define HTTP_LOG_ASYNC_WARN (YES && HTTP_LOG_ASYNC_ENABLED) #define HTTP_LOG_ASYNC_INFO (YES && HTTP_LOG_ASYNC_ENABLED) #define HTTP_LOG_ASYNC_VERBOSE (YES && HTTP_LOG_ASYNC_ENABLED) #define HTTP_LOG_ASYNC_TRACE (YES && HTTP_LOG_ASYNC_ENABLED) // Define logging primitives. #define HTTPLogError(frmt, ...) HTTP_LOG_OBJC_MAYBE(HTTP_LOG_ASYNC_ERROR, httpLogLevel, HTTP_LOG_FLAG_ERROR, \ HTTP_LOG_CONTEXT, frmt, ##__VA_ARGS__) #define HTTPLogWarn(frmt, ...) HTTP_LOG_OBJC_MAYBE(HTTP_LOG_ASYNC_WARN, httpLogLevel, HTTP_LOG_FLAG_WARN, \ HTTP_LOG_CONTEXT, frmt, ##__VA_ARGS__) #define HTTPLogInfo(frmt, ...) HTTP_LOG_OBJC_MAYBE(HTTP_LOG_ASYNC_INFO, httpLogLevel, HTTP_LOG_FLAG_INFO, \ HTTP_LOG_CONTEXT, frmt, ##__VA_ARGS__) #define HTTPLogVerbose(frmt, ...) HTTP_LOG_OBJC_MAYBE(HTTP_LOG_ASYNC_VERBOSE, httpLogLevel, HTTP_LOG_FLAG_VERBOSE, \ HTTP_LOG_CONTEXT, frmt, ##__VA_ARGS__) #define HTTPLogTrace() HTTP_LOG_OBJC_MAYBE(HTTP_LOG_ASYNC_TRACE, httpLogLevel, HTTP_LOG_FLAG_TRACE, \ HTTP_LOG_CONTEXT, @"%@[%p]: %@", THIS_FILE, self, THIS_METHOD) #define HTTPLogTrace2(frmt, ...) HTTP_LOG_OBJC_MAYBE(HTTP_LOG_ASYNC_TRACE, httpLogLevel, HTTP_LOG_FLAG_TRACE, \ HTTP_LOG_CONTEXT, frmt, ##__VA_ARGS__) #define HTTPLogCError(frmt, ...) HTTP_LOG_C_MAYBE(HTTP_LOG_ASYNC_ERROR, httpLogLevel, HTTP_LOG_FLAG_ERROR, \ HTTP_LOG_CONTEXT, frmt, ##__VA_ARGS__) #define HTTPLogCWarn(frmt, ...) HTTP_LOG_C_MAYBE(HTTP_LOG_ASYNC_WARN, httpLogLevel, HTTP_LOG_FLAG_WARN, \ HTTP_LOG_CONTEXT, frmt, ##__VA_ARGS__) #define HTTPLogCInfo(frmt, ...) HTTP_LOG_C_MAYBE(HTTP_LOG_ASYNC_INFO, httpLogLevel, HTTP_LOG_FLAG_INFO, \ HTTP_LOG_CONTEXT, frmt, ##__VA_ARGS__) #define HTTPLogCVerbose(frmt, ...) HTTP_LOG_C_MAYBE(HTTP_LOG_ASYNC_VERBOSE, httpLogLevel, HTTP_LOG_FLAG_VERBOSE, \ HTTP_LOG_CONTEXT, frmt, ##__VA_ARGS__) #define HTTPLogCTrace() HTTP_LOG_C_MAYBE(HTTP_LOG_ASYNC_TRACE, httpLogLevel, HTTP_LOG_FLAG_TRACE, \ HTTP_LOG_CONTEXT, @"%@[%p]: %@", THIS_FILE, self, __FUNCTION__) #define HTTPLogCTrace2(frmt, ...) HTTP_LOG_C_MAYBE(HTTP_LOG_ASYNC_TRACE, httpLogLevel, HTTP_LOG_FLAG_TRACE, \ HTTP_LOG_CONTEXT, frmt, ##__VA_ARGS__) ================================================ FILE: zhuishushenqi/Vendor/CocoaHTTPServer/Core/HTTPMessage.h ================================================ /** * The HTTPMessage class is a simple Objective-C wrapper around Apple's CFHTTPMessage class. **/ #import #if TARGET_OS_IPHONE // Note: You may need to add the CFNetwork Framework to your project #import #endif #define HTTPVersion1_0 ((NSString *)kCFHTTPVersion1_0) #define HTTPVersion1_1 ((NSString *)kCFHTTPVersion1_1) @interface HTTPMessage : NSObject { CFHTTPMessageRef message; } - (id)initEmptyRequest; - (id)initRequestWithMethod:(NSString *)method URL:(NSURL *)url version:(NSString *)version; - (id)initResponseWithStatusCode:(NSInteger)code description:(NSString *)description version:(NSString *)version; - (BOOL)appendData:(NSData *)data; - (BOOL)isHeaderComplete; - (NSString *)version; - (NSString *)method; - (NSURL *)url; - (NSInteger)statusCode; - (NSDictionary *)allHeaderFields; - (NSString *)headerField:(NSString *)headerField; - (void)setHeaderField:(NSString *)headerField value:(NSString *)headerFieldValue; - (NSData *)messageData; - (NSData *)body; - (void)setBody:(NSData *)body; @end ================================================ FILE: zhuishushenqi/Vendor/CocoaHTTPServer/Core/HTTPMessage.m ================================================ #import "HTTPMessage.h" #if ! __has_feature(objc_arc) #warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). #endif @implementation HTTPMessage - (id)initEmptyRequest { if ((self = [super init])) { message = CFHTTPMessageCreateEmpty(NULL, YES); } return self; } - (id)initRequestWithMethod:(NSString *)method URL:(NSURL *)url version:(NSString *)version { if ((self = [super init])) { message = CFHTTPMessageCreateRequest(NULL, (__bridge CFStringRef)method, (__bridge CFURLRef)url, (__bridge CFStringRef)version); } return self; } - (id)initResponseWithStatusCode:(NSInteger)code description:(NSString *)description version:(NSString *)version { if ((self = [super init])) { message = CFHTTPMessageCreateResponse(NULL, (CFIndex)code, (__bridge CFStringRef)description, (__bridge CFStringRef)version); } return self; } - (void)dealloc { if (message) { CFRelease(message); } } - (BOOL)appendData:(NSData *)data { return CFHTTPMessageAppendBytes(message, [data bytes], [data length]); } - (BOOL)isHeaderComplete { return CFHTTPMessageIsHeaderComplete(message); } - (NSString *)version { return (__bridge_transfer NSString *)CFHTTPMessageCopyVersion(message); } - (NSString *)method { return (__bridge_transfer NSString *)CFHTTPMessageCopyRequestMethod(message); } - (NSURL *)url { return (__bridge_transfer NSURL *)CFHTTPMessageCopyRequestURL(message); } - (NSInteger)statusCode { return (NSInteger)CFHTTPMessageGetResponseStatusCode(message); } - (NSDictionary *)allHeaderFields { return (__bridge_transfer NSDictionary *)CFHTTPMessageCopyAllHeaderFields(message); } - (NSString *)headerField:(NSString *)headerField { return (__bridge_transfer NSString *)CFHTTPMessageCopyHeaderFieldValue(message, (__bridge CFStringRef)headerField); } - (void)setHeaderField:(NSString *)headerField value:(NSString *)headerFieldValue { CFHTTPMessageSetHeaderFieldValue(message, (__bridge CFStringRef)headerField, (__bridge CFStringRef)headerFieldValue); } - (NSData *)messageData { return (__bridge_transfer NSData *)CFHTTPMessageCopySerializedMessage(message); } - (NSData *)body { return (__bridge_transfer NSData *)CFHTTPMessageCopyBody(message); } - (void)setBody:(NSData *)body { CFHTTPMessageSetBody(message, (__bridge CFDataRef)body); } @end ================================================ FILE: zhuishushenqi/Vendor/CocoaHTTPServer/Core/HTTPResponse.h ================================================ #import @protocol HTTPResponse /** * Returns the length of the data in bytes. * If you don't know the length in advance, implement the isChunked method and have it return YES. **/ - (UInt64)contentLength; /** * The HTTP server supports range requests in order to allow things like * file download resumption and optimized streaming on mobile devices. **/ - (UInt64)offset; - (void)setOffset:(UInt64)offset; /** * Returns the data for the response. * You do not have to return data of the exact length that is given. * You may optionally return data of a lesser length. * However, you must never return data of a greater length than requested. * Doing so could disrupt proper support for range requests. * * To support asynchronous responses, read the discussion at the bottom of this header. **/ - (NSData *)readDataOfLength:(NSUInteger)length; /** * Should only return YES after the HTTPConnection has read all available data. * That is, all data for the response has been returned to the HTTPConnection via the readDataOfLength method. **/ - (BOOL)isDone; @optional /** * If you need time to calculate any part of the HTTP response headers (status code or header fields), * this method allows you to delay sending the headers so that you may asynchronously execute the calculations. * Simply implement this method and return YES until you have everything you need concerning the headers. * * This method ties into the asynchronous response architecture of the HTTPConnection. * You should read the full discussion at the bottom of this header. * * If you return YES from this method, * the HTTPConnection will wait for you to invoke the responseHasAvailableData method. * After you do, the HTTPConnection will again invoke this method to see if the response is ready to send the headers. * * You should only delay sending the headers until you have everything you need concerning just the headers. * Asynchronously generating the body of the response is not an excuse to delay sending the headers. * Instead you should tie into the asynchronous response architecture, and use techniques such as the isChunked method. * * Important: You should read the discussion at the bottom of this header. **/ - (BOOL)delayResponseHeaders; /** * Status code for response. * Allows for responses such as redirect (301), etc. **/ - (NSInteger)status; /** * If you want to add any extra HTTP headers to the response, * simply return them in a dictionary in this method. **/ - (NSDictionary *)httpHeaders; /** * If you don't know the content-length in advance, * implement this method in your custom response class and return YES. * * Important: You should read the discussion at the bottom of this header. **/ - (BOOL)isChunked; /** * This method is called from the HTTPConnection class when the connection is closed, * or when the connection is finished with the response. * If your response is asynchronous, you should implement this method so you know not to * invoke any methods on the HTTPConnection after this method is called (as the connection may be deallocated). **/ - (void)connectionDidClose; @end /** * Important notice to those implementing custom asynchronous and/or chunked responses: * * HTTPConnection supports asynchronous responses. All you have to do in your custom response class is * asynchronously generate the response, and invoke HTTPConnection's responseHasAvailableData method. * You don't have to wait until you have all of the response ready to invoke this method. For example, if you * generate the response in incremental chunks, you could call responseHasAvailableData after generating * each chunk. Please see the HTTPAsyncFileResponse class for an example of how to do this. * * The normal flow of events for an HTTPConnection while responding to a request is like this: * - Send http resopnse headers * - Get data from response via readDataOfLength method. * - Add data to asyncSocket's write queue. * - Wait for asyncSocket to notify it that the data has been sent. * - Get more data from response via readDataOfLength method. * - ... continue this cycle until the entire response has been sent. * * With an asynchronous response, the flow is a little different. * * First the HTTPResponse is given the opportunity to postpone sending the HTTP response headers. * This allows the response to asynchronously execute any code needed to calculate a part of the header. * An example might be the response needs to generate some custom header fields, * or perhaps the response needs to look for a resource on network-attached storage. * Since the network-attached storage may be slow, the response doesn't know whether to send a 200 or 404 yet. * In situations such as this, the HTTPResponse simply implements the delayResponseHeaders method and returns YES. * After returning YES from this method, the HTTPConnection will wait until the response invokes its * responseHasAvailableData method. After this occurs, the HTTPConnection will again query the delayResponseHeaders * method to see if the response is ready to send the headers. * This cycle will continue until the delayResponseHeaders method returns NO. * * You should only delay sending the response headers until you have everything you need concerning just the headers. * Asynchronously generating the body of the response is not an excuse to delay sending the headers. * * After the response headers have been sent, the HTTPConnection calls your readDataOfLength method. * You may or may not have any available data at this point. If you don't, then simply return nil. * You should later invoke HTTPConnection's responseHasAvailableData when you have data to send. * * You don't have to keep track of when you return nil in the readDataOfLength method, or how many times you've invoked * responseHasAvailableData. Just simply call responseHasAvailableData whenever you've generated new data, and * return nil in your readDataOfLength whenever you don't have any available data in the requested range. * HTTPConnection will automatically detect when it should be requesting new data and will act appropriately. * * It's important that you also keep in mind that the HTTP server supports range requests. * The setOffset method is mandatory, and should not be ignored. * Make sure you take into account the offset within the readDataOfLength method. * You should also be aware that the HTTPConnection automatically sorts any range requests. * So if your setOffset method is called with a value of 100, then you can safely release bytes 0-99. * * HTTPConnection can also help you keep your memory footprint small. * Imagine you're dynamically generating a 10 MB response. You probably don't want to load all this data into * RAM, and sit around waiting for HTTPConnection to slowly send it out over the network. All you need to do * is pay attention to when HTTPConnection requests more data via readDataOfLength. This is because HTTPConnection * will never allow asyncSocket's write queue to get much bigger than READ_CHUNKSIZE bytes. You should * consider how you might be able to take advantage of this fact to generate your asynchronous response on demand, * while at the same time keeping your memory footprint small, and your application lightning fast. * * If you don't know the content-length in advanced, you should also implement the isChunked method. * This means the response will not include a Content-Length header, and will instead use "Transfer-Encoding: chunked". * There's a good chance that if your response is asynchronous and dynamic, it's also chunked. * If your response is chunked, you don't need to worry about range requests. **/ ================================================ FILE: zhuishushenqi/Vendor/CocoaHTTPServer/Core/HTTPServer.h ================================================ #import @class GCDAsyncSocket; @class WebSocket; #if TARGET_OS_IPHONE #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 40000 // iPhone 4.0 #define IMPLEMENTED_PROTOCOLS #else #define IMPLEMENTED_PROTOCOLS #endif #else #if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060 // Mac OS X 10.6 #define IMPLEMENTED_PROTOCOLS #else #define IMPLEMENTED_PROTOCOLS #endif #endif @interface HTTPServer : NSObject IMPLEMENTED_PROTOCOLS { // Underlying asynchronous TCP/IP socket dispatch_queue_t serverQueue; dispatch_queue_t connectionQueue; GCDAsyncSocket *asyncSocket; // HTTP server configuration NSString *documentRoot; Class connectionClass; NSString *interface; UInt16 port; // NSNetService and related variables NSNetService *netService; NSString *domain; NSString *type; NSString *name; NSString *publishedName; NSDictionary *txtRecordDictionary; // Connection management NSMutableArray *connections; NSMutableArray *webSockets; NSLock *connectionsLock; NSLock *webSocketsLock; BOOL isRunning; } /** * Specifies the document root to serve files from. * For example, if you set this to "/Users//Sites", * then it will serve files out of the local Sites directory (including subdirectories). * * The default value is nil. * The default server configuration will not serve any files until this is set. * * If you change the documentRoot while the server is running, * the change will affect future incoming http connections. **/ - (NSString *)documentRoot; - (void)setDocumentRoot:(NSString *)value; /** * The connection class is the class used to handle incoming HTTP connections. * * The default value is [HTTPConnection class]. * You can override HTTPConnection, and then set this to [MyHTTPConnection class]. * * If you change the connectionClass while the server is running, * the change will affect future incoming http connections. **/ - (Class)connectionClass; - (void)setConnectionClass:(Class)value; /** * Set what interface you'd like the server to listen on. * By default this is nil, which causes the server to listen on all available interfaces like en1, wifi etc. * * The interface may be specified by name (e.g. "en1" or "lo0") or by IP address (e.g. "192.168.4.34"). * You may also use the special strings "localhost" or "loopback" to specify that * the socket only accept connections from the local machine. **/ - (NSString *)interface; - (void)setInterface:(NSString *)value; /** * The port number to run the HTTP server on. * * The default port number is zero, meaning the server will automatically use any available port. * This is the recommended port value, as it avoids possible port conflicts with other applications. * Technologies such as Bonjour can be used to allow other applications to automatically discover the port number. * * Note: As is common on most OS's, you need root privledges to bind to port numbers below 1024. * * You can change the port property while the server is running, but it won't affect the running server. * To actually change the port the server is listening for connections on you'll need to restart the server. * * The listeningPort method will always return the port number the running server is listening for connections on. * If the server is not running this method returns 0. **/ - (UInt16)port; - (UInt16)listeningPort; - (void)setPort:(UInt16)value; /** * Bonjour domain for publishing the service. * The default value is "local.". * * Note: Bonjour publishing requires you set a type. * * If you change the domain property after the bonjour service has already been published (server already started), * you'll need to invoke the republishBonjour method to update the broadcasted bonjour service. **/ - (NSString *)domain; - (void)setDomain:(NSString *)value; /** * Bonjour name for publishing the service. * The default value is "". * * If using an empty string ("") for the service name when registering, * the system will automatically use the "Computer Name". * Using an empty string will also handle name conflicts * by automatically appending a digit to the end of the name. * * Note: Bonjour publishing requires you set a type. * * If you change the name after the bonjour service has already been published (server already started), * you'll need to invoke the republishBonjour method to update the broadcasted bonjour service. * * The publishedName method will always return the actual name that was published via the bonjour service. * If the service is not running this method returns nil. **/ - (NSString *)name; - (NSString *)publishedName; - (void)setName:(NSString *)value; /** * Bonjour type for publishing the service. * The default value is nil. * The service will not be published via bonjour unless the type is set. * * If you wish to publish the service as a traditional HTTP server, you should set the type to be "_http._tcp.". * * If you change the type after the bonjour service has already been published (server already started), * you'll need to invoke the republishBonjour method to update the broadcasted bonjour service. **/ - (NSString *)type; - (void)setType:(NSString *)value; /** * Republishes the service via bonjour if the server is running. * If the service was not previously published, this method will publish it (if the server is running). **/ - (void)republishBonjour; /** * **/ - (NSDictionary *)TXTRecordDictionary; - (void)setTXTRecordDictionary:(NSDictionary *)dict; /** * Attempts to starts the server on the configured port, interface, etc. * * If an error occurs, this method returns NO and sets the errPtr (if given). * Otherwise returns YES on success. * * Some examples of errors that might occur: * - You specified the server listen on a port which is already in use by another application. * - You specified the server listen on a port number below 1024, which requires root priviledges. * * Code Example: * * NSError *err = nil; * if (![httpServer start:&err]) * { * NSLog(@"Error starting http server: %@", err); * } **/ - (BOOL)start:(NSError **)errPtr; /** * Stops the server, preventing it from accepting any new connections. * You may specify whether or not you want to close the existing client connections. * * The default stop method (with no arguments) will close any existing connections. (It invokes [self stop:NO]) **/ - (void)stop; - (void)stop:(BOOL)keepExistingConnections; - (BOOL)isRunning; - (void)addWebSocket:(WebSocket *)ws; - (NSUInteger)numberOfHTTPConnections; - (NSUInteger)numberOfWebSocketConnections; @end ================================================ FILE: zhuishushenqi/Vendor/CocoaHTTPServer/Core/HTTPServer.m ================================================ #import "HTTPServer.h" #import "GCDAsyncSocket.h" #import "HTTPConnection.h" #import "WebSocket.h" #import "HTTPLogging.h" #if ! __has_feature(objc_arc) #warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). #endif // Does ARC support support GCD objects? // It does if the minimum deployment target is iOS 6+ or Mac OS X 8+ #if TARGET_OS_IPHONE // Compiling for iOS #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 60000 // iOS 6.0 or later #define NEEDS_DISPATCH_RETAIN_RELEASE 0 #else // iOS 5.X or earlier #define NEEDS_DISPATCH_RETAIN_RELEASE 1 #endif #else // Compiling for Mac OS X #if MAC_OS_X_VERSION_MIN_REQUIRED >= 1080 // Mac OS X 10.8 or later #define NEEDS_DISPATCH_RETAIN_RELEASE 0 #else #define NEEDS_DISPATCH_RETAIN_RELEASE 1 // Mac OS X 10.7 or earlier #endif #endif // Log levels: off, error, warn, info, verbose // Other flags: trace static const int httpLogLevel = HTTP_LOG_LEVEL_INFO; // | HTTP_LOG_FLAG_TRACE; @interface HTTPServer (PrivateAPI) - (void)unpublishBonjour; - (void)publishBonjour; + (void)startBonjourThreadIfNeeded; + (void)performBonjourBlock:(dispatch_block_t)block; @end //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @implementation HTTPServer /** * Standard Constructor. * Instantiates an HTTP server, but does not start it. **/ - (id)init { if ((self = [super init])) { HTTPLogTrace(); // Initialize underlying dispatch queue and GCD based tcp socket serverQueue = dispatch_queue_create("HTTPServer", NULL); asyncSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:serverQueue]; // Use default connection class of HTTPConnection connectionQueue = dispatch_queue_create("HTTPConnection", NULL); connectionClass = [HTTPConnection self]; // By default bind on all available interfaces, en1, wifi etc interface = nil; // Use a default port of 0 // This will allow the kernel to automatically pick an open port for us port = 0; // Configure default values for bonjour service // Bonjour domain. Use the local domain by default domain = @"local."; // If using an empty string ("") for the service name when registering, // the system will automatically use the "Computer Name". // Passing in an empty string will also handle name conflicts // by automatically appending a digit to the end of the name. name = @""; // Initialize arrays to hold all the HTTP and webSocket connections connections = [[NSMutableArray alloc] init]; webSockets = [[NSMutableArray alloc] init]; connectionsLock = [[NSLock alloc] init]; webSocketsLock = [[NSLock alloc] init]; // Register for notifications of closed connections [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(connectionDidDie:) name:HTTPConnectionDidDieNotification object:nil]; // Register for notifications of closed websocket connections [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(webSocketDidDie:) name:WebSocketDidDieNotification object:nil]; isRunning = NO; } return self; } /** * Standard Deconstructor. * Stops the server, and clients, and releases any resources connected with this instance. **/ - (void)dealloc { HTTPLogTrace(); // Remove notification observer [[NSNotificationCenter defaultCenter] removeObserver:self]; // Stop the server if it's running [self stop]; // Release all instance variables #if NEEDS_DISPATCH_RETAIN_RELEASE dispatch_release(serverQueue); dispatch_release(connectionQueue); #endif [asyncSocket setDelegate:nil delegateQueue:NULL]; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark Server Configuration //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /** * The document root is filesystem root for the webserver. * Thus requests for /index.html will be referencing the index.html file within the document root directory. * All file requests are relative to this document root. **/ - (NSString *)documentRoot { __block NSString *result; dispatch_sync(serverQueue, ^{ result = documentRoot; }); return result; } - (void)setDocumentRoot:(NSString *)value { HTTPLogTrace(); // Document root used to be of type NSURL. // Add type checking for early warning to developers upgrading from older versions. if (value && ![value isKindOfClass:[NSString class]]) { HTTPLogWarn(@"%@: %@ - Expecting NSString parameter, received %@ parameter", THIS_FILE, THIS_METHOD, NSStringFromClass([value class])); return; } NSString *valueCopy = [value copy]; dispatch_async(serverQueue, ^{ documentRoot = valueCopy; }); } /** * The connection class is the class that will be used to handle connections. * That is, when a new connection is created, an instance of this class will be intialized. * The default connection class is HTTPConnection. * If you use a different connection class, it is assumed that the class extends HTTPConnection **/ - (Class)connectionClass { __block Class result; dispatch_sync(serverQueue, ^{ result = connectionClass; }); return result; } - (void)setConnectionClass:(Class)value { HTTPLogTrace(); dispatch_async(serverQueue, ^{ connectionClass = value; }); } /** * What interface to bind the listening socket to. **/ - (NSString *)interface { __block NSString *result; dispatch_sync(serverQueue, ^{ result = interface; }); return result; } - (void)setInterface:(NSString *)value { NSString *valueCopy = [value copy]; dispatch_async(serverQueue, ^{ interface = valueCopy; }); } /** * The port to listen for connections on. * By default this port is initially set to zero, which allows the kernel to pick an available port for us. * After the HTTP server has started, the port being used may be obtained by this method. **/ - (UInt16)port { __block UInt16 result; dispatch_sync(serverQueue, ^{ result = port; }); return result; } - (UInt16)listeningPort { __block UInt16 result; dispatch_sync(serverQueue, ^{ if (isRunning) result = [asyncSocket localPort]; else result = 0; }); return result; } - (void)setPort:(UInt16)value { HTTPLogTrace(); dispatch_async(serverQueue, ^{ port = value; }); } /** * Domain on which to broadcast this service via Bonjour. * The default domain is @"local". **/ - (NSString *)domain { __block NSString *result; dispatch_sync(serverQueue, ^{ result = domain; }); return result; } - (void)setDomain:(NSString *)value { HTTPLogTrace(); NSString *valueCopy = [value copy]; dispatch_async(serverQueue, ^{ domain = valueCopy; }); } /** * The name to use for this service via Bonjour. * The default name is an empty string, * which should result in the published name being the host name of the computer. **/ - (NSString *)name { __block NSString *result; dispatch_sync(serverQueue, ^{ result = name; }); return result; } - (NSString *)publishedName { __block NSString *result; dispatch_sync(serverQueue, ^{ if (netService == nil) { result = nil; } else { dispatch_block_t bonjourBlock = ^{ result = [[netService name] copy]; }; [[self class] performBonjourBlock:bonjourBlock]; } }); return result; } - (void)setName:(NSString *)value { NSString *valueCopy = [value copy]; dispatch_async(serverQueue, ^{ name = valueCopy; }); } /** * The type of service to publish via Bonjour. * No type is set by default, and one must be set in order for the service to be published. **/ - (NSString *)type { __block NSString *result; dispatch_sync(serverQueue, ^{ result = type; }); return result; } - (void)setType:(NSString *)value { NSString *valueCopy = [value copy]; dispatch_async(serverQueue, ^{ type = valueCopy; }); } /** * The extra data to use for this service via Bonjour. **/ - (NSDictionary *)TXTRecordDictionary { __block NSDictionary *result; dispatch_sync(serverQueue, ^{ result = txtRecordDictionary; }); return result; } - (void)setTXTRecordDictionary:(NSDictionary *)value { HTTPLogTrace(); NSDictionary *valueCopy = [value copy]; dispatch_async(serverQueue, ^{ txtRecordDictionary = valueCopy; // Update the txtRecord of the netService if it has already been published if (netService) { NSNetService *theNetService = netService; NSData *txtRecordData = nil; if (txtRecordDictionary) txtRecordData = [NSNetService dataFromTXTRecordDictionary:txtRecordDictionary]; dispatch_block_t bonjourBlock = ^{ [theNetService setTXTRecordData:txtRecordData]; }; [[self class] performBonjourBlock:bonjourBlock]; } }); } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark Server Control //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - (BOOL)start:(NSError **)errPtr { HTTPLogTrace(); __block BOOL success = YES; __block NSError *err = nil; dispatch_sync(serverQueue, ^{ @autoreleasepool { success = [asyncSocket acceptOnInterface:interface port:port error:&err]; if (success) { HTTPLogInfo(@"%@: Started HTTP server on port %hu", THIS_FILE, [asyncSocket localPort]); isRunning = YES; [self publishBonjour]; } else { HTTPLogError(@"%@: Failed to start HTTP Server: %@", THIS_FILE, err); } }}); if (errPtr) *errPtr = err; return success; } - (void)stop { [self stop:NO]; } - (void)stop:(BOOL)keepExistingConnections { HTTPLogTrace(); dispatch_sync(serverQueue, ^{ @autoreleasepool { // First stop publishing the service via bonjour [self unpublishBonjour]; // Stop listening / accepting incoming connections [asyncSocket disconnect]; isRunning = NO; if (!keepExistingConnections) { // Stop all HTTP connections the server owns [connectionsLock lock]; for (HTTPConnection *connection in connections) { [connection stop]; } [connections removeAllObjects]; [connectionsLock unlock]; // Stop all WebSocket connections the server owns [webSocketsLock lock]; for (WebSocket *webSocket in webSockets) { [webSocket stop]; } [webSockets removeAllObjects]; [webSocketsLock unlock]; } }}); } - (BOOL)isRunning { __block BOOL result; dispatch_sync(serverQueue, ^{ result = isRunning; }); return result; } - (void)addWebSocket:(WebSocket *)ws { [webSocketsLock lock]; HTTPLogTrace(); [webSockets addObject:ws]; [webSocketsLock unlock]; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark Server Status //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /** * Returns the number of http client connections that are currently connected to the server. **/ - (NSUInteger)numberOfHTTPConnections { NSUInteger result = 0; [connectionsLock lock]; result = [connections count]; [connectionsLock unlock]; return result; } /** * Returns the number of websocket client connections that are currently connected to the server. **/ - (NSUInteger)numberOfWebSocketConnections { NSUInteger result = 0; [webSocketsLock lock]; result = [webSockets count]; [webSocketsLock unlock]; return result; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark Incoming Connections //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - (HTTPConfig *)config { // Override me if you want to provide a custom config to the new connection. // // Generally this involves overriding the HTTPConfig class to include any custom settings, // and then having this method return an instance of 'MyHTTPConfig'. // Note: Think you can make the server faster by putting each connection on its own queue? // Then benchmark it before and after and discover for yourself the shocking truth! // // Try the apache benchmark tool (already installed on your Mac): // $ ab -n 1000 -c 1 http://localhost:/some_path.html return [[HTTPConfig alloc] initWithServer:self documentRoot:documentRoot queue:connectionQueue]; } - (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket { HTTPConnection *newConnection = (HTTPConnection *)[[connectionClass alloc] initWithAsyncSocket:newSocket configuration:[self config]]; [connectionsLock lock]; [connections addObject:newConnection]; [connectionsLock unlock]; [newConnection start]; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark Bonjour //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - (void)publishBonjour { HTTPLogTrace(); NSAssert(dispatch_get_current_queue() == serverQueue, @"Invalid queue"); if (type) { netService = [[NSNetService alloc] initWithDomain:domain type:type name:name port:[asyncSocket localPort]]; [netService setDelegate:self]; NSNetService *theNetService = netService; NSData *txtRecordData = nil; if (txtRecordDictionary) txtRecordData = [NSNetService dataFromTXTRecordDictionary:txtRecordDictionary]; dispatch_block_t bonjourBlock = ^{ [theNetService removeFromRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes]; [theNetService scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes]; [theNetService publish]; // Do not set the txtRecordDictionary prior to publishing!!! // This will cause the OS to crash!!! if (txtRecordData) { [theNetService setTXTRecordData:txtRecordData]; } }; [[self class] startBonjourThreadIfNeeded]; [[self class] performBonjourBlock:bonjourBlock]; } } - (void)unpublishBonjour { HTTPLogTrace(); NSAssert(dispatch_get_current_queue() == serverQueue, @"Invalid queue"); if (netService) { NSNetService *theNetService = netService; dispatch_block_t bonjourBlock = ^{ [theNetService stop]; }; [[self class] performBonjourBlock:bonjourBlock]; netService = nil; } } /** * Republishes the service via bonjour if the server is running. * If the service was not previously published, this method will publish it (if the server is running). **/ - (void)republishBonjour { HTTPLogTrace(); dispatch_async(serverQueue, ^{ [self unpublishBonjour]; [self publishBonjour]; }); } /** * Called when our bonjour service has been successfully published. * This method does nothing but output a log message telling us about the published service. **/ - (void)netServiceDidPublish:(NSNetService *)ns { // Override me to do something here... // // Note: This method is invoked on our bonjour thread. HTTPLogInfo(@"Bonjour Service Published: domain(%@) type(%@) name(%@)", [ns domain], [ns type], [ns name]); } /** * Called if our bonjour service failed to publish itself. * This method does nothing but output a log message telling us about the published service. **/ - (void)netService:(NSNetService *)ns didNotPublish:(NSDictionary *)errorDict { // Override me to do something here... // // Note: This method in invoked on our bonjour thread. HTTPLogWarn(@"Failed to Publish Service: domain(%@) type(%@) name(%@) - %@", [ns domain], [ns type], [ns name], errorDict); } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark Notifications //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /** * This method is automatically called when a notification of type HTTPConnectionDidDieNotification is posted. * It allows us to remove the connection from our array. **/ - (void)connectionDidDie:(NSNotification *)notification { // Note: This method is called on the connection queue that posted the notification [connectionsLock lock]; HTTPLogTrace(); [connections removeObject:[notification object]]; [connectionsLock unlock]; } /** * This method is automatically called when a notification of type WebSocketDidDieNotification is posted. * It allows us to remove the websocket from our array. **/ - (void)webSocketDidDie:(NSNotification *)notification { // Note: This method is called on the connection queue that posted the notification [webSocketsLock lock]; HTTPLogTrace(); [webSockets removeObject:[notification object]]; [webSocketsLock unlock]; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark Bonjour Thread //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /** * NSNetService is runloop based, so it requires a thread with a runloop. * This gives us two options: * * - Use the main thread * - Setup our own dedicated thread * * Since we have various blocks of code that need to synchronously access the netservice objects, * using the main thread becomes troublesome and a potential for deadlock. **/ static NSThread *bonjourThread; + (void)startBonjourThreadIfNeeded { HTTPLogTrace(); static dispatch_once_t predicate; dispatch_once(&predicate, ^{ HTTPLogVerbose(@"%@: Starting bonjour thread...", THIS_FILE); bonjourThread = [[NSThread alloc] initWithTarget:self selector:@selector(bonjourThread) object:nil]; [bonjourThread start]; }); } + (void)bonjourThread { @autoreleasepool { HTTPLogVerbose(@"%@: BonjourThread: Started", THIS_FILE); // We can't run the run loop unless it has an associated input source or a timer. // So we'll just create a timer that will never fire - unless the server runs for 10,000 years. [NSTimer scheduledTimerWithTimeInterval:[[NSDate distantFuture] timeIntervalSinceNow] target:self selector:@selector(donothingatall:) userInfo:nil repeats:YES]; [[NSRunLoop currentRunLoop] run]; HTTPLogVerbose(@"%@: BonjourThread: Aborted", THIS_FILE); } } + (void)executeBonjourBlock:(dispatch_block_t)block { HTTPLogTrace(); NSAssert([NSThread currentThread] == bonjourThread, @"Executed on incorrect thread"); block(); } + (void)performBonjourBlock:(dispatch_block_t)block { HTTPLogTrace(); [self performSelector:@selector(executeBonjourBlock:) onThread:bonjourThread withObject:block waitUntilDone:YES]; } @end ================================================ FILE: zhuishushenqi/Vendor/CocoaHTTPServer/Core/Mime/MultipartFormDataParser.h ================================================ #import "MultipartMessageHeader.h" /* Part one: http://tools.ietf.org/html/rfc2045 (Format of Internet Message Bodies) Part two: http://tools.ietf.org/html/rfc2046 (Media Types) Part three: http://tools.ietf.org/html/rfc2047 (Message Header Extensions for Non-ASCII Text) Part four: http://tools.ietf.org/html/rfc4289 (Registration Procedures) Part five: http://tools.ietf.org/html/rfc2049 (Conformance Criteria and Examples) Internet message format: http://tools.ietf.org/html/rfc2822 Multipart/form-data http://tools.ietf.org/html/rfc2388 */ @class MultipartFormDataParser; //----------------------------------------------------------------- // protocol MultipartFormDataParser //----------------------------------------------------------------- @protocol MultipartFormDataParserDelegate @optional - (void) processContent:(NSData*) data WithHeader:(MultipartMessageHeader*) header; - (void) processEndOfPartWithHeader:(MultipartMessageHeader*) header; - (void) processPreambleData:(NSData*) data; - (void) processEpilogueData:(NSData*) data; - (void) processStartOfPartWithHeader:(MultipartMessageHeader*) header; @end //----------------------------------------------------------------- // interface MultipartFormDataParser //----------------------------------------------------------------- @interface MultipartFormDataParser : NSObject { NSMutableData* pendingData; NSData* boundaryData; MultipartMessageHeader* currentHeader; BOOL waitingForCRLF; BOOL reachedEpilogue; BOOL processedPreamble; BOOL checkForContentEnd; #if __has_feature(objc_arc_weak) __weak id delegate; #else __unsafe_unretained id delegate; #endif int currentEncoding; NSStringEncoding formEncoding; } - (BOOL) appendData:(NSData*) data; - (id) initWithBoundary:(NSString*) boundary formEncoding:(NSStringEncoding) formEncoding; #if __has_feature(objc_arc_weak) @property(weak, readwrite) id delegate; #else @property(unsafe_unretained, readwrite) id delegate; #endif @property(readwrite) NSStringEncoding formEncoding; @end ================================================ FILE: zhuishushenqi/Vendor/CocoaHTTPServer/Core/Mime/MultipartFormDataParser.m ================================================ #import "MultipartFormDataParser.h" #import "DDData.h" #import "HTTPLogging.h" //----------------------------------------------------------------- #pragma mark log level #ifdef DEBUG static const int httpLogLevel = HTTP_LOG_LEVEL_WARN; #else static const int httpLogLevel = HTTP_LOG_LEVEL_WARN; #endif //----------------------------------------------------------------- // interface MultipartFormDataParser (private) //----------------------------------------------------------------- @interface MultipartFormDataParser (private) + (NSData*) decodedDataFromData:(NSData*) data encoding:(int) encoding; - (int) findHeaderEnd:(NSData*) workingData fromOffset:(int) offset; - (int) findContentEnd:(NSData*) data fromOffset:(int) offset; - (int) numberOfBytesToLeavePendingWithData:(NSData*) data length:(int) length encoding:(int) encoding; - (int) offsetTillNewlineSinceOffset:(int) offset inData:(NSData*) data; - (int) processPreamble:(NSData*) workingData; @end //----------------------------------------------------------------- // implementation MultipartFormDataParser //----------------------------------------------------------------- @implementation MultipartFormDataParser @synthesize delegate,formEncoding; - (id) initWithBoundary:(NSString*) boundary formEncoding:(NSStringEncoding) _formEncoding { if( nil == (self = [super init]) ){ return self; } if( nil == boundary ) { HTTPLogWarn(@"MultipartFormDataParser: init with zero boundary"); return nil; } boundaryData = [[@"\r\n--" stringByAppendingString:boundary] dataUsingEncoding:NSASCIIStringEncoding]; pendingData = [[NSMutableData alloc] init]; currentEncoding = contentTransferEncoding_binary; currentHeader = nil; formEncoding = _formEncoding; reachedEpilogue = NO; processedPreamble = NO; return self; } - (BOOL) appendData:(NSData *)data { // Can't parse without boundary; if( nil == boundaryData ) { HTTPLogError(@"MultipartFormDataParser: Trying to parse multipart without specifying a valid boundary"); assert(false); return NO; } NSData* workingData = data; if( pendingData.length ) { [pendingData appendData:data]; workingData = pendingData; } // the parser saves parse stat in the offset variable, which indicates offset of unhandled part in // currently received chunk. Before returning, we always drop all data up to offset, leaving // only unhandled for the next call int offset = 0; // don't parse data unless its size is greater then boundary length, so we couldn't // misfind the boundary, if it got split into different data chunks int sizeToLeavePending = boundaryData.length; if( !reachedEpilogue && workingData.length <= sizeToLeavePending ) { // not enough data even to start parsing. // save to pending data. if( !pendingData.length ) { [pendingData appendData:data]; } if( checkForContentEnd ) { if( pendingData.length >= 2 ) { if( *(uint16_t*)(pendingData.bytes + offset) == 0x2D2D ) { // we found the multipart end. all coming next is an epilogue. HTTPLogVerbose(@"MultipartFormDataParser: End of multipart message"); waitingForCRLF = YES; reachedEpilogue = YES; offset+= 2; } else { checkForContentEnd = NO; waitingForCRLF = YES; return YES; } } else { return YES; } } else { return YES; } } while( true ) { if( checkForContentEnd ) { // the flag will be raised to check if the last part was the last one. if( offset < workingData.length -1 ) { char* bytes = (char*) workingData.bytes; if( *(uint16_t*)(bytes + offset) == 0x2D2D ) { // we found the multipart end. all coming next is an epilogue. HTTPLogVerbose(@"MultipartFormDataParser: End of multipart message"); checkForContentEnd = NO; reachedEpilogue = YES; // still wait for CRLF, that comes after boundary, but before epilogue. waitingForCRLF = YES; offset += 2; } else { // it's not content end, we have to wait till separator line end before next part comes waitingForCRLF = YES; checkForContentEnd = NO; } } else { // we haven't got enough data to check for content end. // save current unhandled data (it may be 1 byte) to pending and recheck on next chunk received if( offset < workingData.length ) { [pendingData setData:[NSData dataWithBytes:workingData.bytes + workingData.length-1 length:1]]; } else { // there is no unhandled data now, wait for more chunks [pendingData setData:[NSData data]]; } return YES; } } if( waitingForCRLF ) { // the flag will be raised in the code below, meaning, we've read the boundary, but // didnt find the end of boundary line yet. offset = [self offsetTillNewlineSinceOffset:offset inData:workingData]; if( -1 == offset ) { // didnt find the endl again. if( offset ) { // we still have to save the unhandled data (maybe it's 1 byte CR) if( *((char*)workingData.bytes + workingData.length -1) == '\r' ) { [pendingData setData:[NSData dataWithBytes:workingData.bytes + workingData.length-1 length:1]]; } else { // or save nothing if it wasnt [pendingData setData:[NSData data]]; } } return YES; } waitingForCRLF = NO; } if( !processedPreamble ) { // got to find the first boundary before the actual content begins. offset = [self processPreamble:workingData]; // wait for more data for preamble if( -1 == offset ) return YES; // invoke continue to skip newline after boundary. continue; } if( reachedEpilogue ) { // parse all epilogue data to delegate. if( [delegate respondsToSelector:@selector(processEpilogueData:)] ) { NSData* epilogueData = [NSData dataWithBytesNoCopy: (char*) workingData.bytes + offset length: workingData.length - offset freeWhenDone:NO]; [delegate processEpilogueData: epilogueData]; } return YES; } if( nil == currentHeader ) { // nil == currentHeader is a state flag, indicating we are waiting for header now. // whenever part is over, currentHeader is set to nil. // try to find CRLFCRLF bytes in the data, which indicates header end. // we won't parse header parts, as they won't be too large. int headerEnd = [self findHeaderEnd:workingData fromOffset:offset]; if( -1 == headerEnd ) { // didn't recieve the full header yet. if( !pendingData.length) { // store the unprocessed data till next chunks come [pendingData appendBytes:data.bytes + offset length:data.length - offset]; } else { if( offset ) { // save the current parse state; drop all handled data and save unhandled only. pendingData = [[NSMutableData alloc] initWithBytes: (char*) workingData.bytes + offset length:workingData.length - offset]; } } return YES; } else { // let the header parser do it's job from now on. NSData * headerData = [NSData dataWithBytesNoCopy: (char*) workingData.bytes + offset length:headerEnd + 2 - offset freeWhenDone:NO]; currentHeader = [[MultipartMessageHeader alloc] initWithData:headerData formEncoding:formEncoding]; if( nil == currentHeader ) { // we've found the data is in wrong format. HTTPLogError(@"MultipartFormDataParser: MultipartFormDataParser: wrong input format, coulnd't get a valid header"); return NO; } if( [delegate respondsToSelector:@selector(processStartOfPartWithHeader:)] ) { [delegate processStartOfPartWithHeader:currentHeader]; } HTTPLogVerbose(@"MultipartFormDataParser: MultipartFormDataParser: Retrieved part header."); } // skip the two trailing \r\n, in addition to the whole header. offset = headerEnd + 4; } // after we've got the header, we try to // find the boundary in the data. int contentEnd = [self findContentEnd:workingData fromOffset:offset]; if( contentEnd == -1 ) { // this case, we didn't find the boundary, so the data is related to the current part. // we leave the sizeToLeavePending amount of bytes to make sure we don't include // boundary part in processed data. int sizeToPass = workingData.length - offset - sizeToLeavePending; // if we parse BASE64 encoded data, or Quoted-Printable data, we will make sure we don't break the format int leaveTrailing = [self numberOfBytesToLeavePendingWithData:data length:sizeToPass encoding:currentEncoding]; sizeToPass -= leaveTrailing; if( sizeToPass <= 0 ) { // wait for more data! if( offset ) { [pendingData setData:[NSData dataWithBytes:(char*) workingData.bytes + offset length:workingData.length - offset]]; } return YES; } // decode the chunk and let the delegate use it (store in a file, for example) NSData* decodedData = [MultipartFormDataParser decodedDataFromData:[NSData dataWithBytesNoCopy:(char*)workingData.bytes + offset length:workingData.length - offset - sizeToLeavePending freeWhenDone:NO] encoding:currentEncoding]; if( [delegate respondsToSelector:@selector(processContent:WithHeader:)] ) { HTTPLogVerbose(@"MultipartFormDataParser: Processed %d bytes of body",sizeToPass); [delegate processContent: decodedData WithHeader:currentHeader]; } // store the unprocessed data till the next chunks come. [pendingData setData:[NSData dataWithBytes:(char*)workingData.bytes + workingData.length - sizeToLeavePending length:sizeToLeavePending]]; return YES; } else { // Here we found the boundary. // let the delegate process it, and continue going to the next parts. if( [delegate respondsToSelector:@selector(processContent:WithHeader:)] ) { [delegate processContent:[NSData dataWithBytesNoCopy:(char*) workingData.bytes + offset length:contentEnd - offset freeWhenDone:NO] WithHeader:currentHeader]; } if( [delegate respondsToSelector:@selector(processEndOfPartWithHeader:)] ){ [delegate processEndOfPartWithHeader:currentHeader]; HTTPLogVerbose(@"MultipartFormDataParser: End of body part"); } currentHeader = nil; // set up offset to continue with the remaining data (if any) offset = contentEnd + boundaryData.length; checkForContentEnd = YES; // setting the flag tells the parser to skip all the data till CRLF } } return YES; } //----------------------------------------------------------------- #pragma mark private methods - (int) offsetTillNewlineSinceOffset:(int) offset inData:(NSData*) data { char* bytes = (char*) data.bytes; int length = data.length; if( offset >= length - 1 ) return -1; while ( *(uint16_t*)(bytes + offset) != 0x0A0D ) { // find the trailing \r\n after the boundary. The boundary line might have any number of whitespaces before CRLF, according to rfc2046 // in debug, we might also want to know, if the file is somehow misformatted. #ifdef DEBUG if( !isspace(*(bytes+offset)) ) { HTTPLogWarn(@"MultipartFormDataParser: Warning, non-whitespace character '%c' between boundary bytes and CRLF in boundary line",*(bytes+offset) ); } if( !isspace(*(bytes+offset+1)) ) { HTTPLogWarn(@"MultipartFormDataParser: Warning, non-whitespace character '%c' between boundary bytes and CRLF in boundary line",*(bytes+offset+1) ); } #endif offset++; if( offset >= length ) { // no endl found within current data return -1; } } offset += 2; return offset; } - (int) processPreamble:(NSData*) data { int offset = 0; char* boundaryBytes = (char*) boundaryData.bytes + 2; // the first boundary won't have CRLF preceding. char* dataBytes = (char*) data.bytes; int boundaryLength = boundaryData.length - 2; int dataLength = data.length; // find the boundary without leading CRLF. while( offset < dataLength - boundaryLength +1 ) { int i; for( i = 0;i < boundaryLength; i++ ) { if( boundaryBytes[i] != dataBytes[offset + i] ) break; } if( i == boundaryLength ) { break; } offset++; } if( offset == dataLength ) { // the end of preamble wasn't found in this chunk int sizeToProcess = dataLength - boundaryLength; if( sizeToProcess > 0) { if( [delegate respondsToSelector:@selector(processPreambleData:)] ) { NSData* preambleData = [NSData dataWithBytesNoCopy: (char*) data.bytes length: data.length - offset - boundaryLength freeWhenDone:NO]; [delegate processPreambleData:preambleData]; HTTPLogVerbose(@"MultipartFormDataParser: processed preamble"); } pendingData = [NSMutableData dataWithBytes: data.bytes + data.length - boundaryLength length:boundaryLength]; } return -1; } else { if ( offset && [delegate respondsToSelector:@selector(processPreambleData:)] ) { NSData* preambleData = [NSData dataWithBytesNoCopy: (char*) data.bytes length: offset freeWhenDone:NO]; [delegate processPreambleData:preambleData]; } offset +=boundaryLength; // tells to skip CRLF after the boundary. processedPreamble = YES; waitingForCRLF = YES; } return offset; } - (int) findHeaderEnd:(NSData*) workingData fromOffset:(int)offset { char* bytes = (char*) workingData.bytes; int inputLength = workingData.length; uint16_t separatorBytes = 0x0A0D; while( true ) { if(inputLength < offset + 3 ) { // wait for more data return -1; } if( (*((uint16_t*) (bytes+offset)) == separatorBytes) && (*((uint16_t*) (bytes+offset)+1) == separatorBytes) ) { return offset; } offset++; } return -1; } - (int) findContentEnd:(NSData*) data fromOffset:(int) offset { char* boundaryBytes = (char*) boundaryData.bytes; char* dataBytes = (char*) data.bytes; int boundaryLength = boundaryData.length; int dataLength = data.length; while( offset < dataLength - boundaryLength +1 ) { int i; for( i = 0;i < boundaryLength; i++ ) { if( boundaryBytes[i] != dataBytes[offset + i] ) break; } if( i == boundaryLength ) { return offset; } offset++; } return -1; } - (int) numberOfBytesToLeavePendingWithData:(NSData*) data length:(int) length encoding:(int) encoding { // If we have BASE64 or Quoted-Printable encoded data, we have to be sure // we don't break the format. int sizeToLeavePending = 0; if( encoding == contentTransferEncoding_base64 ) { char* bytes = (char*) data.bytes; int i; for( i = length - 1; i > 0; i++ ) { if( * (uint16_t*) (bytes + i) == 0x0A0D ) { break; } } // now we've got to be sure that the length of passed data since last line // is multiplier of 4. sizeToLeavePending = (length - i) & ~0x11; // size to leave pending = length-i - (length-i) %4; return sizeToLeavePending; } if( encoding == contentTransferEncoding_quotedPrintable ) { // we don't pass more less then 3 bytes anyway. if( length <= 2 ) return length; // check the last bytes to be start of encoded symbol. const char* bytes = data.bytes + length - 2; if( bytes[0] == '=' ) return 2; if( bytes[1] == '=' ) return 1; return 0; } return 0; } //----------------------------------------------------------------- #pragma mark decoding + (NSData*) decodedDataFromData:(NSData*) data encoding:(int) encoding { switch (encoding) { case contentTransferEncoding_base64: { return [data base64Decoded]; } break; case contentTransferEncoding_quotedPrintable: { return [self decodedDataFromQuotedPrintableData:data]; } break; default: { return data; } break; } } + (NSData*) decodedDataFromQuotedPrintableData:(NSData *)data { // http://tools.ietf.org/html/rfc2045#section-6.7 const char hex [] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', }; NSMutableData* result = [[NSMutableData alloc] initWithLength:data.length]; const char* bytes = (const char*) data.bytes; int count = 0; int length = data.length; while( count < length ) { if( bytes[count] == '=' ) { [result appendBytes:bytes length:count]; bytes = bytes + count + 1; length -= count + 1; count = 0; if( length < 3 ) { HTTPLogWarn(@"MultipartFormDataParser: warning, trailing '=' in quoted printable data"); } // soft newline if( bytes[0] == '\r' ) { bytes += 1; if(bytes[1] == '\n' ) { bytes += 2; } continue; } char encodedByte = 0; for( int i = 0; i < sizeof(hex); i++ ) { if( hex[i] == bytes[0] ) { encodedByte += i << 4; } if( hex[i] == bytes[1] ) { encodedByte += i; } } [result appendBytes:&encodedByte length:1]; bytes += 2; } #ifdef DEBUG if( (unsigned char) bytes[count] > 126 ) { HTTPLogWarn(@"MultipartFormDataParser: Warning, character with code above 126 appears in quoted printable encoded data"); } #endif count++; } return result; } @end ================================================ FILE: zhuishushenqi/Vendor/CocoaHTTPServer/Core/Mime/MultipartMessageHeader.h ================================================ // // MultipartMessagePart.h // HttpServer // // Created by Валерий Гаврилов on 29.03.12. // Copyright (c) 2012 LLC "Online Publishing Partners" (onlinepp.ru). All rights reserved. // #import //----------------------------------------------------------------- // interface MultipartMessageHeader //----------------------------------------------------------------- enum { contentTransferEncoding_unknown, contentTransferEncoding_7bit, contentTransferEncoding_8bit, contentTransferEncoding_binary, contentTransferEncoding_base64, contentTransferEncoding_quotedPrintable, }; @interface MultipartMessageHeader : NSObject { NSMutableDictionary* fields; int encoding; NSString* contentDispositionName; } @property (strong,readonly) NSDictionary* fields; @property (readonly) int encoding; - (id) initWithData:(NSData*) data formEncoding:(NSStringEncoding) encoding; @end ================================================ FILE: zhuishushenqi/Vendor/CocoaHTTPServer/Core/Mime/MultipartMessageHeader.m ================================================ // // MultipartMessagePart.m // HttpServer // // Created by Валерий Гаврилов on 29.03.12. // Copyright (c) 2012 LLC "Online Publishing Partners" (onlinepp.ru). All rights reserved. #import "MultipartMessageHeader.h" #import "MultipartMessageHeaderField.h" #import "HTTPLogging.h" //----------------------------------------------------------------- #pragma mark log level #ifdef DEBUG static const int httpLogLevel = HTTP_LOG_LEVEL_WARN; #else static const int httpLogLevel = HTTP_LOG_LEVEL_WARN; #endif //----------------------------------------------------------------- // implementation MultipartMessageHeader //----------------------------------------------------------------- @implementation MultipartMessageHeader @synthesize fields,encoding; - (id) initWithData:(NSData *)data formEncoding:(NSStringEncoding) formEncoding { if( nil == (self = [super init]) ) { return self; } fields = [[NSMutableDictionary alloc] initWithCapacity:1]; // In case encoding is not mentioned, encoding = contentTransferEncoding_unknown; char* bytes = (char*)data.bytes; int length = data.length; int offset = 0; // split header into header fields, separated by \r\n uint16_t fields_separator = 0x0A0D; // \r\n while( offset < length - 2 ) { // the !isspace condition is to support header unfolding if( (*(uint16_t*) (bytes+offset) == fields_separator) && ((offset == length - 2) || !(isspace(bytes[offset+2])) )) { NSData* fieldData = [NSData dataWithBytesNoCopy:bytes length:offset freeWhenDone:NO]; MultipartMessageHeaderField* field = [[MultipartMessageHeaderField alloc] initWithData: fieldData contentEncoding:formEncoding]; if( field ) { [fields setObject:field forKey:field.name]; HTTPLogVerbose(@"MultipartFormDataParser: Processed Header field '%@'",field.name); } else { NSString* fieldStr = [[NSString alloc] initWithData:fieldData encoding:NSASCIIStringEncoding]; HTTPLogWarn(@"MultipartFormDataParser: Failed to parse MIME header field. Input ASCII string:%@",fieldStr); } // move to the next header field bytes += offset + 2; length -= offset + 2; offset = 0; continue; } ++ offset; } if( !fields.count ) { // it was an empty header. // we have to set default values. // default header. [fields setObject:@"text/plain" forKey:@"Content-Type"]; } return self; } - (NSString *)description { return [NSString stringWithFormat:@"%@",fields]; } @end ================================================ FILE: zhuishushenqi/Vendor/CocoaHTTPServer/Core/Mime/MultipartMessageHeaderField.h ================================================ #import //----------------------------------------------------------------- // interface MultipartMessageHeaderField //----------------------------------------------------------------- @interface MultipartMessageHeaderField : NSObject { NSString* name; NSString* value; NSMutableDictionary* params; } @property (strong, readonly) NSString* value; @property (strong, readonly) NSDictionary* params; @property (strong, readonly) NSString* name; //- (id) initWithLine:(NSString*) line; //- (id) initWithName:(NSString*) paramName value:(NSString*) paramValue; - (id) initWithData:(NSData*) data contentEncoding:(NSStringEncoding) encoding; @end ================================================ FILE: zhuishushenqi/Vendor/CocoaHTTPServer/Core/Mime/MultipartMessageHeaderField.m ================================================ #import "MultipartMessageHeaderField.h" #import "HTTPLogging.h" //----------------------------------------------------------------- #pragma mark log level #ifdef DEBUG static const int httpLogLevel = HTTP_LOG_LEVEL_WARN; #else static const int httpLogLevel = HTTP_LOG_LEVEL_WARN; #endif // helpers int findChar(const char* str,int length, char c); NSString* extractParamValue(const char* bytes, int length, NSStringEncoding encoding); //----------------------------------------------------------------- // interface MultipartMessageHeaderField (private) //----------------------------------------------------------------- @interface MultipartMessageHeaderField (private) -(BOOL) parseHeaderValueBytes:(char*) bytes length:(int) length encoding:(NSStringEncoding) encoding; @end //----------------------------------------------------------------- // implementation MultipartMessageHeaderField //----------------------------------------------------------------- @implementation MultipartMessageHeaderField @synthesize name,value,params; - (id) initWithData:(NSData *)data contentEncoding:(NSStringEncoding)encoding { params = [[NSMutableDictionary alloc] initWithCapacity:1]; char* bytes = (char*)data.bytes; int length = data.length; int separatorOffset = findChar(bytes, length, ':'); if( (-1 == separatorOffset) || (separatorOffset >= length-2) ) { HTTPLogError(@"MultipartFormDataParser: Bad format.No colon in field header."); // tear down return nil; } // header name is always ascii encoded; name = [[NSString alloc] initWithBytes: bytes length: separatorOffset encoding: NSASCIIStringEncoding]; if( nil == name ) { HTTPLogError(@"MultipartFormDataParser: Bad MIME header name."); // tear down return nil; } // skip the separator and the next ' ' symbol bytes += separatorOffset + 2; length -= separatorOffset + 2; separatorOffset = findChar(bytes, length, ';'); if( separatorOffset == -1 ) { // couldn't find ';', means we don't have extra params here. value = [[NSString alloc] initWithBytes:bytes length: length encoding:encoding]; if( nil == value ) { HTTPLogError(@"MultipartFormDataParser: Bad MIME header value for header name: '%@'",name); // tear down return nil; } return self; } value = [[NSString alloc] initWithBytes:bytes length: separatorOffset encoding:encoding]; HTTPLogVerbose(@"MultipartFormDataParser: Processing header field '%@' : '%@'",name,value); // skipe the separator and the next ' ' symbol bytes += separatorOffset + 2; length -= separatorOffset + 2; // parse the "params" part of the header if( ![self parseHeaderValueBytes:bytes length:length encoding:encoding] ) { NSString* paramsStr = [[NSString alloc] initWithBytes:bytes length:length encoding:NSASCIIStringEncoding]; HTTPLogError(@"MultipartFormDataParser: Bad params for header with name '%@' and value '%@'",name,value); HTTPLogError(@"MultipartFormDataParser: Params str: %@",paramsStr); return nil; } return self; } -(BOOL) parseHeaderValueBytes:(char*) bytes length:(int) length encoding:(NSStringEncoding) encoding { int offset = 0; NSString* currentParam = nil; BOOL insideQuote = NO; while( offset < length ) { if( bytes[offset] == '\"' ) { if( !offset || bytes[offset-1] != '\\' ) { insideQuote = !insideQuote; } } // skip quoted symbols if( insideQuote ) { ++ offset; continue; } if( bytes[offset] == '=' ) { if( currentParam ) { // found '=' before terminating previous param. return NO; } currentParam = [[NSString alloc] initWithBytes:bytes length:offset encoding:NSASCIIStringEncoding]; bytes+=offset + 1; length -= offset + 1; offset = 0; continue; } if( bytes[offset] == ';' ) { if( !currentParam ) { // found ; before stating '='. HTTPLogError(@"MultipartFormDataParser: Unexpected ';' when parsing header"); return NO; } NSString* paramValue = extractParamValue(bytes, offset,encoding); if( nil == paramValue ) { HTTPLogWarn(@"MultipartFormDataParser: Failed to exctract paramValue for key %@ in header %@",currentParam,name); } else { #ifdef DEBUG if( [params objectForKey:currentParam] ) { HTTPLogWarn(@"MultipartFormDataParser: param %@ mentioned more then once in header %@",currentParam,name); } #endif [params setObject:paramValue forKey:currentParam]; HTTPLogVerbose(@"MultipartFormDataParser: header param: %@ = %@",currentParam,paramValue); } currentParam = nil; // ';' separator has ' ' following, skip them. bytes+=offset + 2; length -= offset + 2; offset = 0; } ++ offset; } // add last param if( insideQuote ) { HTTPLogWarn(@"MultipartFormDataParser: unterminated quote in header %@",name); // return YES; } if( currentParam ) { NSString* paramValue = extractParamValue(bytes, length,encoding); if( nil == paramValue ) { HTTPLogError(@"MultipartFormDataParser: Failed to exctract paramValue for key %@ in header %@",currentParam,name); } #ifdef DEBUG if( [params objectForKey:currentParam] ) { HTTPLogWarn(@"MultipartFormDataParser: param %@ mentioned more then once in one header",currentParam); } #endif [params setObject:paramValue forKey:currentParam]; HTTPLogVerbose(@"MultipartFormDataParser: header param: %@ = %@",currentParam,paramValue); currentParam = nil; } return YES; } - (NSString *)description { return [NSString stringWithFormat:@"%@:%@\n params: %@",name,value,params]; } @end int findChar(const char* str,int length, char c) { int offset = 0; while( offset < length ) { if( str[offset] == c ) return offset; ++ offset; } return -1; } NSString* extractParamValue(const char* bytes, int length, NSStringEncoding encoding) { if( !length ) return nil; NSMutableString* value = nil; if( bytes[0] == '"' ) { // values may be quoted. Strip the quotes to get what we need. value = [[NSMutableString alloc] initWithBytes:bytes + 1 length: length - 2 encoding:encoding]; } else { value = [[NSMutableString alloc] initWithBytes:bytes length: length encoding:encoding]; } // restore escaped symbols NSRange range= [value rangeOfString:@"\\"]; while ( range.length ) { [value deleteCharactersInRange:range]; range.location ++; range = [value rangeOfString:@"\\" options:NSLiteralSearch range: range]; } return value; } ================================================ FILE: zhuishushenqi/Vendor/CocoaHTTPServer/Core/Responses/HTTPAsyncFileResponse.h ================================================ #import #import "HTTPResponse.h" @class HTTPConnection; /** * This is an asynchronous version of HTTPFileResponse. * It reads data from the given file asynchronously via GCD. * * It may be overriden to allow custom post-processing of the data that has been read from the file. * An example of this is the HTTPDynamicFileResponse class. **/ @interface HTTPAsyncFileResponse : NSObject { HTTPConnection *connection; NSString *filePath; UInt64 fileLength; UInt64 fileOffset; // File offset as pertains to data given to connection UInt64 readOffset; // File offset as pertains to data read from file (but maybe not returned to connection) BOOL aborted; NSData *data; int fileFD; void *readBuffer; NSUInteger readBufferSize; // Malloced size of readBuffer NSUInteger readBufferOffset; // Offset within readBuffer where the end of existing data is NSUInteger readRequestLength; dispatch_queue_t readQueue; dispatch_source_t readSource; BOOL readSourceSuspended; } - (id)initWithFilePath:(NSString *)filePath forConnection:(HTTPConnection *)connection; - (NSString *)filePath; @end /** * Explanation of Variables (excluding those that are obvious) * * fileOffset * This is the number of bytes that have been returned to the connection via the readDataOfLength method. * If 1KB of data has been read from the file, but none of that data has yet been returned to the connection, * then the fileOffset variable remains at zero. * This variable is used in the calculation of the isDone method. * Only after all data has been returned to the connection are we actually done. * * readOffset * Represents the offset of the file descriptor. * In other words, the file position indidcator for our read stream. * It might be easy to think of it as the total number of bytes that have been read from the file. * However, this isn't entirely accurate, as the setOffset: method may have caused us to * jump ahead in the file (lseek). * * readBuffer * Malloc'd buffer to hold data read from the file. * * readBufferSize * Total allocation size of malloc'd buffer. * * readBufferOffset * Represents the position in the readBuffer where we should store new bytes. * * readRequestLength * The total number of bytes that were requested from the connection. * It's OK if we return a lesser number of bytes to the connection. * It's NOT OK if we return a greater number of bytes to the connection. * Doing so would disrupt proper support for range requests. * If, however, the response is chunked then we don't need to worry about this. * Chunked responses inheritly don't support range requests. **/ ================================================ FILE: zhuishushenqi/Vendor/CocoaHTTPServer/Core/Responses/HTTPAsyncFileResponse.m ================================================ #import "HTTPAsyncFileResponse.h" #import "HTTPConnection.h" #import "HTTPLogging.h" #import #import #if ! __has_feature(objc_arc) #warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). #endif /** * Does ARC support support GCD objects? * It does if the minimum deployment target is iOS 6+ or Mac OS X 8+ **/ #if TARGET_OS_IPHONE // Compiling for iOS #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 60000 // iOS 6.0 or later #define NEEDS_DISPATCH_RETAIN_RELEASE 0 #else // iOS 5.X or earlier #define NEEDS_DISPATCH_RETAIN_RELEASE 1 #endif #else // Compiling for Mac OS X #if MAC_OS_X_VERSION_MIN_REQUIRED >= 1080 // Mac OS X 10.8 or later #define NEEDS_DISPATCH_RETAIN_RELEASE 0 #else #define NEEDS_DISPATCH_RETAIN_RELEASE 1 // Mac OS X 10.7 or earlier #endif #endif // Log levels : off, error, warn, info, verbose // Other flags: trace static const int httpLogLevel = HTTP_LOG_LEVEL_WARN; // | HTTP_LOG_FLAG_TRACE; #define NULL_FD -1 /** * Architecure overview: * * HTTPConnection will invoke our readDataOfLength: method to fetch data. * We will return nil, and then proceed to read the data via our readSource on our readQueue. * Once the requested amount of data has been read, we then pause our readSource, * and inform the connection of the available data. * * While our read is in progress, we don't have to worry about the connection calling any other methods, * except the connectionDidClose method, which would be invoked if the remote end closed the socket connection. * To safely handle this, we do a synchronous dispatch on the readQueue, * and nilify the connection as well as cancel our readSource. * * In order to minimize resource consumption during a HEAD request, * we don't open the file until we have to (until the connection starts requesting data). **/ @implementation HTTPAsyncFileResponse - (id)initWithFilePath:(NSString *)fpath forConnection:(HTTPConnection *)parent { if ((self = [super init])) { HTTPLogTrace(); connection = parent; // Parents retain children, children do NOT retain parents fileFD = NULL_FD; filePath = [fpath copy]; if (filePath == nil) { HTTPLogWarn(@"%@: Init failed - Nil filePath", THIS_FILE); return nil; } NSDictionary *fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:NULL]; if (fileAttributes == nil) { HTTPLogWarn(@"%@: Init failed - Unable to get file attributes. filePath: %@", THIS_FILE, filePath); return nil; } fileLength = (UInt64)[[fileAttributes objectForKey:NSFileSize] unsignedLongLongValue]; fileOffset = 0; aborted = NO; // We don't bother opening the file here. // If this is a HEAD request we only need to know the fileLength. } return self; } - (void)abort { HTTPLogTrace(); [connection responseDidAbort:self]; aborted = YES; } - (void)processReadBuffer { // This method is here to allow superclasses to perform post-processing of the data. // For an example, see the HTTPDynamicFileResponse class. // // At this point, the readBuffer has readBufferOffset bytes available. // This method is in charge of updating the readBufferOffset. // Failure to do so will cause the readBuffer to grow to fileLength. (Imagine a 1 GB file...) // Copy the data out of the temporary readBuffer. data = [[NSData alloc] initWithBytes:readBuffer length:readBufferOffset]; // Reset the read buffer. readBufferOffset = 0; // Notify the connection that we have data available for it. [connection responseHasAvailableData:self]; } - (void)pauseReadSource { if (!readSourceSuspended) { HTTPLogVerbose(@"%@[%p]: Suspending readSource", THIS_FILE, self); readSourceSuspended = YES; dispatch_suspend(readSource); } } - (void)resumeReadSource { if (readSourceSuspended) { HTTPLogVerbose(@"%@[%p]: Resuming readSource", THIS_FILE, self); readSourceSuspended = NO; dispatch_resume(readSource); } } - (void)cancelReadSource { HTTPLogVerbose(@"%@[%p]: Canceling readSource", THIS_FILE, self); dispatch_source_cancel(readSource); // Cancelling a dispatch source doesn't // invoke the cancel handler if the dispatch source is paused. if (readSourceSuspended) { readSourceSuspended = NO; dispatch_resume(readSource); } } - (BOOL)openFileAndSetupReadSource { HTTPLogTrace(); fileFD = open([filePath UTF8String], (O_RDONLY | O_NONBLOCK)); if (fileFD == NULL_FD) { HTTPLogError(@"%@: Unable to open file. filePath: %@", THIS_FILE, filePath); return NO; } HTTPLogVerbose(@"%@[%p]: Open fd[%i] -> %@", THIS_FILE, self, fileFD, filePath); readQueue = dispatch_queue_create("HTTPAsyncFileResponse", NULL); readSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, fileFD, 0, readQueue); dispatch_source_set_event_handler(readSource, ^{ HTTPLogTrace2(@"%@: eventBlock - fd[%i]", THIS_FILE, fileFD); // Determine how much data we should read. // // It is OK if we ask to read more bytes than exist in the file. // It is NOT OK to over-allocate the buffer. unsigned long long _bytesAvailableOnFD = dispatch_source_get_data(readSource); UInt64 _bytesLeftInFile = fileLength - readOffset; NSUInteger bytesAvailableOnFD; NSUInteger bytesLeftInFile; bytesAvailableOnFD = (_bytesAvailableOnFD > NSUIntegerMax) ? NSUIntegerMax : (NSUInteger)_bytesAvailableOnFD; bytesLeftInFile = (_bytesLeftInFile > NSUIntegerMax) ? NSUIntegerMax : (NSUInteger)_bytesLeftInFile; NSUInteger bytesLeftInRequest = readRequestLength - readBufferOffset; NSUInteger bytesLeft = MIN(bytesLeftInRequest, bytesLeftInFile); NSUInteger bytesToRead = MIN(bytesAvailableOnFD, bytesLeft); // Make sure buffer is big enough for read request. // Do not over-allocate. if (readBuffer == NULL || bytesToRead > (readBufferSize - readBufferOffset)) { readBufferSize = bytesToRead; readBuffer = reallocf(readBuffer, (size_t)bytesToRead); if (readBuffer == NULL) { HTTPLogError(@"%@[%p]: Unable to allocate buffer", THIS_FILE, self); [self pauseReadSource]; [self abort]; return; } } // Perform the read HTTPLogVerbose(@"%@[%p]: Attempting to read %lu bytes from file", THIS_FILE, self, (unsigned long)bytesToRead); ssize_t result = read(fileFD, readBuffer + readBufferOffset, (size_t)bytesToRead); // Check the results if (result < 0) { HTTPLogError(@"%@: Error(%i) reading file(%@)", THIS_FILE, errno, filePath); [self pauseReadSource]; [self abort]; } else if (result == 0) { HTTPLogError(@"%@: Read EOF on file(%@)", THIS_FILE, filePath); [self pauseReadSource]; [self abort]; } else // (result > 0) { HTTPLogVerbose(@"%@[%p]: Read %lu bytes from file", THIS_FILE, self, (unsigned long)result); readOffset += result; readBufferOffset += result; [self pauseReadSource]; [self processReadBuffer]; } }); int theFileFD = fileFD; #if NEEDS_DISPATCH_RETAIN_RELEASE dispatch_source_t theReadSource = readSource; #endif dispatch_source_set_cancel_handler(readSource, ^{ // Do not access self from within this block in any way, shape or form. // // Note: You access self if you reference an iVar. HTTPLogTrace2(@"%@: cancelBlock - Close fd[%i]", THIS_FILE, theFileFD); #if NEEDS_DISPATCH_RETAIN_RELEASE dispatch_release(theReadSource); #endif close(theFileFD); }); readSourceSuspended = YES; return YES; } - (BOOL)openFileIfNeeded { if (aborted) { // The file operation has been aborted. // This could be because we failed to open the file, // or the reading process failed. return NO; } if (fileFD != NULL_FD) { // File has already been opened. return YES; } return [self openFileAndSetupReadSource]; } - (UInt64)contentLength { HTTPLogTrace2(@"%@[%p]: contentLength - %llu", THIS_FILE, self, fileLength); return fileLength; } - (UInt64)offset { HTTPLogTrace(); return fileOffset; } - (void)setOffset:(UInt64)offset { HTTPLogTrace2(@"%@[%p]: setOffset:%llu", THIS_FILE, self, offset); if (![self openFileIfNeeded]) { // File opening failed, // or response has been aborted due to another error. return; } fileOffset = offset; readOffset = offset; off_t result = lseek(fileFD, (off_t)offset, SEEK_SET); if (result == -1) { HTTPLogError(@"%@[%p]: lseek failed - errno(%i) filePath(%@)", THIS_FILE, self, errno, filePath); [self abort]; } } - (NSData *)readDataOfLength:(NSUInteger)length { HTTPLogTrace2(@"%@[%p]: readDataOfLength:%lu", THIS_FILE, self, (unsigned long)length); if (data) { NSUInteger dataLength = [data length]; HTTPLogVerbose(@"%@[%p]: Returning data of length %lu", THIS_FILE, self, (unsigned long)dataLength); fileOffset += dataLength; NSData *result = data; data = nil; return result; } else { if (![self openFileIfNeeded]) { // File opening failed, // or response has been aborted due to another error. return nil; } dispatch_sync(readQueue, ^{ NSAssert(readSourceSuspended, @"Invalid logic - perhaps HTTPConnection has changed."); readRequestLength = length; [self resumeReadSource]; }); return nil; } } - (BOOL)isDone { BOOL result = (fileOffset == fileLength); HTTPLogTrace2(@"%@[%p]: isDone - %@", THIS_FILE, self, (result ? @"YES" : @"NO")); return result; } - (NSString *)filePath { return filePath; } - (BOOL)isAsynchronous { HTTPLogTrace(); return YES; } - (void)connectionDidClose { HTTPLogTrace(); if (fileFD != NULL_FD) { dispatch_sync(readQueue, ^{ // Prevent any further calls to the connection connection = nil; // Cancel the readSource. // We do this here because the readSource's eventBlock has retained self. // In other words, if we don't cancel the readSource, we will never get deallocated. [self cancelReadSource]; }); } } - (void)dealloc { HTTPLogTrace(); #if NEEDS_DISPATCH_RETAIN_RELEASE if (readQueue) dispatch_release(readQueue); #endif if (readBuffer) free(readBuffer); } @end ================================================ FILE: zhuishushenqi/Vendor/CocoaHTTPServer/Core/Responses/HTTPDataResponse.h ================================================ #import #import "HTTPResponse.h" @interface HTTPDataResponse : NSObject { NSUInteger offset; NSData *data; } - (id)initWithData:(NSData *)data; @end ================================================ FILE: zhuishushenqi/Vendor/CocoaHTTPServer/Core/Responses/HTTPDataResponse.m ================================================ #import "HTTPDataResponse.h" #import "HTTPLogging.h" #if ! __has_feature(objc_arc) #warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). #endif // Log levels : off, error, warn, info, verbose // Other flags: trace static const int httpLogLevel = HTTP_LOG_LEVEL_OFF; // | HTTP_LOG_FLAG_TRACE; @implementation HTTPDataResponse - (id)initWithData:(NSData *)dataParam { if((self = [super init])) { HTTPLogTrace(); offset = 0; data = dataParam; } return self; } - (void)dealloc { HTTPLogTrace(); } - (UInt64)contentLength { UInt64 result = (UInt64)[data length]; HTTPLogTrace2(@"%@[%p]: contentLength - %llu", THIS_FILE, self, result); return result; } - (UInt64)offset { HTTPLogTrace(); return offset; } - (void)setOffset:(UInt64)offsetParam { HTTPLogTrace2(@"%@[%p]: setOffset:%lu", THIS_FILE, self, (unsigned long)offset); offset = (NSUInteger)offsetParam; } - (NSData *)readDataOfLength:(NSUInteger)lengthParameter { HTTPLogTrace2(@"%@[%p]: readDataOfLength:%lu", THIS_FILE, self, (unsigned long)lengthParameter); NSUInteger remaining = [data length] - offset; NSUInteger length = lengthParameter < remaining ? lengthParameter : remaining; void *bytes = (void *)([data bytes] + offset); offset += length; return [NSData dataWithBytesNoCopy:bytes length:length freeWhenDone:NO]; } - (BOOL)isDone { BOOL result = (offset == [data length]); HTTPLogTrace2(@"%@[%p]: isDone - %@", THIS_FILE, self, (result ? @"YES" : @"NO")); return result; } @end ================================================ FILE: zhuishushenqi/Vendor/CocoaHTTPServer/Core/Responses/HTTPDynamicFileResponse.h ================================================ #import #import "HTTPResponse.h" #import "HTTPAsyncFileResponse.h" /** * This class is designed to assist with dynamic content. * Imagine you have a file that you want to make dynamic: * * * *

ComputerName Control Panel

* ... *
  • System Time: SysTime
  • * * * * Now you could generate the entire file in Objective-C, * but this would be a horribly tedious process. * Beside, you want to design the file with professional tools to make it look pretty. * * So all you have to do is escape your dynamic content like this: * * ... *

    %%ComputerName%% Control Panel

    * ... *
  • System Time: %%SysTime%%
  • * * And then you create an instance of this class with: * * - separator = @"%%" * - replacementDictionary = { "ComputerName"="Black MacBook", "SysTime"="2010-04-30 03:18:24" } * * This class will then perform the replacements for you, on the fly, as it reads the file data. * This class is also asynchronous, so it will perform the file IO using its own GCD queue. * * All keys for the replacementDictionary must be NSString's. * Values for the replacementDictionary may be NSString's, or any object that * returns what you want when its description method is invoked. **/ @interface HTTPDynamicFileResponse : HTTPAsyncFileResponse { NSData *separator; NSDictionary *replacementDict; } - (id)initWithFilePath:(NSString *)filePath forConnection:(HTTPConnection *)connection separator:(NSString *)separatorStr replacementDictionary:(NSDictionary *)dictionary; @end ================================================ FILE: zhuishushenqi/Vendor/CocoaHTTPServer/Core/Responses/HTTPDynamicFileResponse.m ================================================ #import "HTTPDynamicFileResponse.h" #import "HTTPConnection.h" #import "HTTPLogging.h" #if ! __has_feature(objc_arc) #warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). #endif // Log levels : off, error, warn, info, verbose // Other flags: trace static const int httpLogLevel = HTTP_LOG_LEVEL_WARN; // | HTTP_LOG_FLAG_TRACE; #define NULL_FD -1 @implementation HTTPDynamicFileResponse - (id)initWithFilePath:(NSString *)fpath forConnection:(HTTPConnection *)parent separator:(NSString *)separatorStr replacementDictionary:(NSDictionary *)dict { if ((self = [super initWithFilePath:fpath forConnection:parent])) { HTTPLogTrace(); separator = [separatorStr dataUsingEncoding:NSUTF8StringEncoding]; replacementDict = dict; } return self; } - (BOOL)isChunked { HTTPLogTrace(); return YES; } - (UInt64)contentLength { // This method shouldn't be called since we're using a chunked response. // We override it just to be safe. HTTPLogTrace(); return 0; } - (void)setOffset:(UInt64)offset { // This method shouldn't be called since we're using a chunked response. // We override it just to be safe. HTTPLogTrace(); } - (BOOL)isDone { BOOL result = (readOffset == fileLength) && (readBufferOffset == 0); HTTPLogTrace2(@"%@[%p]: isDone - %@", THIS_FILE, self, (result ? @"YES" : @"NO")); return result; } - (void)processReadBuffer { HTTPLogTrace(); // At this point, the readBuffer has readBufferOffset bytes available. // This method is in charge of updating the readBufferOffset. NSUInteger bufLen = readBufferOffset; NSUInteger sepLen = [separator length]; // We're going to start looking for the separator at the beginning of the buffer, // and stop when we get to the point where the separator would no longer fit in the buffer. NSUInteger offset = 0; NSUInteger stopOffset = (bufLen > sepLen) ? bufLen - sepLen + 1 : 0; // In order to do the replacement, we need to find the starting and ending separator. // For example: // // %%USER_NAME%% // // Where "%%" is the separator. BOOL found1 = NO; BOOL found2 = NO; NSUInteger s1 = 0; NSUInteger s2 = 0; const void *sep = [separator bytes]; while (offset < stopOffset) { const void *subBuffer = readBuffer + offset; if (memcmp(subBuffer, sep, sepLen) == 0) { if (!found1) { // Found the first separator found1 = YES; s1 = offset; offset += sepLen; HTTPLogVerbose(@"%@[%p]: Found s1 at %lu", THIS_FILE, self, (unsigned long)s1); } else { // Found the second separator found2 = YES; s2 = offset; offset += sepLen; HTTPLogVerbose(@"%@[%p]: Found s2 at %lu", THIS_FILE, self, (unsigned long)s2); } if (found1 && found2) { // We found our separators. // Now extract the string between the two separators. NSRange fullRange = NSMakeRange(s1, (s2 - s1 + sepLen)); NSRange strRange = NSMakeRange(s1 + sepLen, (s2 - s1 - sepLen)); // Wish we could use the simple subdataWithRange method. // But that method copies the bytes... // So for performance reasons, we need to use the methods that don't copy the bytes. void *strBuf = readBuffer + strRange.location; NSUInteger strLen = strRange.length; NSString *key = [[NSString alloc] initWithBytes:strBuf length:strLen encoding:NSUTF8StringEncoding]; if (key) { // Is there a given replacement for this key? id value = [replacementDict objectForKey:key]; if (value) { // Found the replacement value. // Now perform the replacement in the buffer. HTTPLogVerbose(@"%@[%p]: key(%@) -> value(%@)", THIS_FILE, self, key, value); NSData *v = [[value description] dataUsingEncoding:NSUTF8StringEncoding]; NSUInteger vLength = [v length]; if (fullRange.length == vLength) { // Replacement is exactly the same size as what it is replacing // memcpy(void *restrict dst, const void *restrict src, size_t n); memcpy(readBuffer + fullRange.location, [v bytes], vLength); } else // (fullRange.length != vLength) { NSInteger diff = (NSInteger)vLength - (NSInteger)fullRange.length; if (diff > 0) { // Replacement is bigger than what it is replacing. // Make sure there is room in the buffer for the replacement. if (diff > (readBufferSize - bufLen)) { NSUInteger inc = MAX(diff, 256); readBufferSize += inc; readBuffer = reallocf(readBuffer, readBufferSize); } } // Move the data that comes after the replacement. // // If replacement is smaller than what it is replacing, // then we are shifting the data toward the beginning of the buffer. // // If replacement is bigger than what it is replacing, // then we are shifting the data toward the end of the buffer. // // memmove(void *dst, const void *src, size_t n); // // The memmove() function copies n bytes from src to dst. // The two areas may overlap; the copy is always done in a non-destructive manner. void *src = readBuffer + fullRange.location + fullRange.length; void *dst = readBuffer + fullRange.location + vLength; NSUInteger remaining = bufLen - (fullRange.location + fullRange.length); memmove(dst, src, remaining); // Now copy the replacement into its location. // // memcpy(void *restrict dst, const void *restrict src, size_t n) // // The memcpy() function copies n bytes from src to dst. // If the two areas overlap, behavior is undefined. memcpy(readBuffer + fullRange.location, [v bytes], vLength); // And don't forget to update our indices. bufLen += diff; offset += diff; stopOffset += diff; } } } found1 = found2 = NO; } } else { offset++; } } // We've gone through our buffer now, and performed all the replacements that we could. // It's now time to update the amount of available data we have. if (readOffset == fileLength) { // We've read in the entire file. // So there can be no more replacements. data = [[NSData alloc] initWithBytes:readBuffer length:bufLen]; readBufferOffset = 0; } else { // There are a couple different situations that we need to take into account here. // // Imagine the following file: // My name is %%USER_NAME%% // // Situation 1: // The first chunk of data we read was "My name is %%". // So we found the first separator, but not the second. // In this case we can only return the data that precedes the first separator. // // Situation 2: // The first chunk of data we read was "My name is %". // So we didn't find any separators, but part of a separator may be included in our buffer. NSUInteger available; if (found1) { // Situation 1 available = s1; } else { // Situation 2 available = stopOffset; } // Copy available data data = [[NSData alloc] initWithBytes:readBuffer length:available]; // Remove the copied data from the buffer. // We do this by shifting the remaining data toward the beginning of the buffer. NSUInteger remaining = bufLen - available; memmove(readBuffer, readBuffer + available, remaining); readBufferOffset = remaining; } [connection responseHasAvailableData:self]; } - (void)dealloc { HTTPLogTrace(); } @end ================================================ FILE: zhuishushenqi/Vendor/CocoaHTTPServer/Core/Responses/HTTPFileResponse.h ================================================ #import #import "HTTPResponse.h" @class HTTPConnection; @interface HTTPFileResponse : NSObject { HTTPConnection *connection; NSString *filePath; UInt64 fileLength; UInt64 fileOffset; BOOL aborted; int fileFD; void *buffer; NSUInteger bufferSize; } - (id)initWithFilePath:(NSString *)filePath forConnection:(HTTPConnection *)connection; - (NSString *)filePath; @end ================================================ FILE: zhuishushenqi/Vendor/CocoaHTTPServer/Core/Responses/HTTPFileResponse.m ================================================ #import "HTTPFileResponse.h" #import "HTTPConnection.h" #import "HTTPLogging.h" #import #import #if ! __has_feature(objc_arc) #warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). #endif // Log levels : off, error, warn, info, verbose // Other flags: trace static const int httpLogLevel = HTTP_LOG_LEVEL_WARN; // | HTTP_LOG_FLAG_TRACE; #define NULL_FD -1 @implementation HTTPFileResponse - (id)initWithFilePath:(NSString *)fpath forConnection:(HTTPConnection *)parent { if((self = [super init])) { HTTPLogTrace(); connection = parent; // Parents retain children, children do NOT retain parents fileFD = NULL_FD; filePath = [[fpath copy] stringByResolvingSymlinksInPath]; if (filePath == nil) { HTTPLogWarn(@"%@: Init failed - Nil filePath", THIS_FILE); return nil; } NSDictionary *fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:nil]; if (fileAttributes == nil) { HTTPLogWarn(@"%@: Init failed - Unable to get file attributes. filePath: %@", THIS_FILE, filePath); return nil; } fileLength = (UInt64)[[fileAttributes objectForKey:NSFileSize] unsignedLongLongValue]; fileOffset = 0; aborted = NO; // We don't bother opening the file here. // If this is a HEAD request we only need to know the fileLength. } return self; } - (void)abort { HTTPLogTrace(); [connection responseDidAbort:self]; aborted = YES; } - (BOOL)openFile { HTTPLogTrace(); fileFD = open([filePath UTF8String], O_RDONLY); if (fileFD == NULL_FD) { HTTPLogError(@"%@[%p]: Unable to open file. filePath: %@", THIS_FILE, self, filePath); [self abort]; return NO; } HTTPLogVerbose(@"%@[%p]: Open fd[%i] -> %@", THIS_FILE, self, fileFD, filePath); return YES; } - (BOOL)openFileIfNeeded { if (aborted) { // The file operation has been aborted. // This could be because we failed to open the file, // or the reading process failed. return NO; } if (fileFD != NULL_FD) { // File has already been opened. return YES; } return [self openFile]; } - (UInt64)contentLength { HTTPLogTrace(); return fileLength; } - (UInt64)offset { HTTPLogTrace(); return fileOffset; } - (void)setOffset:(UInt64)offset { HTTPLogTrace2(@"%@[%p]: setOffset:%llu", THIS_FILE, self, offset); if (![self openFileIfNeeded]) { // File opening failed, // or response has been aborted due to another error. return; } fileOffset = offset; off_t result = lseek(fileFD, (off_t)offset, SEEK_SET); if (result == -1) { HTTPLogError(@"%@[%p]: lseek failed - errno(%i) filePath(%@)", THIS_FILE, self, errno, filePath); [self abort]; } } - (NSData *)readDataOfLength:(NSUInteger)length { HTTPLogTrace2(@"%@[%p]: readDataOfLength:%lu", THIS_FILE, self, (unsigned long)length); if (![self openFileIfNeeded]) { // File opening failed, // or response has been aborted due to another error. return nil; } // Determine how much data we should read. // // It is OK if we ask to read more bytes than exist in the file. // It is NOT OK to over-allocate the buffer. UInt64 bytesLeftInFile = fileLength - fileOffset; NSUInteger bytesToRead = (NSUInteger)MIN(length, bytesLeftInFile); // Make sure buffer is big enough for read request. // Do not over-allocate. if (buffer == NULL || bufferSize < bytesToRead) { bufferSize = bytesToRead; buffer = reallocf(buffer, (size_t)bufferSize); if (buffer == NULL) { HTTPLogError(@"%@[%p]: Unable to allocate buffer", THIS_FILE, self); [self abort]; return nil; } } // Perform the read HTTPLogVerbose(@"%@[%p]: Attempting to read %lu bytes from file", THIS_FILE, self, (unsigned long)bytesToRead); ssize_t result = read(fileFD, buffer, bytesToRead); // Check the results if (result < 0) { HTTPLogError(@"%@: Error(%i) reading file(%@)", THIS_FILE, errno, filePath); [self abort]; return nil; } else if (result == 0) { HTTPLogError(@"%@: Read EOF on file(%@)", THIS_FILE, filePath); [self abort]; return nil; } else // (result > 0) { HTTPLogVerbose(@"%@[%p]: Read %ld bytes from file", THIS_FILE, self, (long)result); fileOffset += result; return [NSData dataWithBytes:buffer length:result]; } } - (BOOL)isDone { BOOL result = (fileOffset == fileLength); HTTPLogTrace2(@"%@[%p]: isDone - %@", THIS_FILE, self, (result ? @"YES" : @"NO")); return result; } - (NSString *)filePath { return filePath; } - (void)dealloc { HTTPLogTrace(); if (fileFD != NULL_FD) { HTTPLogVerbose(@"%@[%p]: Close fd[%i]", THIS_FILE, self, fileFD); close(fileFD); } if (buffer) free(buffer); } @end ================================================ FILE: zhuishushenqi/Vendor/CocoaHTTPServer/Core/Responses/HTTPRedirectResponse.h ================================================ #import #import "HTTPResponse.h" @interface HTTPRedirectResponse : NSObject { NSString *redirectPath; } - (id)initWithPath:(NSString *)redirectPath; @end ================================================ FILE: zhuishushenqi/Vendor/CocoaHTTPServer/Core/Responses/HTTPRedirectResponse.m ================================================ #import "HTTPRedirectResponse.h" #import "HTTPLogging.h" #if ! __has_feature(objc_arc) #warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). #endif // Log levels : off, error, warn, info, verbose // Other flags: trace static const int httpLogLevel = HTTP_LOG_LEVEL_OFF; // | HTTP_LOG_FLAG_TRACE; @implementation HTTPRedirectResponse - (id)initWithPath:(NSString *)path { if ((self = [super init])) { HTTPLogTrace(); redirectPath = [path copy]; } return self; } - (UInt64)contentLength { return 0; } - (UInt64)offset { return 0; } - (void)setOffset:(UInt64)offset { // Nothing to do } - (NSData *)readDataOfLength:(NSUInteger)length { HTTPLogTrace(); return nil; } - (BOOL)isDone { return YES; } - (NSDictionary *)httpHeaders { HTTPLogTrace(); return [NSDictionary dictionaryWithObject:redirectPath forKey:@"Location"]; } - (NSInteger)status { HTTPLogTrace(); return 302; } - (void)dealloc { HTTPLogTrace(); } @end ================================================ FILE: zhuishushenqi/Vendor/CocoaHTTPServer/Core/WebSocket.h ================================================ #import @class HTTPMessage; @class GCDAsyncSocket; #define WebSocketDidDieNotification @"WebSocketDidDie" @interface WebSocket : NSObject { dispatch_queue_t websocketQueue; HTTPMessage *request; GCDAsyncSocket *asyncSocket; NSData *term; BOOL isStarted; BOOL isOpen; BOOL isVersion76; id __unsafe_unretained delegate; } + (BOOL)isWebSocketRequest:(HTTPMessage *)request; - (id)initWithRequest:(HTTPMessage *)request socket:(GCDAsyncSocket *)socket; /** * Delegate option. * * In most cases it will be easier to subclass WebSocket, * but some circumstances may lead one to prefer standard delegate callbacks instead. **/ @property (/* atomic */ unsafe_unretained) id delegate; /** * The WebSocket class is thread-safe, generally via it's GCD queue. * All public API methods are thread-safe, * and the subclass API methods are thread-safe as they are all invoked on the same GCD queue. **/ @property (nonatomic, readonly) dispatch_queue_t websocketQueue; /** * Public API * * These methods are automatically called by the HTTPServer. * You may invoke the stop method yourself to close the WebSocket manually. **/ - (void)start; - (void)stop; /** * Public API * * Sends a message over the WebSocket. * This method is thread-safe. **/ - (void)sendMessage:(NSString *)msg; /** * Subclass API * * These methods are designed to be overriden by subclasses. **/ - (void)didOpen; - (void)didReceiveMessage:(NSString *)msg; - (void)didClose; @end //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /** * There are two ways to create your own custom WebSocket: * * - Subclass it and override the methods you're interested in. * - Use traditional delegate paradigm along with your own custom class. * * They both exist to allow for maximum flexibility. * In most cases it will be easier to subclass WebSocket. * However some circumstances may lead one to prefer standard delegate callbacks instead. * One such example, you're already subclassing another class, so subclassing WebSocket isn't an option. **/ @protocol WebSocketDelegate @optional - (void)webSocketDidOpen:(WebSocket *)ws; - (void)webSocket:(WebSocket *)ws didReceiveMessage:(NSString *)msg; - (void)webSocketDidClose:(WebSocket *)ws; @end ================================================ FILE: zhuishushenqi/Vendor/CocoaHTTPServer/Core/WebSocket.m ================================================ #import "WebSocket.h" #import "HTTPMessage.h" #import "GCDAsyncSocket.h" #import "DDNumber.h" #import "DDData.h" #import "HTTPLogging.h" #if ! __has_feature(objc_arc) #warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). #endif // Does ARC support support GCD objects? // It does if the minimum deployment target is iOS 6+ or Mac OS X 8+ #if TARGET_OS_IPHONE // Compiling for iOS #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 60000 // iOS 6.0 or later #define NEEDS_DISPATCH_RETAIN_RELEASE 0 #else // iOS 5.X or earlier #define NEEDS_DISPATCH_RETAIN_RELEASE 1 #endif #else // Compiling for Mac OS X #if MAC_OS_X_VERSION_MIN_REQUIRED >= 1080 // Mac OS X 10.8 or later #define NEEDS_DISPATCH_RETAIN_RELEASE 0 #else #define NEEDS_DISPATCH_RETAIN_RELEASE 1 // Mac OS X 10.7 or earlier #endif #endif // Log levels: off, error, warn, info, verbose // Other flags : trace static const int httpLogLevel = HTTP_LOG_LEVEL_WARN; // | HTTP_LOG_FLAG_TRACE; #define TIMEOUT_NONE -1 #define TIMEOUT_REQUEST_BODY 10 #define TAG_HTTP_REQUEST_BODY 100 #define TAG_HTTP_RESPONSE_HEADERS 200 #define TAG_HTTP_RESPONSE_BODY 201 #define TAG_PREFIX 300 #define TAG_MSG_PLUS_SUFFIX 301 #define TAG_MSG_WITH_LENGTH 302 #define TAG_MSG_MASKING_KEY 303 #define TAG_PAYLOAD_PREFIX 304 #define TAG_PAYLOAD_LENGTH 305 #define TAG_PAYLOAD_LENGTH16 306 #define TAG_PAYLOAD_LENGTH64 307 #define WS_OP_CONTINUATION_FRAME 0 #define WS_OP_TEXT_FRAME 1 #define WS_OP_BINARY_FRAME 2 #define WS_OP_CONNECTION_CLOSE 8 #define WS_OP_PING 9 #define WS_OP_PONG 10 static inline BOOL WS_OP_IS_FINAL_FRAGMENT(UInt8 frame) { return (frame & 0x80) ? YES : NO; } static inline BOOL WS_PAYLOAD_IS_MASKED(UInt8 frame) { return (frame & 0x80) ? YES : NO; } static inline NSUInteger WS_PAYLOAD_LENGTH(UInt8 frame) { return frame & 0x7F; } @interface WebSocket (PrivateAPI) - (void)readRequestBody; - (void)sendResponseBody; - (void)sendResponseHeaders; @end //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @implementation WebSocket { BOOL isRFC6455; BOOL nextFrameMasked; NSUInteger nextOpCode; NSData *maskingKey; } + (BOOL)isWebSocketRequest:(HTTPMessage *)request { // Request (Draft 75): // // GET /demo HTTP/1.1 // Upgrade: WebSocket // Connection: Upgrade // Host: example.com // Origin: http://example.com // WebSocket-Protocol: sample // // // Request (Draft 76): // // GET /demo HTTP/1.1 // Upgrade: WebSocket // Connection: Upgrade // Host: example.com // Origin: http://example.com // Sec-WebSocket-Protocol: sample // Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5 // Sec-WebSocket-Key2: 12998 5 Y3 1 .P00 // // ^n:ds[4U // Look for Upgrade: and Connection: headers. // If we find them, and they have the proper value, // we can safely assume this is a websocket request. NSString *upgradeHeaderValue = [request headerField:@"Upgrade"]; NSString *connectionHeaderValue = [request headerField:@"Connection"]; BOOL isWebSocket = YES; if (!upgradeHeaderValue || !connectionHeaderValue) { isWebSocket = NO; } else if (![upgradeHeaderValue caseInsensitiveCompare:@"WebSocket"] == NSOrderedSame) { isWebSocket = NO; } else if ([connectionHeaderValue rangeOfString:@"Upgrade" options:NSCaseInsensitiveSearch].location == NSNotFound) { isWebSocket = NO; } HTTPLogTrace2(@"%@: %@ - %@", THIS_FILE, THIS_METHOD, (isWebSocket ? @"YES" : @"NO")); return isWebSocket; } + (BOOL)isVersion76Request:(HTTPMessage *)request { NSString *key1 = [request headerField:@"Sec-WebSocket-Key1"]; NSString *key2 = [request headerField:@"Sec-WebSocket-Key2"]; BOOL isVersion76; if (!key1 || !key2) { isVersion76 = NO; } else { isVersion76 = YES; } HTTPLogTrace2(@"%@: %@ - %@", THIS_FILE, THIS_METHOD, (isVersion76 ? @"YES" : @"NO")); return isVersion76; } + (BOOL)isRFC6455Request:(HTTPMessage *)request { NSString *key = [request headerField:@"Sec-WebSocket-Key"]; BOOL isRFC6455 = (key != nil); HTTPLogTrace2(@"%@: %@ - %@", THIS_FILE, THIS_METHOD, (isRFC6455 ? @"YES" : @"NO")); return isRFC6455; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark Setup and Teardown //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @synthesize websocketQueue; - (id)initWithRequest:(HTTPMessage *)aRequest socket:(GCDAsyncSocket *)socket { HTTPLogTrace(); if (aRequest == nil) { return nil; } if ((self = [super init])) { if (HTTP_LOG_VERBOSE) { NSData *requestHeaders = [aRequest messageData]; NSString *temp = [[NSString alloc] initWithData:requestHeaders encoding:NSUTF8StringEncoding]; HTTPLogVerbose(@"%@[%p] Request Headers:\n%@", THIS_FILE, self, temp); } websocketQueue = dispatch_queue_create("WebSocket", NULL); request = aRequest; asyncSocket = socket; [asyncSocket setDelegate:self delegateQueue:websocketQueue]; isOpen = NO; isVersion76 = [[self class] isVersion76Request:request]; isRFC6455 = [[self class] isRFC6455Request:request]; term = [[NSData alloc] initWithBytes:"\xFF" length:1]; } return self; } - (void)dealloc { HTTPLogTrace(); #if NEEDS_DISPATCH_RETAIN_RELEASE dispatch_release(websocketQueue); #endif [asyncSocket setDelegate:nil delegateQueue:NULL]; [asyncSocket disconnect]; } - (id)delegate { __block id result = nil; dispatch_sync(websocketQueue, ^{ result = delegate; }); return result; } - (void)setDelegate:(id)newDelegate { dispatch_async(websocketQueue, ^{ delegate = newDelegate; }); } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark Start and Stop //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /** * Starting point for the WebSocket after it has been fully initialized (including subclasses). * This method is called by the HTTPConnection it is spawned from. **/ - (void)start { // This method is not exactly designed to be overriden. // Subclasses are encouraged to override the didOpen method instead. dispatch_async(websocketQueue, ^{ @autoreleasepool { if (isStarted) return; isStarted = YES; if (isVersion76) { [self readRequestBody]; } else { [self sendResponseHeaders]; [self didOpen]; } }}); } /** * This method is called by the HTTPServer if it is asked to stop. * The server, in turn, invokes stop on each WebSocket instance. **/ - (void)stop { // This method is not exactly designed to be overriden. // Subclasses are encouraged to override the didClose method instead. dispatch_async(websocketQueue, ^{ @autoreleasepool { [asyncSocket disconnect]; }}); } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark HTTP Response //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - (void)readRequestBody { HTTPLogTrace(); NSAssert(isVersion76, @"WebSocket version 75 doesn't contain a request body"); [asyncSocket readDataToLength:8 withTimeout:TIMEOUT_NONE tag:TAG_HTTP_REQUEST_BODY]; } - (NSString *)originResponseHeaderValue { HTTPLogTrace(); NSString *origin = [request headerField:@"Origin"]; if (origin == nil) { NSString *port = [NSString stringWithFormat:@"%hu", [asyncSocket localPort]]; return [NSString stringWithFormat:@"http://localhost:%@", port]; } else { return origin; } } - (NSString *)locationResponseHeaderValue { HTTPLogTrace(); NSString *location; NSString *scheme = [asyncSocket isSecure] ? @"wss" : @"ws"; NSString *host = [request headerField:@"Host"]; NSString *requestUri = [[request url] relativeString]; if (host == nil) { NSString *port = [NSString stringWithFormat:@"%hu", [asyncSocket localPort]]; location = [NSString stringWithFormat:@"%@://localhost:%@%@", scheme, port, requestUri]; } else { location = [NSString stringWithFormat:@"%@://%@%@", scheme, host, requestUri]; } return location; } - (NSString *)secWebSocketKeyResponseHeaderValue { NSString *key = [request headerField: @"Sec-WebSocket-Key"]; NSString *guid = @"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; return [[key stringByAppendingString: guid] dataUsingEncoding: NSUTF8StringEncoding].sha1Digest.base64Encoded; } - (void)sendResponseHeaders { HTTPLogTrace(); // Request (Draft 75): // // GET /demo HTTP/1.1 // Upgrade: WebSocket // Connection: Upgrade // Host: example.com // Origin: http://example.com // WebSocket-Protocol: sample // // // Request (Draft 76): // // GET /demo HTTP/1.1 // Upgrade: WebSocket // Connection: Upgrade // Host: example.com // Origin: http://example.com // Sec-WebSocket-Protocol: sample // Sec-WebSocket-Key2: 12998 5 Y3 1 .P00 // Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5 // // ^n:ds[4U // Response (Draft 75): // // HTTP/1.1 101 Web Socket Protocol Handshake // Upgrade: WebSocket // Connection: Upgrade // WebSocket-Origin: http://example.com // WebSocket-Location: ws://example.com/demo // WebSocket-Protocol: sample // // // Response (Draft 76): // // HTTP/1.1 101 WebSocket Protocol Handshake // Upgrade: WebSocket // Connection: Upgrade // Sec-WebSocket-Origin: http://example.com // Sec-WebSocket-Location: ws://example.com/demo // Sec-WebSocket-Protocol: sample // // 8jKS'y:G*Co,Wxa- HTTPMessage *wsResponse = [[HTTPMessage alloc] initResponseWithStatusCode:101 description:@"Web Socket Protocol Handshake" version:HTTPVersion1_1]; [wsResponse setHeaderField:@"Upgrade" value:@"WebSocket"]; [wsResponse setHeaderField:@"Connection" value:@"Upgrade"]; // Note: It appears that WebSocket-Origin and WebSocket-Location // are required for Google's Chrome implementation to work properly. // // If we don't send either header, Chrome will never report the WebSocket as open. // If we only send one of the two, Chrome will immediately close the WebSocket. // // In addition to this it appears that Chrome's implementation is very picky of the values of the headers. // They have to match exactly with what Chrome sent us or it will close the WebSocket. NSString *originValue = [self originResponseHeaderValue]; NSString *locationValue = [self locationResponseHeaderValue]; NSString *originField = isVersion76 ? @"Sec-WebSocket-Origin" : @"WebSocket-Origin"; NSString *locationField = isVersion76 ? @"Sec-WebSocket-Location" : @"WebSocket-Location"; [wsResponse setHeaderField:originField value:originValue]; [wsResponse setHeaderField:locationField value:locationValue]; NSString *acceptValue = [self secWebSocketKeyResponseHeaderValue]; if (acceptValue) { [wsResponse setHeaderField: @"Sec-WebSocket-Accept" value: acceptValue]; } NSData *responseHeaders = [wsResponse messageData]; if (HTTP_LOG_VERBOSE) { NSString *temp = [[NSString alloc] initWithData:responseHeaders encoding:NSUTF8StringEncoding]; HTTPLogVerbose(@"%@[%p] Response Headers:\n%@", THIS_FILE, self, temp); } [asyncSocket writeData:responseHeaders withTimeout:TIMEOUT_NONE tag:TAG_HTTP_RESPONSE_HEADERS]; } - (NSData *)processKey:(NSString *)key { HTTPLogTrace(); unichar c; NSUInteger i; NSUInteger length = [key length]; // Concatenate the digits into a string, // and count the number of spaces. NSMutableString *numStr = [NSMutableString stringWithCapacity:10]; long long numSpaces = 0; for (i = 0; i < length; i++) { c = [key characterAtIndex:i]; if (c >= '0' && c <= '9') { [numStr appendFormat:@"%C", c]; } else if (c == ' ') { numSpaces++; } } long long num = strtoll([numStr UTF8String], NULL, 10); long long resultHostNum; if (numSpaces == 0) resultHostNum = 0; else resultHostNum = num / numSpaces; HTTPLogVerbose(@"key(%@) -> %qi / %qi = %qi", key, num, numSpaces, resultHostNum); // Convert result to 4 byte big-endian (network byte order) // and then convert to raw data. UInt32 result = OSSwapHostToBigInt32((uint32_t)resultHostNum); return [NSData dataWithBytes:&result length:4]; } - (void)sendResponseBody:(NSData *)d3 { HTTPLogTrace(); NSAssert(isVersion76, @"WebSocket version 75 doesn't contain a response body"); NSAssert([d3 length] == 8, @"Invalid requestBody length"); NSString *key1 = [request headerField:@"Sec-WebSocket-Key1"]; NSString *key2 = [request headerField:@"Sec-WebSocket-Key2"]; NSData *d1 = [self processKey:key1]; NSData *d2 = [self processKey:key2]; // Concatenated d1, d2 & d3 NSMutableData *d0 = [NSMutableData dataWithCapacity:(4+4+8)]; [d0 appendData:d1]; [d0 appendData:d2]; [d0 appendData:d3]; // Hash the data using MD5 NSData *responseBody = [d0 md5Digest]; [asyncSocket writeData:responseBody withTimeout:TIMEOUT_NONE tag:TAG_HTTP_RESPONSE_BODY]; if (HTTP_LOG_VERBOSE) { NSString *s1 = [[NSString alloc] initWithData:d1 encoding:NSASCIIStringEncoding]; NSString *s2 = [[NSString alloc] initWithData:d2 encoding:NSASCIIStringEncoding]; NSString *s3 = [[NSString alloc] initWithData:d3 encoding:NSASCIIStringEncoding]; NSString *s0 = [[NSString alloc] initWithData:d0 encoding:NSASCIIStringEncoding]; NSString *sH = [[NSString alloc] initWithData:responseBody encoding:NSASCIIStringEncoding]; HTTPLogVerbose(@"key1 result : raw(%@) str(%@)", d1, s1); HTTPLogVerbose(@"key2 result : raw(%@) str(%@)", d2, s2); HTTPLogVerbose(@"key3 passed : raw(%@) str(%@)", d3, s3); HTTPLogVerbose(@"key0 concat : raw(%@) str(%@)", d0, s0); HTTPLogVerbose(@"responseBody: raw(%@) str(%@)", responseBody, sH); } } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark Core Functionality //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - (void)didOpen { HTTPLogTrace(); // Override me to perform any custom actions once the WebSocket has been opened. // This method is invoked on the websocketQueue. // // Don't forget to invoke [super didOpen] in your method. // Start reading for messages [asyncSocket readDataToLength:1 withTimeout:TIMEOUT_NONE tag:(isRFC6455 ? TAG_PAYLOAD_PREFIX : TAG_PREFIX)]; // Notify delegate if ([delegate respondsToSelector:@selector(webSocketDidOpen:)]) { [delegate webSocketDidOpen:self]; } } - (void)sendMessage:(NSString *)msg { HTTPLogTrace(); NSData *msgData = [msg dataUsingEncoding:NSUTF8StringEncoding]; NSMutableData *data = nil; if (isRFC6455) { NSUInteger length = msgData.length; if (length <= 125) { data = [NSMutableData dataWithCapacity:(length + 2)]; [data appendBytes: "\x81" length:1]; UInt8 len = (UInt8)length; [data appendBytes: &len length:1]; [data appendData:msgData]; } else if (length <= 0xFFFF) { data = [NSMutableData dataWithCapacity:(length + 4)]; [data appendBytes: "\x81\x7E" length:2]; UInt16 len = (UInt16)length; [data appendBytes: (UInt8[]){len >> 8, len & 0xFF} length:2]; [data appendData:msgData]; } else { data = [NSMutableData dataWithCapacity:(length + 10)]; [data appendBytes: "\x81\x7F" length:2]; [data appendBytes: (UInt8[]){0, 0, 0, 0, (UInt8)(length >> 24), (UInt8)(length >> 16), (UInt8)(length >> 8), length & 0xFF} length:8]; [data appendData:msgData]; } } else { data = [NSMutableData dataWithCapacity:([msgData length] + 2)]; [data appendBytes:"\x00" length:1]; [data appendData:msgData]; [data appendBytes:"\xFF" length:1]; } // Remember: GCDAsyncSocket is thread-safe [asyncSocket writeData:data withTimeout:TIMEOUT_NONE tag:0]; } - (void)didReceiveMessage:(NSString *)msg { HTTPLogTrace(); // Override me to process incoming messages. // This method is invoked on the websocketQueue. // // For completeness, you should invoke [super didReceiveMessage:msg] in your method. // Notify delegate if ([delegate respondsToSelector:@selector(webSocket:didReceiveMessage:)]) { [delegate webSocket:self didReceiveMessage:msg]; } } - (void)didClose { HTTPLogTrace(); // Override me to perform any cleanup when the socket is closed // This method is invoked on the websocketQueue. // // Don't forget to invoke [super didClose] at the end of your method. // Notify delegate if ([delegate respondsToSelector:@selector(webSocketDidClose:)]) { [delegate webSocketDidClose:self]; } // Notify HTTPServer [[NSNotificationCenter defaultCenter] postNotificationName:WebSocketDidDieNotification object:self]; } #pragma mark WebSocket Frame - (BOOL)isValidWebSocketFrame:(UInt8)frame { NSUInteger rsv = frame & 0x70; NSUInteger opcode = frame & 0x0F; if (rsv || (3 <= opcode && opcode <= 7) || (0xB <= opcode && opcode <= 0xF)) { return NO; } return YES; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark AsyncSocket Delegate //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // 0 1 2 3 // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 // +-+-+-+-+-------+-+-------------+-------------------------------+ // |F|R|R|R| opcode|M| Payload len | Extended payload length | // |I|S|S|S| (4) |A| (7) | (16/64) | // |N|V|V|V| |S| | (if payload len==126/127) | // | |1|2|3| |K| | | // +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - + // | Extended payload length continued, if payload len == 127 | // + - - - - - - - - - - - - - - - +-------------------------------+ // | |Masking-key, if MASK set to 1 | // +-------------------------------+-------------------------------+ // | Masking-key (continued) | Payload Data | // +-------------------------------- - - - - - - - - - - - - - - - + // : Payload Data continued ... : // + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // | Payload Data continued ... | // +---------------------------------------------------------------+ - (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag { HTTPLogTrace(); if (tag == TAG_HTTP_REQUEST_BODY) { [self sendResponseHeaders]; [self sendResponseBody:data]; [self didOpen]; } else if (tag == TAG_PREFIX) { UInt8 *pFrame = (UInt8 *)[data bytes]; UInt8 frame = *pFrame; if (frame <= 0x7F) { [asyncSocket readDataToData:term withTimeout:TIMEOUT_NONE tag:TAG_MSG_PLUS_SUFFIX]; } else { // Unsupported frame type [self didClose]; } } else if (tag == TAG_PAYLOAD_PREFIX) { UInt8 *pFrame = (UInt8 *)[data bytes]; UInt8 frame = *pFrame; if ([self isValidWebSocketFrame: frame]) { nextOpCode = (frame & 0x0F); [asyncSocket readDataToLength:1 withTimeout:TIMEOUT_NONE tag:TAG_PAYLOAD_LENGTH]; } else { // Unsupported frame type [self didClose]; } } else if (tag == TAG_PAYLOAD_LENGTH) { UInt8 frame = *(UInt8 *)[data bytes]; BOOL masked = WS_PAYLOAD_IS_MASKED(frame); NSUInteger length = WS_PAYLOAD_LENGTH(frame); nextFrameMasked = masked; maskingKey = nil; if (length <= 125) { if (nextFrameMasked) { [asyncSocket readDataToLength:4 withTimeout:TIMEOUT_NONE tag:TAG_MSG_MASKING_KEY]; } [asyncSocket readDataToLength:length withTimeout:TIMEOUT_NONE tag:TAG_MSG_WITH_LENGTH]; } else if (length == 126) { [asyncSocket readDataToLength:2 withTimeout:TIMEOUT_NONE tag:TAG_PAYLOAD_LENGTH16]; } else { [asyncSocket readDataToLength:8 withTimeout:TIMEOUT_NONE tag:TAG_PAYLOAD_LENGTH64]; } } else if (tag == TAG_PAYLOAD_LENGTH16) { UInt8 *pFrame = (UInt8 *)[data bytes]; NSUInteger length = ((NSUInteger)pFrame[0] << 8) | (NSUInteger)pFrame[1]; if (nextFrameMasked) { [asyncSocket readDataToLength:4 withTimeout:TIMEOUT_NONE tag:TAG_MSG_MASKING_KEY]; } [asyncSocket readDataToLength:length withTimeout:TIMEOUT_NONE tag:TAG_MSG_WITH_LENGTH]; } else if (tag == TAG_PAYLOAD_LENGTH64) { // FIXME: 64bit data size in memory? [self didClose]; } else if (tag == TAG_MSG_WITH_LENGTH) { NSUInteger msgLength = [data length]; if (nextFrameMasked && maskingKey) { NSMutableData *masked = data.mutableCopy; UInt8 *pData = (UInt8 *)masked.mutableBytes; UInt8 *pMask = (UInt8 *)maskingKey.bytes; for (NSUInteger i = 0; i < msgLength; i++) { pData[i] = pData[i] ^ pMask[i % 4]; } data = masked; } if (nextOpCode == WS_OP_TEXT_FRAME) { NSString *msg = [[NSString alloc] initWithBytes:[data bytes] length:msgLength encoding:NSUTF8StringEncoding]; [self didReceiveMessage:msg]; } else { [self didClose]; return; } // Read next frame [asyncSocket readDataToLength:1 withTimeout:TIMEOUT_NONE tag:TAG_PAYLOAD_PREFIX]; } else if (tag == TAG_MSG_MASKING_KEY) { maskingKey = data.copy; } else { NSUInteger msgLength = [data length] - 1; // Excluding ending 0xFF frame NSString *msg = [[NSString alloc] initWithBytes:[data bytes] length:msgLength encoding:NSUTF8StringEncoding]; [self didReceiveMessage:msg]; // Read next message [asyncSocket readDataToLength:1 withTimeout:TIMEOUT_NONE tag:TAG_PREFIX]; } } - (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)error { HTTPLogTrace2(@"%@[%p]: socketDidDisconnect:withError: %@", THIS_FILE, self, error); [self didClose]; } @end ================================================ FILE: zhuishushenqi/Vendor/CocoaHTTPServer/Extensions/WebDAV/DAVConnection.h ================================================ #import "HTTPConnection.h" @interface DAVConnection : HTTPConnection { id requestContentBody; NSOutputStream* requestContentStream; } @end ================================================ FILE: zhuishushenqi/Vendor/CocoaHTTPServer/Extensions/WebDAV/DAVConnection.m ================================================ #import "DAVConnection.h" #import "HTTPMessage.h" #import "HTTPFileResponse.h" #import "HTTPAsyncFileResponse.h" #import "PUTResponse.h" #import "DELETEResponse.h" #import "DAVResponse.h" #import "HTTPLogging.h" #define HTTP_BODY_MAX_MEMORY_SIZE (1024 * 1024) #define HTTP_ASYNC_FILE_RESPONSE_THRESHOLD (16 * 1024 * 1024) static const int httpLogLevel = HTTP_LOG_LEVEL_WARN; @implementation DAVConnection - (void) dealloc { [requestContentStream close]; } - (BOOL) supportsMethod:(NSString*)method atPath:(NSString*)path { // HTTPFileResponse & HTTPAsyncFileResponse if ([method isEqualToString:@"GET"]) return YES; if ([method isEqualToString:@"HEAD"]) return YES; // PUTResponse if ([method isEqualToString:@"PUT"]) return YES; // DELETEResponse if ([method isEqualToString:@"DELETE"]) return YES; // DAVResponse if ([method isEqualToString:@"OPTIONS"]) return YES; if ([method isEqualToString:@"PROPFIND"]) return YES; if ([method isEqualToString:@"MKCOL"]) return YES; if ([method isEqualToString:@"MOVE"]) return YES; if ([method isEqualToString:@"COPY"]) return YES; if ([method isEqualToString:@"LOCK"]) return YES; if ([method isEqualToString:@"UNLOCK"]) return YES; return NO; } - (BOOL) expectsRequestBodyFromMethod:(NSString*)method atPath:(NSString*)path { // PUTResponse if ([method isEqualToString:@"PUT"]) { return YES; } // DAVResponse if ([method isEqual:@"PROPFIND"] || [method isEqual:@"MKCOL"]) { return [request headerField:@"Content-Length"] ? YES : NO; } if ([method isEqual:@"LOCK"]) { return YES; } return NO; } - (void) prepareForBodyWithSize:(UInt64)contentLength { NSAssert(requestContentStream == nil, @"requestContentStream should be nil"); NSAssert(requestContentBody == nil, @"requestContentBody should be nil"); if (contentLength > HTTP_BODY_MAX_MEMORY_SIZE) { requestContentBody = [[NSTemporaryDirectory() stringByAppendingString:[[NSProcessInfo processInfo] globallyUniqueString]] copy]; requestContentStream = [[NSOutputStream alloc] initToFileAtPath:requestContentBody append:NO]; [requestContentStream open]; } else { requestContentBody = [[NSMutableData alloc] initWithCapacity:(NSUInteger)contentLength]; requestContentStream = nil; } } - (void) processBodyData:(NSData*)postDataChunk { NSAssert(requestContentBody != nil, @"requestContentBody should not be nil"); if (requestContentStream) { [requestContentStream write:[postDataChunk bytes] maxLength:[postDataChunk length]]; } else { [(NSMutableData*)requestContentBody appendData:postDataChunk]; } } - (void) finishBody { NSAssert(requestContentBody != nil, @"requestContentBody should not be nil"); if (requestContentStream) { [requestContentStream close]; requestContentStream = nil; } } - (void)finishResponse { NSAssert(requestContentStream == nil, @"requestContentStream should be nil"); requestContentBody = nil; [super finishResponse]; } - (NSObject*) httpResponseForMethod:(NSString*)method URI:(NSString*)path { if ([method isEqualToString:@"HEAD"] || [method isEqualToString:@"GET"]) { NSString* filePath = [self filePathForURI:path allowDirectory:NO]; if (filePath) { NSDictionary* fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:NULL]; if (fileAttributes) { if ([[fileAttributes objectForKey:NSFileSize] unsignedLongLongValue] > HTTP_ASYNC_FILE_RESPONSE_THRESHOLD) { return [[HTTPAsyncFileResponse alloc] initWithFilePath:filePath forConnection:self]; } else { return [[HTTPFileResponse alloc] initWithFilePath:filePath forConnection:self]; } } } } if ([method isEqualToString:@"PUT"]) { NSString* filePath = [self filePathForURI:path allowDirectory:YES]; if (filePath) { if ([requestContentBody isKindOfClass:[NSString class]]) { return [[PUTResponse alloc] initWithFilePath:filePath headers:[request allHeaderFields] bodyFile:requestContentBody]; } else if ([requestContentBody isKindOfClass:[NSData class]]) { return [[PUTResponse alloc] initWithFilePath:filePath headers:[request allHeaderFields] bodyData:requestContentBody]; } else { HTTPLogError(@"Internal error"); } } } if ([method isEqualToString:@"DELETE"]) { NSString* filePath = [self filePathForURI:path allowDirectory:YES]; if (filePath) { return [[DELETEResponse alloc] initWithFilePath:filePath]; } } if ([method isEqualToString:@"OPTIONS"] || [method isEqualToString:@"PROPFIND"] || [method isEqualToString:@"MKCOL"] || [method isEqualToString:@"MOVE"] || [method isEqualToString:@"COPY"] || [method isEqualToString:@"LOCK"] || [method isEqualToString:@"UNLOCK"]) { NSString* filePath = [self filePathForURI:path allowDirectory:YES]; if (filePath) { NSString* rootPath = [config documentRoot]; NSString* resourcePath = [filePath substringFromIndex:([rootPath length] + 1)]; if (requestContentBody) { if ([requestContentBody isKindOfClass:[NSString class]]) { requestContentBody = [NSData dataWithContentsOfFile:requestContentBody]; } else if (![requestContentBody isKindOfClass:[NSData class]]) { HTTPLogError(@"Internal error"); return nil; } } return [[DAVResponse alloc] initWithMethod:method headers:[request allHeaderFields] bodyData:requestContentBody resourcePath:resourcePath rootPath:rootPath]; } } return nil; } @end ================================================ FILE: zhuishushenqi/Vendor/CocoaHTTPServer/Extensions/WebDAV/DAVResponse.h ================================================ #import "HTTPResponse.h" @interface DAVResponse : NSObject { @private UInt64 _offset; NSMutableDictionary* _headers; NSData* _data; NSInteger _status; } - (id) initWithMethod:(NSString*)method headers:(NSDictionary*)headers bodyData:(NSData*)body resourcePath:(NSString*)resourcePath rootPath:(NSString*)rootPath; @end ================================================ FILE: zhuishushenqi/Vendor/CocoaHTTPServer/Extensions/WebDAV/DAVResponse.m ================================================ #import #import "DAVResponse.h" #import "HTTPLogging.h" // WebDAV specifications: http://webdav.org/specs/rfc4918.html typedef enum { kDAVProperty_ResourceType = (1 << 0), kDAVProperty_CreationDate = (1 << 1), kDAVProperty_LastModified = (1 << 2), kDAVProperty_ContentLength = (1 << 3), kDAVAllProperties = kDAVProperty_ResourceType | kDAVProperty_CreationDate | kDAVProperty_LastModified | kDAVProperty_ContentLength } DAVProperties; #define kXMLParseOptions (XML_PARSE_NONET | XML_PARSE_RECOVER | XML_PARSE_NOBLANKS | XML_PARSE_COMPACT | XML_PARSE_NOWARNING | XML_PARSE_NOERROR) static const int httpLogLevel = HTTP_LOG_LEVEL_WARN; @implementation DAVResponse static void _AddPropertyResponse(NSString* itemPath, NSString* resourcePath, DAVProperties properties, NSMutableString* xmlString) { CFStringRef escapedPath = CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (__bridge CFStringRef)resourcePath, NULL, CFSTR("<&>?+"), kCFStringEncodingUTF8); if (escapedPath) { NSDictionary* attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:itemPath error:NULL]; BOOL isDirectory = [[attributes fileType] isEqualToString:NSFileTypeDirectory]; [xmlString appendString:@""]; [xmlString appendFormat:@"%@", escapedPath]; [xmlString appendString:@""]; [xmlString appendString:@""]; if (properties & kDAVProperty_ResourceType) { if (isDirectory) { [xmlString appendString:@""]; } else { [xmlString appendString:@""]; } } if ((properties & kDAVProperty_CreationDate) && [attributes objectForKey:NSFileCreationDate]) { NSDateFormatter* formatter = [[NSDateFormatter alloc] init]; formatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US"]; formatter.timeZone = [NSTimeZone timeZoneWithName:@"GMT"]; formatter.dateFormat = @"yyyy-MM-dd'T'HH:mm:ss'+00:00'"; [xmlString appendFormat:@"%@", [formatter stringFromDate:[attributes fileCreationDate]]]; } if ((properties & kDAVProperty_LastModified) && [attributes objectForKey:NSFileModificationDate]) { NSDateFormatter* formatter = [[NSDateFormatter alloc] init]; formatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US"]; formatter.timeZone = [NSTimeZone timeZoneWithName:@"GMT"]; formatter.dateFormat = @"EEE', 'd' 'MMM' 'yyyy' 'HH:mm:ss' GMT'"; [xmlString appendFormat:@"%@", [formatter stringFromDate:[attributes fileModificationDate]]]; } if ((properties & kDAVProperty_ContentLength) && !isDirectory && [attributes objectForKey:NSFileSize]) { [xmlString appendFormat:@"%qu", [attributes fileSize]]; } [xmlString appendString:@""]; [xmlString appendString:@"HTTP/1.1 200 OK"]; [xmlString appendString:@""]; [xmlString appendString:@"\n"]; CFRelease(escapedPath); } } static xmlNodePtr _XMLChildWithName(xmlNodePtr child, const xmlChar* name) { while (child) { if ((child->type == XML_ELEMENT_NODE) && !xmlStrcmp(child->name, name)) { return child; } child = child->next; } return NULL; } - (id) initWithMethod:(NSString*)method headers:(NSDictionary*)headers bodyData:(NSData*)body resourcePath:(NSString*)resourcePath rootPath:(NSString*)rootPath { if ((self = [super init])) { _status = 200; _headers = [[NSMutableDictionary alloc] init]; // 10.1 DAV Header if ([method isEqualToString:@"OPTIONS"]) { if ([[headers objectForKey:@"User-Agent"] hasPrefix:@"WebDAVFS/"]) { // Mac OS X WebDAV support [_headers setObject:@"1, 2" forKey:@"DAV"]; } else { [_headers setObject:@"1" forKey:@"DAV"]; } } // 9.1 PROPFIND Method if ([method isEqualToString:@"PROPFIND"]) { NSInteger depth; NSString* depthHeader = [headers objectForKey:@"Depth"]; if ([depthHeader isEqualToString:@"0"]) { depth = 0; } else if ([depthHeader isEqualToString:@"1"]) { depth = 1; } else { HTTPLogError(@"Unsupported DAV depth \"%@\"", depthHeader); return nil; } DAVProperties properties = 0; xmlDocPtr document = xmlReadMemory(body.bytes, (int)body.length, NULL, NULL, kXMLParseOptions); if (document) { xmlNodePtr node = _XMLChildWithName(document->children, (const xmlChar*)"propfind"); if (node) { node = _XMLChildWithName(node->children, (const xmlChar*)"prop"); } if (node) { node = node->children; while (node) { if (!xmlStrcmp(node->name, (const xmlChar*)"resourcetype")) { properties |= kDAVProperty_ResourceType; } else if (!xmlStrcmp(node->name, (const xmlChar*)"creationdate")) { properties |= kDAVProperty_CreationDate; } else if (!xmlStrcmp(node->name, (const xmlChar*)"getlastmodified")) { properties |= kDAVProperty_LastModified; } else if (!xmlStrcmp(node->name, (const xmlChar*)"getcontentlength")) { properties |= kDAVProperty_ContentLength; } else { HTTPLogWarn(@"Unknown DAV property requested \"%s\"", node->name); } node = node->next; } } else { HTTPLogWarn(@"HTTP Server: Invalid DAV properties\n%@", [[NSString alloc] initWithData:body encoding:NSUTF8StringEncoding]); } xmlFreeDoc(document); } if (!properties) { properties = kDAVAllProperties; } NSString* basePath = [rootPath stringByAppendingPathComponent:resourcePath]; if (![basePath hasPrefix:rootPath] || ![[NSFileManager defaultManager] fileExistsAtPath:basePath]) { return nil; } NSMutableString* xmlString = [NSMutableString stringWithString:@""]; [xmlString appendString:@"\n"]; if (![resourcePath hasPrefix:@"/"]) { resourcePath = [@"/" stringByAppendingString:resourcePath]; } _AddPropertyResponse(basePath, resourcePath, properties, xmlString); if (depth == 1) { if (![resourcePath hasSuffix:@"/"]) { resourcePath = [resourcePath stringByAppendingString:@"/"]; } NSDirectoryEnumerator* enumerator = [[NSFileManager defaultManager] enumeratorAtPath:basePath]; NSString* path; while ((path = [enumerator nextObject])) { _AddPropertyResponse([basePath stringByAppendingPathComponent:path], [resourcePath stringByAppendingString:path], properties, xmlString); [enumerator skipDescendents]; } } [xmlString appendString:@""]; [_headers setObject:@"application/xml; charset=\"utf-8\"" forKey:@"Content-Type"]; _data = [xmlString dataUsingEncoding:NSUTF8StringEncoding]; _status = 207; } // 9.3 MKCOL Method if ([method isEqualToString:@"MKCOL"]) { NSString* path = [rootPath stringByAppendingPathComponent:resourcePath]; if (![path hasPrefix:rootPath]) { return nil; } if (![[NSFileManager defaultManager] fileExistsAtPath:[path stringByDeletingLastPathComponent]]) { HTTPLogError(@"Missing intermediate collection(s) at \"%@\"", path); _status = 409; } else if (![[NSFileManager defaultManager] createDirectoryAtPath:path withIntermediateDirectories:NO attributes:nil error:NULL]) { HTTPLogError(@"Failed creating collection at \"%@\"", path); _status = 405; } } // 9.8 COPY Method // 9.9 MOVE Method if ([method isEqualToString:@"MOVE"] || [method isEqualToString:@"COPY"]) { if ([method isEqualToString:@"COPY"] && ![[headers objectForKey:@"Depth"] isEqualToString:@"infinity"]) { HTTPLogError(@"Unsupported DAV depth \"%@\"", [headers objectForKey:@"Depth"]); return nil; } NSString* sourcePath = [rootPath stringByAppendingPathComponent:resourcePath]; if (![sourcePath hasPrefix:rootPath] || ![[NSFileManager defaultManager] fileExistsAtPath:sourcePath]) { return nil; } NSString* destination = [headers objectForKey:@"Destination"]; NSRange range = [destination rangeOfString:[headers objectForKey:@"Host"]]; if (range.location == NSNotFound) { return nil; } NSString* destinationPath = [rootPath stringByAppendingPathComponent: [[destination substringFromIndex:(range.location + range.length)] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]; if (![destinationPath hasPrefix:rootPath] || [[NSFileManager defaultManager] fileExistsAtPath:destinationPath]) { return nil; } BOOL isDirectory; if (![[NSFileManager defaultManager] fileExistsAtPath:[destinationPath stringByDeletingLastPathComponent] isDirectory:&isDirectory] || !isDirectory) { HTTPLogError(@"Invalid destination path \"%@\"", destinationPath); _status = 409; } else { BOOL existing = [[NSFileManager defaultManager] fileExistsAtPath:destinationPath]; if (existing && [[headers objectForKey:@"Overwrite"] isEqualToString:@"F"]) { HTTPLogError(@"Pre-existing destination path \"%@\"", destinationPath); _status = 412; } else { if ([method isEqualToString:@"COPY"]) { if ([[NSFileManager defaultManager] copyItemAtPath:sourcePath toPath:destinationPath error:NULL]) { _status = existing ? 204 : 201; } else { HTTPLogError(@"Failed copying \"%@\" to \"%@\"", sourcePath, destinationPath); _status = 403; } } else { if ([[NSFileManager defaultManager] moveItemAtPath:sourcePath toPath:destinationPath error:NULL]) { _status = existing ? 204 : 201; } else { HTTPLogError(@"Failed moving \"%@\" to \"%@\"", sourcePath, destinationPath); _status = 403; } } } } } // 9.10 LOCK Method - TODO: Actually lock the resource if ([method isEqualToString:@"LOCK"]) { NSString* path = [rootPath stringByAppendingPathComponent:resourcePath]; if (![path hasPrefix:rootPath]) { return nil; } NSString* depth = [headers objectForKey:@"Depth"]; NSString* scope = nil; NSString* type = nil; NSString* owner = nil; xmlDocPtr document = xmlReadMemory(body.bytes, (int)body.length, NULL, NULL, kXMLParseOptions); if (document) { xmlNodePtr node = _XMLChildWithName(document->children, (const xmlChar*)"lockinfo"); if (node) { xmlNodePtr scopeNode = _XMLChildWithName(node->children, (const xmlChar*)"lockscope"); if (scopeNode && scopeNode->children && scopeNode->children->name) { scope = [NSString stringWithUTF8String:(const char*)scopeNode->children->name]; } xmlNodePtr typeNode = _XMLChildWithName(node->children, (const xmlChar*)"locktype"); if (typeNode && typeNode->children && typeNode->children->name) { type = [NSString stringWithUTF8String:(const char*)typeNode->children->name]; } xmlNodePtr ownerNode = _XMLChildWithName(node->children, (const xmlChar*)"owner"); if (ownerNode) { ownerNode = _XMLChildWithName(ownerNode->children, (const xmlChar*)"href"); if (ownerNode && ownerNode->children && ownerNode->children->content) { owner = [NSString stringWithUTF8String:(const char*)ownerNode->children->content]; } } } else { HTTPLogWarn(@"HTTP Server: Invalid DAV properties\n%@", [[NSString alloc] initWithData:body encoding:NSUTF8StringEncoding]); } xmlFreeDoc(document); } if ([scope isEqualToString:@"exclusive"] && [type isEqualToString:@"write"] && [depth isEqualToString:@"0"] && ([[NSFileManager defaultManager] fileExistsAtPath:path] || [[NSData data] writeToFile:path atomically:YES])) { NSString* timeout = [headers objectForKey:@"Timeout"]; CFUUIDRef uuid = CFUUIDCreate(kCFAllocatorDefault); NSString *uuidStr = (__bridge_transfer NSString *)CFUUIDCreateString(kCFAllocatorDefault, uuid); NSString* token = [NSString stringWithFormat:@"urn:uuid:%@", uuidStr]; CFRelease(uuid); NSMutableString* xmlString = [NSMutableString stringWithString:@""]; [xmlString appendString:@"\n"]; [xmlString appendString:@"\n\n"]; [xmlString appendFormat:@"\n", type]; [xmlString appendFormat:@"\n", scope]; [xmlString appendFormat:@"%@\n", depth]; if (owner) { [xmlString appendFormat:@"%@\n", owner]; } if (timeout) { [xmlString appendFormat:@"%@\n", timeout]; } [xmlString appendFormat:@"%@\n", token]; // [xmlString appendFormat:@"%@\n", root]; [xmlString appendString:@"\n\n"]; [xmlString appendString:@""]; [_headers setObject:@"application/xml; charset=\"utf-8\"" forKey:@"Content-Type"]; _data = [xmlString dataUsingEncoding:NSUTF8StringEncoding]; _status = 200; HTTPLogVerbose(@"Pretending to lock \"%@\"", resourcePath); } else { HTTPLogError(@"Locking request \"%@/%@/%@\" for \"%@\" is not allowed", scope, type, depth, resourcePath); _status = 403; } } // 9.11 UNLOCK Method - TODO: Actually unlock the resource if ([method isEqualToString:@"UNLOCK"]) { NSString* path = [rootPath stringByAppendingPathComponent:resourcePath]; if (![path hasPrefix:rootPath] || ![[NSFileManager defaultManager] fileExistsAtPath:path]) { return nil; } NSString* token = [headers objectForKey:@"Lock-Token"]; _status = token ? 204 : 400; HTTPLogVerbose(@"Pretending to unlock \"%@\"", resourcePath); } } return self; } - (UInt64) contentLength { return _data ? _data.length : 0; } - (UInt64) offset { return _offset; } - (void) setOffset:(UInt64)offset { _offset = offset; } - (NSData*) readDataOfLength:(NSUInteger)lengthParameter { if (_data) { NSUInteger remaining = _data.length - (NSUInteger)_offset; NSUInteger length = lengthParameter < remaining ? lengthParameter : remaining; void* bytes = (void*)(_data.bytes + _offset); _offset += length; return [NSData dataWithBytesNoCopy:bytes length:length freeWhenDone:NO]; } return nil; } - (BOOL) isDone { return _data ? _offset == _data.length : YES; } - (NSInteger) status { return _status; } - (NSDictionary*) httpHeaders { return _headers; } @end ================================================ FILE: zhuishushenqi/Vendor/CocoaHTTPServer/Extensions/WebDAV/DELETEResponse.h ================================================ #import "HTTPResponse.h" @interface DELETEResponse : NSObject { NSInteger _status; } - (id) initWithFilePath:(NSString*)path; @end ================================================ FILE: zhuishushenqi/Vendor/CocoaHTTPServer/Extensions/WebDAV/DELETEResponse.m ================================================ #import "DELETEResponse.h" #import "HTTPLogging.h" // HTTP methods: http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html // HTTP headers: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html // HTTP status codes: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html static const int httpLogLevel = HTTP_LOG_LEVEL_WARN; @implementation DELETEResponse - (id) initWithFilePath:(NSString*)path { if ((self = [super init])) { BOOL exists = [[NSFileManager defaultManager] fileExistsAtPath:path]; if ([[NSFileManager defaultManager] removeItemAtPath:path error:NULL]) { _status = exists ? 200 : 204; } else { HTTPLogError(@"Failed deleting \"%@\"", path); _status = 404; } } return self; } - (UInt64) contentLength { return 0; } - (UInt64) offset { return 0; } - (void)setOffset:(UInt64)offset { ; } - (NSData*) readDataOfLength:(NSUInteger)length { return nil; } - (BOOL) isDone { return YES; } - (NSInteger) status { return _status; } @end ================================================ FILE: zhuishushenqi/Vendor/CocoaHTTPServer/Extensions/WebDAV/PUTResponse.h ================================================ #import "HTTPResponse.h" @interface PUTResponse : NSObject { NSInteger _status; } - (id) initWithFilePath:(NSString*)path headers:(NSDictionary*)headers bodyData:(NSData*)body; - (id) initWithFilePath:(NSString*)path headers:(NSDictionary*)headers bodyFile:(NSString*)body; @end ================================================ FILE: zhuishushenqi/Vendor/CocoaHTTPServer/Extensions/WebDAV/PUTResponse.m ================================================ #import "PUTResponse.h" #import "HTTPLogging.h" // HTTP methods: http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html // HTTP headers: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html // HTTP status codes: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html static const int httpLogLevel = HTTP_LOG_LEVEL_WARN; @implementation PUTResponse - (id) initWithFilePath:(NSString*)path headers:(NSDictionary*)headers body:(id)body { if ((self = [super init])) { if ([headers objectForKey:@"Content-Range"]) { HTTPLogError(@"Content-Range not supported for upload to \"%@\"", path); _status = 400; } else { BOOL overwrite = [[NSFileManager defaultManager] fileExistsAtPath:path]; BOOL success; if ([body isKindOfClass:[NSString class]]) { [[NSFileManager defaultManager] removeItemAtPath:path error:NULL]; success = [[NSFileManager defaultManager] moveItemAtPath:body toPath:path error:NULL]; } else { success = [body writeToFile:path atomically:YES]; } if (success) { _status = overwrite ? 200 : 201; } else { HTTPLogError(@"Failed writing upload to \"%@\"", path); _status = 403; } } } return self; } - (id) initWithFilePath:(NSString*)path headers:(NSDictionary*)headers bodyData:(NSData*)body { return [self initWithFilePath:path headers:headers body:body]; } - (id) initWithFilePath:(NSString*)path headers:(NSDictionary*)headers bodyFile:(NSString*)body { return [self initWithFilePath:path headers:headers body:body]; } - (UInt64) contentLength { return 0; } - (UInt64) offset { return 0; } - (void) setOffset:(UInt64)offset { ; } - (NSData*) readDataOfLength:(NSUInteger)length { return nil; } - (BOOL) isDone { return YES; } - (NSInteger) status { return _status; } @end ================================================ FILE: zhuishushenqi/Vendor/CocoaHTTPServer/LICENSE.txt ================================================ Software License Agreement (BSD License) Copyright (c) 2011, Deusty, LLC All rights reserved. Redistribution and use of this software in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Neither the name of Deusty nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission of Deusty, LLC. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ================================================ FILE: zhuishushenqi/Vendor/CocoaHTTPServer/README.markdown ================================================ CocoaHTTPServer is a small, lightweight, embeddable HTTP server for Mac OS X or iOS applications. Sometimes developers need an embedded HTTP server in their app. Perhaps it's a server application with remote monitoring. Or perhaps it's a desktop application using HTTP for the communication backend. Or perhaps it's an iOS app providing over-the-air access to documents. Whatever your reason, CocoaHTTPServer can get the job done. It provides: - Built in support for bonjour broadcasting - IPv4 and IPv6 support - Asynchronous networking using GCD and standard sockets - Password protection support - SSL/TLS encryption support - Extremely FAST and memory efficient - Extremely scalable (built entirely upon GCD) - Heavily commented code - Very easily extensible - WebDAV is supported too!
    Can't find the answer to your question in any of the [wiki](https://github.com/robbiehanson/CocoaHTTPServer/wiki) articles? Try the **[mailing list](http://groups.google.com/group/cocoahttpserver)**.

    Love the project? Wanna buy me a coffee? (or a beer :D) [![donation](http://www.paypal.com/en_US/i/btn/btn_donate_SM.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=BHF2DJRETGV5S) ================================================ FILE: zhuishushenqi/Vendor/M80AttributedLabel/M80AttributedLabel.h ================================================ // // M80AttributedLabel.h // M80AttributedLabel // // Created by amao on 13-9-1. // Copyright (c) 2013年 www.xiangwangfeng.com. All rights reserved. // #import "M80AttributedLabelDefines.h" #import "NSMutableAttributedString+M80.h" NS_ASSUME_NONNULL_BEGIN @class M80AttributedLabelURL; @interface M80AttributedLabel : UIView @property (nonatomic,weak,nullable) id delegate; @property (nonatomic,strong,nullable) UIFont *font; //字体 @property (nonatomic,strong,nullable) UIColor *textColor; //文字颜色 @property (nonatomic,strong,nullable) UIColor *highlightColor; //链接点击时背景高亮色 @property (nonatomic,strong,nullable) UIColor *linkColor; //链接色 @property (nonatomic,strong,nullable) UIColor *shadowColor; //阴影颜色 @property (nonatomic,assign) CGSize shadowOffset; //阴影offset @property (nonatomic,assign) CGFloat shadowBlur; //阴影半径 @property (nonatomic,assign) BOOL underLineForLink; //链接是否带下划线 @property (nonatomic,assign) BOOL autoDetectLinks; //自动检测 @property (nonatomic,assign) NSInteger numberOfLines; //行数 @property (nonatomic,assign) CTTextAlignment textAlignment; //文字排版样式 @property (nonatomic,assign) CTLineBreakMode lineBreakMode; //LineBreakMode @property (nonatomic,assign) CGFloat lineSpacing; //行间距 @property (nonatomic,assign) CGFloat paragraphSpacing; //段间距 @property (nonatomic,copy,nullable) NSString *text; //普通文本 @property (nonatomic,copy,nullable) NSAttributedString *attributedText; //属性文本 //添加文本 - (void)appendText:(NSString *)text; - (void)appendAttributedText:(NSAttributedString *)attributedText; //图片 - (void)appendImage:(UIImage *)image; - (void)appendImage:(UIImage *)image maxSize:(CGSize)maxSize; - (void)appendImage:(UIImage *)image maxSize:(CGSize)maxSize margin:(UIEdgeInsets)margin; - (void)appendImage:(UIImage *)image maxSize:(CGSize)maxSize margin:(UIEdgeInsets)margin alignment:(M80ImageAlignment)alignment; //UI控件 - (void)appendView:(UIView *)view; - (void)appendView:(UIView *)view margin:(UIEdgeInsets)margin; - (void)appendView:(UIView *)view margin:(UIEdgeInsets)margin alignment:(M80ImageAlignment)alignment; //添加自定义链接 - (void)addCustomLink:(id)linkData forRange:(NSRange)range; - (void)addCustomLink:(id)linkData forRange:(NSRange)range linkColor:(UIColor *)color; //大小 - (CGSize)sizeThatFits:(CGSize)size; //设置全局的自定义Link检测Block(详见M80AttributedLabelURL) + (void)setCustomDetectMethod:(nullable M80CustomDetectLinkBlock)block; @end NS_ASSUME_NONNULL_END ================================================ FILE: zhuishushenqi/Vendor/M80AttributedLabel/M80AttributedLabel.m ================================================ // // M80AttributedLabel.m // M80AttributedLabel // // Created by amao on 13-9-1. // Copyright (c) 2013年 www.xiangwangfeng.com. All rights reserved. // #import "M80AttributedLabel.h" #import "M80AttributedLabelAttachment.h" #import "M80AttributedLabelURL.h" static NSString* const M80EllipsesCharacter = @"\u2026"; static dispatch_queue_t m80_attributed_label_parse_queue; static dispatch_queue_t get_m80_attributed_label_parse_queue() \ { if (m80_attributed_label_parse_queue == NULL) { m80_attributed_label_parse_queue = dispatch_queue_create("com.m80.parse_queue", 0); } return m80_attributed_label_parse_queue; } @interface M80AttributedLabel () { NSMutableArray *_attachments; NSMutableArray *_linkLocations; CTFrameRef _textFrame; CGFloat _fontAscent; CGFloat _fontDescent; CGFloat _fontHeight; } @property (nonatomic,strong) NSMutableAttributedString *attributedString; @property (nonatomic,strong) M80AttributedLabelURL *touchedLink; @property (nonatomic,assign) BOOL linkDetected; @property (nonatomic,assign) BOOL ignoreRedraw; @end @implementation M80AttributedLabel - (id)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { [self commonInit]; } return self; } - (id)initWithCoder:(NSCoder *)aDecoder { self = [super initWithCoder:aDecoder]; if (self) { [self commonInit]; } return self; } - (void)dealloc { if (_textFrame) { CFRelease(_textFrame); } } #pragma mark - 初始化 - (void)commonInit { _attributedString = [[NSMutableAttributedString alloc]init]; _attachments = [[NSMutableArray alloc]init]; _linkLocations = [[NSMutableArray alloc]init]; _textFrame = nil; _linkColor = [UIColor blueColor]; _font = [UIFont systemFontOfSize:15]; _textColor = [UIColor blackColor]; _highlightColor = [UIColor colorWithRed:0xd7/255.0 green:0xf2/255.0 blue:0xff/255.0 alpha:1]; _lineBreakMode = kCTLineBreakByWordWrapping; _underLineForLink = YES; _autoDetectLinks = YES; _lineSpacing = 0.0; _paragraphSpacing = 0.0; if (self.backgroundColor == nil) { self.backgroundColor = [UIColor whiteColor]; } self.userInteractionEnabled = YES; [self resetFont]; } - (void)cleanAll { _ignoreRedraw = NO; _linkDetected = NO; [_attachments removeAllObjects]; [_linkLocations removeAllObjects]; self.touchedLink = nil; for (UIView *subView in self.subviews) { [subView removeFromSuperview]; } [self resetTextFrame]; } - (void)resetTextFrame { if (_textFrame) { CFRelease(_textFrame); _textFrame = nil; } if ([NSThread isMainThread] && !_ignoreRedraw) { [self setNeedsDisplay]; } } - (void)resetFont { CTFontRef fontRef = CTFontCreateWithName((CFStringRef)self.font.fontName, self.font.pointSize, NULL); if (fontRef) { _fontAscent = CTFontGetAscent(fontRef); _fontDescent = CTFontGetDescent(fontRef); _fontHeight = CTFontGetSize(fontRef); CFRelease(fontRef); } } #pragma mark - 属性设置 //保证正常绘制,如果传入nil就直接不处理 - (void)setFont:(UIFont *)font { if (font && _font != font) { _font = font; [_attributedString m80_setFont:_font]; [self resetFont]; for (M80AttributedLabelAttachment *attachment in _attachments) { attachment.fontAscent = _fontAscent; attachment.fontDescent = _fontDescent; } [self resetTextFrame]; } } - (void)setTextColor:(UIColor *)textColor { if (textColor && _textColor != textColor) { _textColor = textColor; [_attributedString m80_setTextColor:textColor]; [self resetTextFrame]; } } - (void)setHighlightColor:(UIColor *)highlightColor { if (highlightColor && _highlightColor != highlightColor) { _highlightColor = highlightColor; [self resetTextFrame]; } } - (void)setLinkColor:(UIColor *)linkColor { if (_linkColor != linkColor) { _linkColor = linkColor; [self resetTextFrame]; } } - (void)setFrame:(CGRect)frame { CGRect oldRect = self.bounds; [super setFrame:frame]; if (!CGRectEqualToRect(self.bounds, oldRect)) { [self resetTextFrame]; } } - (void)setBounds:(CGRect)bounds { CGRect oldRect = self.bounds; [super setBounds:bounds]; if (!CGRectEqualToRect(self.bounds, oldRect)) { [self resetTextFrame]; } } - (void)setShadowColor:(UIColor *)shadowColor { if (_shadowColor != shadowColor) { _shadowColor = shadowColor; [self resetTextFrame]; } } - (void)setShadowOffset:(CGSize)shadowOffset { if (!CGSizeEqualToSize(_shadowOffset, shadowOffset)) { _shadowOffset = shadowOffset; [self resetTextFrame]; } } - (void)setShadowBlur:(CGFloat)shadowBlur { if (_shadowBlur != shadowBlur) { _shadowBlur = shadowBlur; [self resetTextFrame]; } } #pragma mark - 辅助方法 - (NSAttributedString *)attributedString:(NSString *)text { if ([text length]) { NSMutableAttributedString *string = [[NSMutableAttributedString alloc]initWithString:text]; [string m80_setFont:self.font]; [string m80_setTextColor:self.textColor]; return string; } else { return [[NSAttributedString alloc] init]; } } - (NSInteger)numberOfDisplayedLines { CFArrayRef lines = CTFrameGetLines(_textFrame); return _numberOfLines > 0 ? MIN(CFArrayGetCount(lines), _numberOfLines) : CFArrayGetCount(lines); } - (NSAttributedString *)attributedStringForDraw { if (_attributedString) { //添加排版格式 NSMutableAttributedString *drawString = [_attributedString mutableCopy]; //如果LineBreakMode为TranncateTail,那么默认排版模式改成kCTLineBreakByCharWrapping,使得尽可能地显示所有文字 CTLineBreakMode lineBreakMode = self.lineBreakMode; if (self.lineBreakMode == kCTLineBreakByTruncatingTail) { lineBreakMode = _numberOfLines == 1 ? kCTLineBreakByTruncatingTail : kCTLineBreakByWordWrapping; } CGFloat fontLineHeight = self.font.lineHeight; //使用全局fontHeight作为最小lineHeight CTParagraphStyleSetting settings[] = { {kCTParagraphStyleSpecifierAlignment,sizeof(_textAlignment),&_textAlignment}, {kCTParagraphStyleSpecifierLineBreakMode,sizeof(lineBreakMode),&lineBreakMode}, {kCTParagraphStyleSpecifierMaximumLineSpacing,sizeof(_lineSpacing),&_lineSpacing}, {kCTParagraphStyleSpecifierMinimumLineSpacing,sizeof(_lineSpacing),&_lineSpacing}, {kCTParagraphStyleSpecifierParagraphSpacing,sizeof(_paragraphSpacing),&_paragraphSpacing}, {kCTParagraphStyleSpecifierMinimumLineHeight,sizeof(fontLineHeight),&fontLineHeight}, }; CTParagraphStyleRef paragraphStyle = CTParagraphStyleCreate(settings,sizeof(settings) / sizeof(settings[0])); [drawString addAttribute:(id)kCTParagraphStyleAttributeName value:(__bridge id)paragraphStyle range:NSMakeRange(0, [drawString length])]; CFRelease(paragraphStyle); for (M80AttributedLabelURL *url in _linkLocations) { if (url.range.location + url.range.length >[_attributedString length]) { continue; } UIColor *drawLinkColor = url.color ? : self.linkColor; [drawString m80_setTextColor:drawLinkColor range:url.range]; [drawString m80_setUnderlineStyle:_underLineForLink ? kCTUnderlineStyleSingle : kCTUnderlineStyleNone modifier:kCTUnderlinePatternSolid range:url.range]; } return drawString; } else { return nil; } } - (M80AttributedLabelURL *)urlForPoint:(CGPoint)point { static const CGFloat kVMargin = 5; if (!CGRectContainsPoint(CGRectInset(self.bounds, 0, -kVMargin), point) || _textFrame == nil) { return nil; } CFArrayRef lines = CTFrameGetLines(_textFrame); if (!lines) return nil; CFIndex count = CFArrayGetCount(lines); CGPoint origins[count]; CTFrameGetLineOrigins(_textFrame, CFRangeMake(0,0), origins); CGAffineTransform transform = [self transformForCoreText]; CGFloat verticalOffset = 0; //不像Nimbus一样设置文字的对齐方式,都统一是TOP,那么offset就为0 for (int i = 0; i < count; i++) { CGPoint linePoint = origins[i]; CTLineRef line = CFArrayGetValueAtIndex(lines, i); CGRect flippedRect = [self getLineBounds:line point:linePoint]; CGRect rect = CGRectApplyAffineTransform(flippedRect, transform); rect = CGRectInset(rect, 0, -kVMargin); rect = CGRectOffset(rect, 0, verticalOffset); if (CGRectContainsPoint(rect, point)) { CGPoint relativePoint = CGPointMake(point.x-CGRectGetMinX(rect), point.y-CGRectGetMinY(rect)); CFIndex idx = CTLineGetStringIndexForPosition(line, relativePoint); M80AttributedLabelURL *url = [self linkAtIndex:idx]; if (url) { return url; } } } return nil; } - (id)linkDataForPoint:(CGPoint)point { M80AttributedLabelURL *url = [self urlForPoint:point]; return url ? url.linkData : nil; } - (CGAffineTransform)transformForCoreText { return CGAffineTransformScale(CGAffineTransformMakeTranslation(0, self.bounds.size.height), 1.f, -1.f); } - (CGRect)getLineBounds:(CTLineRef)line point:(CGPoint) point { CGFloat ascent = 0.0f; CGFloat descent = 0.0f; CGFloat leading = 0.0f; CGFloat width = (CGFloat)CTLineGetTypographicBounds(line, &ascent, &descent, &leading); CGFloat height = ascent + descent; return CGRectMake(point.x, point.y - descent, width, height); } - (M80AttributedLabelURL *)linkAtIndex:(CFIndex)index { for (M80AttributedLabelURL *url in _linkLocations) { if (NSLocationInRange(index, url.range)) { return url; } } return nil; } - (CGRect)rectForRange:(NSRange)range inLine:(CTLineRef)line lineOrigin:(CGPoint)lineOrigin { CGRect rectForRange = CGRectZero; CFArrayRef runs = CTLineGetGlyphRuns(line); CFIndex runCount = CFArrayGetCount(runs); // Iterate through each of the "runs" (i.e. a chunk of text) and find the runs that // intersect with the range. for (CFIndex k = 0; k < runCount; k++) { CTRunRef run = CFArrayGetValueAtIndex(runs, k); CFRange stringRunRange = CTRunGetStringRange(run); NSRange lineRunRange = NSMakeRange(stringRunRange.location, stringRunRange.length); NSRange intersectedRunRange = NSIntersectionRange(lineRunRange, range); if (intersectedRunRange.length == 0) { // This run doesn't intersect the range, so skip it. continue; } CGFloat ascent = 0.0f; CGFloat descent = 0.0f; CGFloat leading = 0.0f; // Use of 'leading' doesn't properly highlight Japanese-character link. CGFloat width = (CGFloat)CTRunGetTypographicBounds(run, CFRangeMake(0, 0), &ascent, &descent, NULL); //&leading); CGFloat height = ascent + descent; CGFloat xOffset = CTLineGetOffsetForStringIndex(line, CTRunGetStringRange(run).location, nil); CGRect linkRect = CGRectMake(lineOrigin.x + xOffset - leading, lineOrigin.y - descent, width + leading, height); linkRect.origin.y = roundf(linkRect.origin.y); linkRect.origin.x = roundf(linkRect.origin.x); linkRect.size.width = roundf(linkRect.size.width); linkRect.size.height = roundf(linkRect.size.height); rectForRange = CGRectIsEmpty(rectForRange) ? linkRect : CGRectUnion(rectForRange, linkRect); } return rectForRange; } - (void)appendAttachment:(M80AttributedLabelAttachment *)attachment { attachment.fontAscent = _fontAscent; attachment.fontDescent = _fontDescent; unichar objectReplacementChar = 0xFFFC; NSString *objectReplacementString = [NSString stringWithCharacters:&objectReplacementChar length:1]; NSMutableAttributedString *attachText = [[NSMutableAttributedString alloc]initWithString:objectReplacementString]; CTRunDelegateCallbacks callbacks; callbacks.version = kCTRunDelegateVersion1; callbacks.getAscent = ascentCallback; callbacks.getDescent = descentCallback; callbacks.getWidth = widthCallback; callbacks.dealloc = deallocCallback; CTRunDelegateRef delegate = CTRunDelegateCreate(&callbacks, (void *)attachment); NSDictionary *attr = [NSDictionary dictionaryWithObjectsAndKeys:(__bridge id)delegate,kCTRunDelegateAttributeName, nil]; [attachText setAttributes:attr range:NSMakeRange(0, 1)]; CFRelease(delegate); [_attachments addObject:attachment]; [self appendAttributedText:attachText]; } #pragma mark - 设置文本 - (void)setText:(NSString *)text { NSAttributedString *attributedText = [self attributedString:text]; [self setAttributedText:attributedText]; } - (void)setAttributedText:(NSAttributedString *)attributedText { _attributedString = [[NSMutableAttributedString alloc]initWithAttributedString:attributedText]; [self cleanAll]; } - (NSString *)text { return [_attributedString string]; } - (NSAttributedString *)attributedText { return [_attributedString copy]; } #pragma mark - 添加文本 - (void)appendText:(NSString *)text { NSAttributedString *attributedText = [self attributedString:text]; [self appendAttributedText:attributedText]; } - (void)appendAttributedText:(NSAttributedString *)attributedText { [_attributedString appendAttributedString:attributedText]; [self resetTextFrame]; } #pragma mark - 添加图片 - (void)appendImage:(UIImage *)image { [self appendImage:image maxSize:image.size]; } - (void)appendImage:(UIImage *)image maxSize:(CGSize)maxSize { [self appendImage:image maxSize:maxSize margin:UIEdgeInsetsZero]; } - (void)appendImage:(UIImage *)image maxSize:(CGSize)maxSize margin:(UIEdgeInsets)margin { [self appendImage:image maxSize:maxSize margin:margin alignment:M80ImageAlignmentBottom]; } - (void)appendImage:(UIImage *)image maxSize:(CGSize)maxSize margin:(UIEdgeInsets)margin alignment:(M80ImageAlignment)alignment { M80AttributedLabelAttachment *attachment = [M80AttributedLabelAttachment attachmentWith:image margin:margin alignment:alignment maxSize:maxSize]; [self appendAttachment:attachment]; } #pragma mark - 添加UI控件 - (void)appendView:(UIView *)view { [self appendView:view margin:UIEdgeInsetsZero]; } - (void)appendView:(UIView *)view margin:(UIEdgeInsets)margin { [self appendView:view margin:margin alignment:M80ImageAlignmentBottom]; } - (void)appendView:(UIView *)view margin:(UIEdgeInsets)margin alignment:(M80ImageAlignment)alignment { M80AttributedLabelAttachment *attachment = [M80AttributedLabelAttachment attachmentWith:view margin:margin alignment:alignment maxSize:CGSizeZero]; [self appendAttachment:attachment]; } #pragma mark - 添加链接 - (void)addCustomLink:(id)linkData forRange:(NSRange)range { [self addCustomLink:linkData forRange:range linkColor:self.linkColor]; } - (void)addCustomLink:(id)linkData forRange:(NSRange)range linkColor:(UIColor *)color { M80AttributedLabelURL *url = [M80AttributedLabelURL urlWithLinkData:linkData range:range color:color]; [_linkLocations addObject:url]; [self resetTextFrame]; } #pragma mark - 计算大小 - (CGSize)sizeThatFits:(CGSize)size { NSAttributedString *drawString = [self attributedStringForDraw]; if (drawString == nil) { return CGSizeZero; } CFAttributedStringRef attributedStringRef = (__bridge CFAttributedStringRef)drawString; CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(attributedStringRef); CFRange range = CFRangeMake(0, 0); if (_numberOfLines > 0 && framesetter) { CGMutablePathRef path = CGPathCreateMutable(); CGPathAddRect(path, NULL, CGRectMake(0, 0, size.width, size.height)); CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), path, NULL); CFArrayRef lines = CTFrameGetLines(frame); if (nil != lines && CFArrayGetCount(lines) > 0) { NSInteger lastVisibleLineIndex = MIN(_numberOfLines, CFArrayGetCount(lines)) - 1; CTLineRef lastVisibleLine = CFArrayGetValueAtIndex(lines, lastVisibleLineIndex); CFRange rangeToLayout = CTLineGetStringRange(lastVisibleLine); range = CFRangeMake(0, rangeToLayout.location + rangeToLayout.length); } CFRelease(frame); CFRelease(path); } CFRange fitCFRange = CFRangeMake(0, 0); CGSize newSize = CTFramesetterSuggestFrameSizeWithConstraints(framesetter, range, NULL, size, &fitCFRange); if (framesetter) { CFRelease(framesetter); } //hack: //1.需要加上额外的一部分size,有些情况下计算出来的像素点并不是那么精准 //2.iOS7 的 CTFramesetterSuggestFrameSizeWithConstraint s方法比较残,需要多加一部分 height //3.iOS7 多行中如果首行带有很多空格,会导致返回的 suggestionWidth 远小于真实 width ,那么多行情况下就是用传入的 width if (newSize.height < _fontHeight * 2) //单行 { return CGSizeMake(ceilf(newSize.width) + 2.0, ceilf(newSize.height) + 4.0); } else { return CGSizeMake(size.width, ceilf(newSize.height) + 4.0); } } - (CGSize)intrinsicContentSize { return [self sizeThatFits:CGSizeMake(CGRectGetWidth(self.bounds), CGFLOAT_MAX)]; } #pragma mark - 绘制方法 - (void)drawRect:(CGRect)rect { CGContextRef ctx = UIGraphicsGetCurrentContext(); if (ctx == nil) { return; } CGContextSaveGState(ctx); CGAffineTransform transform = [self transformForCoreText]; CGContextConcatCTM(ctx, transform); [self recomputeLinksIfNeeded]; NSAttributedString *drawString = [self attributedStringForDraw]; if (drawString) { [self prepareTextFrame:drawString rect:rect]; [self drawHighlightWithRect:rect]; [self drawAttachments]; [self drawShadow:ctx]; [self drawText:drawString rect:rect context:ctx]; } CGContextRestoreGState(ctx); } - (void)prepareTextFrame:(NSAttributedString *)string rect:(CGRect)rect { if (_textFrame == nil) { CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)string); CGMutablePathRef path = CGPathCreateMutable(); CGPathAddRect(path, nil,rect); _textFrame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), path, NULL); CGPathRelease(path); CFRelease(framesetter); } } - (void)drawHighlightWithRect:(CGRect)rect { if (self.touchedLink && self.highlightColor) { [self.highlightColor setFill]; NSRange linkRange = self.touchedLink.range; CFArrayRef lines = CTFrameGetLines(_textFrame); CFIndex count = CFArrayGetCount(lines); CGPoint lineOrigins[count]; CTFrameGetLineOrigins(_textFrame, CFRangeMake(0, 0), lineOrigins); NSInteger numberOfLines = [self numberOfDisplayedLines]; CGContextRef ctx = UIGraphicsGetCurrentContext(); for (CFIndex i = 0; i < numberOfLines; i++) { CTLineRef line = CFArrayGetValueAtIndex(lines, i); CFRange stringRange = CTLineGetStringRange(line); NSRange lineRange = NSMakeRange(stringRange.location, stringRange.length); NSRange intersectedRange = NSIntersectionRange(lineRange, linkRange); if (intersectedRange.length == 0) { continue; } CGRect highlightRect = [self rectForRange:linkRange inLine:line lineOrigin:lineOrigins[i]]; highlightRect = CGRectOffset(highlightRect, 0, -rect.origin.y); if (!CGRectIsEmpty(highlightRect)) { CGFloat pi = (CGFloat)M_PI; CGFloat radius = 1.0f; CGContextMoveToPoint(ctx, highlightRect.origin.x, highlightRect.origin.y + radius); CGContextAddLineToPoint(ctx, highlightRect.origin.x, highlightRect.origin.y + highlightRect.size.height - radius); CGContextAddArc(ctx, highlightRect.origin.x + radius, highlightRect.origin.y + highlightRect.size.height - radius, radius, pi, pi / 2.0f, 1.0f); CGContextAddLineToPoint(ctx, highlightRect.origin.x + highlightRect.size.width - radius, highlightRect.origin.y + highlightRect.size.height); CGContextAddArc(ctx, highlightRect.origin.x + highlightRect.size.width - radius, highlightRect.origin.y + highlightRect.size.height - radius, radius, pi / 2, 0.0f, 1.0f); CGContextAddLineToPoint(ctx, highlightRect.origin.x + highlightRect.size.width, highlightRect.origin.y + radius); CGContextAddArc(ctx, highlightRect.origin.x + highlightRect.size.width - radius, highlightRect.origin.y + radius, radius, 0.0f, -pi / 2.0f, 1.0f); CGContextAddLineToPoint(ctx, highlightRect.origin.x + radius, highlightRect.origin.y); CGContextAddArc(ctx, highlightRect.origin.x + radius, highlightRect.origin.y + radius, radius, -pi / 2, pi, 1); CGContextFillPath(ctx); } } } } - (void)drawShadow:(CGContextRef)ctx { if (self.shadowColor) { CGContextSetShadowWithColor(ctx, self.shadowOffset, self.shadowBlur, self.shadowColor.CGColor); } } - (void)drawText:(NSAttributedString *)attributedString rect:(CGRect)rect context:(CGContextRef)context { if (_textFrame) { if (_numberOfLines > 0) { CFArrayRef lines = CTFrameGetLines(_textFrame); NSInteger numberOfLines = [self numberOfDisplayedLines]; CGPoint lineOrigins[numberOfLines]; CTFrameGetLineOrigins(_textFrame, CFRangeMake(0, numberOfLines), lineOrigins); for (CFIndex lineIndex = 0; lineIndex < numberOfLines; lineIndex++) { CGPoint lineOrigin = lineOrigins[lineIndex]; CGContextSetTextPosition(context, lineOrigin.x, lineOrigin.y); CTLineRef line = CFArrayGetValueAtIndex(lines, lineIndex); BOOL shouldDrawLine = YES; if (lineIndex == numberOfLines - 1 && _lineBreakMode == kCTLineBreakByTruncatingTail) { //找到最后一行并检查是否需要 truncatingTail CFRange lastLineRange = CTLineGetStringRange(line); if (lastLineRange.location + lastLineRange.length < attributedString.length) { CTLineTruncationType truncationType = kCTLineTruncationEnd; NSUInteger truncationAttributePosition = lastLineRange.location + lastLineRange.length - 1; NSDictionary *tokenAttributes = [attributedString attributesAtIndex:truncationAttributePosition effectiveRange:NULL]; NSAttributedString *tokenString = [[NSAttributedString alloc] initWithString:M80EllipsesCharacter attributes:tokenAttributes]; CTLineRef truncationToken = CTLineCreateWithAttributedString((CFAttributedStringRef)tokenString); NSMutableAttributedString *truncationString = [[attributedString attributedSubstringFromRange:NSMakeRange(lastLineRange.location, lastLineRange.length)] mutableCopy]; if (lastLineRange.length > 0) { //移除掉最后一个对象...(其实这个地方有点问题,也有可能需要移除最后 2 个对象,因为 attachment 宽度的关系) [truncationString deleteCharactersInRange:NSMakeRange(lastLineRange.length - 1, 1)]; } [truncationString appendAttributedString:tokenString]; CTLineRef truncationLine = CTLineCreateWithAttributedString((CFAttributedStringRef)truncationString); CTLineRef truncatedLine = CTLineCreateTruncatedLine(truncationLine, rect.size.width, truncationType, truncationToken); if (!truncatedLine) { truncatedLine = CFRetain(truncationToken); } CFRelease(truncationLine); CFRelease(truncationToken); CTLineDraw(truncatedLine, context); CFRelease(truncatedLine); shouldDrawLine = NO; } } if(shouldDrawLine) { CTLineDraw(line, context); } } } else { CTFrameDraw(_textFrame,context); } } } - (void)drawAttachments { if ([_attachments count] == 0) { return; } CGContextRef ctx = UIGraphicsGetCurrentContext(); if (ctx == nil) { return; } CFArrayRef lines = CTFrameGetLines(_textFrame); CFIndex lineCount = CFArrayGetCount(lines); CGPoint lineOrigins[lineCount]; CTFrameGetLineOrigins(_textFrame, CFRangeMake(0, 0), lineOrigins); NSInteger numberOfLines = [self numberOfDisplayedLines]; for (CFIndex i = 0; i < numberOfLines; i++) { CTLineRef line = CFArrayGetValueAtIndex(lines, i); CFArrayRef runs = CTLineGetGlyphRuns(line); CFIndex runCount = CFArrayGetCount(runs); CGPoint lineOrigin = lineOrigins[i]; CGFloat lineAscent; CGFloat lineDescent; CTLineGetTypographicBounds(line, &lineAscent, &lineDescent, NULL); CGFloat lineHeight = lineAscent + lineDescent; CGFloat lineBottomY = lineOrigin.y - lineDescent; //遍历以找到对应的 attachment 进行绘制 for (CFIndex k = 0; k < runCount; k++) { CTRunRef run = CFArrayGetValueAtIndex(runs, k); NSDictionary *runAttributes = (NSDictionary *)CTRunGetAttributes(run); CTRunDelegateRef delegate = (__bridge CTRunDelegateRef)[runAttributes valueForKey:(id)kCTRunDelegateAttributeName]; if (nil == delegate) { continue; } M80AttributedLabelAttachment* attributedImage = (M80AttributedLabelAttachment *)CTRunDelegateGetRefCon(delegate); CGFloat ascent = 0.0f; CGFloat descent = 0.0f; CGFloat width = (CGFloat)CTRunGetTypographicBounds(run, CFRangeMake(0, 0), &ascent, &descent, NULL); CGFloat imageBoxHeight = [attributedImage boxSize].height; CGFloat xOffset = CTLineGetOffsetForStringIndex(line, CTRunGetStringRange(run).location, nil); CGFloat imageBoxOriginY = 0.0f; switch (attributedImage.alignment) { case M80ImageAlignmentTop: imageBoxOriginY = lineBottomY + (lineHeight - imageBoxHeight); break; case M80ImageAlignmentCenter: imageBoxOriginY = lineBottomY + (lineHeight - imageBoxHeight) / 2.0; break; case M80ImageAlignmentBottom: imageBoxOriginY = lineBottomY; break; } CGRect rect = CGRectMake(lineOrigin.x + xOffset, imageBoxOriginY, width, imageBoxHeight); UIEdgeInsets flippedMargins = attributedImage.margin; CGFloat top = flippedMargins.top; flippedMargins.top = flippedMargins.bottom; flippedMargins.bottom = top; CGRect attatchmentRect = UIEdgeInsetsInsetRect(rect, flippedMargins); if (i == numberOfLines - 1 && k >= runCount - 2 && _lineBreakMode == kCTLineBreakByTruncatingTail) { //最后行最后的2个CTRun需要做额外判断 CGFloat attachmentWidth = CGRectGetWidth(attatchmentRect); const CGFloat kMinEllipsesWidth = attachmentWidth; if (CGRectGetWidth(self.bounds) - CGRectGetMinX(attatchmentRect) - attachmentWidth < kMinEllipsesWidth) { continue; } } id content = attributedImage.content; if ([content isKindOfClass:[UIImage class]]) { CGContextDrawImage(ctx, attatchmentRect, ((UIImage *)content).CGImage); } else if ([content isKindOfClass:[UIView class]]) { UIView *view = (UIView *)content; if (view.superview == nil) { [self addSubview:view]; } CGRect viewFrame = CGRectMake(attatchmentRect.origin.x, self.bounds.size.height - attatchmentRect.origin.y - attatchmentRect.size.height, attatchmentRect.size.width, attatchmentRect.size.height); [view setFrame:viewFrame]; } else { NSLog(@"Attachment Content Not Supported %@",content); } } } } #pragma mark - 点击事件处理 - (BOOL)onLabelClick:(CGPoint)point { id linkData = [self linkDataForPoint:point]; if (linkData) { if (_delegate && [_delegate respondsToSelector:@selector(m80AttributedLabel:clickedOnLink:)]) { [_delegate m80AttributedLabel:self clickedOnLink:linkData]; } else { NSURL *url = nil; if ([linkData isKindOfClass:[NSString class]]) { url = [NSURL URLWithString:linkData]; } else if([linkData isKindOfClass:[NSURL class]]) { url = linkData; } if (url) { [[UIApplication sharedApplication] openURL:url]; } } return YES; } return NO; } #pragma mark - 链接处理 - (void)recomputeLinksIfNeeded { const NSInteger kMinHttpLinkLength = 5; if (!_autoDetectLinks || _linkDetected) { return; } NSString *text = [[_attributedString string] copy]; NSUInteger length = [text length]; if (length <= kMinHttpLinkLength) { return; } BOOL sync = length <= M80MinAsyncDetectLinkLength; [self computeLink:text sync:sync]; } - (void)computeLink:(NSString *)text sync:(BOOL)sync { __weak typeof(self) weakSelf = self; typedef void (^LinkBlock) (NSArray *); LinkBlock block = ^(NSArray *links) { weakSelf.linkDetected = YES; if ([links count]) { for (M80AttributedLabelURL *link in links) { [weakSelf addAutoDetectedLink:link]; } [weakSelf resetTextFrame]; } }; if (sync) { _ignoreRedraw = YES; NSArray *links = [M80AttributedLabelURL detectLinks:text]; block(links); _ignoreRedraw = NO; } else { dispatch_async(get_m80_attributed_label_parse_queue(), ^{ NSArray *links = [M80AttributedLabelURL detectLinks:text]; dispatch_async(dispatch_get_main_queue(), ^{ NSString *plainText = [[weakSelf attributedString] string]; if ([plainText isEqualToString:text]) { block(links); } }); }); } } - (void)addAutoDetectedLink:(M80AttributedLabelURL *)link { NSRange range = link.range; for (M80AttributedLabelURL *url in _linkLocations) { if (NSIntersectionRange(range, url.range).length != 0) { return; } } [self addCustomLink:link.linkData forRange:link.range]; } #pragma mark - 点击事件相应 - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { if (self.touchedLink == nil) { UITouch *touch = [touches anyObject]; CGPoint point = [touch locationInView:self]; self.touchedLink = [self urlForPoint:point]; } if (self.touchedLink) { [self setNeedsDisplay]; } else { [super touchesBegan:touches withEvent:event]; } } - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { [super touchesMoved:touches withEvent:event]; UITouch *touch = [touches anyObject]; CGPoint point = [touch locationInView:self]; M80AttributedLabelURL *touchedLink = [self urlForPoint:point]; if (self.touchedLink != touchedLink) { self.touchedLink = touchedLink; [self setNeedsDisplay]; } } - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { [super touchesCancelled:touches withEvent:event]; if (self.touchedLink) { self.touchedLink = nil; [self setNeedsDisplay]; } } - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { UITouch *touch = [touches anyObject]; CGPoint point = [touch locationInView:self]; if(![self onLabelClick:point]) { [super touchesEnded:touches withEvent:event]; } if (self.touchedLink) { self.touchedLink = nil; [self setNeedsDisplay]; } } - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { M80AttributedLabelURL *touchedLink = [self urlForPoint:point]; if (touchedLink == nil) { NSArray *subViews = [self subviews]; for (UIView *view in subViews) { CGPoint hitPoint = [view convertPoint:point fromView:self]; UIView *hitTestView = [view hitTest:hitPoint withEvent:event]; if (hitTestView) { return hitTestView; } } return nil; } else { return self; } } #pragma mark - 设置自定义的连接检测block + (void)setCustomDetectMethod:(M80CustomDetectLinkBlock)block { [M80AttributedLabelURL setCustomDetectMethod:block]; } @end ================================================ FILE: zhuishushenqi/Vendor/M80AttributedLabel/M80AttributedLabelAttachment.h ================================================ // // M80AttributedLabelAttachment.h // M80AttributedLabel // // Created by amao on 13-8-31. // Copyright (c) 2013年 www.xiangwangfeng.com. All rights reserved. // #import "M80AttributedLabelDefines.h" NS_ASSUME_NONNULL_BEGIN void deallocCallback(void* ref); CGFloat ascentCallback(void *ref); CGFloat descentCallback(void *ref); CGFloat widthCallback(void* ref); @interface M80AttributedLabelAttachment : NSObject @property (nonatomic,strong) id content; @property (nonatomic,assign) UIEdgeInsets margin; @property (nonatomic,assign) M80ImageAlignment alignment; @property (nonatomic,assign) CGFloat fontAscent; @property (nonatomic,assign) CGFloat fontDescent; @property (nonatomic,assign) CGSize maxSize; + (M80AttributedLabelAttachment *)attachmentWith:(id)content margin:(UIEdgeInsets)margin alignment:(M80ImageAlignment)alignment maxSize:(CGSize)maxSize; - (CGSize)boxSize; @end NS_ASSUME_NONNULL_END ================================================ FILE: zhuishushenqi/Vendor/M80AttributedLabel/M80AttributedLabelAttachment.m ================================================ // // M80AttributedLabelAttachment.m // M80AttributedLabel // // Created by amao on 13-8-31. // Copyright (c) 2013年 www.xiangwangfeng.com. All rights reserved. // #import "M80AttributedLabelAttachment.h" void deallocCallback(void* ref) { } CGFloat ascentCallback(void *ref) { M80AttributedLabelAttachment *image = (__bridge M80AttributedLabelAttachment *)ref; CGFloat ascent = 0; CGFloat height = [image boxSize].height; switch (image.alignment) { case M80ImageAlignmentTop: ascent = image.fontAscent; break; case M80ImageAlignmentCenter: { CGFloat fontAscent = image.fontAscent; CGFloat fontDescent = image.fontDescent; CGFloat baseLine = (fontAscent + fontDescent) / 2 - fontDescent; ascent = height / 2 + baseLine; } break; case M80ImageAlignmentBottom: ascent = height - image.fontDescent; break; default: break; } return ascent; } CGFloat descentCallback(void *ref) { M80AttributedLabelAttachment *image = (__bridge M80AttributedLabelAttachment *)ref; CGFloat descent = 0; CGFloat height = [image boxSize].height; switch (image.alignment) { case M80ImageAlignmentTop: { descent = height - image.fontAscent; break; } case M80ImageAlignmentCenter: { CGFloat fontAscent = image.fontAscent; CGFloat fontDescent = image.fontDescent; CGFloat baseLine = (fontAscent + fontDescent) / 2 - fontDescent; descent = height / 2 - baseLine; } break; case M80ImageAlignmentBottom: { descent = image.fontDescent; break; } default: break; } return descent; } CGFloat widthCallback(void* ref) { M80AttributedLabelAttachment *image = (__bridge M80AttributedLabelAttachment *)ref; return [image boxSize].width; } #pragma mark - M80AttributedLabelImage @interface M80AttributedLabelAttachment () - (CGSize)calculateContentSize; - (CGSize)attachmentSize; @end @implementation M80AttributedLabelAttachment + (M80AttributedLabelAttachment *)attachmentWith:(id)content margin:(UIEdgeInsets)margin alignment:(M80ImageAlignment)alignment maxSize:(CGSize)maxSize { M80AttributedLabelAttachment *attachment = [[M80AttributedLabelAttachment alloc]init]; attachment.content = content; attachment.margin = margin; attachment.alignment = alignment; attachment.maxSize = maxSize; return attachment; } - (CGSize)boxSize { CGSize contentSize = [self attachmentSize]; if (_maxSize.width > 0 &&_maxSize.height > 0 && contentSize.width > 0 && contentSize.height > 0) { contentSize = [self calculateContentSize]; } return CGSizeMake(contentSize.width + _margin.left + _margin.right, contentSize.height+ _margin.top + _margin.bottom); } #pragma mark - 辅助方法 - (CGSize)calculateContentSize { CGSize attachmentSize = [self attachmentSize]; CGFloat width = attachmentSize.width; CGFloat height = attachmentSize.height; CGFloat newWidth = _maxSize.width; CGFloat newHeight = _maxSize.height; if (width <= newWidth && height<= newHeight) { return attachmentSize; } CGSize size; if (width / height > newWidth / newHeight) { size = CGSizeMake(newWidth, newWidth * height / width); } else { size = CGSizeMake(newHeight * width / height, newHeight); } return size; } - (CGSize)attachmentSize { CGSize size = CGSizeZero; if ([_content isKindOfClass:[UIImage class]]) { size = [((UIImage *)_content) size]; } else if ([_content isKindOfClass:[UIView class]]) { size = [((UIView *)_content) bounds].size; } return size; } @end ================================================ FILE: zhuishushenqi/Vendor/M80AttributedLabel/M80AttributedLabelDefines.h ================================================ // // M80AttributedLabelDefines.h // M80AttributedLabel // // Created by amao on 13-8-31. // Copyright (c) 2013年 www.xiangwangfeng.com. All rights reserved. // #ifndef M80AttributedLabel_M80AttributedLabelDefines_h #define M80AttributedLabel_M80AttributedLabelDefines_h #import #import #import NS_ASSUME_NONNULL_BEGIN typedef NS_OPTIONS(NSUInteger, M80ImageAlignment) { M80ImageAlignmentTop, M80ImageAlignmentCenter, M80ImageAlignmentBottom }; @class M80AttributedLabel; @protocol M80AttributedLabelDelegate - (void)m80AttributedLabel:(M80AttributedLabel *)label clickedOnLink:(id)linkData; @end typedef NSArray * _Nullable (^M80CustomDetectLinkBlock)(NSString * _Nullable text); //如果文本长度小于这个值,直接在UI线程做Link检测,否则都dispatch到共享线程 #define M80MinAsyncDetectLinkLength 50 NS_ASSUME_NONNULL_END #endif ================================================ FILE: zhuishushenqi/Vendor/M80AttributedLabel/M80AttributedLabelURL.h ================================================ // // M80AttributedLabelURL.h // M80AttributedLabel // // Created by amao on 13-8-31. // Copyright (c) 2013年 www.xiangwangfeng.com. All rights reserved. // #import "M80AttributedLabelDefines.h" NS_ASSUME_NONNULL_BEGIN @interface M80AttributedLabelURL : NSObject @property (nonatomic,strong) id linkData; @property (nonatomic,assign) NSRange range; @property (nonatomic,strong,nullable) UIColor *color; + (M80AttributedLabelURL *)urlWithLinkData:(id)linkData range:(NSRange)range color:(nullable UIColor *)color; + (nullable NSArray *)detectLinks:(nullable NSString *)plainText; + (void)setCustomDetectMethod:(nullable M80CustomDetectLinkBlock)block; @end NS_ASSUME_NONNULL_END ================================================ FILE: zhuishushenqi/Vendor/M80AttributedLabel/M80AttributedLabelURL.m ================================================ // // M80AttributedLabelURL.m // M80AttributedLabel // // Created by amao on 13-8-31. // Copyright (c) 2013年 www.xiangwangfeng.com. All rights reserved. // #import "M80AttributedLabelURL.h" static NSString *M80URLExpression = @"((([A-Za-z]{3,9}:(?:\\/\\/)?)(?:[\\-;:&=\\+\\$,\\w]+@)?[A-Za-z0-9\\.\\-]+|(?:www\\.|[\\-;:&=\\+\\$,\\w]+@)[A-Za-z0-9\\.\\-]+)((:[0-9]+)?)((?:\\/[\\+~%\\/\\.\\w\\-]*)?\\??(?:[\\-\\+=&;%@\\.\\w]*)#?(?:[\\.\\!\\/\\\\\\w]*))?)"; static M80CustomDetectLinkBlock customDetectBlock = nil; static NSString *M80URLExpressionKey = @"M80URLExpressionKey"; @implementation M80AttributedLabelURL + (M80AttributedLabelURL *)urlWithLinkData:(id)linkData range:(NSRange)range color:(UIColor *)color { M80AttributedLabelURL *url = [[M80AttributedLabelURL alloc]init]; url.linkData = linkData; url.range = range; url.color = color; return url; } + (NSArray *)detectLinks:(NSString *)plainText { if (customDetectBlock) { return customDetectBlock(plainText); } else { NSMutableArray *links = nil; if ([plainText length]) { links = [NSMutableArray array]; NSRegularExpression *urlRegex = [M80AttributedLabelURL urlExpression]; [urlRegex enumerateMatchesInString:plainText options:0 range:NSMakeRange(0, [plainText length]) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) { NSRange range = result.range; NSString *text = [plainText substringWithRange:range]; M80AttributedLabelURL *link = [M80AttributedLabelURL urlWithLinkData:text range:range color:nil]; [links addObject:link]; }]; } return links; } } + (NSRegularExpression *)urlExpression { NSMutableDictionary *dict = [[NSThread currentThread] threadDictionary]; NSRegularExpression *exp = dict[M80URLExpressionKey]; if (exp == nil) { exp = [NSRegularExpression regularExpressionWithPattern:M80URLExpression options:NSRegularExpressionCaseInsensitive error:nil]; dict[M80URLExpressionKey] = exp; } return exp; } + (void)setCustomDetectMethod:(M80CustomDetectLinkBlock)block { customDetectBlock = [block copy]; } @end ================================================ FILE: zhuishushenqi/Vendor/M80AttributedLabel/NSMutableAttributedString+M80.h ================================================ // // NSMutableAttributedString+M80.h // M80AttributedLabel // // Created by amao on 13-8-31. // Copyright (c) 2013年 www.xiangwangfeng.com. All rights reserved. // #import "M80AttributedLabelDefines.h" NS_ASSUME_NONNULL_BEGIN @interface NSMutableAttributedString (M80) - (void)m80_setTextColor:(UIColor*)color; - (void)m80_setTextColor:(UIColor*)color range:(NSRange)range; - (void)m80_setFont:(UIFont*)font; - (void)m80_setFont:(UIFont*)font range:(NSRange)range; - (void)m80_setUnderlineStyle:(CTUnderlineStyle)style modifier:(CTUnderlineStyleModifiers)modifier; - (void)m80_setUnderlineStyle:(CTUnderlineStyle)style modifier:(CTUnderlineStyleModifiers)modifier range:(NSRange)range; @end NS_ASSUME_NONNULL_END ================================================ FILE: zhuishushenqi/Vendor/M80AttributedLabel/NSMutableAttributedString+M80.m ================================================ // // NSMutableAttributedString+M80.m // M80AttributedLabel // // Created by amao on 13-8-31. // Copyright (c) 2013年 www.xiangwangfeng.com. All rights reserved. // #import "NSMutableAttributedString+M80.h" @implementation NSMutableAttributedString (M80) - (void)m80_setTextColor:(UIColor*)color { [self m80_setTextColor:color range:NSMakeRange(0, [self length])]; } - (void)m80_setTextColor:(UIColor*)color range:(NSRange)range { if (color.CGColor) { [self removeAttribute:(NSString *)kCTForegroundColorAttributeName range:range]; [self addAttribute:(NSString *)kCTForegroundColorAttributeName value:(id)color.CGColor range:range]; } } - (void)m80_setFont:(UIFont*)font { [self m80_setFont:font range:NSMakeRange(0, [self length])]; } - (void)m80_setFont:(UIFont*)font range:(NSRange)range { if (font) { [self removeAttribute:(NSString*)kCTFontAttributeName range:range]; CTFontRef fontRef = CTFontCreateWithName((CFStringRef)font.fontName, font.pointSize, nil); if (nil != fontRef) { [self addAttribute:(NSString *)kCTFontAttributeName value:(__bridge id)fontRef range:range]; CFRelease(fontRef); } } } - (void)m80_setUnderlineStyle:(CTUnderlineStyle)style modifier:(CTUnderlineStyleModifiers)modifier { [self m80_setUnderlineStyle:style modifier:modifier range:NSMakeRange(0, self.length)]; } - (void)m80_setUnderlineStyle:(CTUnderlineStyle)style modifier:(CTUnderlineStyleModifiers)modifier range:(NSRange)range { [self removeAttribute:(NSString *)kCTUnderlineColorAttributeName range:range]; [self addAttribute:(NSString *)kCTUnderlineStyleAttributeName value:[NSNumber numberWithInt:(style|modifier)] range:range]; } @end ================================================ FILE: zhuishushenqi/Vendor/OCGumbo/OCGumbo+Query.h ================================================ // Copyright [2013] tracy.cpp@gmail.com (TracyYih) // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #import "OCGumbo.h" @class OCQueryObject; typedef OCQueryObject * (^OCGumboQueryBlockAS) (NSString *); typedef NSString * (^OCGumboQueryBlockSS) (NSString *); typedef NSString * (^OCGumboQueryBlockSV) (void); @interface OCGumboNode (Query) /** * Query children elements from current node by selector. * * @param selector (NSString *) can be elementID | tagName | classSelector | tagName.classSelector | tagName#elementID. */ @property (nonatomic, weak, readonly) OCGumboQueryBlockAS Query; /** * Get the attribute value of the element by attributeName. * * @param attributeName (NSString *) the attribute name. */ @property (nonatomic, weak, readonly) OCGumboQueryBlockSS attr; /** * Get the combined text contents of element. */ @property (nonatomic, weak, readonly) OCGumboQueryBlockSV text; /** * Get the raw contents of element. */ @property (nonatomic, weak, readonly) OCGumboQueryBlockSV html; @end #pragma mark - typedef OCGumboNode * (^NSArrayQueryBlockNV) (void); typedef OCGumboNode * (^NSArrayQueryBlockNI) (NSUInteger); typedef BOOL (^NSArrayQueryBlockBS) (NSString *); typedef NSUInteger (^NSArrayQueryBlockIN) (OCGumboNode *); typedef OCQueryObject * (^NSArrayQueryBlockAS) (NSString *); typedef OCQueryObject * (^NSArrayQueryBlockSA) (void); typedef NSString * (^NSArrayQueryBlockSV) (void); @interface OCQueryObject : NSArray /** * Get the combined text contents of the collection. */ @property (nonatomic, weak, readonly) NSArrayQueryBlockSV text; /** * Get the combined text array of element. */ @property (nonatomic, weak, readonly) NSArrayQueryBlockSA textArray; /** * Get the first element of the current collection. */ @property (nonatomic, weak, readonly) NSArrayQueryBlockNV first; /** * Get the last element of the current collection. */ @property (nonatomic, weak, readonly) NSArrayQueryBlockNV last; /** * Get the element by index from current collection. * * @param index (NSUInteger) the index of the element. */ @property (nonatomic, weak, readonly) NSArrayQueryBlockNI get; /** * Check if any elements in the collection have the specified class. */ @property (nonatomic, weak, readonly) NSArrayQueryBlockBS hasClass; /** * Get the position of an element in current collection. * * @param element (OCGumboNode *) */ @property (nonatomic, weak, readonly) NSArrayQueryBlockIN index; /** * Find elements that match the selector in the current collection. * * @param selector (NSString *) can be elementID | tagName | classSelector | tagName.classSelector | tagName#elementID. */ @property (nonatomic, weak, readonly) NSArrayQueryBlockAS find; /** * Get immediate children of each element in the current collection matching the selector. * * @param selector (NSString *) can be elementID | tagName | classSelector | tagName.classSelector | tagName#elementID. */ @property (nonatomic, weak, readonly) NSArrayQueryBlockAS children; /** * Get immediate parents of each element in the collection matching the selector. * * @param selector (NSString *) can be elementID | tagName | classSelector | tagName.classSelector | tagName#elementID. */ @property (nonatomic, weak, readonly) NSArrayQueryBlockAS parent; /** * Get all ancestors of each element in the collection matching the selector. * * @param selector (NSString *) can be elementID | tagName | classSelector | tagName.classSelector | tagName#elementID. */ @property (nonatomic, weak, readonly) NSArrayQueryBlockAS parents; @end ================================================ FILE: zhuishushenqi/Vendor/OCGumbo/OCGumbo+Query.m ================================================ // Copyright [2013] tracy.cpp@gmail.com (TracyYih) // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #import "OCGumbo+Query.h" #pragma mark - C Methods NS_INLINE const char *oc_gumbo_get_attribute(GumboNode *node, const char *name) { if (node->type == GUMBO_NODE_ELEMENT) { GumboVector attributes = node->v.element.attributes; GumboAttribute *attribute = gumbo_get_attribute(&attributes, name); if (attribute) { return attribute->value; } } return NULL; } NS_INLINE void add_valid_node_to_array(GumboNode *node, NSString *selector, NSMutableArray *__autoreleasing *array) { //class selector (.clsname) if ([selector hasPrefix:@"."]) { GumboAttribute *classAttribute = gumbo_get_attribute(&node->v.element.attributes, "class"); if (classAttribute) { NSArray *selectors = [@(classAttribute->value) componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; if ([selectors containsObject:[selector substringFromIndex:1]]) { [*array addObject:OCGumboNodeCast(node)]; } } } //id selector (#eleId) else if ([selector hasPrefix:@"#"]) { GumboAttribute *idAttribute = gumbo_get_attribute(&node->v.element.attributes, "id"); if (idAttribute) { const char *elementId = idAttribute->value; if (!strcasecmp(elementId, [[selector substringFromIndex:1] UTF8String])) { [*array addObject:OCGumboNodeCast(node)]; } } } //tag selector (p | p.clsname | p#eleId) else { NSString *tag = nil; NSUInteger classMark = [selector rangeOfString:@"."].location; NSUInteger idMark = [selector rangeOfString:@"#"].location; const char *tagname = gumbo_normalized_tagname(node->v.element.tag); if (classMark != NSNotFound) { tag = [selector substringToIndex:classMark]; NSString *cls = [selector substringFromIndex:classMark + 1]; const char *attrValue = oc_gumbo_get_attribute(node, "class"); if (attrValue && !strcasecmp(tagname, [tag UTF8String])) { NSString *clsValue = @(attrValue); NSArray *values = [clsValue componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; if ([values containsObject:cls]) { [*array addObject:OCGumboNodeCast(node)]; } } } else if (idMark != NSNotFound) { tag = [selector substringToIndex:idMark]; NSString *Id = [selector substringFromIndex:idMark + 1]; const char *attrValue = oc_gumbo_get_attribute(node, "id"); if (attrValue && !strcasecmp(tagname, [tag UTF8String]) && !strcasecmp(attrValue, [Id UTF8String])) { [*array addObject:OCGumboNodeCast(node)]; } } else { tag = selector; if (!strcasecmp(tagname, [tag UTF8String])) { [*array addObject:OCGumboNodeCast(node)]; } } } } NS_INLINE NSArray *oc_gumbo_find_children(GumboNode *root, NSString *selector, bool deep) { NSMutableArray *result = [NSMutableArray array]; GumboVector children = kGumboEmptyVector; if (root->type == GUMBO_NODE_DOCUMENT) { children = root->v.document.children; } else if (root->type == GUMBO_NODE_ELEMENT){ children = root->v.element.children; } for (int i = 0; i < children.length; i++) { GumboNode *child = children.data[i]; if (selector && selector.length) { if (child->type == GUMBO_NODE_ELEMENT) { add_valid_node_to_array(child, selector, &result); if (deep) { NSArray *subList = oc_gumbo_find_children(child, selector, deep); if (subList && [subList count]) { [result addObjectsFromArray:subList]; } } } } else { [result addObject:OCGumboNodeCast(child)]; } } return result; } NS_INLINE NSArray *oc_gumbo_find_parents(GumboNode *node, NSString *selector, BOOL deep) { NSMutableArray *result = [NSMutableArray array]; GumboNode *parent = node->parent; while (parent) { if (parent->type == GUMBO_NODE_ELEMENT) { add_valid_node_to_array(parent, selector, &result); if (deep) { NSArray *subList = oc_gumbo_find_children(parent, selector, deep); if (subList && [subList count]) { [result addObjectsFromArray:subList]; } } } parent = parent->parent; } return result; } #pragma mark - @implementation OCGumboNode (Query) - (OCGumboQueryBlockAS)Query { OCGumboQueryBlockAS block = ^ id (NSString *selector) { return oc_gumbo_find_children(self->_gumboNode, selector, true); }; return block; } - (OCGumboQueryBlockSV)text { OCGumboQueryBlockSV block = ^ NSString *(void) { NSMutableString *result = [NSMutableString string]; if (self.nodeType == GUMBO_NODE_DOCUMENT || self.nodeType == GUMBO_NODE_ELEMENT) { NSString *text = self.Query(nil).text(); if (text && text.length) { [result appendString:text]; } } else if(self.nodeType == GUMBO_NODE_TEXT){ [result appendString:self.nodeValue]; } return result; }; return block; } //may be not a fully reliable implementation. - (OCGumboQueryBlockSV)html { OCGumboQueryBlockSV block = ^ NSString *(void) { if (self.nodeType == GUMBO_NODE_ELEMENT) { GumboElement *element = &self->_gumboNode->v.element; NSString *result = @(element->original_tag.data); NSString *originalEndTag = @(element->original_end_tag.data); result = [result stringByReplacingCharactersInRange:NSMakeRange(result.length - originalEndTag.length, originalEndTag.length) withString:@""]; NSUInteger start = [result rangeOfString:@">"].location; if (start != NSNotFound) { result = [result substringFromIndex:start + 1]; } return result; } else { return nil; } }; return block; } - (OCGumboQueryBlockSS)attr { OCGumboQueryBlockSS block = ^ NSString *(NSString *name) { if (self->_gumboNode->type == GUMBO_NODE_ELEMENT) { GumboAttribute *attribute = gumbo_get_attribute(&self->_gumboNode->v.element.attributes, [name UTF8String]); if (attribute) { return @(attribute->value); } } return nil; }; return block; } - (OCGumboQueryBlockSS)own { OCGumboQueryBlockSS block = ^ NSString *(NSString *name) { if (self->_gumboNode->type == GUMBO_NODE_ELEMENT) { GumboElement *element = &self->_gumboNode->v.element; NSString *result = @(element->original_tag.data); NSString *originalEndTag = @(element->original_end_tag.data); result = [result stringByReplacingCharactersInRange:NSMakeRange(result.length - originalEndTag.length, originalEndTag.length) withString:@""]; NSUInteger start = [result rangeOfString:@">"].location; if (start != NSNotFound) { result = [result substringFromIndex:start + 1]; } return result; } else { return nil; } }; return block; } @end #pragma mark - @implementation NSArray (Query) - (NSArrayQueryBlockSV)text { NSArrayQueryBlockSV block = ^ NSString *(void) { NSMutableString *result = [NSMutableString string]; for (OCGumboNode *node in self) { if (node.nodeType == GUMBO_NODE_DOCUMENT || node.nodeType == GUMBO_NODE_ELEMENT) { NSString *text = node.childNodes.text(); if (text && text.length) { [result appendString:text]; } } else if(node.nodeType == GUMBO_NODE_TEXT){ [result appendString:node.nodeValue]; } } return result; }; return block; } - (NSArrayQueryBlockSA)textArray{ NSArrayQueryBlockSA block = ^ OCQueryObject *(void){ NSMutableArray *result = [NSMutableArray array]; for (OCGumboNode *node in self) { if (node.nodeType == GUMBO_NODE_DOCUMENT || node.nodeType == GUMBO_NODE_ELEMENT) { OCQueryObject *text = node.childNodes.textArray(); if (text && text.count) { [result addObjectsFromArray:text]; } } else if(node.nodeType == GUMBO_NODE_TEXT){ [result addObject:node.nodeValue]; } } return (OCQueryObject *)result; }; return block; } - (NSArrayQueryBlockNV)first { NSArrayQueryBlockNV block = ^ OCGumboNode *(void) { if ([self count]) { return self[0]; } return nil; }; return block; } - (NSArrayQueryBlockNV)last { NSArrayQueryBlockNV block = ^ OCGumboNode *(void) { return [self lastObject]; }; return block; } - (NSArrayQueryBlockNI)get { NSArrayQueryBlockNI block = ^ OCGumboNode *(NSUInteger index) { if (index < [self count]) { return self[index]; } return nil; }; return block; } - (NSArrayQueryBlockBS)hasClass { NSArrayQueryBlockBS block = ^ BOOL (NSString *name) { return NO; }; return block; } - (NSArrayQueryBlockIN)index { NSArrayQueryBlockIN block = ^ NSUInteger (OCGumboNode *child) { return [self indexOfObject:child]; }; return block; } - (NSArrayQueryBlockAS)find { NSArrayQueryBlockAS block = ^ OCQueryObject *(NSString *selector) { NSMutableArray *result = [NSMutableArray array]; for (OCGumboNode *child in self) { NSArray *nodes = oc_gumbo_find_children(child->_gumboNode, selector, true); if (nodes && [nodes count]) { [result addObjectsFromArray:nodes]; } } return (OCQueryObject *)result; }; return block; } - (NSArrayQueryBlockAS)children { NSArrayQueryBlockAS block = ^ OCQueryObject *(NSString *selector) { NSMutableArray *result = [NSMutableArray array]; for (OCGumboNode *child in self) { NSArray *nodes = oc_gumbo_find_children(child->_gumboNode, selector, false); if (nodes && [nodes count]) { [result addObjectsFromArray:nodes]; } } return (OCQueryObject *)result; }; return block; } - (NSArray *)_filterArray { NSMutableArray *result = [NSMutableArray array]; for (OCGumboNode *node in self) { if (![result containsObject:node]) { [result addObject:node]; } } return result; } - (NSArrayQueryBlockAS)parent { NSArrayQueryBlockAS block = ^ OCQueryObject *(NSString *selector) { NSMutableArray *result = [NSMutableArray array]; for (OCGumboNode *child in self) { NSArray *nodes = oc_gumbo_find_parents(child->_gumboNode, selector, false); if (nodes && [nodes count]) { [result addObjectsFromArray:nodes]; } } return (OCQueryObject *)[result _filterArray]; }; return block; } - (NSArrayQueryBlockAS)parents { NSArrayQueryBlockAS block = ^ OCQueryObject *(NSString *selector) { NSMutableArray *result = [NSMutableArray array]; for (OCGumboNode *child in self) { NSArray *nodes = oc_gumbo_find_parents(child->_gumboNode, selector, true); if (nodes && [nodes count]) { [result addObjectsFromArray:nodes]; } } return (OCQueryObject *)[result _filterArray]; }; return block; } @end ================================================ FILE: zhuishushenqi/Vendor/OCGumbo/OCGumbo.h ================================================ // Copyright [2013] tracy.cpp@gmail.com (TracyYih) // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #if !__has_feature(objc_arc) #error OCGumbo must be built with ARC. #endif //please add gumbo(https://github.com/google/gumbo-parser/tree/master/src) sources or lib to the project. #include "gumbo.h" #import //for OCGumbo+Query. id OCGumboNodeCast(GumboNode *node); id OCGumboAttributeCast(GumboAttribute *attribute); #pragma mark - @interface OCGumboNode : NSObject { @public GumboNode *_gumboNode; //public for OCGumbo+Query. } @property (nonatomic, copy, readonly) NSString *nodeName; @property (nonatomic, copy, readonly) NSString *nodeValue; @property (nonatomic, readonly) GumboNodeType nodeType; @property (nonatomic, readonly) NSArray *childNodes; @property (nonatomic, readonly) OCGumboNode *parentNode; @end #pragma mark - @class OCGumboAttribute; @interface OCGumboElement : OCGumboNode @property (nonatomic, copy, readonly) NSString *tagName; @property (nonatomic, readonly) GumboTag tag; @property (nonatomic, readonly) NSArray *attributes; - (BOOL)hasAttribute:(NSString *)name; - (NSString *)getAttribute:(NSString *)name; - (OCGumboAttribute *)getAttributeNode:(NSString *)name; @end #pragma mark - @interface OCGumboText : OCGumboNode @property (nonatomic, copy, readonly) NSString *data; @end #pragma mark - @interface OCGumboDocument : OCGumboNode @property (nonatomic, copy, readonly) NSString *title; @property (nonatomic, readonly) BOOL hasDoctype; @property (nonatomic, copy, readonly) NSString *publicID; @property (nonatomic, copy, readonly) NSString *systemID; @property (nonatomic, readonly) OCGumboElement *rootElement; @property (nonatomic, readonly) OCGumboElement *head; @property (nonatomic, readonly) OCGumboElement *body; - (instancetype)initWithHTMLString:(NSString *)htmlString; @end #pragma mark - @interface OCGumboAttribute : NSObject @property (nonatomic, copy, readonly) NSString *name; @property (nonatomic, copy, readonly) NSString *value; @end ================================================ FILE: zhuishushenqi/Vendor/OCGumbo/OCGumbo.m ================================================ // Copyright [2013] tracy.cpp@gmail.com (TracyYih) // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #import "OCGumbo.h" #pragma mark C Methods #if !defined(NS_INLINE) #define NS_INLINE static inline #endif NS_INLINE GumboVector oc_gumbo_get_children(GumboNode *node) { if (node->type == GUMBO_NODE_DOCUMENT) { return node->v.document.children; } if (node->type == GUMBO_NODE_ELEMENT) { return node->v.element.children; } return kGumboEmptyVector; } NS_INLINE int oc_gumbo_get_child_cout(GumboNode *node) { return oc_gumbo_get_children(node).length; } NS_INLINE GumboTag oc_gumbo_get_tag(GumboNode *node) { return node->v.element.tag; } NS_INLINE const char *oc_gumbo_get_tagname(GumboNode *node) { if (node->type == GUMBO_NODE_ELEMENT) { return gumbo_normalized_tagname(node->v.element.tag); } return NULL; } NS_INLINE const char *oc_gumbo_get_attribute(GumboNode *node, const char *name) { if (node->type == GUMBO_NODE_ELEMENT) { GumboVector attributes = node->v.element.attributes; GumboAttribute *attribute = gumbo_get_attribute(&attributes, name); if (attribute) { return attribute->value; } } return NULL; } NS_INLINE int oc_gumbo_get_attribute_count(GumboNode *node) { if (node->type == GUMBO_NODE_ELEMENT) { return node->v.element.attributes.length; } return 0; } NS_INLINE GumboNode *oc_gumbo_get_child_at_index(GumboNode *node, int index) { return oc_gumbo_get_children(node).data[index]; } NS_INLINE GumboNode *oc_gumbo_get_firstchild(GumboNode *node) { GumboVector children = oc_gumbo_get_children(node); if (children.length) { return children.data[0]; } return NULL; } NS_INLINE GumboNode *oc_gumbo_get_first_element_by_tag(GumboNode *node, GumboTag tag) { GumboNode *root = node; int count = oc_gumbo_get_child_cout(root); for (int i = 0; i < count; i++) { GumboNode *child = oc_gumbo_get_child_at_index(root, i); if (child->type == GUMBO_NODE_ELEMENT) { if (oc_gumbo_get_tag(child) == tag) { return child; } else { GumboNode *node = oc_gumbo_get_first_element_by_tag(child, tag); if (node) { return node; } } } } return NULL; } #pragma mark - @implementation OCGumboNode - (id)initWithGumboNode:(GumboNode *)node { self = [super init]; if (self) { _gumboNode = node; } return self; } + (id)nodeWithGumboNode:(GumboNode *)node { Class cls; if (node->type == GUMBO_NODE_DOCUMENT) { cls = [OCGumboDocument class]; } else if (node->type == GUMBO_NODE_ELEMENT) { cls = [OCGumboElement class]; } else { cls = [OCGumboText class]; } return [[cls alloc] initWithGumboNode:node]; } id OCGumboNodeCast(GumboNode *node) { return [OCGumboNode nodeWithGumboNode:node]; } - (NSString *)description { return [NSString stringWithFormat:@"<%@ %p %@>" ,[self class],self, self.nodeName ? self.nodeName : self.nodeValue]; } - (BOOL)isEqual:(OCGumboNode *)object { if (object && [object isKindOfClass:[OCGumboNode class]]) { return _gumboNode == object->_gumboNode; } return NO; } #pragma mark - Properties - (NSString *)nodeName { if (_gumboNode->type == GUMBO_NODE_DOCUMENT) { return @"#document"; } if (_gumboNode->type == GUMBO_NODE_ELEMENT) { return @(oc_gumbo_get_tagname(_gumboNode)); } if (_gumboNode->type == GUMBO_NODE_TEXT) { return @"#text"; } return nil; } - (NSString *)nodeValue { GumboNodeType type = _gumboNode->type; if (type != GUMBO_NODE_DOCUMENT && type != GUMBO_NODE_ELEMENT) { return @(_gumboNode->v.text.text); } return nil; } - (GumboNodeType)nodeType { return _gumboNode->type; } - (NSArray *)childNodes { NSMutableArray *childNodes = [[NSMutableArray alloc] init]; GumboVector children = oc_gumbo_get_children(_gumboNode); for (int i = 0; i < children.length; i++) { GumboNode *child = children.data[i]; [childNodes addObject:OCGumboNodeCast(child)]; } return childNodes; } - (OCGumboNode *)parentNode { return OCGumboNodeCast(_gumboNode->parent); } @end #pragma mark - @implementation OCGumboElement - (NSString *)tagName { return self.nodeName; } - (GumboTag)tag { return oc_gumbo_get_tag(_gumboNode); } - (NSArray *)attributes { NSMutableArray *result = [[NSMutableArray alloc] init]; GumboVector attributes = _gumboNode->v.element.attributes; for (int i = 0; i < attributes.length; i++) { GumboAttribute *attribute = attributes.data[i]; [result addObject:OCGumboAttributeCast(attribute)]; } return result; } - (BOOL)hasAttribute:(NSString *)name { return gumbo_get_attribute(&_gumboNode->v.element.attributes, [name UTF8String]) != NULL; } - (OCGumboAttribute *)getAttributeNode:(NSString *)name { return OCGumboAttributeCast(gumbo_get_attribute(&_gumboNode->v.element.attributes, [name UTF8String])); } - (NSString *)getAttribute:(NSString *)name { const char *text = oc_gumbo_get_attribute(_gumboNode, [name UTF8String]); return text ? @(text) : nil; } @end #pragma mark - @implementation OCGumboText - (NSString *)data { return @(_gumboNode->v.text.text); } @end #pragma mark - @implementation OCGumboDocument { GumboOutput *_gumboOutput; } - (void)dealloc { gumbo_destroy_output(&kGumboDefaultOptions, _gumboOutput); } - (instancetype)initWithHTMLString:(NSString *)htmlString { self = [super init]; if (self) { _gumboOutput = gumbo_parse([htmlString UTF8String]); _gumboNode = _gumboOutput->document; } return self; } #pragma mark - Properties - (NSString *)title { GumboNode *node = oc_gumbo_get_first_element_by_tag(_gumboNode, GUMBO_TAG_TITLE); if (node && node->v.element.children.length) { GumboNode *text = node->v.element.children.data[0]; if (text->type == GUMBO_NODE_TEXT) { return @(text->v.text.text); } } return nil; } - (BOOL)hasDoctype { return _gumboNode->v.document.has_doctype; } - (NSString *)publicID { return @(_gumboNode->v.document.public_identifier); } - (NSString *)systemID { return @(_gumboNode->v.document.system_identifier); } - (OCGumboElement *)rootElement { return OCGumboNodeCast(_gumboOutput->root); } - (OCGumboElement *)head { GumboNode *node = oc_gumbo_get_first_element_by_tag(_gumboNode, GUMBO_TAG_HEAD); return OCGumboNodeCast(node); } - (OCGumboElement *)body { GumboNode *node = oc_gumbo_get_first_element_by_tag(_gumboNode, GUMBO_TAG_BODY); return OCGumboNodeCast(node); } @end #pragma mark - @implementation OCGumboAttribute { GumboAttribute *_gumboAttribute; } - (instancetype)initWithGumboAttribute:(GumboAttribute *)attribute { self = [super init]; if (self) { _gumboAttribute = attribute; } return self; } + (instancetype)attributeWithGumboAttribute:(GumboAttribute *)attribute { return [[OCGumboAttribute alloc] initWithGumboAttribute:attribute]; } id OCGumboAttributeCast(GumboAttribute *attribute) { return [OCGumboAttribute attributeWithGumboAttribute:attribute]; } - (NSString *)description { return [NSString stringWithFormat:@"<%@ %p %@=%@>", [self class], self, self.name, self.value]; } #pragma mark - Properties - (NSString *)name { return @(_gumboAttribute->name); } - (NSString *)value { return @(_gumboAttribute->value); } @end ================================================ FILE: zhuishushenqi/Vendor/OCGumbo/gumbo/attribute.c ================================================ // Copyright 2010 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // Author: jdtang@google.com (Jonathan Tang) #include "attribute.h" #include #include #include #include #include "util.h" struct GumboInternalParser; GumboAttribute* gumbo_get_attribute( const GumboVector* attributes, const char* name) { for (unsigned int i = 0; i < attributes->length; ++i) { GumboAttribute* attr = attributes->data[i]; if (!strcasecmp(attr->name, name)) { return attr; } } return NULL; } void gumbo_destroy_attribute( struct GumboInternalParser* parser, GumboAttribute* attribute) { gumbo_parser_deallocate(parser, (void*) attribute->name); gumbo_parser_deallocate(parser, (void*) attribute->value); gumbo_parser_deallocate(parser, (void*) attribute); } ================================================ FILE: zhuishushenqi/Vendor/OCGumbo/gumbo/attribute.h ================================================ // Copyright 2010 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // Author: jdtang@google.com (Jonathan Tang) #ifndef GUMBO_ATTRIBUTE_H_ #define GUMBO_ATTRIBUTE_H_ #include "gumbo.h" #ifdef __cplusplus extern "C" { #endif struct GumboInternalParser; // Release the memory used for an GumboAttribute, including the attribute // itself. void gumbo_destroy_attribute( struct GumboInternalParser* parser, GumboAttribute* attribute); #ifdef __cplusplus } #endif #endif // GUMBO_ATTRIBUTE_H_ ================================================ FILE: zhuishushenqi/Vendor/OCGumbo/gumbo/char_ref.c ================================================ #line 1 "char_ref.rl" // Copyright 2011 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // Author: jdtang@google.com (Jonathan Tang) // // This is a Ragel state machine re-implementation of the original char_ref.c, // rewritten to improve efficiency. To generate the .c file from it, // // $ ragel -F0 char_ref.rl // // The generated source is also checked into source control so that most people // hacking on the parser do not need to install ragel. #include "char_ref.h" #include #include #include #include #include // Only for debug assertions at present. #include "error.h" #include "string_piece.h" #include "utf8.h" #include "util.h" struct GumboInternalParser; const int kGumboNoChar = -1; // Table of replacement characters. The spec specifies that any occurrence of // the first character should be replaced by the second character, and a parse // error recorded. typedef struct { int from_char; int to_char; } CharReplacement; static const CharReplacement kCharReplacements[] = {{0x00, 0xfffd}, {0x0d, 0x000d}, {0x80, 0x20ac}, {0x81, 0x0081}, {0x82, 0x201A}, {0x83, 0x0192}, {0x84, 0x201E}, {0x85, 0x2026}, {0x86, 0x2020}, {0x87, 0x2021}, {0x88, 0x02C6}, {0x89, 0x2030}, {0x8A, 0x0160}, {0x8B, 0x2039}, {0x8C, 0x0152}, {0x8D, 0x008D}, {0x8E, 0x017D}, {0x8F, 0x008F}, {0x90, 0x0090}, {0x91, 0x2018}, {0x92, 0x2019}, {0x93, 0x201C}, {0x94, 0x201D}, {0x95, 0x2022}, {0x96, 0x2013}, {0x97, 0x2014}, {0x98, 0x02DC}, {0x99, 0x2122}, {0x9A, 0x0161}, {0x9B, 0x203A}, {0x9C, 0x0153}, {0x9D, 0x009D}, {0x9E, 0x017E}, {0x9F, 0x0178}, // Terminator. {-1, -1}}; static int parse_digit(int c, bool allow_hex) { if (c >= '0' && c <= '9') { return c - '0'; } if (allow_hex && c >= 'a' && c <= 'f') { return c - 'a' + 10; } if (allow_hex && c >= 'A' && c <= 'F') { return c - 'A' + 10; } return -1; } static void add_no_digit_error( struct GumboInternalParser* parser, Utf8Iterator* input) { GumboError* error = gumbo_add_error(parser); if (!error) { return; } utf8iterator_fill_error_at_mark(input, error); error->type = GUMBO_ERR_NUMERIC_CHAR_REF_NO_DIGITS; } static void add_codepoint_error(struct GumboInternalParser* parser, Utf8Iterator* input, GumboErrorType type, int codepoint) { GumboError* error = gumbo_add_error(parser); if (!error) { return; } utf8iterator_fill_error_at_mark(input, error); error->type = type; error->v.codepoint = codepoint; } static void add_named_reference_error(struct GumboInternalParser* parser, Utf8Iterator* input, GumboErrorType type, GumboStringPiece text) { GumboError* error = gumbo_add_error(parser); if (!error) { return; } utf8iterator_fill_error_at_mark(input, error); error->type = type; error->v.text = text; } static int maybe_replace_codepoint(int codepoint) { for (int i = 0; kCharReplacements[i].from_char != -1; ++i) { if (kCharReplacements[i].from_char == codepoint) { return kCharReplacements[i].to_char; } } return -1; } static bool consume_numeric_ref( struct GumboInternalParser* parser, Utf8Iterator* input, int* output) { utf8iterator_next(input); bool is_hex = false; int c = utf8iterator_current(input); if (c == 'x' || c == 'X') { is_hex = true; utf8iterator_next(input); c = utf8iterator_current(input); } int digit = parse_digit(c, is_hex); if (digit == -1) { // First digit was invalid; add a parse error and return. add_no_digit_error(parser, input); utf8iterator_reset(input); *output = kGumboNoChar; return false; } int codepoint = 0; bool status = true; do { codepoint = (codepoint * (is_hex ? 16 : 10)) + digit; utf8iterator_next(input); digit = parse_digit(utf8iterator_current(input), is_hex); } while (digit != -1); if (utf8iterator_current(input) != ';') { add_codepoint_error( parser, input, GUMBO_ERR_NUMERIC_CHAR_REF_WITHOUT_SEMICOLON, codepoint); status = false; } else { utf8iterator_next(input); } int replacement = maybe_replace_codepoint(codepoint); if (replacement != -1) { add_codepoint_error( parser, input, GUMBO_ERR_NUMERIC_CHAR_REF_INVALID, codepoint); *output = replacement; return false; } if ((codepoint >= 0xd800 && codepoint <= 0xdfff) || codepoint > 0x10ffff) { add_codepoint_error( parser, input, GUMBO_ERR_NUMERIC_CHAR_REF_INVALID, codepoint); *output = 0xfffd; return false; } if (utf8_is_invalid_code_point(codepoint) || codepoint == 0xb) { add_codepoint_error( parser, input, GUMBO_ERR_NUMERIC_CHAR_REF_INVALID, codepoint); status = false; // But return it anyway, per spec. } *output = codepoint; return status; } static bool maybe_add_invalid_named_reference( struct GumboInternalParser* parser, Utf8Iterator* input) { // The iterator will always be reset in this code path, so we don't need to // worry about consuming characters. const char* start = utf8iterator_get_char_pointer(input); int c = utf8iterator_current(input); while ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9')) { utf8iterator_next(input); c = utf8iterator_current(input); } if (c == ';') { GumboStringPiece bad_ref; bad_ref.data = start; bad_ref.length = utf8iterator_get_char_pointer(input) - start; add_named_reference_error( parser, input, GUMBO_ERR_NAMED_CHAR_REF_INVALID, bad_ref); return false; } return true; } #line 2465 "char_ref.rl" // clang-format off #line 238 "char_ref.c" static const short _char_ref_actions[] = { 0, 1, 0, 1, 1, 1, 2, 1, 3, 1, 4, 1, 5, 1, 6, 1, 7, 1, 8, 1, 9, 1, 10, 1, 11, 1, 12, 1, 13, 1, 14, 1, 15, 1, 16, 1, 17, 1, 18, 1, 19, 1, 20, 1, 21, 1, 22, 1, 23, 1, 24, 1, 25, 1, 26, 1, 27, 1, 28, 1, 29, 1, 30, 1, 31, 1, 32, 1, 33, 1, 34, 1, 35, 1, 36, 1, 37, 1, 38, 1, 39, 1, 40, 1, 41, 1, 42, 1, 43, 1, 44, 1, 45, 1, 46, 1, 47, 1, 48, 1, 49, 1, 50, 1, 51, 1, 52, 1, 53, 1, 54, 1, 55, 1, 56, 1, 57, 1, 58, 1, 59, 1, 60, 1, 61, 1, 62, 1, 63, 1, 64, 1, 65, 1, 66, 1, 67, 1, 68, 1, 69, 1, 70, 1, 71, 1, 72, 1, 73, 1, 74, 1, 75, 1, 76, 1, 77, 1, 78, 1, 79, 1, 80, 1, 81, 1, 82, 1, 83, 1, 84, 1, 85, 1, 86, 1, 87, 1, 88, 1, 89, 1, 90, 1, 91, 1, 92, 1, 93, 1, 94, 1, 95, 1, 96, 1, 97, 1, 98, 1, 99, 1, 100, 1, 101, 1, 102, 1, 103, 1, 104, 1, 105, 1, 106, 1, 107, 1, 108, 1, 109, 1, 110, 1, 111, 1, 112, 1, 113, 1, 114, 1, 115, 1, 116, 1, 117, 1, 118, 1, 119, 1, 120, 1, 121, 1, 122, 1, 123, 1, 124, 1, 125, 1, 126, 1, 127, 1, 128, 1, 129, 1, 130, 1, 131, 1, 132, 1, 133, 1, 134, 1, 135, 1, 136, 1, 137, 1, 138, 1, 139, 1, 140, 1, 141, 1, 142, 1, 143, 1, 144, 1, 145, 1, 146, 1, 147, 1, 148, 1, 149, 1, 150, 1, 151, 1, 152, 1, 153, 1, 154, 1, 155, 1, 156, 1, 157, 1, 158, 1, 159, 1, 160, 1, 161, 1, 162, 1, 163, 1, 164, 1, 165, 1, 166, 1, 167, 1, 168, 1, 169, 1, 170, 1, 171, 1, 172, 1, 173, 1, 174, 1, 175, 1, 176, 1, 177, 1, 178, 1, 179, 1, 180, 1, 181, 1, 182, 1, 183, 1, 184, 1, 185, 1, 186, 1, 187, 1, 188, 1, 189, 1, 190, 1, 191, 1, 192, 1, 193, 1, 194, 1, 195, 1, 196, 1, 197, 1, 198, 1, 199, 1, 200, 1, 201, 1, 202, 1, 203, 1, 204, 1, 205, 1, 206, 1, 207, 1, 208, 1, 209, 1, 210, 1, 211, 1, 212, 1, 213, 1, 214, 1, 215, 1, 216, 1, 217, 1, 218, 1, 219, 1, 220, 1, 221, 1, 222, 1, 223, 1, 224, 1, 225, 1, 226, 1, 227, 1, 228, 1, 229, 1, 230, 1, 231, 1, 232, 1, 233, 1, 234, 1, 235, 1, 236, 1, 237, 1, 238, 1, 239, 1, 240, 1, 241, 1, 242, 1, 243, 1, 244, 1, 245, 1, 246, 1, 247, 1, 248, 1, 249, 1, 250, 1, 251, 1, 252, 1, 253, 1, 254, 1, 255, 1, 256, 1, 257, 1, 258, 1, 259, 1, 260, 1, 261, 1, 262, 1, 263, 1, 264, 1, 265, 1, 266, 1, 267, 1, 268, 1, 269, 1, 270, 1, 271, 1, 272, 1, 273, 1, 274, 1, 275, 1, 276, 1, 277, 1, 278, 1, 279, 1, 280, 1, 281, 1, 282, 1, 283, 1, 284, 1, 285, 1, 286, 1, 287, 1, 288, 1, 289, 1, 290, 1, 291, 1, 292, 1, 293, 1, 294, 1, 295, 1, 296, 1, 297, 1, 298, 1, 299, 1, 300, 1, 301, 1, 302, 1, 303, 1, 304, 1, 305, 1, 306, 1, 307, 1, 308, 1, 309, 1, 310, 1, 311, 1, 312, 1, 313, 1, 314, 1, 315, 1, 316, 1, 317, 1, 318, 1, 319, 1, 320, 1, 321, 1, 322, 1, 323, 1, 324, 1, 325, 1, 326, 1, 327, 1, 328, 1, 329, 1, 330, 1, 331, 1, 332, 1, 333, 1, 334, 1, 335, 1, 336, 1, 337, 1, 338, 1, 339, 1, 340, 1, 341, 1, 342, 1, 343, 1, 344, 1, 345, 1, 346, 1, 347, 1, 348, 1, 349, 1, 350, 1, 351, 1, 352, 1, 353, 1, 354, 1, 355, 1, 356, 1, 357, 1, 358, 1, 359, 1, 360, 1, 361, 1, 362, 1, 363, 1, 364, 1, 365, 1, 366, 1, 367, 1, 368, 1, 369, 1, 370, 1, 371, 1, 372, 1, 373, 1, 374, 1, 375, 1, 376, 1, 377, 1, 378, 1, 379, 1, 380, 1, 381, 1, 382, 1, 383, 1, 384, 1, 385, 1, 386, 1, 387, 1, 388, 1, 389, 1, 390, 1, 391, 1, 392, 1, 393, 1, 394, 1, 395, 1, 396, 1, 397, 1, 398, 1, 399, 1, 400, 1, 401, 1, 402, 1, 403, 1, 404, 1, 405, 1, 406, 1, 407, 1, 408, 1, 409, 1, 410, 1, 411, 1, 412, 1, 413, 1, 414, 1, 415, 1, 416, 1, 417, 1, 418, 1, 419, 1, 420, 1, 421, 1, 422, 1, 423, 1, 424, 1, 425, 1, 426, 1, 427, 1, 428, 1, 429, 1, 430, 1, 431, 1, 432, 1, 433, 1, 434, 1, 435, 1, 436, 1, 437, 1, 438, 1, 439, 1, 440, 1, 441, 1, 442, 1, 443, 1, 444, 1, 445, 1, 446, 1, 447, 1, 448, 1, 449, 1, 450, 1, 451, 1, 452, 1, 453, 1, 454, 1, 455, 1, 456, 1, 457, 1, 458, 1, 459, 1, 460, 1, 461, 1, 462, 1, 463, 1, 464, 1, 465, 1, 466, 1, 467, 1, 468, 1, 469, 1, 470, 1, 471, 1, 472, 1, 473, 1, 474, 1, 475, 1, 476, 1, 477, 1, 478, 1, 479, 1, 480, 1, 481, 1, 482, 1, 483, 1, 484, 1, 485, 1, 486, 1, 487, 1, 488, 1, 489, 1, 490, 1, 491, 1, 492, 1, 493, 1, 494, 1, 495, 1, 496, 1, 497, 1, 498, 1, 499, 1, 500, 1, 501, 1, 502, 1, 503, 1, 504, 1, 505, 1, 506, 1, 507, 1, 508, 1, 509, 1, 510, 1, 511, 1, 512, 1, 513, 1, 514, 1, 515, 1, 516, 1, 517, 1, 518, 1, 519, 1, 520, 1, 521, 1, 522, 1, 523, 1, 524, 1, 525, 1, 526, 1, 527, 1, 528, 1, 529, 1, 530, 1, 531, 1, 532, 1, 533, 1, 534, 1, 535, 1, 536, 1, 537, 1, 538, 1, 539, 1, 540, 1, 541, 1, 542, 1, 543, 1, 544, 1, 545, 1, 546, 1, 547, 1, 548, 1, 549, 1, 550, 1, 551, 1, 552, 1, 553, 1, 554, 1, 555, 1, 556, 1, 557, 1, 558, 1, 559, 1, 560, 1, 561, 1, 562, 1, 563, 1, 564, 1, 565, 1, 566, 1, 567, 1, 568, 1, 569, 1, 570, 1, 571, 1, 572, 1, 573, 1, 574, 1, 575, 1, 576, 1, 577, 1, 578, 1, 579, 1, 580, 1, 581, 1, 582, 1, 583, 1, 584, 1, 585, 1, 586, 1, 587, 1, 588, 1, 589, 1, 590, 1, 591, 1, 592, 1, 593, 1, 594, 1, 595, 1, 596, 1, 597, 1, 598, 1, 599, 1, 600, 1, 601, 1, 602, 1, 603, 1, 604, 1, 605, 1, 606, 1, 607, 1, 608, 1, 609, 1, 610, 1, 611, 1, 612, 1, 613, 1, 614, 1, 615, 1, 616, 1, 617, 1, 618, 1, 619, 1, 620, 1, 621, 1, 622, 1, 623, 1, 624, 1, 625, 1, 626, 1, 627, 1, 628, 1, 629, 1, 630, 1, 631, 1, 632, 1, 633, 1, 634, 1, 635, 1, 636, 1, 637, 1, 638, 1, 639, 1, 640, 1, 641, 1, 642, 1, 643, 1, 644, 1, 645, 1, 646, 1, 647, 1, 648, 1, 649, 1, 650, 1, 651, 1, 652, 1, 653, 1, 654, 1, 655, 1, 656, 1, 657, 1, 658, 1, 659, 1, 660, 1, 661, 1, 662, 1, 663, 1, 664, 1, 665, 1, 666, 1, 667, 1, 668, 1, 669, 1, 670, 1, 671, 1, 672, 1, 673, 1, 674, 1, 675, 1, 676, 1, 677, 1, 678, 1, 679, 1, 680, 1, 681, 1, 682, 1, 683, 1, 684, 1, 685, 1, 686, 1, 687, 1, 688, 1, 689, 1, 690, 1, 691, 1, 692, 1, 693, 1, 694, 1, 695, 1, 696, 1, 697, 1, 698, 1, 699, 1, 700, 1, 701, 1, 702, 1, 703, 1, 704, 1, 705, 1, 706, 1, 707, 1, 708, 1, 709, 1, 710, 1, 711, 1, 712, 1, 713, 1, 714, 1, 715, 1, 716, 1, 717, 1, 718, 1, 719, 1, 720, 1, 721, 1, 722, 1, 723, 1, 724, 1, 725, 1, 726, 1, 727, 1, 728, 1, 729, 1, 730, 1, 731, 1, 732, 1, 733, 1, 734, 1, 735, 1, 736, 1, 737, 1, 738, 1, 739, 1, 740, 1, 741, 1, 742, 1, 743, 1, 744, 1, 745, 1, 746, 1, 747, 1, 748, 1, 749, 1, 750, 1, 751, 1, 752, 1, 753, 1, 754, 1, 755, 1, 756, 1, 757, 1, 758, 1, 759, 1, 760, 1, 761, 1, 762, 1, 763, 1, 764, 1, 765, 1, 766, 1, 767, 1, 768, 1, 769, 1, 770, 1, 771, 1, 772, 1, 773, 1, 774, 1, 775, 1, 776, 1, 777, 1, 778, 1, 779, 1, 780, 1, 781, 1, 782, 1, 783, 1, 784, 1, 785, 1, 786, 1, 787, 1, 788, 1, 789, 1, 790, 1, 791, 1, 792, 1, 793, 1, 794, 1, 795, 1, 796, 1, 797, 1, 798, 1, 799, 1, 800, 1, 801, 1, 802, 1, 803, 1, 804, 1, 805, 1, 806, 1, 807, 1, 808, 1, 809, 1, 810, 1, 811, 1, 812, 1, 813, 1, 814, 1, 815, 1, 816, 1, 817, 1, 818, 1, 819, 1, 820, 1, 821, 1, 822, 1, 823, 1, 824, 1, 825, 1, 826, 1, 827, 1, 828, 1, 829, 1, 830, 1, 831, 1, 832, 1, 833, 1, 834, 1, 835, 1, 836, 1, 837, 1, 838, 1, 839, 1, 840, 1, 841, 1, 842, 1, 843, 1, 844, 1, 845, 1, 846, 1, 847, 1, 848, 1, 849, 1, 850, 1, 851, 1, 852, 1, 853, 1, 854, 1, 855, 1, 856, 1, 857, 1, 858, 1, 859, 1, 860, 1, 861, 1, 862, 1, 863, 1, 864, 1, 865, 1, 866, 1, 867, 1, 868, 1, 869, 1, 870, 1, 871, 1, 872, 1, 873, 1, 874, 1, 875, 1, 876, 1, 877, 1, 878, 1, 879, 1, 880, 1, 881, 1, 882, 1, 883, 1, 884, 1, 885, 1, 886, 1, 887, 1, 888, 1, 889, 1, 890, 1, 891, 1, 892, 1, 893, 1, 894, 1, 895, 1, 896, 1, 897, 1, 898, 1, 899, 1, 900, 1, 901, 1, 902, 1, 903, 1, 904, 1, 905, 1, 906, 1, 907, 1, 908, 1, 909, 1, 910, 1, 911, 1, 912, 1, 913, 1, 914, 1, 915, 1, 916, 1, 917, 1, 918, 1, 919, 1, 920, 1, 921, 1, 922, 1, 923, 1, 924, 1, 925, 1, 926, 1, 927, 1, 928, 1, 929, 1, 930, 1, 931, 1, 932, 1, 933, 1, 934, 1, 935, 1, 936, 1, 937, 1, 938, 1, 939, 1, 940, 1, 941, 1, 942, 1, 943, 1, 944, 1, 945, 1, 946, 1, 947, 1, 948, 1, 949, 1, 950, 1, 951, 1, 952, 1, 953, 1, 954, 1, 955, 1, 956, 1, 957, 1, 958, 1, 959, 1, 960, 1, 961, 1, 962, 1, 963, 1, 964, 1, 965, 1, 966, 1, 967, 1, 968, 1, 969, 1, 970, 1, 971, 1, 972, 1, 973, 1, 974, 1, 975, 1, 976, 1, 977, 1, 978, 1, 979, 1, 980, 1, 981, 1, 982, 1, 983, 1, 984, 1, 985, 1, 986, 1, 987, 1, 988, 1, 989, 1, 990, 1, 991, 1, 992, 1, 993, 1, 994, 1, 995, 1, 996, 1, 997, 1, 998, 1, 999, 1, 1000, 1, 1001, 1, 1002, 1, 1003, 1, 1004, 1, 1005, 1, 1006, 1, 1007, 1, 1008, 1, 1009, 1, 1010, 1, 1011, 1, 1012, 1, 1013, 1, 1014, 1, 1015, 1, 1016, 1, 1017, 1, 1018, 1, 1019, 1, 1020, 1, 1021, 1, 1022, 1, 1023, 1, 1024, 1, 1025, 1, 1026, 1, 1027, 1, 1028, 1, 1029, 1, 1030, 1, 1031, 1, 1032, 1, 1033, 1, 1034, 1, 1035, 1, 1036, 1, 1037, 1, 1038, 1, 1039, 1, 1040, 1, 1041, 1, 1042, 1, 1043, 1, 1044, 1, 1045, 1, 1046, 1, 1047, 1, 1048, 1, 1049, 1, 1050, 1, 1051, 1, 1052, 1, 1053, 1, 1054, 1, 1055, 1, 1056, 1, 1057, 1, 1058, 1, 1059, 1, 1060, 1, 1061, 1, 1062, 1, 1063, 1, 1064, 1, 1065, 1, 1066, 1, 1067, 1, 1068, 1, 1069, 1, 1070, 1, 1071, 1, 1072, 1, 1073, 1, 1074, 1, 1075, 1, 1076, 1, 1077, 1, 1078, 1, 1079, 1, 1080, 1, 1081, 1, 1082, 1, 1083, 1, 1084, 1, 1085, 1, 1086, 1, 1087, 1, 1088, 1, 1089, 1, 1090, 1, 1091, 1, 1092, 1, 1093, 1, 1094, 1, 1095, 1, 1096, 1, 1097, 1, 1098, 1, 1099, 1, 1100, 1, 1101, 1, 1102, 1, 1103, 1, 1104, 1, 1105, 1, 1106, 1, 1107, 1, 1108, 1, 1109, 1, 1110, 1, 1111, 1, 1112, 1, 1113, 1, 1114, 1, 1115, 1, 1116, 1, 1117, 1, 1118, 1, 1119, 1, 1120, 1, 1121, 1, 1122, 1, 1123, 1, 1124, 1, 1125, 1, 1126, 1, 1127, 1, 1128, 1, 1129, 1, 1130, 1, 1131, 1, 1132, 1, 1133, 1, 1134, 1, 1135, 1, 1136, 1, 1137, 1, 1138, 1, 1139, 1, 1140, 1, 1141, 1, 1142, 1, 1143, 1, 1144, 1, 1145, 1, 1146, 1, 1147, 1, 1148, 1, 1149, 1, 1150, 1, 1151, 1, 1152, 1, 1153, 1, 1154, 1, 1155, 1, 1156, 1, 1157, 1, 1158, 1, 1159, 1, 1160, 1, 1161, 1, 1162, 1, 1163, 1, 1164, 1, 1165, 1, 1166, 1, 1167, 1, 1168, 1, 1169, 1, 1170, 1, 1171, 1, 1172, 1, 1173, 1, 1174, 1, 1175, 1, 1176, 1, 1177, 1, 1178, 1, 1179, 1, 1180, 1, 1181, 1, 1182, 1, 1183, 1, 1184, 1, 1185, 1, 1186, 1, 1187, 1, 1188, 1, 1189, 1, 1190, 1, 1191, 1, 1192, 1, 1193, 1, 1194, 1, 1195, 1, 1196, 1, 1197, 1, 1198, 1, 1199, 1, 1200, 1, 1201, 1, 1202, 1, 1203, 1, 1204, 1, 1205, 1, 1206, 1, 1207, 1, 1208, 1, 1209, 1, 1210, 1, 1211, 1, 1212, 1, 1213, 1, 1214, 1, 1215, 1, 1216, 1, 1217, 1, 1218, 1, 1219, 1, 1220, 1, 1221, 1, 1222, 1, 1223, 1, 1224, 1, 1225, 1, 1226, 1, 1227, 1, 1228, 1, 1229, 1, 1230, 1, 1231, 1, 1232, 1, 1233, 1, 1234, 1, 1235, 1, 1236, 1, 1237, 1, 1238, 1, 1239, 1, 1240, 1, 1241, 1, 1242, 1, 1243, 1, 1244, 1, 1245, 1, 1246, 1, 1247, 1, 1248, 1, 1249, 1, 1250, 1, 1251, 1, 1252, 1, 1253, 1, 1254, 1, 1255, 1, 1256, 1, 1257, 1, 1258, 1, 1259, 1, 1260, 1, 1261, 1, 1262, 1, 1263, 1, 1264, 1, 1265, 1, 1266, 1, 1267, 1, 1268, 1, 1269, 1, 1270, 1, 1271, 1, 1272, 1, 1273, 1, 1274, 1, 1275, 1, 1276, 1, 1277, 1, 1278, 1, 1279, 1, 1280, 1, 1281, 1, 1282, 1, 1283, 1, 1284, 1, 1285, 1, 1286, 1, 1287, 1, 1288, 1, 1289, 1, 1290, 1, 1291, 1, 1292, 1, 1293, 1, 1294, 1, 1295, 1, 1296, 1, 1297, 1, 1298, 1, 1299, 1, 1300, 1, 1301, 1, 1302, 1, 1303, 1, 1304, 1, 1305, 1, 1306, 1, 1307, 1, 1308, 1, 1309, 1, 1310, 1, 1311, 1, 1312, 1, 1313, 1, 1314, 1, 1315, 1, 1316, 1, 1317, 1, 1318, 1, 1319, 1, 1320, 1, 1321, 1, 1322, 1, 1323, 1, 1324, 1, 1325, 1, 1326, 1, 1327, 1, 1328, 1, 1329, 1, 1330, 1, 1331, 1, 1332, 1, 1333, 1, 1334, 1, 1335, 1, 1336, 1, 1337, 1, 1338, 1, 1339, 1, 1340, 1, 1341, 1, 1342, 1, 1343, 1, 1344, 1, 1345, 1, 1346, 1, 1347, 1, 1348, 1, 1349, 1, 1350, 1, 1351, 1, 1352, 1, 1353, 1, 1354, 1, 1355, 1, 1356, 1, 1357, 1, 1358, 1, 1359, 1, 1360, 1, 1361, 1, 1362, 1, 1363, 1, 1364, 1, 1365, 1, 1366, 1, 1367, 1, 1368, 1, 1369, 1, 1370, 1, 1371, 1, 1372, 1, 1373, 1, 1374, 1, 1375, 1, 1376, 1, 1377, 1, 1378, 1, 1379, 1, 1380, 1, 1381, 1, 1382, 1, 1383, 1, 1384, 1, 1385, 1, 1386, 1, 1387, 1, 1388, 1, 1389, 1, 1390, 1, 1391, 1, 1392, 1, 1393, 1, 1394, 1, 1395, 1, 1396, 1, 1397, 1, 1398, 1, 1399, 1, 1400, 1, 1401, 1, 1402, 1, 1403, 1, 1404, 1, 1405, 1, 1406, 1, 1407, 1, 1408, 1, 1409, 1, 1410, 1, 1411, 1, 1412, 1, 1413, 1, 1414, 1, 1415, 1, 1416, 1, 1417, 1, 1418, 1, 1419, 1, 1420, 1, 1421, 1, 1422, 1, 1423, 1, 1424, 1, 1425, 1, 1426, 1, 1427, 1, 1428, 1, 1429, 1, 1430, 1, 1431, 1, 1432, 1, 1433, 1, 1434, 1, 1435, 1, 1436, 1, 1437, 1, 1438, 1, 1439, 1, 1440, 1, 1441, 1, 1442, 1, 1443, 1, 1444, 1, 1445, 1, 1446, 1, 1447, 1, 1448, 1, 1449, 1, 1450, 1, 1451, 1, 1452, 1, 1453, 1, 1454, 1, 1455, 1, 1456, 1, 1457, 1, 1458, 1, 1459, 1, 1460, 1, 1461, 1, 1462, 1, 1463, 1, 1464, 1, 1465, 1, 1466, 1, 1467, 1, 1468, 1, 1469, 1, 1470, 1, 1471, 1, 1472, 1, 1473, 1, 1474, 1, 1475, 1, 1476, 1, 1477, 1, 1478, 1, 1479, 1, 1480, 1, 1481, 1, 1482, 1, 1483, 1, 1484, 1, 1485, 1, 1486, 1, 1487, 1, 1488, 1, 1489, 1, 1490, 1, 1491, 1, 1492, 1, 1493, 1, 1494, 1, 1495, 1, 1496, 1, 1497, 1, 1498, 1, 1499, 1, 1500, 1, 1501, 1, 1502, 1, 1503, 1, 1504, 1, 1505, 1, 1506, 1, 1507, 1, 1508, 1, 1509, 1, 1510, 1, 1511, 1, 1512, 1, 1513, 1, 1514, 1, 1515, 1, 1516, 1, 1517, 1, 1518, 1, 1519, 1, 1520, 1, 1521, 1, 1522, 1, 1523, 1, 1524, 1, 1525, 1, 1526, 1, 1527, 1, 1528, 1, 1529, 1, 1530, 1, 1531, 1, 1532, 1, 1533, 1, 1534, 1, 1535, 1, 1536, 1, 1537, 1, 1538, 1, 1539, 1, 1540, 1, 1541, 1, 1542, 1, 1543, 1, 1544, 1, 1545, 1, 1546, 1, 1547, 1, 1548, 1, 1549, 1, 1550, 1, 1551, 1, 1552, 1, 1553, 1, 1554, 1, 1555, 1, 1556, 1, 1557, 1, 1558, 1, 1559, 1, 1560, 1, 1561, 1, 1562, 1, 1563, 1, 1564, 1, 1565, 1, 1566, 1, 1567, 1, 1568, 1, 1569, 1, 1570, 1, 1571, 1, 1572, 1, 1573, 1, 1574, 1, 1575, 1, 1576, 1, 1577, 1, 1578, 1, 1579, 1, 1580, 1, 1581, 1, 1582, 1, 1583, 1, 1584, 1, 1585, 1, 1586, 1, 1587, 1, 1588, 1, 1589, 1, 1590, 1, 1591, 1, 1592, 1, 1593, 1, 1594, 1, 1595, 1, 1596, 1, 1597, 1, 1598, 1, 1599, 1, 1600, 1, 1601, 1, 1602, 1, 1603, 1, 1604, 1, 1605, 1, 1606, 1, 1607, 1, 1608, 1, 1609, 1, 1610, 1, 1611, 1, 1612, 1, 1613, 1, 1614, 1, 1615, 1, 1616, 1, 1617, 1, 1618, 1, 1619, 1, 1620, 1, 1621, 1, 1622, 1, 1623, 1, 1624, 1, 1625, 1, 1626, 1, 1627, 1, 1628, 1, 1629, 1, 1630, 1, 1631, 1, 1632, 1, 1633, 1, 1634, 1, 1635, 1, 1636, 1, 1637, 1, 1638, 1, 1639, 1, 1640, 1, 1641, 1, 1642, 1, 1643, 1, 1644, 1, 1645, 1, 1646, 1, 1647, 1, 1648, 1, 1649, 1, 1650, 1, 1651, 1, 1652, 1, 1653, 1, 1654, 1, 1655, 1, 1656, 1, 1657, 1, 1658, 1, 1659, 1, 1660, 1, 1661, 1, 1662, 1, 1663, 1, 1664, 1, 1665, 1, 1666, 1, 1667, 1, 1668, 1, 1669, 1, 1670, 1, 1671, 1, 1672, 1, 1673, 1, 1674, 1, 1675, 1, 1676, 1, 1677, 1, 1678, 1, 1679, 1, 1680, 1, 1681, 1, 1682, 1, 1683, 1, 1684, 1, 1685, 1, 1686, 1, 1687, 1, 1688, 1, 1689, 1, 1690, 1, 1691, 1, 1692, 1, 1693, 1, 1694, 1, 1695, 1, 1696, 1, 1697, 1, 1698, 1, 1699, 1, 1700, 1, 1701, 1, 1702, 1, 1703, 1, 1704, 1, 1705, 1, 1706, 1, 1707, 1, 1708, 1, 1709, 1, 1710, 1, 1711, 1, 1712, 1, 1713, 1, 1714, 1, 1715, 1, 1716, 1, 1717, 1, 1718, 1, 1719, 1, 1720, 1, 1721, 1, 1722, 1, 1723, 1, 1724, 1, 1725, 1, 1726, 1, 1727, 1, 1728, 1, 1729, 1, 1730, 1, 1731, 1, 1732, 1, 1733, 1, 1734, 1, 1735, 1, 1736, 1, 1737, 1, 1738, 1, 1739, 1, 1740, 1, 1741, 1, 1742, 1, 1743, 1, 1744, 1, 1745, 1, 1746, 1, 1747, 1, 1748, 1, 1749, 1, 1750, 1, 1751, 1, 1752, 1, 1753, 1, 1754, 1, 1755, 1, 1756, 1, 1757, 1, 1758, 1, 1759, 1, 1760, 1, 1761, 1, 1762, 1, 1763, 1, 1764, 1, 1765, 1, 1766, 1, 1767, 1, 1768, 1, 1769, 1, 1770, 1, 1771, 1, 1772, 1, 1773, 1, 1774, 1, 1775, 1, 1776, 1, 1777, 1, 1778, 1, 1779, 1, 1780, 1, 1781, 1, 1782, 1, 1783, 1, 1784, 1, 1785, 1, 1786, 1, 1787, 1, 1788, 1, 1789, 1, 1790, 1, 1791, 1, 1792, 1, 1793, 1, 1794, 1, 1795, 1, 1796, 1, 1797, 1, 1798, 1, 1799, 1, 1800, 1, 1801, 1, 1802, 1, 1803, 1, 1804, 1, 1805, 1, 1806, 1, 1807, 1, 1808, 1, 1809, 1, 1810, 1, 1811, 1, 1812, 1, 1813, 1, 1814, 1, 1815, 1, 1816, 1, 1817, 1, 1818, 1, 1819, 1, 1820, 1, 1821, 1, 1822, 1, 1823, 1, 1824, 1, 1825, 1, 1826, 1, 1827, 1, 1828, 1, 1829, 1, 1830, 1, 1831, 1, 1832, 1, 1833, 1, 1834, 1, 1835, 1, 1836, 1, 1837, 1, 1838, 1, 1839, 1, 1840, 1, 1841, 1, 1842, 1, 1843, 1, 1844, 1, 1845, 1, 1846, 1, 1847, 1, 1848, 1, 1849, 1, 1850, 1, 1851, 1, 1852, 1, 1853, 1, 1854, 1, 1855, 1, 1856, 1, 1857, 1, 1858, 1, 1859, 1, 1860, 1, 1861, 1, 1862, 1, 1863, 1, 1864, 1, 1865, 1, 1866, 1, 1867, 1, 1868, 1, 1869, 1, 1870, 1, 1871, 1, 1872, 1, 1873, 1, 1874, 1, 1875, 1, 1876, 1, 1877, 1, 1878, 1, 1879, 1, 1880, 1, 1881, 1, 1882, 1, 1883, 1, 1884, 1, 1885, 1, 1886, 1, 1887, 1, 1888, 1, 1889, 1, 1890, 1, 1891, 1, 1892, 1, 1893, 1, 1894, 1, 1895, 1, 1896, 1, 1897, 1, 1898, 1, 1899, 1, 1900, 1, 1901, 1, 1902, 1, 1903, 1, 1904, 1, 1905, 1, 1906, 1, 1907, 1, 1908, 1, 1909, 1, 1910, 1, 1911, 1, 1912, 1, 1913, 1, 1914, 1, 1915, 1, 1916, 1, 1917, 1, 1918, 1, 1919, 1, 1920, 1, 1921, 1, 1922, 1, 1923, 1, 1924, 1, 1925, 1, 1926, 1, 1927, 1, 1928, 1, 1929, 1, 1930, 1, 1931, 1, 1932, 1, 1933, 1, 1934, 1, 1935, 1, 1936, 1, 1937, 1, 1938, 1, 1939, 1, 1940, 1, 1941, 1, 1942, 1, 1943, 1, 1944, 1, 1945, 1, 1946, 1, 1947, 1, 1948, 1, 1949, 1, 1950, 1, 1951, 1, 1952, 1, 1953, 1, 1954, 1, 1955, 1, 1956, 1, 1957, 1, 1958, 1, 1959, 1, 1960, 1, 1961, 1, 1962, 1, 1963, 1, 1964, 1, 1965, 1, 1966, 1, 1967, 1, 1968, 1, 1969, 1, 1970, 1, 1971, 1, 1972, 1, 1973, 1, 1974, 1, 1975, 1, 1976, 1, 1977, 1, 1978, 1, 1979, 1, 1980, 1, 1981, 1, 1982, 1, 1983, 1, 1984, 1, 1985, 1, 1986, 1, 1987, 1, 1988, 1, 1989, 1, 1990, 1, 1991, 1, 1992, 1, 1993, 1, 1994, 1, 1995, 1, 1996, 1, 1997, 1, 1998, 1, 1999, 1, 2000, 1, 2001, 1, 2002, 1, 2003, 1, 2004, 1, 2005, 1, 2006, 1, 2007, 1, 2008, 1, 2009, 1, 2010, 1, 2011, 1, 2012, 1, 2013, 1, 2014, 1, 2015, 1, 2016, 1, 2017, 1, 2018, 1, 2019, 1, 2020, 1, 2021, 1, 2022, 1, 2023, 1, 2024, 1, 2025, 1, 2026, 1, 2027, 1, 2028, 1, 2029, 1, 2030, 1, 2031, 1, 2032, 1, 2033, 1, 2034, 1, 2035, 1, 2036, 1, 2037, 1, 2038, 1, 2039, 1, 2040, 1, 2041, 1, 2042, 1, 2043, 1, 2044, 1, 2045, 1, 2046, 1, 2047, 1, 2048, 1, 2049, 1, 2050, 1, 2051, 1, 2052, 1, 2053, 1, 2054, 1, 2055, 1, 2056, 1, 2057, 1, 2058, 1, 2059, 1, 2060, 1, 2061, 1, 2062, 1, 2063, 1, 2064, 1, 2065, 1, 2066, 1, 2067, 1, 2068, 1, 2069, 1, 2070, 1, 2071, 1, 2072, 1, 2073, 1, 2074, 1, 2075, 1, 2076, 1, 2077, 1, 2078, 1, 2079, 1, 2080, 1, 2081, 1, 2082, 1, 2083, 1, 2084, 1, 2085, 1, 2086, 1, 2087, 1, 2088, 1, 2089, 1, 2090, 1, 2091, 1, 2092, 1, 2093, 1, 2094, 1, 2095, 1, 2096, 1, 2097, 1, 2098, 1, 2099, 1, 2100, 1, 2101, 1, 2102, 1, 2103, 1, 2104, 1, 2105, 1, 2106, 1, 2107, 1, 2108, 1, 2109, 1, 2110, 1, 2111, 1, 2112, 1, 2113, 1, 2114, 1, 2115, 1, 2116, 1, 2117, 1, 2118, 1, 2119, 1, 2120, 1, 2121, 1, 2122, 1, 2123, 1, 2124, 1, 2125, 1, 2126, 1, 2127, 1, 2128, 1, 2129, 1, 2130, 1, 2131, 1, 2132, 1, 2133, 1, 2134, 1, 2135, 1, 2136, 1, 2137, 1, 2138, 1, 2139, 1, 2140, 1, 2141, 1, 2142, 1, 2143, 1, 2144, 1, 2145, 1, 2146, 1, 2147, 1, 2148, 1, 2149, 1, 2150, 1, 2151, 1, 2152, 1, 2153, 1, 2154, 1, 2155, 1, 2156, 1, 2157, 1, 2158, 1, 2159, 1, 2160, 1, 2161, 1, 2162, 1, 2163, 1, 2164, 1, 2165, 1, 2166, 1, 2167, 1, 2168, 1, 2169, 1, 2170, 1, 2171, 1, 2172, 1, 2173, 1, 2174, 1, 2175, 1, 2176, 1, 2177, 1, 2178, 1, 2179, 1, 2180, 1, 2181, 1, 2182, 1, 2183, 1, 2184, 1, 2185, 1, 2186, 1, 2187, 1, 2188, 1, 2189, 1, 2190, 1, 2191, 1, 2192, 1, 2193, 1, 2194, 1, 2195, 1, 2196, 1, 2197, 1, 2198, 1, 2199, 1, 2200, 1, 2201, 1, 2202, 1, 2203, 1, 2204, 1, 2205, 1, 2206, 1, 2207, 1, 2208, 1, 2209, 1, 2210, 1, 2211, 1, 2212, 1, 2213, 1, 2214, 1, 2215, 1, 2216, 1, 2217, 1, 2218, 1, 2219, 1, 2220, 1, 2221, 1, 2222, 1, 2223, 1, 2224, 1, 2225, 1, 2226, 1, 2227, 1, 2228, 1, 2229, 1, 2230, 1, 2231, 1, 2232, 1, 2233, 1, 2234, 1, 2235, 1, 2236, 1, 2237, 1, 2238, 1, 2239, 1, 2240, 1, 2241 }; static const char _char_ref_trans_keys[] = { 0, 0, 69, 117, 108, 108, 105, 105, 103, 103, 80, 80, 99, 99, 117, 117, 116, 116, 101, 101, 114, 114, 101, 101, 118, 118, 101, 101, 59, 59, 105, 121, 114, 114, 99, 99, 59, 59, 114, 114, 59, 59, 114, 114, 97, 97, 118, 118, 101, 101, 112, 112, 104, 104, 97, 97, 59, 59, 97, 97, 99, 99, 114, 114, 59, 59, 100, 100, 59, 59, 103, 112, 111, 111, 110, 110, 59, 59, 102, 102, 59, 59, 112, 112, 108, 108, 121, 121, 70, 70, 117, 117, 110, 110, 99, 99, 116, 116, 105, 105, 111, 111, 110, 110, 59, 59, 105, 105, 110, 110, 103, 103, 99, 115, 114, 114, 59, 59, 105, 105, 103, 103, 110, 110, 59, 59, 105, 105, 108, 108, 100, 100, 101, 101, 109, 109, 108, 108, 97, 117, 99, 114, 107, 107, 115, 115, 108, 108, 97, 97, 115, 115, 104, 104, 59, 59, 118, 119, 59, 59, 101, 101, 100, 100, 59, 59, 121, 121, 59, 59, 99, 116, 97, 97, 117, 117, 115, 115, 101, 101, 59, 59, 110, 110, 111, 111, 117, 117, 108, 108, 108, 108, 105, 105, 115, 115, 59, 59, 97, 97, 59, 59, 114, 114, 59, 59, 112, 112, 102, 102, 59, 59, 101, 101, 118, 118, 101, 101, 59, 59, 99, 99, 114, 114, 59, 59, 109, 109, 112, 112, 101, 101, 113, 113, 59, 59, 72, 117, 99, 99, 121, 121, 59, 59, 80, 80, 89, 89, 99, 121, 117, 117, 116, 116, 101, 101, 59, 59, 59, 105, 116, 116, 97, 97, 108, 108, 68, 68, 105, 105, 102, 102, 102, 102, 101, 101, 114, 114, 101, 101, 110, 110, 116, 116, 105, 105, 97, 97, 108, 108, 68, 68, 59, 59, 108, 108, 101, 101, 121, 121, 115, 115, 59, 59, 97, 111, 114, 114, 111, 111, 110, 110, 59, 59, 100, 100, 105, 105, 108, 108, 114, 114, 99, 99, 59, 59, 110, 110, 105, 105, 110, 110, 116, 116, 59, 59, 111, 111, 116, 116, 59, 59, 100, 110, 105, 105, 108, 108, 108, 108, 97, 97, 59, 59, 116, 116, 101, 101, 114, 114, 68, 68, 111, 111, 116, 116, 59, 59, 114, 114, 59, 59, 105, 105, 59, 59, 114, 114, 99, 99, 108, 108, 101, 101, 68, 84, 111, 111, 116, 116, 59, 59, 105, 105, 110, 110, 117, 117, 115, 115, 59, 59, 108, 108, 117, 117, 115, 115, 59, 59, 105, 105, 109, 109, 101, 101, 115, 115, 59, 59, 111, 111, 99, 115, 107, 107, 119, 119, 105, 105, 115, 115, 101, 101, 67, 67, 111, 111, 110, 110, 116, 116, 111, 111, 117, 117, 114, 114, 73, 73, 110, 110, 116, 116, 101, 101, 103, 103, 114, 114, 97, 97, 108, 108, 59, 59, 101, 101, 67, 67, 117, 117, 114, 114, 108, 108, 121, 121, 68, 81, 111, 111, 117, 117, 98, 98, 108, 108, 101, 101, 81, 81, 117, 117, 111, 111, 116, 116, 101, 101, 59, 59, 117, 117, 111, 111, 116, 116, 101, 101, 59, 59, 108, 117, 111, 111, 110, 110, 59, 101, 59, 59, 103, 116, 114, 114, 117, 117, 101, 101, 110, 110, 116, 116, 59, 59, 110, 110, 116, 116, 59, 59, 111, 111, 117, 117, 114, 114, 73, 73, 110, 110, 116, 116, 101, 101, 103, 103, 114, 114, 97, 97, 108, 108, 59, 59, 102, 114, 59, 59, 111, 111, 100, 100, 117, 117, 99, 99, 116, 116, 59, 59, 110, 110, 116, 116, 101, 101, 114, 114, 67, 67, 108, 108, 111, 111, 99, 99, 107, 107, 119, 119, 105, 105, 115, 115, 101, 101, 67, 67, 111, 111, 110, 110, 116, 116, 111, 111, 117, 117, 114, 114, 73, 73, 110, 110, 116, 116, 101, 101, 103, 103, 114, 114, 97, 97, 108, 108, 59, 59, 111, 111, 115, 115, 115, 115, 59, 59, 99, 99, 114, 114, 59, 59, 112, 112, 59, 67, 97, 97, 112, 112, 59, 59, 68, 115, 59, 111, 116, 116, 114, 114, 97, 97, 104, 104, 100, 100, 59, 59, 99, 99, 121, 121, 59, 59, 99, 99, 121, 121, 59, 59, 99, 99, 121, 121, 59, 59, 103, 115, 103, 103, 101, 101, 114, 114, 59, 59, 114, 114, 59, 59, 104, 104, 118, 118, 59, 59, 97, 121, 114, 114, 111, 111, 110, 110, 59, 59, 59, 59, 108, 108, 59, 116, 97, 97, 59, 59, 114, 114, 59, 59, 97, 102, 99, 109, 114, 114, 105, 105, 116, 116, 105, 105, 99, 99, 97, 97, 108, 108, 65, 84, 99, 99, 117, 117, 116, 116, 101, 101, 59, 59, 111, 111, 116, 117, 59, 59, 98, 98, 108, 108, 101, 101, 65, 65, 99, 99, 117, 117, 116, 116, 101, 101, 59, 59, 114, 114, 97, 97, 118, 118, 101, 101, 59, 59, 105, 105, 108, 108, 100, 100, 101, 101, 59, 59, 111, 111, 110, 110, 100, 100, 59, 59, 102, 102, 101, 101, 114, 114, 101, 101, 110, 110, 116, 116, 105, 105, 97, 97, 108, 108, 68, 68, 59, 59, 112, 119, 102, 102, 59, 59, 59, 69, 111, 111, 116, 116, 59, 59, 113, 113, 117, 117, 97, 97, 108, 108, 59, 59, 98, 98, 108, 108, 101, 101, 67, 86, 111, 111, 110, 110, 116, 116, 111, 111, 117, 117, 114, 114, 73, 73, 110, 110, 116, 116, 101, 101, 103, 103, 114, 114, 97, 97, 108, 108, 59, 59, 111, 111, 116, 119, 59, 59, 110, 110, 65, 65, 114, 114, 114, 114, 111, 111, 119, 119, 59, 59, 101, 111, 102, 102, 116, 116, 65, 84, 114, 114, 114, 114, 111, 111, 119, 119, 59, 59, 105, 105, 103, 103, 104, 104, 116, 116, 65, 65, 114, 114, 114, 114, 111, 111, 119, 119, 59, 59, 101, 101, 101, 101, 59, 59, 110, 110, 103, 103, 76, 82, 101, 101, 102, 102, 116, 116, 65, 82, 114, 114, 114, 114, 111, 111, 119, 119, 59, 59, 105, 105, 103, 103, 104, 104, 116, 116, 65, 65, 114, 114, 114, 114, 111, 111, 119, 119, 59, 59, 105, 105, 103, 103, 104, 104, 116, 116, 65, 65, 114, 114, 114, 114, 111, 111, 119, 119, 59, 59, 105, 105, 103, 103, 104, 104, 116, 116, 65, 84, 114, 114, 114, 114, 111, 111, 119, 119, 59, 59, 101, 101, 101, 101, 59, 59, 112, 112, 65, 68, 114, 114, 114, 114, 111, 111, 119, 119, 59, 59, 111, 111, 119, 119, 110, 110, 65, 65, 114, 114, 114, 114, 111, 111, 119, 119, 59, 59, 101, 101, 114, 114, 116, 116, 105, 105, 99, 99, 97, 97, 108, 108, 66, 66, 97, 97, 114, 114, 59, 59, 110, 110, 65, 97, 114, 114, 114, 114, 111, 111, 119, 119, 59, 85, 97, 97, 114, 114, 59, 59, 112, 112, 65, 65, 114, 114, 114, 114, 111, 111, 119, 119, 59, 59, 114, 114, 101, 101, 118, 118, 101, 101, 59, 59, 101, 101, 102, 102, 116, 116, 82, 86, 105, 105, 103, 103, 104, 104, 116, 116, 86, 86, 101, 101, 99, 99, 116, 116, 111, 111, 114, 114, 59, 59, 101, 101, 101, 101, 86, 86, 101, 101, 99, 99, 116, 116, 111, 111, 114, 114, 59, 59, 101, 101, 99, 99, 116, 116, 111, 111, 114, 114, 59, 66, 97, 97, 114, 114, 59, 59, 105, 105, 103, 103, 104, 104, 116, 116, 84, 86, 101, 101, 101, 101, 86, 86, 101, 101, 99, 99, 116, 116, 111, 111, 114, 114, 59, 59, 101, 101, 99, 99, 116, 116, 111, 111, 114, 114, 59, 66, 97, 97, 114, 114, 59, 59, 101, 101, 101, 101, 59, 65, 114, 114, 114, 114, 111, 111, 119, 119, 59, 59, 114, 114, 114, 114, 111, 111, 119, 119, 59, 59, 99, 116, 114, 114, 59, 59, 114, 114, 111, 111, 107, 107, 59, 59, 78, 120, 71, 71, 59, 59, 72, 72, 99, 99, 117, 117, 116, 116, 101, 101, 97, 121, 114, 114, 111, 111, 110, 110, 59, 59, 114, 114, 99, 99, 59, 59, 111, 111, 116, 116, 59, 59, 114, 114, 59, 59, 114, 114, 97, 97, 118, 118, 101, 101, 101, 101, 109, 109, 101, 101, 110, 110, 116, 116, 59, 59, 97, 112, 99, 99, 114, 114, 59, 59, 116, 116, 121, 121, 83, 86, 109, 109, 97, 97, 108, 108, 108, 108, 83, 83, 113, 113, 117, 117, 97, 97, 114, 114, 101, 101, 59, 59, 101, 101, 114, 114, 121, 121, 83, 83, 109, 109, 97, 97, 108, 108, 108, 108, 83, 83, 113, 113, 117, 117, 97, 97, 114, 114, 101, 101, 59, 59, 103, 112, 111, 111, 110, 110, 59, 59, 102, 102, 59, 59, 115, 115, 105, 105, 108, 108, 111, 111, 110, 110, 59, 59, 117, 117, 97, 105, 108, 108, 59, 84, 105, 105, 108, 108, 100, 100, 101, 101, 59, 59, 108, 108, 105, 105, 98, 98, 114, 114, 105, 105, 117, 117, 109, 109, 59, 59, 99, 105, 114, 114, 59, 59, 109, 109, 59, 59, 97, 97, 59, 59, 109, 109, 108, 108, 105, 112, 115, 115, 116, 116, 115, 115, 59, 59, 111, 111, 110, 110, 101, 101, 110, 110, 116, 116, 105, 105, 97, 97, 108, 108, 69, 69, 59, 59, 99, 115, 121, 121, 59, 59, 114, 114, 59, 59, 108, 108, 108, 108, 101, 101, 100, 100, 83, 86, 109, 109, 97, 97, 108, 108, 108, 108, 83, 83, 113, 113, 117, 117, 97, 97, 114, 114, 101, 101, 59, 59, 101, 101, 114, 114, 121, 121, 83, 83, 109, 109, 97, 97, 108, 108, 108, 108, 83, 83, 113, 113, 117, 117, 97, 97, 114, 114, 101, 101, 59, 59, 112, 117, 102, 102, 59, 59, 65, 65, 108, 108, 108, 108, 59, 59, 114, 114, 105, 105, 101, 101, 114, 114, 116, 116, 114, 114, 102, 102, 59, 59, 99, 99, 114, 114, 59, 59, 74, 116, 99, 99, 121, 121, 59, 59, 109, 109, 109, 109, 97, 97, 59, 100, 59, 59, 114, 114, 101, 101, 118, 118, 101, 101, 59, 59, 101, 121, 100, 100, 105, 105, 108, 108, 59, 59, 114, 114, 99, 99, 59, 59, 59, 59, 111, 111, 116, 116, 59, 59, 114, 114, 59, 59, 59, 59, 112, 112, 102, 102, 59, 59, 101, 101, 97, 97, 116, 116, 101, 101, 114, 114, 69, 84, 113, 113, 117, 117, 97, 97, 108, 108, 59, 76, 101, 101, 115, 115, 115, 115, 59, 59, 117, 117, 108, 108, 108, 108, 69, 69, 113, 113, 117, 117, 97, 97, 108, 108, 59, 59, 114, 114, 101, 101, 97, 97, 116, 116, 101, 101, 114, 114, 59, 59, 101, 101, 115, 115, 115, 115, 59, 59, 108, 108, 97, 97, 110, 110, 116, 116, 69, 69, 113, 113, 117, 117, 97, 97, 108, 108, 59, 59, 105, 105, 108, 108, 100, 100, 101, 101, 59, 59, 99, 99, 114, 114, 59, 59, 59, 59, 65, 117, 82, 82, 68, 68, 99, 99, 121, 121, 59, 59, 99, 116, 101, 101, 107, 107, 59, 59, 59, 59, 105, 105, 114, 114, 99, 99, 59, 59, 114, 114, 59, 59, 108, 108, 98, 98, 101, 101, 114, 114, 116, 116, 83, 83, 112, 112, 97, 97, 99, 99, 101, 101, 59, 59, 112, 114, 102, 102, 59, 59, 105, 105, 122, 122, 111, 111, 110, 110, 116, 116, 97, 97, 108, 108, 76, 76, 105, 105, 110, 110, 101, 101, 59, 59, 99, 116, 114, 114, 59, 59, 114, 114, 111, 111, 107, 107, 59, 59, 109, 109, 112, 112, 68, 69, 111, 111, 119, 119, 110, 110, 72, 72, 117, 117, 109, 109, 112, 112, 59, 59, 113, 113, 117, 117, 97, 97, 108, 108, 59, 59, 69, 117, 99, 99, 121, 121, 59, 59, 108, 108, 105, 105, 103, 103, 59, 59, 99, 99, 121, 121, 59, 59, 99, 99, 117, 117, 116, 116, 101, 101, 105, 121, 114, 114, 99, 99, 59, 59, 111, 111, 116, 116, 59, 59, 114, 114, 59, 59, 114, 114, 97, 97, 118, 118, 101, 101, 59, 112, 99, 103, 114, 114, 59, 59, 105, 105, 110, 110, 97, 97, 114, 114, 121, 121, 73, 73, 59, 59, 108, 108, 105, 105, 101, 101, 115, 115, 59, 59, 116, 118, 59, 101, 103, 114, 114, 114, 97, 97, 108, 108, 59, 59, 115, 115, 101, 101, 99, 99, 116, 116, 105, 105, 111, 111, 110, 110, 59, 59, 105, 105, 115, 115, 105, 105, 98, 98, 108, 108, 101, 101, 67, 84, 111, 111, 109, 109, 109, 109, 97, 97, 59, 59, 105, 105, 109, 109, 101, 101, 115, 115, 59, 59, 103, 116, 111, 111, 110, 110, 59, 59, 102, 102, 59, 59, 97, 97, 59, 59, 99, 99, 114, 114, 59, 59, 105, 105, 108, 108, 100, 100, 101, 101, 59, 59, 107, 109, 99, 99, 121, 121, 59, 59, 108, 108, 99, 117, 105, 121, 114, 114, 99, 99, 59, 59, 59, 59, 114, 114, 59, 59, 112, 112, 102, 102, 59, 59, 99, 101, 114, 114, 59, 59, 114, 114, 99, 99, 121, 121, 59, 59, 107, 107, 99, 99, 121, 121, 59, 59, 72, 115, 99, 99, 121, 121, 59, 59, 99, 99, 121, 121, 59, 59, 112, 112, 112, 112, 97, 97, 59, 59, 101, 121, 100, 100, 105, 105, 108, 108, 59, 59, 59, 59, 114, 114, 59, 59, 112, 112, 102, 102, 59, 59, 99, 99, 114, 114, 59, 59, 74, 116, 99, 99, 121, 121, 59, 59, 99, 114, 117, 117, 116, 116, 101, 101, 59, 59, 98, 98, 100, 100, 97, 97, 59, 59, 103, 103, 59, 59, 108, 108, 97, 97, 99, 99, 101, 101, 116, 116, 114, 114, 102, 102, 59, 59, 114, 114, 59, 59, 97, 121, 114, 114, 111, 111, 110, 110, 59, 59, 100, 100, 105, 105, 108, 108, 59, 59, 59, 59, 102, 115, 116, 116, 65, 114, 110, 114, 103, 103, 108, 108, 101, 101, 66, 66, 114, 114, 97, 97, 99, 99, 107, 107, 101, 101, 116, 116, 59, 59, 114, 114, 111, 111, 119, 119, 59, 82, 97, 97, 114, 114, 59, 59, 105, 105, 103, 103, 104, 104, 116, 116, 65, 65, 114, 114, 114, 114, 111, 111, 119, 119, 59, 59, 101, 101, 105, 105, 108, 108, 105, 105, 110, 110, 103, 103, 59, 59, 111, 111, 117, 119, 98, 98, 108, 108, 101, 101, 66, 66, 114, 114, 97, 97, 99, 99, 107, 107, 101, 101, 116, 116, 59, 59, 110, 110, 84, 86, 101, 101, 101, 101, 86, 86, 101, 101, 99, 99, 116, 116, 111, 111, 114, 114, 59, 59, 101, 101, 99, 99, 116, 116, 111, 111, 114, 114, 59, 66, 97, 97, 114, 114, 59, 59, 108, 108, 111, 111, 111, 111, 114, 114, 59, 59, 105, 105, 103, 103, 104, 104, 116, 116, 65, 86, 114, 114, 114, 114, 111, 111, 119, 119, 59, 59, 101, 101, 99, 99, 116, 116, 111, 111, 114, 114, 59, 59, 101, 114, 101, 101, 59, 86, 114, 114, 114, 114, 111, 111, 119, 119, 59, 59, 101, 101, 99, 99, 116, 116, 111, 111, 114, 114, 59, 59, 105, 105, 97, 97, 110, 110, 103, 103, 108, 108, 101, 101, 59, 69, 97, 97, 114, 114, 59, 59, 113, 113, 117, 117, 97, 97, 108, 108, 59, 59, 112, 112, 68, 86, 111, 111, 119, 119, 110, 110, 86, 86, 101, 101, 99, 99, 116, 116, 111, 111, 114, 114, 59, 59, 101, 101, 101, 101, 86, 86, 101, 101, 99, 99, 116, 116, 111, 111, 114, 114, 59, 59, 101, 101, 99, 99, 116, 116, 111, 111, 114, 114, 59, 66, 97, 97, 114, 114, 59, 59, 101, 101, 99, 99, 116, 116, 111, 111, 114, 114, 59, 66, 97, 97, 114, 114, 59, 59, 114, 114, 114, 114, 111, 111, 119, 119, 59, 59, 105, 105, 103, 103, 104, 104, 116, 116, 97, 97, 114, 114, 114, 114, 111, 111, 119, 119, 59, 59, 115, 115, 69, 84, 113, 113, 117, 117, 97, 97, 108, 108, 71, 71, 114, 114, 101, 101, 97, 97, 116, 116, 101, 101, 114, 114, 59, 59, 117, 117, 108, 108, 108, 108, 69, 69, 113, 113, 117, 117, 97, 97, 108, 108, 59, 59, 114, 114, 101, 101, 97, 97, 116, 116, 101, 101, 114, 114, 59, 59, 101, 101, 115, 115, 115, 115, 59, 59, 108, 108, 97, 97, 110, 110, 116, 116, 69, 69, 113, 113, 117, 117, 97, 97, 108, 108, 59, 59, 105, 105, 108, 108, 100, 100, 101, 101, 59, 59, 114, 114, 59, 59, 59, 101, 102, 102, 116, 116, 97, 97, 114, 114, 114, 114, 111, 111, 119, 119, 59, 59, 105, 105, 100, 100, 111, 111, 116, 116, 59, 59, 110, 119, 103, 103, 76, 114, 101, 101, 102, 102, 116, 116, 65, 82, 114, 114, 114, 114, 111, 111, 119, 119, 59, 59, 105, 105, 103, 103, 104, 104, 116, 116, 65, 65, 114, 114, 114, 114, 111, 111, 119, 119, 59, 59, 105, 105, 103, 103, 104, 104, 116, 116, 65, 65, 114, 114, 114, 114, 111, 111, 119, 119, 59, 59, 101, 101, 102, 102, 116, 116, 97, 114, 114, 114, 114, 114, 111, 111, 119, 119, 59, 59, 105, 105, 103, 103, 104, 104, 116, 116, 97, 97, 114, 114, 114, 114, 111, 111, 119, 119, 59, 59, 105, 105, 103, 103, 104, 104, 116, 116, 97, 97, 114, 114, 114, 114, 111, 111, 119, 119, 59, 59, 102, 102, 59, 59, 101, 101, 114, 114, 76, 82, 101, 101, 102, 102, 116, 116, 65, 65, 114, 114, 114, 114, 111, 111, 119, 119, 59, 59, 105, 105, 103, 103, 104, 104, 116, 116, 65, 65, 114, 114, 114, 114, 111, 111, 119, 119, 59, 59, 99, 116, 114, 114, 59, 59, 59, 59, 114, 114, 111, 111, 107, 107, 59, 59, 59, 59, 97, 117, 112, 112, 59, 59, 121, 121, 59, 59, 100, 108, 105, 105, 117, 117, 109, 109, 83, 83, 112, 112, 97, 97, 99, 99, 101, 101, 59, 59, 108, 108, 105, 105, 110, 110, 116, 116, 114, 114, 102, 102, 59, 59, 114, 114, 59, 59, 110, 110, 117, 117, 115, 115, 80, 80, 108, 108, 117, 117, 115, 115, 59, 59, 112, 112, 102, 102, 59, 59, 99, 99, 114, 114, 59, 59, 59, 59, 74, 117, 99, 99, 121, 121, 59, 59, 99, 99, 117, 117, 116, 116, 101, 101, 59, 59, 97, 121, 114, 114, 111, 111, 110, 110, 59, 59, 100, 100, 105, 105, 108, 108, 59, 59, 59, 59, 103, 119, 97, 97, 116, 116, 105, 105, 118, 118, 101, 101, 77, 86, 101, 101, 100, 100, 105, 105, 117, 117, 109, 109, 83, 83, 112, 112, 97, 97, 99, 99, 101, 101, 59, 59, 104, 104, 105, 105, 99, 110, 107, 107, 83, 83, 112, 112, 97, 97, 99, 99, 101, 101, 59, 59, 83, 83, 112, 112, 97, 97, 99, 99, 101, 101, 59, 59, 101, 101, 114, 114, 121, 121, 84, 84, 104, 104, 105, 105, 110, 110, 83, 83, 112, 112, 97, 97, 99, 99, 101, 101, 59, 59, 116, 116, 101, 101, 100, 100, 71, 76, 114, 114, 101, 101, 97, 97, 116, 116, 101, 101, 114, 114, 71, 71, 114, 114, 101, 101, 97, 97, 116, 116, 101, 101, 114, 114, 59, 59, 101, 101, 115, 115, 115, 115, 76, 76, 101, 101, 115, 115, 115, 115, 59, 59, 76, 76, 105, 105, 110, 110, 101, 101, 59, 59, 114, 114, 59, 59, 66, 116, 114, 114, 101, 101, 97, 97, 107, 107, 59, 59, 66, 66, 114, 114, 101, 101, 97, 97, 107, 107, 105, 105, 110, 110, 103, 103, 83, 83, 112, 112, 97, 97, 99, 99, 101, 101, 59, 59, 102, 102, 59, 59, 59, 86, 111, 117, 110, 110, 103, 103, 114, 114, 117, 117, 101, 101, 110, 110, 116, 116, 59, 59, 112, 112, 67, 67, 97, 97, 112, 112, 59, 59, 111, 111, 117, 117, 98, 98, 108, 108, 101, 101, 86, 86, 101, 101, 114, 114, 116, 116, 105, 105, 99, 99, 97, 97, 108, 108, 66, 66, 97, 97, 114, 114, 59, 59, 108, 120, 101, 101, 109, 109, 101, 101, 110, 110, 116, 116, 59, 59, 117, 117, 97, 97, 108, 108, 59, 84, 105, 105, 108, 108, 100, 100, 101, 101, 59, 59, 105, 105, 115, 115, 116, 116, 115, 115, 59, 59, 114, 114, 101, 101, 97, 97, 116, 116, 101, 101, 114, 114, 59, 84, 113, 113, 117, 117, 97, 97, 108, 108, 59, 59, 117, 117, 108, 108, 108, 108, 69, 69, 113, 113, 117, 117, 97, 97, 108, 108, 59, 59, 114, 114, 101, 101, 97, 97, 116, 116, 101, 101, 114, 114, 59, 59, 101, 101, 115, 115, 115, 115, 59, 59, 108, 108, 97, 97, 110, 110, 116, 116, 69, 69, 113, 113, 117, 117, 97, 97, 108, 108, 59, 59, 105, 105, 108, 108, 100, 100, 101, 101, 59, 59, 117, 117, 109, 109, 112, 112, 68, 69, 111, 111, 119, 119, 110, 110, 72, 72, 117, 117, 109, 109, 112, 112, 59, 59, 113, 113, 117, 117, 97, 97, 108, 108, 59, 59, 101, 101, 102, 115, 116, 116, 84, 84, 114, 114, 105, 105, 97, 97, 110, 110, 103, 103, 108, 108, 101, 101, 59, 69, 97, 97, 114, 114, 59, 59, 113, 113, 117, 117, 97, 97, 108, 108, 59, 59, 115, 115, 59, 84, 113, 113, 117, 117, 97, 97, 108, 108, 59, 59, 114, 114, 101, 101, 97, 97, 116, 116, 101, 101, 114, 114, 59, 59, 101, 101, 115, 115, 115, 115, 59, 59, 108, 108, 97, 97, 110, 110, 116, 116, 69, 69, 113, 113, 117, 117, 97, 97, 108, 108, 59, 59, 105, 105, 108, 108, 100, 100, 101, 101, 59, 59, 101, 101, 115, 115, 116, 116, 101, 101, 100, 100, 71, 76, 114, 114, 101, 101, 97, 97, 116, 116, 101, 101, 114, 114, 71, 71, 114, 114, 101, 101, 97, 97, 116, 116, 101, 101, 114, 114, 59, 59, 101, 101, 115, 115, 115, 115, 76, 76, 101, 101, 115, 115, 115, 115, 59, 59, 114, 114, 101, 101, 99, 99, 101, 101, 100, 100, 101, 101, 115, 115, 59, 83, 113, 113, 117, 117, 97, 97, 108, 108, 59, 59, 108, 108, 97, 97, 110, 110, 116, 116, 69, 69, 113, 113, 117, 117, 97, 97, 108, 108, 59, 59, 101, 105, 118, 118, 101, 101, 114, 114, 115, 115, 101, 101, 69, 69, 108, 108, 101, 101, 109, 109, 101, 101, 110, 110, 116, 116, 59, 59, 103, 103, 104, 104, 116, 116, 84, 84, 114, 114, 105, 105, 97, 97, 110, 110, 103, 103, 108, 108, 101, 101, 59, 69, 97, 97, 114, 114, 59, 59, 113, 113, 117, 117, 97, 97, 108, 108, 59, 59, 113, 117, 117, 117, 97, 97, 114, 114, 101, 101, 83, 83, 117, 117, 98, 112, 115, 115, 101, 101, 116, 116, 59, 69, 113, 113, 117, 117, 97, 97, 108, 108, 59, 59, 101, 101, 114, 114, 115, 115, 101, 101, 116, 116, 59, 69, 113, 113, 117, 117, 97, 97, 108, 108, 59, 59, 98, 112, 115, 115, 101, 101, 116, 116, 59, 69, 113, 113, 117, 117, 97, 97, 108, 108, 59, 59, 99, 99, 101, 101, 101, 101, 100, 100, 115, 115, 59, 84, 113, 113, 117, 117, 97, 97, 108, 108, 59, 59, 108, 108, 97, 97, 110, 110, 116, 116, 69, 69, 113, 113, 117, 117, 97, 97, 108, 108, 59, 59, 105, 105, 108, 108, 100, 100, 101, 101, 59, 59, 101, 101, 114, 114, 115, 115, 101, 101, 116, 116, 59, 69, 113, 113, 117, 117, 97, 97, 108, 108, 59, 59, 105, 105, 108, 108, 100, 100, 101, 101, 59, 84, 113, 113, 117, 117, 97, 97, 108, 108, 59, 59, 117, 117, 108, 108, 108, 108, 69, 69, 113, 113, 117, 117, 97, 97, 108, 108, 59, 59, 105, 105, 108, 108, 100, 100, 101, 101, 59, 59, 101, 101, 114, 114, 116, 116, 105, 105, 99, 99, 97, 97, 108, 108, 66, 66, 97, 97, 114, 114, 59, 59, 99, 99, 114, 114, 59, 59, 105, 105, 108, 108, 100, 100, 101, 101, 59, 59, 69, 118, 108, 108, 105, 105, 103, 103, 59, 59, 99, 99, 117, 117, 116, 116, 101, 101, 105, 121, 114, 114, 99, 99, 59, 59, 98, 98, 108, 108, 97, 97, 99, 99, 59, 59, 114, 114, 59, 59, 114, 114, 97, 97, 118, 118, 101, 101, 97, 105, 99, 99, 114, 114, 59, 59, 103, 103, 97, 97, 59, 59, 99, 99, 114, 114, 111, 111, 110, 110, 59, 59, 112, 112, 102, 102, 59, 59, 101, 101, 110, 110, 67, 67, 117, 117, 114, 114, 108, 108, 121, 121, 68, 81, 111, 111, 117, 117, 98, 98, 108, 108, 101, 101, 81, 81, 117, 117, 111, 111, 116, 116, 101, 101, 59, 59, 117, 117, 111, 111, 116, 116, 101, 101, 59, 59, 59, 59, 99, 108, 114, 114, 59, 59, 97, 97, 115, 115, 104, 104, 105, 105, 108, 109, 100, 100, 101, 101, 101, 101, 115, 115, 59, 59, 109, 109, 108, 108, 101, 101, 114, 114, 66, 80, 97, 114, 114, 114, 59, 59, 97, 97, 99, 99, 101, 107, 59, 59, 101, 101, 116, 116, 59, 59, 97, 97, 114, 114, 101, 101, 110, 110, 116, 116, 104, 104, 101, 101, 115, 115, 105, 105, 115, 115, 59, 59, 97, 115, 114, 114, 116, 116, 105, 105, 97, 97, 108, 108, 68, 68, 59, 59, 121, 121, 59, 59, 114, 114, 59, 59, 105, 105, 59, 59, 59, 59, 117, 117, 115, 115, 77, 77, 105, 105, 110, 110, 117, 117, 115, 115, 59, 59, 105, 112, 110, 110, 99, 99, 97, 97, 114, 114, 101, 101, 112, 112, 108, 108, 97, 97, 110, 110, 101, 101, 59, 59, 102, 102, 59, 59, 59, 111, 99, 99, 101, 101, 100, 100, 101, 101, 115, 115, 59, 84, 113, 113, 117, 117, 97, 97, 108, 108, 59, 59, 108, 108, 97, 97, 110, 110, 116, 116, 69, 69, 113, 113, 117, 117, 97, 97, 108, 108, 59, 59, 105, 105, 108, 108, 100, 100, 101, 101, 59, 59, 109, 109, 101, 101, 59, 59, 100, 112, 117, 117, 99, 99, 116, 116, 59, 59, 111, 111, 114, 114, 116, 116, 105, 105, 111, 111, 110, 110, 59, 97, 108, 108, 59, 59, 99, 105, 114, 114, 59, 59, 59, 59, 85, 115, 79, 79, 84, 84, 114, 114, 59, 59, 112, 112, 102, 102, 59, 59, 99, 99, 114, 114, 59, 59, 66, 117, 97, 97, 114, 114, 114, 114, 59, 59, 71, 71, 99, 114, 117, 117, 116, 116, 101, 101, 59, 59, 103, 103, 59, 59, 114, 114, 59, 116, 108, 108, 59, 59, 97, 121, 114, 114, 111, 111, 110, 110, 59, 59, 100, 100, 105, 105, 108, 108, 59, 59, 59, 59, 59, 118, 101, 101, 114, 114, 115, 115, 101, 101, 69, 85, 108, 113, 101, 101, 109, 109, 101, 101, 110, 110, 116, 116, 59, 59, 117, 117, 105, 105, 108, 108, 105, 105, 98, 98, 114, 114, 105, 105, 117, 117, 109, 109, 59, 59, 112, 112, 69, 69, 113, 113, 117, 117, 105, 105, 108, 108, 105, 105, 98, 98, 114, 114, 105, 105, 117, 117, 109, 109, 59, 59, 114, 114, 59, 59, 111, 111, 59, 59, 103, 103, 104, 104, 116, 116, 65, 97, 110, 114, 103, 103, 108, 108, 101, 101, 66, 66, 114, 114, 97, 97, 99, 99, 107, 107, 101, 101, 116, 116, 59, 59, 114, 114, 111, 111, 119, 119, 59, 76, 97, 97, 114, 114, 59, 59, 101, 101, 102, 102, 116, 116, 65, 65, 114, 114, 114, 114, 111, 111, 119, 119, 59, 59, 101, 101, 105, 105, 108, 108, 105, 105, 110, 110, 103, 103, 59, 59, 111, 111, 117, 119, 98, 98, 108, 108, 101, 101, 66, 66, 114, 114, 97, 97, 99, 99, 107, 107, 101, 101, 116, 116, 59, 59, 110, 110, 84, 86, 101, 101, 101, 101, 86, 86, 101, 101, 99, 99, 116, 116, 111, 111, 114, 114, 59, 59, 101, 101, 99, 99, 116, 116, 111, 111, 114, 114, 59, 66, 97, 97, 114, 114, 59, 59, 108, 108, 111, 111, 111, 111, 114, 114, 59, 59, 101, 114, 101, 101, 59, 86, 114, 114, 114, 114, 111, 111, 119, 119, 59, 59, 101, 101, 99, 99, 116, 116, 111, 111, 114, 114, 59, 59, 105, 105, 97, 97, 110, 110, 103, 103, 108, 108, 101, 101, 59, 69, 97, 97, 114, 114, 59, 59, 113, 113, 117, 117, 97, 97, 108, 108, 59, 59, 112, 112, 68, 86, 111, 111, 119, 119, 110, 110, 86, 86, 101, 101, 99, 99, 116, 116, 111, 111, 114, 114, 59, 59, 101, 101, 101, 101, 86, 86, 101, 101, 99, 99, 116, 116, 111, 111, 114, 114, 59, 59, 101, 101, 99, 99, 116, 116, 111, 111, 114, 114, 59, 66, 97, 97, 114, 114, 59, 59, 101, 101, 99, 99, 116, 116, 111, 111, 114, 114, 59, 66, 97, 97, 114, 114, 59, 59, 114, 114, 114, 114, 111, 111, 119, 119, 59, 59, 112, 117, 102, 102, 59, 59, 110, 110, 100, 100, 73, 73, 109, 109, 112, 112, 108, 108, 105, 105, 101, 101, 115, 115, 59, 59, 105, 105, 103, 103, 104, 104, 116, 116, 97, 97, 114, 114, 114, 114, 111, 111, 119, 119, 59, 59, 99, 104, 114, 114, 59, 59, 59, 59, 108, 108, 101, 101, 68, 68, 101, 101, 108, 108, 97, 97, 121, 121, 101, 101, 100, 100, 59, 59, 72, 117, 67, 99, 72, 72, 99, 99, 121, 121, 59, 59, 121, 121, 59, 59, 70, 70, 84, 84, 99, 99, 121, 121, 59, 59, 99, 99, 117, 117, 116, 116, 101, 101, 59, 59, 59, 121, 114, 114, 111, 111, 110, 110, 59, 59, 100, 100, 105, 105, 108, 108, 59, 59, 114, 114, 99, 99, 59, 59, 59, 59, 114, 114, 59, 59, 111, 111, 114, 114, 116, 116, 68, 85, 111, 111, 119, 119, 110, 110, 65, 65, 114, 114, 114, 114, 111, 111, 119, 119, 59, 59, 101, 101, 102, 102, 116, 116, 65, 65, 114, 114, 114, 114, 111, 111, 119, 119, 59, 59, 105, 105, 103, 103, 104, 104, 116, 116, 65, 65, 114, 114, 114, 114, 111, 111, 119, 119, 59, 59, 112, 112, 65, 65, 114, 114, 114, 114, 111, 111, 119, 119, 59, 59, 103, 103, 109, 109, 97, 97, 59, 59, 97, 97, 108, 108, 108, 108, 67, 67, 105, 105, 114, 114, 99, 99, 108, 108, 101, 101, 59, 59, 112, 112, 102, 102, 59, 59, 114, 117, 116, 116, 59, 59, 97, 97, 114, 114, 101, 101, 59, 85, 110, 110, 116, 116, 101, 101, 114, 114, 115, 115, 101, 101, 99, 99, 116, 116, 105, 105, 111, 111, 110, 110, 59, 59, 117, 117, 98, 112, 115, 115, 101, 101, 116, 116, 59, 69, 113, 113, 117, 117, 97, 97, 108, 108, 59, 59, 101, 101, 114, 114, 115, 115, 101, 101, 116, 116, 59, 69, 113, 113, 117, 117, 97, 97, 108, 108, 59, 59, 110, 110, 105, 105, 111, 111, 110, 110, 59, 59, 99, 99, 114, 114, 59, 59, 97, 97, 114, 114, 59, 59, 98, 112, 59, 115, 101, 101, 116, 116, 59, 69, 113, 113, 117, 117, 97, 97, 108, 108, 59, 59, 99, 104, 101, 101, 101, 101, 100, 100, 115, 115, 59, 84, 113, 113, 117, 117, 97, 97, 108, 108, 59, 59, 108, 108, 97, 97, 110, 110, 116, 116, 69, 69, 113, 113, 117, 117, 97, 97, 108, 108, 59, 59, 105, 105, 108, 108, 100, 100, 101, 101, 59, 59, 84, 84, 104, 104, 97, 97, 116, 116, 59, 59, 59, 59, 59, 115, 114, 114, 115, 115, 101, 101, 116, 116, 59, 69, 113, 113, 117, 117, 97, 97, 108, 108, 59, 59, 101, 101, 116, 116, 59, 59, 72, 115, 79, 79, 82, 82, 78, 78, 65, 65, 68, 68, 69, 69, 59, 59, 72, 99, 99, 99, 121, 121, 59, 59, 121, 121, 59, 59, 98, 117, 59, 59, 59, 59, 97, 121, 114, 114, 111, 111, 110, 110, 59, 59, 100, 100, 105, 105, 108, 108, 59, 59, 59, 59, 114, 114, 59, 59, 101, 105, 114, 116, 101, 101, 102, 102, 111, 111, 114, 114, 101, 101, 59, 59, 97, 97, 59, 59, 99, 110, 107, 107, 83, 83, 112, 112, 97, 97, 99, 99, 101, 101, 59, 59, 83, 83, 112, 112, 97, 97, 99, 99, 101, 101, 59, 59, 108, 108, 100, 100, 101, 101, 59, 84, 113, 113, 117, 117, 97, 97, 108, 108, 59, 59, 117, 117, 108, 108, 108, 108, 69, 69, 113, 113, 117, 117, 97, 97, 108, 108, 59, 59, 105, 105, 108, 108, 100, 100, 101, 101, 59, 59, 112, 112, 102, 102, 59, 59, 105, 105, 112, 112, 108, 108, 101, 101, 68, 68, 111, 111, 116, 116, 59, 59, 99, 116, 114, 114, 59, 59, 114, 114, 111, 111, 107, 107, 59, 59, 97, 117, 99, 114, 117, 117, 116, 116, 101, 101, 114, 114, 59, 111, 99, 99, 105, 105, 114, 114, 59, 59, 114, 114, 99, 101, 121, 121, 59, 59, 118, 118, 101, 101, 59, 59, 105, 121, 114, 114, 99, 99, 59, 59, 98, 98, 108, 108, 97, 97, 99, 99, 59, 59, 114, 114, 59, 59, 114, 114, 97, 97, 118, 118, 101, 101, 97, 97, 99, 99, 114, 114, 59, 59, 100, 105, 101, 101, 114, 114, 66, 80, 97, 114, 114, 114, 59, 59, 97, 97, 99, 99, 101, 107, 59, 59, 101, 101, 116, 116, 59, 59, 97, 97, 114, 114, 101, 101, 110, 110, 116, 116, 104, 104, 101, 101, 115, 115, 105, 105, 115, 115, 59, 59, 111, 111, 110, 110, 59, 80, 108, 108, 117, 117, 115, 115, 59, 59, 103, 112, 111, 111, 110, 110, 59, 59, 102, 102, 59, 59, 65, 115, 114, 114, 114, 114, 111, 111, 119, 119, 59, 68, 97, 97, 114, 114, 59, 59, 111, 111, 119, 119, 110, 110, 65, 65, 114, 114, 114, 114, 111, 111, 119, 119, 59, 59, 111, 111, 119, 119, 110, 110, 65, 65, 114, 114, 114, 114, 111, 111, 119, 119, 59, 59, 113, 113, 117, 117, 105, 105, 108, 108, 105, 105, 98, 98, 114, 114, 105, 105, 117, 117, 109, 109, 59, 59, 101, 101, 101, 101, 59, 65, 114, 114, 114, 114, 111, 111, 119, 119, 59, 59, 114, 114, 114, 114, 111, 111, 119, 119, 59, 59, 111, 111, 119, 119, 110, 110, 97, 97, 114, 114, 114, 114, 111, 111, 119, 119, 59, 59, 101, 101, 114, 114, 76, 82, 101, 101, 102, 102, 116, 116, 65, 65, 114, 114, 114, 114, 111, 111, 119, 119, 59, 59, 105, 105, 103, 103, 104, 104, 116, 116, 65, 65, 114, 114, 114, 114, 111, 111, 119, 119, 59, 59, 105, 105, 59, 108, 111, 111, 110, 110, 59, 59, 105, 105, 110, 110, 103, 103, 59, 59, 99, 99, 114, 114, 59, 59, 105, 105, 108, 108, 100, 100, 101, 101, 59, 59, 109, 109, 108, 108, 68, 118, 97, 97, 115, 115, 104, 104, 59, 59, 97, 97, 114, 114, 59, 59, 121, 121, 59, 59, 97, 97, 115, 115, 104, 104, 59, 108, 59, 59, 101, 114, 59, 59, 98, 121, 97, 97, 114, 114, 59, 59, 59, 105, 99, 99, 97, 97, 108, 108, 66, 84, 97, 97, 114, 114, 59, 59, 105, 105, 110, 110, 101, 101, 59, 59, 101, 101, 112, 112, 97, 97, 114, 114, 97, 97, 116, 116, 111, 111, 114, 114, 59, 59, 105, 105, 108, 108, 100, 100, 101, 101, 59, 59, 84, 84, 104, 104, 105, 105, 110, 110, 83, 83, 112, 112, 97, 97, 99, 99, 101, 101, 59, 59, 114, 114, 59, 59, 112, 112, 102, 102, 59, 59, 99, 99, 114, 114, 59, 59, 100, 100, 97, 97, 115, 115, 104, 104, 59, 59, 99, 115, 105, 105, 114, 114, 99, 99, 59, 59, 100, 100, 103, 103, 101, 101, 59, 59, 114, 114, 59, 59, 112, 112, 102, 102, 59, 59, 99, 99, 114, 114, 59, 59, 102, 115, 114, 114, 59, 59, 59, 59, 112, 112, 102, 102, 59, 59, 99, 99, 114, 114, 59, 59, 65, 117, 99, 99, 121, 121, 59, 59, 99, 99, 121, 121, 59, 59, 99, 99, 121, 121, 59, 59, 99, 99, 117, 117, 116, 116, 101, 101, 105, 121, 114, 114, 99, 99, 59, 59, 59, 59, 114, 114, 59, 59, 112, 112, 102, 102, 59, 59, 99, 99, 114, 114, 59, 59, 109, 109, 108, 108, 59, 59, 72, 115, 99, 99, 121, 121, 59, 59, 99, 99, 117, 117, 116, 116, 101, 101, 59, 59, 97, 121, 114, 114, 111, 111, 110, 110, 59, 59, 59, 59, 111, 111, 116, 116, 59, 59, 114, 116, 111, 111, 87, 87, 105, 105, 100, 100, 116, 116, 104, 104, 83, 83, 112, 112, 97, 97, 99, 99, 101, 101, 59, 59, 97, 97, 59, 59, 114, 114, 59, 59, 112, 112, 102, 102, 59, 59, 99, 99, 114, 114, 59, 59, 97, 119, 99, 99, 117, 117, 116, 116, 101, 101, 114, 114, 101, 101, 118, 118, 101, 101, 59, 59, 59, 121, 59, 59, 59, 59, 114, 114, 99, 99, 116, 116, 101, 101, 59, 59, 108, 108, 105, 105, 103, 103, 59, 114, 59, 59, 114, 114, 97, 97, 118, 118, 101, 101, 101, 112, 102, 112, 115, 115, 121, 121, 109, 109, 59, 59, 104, 104, 59, 59, 104, 104, 97, 97, 59, 59, 97, 112, 99, 108, 114, 114, 59, 59, 103, 103, 59, 59, 100, 103, 59, 118, 110, 110, 100, 100, 59, 59, 59, 59, 108, 108, 111, 111, 112, 112, 101, 101, 59, 59, 59, 59, 59, 122, 59, 59, 101, 101, 59, 59, 115, 115, 100, 100, 59, 97, 97, 104, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 116, 116, 59, 118, 98, 98, 59, 100, 59, 59, 112, 116, 104, 104, 59, 59, 59, 59, 97, 97, 114, 114, 114, 114, 59, 59, 103, 112, 111, 111, 110, 110, 59, 59, 102, 102, 59, 59, 59, 112, 59, 59, 99, 99, 105, 105, 114, 114, 59, 59, 59, 59, 100, 100, 59, 59, 115, 115, 59, 59, 114, 114, 111, 111, 120, 120, 59, 101, 113, 113, 59, 59, 105, 105, 110, 110, 103, 103, 99, 121, 114, 114, 59, 59, 59, 59, 109, 109, 112, 112, 59, 101, 113, 113, 59, 59, 105, 105, 108, 108, 100, 100, 101, 101, 109, 109, 108, 108, 99, 105, 111, 111, 110, 110, 105, 105, 110, 110, 116, 116, 59, 59, 110, 110, 116, 116, 59, 59, 78, 117, 111, 111, 116, 116, 59, 59, 99, 114, 107, 107, 99, 115, 111, 111, 110, 110, 103, 103, 59, 59, 112, 112, 115, 115, 105, 105, 108, 108, 111, 111, 110, 110, 59, 59, 114, 114, 105, 105, 109, 109, 101, 101, 59, 59, 105, 105, 109, 109, 59, 101, 113, 113, 59, 59, 118, 119, 101, 101, 101, 101, 59, 59, 101, 101, 100, 100, 59, 103, 101, 101, 59, 59, 114, 114, 107, 107, 59, 116, 98, 98, 114, 114, 107, 107, 59, 59, 111, 121, 110, 110, 103, 103, 59, 59, 59, 59, 113, 113, 117, 117, 111, 111, 59, 59, 99, 116, 97, 97, 117, 117, 115, 115, 59, 101, 59, 59, 112, 112, 116, 116, 121, 121, 118, 118, 59, 59, 115, 115, 105, 105, 59, 59, 110, 110, 111, 111, 117, 117, 59, 59, 97, 119, 59, 59, 59, 59, 101, 101, 101, 101, 110, 110, 59, 59, 114, 114, 59, 59, 103, 103, 99, 119, 97, 117, 112, 112, 59, 59, 114, 114, 99, 99, 59, 59, 112, 112, 59, 59, 100, 116, 111, 111, 116, 116, 59, 59, 108, 108, 117, 117, 115, 115, 59, 59, 105, 105, 109, 109, 101, 101, 115, 115, 59, 59, 113, 116, 99, 99, 117, 117, 112, 112, 59, 59, 97, 97, 114, 114, 59, 59, 114, 114, 105, 105, 97, 97, 110, 110, 103, 103, 108, 108, 101, 101, 100, 117, 111, 111, 119, 119, 110, 110, 59, 59, 112, 112, 59, 59, 112, 112, 108, 108, 117, 117, 115, 115, 59, 59, 101, 101, 101, 101, 59, 59, 101, 101, 100, 100, 103, 103, 101, 101, 59, 59, 97, 97, 114, 114, 111, 111, 119, 119, 59, 59, 97, 111, 99, 110, 107, 107, 108, 116, 111, 111, 122, 122, 101, 101, 110, 110, 103, 103, 101, 101, 59, 59, 113, 113, 117, 117, 97, 97, 114, 114, 101, 101, 59, 59, 114, 114, 105, 105, 97, 97, 110, 110, 103, 103, 108, 108, 101, 101, 59, 114, 111, 111, 119, 119, 110, 110, 59, 59, 101, 101, 102, 102, 116, 116, 59, 59, 105, 105, 103, 103, 104, 104, 116, 116, 59, 59, 107, 107, 59, 59, 49, 51, 50, 52, 59, 59, 59, 59, 52, 52, 59, 59, 99, 99, 107, 107, 59, 59, 101, 111, 59, 113, 117, 117, 105, 105, 118, 118, 59, 59, 116, 116, 59, 59, 112, 120, 102, 102, 59, 59, 59, 116, 111, 111, 109, 109, 59, 59, 116, 116, 105, 105, 101, 101, 59, 59, 68, 118, 76, 114, 59, 59, 59, 59, 59, 59, 59, 59, 59, 117, 59, 59, 59, 59, 59, 59, 59, 59, 76, 114, 59, 59, 59, 59, 59, 59, 59, 59, 59, 114, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 111, 111, 120, 120, 59, 59, 76, 114, 59, 59, 59, 59, 59, 59, 59, 59, 59, 117, 59, 59, 59, 59, 59, 59, 59, 59, 105, 105, 110, 110, 117, 117, 115, 115, 59, 59, 108, 108, 117, 117, 115, 115, 59, 59, 105, 105, 109, 109, 101, 101, 115, 115, 59, 59, 76, 114, 59, 59, 59, 59, 59, 59, 59, 59, 59, 114, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 114, 114, 105, 105, 109, 109, 101, 101, 59, 59, 101, 118, 118, 118, 101, 101, 59, 59, 98, 98, 97, 97, 114, 114, 99, 111, 114, 114, 59, 59, 109, 109, 105, 105, 59, 59, 109, 109, 59, 101, 59, 59, 108, 108, 59, 104, 59, 59, 115, 115, 117, 117, 98, 98, 59, 59, 108, 109, 108, 108, 59, 101, 116, 116, 59, 59, 112, 112, 59, 101, 59, 59, 59, 113, 59, 59, 97, 121, 99, 114, 117, 117, 116, 116, 101, 101, 59, 59, 59, 115, 110, 110, 100, 100, 59, 59, 114, 114, 99, 99, 117, 117, 112, 112, 59, 59, 97, 117, 112, 112, 59, 59, 112, 112, 59, 59, 111, 111, 116, 116, 59, 59, 59, 59, 101, 111, 116, 116, 59, 59, 110, 110, 59, 59, 97, 117, 112, 114, 115, 115, 59, 59, 111, 111, 110, 110, 59, 59, 100, 100, 105, 105, 108, 108, 114, 114, 99, 99, 59, 59, 112, 112, 115, 115, 59, 115, 109, 109, 59, 59, 111, 111, 116, 116, 59, 59, 100, 110, 105, 105, 108, 108, 112, 112, 116, 116, 121, 121, 118, 118, 59, 59, 116, 116, 114, 114, 100, 100, 111, 111, 116, 116, 59, 59, 114, 114, 59, 59, 99, 105, 121, 121, 59, 59, 99, 99, 107, 107, 59, 109, 97, 97, 114, 114, 107, 107, 59, 59, 59, 59, 114, 114, 59, 115, 59, 59, 59, 108, 113, 113, 59, 59, 101, 101, 97, 100, 114, 114, 114, 114, 111, 111, 119, 119, 108, 114, 101, 101, 102, 102, 116, 116, 59, 59, 105, 105, 103, 103, 104, 104, 116, 116, 59, 59, 82, 100, 59, 59, 59, 59, 115, 115, 116, 116, 59, 59, 105, 105, 114, 114, 99, 99, 59, 59, 97, 97, 115, 115, 104, 104, 59, 59, 59, 59, 110, 110, 105, 105, 110, 110, 116, 116, 59, 59, 105, 105, 100, 100, 59, 59, 99, 99, 105, 105, 114, 114, 59, 59, 117, 117, 98, 98, 115, 115, 59, 117, 105, 105, 116, 116, 59, 59, 108, 112, 111, 111, 110, 110, 59, 101, 59, 113, 59, 59, 109, 112, 97, 97, 59, 116, 59, 59, 59, 108, 110, 110, 59, 59, 101, 101, 109, 120, 101, 101, 110, 110, 116, 116, 59, 59, 101, 101, 115, 115, 59, 59, 103, 105, 59, 100, 111, 111, 116, 116, 59, 59, 110, 110, 116, 116, 59, 59, 102, 121, 59, 59, 111, 111, 100, 100, 59, 59, 114, 114, 59, 59, 97, 111, 114, 114, 114, 114, 59, 59, 115, 115, 115, 115, 59, 59, 99, 117, 114, 114, 59, 59, 98, 112, 59, 101, 59, 59, 59, 101, 59, 59, 100, 100, 111, 111, 116, 116, 59, 59, 100, 119, 97, 97, 114, 114, 114, 114, 108, 114, 59, 59, 59, 59, 112, 115, 114, 114, 59, 59, 99, 99, 59, 59, 97, 97, 114, 114, 114, 114, 59, 112, 59, 59, 59, 115, 114, 114, 99, 99, 97, 97, 112, 112, 59, 59, 97, 117, 112, 112, 59, 59, 112, 112, 59, 59, 111, 111, 116, 116, 59, 59, 114, 114, 59, 59, 59, 59, 97, 118, 114, 114, 114, 114, 59, 109, 59, 59, 121, 121, 101, 119, 113, 113, 112, 115, 114, 114, 101, 101, 99, 99, 59, 59, 117, 117, 99, 99, 99, 99, 59, 59, 101, 101, 101, 101, 59, 59, 101, 101, 100, 100, 103, 103, 101, 101, 59, 59, 101, 101, 110, 110, 101, 101, 97, 97, 114, 114, 114, 114, 111, 111, 119, 119, 108, 114, 101, 101, 102, 102, 116, 116, 59, 59, 105, 105, 103, 103, 104, 104, 116, 116, 59, 59, 101, 101, 101, 101, 59, 59, 101, 101, 100, 100, 59, 59, 99, 105, 111, 111, 110, 110, 105, 105, 110, 110, 116, 116, 59, 59, 110, 110, 116, 116, 59, 59, 108, 108, 99, 99, 116, 116, 121, 121, 59, 59, 65, 122, 114, 114, 114, 114, 59, 59, 97, 97, 114, 114, 59, 59, 103, 115, 103, 103, 101, 101, 114, 114, 59, 59, 101, 101, 116, 116, 104, 104, 59, 59, 114, 114, 59, 59, 104, 104, 59, 118, 59, 59, 107, 108, 97, 97, 114, 114, 111, 111, 119, 119, 59, 59, 97, 97, 99, 99, 59, 59, 97, 121, 114, 114, 111, 111, 110, 110, 59, 59, 59, 59, 59, 111, 103, 114, 103, 103, 101, 101, 114, 114, 59, 59, 114, 114, 59, 59, 116, 116, 115, 115, 101, 101, 113, 113, 59, 59, 103, 109, 116, 116, 97, 97, 59, 59, 112, 112, 116, 116, 121, 121, 118, 118, 59, 59, 105, 114, 115, 115, 104, 104, 116, 116, 59, 59, 59, 59, 97, 97, 114, 114, 108, 114, 59, 59, 59, 59, 97, 118, 109, 109, 59, 115, 110, 110, 100, 100, 59, 115, 117, 117, 105, 105, 116, 116, 59, 59, 59, 59, 59, 59, 97, 97, 109, 109, 109, 109, 97, 97, 59, 59, 105, 105, 110, 110, 59, 59, 59, 111, 100, 100, 101, 101, 110, 110, 116, 116, 105, 105, 109, 109, 101, 101, 115, 115, 59, 59, 110, 110, 120, 120, 59, 59, 99, 99, 121, 121, 59, 59, 99, 99, 111, 114, 114, 114, 110, 110, 59, 59, 111, 111, 112, 112, 59, 59, 108, 119, 108, 108, 97, 97, 114, 114, 59, 59, 102, 102, 59, 59, 59, 115, 113, 113, 59, 100, 111, 111, 116, 116, 59, 59, 105, 105, 110, 110, 117, 117, 115, 115, 59, 59, 108, 108, 117, 117, 115, 115, 59, 59, 113, 113, 117, 117, 97, 97, 114, 114, 101, 101, 59, 59, 98, 98, 108, 108, 101, 101, 98, 98, 97, 97, 114, 114, 119, 119, 101, 101, 100, 100, 103, 103, 101, 101, 59, 59, 110, 110, 97, 104, 114, 114, 114, 114, 111, 111, 119, 119, 59, 59, 111, 111, 119, 119, 110, 110, 97, 97, 114, 114, 114, 114, 111, 111, 119, 119, 115, 115, 59, 59, 97, 97, 114, 114, 112, 112, 111, 111, 111, 111, 110, 110, 108, 114, 101, 101, 102, 102, 116, 116, 59, 59, 105, 105, 103, 103, 104, 104, 116, 116, 59, 59, 98, 99, 107, 107, 97, 97, 114, 114, 111, 111, 119, 119, 59, 59, 111, 114, 114, 114, 110, 110, 59, 59, 111, 111, 112, 112, 59, 59, 99, 116, 114, 121, 59, 59, 59, 59, 108, 108, 59, 59, 114, 114, 111, 111, 107, 107, 59, 59, 100, 114, 111, 111, 116, 116, 59, 59, 105, 105, 59, 102, 59, 59, 97, 104, 114, 114, 114, 114, 59, 59, 97, 97, 114, 114, 59, 59, 97, 97, 110, 110, 103, 103, 108, 108, 101, 101, 59, 59, 99, 105, 121, 121, 59, 59, 103, 103, 114, 114, 97, 97, 114, 114, 114, 114, 59, 59, 68, 120, 68, 111, 111, 111, 116, 116, 59, 59, 116, 116, 59, 59, 99, 115, 117, 117, 116, 116, 101, 101, 116, 116, 101, 101, 114, 114, 59, 59, 97, 121, 114, 114, 111, 111, 110, 110, 59, 59, 114, 114, 59, 99, 108, 108, 111, 111, 110, 110, 59, 59, 59, 59, 111, 111, 116, 116, 59, 59, 59, 59, 68, 114, 111, 111, 116, 116, 59, 59, 59, 59, 59, 115, 97, 97, 118, 118, 101, 101, 59, 100, 111, 111, 116, 116, 59, 59, 59, 115, 110, 110, 116, 116, 101, 101, 114, 114, 115, 115, 59, 59, 59, 59, 59, 100, 111, 111, 116, 116, 59, 59, 97, 115, 99, 99, 114, 114, 59, 59, 116, 116, 121, 121, 59, 118, 101, 101, 116, 116, 59, 59, 59, 59, 112, 112, 49, 59, 51, 52, 59, 59, 59, 59, 103, 115, 59, 59, 112, 112, 59, 59, 103, 112, 111, 111, 110, 110, 59, 59, 102, 102, 59, 59, 97, 115, 114, 114, 59, 115, 108, 108, 59, 59, 117, 117, 115, 115, 59, 59, 105, 105, 59, 118, 111, 111, 110, 110, 59, 59, 59, 59, 99, 118, 105, 111, 114, 114, 99, 99, 59, 59, 108, 108, 111, 111, 110, 110, 59, 59, 105, 108, 109, 109, 59, 59, 97, 97, 110, 110, 116, 116, 103, 108, 116, 116, 114, 114, 59, 59, 101, 101, 115, 115, 115, 115, 59, 59, 97, 105, 108, 108, 115, 115, 59, 59, 115, 115, 116, 116, 59, 59, 118, 118, 59, 68, 68, 68, 59, 59, 112, 112, 97, 97, 114, 114, 115, 115, 108, 108, 59, 59, 68, 97, 111, 111, 116, 116, 59, 59, 114, 114, 114, 114, 59, 59, 99, 105, 114, 114, 59, 59, 111, 111, 116, 116, 59, 59, 109, 109, 59, 59, 97, 104, 59, 59, 109, 114, 108, 108, 111, 111, 59, 59, 99, 112, 108, 108, 59, 59, 115, 115, 116, 116, 59, 59, 101, 111, 99, 99, 116, 116, 97, 97, 116, 116, 105, 105, 111, 111, 110, 110, 59, 59, 110, 110, 101, 101, 110, 110, 116, 116, 105, 105, 97, 97, 108, 108, 101, 101, 59, 59, 97, 115, 108, 108, 108, 108, 105, 105, 110, 110, 103, 103, 100, 100, 111, 111, 116, 116, 115, 115, 101, 101, 113, 113, 59, 59, 121, 121, 59, 59, 109, 109, 97, 97, 108, 108, 101, 101, 59, 59, 105, 114, 108, 108, 105, 105, 103, 103, 59, 59, 105, 108, 103, 103, 59, 59, 105, 105, 103, 103, 59, 59, 59, 59, 108, 108, 105, 105, 103, 103, 59, 59, 108, 108, 105, 105, 103, 103, 59, 59, 97, 116, 116, 116, 59, 59, 105, 105, 103, 103, 59, 59, 110, 110, 115, 115, 59, 59, 111, 111, 102, 102, 59, 59, 112, 114, 102, 102, 59, 59, 97, 107, 108, 108, 108, 108, 59, 59, 59, 118, 59, 59, 97, 97, 114, 114, 116, 116, 105, 105, 110, 110, 116, 116, 59, 59, 97, 111, 99, 115, 49, 55, 50, 56, 59, 59, 59, 59, 59, 59, 59, 59, 51, 53, 59, 59, 59, 59, 52, 56, 59, 59, 59, 59, 53, 53, 59, 59, 54, 56, 59, 59, 59, 59, 56, 56, 59, 59, 108, 108, 59, 59, 119, 119, 110, 110, 59, 59, 99, 99, 114, 114, 59, 59, 69, 118, 59, 108, 59, 59, 99, 112, 117, 117, 116, 116, 101, 101, 59, 59, 109, 109, 97, 97, 59, 100, 59, 59, 59, 59, 114, 114, 101, 101, 118, 118, 101, 101, 59, 59, 105, 121, 114, 114, 99, 99, 59, 59, 59, 59, 111, 111, 116, 116, 59, 59, 59, 115, 59, 59, 59, 115, 59, 59, 108, 108, 97, 97, 110, 110, 116, 116, 59, 59, 59, 108, 99, 99, 59, 59, 111, 111, 116, 116, 59, 111, 59, 108, 59, 59, 59, 101, 115, 115, 59, 59, 114, 114, 59, 59, 59, 103, 59, 59, 109, 109, 101, 101, 108, 108, 59, 59, 99, 99, 121, 121, 59, 59, 59, 106, 59, 59, 59, 59, 59, 59, 69, 115, 59, 59, 112, 112, 59, 112, 114, 114, 111, 111, 120, 120, 59, 59, 59, 113, 59, 113, 59, 59, 105, 105, 109, 109, 59, 59, 112, 112, 102, 102, 59, 59, 97, 97, 118, 118, 101, 101, 59, 59, 99, 105, 114, 114, 59, 59, 109, 109, 59, 108, 59, 59, 59, 59, 99, 105, 59, 59, 114, 114, 59, 59, 111, 111, 116, 116, 59, 59, 80, 80, 97, 97, 114, 114, 59, 59, 117, 117, 101, 101, 115, 115, 116, 116, 59, 59, 97, 115, 112, 114, 112, 112, 114, 114, 111, 111, 120, 120, 59, 59, 114, 114, 59, 59, 111, 111, 116, 116, 59, 59, 113, 113, 108, 113, 101, 101, 115, 115, 115, 115, 59, 59, 108, 108, 101, 101, 115, 115, 115, 115, 59, 59, 101, 101, 115, 115, 115, 115, 59, 59, 105, 105, 109, 109, 59, 59, 101, 110, 114, 114, 116, 116, 110, 110, 101, 101, 113, 113, 113, 113, 59, 59, 69, 69, 59, 59, 65, 121, 114, 114, 114, 114, 59, 59, 105, 114, 114, 114, 115, 115, 112, 112, 59, 59, 102, 102, 59, 59, 105, 105, 108, 108, 116, 116, 59, 59, 100, 114, 99, 99, 121, 121, 59, 59, 59, 119, 105, 105, 114, 114, 59, 59, 59, 59, 97, 97, 114, 114, 59, 59, 105, 105, 114, 114, 99, 99, 59, 59, 97, 114, 114, 114, 116, 116, 115, 115, 59, 117, 105, 105, 116, 116, 59, 59, 108, 108, 105, 105, 112, 112, 59, 59, 99, 99, 111, 111, 110, 110, 59, 59, 114, 114, 59, 59, 115, 115, 101, 119, 97, 97, 114, 114, 111, 111, 119, 119, 59, 59, 97, 97, 114, 114, 111, 111, 119, 119, 59, 59, 97, 114, 114, 114, 114, 114, 59, 59, 116, 116, 104, 104, 116, 116, 59, 59, 107, 107, 108, 114, 101, 101, 102, 102, 116, 116, 97, 97, 114, 114, 114, 114, 111, 111, 119, 119, 59, 59, 105, 105, 103, 103, 104, 104, 116, 116, 97, 97, 114, 114, 114, 114, 111, 111, 119, 119, 59, 59, 102, 102, 59, 59, 98, 98, 97, 97, 114, 114, 59, 59, 99, 116, 114, 114, 59, 59, 97, 97, 115, 115, 104, 104, 59, 59, 114, 114, 111, 111, 107, 107, 59, 59, 98, 112, 117, 117, 108, 108, 108, 108, 59, 59, 104, 104, 101, 101, 110, 110, 59, 59, 97, 117, 99, 99, 117, 117, 116, 116, 101, 101, 59, 121, 114, 114, 99, 99, 59, 59, 99, 120, 121, 121, 59, 59, 99, 99, 108, 108, 102, 114, 59, 59, 59, 59, 114, 114, 97, 97, 118, 118, 101, 101, 59, 111, 105, 110, 110, 110, 116, 116, 59, 59, 116, 116, 59, 59, 102, 102, 105, 105, 110, 110, 59, 59, 116, 116, 97, 97, 59, 59, 108, 108, 105, 105, 103, 103, 59, 59, 97, 112, 99, 116, 114, 114, 59, 59, 101, 112, 59, 59, 105, 105, 110, 110, 101, 101, 59, 59, 97, 97, 114, 114, 116, 116, 59, 59, 104, 104, 59, 59, 102, 102, 59, 59, 101, 101, 100, 100, 59, 59, 59, 116, 97, 97, 114, 114, 101, 101, 59, 59, 105, 105, 110, 110, 59, 116, 105, 105, 101, 101, 59, 59, 100, 100, 111, 111, 116, 116, 59, 59, 59, 112, 97, 97, 108, 108, 59, 59, 103, 114, 101, 101, 114, 114, 115, 115, 59, 59, 99, 99, 97, 97, 108, 108, 59, 59, 97, 97, 114, 114, 104, 104, 107, 107, 59, 59, 114, 114, 111, 111, 100, 100, 59, 59, 99, 116, 121, 121, 59, 59, 111, 111, 110, 110, 59, 59, 102, 102, 59, 59, 97, 97, 59, 59, 114, 114, 111, 111, 100, 100, 59, 59, 117, 117, 101, 101, 115, 115, 116, 116, 99, 105, 114, 114, 59, 59, 110, 110, 59, 118, 59, 59, 111, 111, 116, 116, 59, 59, 59, 118, 59, 59, 59, 59, 59, 105, 108, 108, 100, 100, 101, 101, 59, 59, 107, 109, 99, 99, 121, 121, 59, 59, 108, 108, 99, 117, 105, 121, 114, 114, 99, 99, 59, 59, 59, 59, 114, 114, 59, 59, 97, 97, 116, 116, 104, 104, 59, 59, 112, 112, 102, 102, 59, 59, 99, 101, 114, 114, 59, 59, 114, 114, 99, 99, 121, 121, 59, 59, 107, 107, 99, 99, 121, 121, 59, 59, 97, 115, 112, 112, 112, 112, 97, 97, 59, 118, 59, 59, 101, 121, 100, 100, 105, 105, 108, 108, 59, 59, 59, 59, 114, 114, 59, 59, 114, 114, 101, 101, 101, 101, 110, 110, 59, 59, 99, 99, 121, 121, 59, 59, 99, 99, 121, 121, 59, 59, 112, 112, 102, 102, 59, 59, 99, 99, 114, 114, 59, 59, 65, 118, 97, 116, 114, 114, 114, 114, 59, 59, 114, 114, 59, 59, 97, 97, 105, 105, 108, 108, 59, 59, 97, 97, 114, 114, 114, 114, 59, 59, 59, 103, 59, 59, 97, 97, 114, 114, 59, 59, 99, 116, 117, 117, 116, 116, 101, 101, 59, 59, 109, 109, 112, 112, 116, 116, 121, 121, 118, 118, 59, 59, 114, 114, 97, 97, 110, 110, 59, 59, 98, 98, 100, 100, 97, 97, 59, 59, 103, 103, 59, 108, 59, 59, 101, 101, 59, 59, 59, 59, 117, 117, 111, 111, 114, 114, 59, 116, 59, 102, 115, 115, 59, 59, 115, 115, 59, 59, 107, 107, 59, 59, 112, 112, 59, 59, 108, 108, 59, 59, 105, 105, 109, 109, 59, 59, 108, 108, 59, 59, 59, 101, 105, 105, 108, 108, 59, 59, 59, 115, 59, 59, 97, 114, 114, 114, 114, 114, 59, 59, 114, 114, 107, 107, 59, 59, 97, 107, 99, 99, 101, 107, 59, 59, 59, 59, 101, 115, 59, 59, 108, 108, 100, 117, 59, 59, 59, 59, 97, 121, 114, 114, 111, 111, 110, 110, 59, 59, 100, 105, 105, 105, 108, 108, 59, 59, 108, 108, 59, 59, 98, 98, 59, 59, 59, 59, 99, 115, 97, 97, 59, 59, 117, 117, 111, 111, 59, 114, 59, 59, 100, 117, 104, 104, 97, 97, 114, 114, 59, 59, 115, 115, 104, 104, 97, 97, 114, 114, 59, 59, 104, 104, 59, 59, 59, 115, 116, 116, 97, 116, 114, 114, 114, 114, 111, 111, 119, 119, 59, 116, 97, 97, 105, 105, 108, 108, 59, 59, 97, 97, 114, 114, 112, 112, 111, 111, 111, 111, 110, 110, 100, 117, 111, 111, 119, 119, 110, 110, 59, 59, 112, 112, 59, 59, 101, 101, 102, 102, 116, 116, 97, 97, 114, 114, 114, 114, 111, 111, 119, 119, 115, 115, 59, 59, 105, 105, 103, 103, 104, 104, 116, 116, 97, 115, 114, 114, 114, 114, 111, 111, 119, 119, 59, 115, 59, 59, 97, 97, 114, 114, 112, 112, 111, 111, 111, 111, 110, 110, 115, 115, 59, 59, 113, 113, 117, 117, 105, 105, 103, 103, 97, 97, 114, 114, 114, 114, 111, 111, 119, 119, 59, 59, 104, 104, 114, 114, 101, 101, 101, 101, 116, 116, 105, 105, 109, 109, 101, 101, 115, 115, 59, 59, 59, 59, 59, 115, 59, 59, 108, 108, 97, 97, 110, 110, 116, 116, 59, 59, 59, 115, 99, 99, 59, 59, 111, 111, 116, 116, 59, 111, 59, 114, 59, 59, 59, 101, 115, 115, 59, 59, 97, 115, 112, 112, 112, 112, 114, 114, 111, 111, 120, 120, 59, 59, 111, 111, 116, 116, 59, 59, 113, 113, 103, 113, 116, 116, 114, 114, 59, 59, 103, 103, 116, 116, 114, 114, 59, 59, 116, 116, 114, 114, 59, 59, 105, 105, 109, 109, 59, 59, 105, 114, 115, 115, 104, 104, 116, 116, 59, 59, 111, 111, 111, 111, 114, 114, 59, 59, 59, 59, 59, 69, 59, 59, 97, 98, 114, 114, 100, 117, 59, 59, 59, 108, 59, 59, 108, 108, 107, 107, 59, 59, 99, 99, 121, 121, 59, 59, 59, 116, 114, 114, 114, 114, 59, 59, 111, 111, 114, 114, 110, 110, 101, 101, 114, 114, 59, 59, 97, 97, 114, 114, 100, 100, 59, 59, 114, 114, 105, 105, 59, 59, 105, 111, 100, 100, 111, 111, 116, 116, 59, 59, 117, 117, 115, 115, 116, 116, 59, 97, 99, 99, 104, 104, 101, 101, 59, 59, 69, 115, 59, 59, 112, 112, 59, 112, 114, 114, 111, 111, 120, 120, 59, 59, 59, 113, 59, 113, 59, 59, 105, 105, 109, 109, 59, 59, 97, 122, 110, 114, 103, 103, 59, 59, 114, 114, 59, 59, 114, 114, 107, 107, 59, 59, 103, 103, 108, 114, 101, 101, 102, 102, 116, 116, 97, 114, 114, 114, 114, 114, 111, 111, 119, 119, 59, 59, 105, 105, 103, 103, 104, 104, 116, 116, 97, 97, 114, 114, 114, 114, 111, 111, 119, 119, 59, 59, 97, 97, 112, 112, 115, 115, 116, 116, 111, 111, 59, 59, 105, 105, 103, 103, 104, 104, 116, 116, 97, 97, 114, 114, 114, 114, 111, 111, 119, 119, 59, 59, 112, 112, 97, 97, 114, 114, 114, 114, 111, 111, 119, 119, 108, 114, 101, 101, 102, 102, 116, 116, 59, 59, 105, 105, 103, 103, 104, 104, 116, 116, 59, 59, 97, 108, 114, 114, 59, 59, 59, 59, 117, 117, 115, 115, 59, 59, 105, 105, 109, 109, 101, 101, 115, 115, 59, 59, 97, 98, 115, 115, 116, 116, 59, 59, 97, 97, 114, 114, 59, 59, 59, 102, 110, 110, 103, 103, 101, 101, 59, 59, 59, 59, 97, 97, 114, 114, 59, 108, 116, 116, 59, 59, 97, 116, 114, 114, 114, 114, 59, 59, 111, 111, 114, 114, 110, 110, 101, 101, 114, 114, 59, 59, 97, 97, 114, 114, 59, 100, 59, 59, 59, 59, 114, 114, 105, 105, 59, 59, 97, 116, 113, 113, 117, 117, 111, 111, 59, 59, 114, 114, 59, 59, 59, 59, 109, 109, 59, 103, 59, 59, 59, 59, 98, 117, 59, 59, 111, 111, 59, 114, 59, 59, 114, 114, 111, 111, 107, 107, 59, 59, 99, 105, 59, 59, 114, 114, 59, 59, 111, 111, 116, 116, 59, 59, 114, 114, 101, 101, 101, 101, 59, 59, 109, 109, 101, 101, 115, 115, 59, 59, 97, 97, 114, 114, 114, 114, 59, 59, 117, 117, 101, 101, 115, 115, 116, 116, 59, 59, 80, 105, 97, 97, 114, 114, 59, 59, 59, 102, 59, 59, 59, 59, 114, 114, 100, 117, 115, 115, 104, 104, 97, 97, 114, 114, 59, 59, 104, 104, 97, 97, 114, 114, 59, 59, 101, 110, 114, 114, 116, 116, 110, 110, 101, 101, 113, 113, 113, 113, 59, 59, 69, 69, 59, 59, 68, 117, 68, 68, 111, 111, 116, 116, 59, 59, 99, 114, 114, 114, 101, 116, 59, 59, 59, 101, 115, 115, 101, 101, 59, 59, 59, 115, 116, 116, 111, 111, 59, 117, 111, 111, 119, 119, 110, 110, 59, 59, 101, 101, 102, 102, 116, 116, 59, 59, 112, 112, 59, 59, 107, 107, 101, 101, 114, 114, 59, 59, 111, 121, 109, 109, 109, 109, 97, 97, 59, 59, 59, 59, 97, 97, 115, 115, 104, 104, 59, 59, 97, 97, 115, 115, 117, 117, 114, 114, 101, 101, 100, 100, 97, 97, 110, 110, 103, 103, 108, 108, 101, 101, 59, 59, 114, 114, 59, 59, 111, 111, 59, 59, 99, 110, 114, 114, 111, 111, 59, 100, 115, 115, 116, 116, 59, 59, 105, 105, 114, 114, 59, 59, 111, 111, 116, 116, 117, 117, 115, 115, 59, 100, 59, 59, 59, 117, 59, 59, 99, 100, 112, 112, 59, 59, 114, 114, 59, 59, 112, 112, 108, 108, 117, 117, 115, 115, 59, 59, 100, 112, 101, 101, 108, 108, 115, 115, 59, 59, 102, 102, 59, 59, 59, 59, 99, 116, 114, 114, 59, 59, 112, 112, 111, 111, 115, 115, 59, 59, 59, 109, 116, 116, 105, 105, 109, 109, 97, 97, 112, 112, 59, 59, 97, 97, 112, 112, 59, 59, 71, 119, 103, 116, 59, 59, 59, 118, 59, 59, 101, 116, 102, 102, 116, 116, 97, 114, 114, 114, 114, 114, 111, 111, 119, 119, 59, 59, 105, 105, 103, 103, 104, 104, 116, 116, 97, 97, 114, 114, 114, 114, 111, 111, 119, 119, 59, 59, 59, 59, 59, 118, 59, 59, 105, 105, 103, 103, 104, 104, 116, 116, 97, 97, 114, 114, 114, 114, 111, 111, 119, 119, 59, 59, 68, 100, 97, 97, 115, 115, 104, 104, 59, 59, 97, 97, 115, 115, 104, 104, 59, 59, 98, 116, 108, 108, 97, 97, 59, 59, 117, 117, 116, 116, 101, 101, 59, 59, 103, 103, 59, 59, 59, 112, 59, 59, 100, 100, 59, 59, 115, 115, 59, 59, 114, 114, 111, 111, 120, 120, 59, 59, 117, 117, 114, 114, 59, 97, 108, 108, 59, 115, 59, 59, 115, 117, 112, 112, 109, 109, 112, 112, 59, 101, 59, 59, 97, 121, 112, 114, 59, 59, 111, 111, 110, 110, 59, 59, 100, 100, 105, 105, 108, 108, 59, 59, 110, 110, 103, 103, 59, 100, 111, 111, 116, 116, 59, 59, 112, 112, 59, 59, 59, 59, 97, 97, 115, 115, 104, 104, 59, 59, 59, 120, 114, 114, 114, 114, 59, 59, 114, 114, 104, 114, 107, 107, 59, 59, 59, 111, 119, 119, 59, 59, 111, 111, 116, 116, 59, 59, 117, 117, 105, 105, 118, 118, 59, 59, 101, 105, 97, 97, 114, 114, 59, 59, 109, 109, 59, 59, 105, 105, 115, 115, 116, 116, 59, 115, 59, 59, 114, 114, 59, 59, 69, 116, 59, 59, 59, 115, 59, 115, 59, 59, 108, 108, 97, 97, 110, 110, 116, 116, 59, 59, 59, 59, 105, 105, 109, 109, 59, 59, 59, 114, 59, 59, 65, 112, 114, 114, 114, 114, 59, 59, 114, 114, 114, 114, 59, 59, 97, 97, 114, 114, 59, 59, 59, 118, 59, 100, 59, 59, 59, 59, 99, 99, 121, 121, 59, 59, 65, 116, 114, 114, 114, 114, 59, 59, 59, 59, 114, 114, 114, 114, 59, 59, 114, 114, 59, 59, 59, 115, 116, 116, 97, 114, 114, 114, 114, 114, 111, 111, 119, 119, 59, 59, 105, 105, 103, 103, 104, 104, 116, 116, 97, 97, 114, 114, 114, 114, 111, 111, 119, 119, 59, 59, 59, 115, 59, 59, 108, 108, 97, 97, 110, 110, 116, 116, 59, 59, 59, 115, 59, 59, 105, 105, 109, 109, 59, 59, 59, 114, 105, 105, 59, 101, 59, 59, 105, 105, 100, 100, 59, 59, 112, 116, 102, 102, 59, 59, 110, 110, 59, 118, 59, 59, 111, 111, 116, 116, 59, 59, 97, 99, 59, 59, 59, 59, 59, 59, 105, 105, 59, 118, 97, 99, 59, 59, 59, 59, 59, 59, 97, 114, 114, 114, 59, 116, 108, 108, 108, 108, 101, 101, 108, 108, 59, 59, 108, 108, 59, 59, 59, 59, 108, 108, 105, 105, 110, 110, 116, 116, 59, 59, 59, 101, 117, 117, 101, 101, 59, 59, 59, 99, 59, 101, 113, 113, 59, 59, 65, 116, 114, 114, 114, 114, 59, 59, 114, 114, 114, 114, 59, 119, 59, 59, 59, 59, 103, 103, 104, 104, 116, 116, 97, 97, 114, 114, 114, 114, 111, 111, 119, 119, 59, 59, 114, 114, 105, 105, 59, 101, 59, 59, 99, 117, 59, 114, 117, 117, 101, 101, 59, 59, 59, 59, 59, 59, 111, 111, 114, 114, 116, 116, 109, 112, 105, 105, 100, 100, 59, 59, 97, 97, 114, 114, 97, 97, 108, 108, 108, 108, 101, 101, 108, 108, 59, 59, 109, 109, 59, 101, 59, 113, 59, 59, 105, 105, 100, 100, 59, 59, 97, 97, 114, 114, 59, 59, 115, 115, 117, 117, 98, 112, 101, 101, 59, 59, 101, 101, 59, 59, 98, 112, 59, 115, 59, 59, 59, 59, 101, 101, 116, 116, 59, 101, 113, 113, 59, 113, 59, 59, 99, 99, 59, 101, 113, 113, 59, 59, 59, 115, 59, 59, 59, 59, 101, 101, 116, 116, 59, 101, 113, 113, 59, 113, 59, 59, 103, 114, 108, 108, 59, 59, 108, 108, 100, 100, 101, 101, 103, 103, 59, 59, 105, 105, 97, 97, 110, 110, 103, 103, 108, 108, 101, 101, 108, 114, 101, 101, 102, 102, 116, 116, 59, 101, 113, 113, 59, 59, 105, 105, 103, 103, 104, 104, 116, 116, 59, 101, 113, 113, 59, 59, 59, 109, 59, 115, 114, 114, 111, 111, 59, 59, 112, 112, 59, 59, 68, 115, 97, 97, 115, 115, 104, 104, 59, 59, 97, 97, 114, 114, 114, 114, 59, 59, 112, 112, 59, 59, 97, 97, 115, 115, 104, 104, 59, 59, 101, 116, 59, 59, 59, 59, 110, 110, 102, 102, 105, 105, 110, 110, 59, 59, 65, 116, 114, 114, 114, 114, 59, 59, 59, 59, 59, 114, 105, 105, 101, 101, 59, 59, 65, 116, 114, 114, 114, 114, 59, 59, 114, 114, 105, 105, 101, 101, 59, 59, 105, 105, 109, 109, 59, 59, 65, 110, 114, 114, 114, 114, 59, 59, 114, 114, 104, 114, 107, 107, 59, 59, 59, 111, 119, 119, 59, 59, 101, 101, 97, 97, 114, 114, 59, 59, 83, 118, 59, 59, 99, 115, 117, 117, 116, 116, 101, 101, 116, 116, 59, 59, 105, 121, 114, 114, 59, 99, 59, 59, 97, 115, 115, 115, 104, 104, 59, 59, 108, 108, 97, 97, 99, 99, 59, 59, 118, 118, 59, 59, 116, 116, 59, 59, 111, 111, 108, 108, 100, 100, 59, 59, 108, 108, 105, 105, 103, 103, 59, 59, 99, 114, 105, 105, 114, 114, 59, 59, 59, 59, 111, 116, 110, 110, 59, 59, 97, 97, 118, 118, 101, 101, 59, 59, 98, 109, 97, 97, 114, 114, 59, 59, 59, 59, 110, 110, 116, 116, 59, 59, 97, 116, 114, 114, 114, 114, 59, 59, 105, 114, 114, 114, 59, 59, 111, 111, 115, 115, 115, 115, 59, 59, 110, 110, 101, 101, 59, 59, 59, 59, 97, 105, 99, 99, 114, 114, 59, 59, 103, 103, 97, 97, 59, 59, 99, 110, 114, 114, 111, 111, 110, 110, 59, 59, 59, 59, 117, 117, 115, 115, 59, 59, 112, 112, 102, 102, 59, 59, 97, 108, 114, 114, 59, 59, 114, 114, 112, 112, 59, 59, 117, 117, 115, 115, 59, 59, 59, 118, 114, 114, 114, 114, 59, 59, 59, 109, 114, 114, 59, 111, 102, 102, 59, 59, 103, 103, 111, 111, 102, 102, 59, 59, 114, 114, 59, 59, 108, 108, 111, 111, 112, 112, 101, 101, 59, 59, 59, 59, 99, 111, 114, 114, 59, 59, 97, 97, 115, 115, 104, 104, 108, 108, 59, 59, 105, 105, 108, 109, 100, 100, 101, 101, 101, 101, 115, 115, 59, 97, 115, 115, 59, 59, 109, 109, 108, 108, 98, 98, 97, 97, 114, 114, 59, 59, 97, 117, 114, 114, 59, 116, 108, 108, 101, 101, 108, 108, 59, 59, 105, 108, 109, 109, 59, 59, 59, 59, 59, 59, 121, 121, 59, 59, 114, 114, 99, 116, 110, 110, 116, 116, 59, 59, 111, 111, 100, 100, 59, 59, 105, 105, 108, 108, 59, 59, 59, 59, 101, 101, 110, 110, 107, 107, 59, 59, 114, 114, 59, 59, 105, 111, 59, 118, 59, 59, 109, 109, 97, 97, 116, 116, 59, 59, 110, 110, 101, 101, 59, 59, 59, 118, 99, 99, 104, 104, 102, 102, 111, 111, 114, 114, 107, 107, 59, 59, 59, 59, 97, 117, 110, 110, 99, 107, 107, 107, 59, 104, 59, 59, 118, 118, 59, 59, 115, 115, 59, 116, 99, 99, 105, 105, 114, 114, 59, 59, 59, 59, 105, 105, 114, 114, 59, 59, 111, 117, 59, 59, 59, 59, 59, 59, 110, 110, 105, 105, 109, 109, 59, 59, 119, 119, 111, 111, 59, 59, 59, 59, 105, 117, 110, 110, 116, 116, 105, 105, 110, 110, 116, 116, 59, 59, 102, 102, 59, 59, 110, 110, 100, 100, 59, 117, 59, 59, 112, 112, 59, 59, 117, 117, 101, 101, 59, 59, 59, 99, 59, 115, 112, 112, 112, 112, 114, 114, 111, 111, 120, 120, 59, 59, 117, 117, 114, 114, 108, 108, 121, 121, 101, 101, 113, 113, 59, 59, 113, 113, 59, 59, 97, 115, 112, 112, 112, 112, 114, 114, 111, 111, 120, 120, 59, 59, 113, 113, 113, 113, 59, 59, 105, 105, 109, 109, 59, 59, 105, 105, 109, 109, 59, 59, 109, 109, 101, 101, 59, 115, 59, 59, 69, 115, 59, 59, 112, 112, 59, 59, 105, 105, 109, 109, 59, 59, 100, 112, 59, 59, 97, 115, 108, 108, 97, 97, 114, 114, 59, 59, 105, 105, 110, 110, 101, 101, 59, 59, 117, 117, 114, 114, 102, 102, 59, 59, 59, 116, 111, 111, 59, 59, 105, 105, 109, 109, 59, 59, 114, 114, 101, 101, 108, 108, 59, 59, 99, 105, 114, 114, 59, 59, 59, 59, 110, 110, 99, 99, 115, 115, 112, 112, 59, 59, 102, 117, 114, 114, 59, 59, 110, 110, 116, 116, 59, 59, 112, 112, 102, 102, 59, 59, 114, 114, 105, 105, 109, 109, 101, 101, 59, 59, 99, 99, 114, 114, 59, 59, 97, 111, 116, 116, 101, 105, 114, 114, 110, 110, 105, 105, 111, 111, 110, 110, 115, 115, 59, 59, 110, 110, 116, 116, 59, 59, 115, 115, 116, 116, 59, 101, 113, 113, 59, 59, 116, 116, 65, 120, 97, 116, 114, 114, 114, 114, 59, 59, 114, 114, 59, 59, 97, 97, 105, 105, 108, 108, 59, 59, 97, 97, 114, 114, 114, 114, 59, 59, 97, 97, 114, 114, 59, 59, 99, 116, 101, 117, 59, 59, 116, 116, 101, 101, 59, 59, 105, 105, 99, 99, 59, 59, 109, 109, 112, 112, 116, 116, 121, 121, 118, 118, 59, 59, 103, 103, 59, 108, 59, 59, 59, 59, 101, 101, 59, 59, 117, 117, 111, 111, 114, 114, 59, 119, 112, 112, 59, 59, 59, 102, 115, 115, 59, 59, 59, 59, 115, 115, 59, 59, 107, 107, 59, 59, 112, 112, 59, 59, 108, 108, 59, 59, 105, 105, 109, 109, 59, 59, 108, 108, 59, 59, 59, 59, 97, 105, 105, 105, 108, 108, 59, 59, 111, 111, 59, 110, 97, 97, 108, 108, 115, 115, 59, 59, 97, 114, 114, 114, 114, 114, 59, 59, 114, 114, 107, 107, 59, 59, 97, 107, 99, 99, 101, 107, 59, 59, 59, 59, 101, 115, 59, 59, 108, 108, 100, 117, 59, 59, 59, 59, 97, 121, 114, 114, 111, 111, 110, 110, 59, 59, 100, 105, 105, 105, 108, 108, 59, 59, 108, 108, 59, 59, 98, 98, 59, 59, 59, 59, 99, 115, 97, 97, 59, 59, 100, 100, 104, 104, 97, 97, 114, 114, 59, 59, 117, 117, 111, 111, 59, 114, 59, 59, 104, 104, 59, 59, 97, 103, 108, 108, 59, 115, 110, 110, 101, 101, 59, 59, 97, 97, 114, 114, 116, 116, 59, 59, 59, 59, 116, 116, 59, 59, 105, 114, 115, 115, 104, 104, 116, 116, 59, 59, 111, 111, 111, 111, 114, 114, 59, 59, 59, 59, 97, 111, 114, 114, 100, 117, 59, 59, 59, 108, 59, 59, 59, 118, 59, 59, 103, 115, 104, 104, 116, 116, 97, 116, 114, 114, 114, 114, 111, 111, 119, 119, 59, 116, 97, 97, 105, 105, 108, 108, 59, 59, 97, 97, 114, 114, 112, 112, 111, 111, 111, 111, 110, 110, 100, 117, 111, 111, 119, 119, 110, 110, 59, 59, 112, 112, 59, 59, 101, 101, 102, 102, 116, 116, 97, 104, 114, 114, 114, 114, 111, 111, 119, 119, 115, 115, 59, 59, 97, 97, 114, 114, 112, 112, 111, 111, 111, 111, 110, 110, 115, 115, 59, 59, 105, 105, 103, 103, 104, 104, 116, 116, 97, 97, 114, 114, 114, 114, 111, 111, 119, 119, 115, 115, 59, 59, 113, 113, 117, 117, 105, 105, 103, 103, 97, 97, 114, 114, 114, 114, 111, 111, 119, 119, 59, 59, 104, 104, 114, 114, 101, 101, 101, 101, 116, 116, 105, 105, 109, 109, 101, 101, 115, 115, 59, 59, 103, 103, 59, 59, 105, 105, 110, 110, 103, 103, 100, 100, 111, 111, 116, 116, 115, 115, 101, 101, 113, 113, 59, 59, 97, 109, 114, 114, 114, 114, 59, 59, 97, 97, 114, 114, 59, 59, 59, 59, 111, 111, 117, 117, 115, 115, 116, 116, 59, 97, 99, 99, 104, 104, 101, 101, 59, 59, 109, 109, 105, 105, 100, 100, 59, 59, 97, 116, 110, 114, 103, 103, 59, 59, 114, 114, 59, 59, 114, 114, 107, 107, 59, 59, 97, 108, 114, 114, 59, 59, 59, 59, 117, 117, 115, 115, 59, 59, 105, 105, 109, 109, 101, 101, 115, 115, 59, 59, 97, 112, 114, 114, 59, 103, 116, 116, 59, 59, 111, 111, 108, 108, 105, 105, 110, 110, 116, 116, 59, 59, 97, 97, 114, 114, 114, 114, 59, 59, 97, 113, 113, 113, 117, 117, 111, 111, 59, 59, 114, 114, 59, 59, 59, 59, 98, 117, 59, 59, 111, 111, 59, 114, 59, 59, 104, 114, 114, 114, 101, 101, 101, 101, 59, 59, 109, 109, 101, 101, 115, 115, 59, 59, 105, 105, 59, 108, 59, 59, 59, 59, 116, 116, 114, 114, 105, 105, 59, 59, 108, 108, 117, 117, 104, 104, 97, 97, 114, 114, 59, 59, 59, 59, 97, 122, 99, 99, 117, 117, 116, 116, 101, 101, 59, 59, 113, 113, 117, 117, 111, 111, 59, 59, 59, 121, 59, 59, 112, 114, 59, 59, 111, 111, 110, 110, 59, 59, 117, 117, 101, 101, 59, 59, 59, 100, 105, 105, 108, 108, 59, 59, 114, 114, 99, 99, 59, 59, 69, 115, 59, 59, 112, 112, 59, 59, 105, 105, 109, 109, 59, 59, 111, 111, 108, 108, 105, 105, 110, 110, 116, 116, 59, 59, 105, 105, 109, 109, 59, 59, 59, 59, 111, 111, 116, 116, 59, 101, 59, 59, 59, 59, 65, 120, 114, 114, 114, 114, 59, 59, 114, 114, 104, 114, 107, 107, 59, 59, 59, 111, 119, 119, 59, 59, 116, 116, 105, 105, 59, 59, 119, 119, 97, 97, 114, 114, 59, 59, 109, 109, 105, 110, 110, 110, 117, 117, 115, 115, 59, 59, 59, 59, 116, 116, 59, 59, 114, 114, 59, 111, 119, 119, 110, 110, 59, 59, 97, 121, 114, 114, 112, 112, 59, 59, 104, 121, 99, 99, 121, 121, 59, 59, 59, 59, 114, 114, 116, 116, 109, 112, 105, 105, 100, 100, 59, 59, 97, 97, 114, 114, 97, 97, 108, 108, 108, 108, 101, 101, 108, 108, 59, 59, 103, 109, 109, 109, 97, 97, 59, 118, 59, 59, 59, 59, 59, 114, 111, 111, 116, 116, 59, 59, 59, 113, 59, 59, 59, 69, 59, 59, 59, 69, 59, 59, 101, 101, 59, 59, 108, 108, 117, 117, 115, 115, 59, 59, 97, 97, 114, 114, 114, 114, 59, 59, 97, 97, 114, 114, 114, 114, 59, 59, 97, 116, 108, 115, 108, 108, 115, 115, 101, 101, 116, 116, 109, 109, 105, 105, 110, 110, 117, 117, 115, 115, 59, 59, 104, 104, 112, 112, 59, 59, 112, 112, 97, 97, 114, 114, 115, 115, 108, 108, 59, 59, 100, 108, 59, 59, 101, 101, 59, 59, 59, 101, 59, 115, 59, 59, 102, 112, 116, 116, 99, 99, 121, 121, 59, 59, 59, 98, 59, 97, 114, 114, 59, 59, 102, 102, 59, 59, 97, 97, 100, 114, 101, 101, 115, 115, 59, 117, 105, 105, 116, 116, 59, 59, 59, 59, 99, 117, 97, 117, 112, 112, 59, 115, 59, 59, 112, 112, 59, 115, 59, 59, 117, 117, 98, 112, 59, 115, 59, 59, 101, 101, 116, 116, 59, 101, 113, 113, 59, 59, 59, 115, 59, 59, 101, 101, 116, 116, 59, 101, 113, 113, 59, 59, 59, 102, 114, 114, 101, 102, 59, 59, 59, 59, 59, 59, 97, 97, 114, 114, 114, 114, 59, 59, 99, 116, 114, 114, 59, 59, 116, 116, 109, 109, 110, 110, 59, 59, 105, 105, 108, 108, 101, 101, 59, 59, 97, 97, 114, 114, 102, 102, 59, 59, 97, 114, 114, 114, 59, 102, 59, 59, 97, 110, 105, 105, 103, 103, 104, 104, 116, 116, 101, 112, 112, 112, 115, 115, 105, 105, 108, 108, 111, 111, 110, 110, 59, 59, 104, 104, 105, 105, 59, 59, 115, 115, 59, 59, 98, 112, 59, 115, 59, 59, 111, 111, 116, 116, 59, 59, 59, 100, 111, 111, 116, 116, 59, 59, 117, 117, 108, 108, 116, 116, 59, 59, 69, 101, 59, 59, 59, 59, 108, 108, 117, 117, 115, 115, 59, 59, 97, 97, 114, 114, 114, 114, 59, 59, 101, 117, 116, 116, 59, 110, 113, 113, 59, 113, 59, 59, 101, 101, 113, 113, 59, 113, 59, 59, 109, 109, 59, 59, 98, 112, 59, 59, 59, 59, 99, 99, 59, 115, 112, 112, 112, 112, 114, 114, 111, 111, 120, 120, 59, 59, 117, 117, 114, 114, 108, 108, 121, 121, 101, 101, 113, 113, 59, 59, 113, 113, 59, 59, 97, 115, 112, 112, 112, 112, 114, 114, 111, 111, 120, 120, 59, 59, 113, 113, 113, 113, 59, 59, 105, 105, 109, 109, 59, 59, 105, 105, 109, 109, 59, 59, 59, 59, 103, 103, 59, 59, 49, 115, 59, 59, 111, 115, 116, 116, 59, 59, 117, 117, 98, 98, 59, 59, 59, 100, 111, 111, 116, 116, 59, 59, 115, 115, 111, 117, 108, 108, 59, 59, 98, 98, 59, 59, 97, 97, 114, 114, 114, 114, 59, 59, 117, 117, 108, 108, 116, 116, 59, 59, 69, 101, 59, 59, 59, 59, 108, 108, 117, 117, 115, 115, 59, 59, 101, 117, 116, 116, 59, 110, 113, 113, 59, 113, 59, 59, 101, 101, 113, 113, 59, 113, 59, 59, 109, 109, 59, 59, 98, 112, 59, 59, 59, 59, 65, 110, 114, 114, 114, 114, 59, 59, 114, 114, 104, 114, 107, 107, 59, 59, 59, 111, 119, 119, 59, 59, 119, 119, 97, 97, 114, 114, 59, 59, 108, 108, 105, 105, 103, 103, 97, 119, 114, 117, 103, 103, 101, 101, 116, 116, 59, 59, 59, 59, 114, 114, 107, 107, 59, 59, 97, 121, 114, 114, 111, 111, 110, 110, 59, 59, 100, 100, 105, 105, 108, 108, 59, 59, 59, 59, 111, 111, 116, 116, 59, 59, 108, 108, 114, 114, 101, 101, 99, 99, 59, 59, 114, 114, 59, 59, 101, 111, 114, 116, 101, 101, 52, 102, 59, 59, 111, 111, 114, 114, 101, 101, 59, 59, 97, 97, 59, 118, 121, 121, 109, 109, 59, 59, 59, 59, 99, 110, 107, 107, 97, 115, 112, 112, 112, 112, 114, 114, 111, 111, 120, 120, 59, 59, 105, 105, 109, 109, 59, 59, 115, 115, 112, 112, 59, 59, 97, 115, 112, 112, 59, 59, 105, 105, 109, 109, 59, 59, 114, 114, 110, 110, 108, 110, 100, 100, 101, 101, 59, 59, 101, 101, 115, 115, 59, 97, 114, 114, 59, 59, 59, 59, 116, 116, 59, 59, 101, 115, 97, 97, 59, 59, 59, 102, 111, 111, 116, 116, 59, 59, 105, 105, 114, 114, 59, 59, 59, 111, 114, 114, 107, 107, 59, 59, 97, 97, 59, 59, 114, 114, 105, 105, 109, 109, 101, 101, 59, 59, 97, 112, 100, 100, 101, 101, 59, 59, 97, 116, 110, 110, 103, 103, 108, 108, 101, 101, 59, 114, 111, 111, 119, 119, 110, 110, 59, 59, 101, 101, 102, 102, 116, 116, 59, 101, 113, 113, 59, 59, 59, 59, 105, 105, 103, 103, 104, 104, 116, 116, 59, 101, 113, 113, 59, 59, 111, 111, 116, 116, 59, 59, 59, 59, 105, 105, 110, 110, 117, 117, 115, 115, 59, 59, 108, 108, 117, 117, 115, 115, 59, 59, 98, 98, 59, 59, 105, 105, 109, 109, 101, 101, 59, 59, 101, 101, 122, 122, 105, 105, 117, 117, 109, 109, 59, 59, 99, 116, 114, 121, 59, 59, 59, 59, 99, 99, 121, 121, 59, 59, 114, 114, 111, 111, 107, 107, 59, 59, 105, 111, 120, 120, 116, 116, 59, 59, 104, 104, 101, 101, 97, 97, 100, 100, 108, 114, 101, 101, 102, 102, 116, 116, 97, 97, 114, 114, 114, 114, 111, 111, 119, 119, 59, 59, 105, 105, 103, 103, 104, 104, 116, 116, 97, 97, 114, 114, 114, 114, 111, 111, 119, 119, 59, 59, 65, 119, 114, 114, 114, 114, 59, 59, 97, 97, 114, 114, 59, 59, 99, 114, 117, 117, 116, 116, 101, 101, 114, 114, 59, 59, 114, 114, 99, 101, 121, 121, 59, 59, 118, 118, 101, 101, 59, 59, 105, 121, 114, 114, 99, 99, 59, 59, 97, 104, 114, 114, 114, 114, 59, 59, 108, 108, 97, 97, 99, 99, 59, 59, 97, 97, 114, 114, 59, 59, 105, 114, 115, 115, 104, 104, 116, 116, 59, 59, 59, 59, 114, 114, 97, 97, 118, 118, 101, 101, 97, 98, 114, 114, 108, 114, 59, 59, 59, 59, 108, 108, 107, 107, 59, 59, 99, 116, 111, 114, 114, 114, 110, 110, 59, 101, 114, 114, 59, 59, 111, 111, 112, 112, 59, 59, 114, 114, 105, 105, 59, 59, 97, 108, 99, 99, 114, 114, 59, 59, 103, 112, 111, 111, 110, 110, 59, 59, 102, 102, 59, 59, 97, 117, 114, 114, 114, 114, 111, 111, 119, 119, 59, 59, 111, 111, 119, 119, 110, 110, 97, 97, 114, 114, 114, 114, 111, 111, 119, 119, 59, 59, 97, 97, 114, 114, 112, 112, 111, 111, 111, 111, 110, 110, 108, 114, 101, 101, 102, 102, 116, 116, 59, 59, 105, 105, 103, 103, 104, 104, 116, 116, 59, 59, 117, 117, 115, 115, 59, 59, 105, 105, 59, 108, 59, 59, 111, 111, 110, 110, 59, 59, 112, 112, 97, 97, 114, 114, 114, 114, 111, 111, 119, 119, 115, 115, 59, 59, 99, 116, 111, 114, 114, 114, 110, 110, 59, 101, 114, 114, 59, 59, 111, 111, 112, 112, 59, 59, 110, 110, 103, 103, 59, 59, 114, 114, 105, 105, 59, 59, 99, 99, 114, 114, 59, 59, 100, 114, 111, 111, 116, 116, 59, 59, 108, 108, 100, 100, 101, 101, 59, 59, 105, 105, 59, 102, 59, 59, 97, 109, 114, 114, 114, 114, 59, 59, 108, 108, 97, 97, 110, 110, 103, 103, 108, 108, 101, 101, 59, 59, 65, 122, 114, 114, 114, 114, 59, 59, 97, 97, 114, 114, 59, 118, 59, 59, 97, 97, 115, 115, 104, 104, 59, 59, 110, 114, 103, 103, 114, 114, 116, 116, 59, 59, 101, 116, 112, 112, 115, 115, 105, 105, 108, 108, 111, 111, 110, 110, 59, 59, 97, 97, 112, 112, 112, 112, 97, 97, 59, 59, 111, 111, 116, 116, 104, 104, 105, 105, 110, 110, 103, 103, 59, 59, 104, 114, 105, 105, 59, 59, 59, 59, 111, 111, 112, 112, 116, 116, 111, 111, 59, 59, 59, 104, 111, 111, 59, 59, 105, 117, 103, 103, 109, 109, 97, 97, 59, 59, 98, 112, 115, 115, 101, 101, 116, 116, 110, 110, 101, 101, 113, 113, 59, 113, 59, 59, 115, 115, 101, 101, 116, 116, 110, 110, 101, 101, 113, 113, 59, 113, 59, 59, 104, 114, 101, 101, 116, 116, 97, 97, 59, 59, 105, 105, 97, 97, 110, 110, 103, 103, 108, 108, 101, 101, 108, 114, 101, 101, 102, 102, 116, 116, 59, 59, 105, 105, 103, 103, 104, 104, 116, 116, 59, 59, 121, 121, 59, 59, 97, 97, 115, 115, 104, 104, 59, 59, 101, 114, 59, 101, 97, 97, 114, 114, 59, 59, 113, 113, 59, 59, 108, 108, 105, 105, 112, 112, 59, 59, 98, 116, 97, 97, 114, 114, 59, 59, 59, 59, 114, 114, 59, 59, 116, 116, 114, 114, 105, 105, 59, 59, 115, 115, 117, 117, 98, 112, 59, 59, 59, 59, 112, 112, 102, 102, 59, 59, 114, 114, 111, 111, 112, 112, 59, 59, 116, 116, 114, 114, 105, 105, 59, 59, 99, 117, 114, 114, 59, 59, 98, 112, 110, 110, 69, 101, 59, 59, 59, 59, 110, 110, 69, 101, 59, 59, 59, 59, 105, 105, 103, 103, 122, 122, 97, 97, 103, 103, 59, 59, 99, 115, 105, 105, 114, 114, 99, 99, 59, 59, 100, 105, 98, 103, 97, 97, 114, 114, 59, 59, 101, 101, 59, 113, 59, 59, 101, 101, 114, 114, 112, 112, 59, 59, 114, 114, 59, 59, 112, 112, 102, 102, 59, 59, 59, 59, 59, 101, 97, 97, 116, 116, 104, 104, 59, 59, 99, 99, 114, 114, 59, 59, 99, 119, 97, 117, 112, 112, 59, 59, 114, 114, 99, 99, 59, 59, 112, 112, 59, 59, 116, 116, 114, 114, 105, 105, 59, 59, 114, 114, 59, 59, 65, 97, 114, 114, 114, 114, 59, 59, 114, 114, 114, 114, 59, 59, 59, 59, 65, 97, 114, 114, 114, 114, 59, 59, 114, 114, 114, 114, 59, 59, 97, 97, 112, 112, 59, 59, 105, 105, 115, 115, 59, 59, 100, 116, 111, 111, 116, 116, 59, 59, 102, 108, 59, 59, 117, 117, 115, 115, 59, 59, 105, 105, 109, 109, 101, 101, 59, 59, 65, 97, 114, 114, 114, 114, 59, 59, 114, 114, 114, 114, 59, 59, 99, 113, 114, 114, 59, 59, 99, 99, 117, 117, 112, 112, 59, 59, 112, 116, 108, 108, 117, 117, 115, 115, 59, 59, 114, 114, 105, 105, 59, 59, 101, 101, 101, 101, 59, 59, 101, 101, 100, 100, 103, 103, 101, 101, 59, 59, 97, 117, 99, 99, 117, 121, 116, 116, 101, 101, 59, 59, 105, 121, 114, 114, 99, 99, 59, 59, 59, 59, 110, 110, 114, 114, 59, 59, 99, 99, 121, 121, 59, 59, 112, 112, 102, 102, 59, 59, 99, 99, 114, 114, 59, 59, 99, 109, 121, 121, 59, 59, 108, 108, 97, 119, 99, 99, 117, 117, 116, 116, 101, 101, 59, 59, 97, 121, 114, 114, 111, 111, 110, 110, 59, 59, 59, 59, 111, 111, 116, 116, 59, 59, 101, 116, 116, 116, 114, 114, 102, 102, 59, 59, 97, 97, 59, 59, 114, 114, 59, 59, 99, 99, 121, 121, 59, 59, 103, 103, 114, 114, 97, 97, 114, 114, 114, 114, 59, 59, 112, 112, 102, 102, 59, 59, 99, 99, 114, 114, 59, 59, 106, 110, 59, 59, 106, 106, 59, 59, 65, 122, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 101, 59, 115, 59, 59, 59, 59, 59, 111, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 114, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 114, 59, 59, 59, 59, 59, 59, 59, 59, 59, 110, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 108, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 100, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 0 }; static const char _char_ref_key_spans[] = { 0, 49, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 17, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 10, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 17, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 21, 16, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 18, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 46, 1, 1, 1, 1, 1, 23, 1, 1, 1, 1, 47, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 15, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 11, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 17, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 17, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 14, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 10, 1, 1, 43, 1, 14, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 13, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 1, 1, 1, 48, 53, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 13, 1, 1, 1, 1, 1, 1, 1, 1, 1, 25, 1, 1, 1, 1, 1, 1, 58, 1, 1, 1, 1, 6, 11, 1, 1, 1, 1, 1, 1, 1, 20, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8, 1, 1, 11, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 20, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4, 1, 1, 1, 1, 1, 1, 1, 1, 11, 1, 1, 20, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7, 1, 1, 1, 18, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 20, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 33, 1, 1, 1, 1, 27, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8, 1, 1, 1, 1, 1, 7, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 18, 1, 1, 1, 1, 1, 1, 43, 1, 1, 1, 1, 1, 1, 1, 25, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 16, 1, 1, 1, 1, 1, 4, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 10, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 1, 26, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7, 1, 1, 1, 1, 1, 1, 1, 1, 8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 17, 1, 1, 1, 1, 1, 1, 1, 1, 4, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 43, 1, 1, 1, 1, 1, 1, 42, 1, 1, 1, 1, 1, 1, 21, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 16, 1, 1, 1, 1, 18, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 53, 1, 1, 1, 1, 1, 18, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 18, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 49, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 17, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 54, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 43, 12, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 18, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 14, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 19, 17, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 44, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 21, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 43, 1, 1, 1, 16, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 25, 1, 1, 1, 1, 1, 1, 1, 1, 1, 14, 1, 50, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 24, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 22, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 14, 1, 28, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 11, 1, 1, 1, 1, 1, 1, 1, 1, 1, 19, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8, 1, 1, 1, 1, 1, 1, 1, 1, 8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 16, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 43, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 10, 1, 39, 1, 1, 1, 18, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 18, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 18, 1, 1, 1, 1, 1, 1, 1, 1, 21, 1, 1, 1, 1, 9, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 44, 1, 1, 1, 1, 1, 1, 1, 1, 25, 1, 1, 1, 1, 1, 1, 1, 1, 1, 17, 1, 1, 1, 1, 1, 10, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 12, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 51, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 28, 7, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 13, 1, 1, 1, 1, 1, 1, 1, 1, 1, 26, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 26, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 14, 1, 1, 1, 1, 1, 1, 1, 1, 1, 11, 1, 1, 1, 1, 1, 1, 1, 1, 1, 26, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 25, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 11, 1, 1, 1, 1, 1, 1, 1, 1, 5, 1, 1, 1, 1, 1, 1, 15, 1, 1, 1, 11, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 11, 1, 1, 1, 1, 1, 15, 1, 1, 1, 11, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 26, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 11, 1, 1, 1, 1, 1, 1, 1, 1, 1, 26, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 50, 1, 1, 1, 1, 1, 1, 1, 1, 17, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 14, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 10, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 15, 18, 1, 1, 1, 1, 7, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 19, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 53, 1, 1, 1, 1, 1, 26, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 13, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 39, 1, 1, 7, 1, 1, 1, 31, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 52, 1, 1, 1, 1, 1, 16, 1, 1, 1, 1, 1, 1, 1, 58, 1, 1, 25, 1, 1, 1, 1, 1, 1, 1, 1, 1, 60, 1, 1, 1, 1, 17, 6, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 33, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 18, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8, 1, 1, 1, 1, 1, 1, 1, 1, 14, 1, 28, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 11, 1, 1, 1, 1, 1, 1, 1, 1, 1, 19, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8, 1, 1, 1, 1, 1, 1, 1, 1, 8, 1, 1, 1, 1, 1, 1, 1, 1, 6, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 46, 33, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 63, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 18, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4, 1, 1, 1, 1, 1, 27, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 15, 1, 1, 1, 11, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 11, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 15, 57, 1, 1, 11, 1, 1, 1, 1, 1, 6, 1, 1, 1, 1, 26, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 57, 1, 1, 1, 1, 11, 1, 1, 1, 1, 1, 1, 1, 1, 44, 1, 1, 1, 1, 1, 1, 1, 28, 1, 1, 1, 1, 1, 20, 1, 1, 25, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 3, 1, 1, 1, 1, 1, 1, 1, 1, 12, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 26, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 18, 1, 1, 1, 1, 1, 1, 21, 16, 1, 1, 1, 1, 53, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 17, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6, 1, 1, 15, 18, 1, 1, 1, 1, 7, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 22, 1, 1, 1, 1, 10, 1, 1, 1, 1, 1, 51, 1, 1, 1, 1, 10, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 50, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 51, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 50, 1, 14, 1, 24, 1, 1, 1, 47, 1, 1, 1, 19, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 17, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 14, 1, 1, 1, 1, 1, 1, 1, 1, 1, 53, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 17, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 44, 1, 1, 1, 1, 1, 1, 1, 1, 25, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 23, 1, 1, 1, 1, 1, 1, 1, 1, 1, 63, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 56, 1, 1, 1, 1, 1, 12, 11, 1, 1, 1, 1, 1, 1, 1, 1, 1, 16, 10, 1, 1, 1, 1, 4, 60, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 64, 1, 1, 1, 1, 1, 39, 8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 60, 1, 42, 1, 5, 1, 1, 1, 1, 1, 1, 1, 10, 1, 1, 1, 1, 1, 54, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 43, 1, 1, 1, 1, 1, 23, 1, 1, 1, 1, 1, 43, 1, 1, 1, 1, 1, 1, 1, 1, 7, 1, 1, 1, 1, 1, 1, 1, 1, 1, 40, 1, 1, 1, 16, 1, 17, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 43, 1, 1, 2, 1, 1, 1, 1, 1, 45, 1, 1, 1, 1, 58, 1, 1, 1, 1, 11, 1, 1, 1, 1, 1, 1, 1, 1, 18, 1, 1, 1, 43, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 23, 1, 1, 1, 1, 1, 1, 1, 1, 1, 21, 21, 1, 1, 1, 1, 1, 1, 1, 17, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 18, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 15, 12, 1, 9, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 56, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 3, 1, 1, 1, 1, 1, 1, 1, 11, 55, 1, 1, 1, 1, 1, 1, 9, 1, 1, 58, 1, 1, 1, 1, 1, 1, 1, 51, 39, 1, 1, 1, 1, 59, 1, 1, 1, 1, 39, 1, 1, 1, 1, 56, 1, 1, 1, 1, 1, 1, 1, 1, 1, 39, 1, 1, 1, 1, 59, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 39, 1, 1, 1, 1, 56, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 18, 1, 1, 1, 1, 1, 1, 13, 1, 1, 1, 1, 1, 1, 43, 1, 1, 46, 1, 1, 1, 1, 1, 2, 1, 43, 1, 1, 1, 43, 1, 55, 1, 25, 16, 1, 1, 1, 1, 57, 1, 1, 1, 1, 1, 1, 1, 1, 21, 1, 1, 1, 1, 1, 1, 1, 1, 11, 1, 1, 1, 1, 21, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 57, 1, 1, 1, 1, 1, 11, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7, 1, 1, 1, 1, 51, 1, 1, 1, 1, 1, 1, 57, 1, 50, 1, 1, 1, 4, 1, 1, 1, 1, 7, 1, 1, 1, 1, 1, 1, 1, 1, 1, 19, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 59, 1, 1, 1, 5, 1, 1, 43, 55, 1, 4, 1, 58, 1, 50, 1, 1, 1, 12, 1, 1, 1, 1, 1, 1, 1, 3, 42, 1, 1, 1, 1, 1, 1, 20, 1, 1, 1, 1, 1, 1, 15, 1, 1, 1, 1, 1, 1, 19, 1, 1, 15, 43, 1, 43, 1, 1, 1, 1, 1, 20, 1, 1, 1, 7, 1, 1, 4, 1, 1, 1, 1, 1, 1, 1, 54, 1, 57, 1, 1, 1, 1, 1, 21, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 22, 1, 1, 51, 1, 1, 19, 1, 4, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 58, 1, 1, 1, 1, 1, 1, 13, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 60, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 25, 1, 1, 1, 1, 1, 53, 12, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7, 1, 1, 1, 1, 1, 1, 1, 1, 10, 1, 1, 1, 1, 1, 1, 1, 7, 1, 1, 22, 1, 57, 1, 1, 57, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 53, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4, 1, 1, 1, 1, 1, 1, 12, 1, 1, 1, 1, 1, 1, 57, 1, 42, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 4, 1, 1, 1, 1, 1, 1, 18, 8, 1, 1, 1, 1, 1, 1, 1, 1, 15, 1, 1, 1, 1, 44, 1, 8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7, 1, 1, 1, 1, 1, 1, 1, 1, 53, 44, 1, 1, 1, 1, 1, 17, 1, 1, 1, 1, 1, 1, 1, 25, 1, 1, 1, 1, 1, 41, 1, 1, 1, 1, 1, 1, 1, 1, 1, 47, 1, 1, 1, 1, 57, 1, 1, 1, 42, 1, 1, 1, 57, 1, 1, 1, 1, 1, 1, 1, 42, 1, 1, 1, 19, 1, 1, 1, 1, 1, 60, 1, 1, 1, 1, 1, 11, 2, 1, 1, 13, 1, 1, 1, 10, 1, 1, 1, 1, 1, 19, 1, 57, 1, 1, 1, 1, 1, 1, 60, 1, 1, 1, 1, 20, 7, 1, 1, 1, 1, 1, 1, 1, 4, 1, 1, 1, 1, 1, 6, 1, 1, 1, 1, 1, 1, 1, 9, 1, 1, 1, 1, 1, 1, 1, 10, 1, 1, 1, 1, 1, 1, 1, 1, 30, 1, 1, 1, 1, 1, 1, 7, 1, 1, 1, 1, 1, 1, 1, 8, 1, 6, 1, 1, 1, 14, 1, 1, 1, 1, 1, 11, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 19, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 10, 1, 1, 1, 1, 4, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 20, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 11, 1, 1, 1, 60, 1, 1, 1, 1, 1, 1, 1, 1, 15, 17, 7, 7, 1, 1, 1, 1, 3, 1, 1, 5, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 50, 50, 1, 14, 1, 1, 1, 1, 1, 1, 42, 1, 1, 1, 1, 1, 1, 1, 17, 1, 1, 1, 1, 1, 1, 1, 57, 1, 57, 1, 1, 1, 1, 1, 1, 50, 1, 1, 1, 1, 53, 50, 1, 43, 1, 1, 1, 1, 45, 1, 1, 1, 1, 1, 1, 1, 1, 48, 1, 1, 1, 47, 1, 1, 54, 1, 1, 1, 1, 55, 55, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7, 1, 1, 1, 50, 1, 1, 7, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 19, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 10, 1, 1, 1, 1, 1, 1, 1, 1, 1, 57, 1, 1, 1, 10, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 15, 1, 1, 1, 61, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 18, 1, 1, 1, 59, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 19, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 18, 1, 1, 1, 1, 1, 1, 1, 1, 7, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 18, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 15, 1, 1, 1, 1, 1, 1, 1, 1, 21, 1, 1, 1, 1, 63, 1, 1, 1, 22, 1, 1, 1, 1, 13, 1, 1, 1, 1, 1, 1, 53, 6, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 16, 18, 1, 1, 12, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 58, 1, 1, 1, 1, 1, 1, 58, 1, 1, 1, 1, 1, 1, 1, 54, 1, 1, 1, 12, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 18, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7, 1, 1, 1, 60, 1, 1, 1, 1, 60, 1, 1, 47, 1, 1, 1, 1, 3, 1, 1, 1, 1, 19, 17, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 19, 1, 1, 1, 60, 1, 21, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 54, 20, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 45, 1, 1, 1, 1, 18, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 50, 1, 1, 1, 1, 1, 1, 1, 58, 44, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 43, 1, 1, 1, 57, 1, 18, 1, 1, 1, 1, 1, 1, 11, 1, 7, 1, 1, 15, 1, 1, 18, 1, 1, 25, 1, 1, 1, 1, 6, 1, 1, 1, 1, 1, 1, 1, 1, 17, 1, 1, 1, 1, 56, 1, 18, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 57, 1, 20, 1, 1, 1, 1, 58, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 18, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 19, 1, 1, 1, 1, 57, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 57, 1, 1, 1, 1, 1, 1, 57, 1, 1, 1, 1, 53, 56, 1, 43, 1, 1, 19, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 11, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 10, 1, 1, 1, 1, 1, 1, 1, 1, 1, 11, 1, 2, 1, 18, 1, 50, 1, 1, 1, 1, 1, 1, 1, 58, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7, 1, 1, 1, 1, 1, 1, 1, 39, 1, 1, 1, 1, 47, 1, 1, 54, 1, 1, 1, 1, 55, 55, 1, 1, 1, 1, 26, 5, 1, 1, 1, 1, 1, 1, 1, 1, 7, 1, 1, 1, 18, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7, 1, 1, 1, 1, 1, 1, 1, 1, 1, 12, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 44, 1, 1, 1, 1, 1, 1, 1, 50, 1, 1, 20, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 42, 1, 1, 1, 1, 1, 20, 1, 1, 1, 1, 1, 1, 1, 1, 45, 1, 1, 20, 1, 1, 56, 1, 1, 1, 1, 1, 7, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 26, 1, 1, 1, 44, 1, 1, 1, 18, 1, 1, 1, 1, 1, 1, 1, 1, 1, 10, 1, 1, 1, 1, 1, 1, 1, 1, 1, 50, 1, 1, 1, 1, 16, 1, 16, 1, 43, 1, 1, 1, 57, 1, 1, 59, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 11, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 12, 1, 1, 42, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 42, 1, 59, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 13, 1, 1, 1, 1, 1, 1, 1, 18, 1, 1, 1, 1, 1, 1, 51, 1, 1, 1, 1, 1, 1, 1, 1, 1, 49, 14, 1, 60, 1, 16, 1, 1, 18, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 60, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 33, 1, 1, 1, 1, 1, 1, 1, 1, 19, 1, 1, 1, 1, 1, 1, 1, 1, 1, 54, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 39, 1, 57, 1, 3, 1, 1, 1, 43, 1, 25, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 42, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 62, 1, 1, 1, 1, 11, 1, 1, 53, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 1, 1, 1, 1, 1, 1, 1, 1, 57, 1, 1, 1, 48, 1, 57, 57, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 56, 1, 48, 1, 1, 1, 1, 1, 1, 1, 1, 1, 60, 42, 1, 1, 1, 1, 1, 52, 1, 1, 1, 1, 1, 1, 1, 1, 1, 57, 1, 18, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 57, 1, 1, 1, 1, 1, 1, 57, 1, 1, 1, 1, 56, 1, 43, 1, 1, 1, 1, 5, 1, 1, 1, 60, 1, 1, 1, 1, 3, 1, 1, 1, 1, 60, 3, 1, 1, 1, 18, 1, 58, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 43, 1, 1, 1, 41, 43, 1, 1, 52, 1, 1, 1, 1, 1, 61, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 43, 1, 19, 56, 1, 1, 1, 1, 1, 1, 1, 1, 4, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 43, 55, 1, 1, 1, 1, 1, 1, 1, 1, 1, 15, 1, 1, 1, 1, 15, 57, 1, 1, 1, 1, 43, 1, 55, 1, 1, 43, 1, 1, 57, 1, 1, 1, 1, 43, 1, 55, 1, 12, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7, 1, 1, 1, 43, 1, 1, 1, 1, 1, 1, 43, 1, 1, 51, 57, 1, 1, 1, 1, 1, 48, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 16, 1, 1, 1, 1, 1, 1, 1, 52, 1, 1, 1, 1, 56, 1, 1, 1, 52, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 46, 1, 1, 1, 1, 11, 1, 1, 53, 1, 1, 1, 1, 1, 1, 36, 1, 17, 1, 1, 1, 1, 1, 17, 1, 41, 1, 19, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 16, 1, 1, 1, 1, 6, 1, 1, 1, 1, 1, 1, 12, 1, 1, 1, 1, 1, 1, 1, 20, 1, 1, 1, 10, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 1, 1, 1, 1, 1, 1, 12, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 12, 1, 1, 1, 1, 1, 1, 1, 1, 60, 1, 1, 1, 51, 1, 53, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 13, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 39, 1, 1, 1, 1, 1, 1, 1, 1, 21, 1, 58, 1, 1, 1, 1, 4, 1, 1, 1, 1, 1, 1, 1, 18, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7, 60, 1, 1, 1, 1, 1, 1, 1, 1, 60, 1, 1, 1, 1, 1, 1, 1, 1, 21, 1, 9, 1, 46, 1, 1, 1, 1, 58, 1, 1, 1, 1, 1, 1, 1, 1, 7, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 13, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 59, 1, 1, 1, 1, 1, 1, 41, 57, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 19, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 57, 1, 47, 1, 1, 1, 1, 1, 1, 13, 1, 19, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 58, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7, 1, 1, 1, 1, 1, 1, 1, 1, 16, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 15, 1, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 43, 1, 1, 1, 56, 20, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 18, 17, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 50, 1, 1, 1, 1, 1, 1, 1, 61, 1, 1, 44, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 1, 1, 1, 1, 52, 1, 1, 1, 1, 18, 1, 1, 1, 1, 1, 1, 11, 1, 7, 1, 1, 15, 1, 1, 18, 1, 1, 25, 1, 1, 1, 1, 6, 1, 1, 1, 1, 1, 1, 1, 1, 17, 1, 1, 1, 1, 1, 1, 1, 1, 1, 56, 1, 1, 1, 7, 1, 57, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 10, 1, 1, 1, 1, 1, 1, 1, 1, 1, 15, 1, 18, 1, 50, 1, 60, 1, 13, 1, 1, 20, 1, 1, 1, 1, 58, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 18, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 13, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 39, 1, 1, 1, 1, 1, 1, 1, 1, 20, 5, 1, 1, 1, 1, 1, 1, 1, 12, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 16, 1, 45, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 17, 1, 1, 1, 1, 1, 1, 1, 20, 1, 1, 56, 1, 11, 1, 1, 1, 1, 1, 1, 1, 1, 1, 50, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 26, 1, 1, 1, 1, 1, 1, 1, 1, 1, 63, 1, 3, 1, 1, 1, 1, 1, 1, 1, 42, 1, 1, 1, 1, 1, 1, 47, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 43, 1, 1, 56, 1, 1, 1, 1, 11, 1, 1, 53, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6, 1, 1, 1, 1, 1, 1, 1, 1, 53, 1, 1, 1, 25, 1, 1, 1, 18, 1, 1, 1, 1, 1, 1, 4, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7, 1, 1, 60, 1, 1, 56, 1, 1, 1, 55, 1, 11, 1, 11, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 20, 8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 1, 1, 1, 43, 57, 1, 11, 1, 1, 1, 1, 40, 39, 1, 1, 1, 1, 1, 15, 1, 1, 59, 1, 1, 1, 1, 19, 21, 1, 57, 1, 1, 57, 1, 1, 15, 57, 1, 1, 1, 43, 1, 1, 57, 1, 1, 1, 43, 1, 1, 44, 1, 2, 1, 1, 1, 1, 1, 1, 1, 18, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 18, 1, 44, 1, 14, 1, 1, 1, 1, 12, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 15, 57, 1, 1, 1, 1, 42, 1, 1, 1, 1, 1, 1, 1, 33, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 17, 1, 52, 1, 55, 1, 1, 1, 55, 1, 1, 1, 15, 1, 1, 1, 57, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 19, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 67, 1, 5, 1, 1, 1, 1, 1, 42, 1, 1, 1, 1, 7, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 33, 1, 1, 1, 1, 1, 1, 17, 1, 52, 1, 55, 1, 1, 1, 55, 1, 1, 1, 15, 1, 1, 46, 1, 1, 1, 1, 11, 1, 1, 53, 1, 1, 1, 1, 1, 1, 1, 1, 1, 23, 4, 1, 1, 1, 1, 1, 1, 1, 1, 25, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 11, 3, 1, 51, 1, 1, 1, 1, 1, 1, 60, 1, 1, 1, 1, 12, 1, 19, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 19, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 39, 1, 1, 1, 1, 1, 15, 1, 1, 44, 1, 1, 1, 1, 1, 1, 53, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 16, 1, 1, 1, 20, 1, 1, 1, 1, 56, 1, 1, 1, 1, 1, 1, 1, 43, 1, 1, 1, 1, 1, 1, 1, 43, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 18, 8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7, 1, 1, 1, 1, 1, 1, 1, 7, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 55, 1, 1, 1, 1, 1, 1, 16, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 17, 1, 1, 1, 8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 10, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 7, 1, 1, 1, 1, 1, 18, 4, 1, 1, 43, 1, 1, 1, 1, 1, 1, 1, 1, 12, 1, 1, 1, 10, 1, 1, 1, 1, 1, 21, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 50, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 18, 4, 1, 1, 43, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 15, 1, 1, 1, 1, 1, 1, 1, 1, 44, 1, 13, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 58, 1, 1, 1, 1, 1, 60, 1, 1, 1, 1, 1, 5, 1, 1, 1, 1, 16, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 11, 1, 1, 1, 1, 1, 1, 1, 1, 46, 1, 1, 13, 1, 1, 1, 1, 15, 1, 1, 1, 1, 1, 1, 55, 1, 1, 1, 1, 1, 1, 1, 55, 1, 11, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 14, 43, 1, 1, 1, 1, 1, 1, 1, 1, 1, 19, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 15, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 19, 1, 1, 15, 1, 33, 1, 1, 1, 33, 1, 1, 1, 1, 1, 1, 1, 1, 17, 1, 1, 1, 1, 6, 6, 1, 1, 1, 1, 55, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 43, 1, 1, 1, 1, 1, 1, 1, 21, 21, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 33, 1, 1, 1, 1, 1, 1, 1, 33, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 17, 1, 1, 1, 7, 1, 1, 1, 1, 1, 1, 1, 1, 33, 1, 1, 1, 1, 1, 1, 15, 1, 1, 1, 1, 1, 1, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 21, 1, 5, 1, 1, 1, 17, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 11, 1, 1, 1, 23, 1, 1, 1, 1, 1, 25, 1, 1, 1, 1, 1, 1, 1, 1, 16, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 1, 1, 1, 58, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 43, 57, 1, 1, 53, 1, 1, 1, 1, 1, 1, 1, 1, 56, 1, 1, 1, 1, 1, 1, 1, 56, 1, 1, 1, 1, 52, 1, 1, 1, 1, 1, 1, 1, 1, 1, 50, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 42, 1, 1, 1, 1, 1, 1, 1, 1 }; static const unsigned short _char_ref_index_offsets[] = { 0, 0, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 94, 96, 98, 100, 102, 104, 106, 108, 110, 112, 114, 116, 118, 120, 122, 124, 126, 128, 130, 132, 143, 145, 147, 149, 151, 153, 155, 157, 159, 161, 163, 165, 167, 169, 171, 173, 175, 177, 179, 181, 183, 201, 203, 205, 207, 209, 211, 213, 215, 217, 219, 221, 223, 225, 247, 264, 266, 268, 270, 272, 274, 276, 278, 281, 283, 285, 287, 289, 291, 293, 312, 314, 316, 318, 320, 322, 324, 326, 328, 330, 332, 334, 336, 338, 340, 342, 344, 346, 348, 350, 352, 354, 356, 358, 360, 362, 364, 366, 368, 370, 372, 374, 376, 423, 425, 427, 429, 431, 433, 457, 459, 461, 463, 465, 513, 515, 517, 519, 521, 523, 525, 527, 529, 531, 533, 535, 537, 539, 541, 543, 545, 547, 549, 551, 553, 555, 557, 573, 575, 577, 579, 581, 583, 585, 587, 589, 591, 593, 595, 597, 599, 601, 603, 605, 607, 609, 621, 623, 625, 627, 629, 631, 633, 635, 637, 639, 641, 643, 645, 647, 649, 651, 653, 655, 657, 659, 661, 679, 681, 683, 685, 687, 689, 691, 693, 695, 697, 699, 701, 703, 705, 707, 709, 711, 713, 715, 733, 735, 737, 739, 741, 743, 745, 747, 749, 751, 753, 755, 757, 759, 761, 763, 765, 767, 769, 771, 773, 775, 777, 779, 781, 783, 785, 787, 802, 804, 806, 808, 810, 812, 814, 816, 818, 820, 822, 824, 826, 828, 830, 832, 834, 845, 847, 849, 893, 895, 910, 912, 914, 916, 918, 920, 922, 924, 926, 928, 930, 932, 934, 936, 938, 940, 942, 944, 946, 948, 950, 952, 966, 968, 970, 972, 974, 976, 978, 980, 982, 984, 986, 988, 990, 992, 994, 996, 998, 1000, 1002, 1004, 1006, 1008, 1010, 1012, 1014, 1016, 1018, 1020, 1022, 1024, 1026, 1028, 1030, 1032, 1034, 1036, 1038, 1040, 1042, 1044, 1046, 1048, 1050, 1052, 1054, 1064, 1066, 1068, 1070, 1119, 1173, 1175, 1177, 1179, 1181, 1183, 1185, 1187, 1189, 1191, 1193, 1195, 1197, 1199, 1201, 1203, 1217, 1219, 1221, 1223, 1225, 1227, 1229, 1231, 1233, 1235, 1261, 1263, 1265, 1267, 1269, 1271, 1273, 1332, 1334, 1336, 1338, 1340, 1347, 1359, 1361, 1363, 1365, 1367, 1369, 1371, 1373, 1394, 1396, 1398, 1400, 1402, 1404, 1406, 1409, 1411, 1413, 1415, 1417, 1419, 1421, 1423, 1425, 1427, 1429, 1431, 1433, 1435, 1437, 1439, 1441, 1443, 1445, 1447, 1449, 1451, 1453, 1455, 1457, 1459, 1461, 1463, 1465, 1467, 1469, 1471, 1473, 1475, 1477, 1479, 1488, 1490, 1492, 1504, 1506, 1508, 1510, 1512, 1514, 1516, 1518, 1520, 1522, 1524, 1526, 1547, 1549, 1551, 1553, 1555, 1557, 1559, 1561, 1563, 1565, 1567, 1569, 1571, 1573, 1575, 1577, 1579, 1584, 1586, 1588, 1590, 1592, 1594, 1596, 1598, 1600, 1612, 1614, 1616, 1637, 1639, 1641, 1643, 1645, 1647, 1649, 1651, 1653, 1655, 1657, 1659, 1661, 1663, 1665, 1667, 1669, 1671, 1673, 1675, 1677, 1685, 1687, 1689, 1691, 1710, 1712, 1714, 1716, 1718, 1720, 1722, 1724, 1726, 1728, 1730, 1732, 1734, 1736, 1738, 1740, 1742, 1744, 1746, 1748, 1750, 1752, 1754, 1756, 1758, 1760, 1762, 1764, 1766, 1768, 1789, 1791, 1793, 1795, 1797, 1799, 1801, 1803, 1805, 1807, 1812, 1814, 1816, 1818, 1820, 1822, 1824, 1826, 1828, 1830, 1832, 1834, 1836, 1838, 1840, 1842, 1844, 1846, 1848, 1850, 1852, 1854, 1856, 1858, 1860, 1862, 1864, 1898, 1900, 1902, 1904, 1906, 1934, 1936, 1938, 1940, 1942, 1944, 1946, 1948, 1950, 1952, 1954, 1956, 1958, 1960, 1962, 1964, 1966, 1968, 1970, 1976, 1978, 1980, 1982, 1984, 1986, 1988, 1990, 1992, 1994, 1996, 1998, 2000, 2002, 2004, 2006, 2008, 2010, 2012, 2014, 2016, 2018, 2020, 2022, 2024, 2026, 2035, 2037, 2039, 2041, 2043, 2045, 2047, 2049, 2053, 2055, 2057, 2059, 2061, 2063, 2065, 2067, 2069, 2071, 2073, 2075, 2077, 2079, 2081, 2090, 2092, 2094, 2096, 2098, 2100, 2108, 2110, 2112, 2114, 2116, 2118, 2120, 2122, 2124, 2126, 2128, 2147, 2149, 2151, 2153, 2155, 2157, 2159, 2203, 2205, 2207, 2209, 2211, 2213, 2215, 2217, 2243, 2245, 2247, 2249, 2251, 2253, 2255, 2257, 2259, 2261, 2263, 2265, 2267, 2269, 2271, 2273, 2275, 2277, 2279, 2281, 2283, 2285, 2287, 2304, 2306, 2308, 2310, 2312, 2314, 2319, 2321, 2323, 2325, 2327, 2329, 2331, 2333, 2335, 2337, 2339, 2341, 2343, 2345, 2347, 2349, 2351, 2353, 2355, 2357, 2359, 2361, 2363, 2365, 2367, 2369, 2371, 2382, 2384, 2386, 2388, 2390, 2392, 2394, 2396, 2398, 2400, 2402, 2404, 2406, 2416, 2418, 2445, 2447, 2449, 2451, 2453, 2455, 2457, 2459, 2461, 2463, 2465, 2467, 2469, 2471, 2479, 2481, 2483, 2485, 2487, 2489, 2491, 2493, 2495, 2504, 2506, 2508, 2510, 2512, 2514, 2516, 2518, 2520, 2522, 2524, 2526, 2528, 2530, 2532, 2550, 2552, 2554, 2556, 2558, 2560, 2562, 2564, 2566, 2571, 2573, 2575, 2577, 2579, 2581, 2583, 2585, 2587, 2589, 2591, 2593, 2595, 2597, 2599, 2601, 2603, 2605, 2607, 2609, 2611, 2613, 2615, 2617, 2619, 2621, 2623, 2630, 2632, 2634, 2636, 2638, 2640, 2642, 2644, 2646, 2648, 2650, 2652, 2654, 2656, 2658, 2660, 2662, 2664, 2708, 2710, 2712, 2714, 2716, 2718, 2720, 2763, 2765, 2767, 2769, 2771, 2773, 2775, 2797, 2799, 2801, 2803, 2805, 2807, 2809, 2811, 2813, 2815, 2817, 2819, 2821, 2823, 2825, 2827, 2829, 2831, 2833, 2835, 2837, 2839, 2841, 2858, 2860, 2862, 2864, 2866, 2885, 2887, 2889, 2891, 2893, 2895, 2897, 2899, 2901, 2903, 2905, 2907, 2909, 2911, 2913, 2915, 2917, 2919, 2921, 2923, 2925, 2927, 2929, 2931, 2933, 2935, 2937, 2939, 2941, 2943, 2945, 2947, 2949, 2951, 2953, 2955, 2957, 2959, 2961, 2963, 2965, 2967, 2969, 2971, 3025, 3027, 3029, 3031, 3033, 3035, 3054, 3056, 3058, 3060, 3062, 3064, 3066, 3068, 3070, 3072, 3074, 3076, 3078, 3080, 3082, 3084, 3086, 3088, 3090, 3092, 3094, 3096, 3100, 3102, 3104, 3106, 3108, 3110, 3112, 3114, 3116, 3118, 3120, 3122, 3124, 3126, 3128, 3147, 3149, 3151, 3153, 3155, 3157, 3159, 3161, 3163, 3166, 3168, 3170, 3172, 3174, 3176, 3178, 3180, 3182, 3184, 3186, 3188, 3190, 3192, 3242, 3244, 3246, 3248, 3250, 3252, 3254, 3256, 3258, 3260, 3262, 3264, 3266, 3268, 3270, 3288, 3290, 3292, 3294, 3296, 3298, 3300, 3302, 3304, 3306, 3308, 3310, 3312, 3367, 3373, 3375, 3377, 3379, 3381, 3383, 3385, 3387, 3389, 3391, 3393, 3395, 3397, 3399, 3401, 3405, 3449, 3462, 3464, 3466, 3468, 3470, 3472, 3474, 3476, 3478, 3480, 3482, 3484, 3486, 3488, 3490, 3492, 3494, 3496, 3498, 3517, 3519, 3521, 3523, 3525, 3527, 3529, 3531, 3533, 3535, 3537, 3552, 3554, 3556, 3558, 3560, 3562, 3564, 3566, 3568, 3570, 3572, 3574, 3576, 3578, 3580, 3582, 3586, 3588, 3590, 3592, 3594, 3614, 3632, 3634, 3636, 3638, 3640, 3642, 3644, 3646, 3648, 3650, 3654, 3656, 3658, 3660, 3662, 3664, 3666, 3668, 3670, 3672, 3674, 3719, 3721, 3723, 3725, 3727, 3729, 3731, 3733, 3735, 3737, 3739, 3761, 3763, 3765, 3767, 3769, 3771, 3773, 3775, 3777, 3779, 3781, 3783, 3785, 3787, 3831, 3833, 3835, 3837, 3854, 3856, 3858, 3860, 3862, 3864, 3866, 3868, 3870, 3872, 3874, 3876, 3878, 3880, 3882, 3884, 3886, 3888, 3890, 3892, 3894, 3920, 3922, 3924, 3926, 3928, 3930, 3932, 3934, 3936, 3938, 3953, 3955, 4006, 4012, 4014, 4016, 4018, 4020, 4022, 4024, 4026, 4028, 4030, 4032, 4034, 4036, 4038, 4040, 4065, 4067, 4069, 4071, 4073, 4075, 4077, 4079, 4081, 4083, 4085, 4087, 4089, 4091, 4093, 4095, 4097, 4099, 4101, 4103, 4105, 4107, 4111, 4113, 4115, 4117, 4119, 4121, 4123, 4125, 4127, 4129, 4131, 4133, 4135, 4139, 4141, 4143, 4145, 4147, 4149, 4151, 4153, 4155, 4157, 4159, 4161, 4163, 4165, 4167, 4176, 4178, 4180, 4182, 4184, 4186, 4188, 4190, 4192, 4194, 4196, 4198, 4200, 4223, 4225, 4227, 4229, 4231, 4233, 4235, 4237, 4239, 4241, 4243, 4245, 4260, 4262, 4291, 4293, 4295, 4297, 4299, 4301, 4303, 4305, 4307, 4309, 4311, 4313, 4315, 4317, 4319, 4321, 4323, 4325, 4337, 4339, 4341, 4343, 4345, 4347, 4349, 4351, 4353, 4355, 4375, 4377, 4379, 4381, 4383, 4385, 4387, 4389, 4391, 4393, 4395, 4397, 4399, 4401, 4403, 4405, 4407, 4409, 4411, 4413, 4415, 4417, 4419, 4421, 4423, 4432, 4434, 4436, 4438, 4440, 4442, 4444, 4446, 4448, 4457, 4459, 4461, 4463, 4465, 4467, 4469, 4471, 4473, 4475, 4477, 4479, 4481, 4483, 4485, 4487, 4489, 4491, 4493, 4495, 4512, 4514, 4516, 4518, 4520, 4522, 4524, 4526, 4528, 4530, 4532, 4534, 4536, 4538, 4540, 4542, 4544, 4546, 4548, 4550, 4552, 4554, 4556, 4558, 4560, 4562, 4564, 4566, 4568, 4570, 4572, 4574, 4576, 4578, 4580, 4582, 4584, 4586, 4588, 4590, 4592, 4594, 4596, 4598, 4600, 4602, 4604, 4606, 4608, 4610, 4654, 4656, 4658, 4660, 4662, 4664, 4666, 4668, 4670, 4672, 4674, 4676, 4678, 4680, 4691, 4693, 4733, 4735, 4737, 4739, 4758, 4760, 4762, 4764, 4766, 4768, 4770, 4772, 4774, 4776, 4778, 4780, 4782, 4784, 4786, 4788, 4790, 4792, 4794, 4796, 4798, 4800, 4802, 4804, 4806, 4808, 4810, 4812, 4814, 4833, 4835, 4837, 4839, 4841, 4843, 4845, 4847, 4849, 4851, 4853, 4855, 4857, 4859, 4861, 4863, 4865, 4867, 4869, 4871, 4873, 4875, 4877, 4879, 4881, 4883, 4885, 4887, 4889, 4891, 4899, 4901, 4903, 4905, 4907, 4909, 4911, 4913, 4915, 4917, 4919, 4921, 4923, 4925, 4927, 4929, 4931, 4933, 4935, 4937, 4956, 4958, 4960, 4962, 4964, 4966, 4968, 4970, 4972, 4994, 4996, 4998, 5000, 5002, 5012, 5014, 5016, 5018, 5020, 5022, 5024, 5026, 5028, 5030, 5032, 5034, 5036, 5038, 5040, 5042, 5044, 5046, 5048, 5050, 5052, 5054, 5056, 5058, 5060, 5062, 5064, 5066, 5068, 5070, 5072, 5074, 5076, 5078, 5123, 5125, 5127, 5129, 5131, 5133, 5135, 5137, 5139, 5165, 5167, 5169, 5171, 5173, 5175, 5177, 5179, 5181, 5183, 5201, 5203, 5205, 5207, 5209, 5211, 5222, 5224, 5226, 5228, 5230, 5232, 5234, 5236, 5238, 5240, 5242, 5244, 5246, 5248, 5261, 5263, 5265, 5267, 5269, 5271, 5273, 5275, 5277, 5279, 5281, 5283, 5285, 5287, 5289, 5291, 5293, 5295, 5297, 5299, 5301, 5303, 5305, 5307, 5309, 5311, 5313, 5315, 5317, 5319, 5326, 5328, 5330, 5332, 5334, 5336, 5338, 5340, 5342, 5344, 5346, 5348, 5350, 5352, 5354, 5356, 5358, 5360, 5362, 5364, 5366, 5368, 5370, 5372, 5374, 5376, 5378, 5380, 5382, 5384, 5436, 5438, 5440, 5442, 5444, 5446, 5448, 5450, 5452, 5454, 5456, 5458, 5460, 5462, 5464, 5466, 5468, 5470, 5472, 5474, 5476, 5478, 5507, 5515, 5517, 5519, 5521, 5523, 5525, 5527, 5529, 5531, 5533, 5535, 5537, 5539, 5541, 5543, 5545, 5547, 5549, 5551, 5553, 5555, 5557, 5559, 5561, 5563, 5565, 5567, 5569, 5571, 5573, 5575, 5589, 5591, 5593, 5595, 5597, 5599, 5601, 5603, 5605, 5607, 5634, 5636, 5638, 5640, 5642, 5644, 5646, 5648, 5650, 5652, 5654, 5656, 5658, 5660, 5662, 5664, 5666, 5693, 5695, 5697, 5699, 5701, 5703, 5705, 5707, 5709, 5711, 5713, 5715, 5717, 5719, 5721, 5723, 5725, 5727, 5729, 5731, 5733, 5735, 5737, 5739, 5741, 5743, 5745, 5747, 5749, 5751, 5753, 5755, 5757, 5759, 5761, 5763, 5765, 5767, 5769, 5771, 5773, 5775, 5777, 5779, 5782, 5784, 5786, 5788, 5790, 5792, 5794, 5796, 5798, 5800, 5802, 5804, 5806, 5808, 5810, 5825, 5827, 5829, 5831, 5833, 5835, 5837, 5839, 5841, 5843, 5855, 5857, 5859, 5861, 5863, 5865, 5867, 5869, 5871, 5873, 5900, 5902, 5904, 5906, 5908, 5910, 5912, 5914, 5916, 5918, 5920, 5922, 5924, 5926, 5928, 5930, 5932, 5934, 5936, 5938, 5940, 5942, 5944, 5946, 5948, 5950, 5952, 5954, 5956, 5958, 5960, 5962, 5964, 5966, 5968, 5970, 5972, 5979, 5981, 5983, 5985, 5987, 5989, 5991, 5993, 5995, 5997, 5999, 6001, 6003, 6005, 6007, 6009, 6011, 6013, 6015, 6017, 6019, 6021, 6023, 6025, 6027, 6029, 6031, 6033, 6035, 6037, 6063, 6065, 6067, 6069, 6071, 6073, 6075, 6077, 6079, 6081, 6083, 6085, 6087, 6089, 6091, 6093, 6099, 6101, 6103, 6105, 6107, 6109, 6111, 6113, 6115, 6117, 6119, 6121, 6123, 6125, 6127, 6129, 6131, 6133, 6135, 6137, 6139, 6141, 6143, 6145, 6147, 6159, 6161, 6163, 6165, 6167, 6169, 6171, 6173, 6175, 6181, 6183, 6185, 6187, 6189, 6191, 6193, 6209, 6211, 6213, 6215, 6227, 6229, 6231, 6233, 6235, 6237, 6239, 6241, 6243, 6245, 6247, 6259, 6261, 6263, 6265, 6267, 6269, 6285, 6287, 6289, 6291, 6303, 6305, 6307, 6309, 6311, 6313, 6315, 6317, 6319, 6321, 6323, 6350, 6352, 6354, 6356, 6358, 6360, 6362, 6364, 6366, 6368, 6370, 6372, 6374, 6376, 6378, 6380, 6382, 6384, 6386, 6388, 6390, 6392, 6394, 6396, 6398, 6400, 6412, 6414, 6416, 6418, 6420, 6422, 6424, 6426, 6428, 6430, 6457, 6459, 6461, 6463, 6465, 6467, 6469, 6471, 6473, 6475, 6477, 6479, 6481, 6483, 6485, 6487, 6489, 6491, 6493, 6495, 6497, 6499, 6501, 6503, 6505, 6507, 6509, 6511, 6513, 6515, 6517, 6519, 6521, 6523, 6525, 6527, 6529, 6531, 6533, 6584, 6586, 6588, 6590, 6592, 6594, 6596, 6598, 6600, 6618, 6620, 6622, 6624, 6626, 6628, 6630, 6632, 6634, 6636, 6638, 6640, 6642, 6644, 6646, 6656, 6658, 6660, 6662, 6664, 6666, 6668, 6670, 6672, 6674, 6676, 6678, 6680, 6682, 6684, 6686, 6688, 6690, 6692, 6694, 6696, 6698, 6713, 6715, 6717, 6719, 6721, 6723, 6725, 6727, 6729, 6731, 6733, 6735, 6737, 6739, 6741, 6743, 6745, 6747, 6758, 6760, 6762, 6764, 6766, 6768, 6770, 6773, 6775, 6777, 6779, 6781, 6783, 6785, 6787, 6789, 6791, 6807, 6826, 6828, 6830, 6832, 6834, 6842, 6844, 6846, 6848, 6850, 6852, 6854, 6856, 6858, 6860, 6862, 6864, 6866, 6868, 6870, 6872, 6892, 6894, 6896, 6898, 6900, 6902, 6904, 6906, 6908, 6910, 6912, 6914, 6916, 6918, 6920, 6922, 6924, 6926, 6928, 6930, 6932, 6934, 6936, 6945, 6947, 6949, 6951, 6953, 6955, 6957, 6959, 6961, 6963, 6965, 6967, 6969, 6971, 7025, 7027, 7029, 7031, 7033, 7035, 7062, 7064, 7066, 7068, 7070, 7072, 7074, 7076, 7078, 7080, 7082, 7084, 7086, 7088, 7090, 7092, 7094, 7096, 7098, 7100, 7102, 7104, 7106, 7108, 7122, 7124, 7126, 7128, 7130, 7132, 7134, 7136, 7138, 7140, 7142, 7182, 7184, 7186, 7194, 7196, 7198, 7200, 7232, 7234, 7236, 7238, 7240, 7242, 7244, 7246, 7248, 7250, 7252, 7305, 7307, 7309, 7311, 7313, 7315, 7332, 7334, 7336, 7338, 7340, 7342, 7344, 7346, 7405, 7407, 7409, 7435, 7437, 7439, 7441, 7443, 7445, 7447, 7449, 7451, 7453, 7514, 7516, 7518, 7520, 7522, 7540, 7547, 7549, 7551, 7553, 7555, 7557, 7559, 7561, 7563, 7565, 7567, 7569, 7571, 7573, 7575, 7577, 7579, 7581, 7583, 7585, 7587, 7589, 7591, 7593, 7595, 7597, 7599, 7601, 7603, 7605, 7607, 7609, 7611, 7613, 7615, 7617, 7619, 7653, 7659, 7661, 7663, 7665, 7667, 7669, 7671, 7673, 7675, 7677, 7679, 7681, 7683, 7685, 7687, 7706, 7708, 7710, 7712, 7714, 7716, 7718, 7720, 7722, 7724, 7726, 7728, 7730, 7732, 7734, 7736, 7738, 7740, 7742, 7744, 7746, 7750, 7752, 7754, 7756, 7758, 7760, 7762, 7764, 7766, 7768, 7770, 7772, 7774, 7778, 7780, 7782, 7784, 7786, 7788, 7790, 7792, 7794, 7796, 7798, 7800, 7802, 7804, 7806, 7815, 7817, 7819, 7821, 7823, 7825, 7827, 7829, 7831, 7846, 7848, 7877, 7879, 7881, 7883, 7885, 7887, 7889, 7891, 7893, 7895, 7897, 7899, 7901, 7903, 7905, 7907, 7909, 7911, 7923, 7925, 7927, 7929, 7931, 7933, 7935, 7937, 7939, 7941, 7961, 7963, 7965, 7967, 7969, 7971, 7973, 7975, 7977, 7979, 7981, 7983, 7985, 7987, 7989, 7991, 7993, 7995, 7997, 7999, 8001, 8003, 8005, 8007, 8009, 8018, 8020, 8022, 8024, 8026, 8028, 8030, 8032, 8034, 8043, 8045, 8047, 8049, 8051, 8053, 8055, 8057, 8059, 8066, 8068, 8070, 8072, 8074, 8076, 8078, 8080, 8082, 8084, 8086, 8088, 8090, 8092, 8094, 8096, 8098, 8100, 8102, 8104, 8106, 8108, 8110, 8117, 8119, 8121, 8123, 8125, 8127, 8129, 8131, 8133, 8135, 8137, 8139, 8141, 8143, 8190, 8224, 8226, 8228, 8230, 8232, 8234, 8236, 8238, 8240, 8242, 8244, 8246, 8248, 8250, 8252, 8254, 8256, 8320, 8322, 8324, 8326, 8328, 8330, 8332, 8334, 8336, 8338, 8340, 8342, 8344, 8346, 8348, 8350, 8352, 8354, 8373, 8375, 8377, 8379, 8381, 8383, 8385, 8387, 8389, 8391, 8393, 8395, 8397, 8399, 8401, 8403, 8405, 8407, 8409, 8411, 8413, 8415, 8417, 8419, 8421, 8423, 8425, 8427, 8429, 8431, 8433, 8435, 8437, 8439, 8441, 8443, 8445, 8447, 8449, 8451, 8453, 8455, 8457, 8459, 8461, 8463, 8465, 8467, 8469, 8471, 8473, 8475, 8477, 8482, 8484, 8486, 8488, 8490, 8492, 8520, 8522, 8524, 8526, 8528, 8530, 8532, 8534, 8536, 8538, 8540, 8542, 8544, 8546, 8562, 8564, 8566, 8568, 8580, 8582, 8584, 8586, 8588, 8590, 8592, 8594, 8596, 8598, 8600, 8612, 8614, 8616, 8618, 8620, 8622, 8624, 8626, 8628, 8630, 8632, 8634, 8636, 8638, 8640, 8642, 8644, 8660, 8718, 8720, 8722, 8734, 8736, 8738, 8740, 8742, 8744, 8751, 8753, 8755, 8757, 8759, 8786, 8788, 8790, 8792, 8794, 8796, 8798, 8800, 8802, 8804, 8806, 8808, 8810, 8812, 8814, 8816, 8818, 8820, 8822, 8824, 8826, 8828, 8830, 8832, 8834, 8836, 8838, 8896, 8898, 8900, 8902, 8904, 8916, 8918, 8920, 8922, 8924, 8926, 8928, 8930, 8932, 8977, 8979, 8981, 8983, 8985, 8987, 8989, 8991, 9020, 9022, 9024, 9026, 9028, 9030, 9051, 9053, 9055, 9081, 9083, 9085, 9087, 9089, 9091, 9093, 9095, 9097, 9099, 9101, 9103, 9109, 9113, 9115, 9117, 9119, 9121, 9123, 9125, 9127, 9129, 9142, 9144, 9146, 9148, 9150, 9152, 9154, 9156, 9158, 9160, 9162, 9164, 9166, 9168, 9170, 9172, 9174, 9201, 9203, 9205, 9207, 9209, 9211, 9213, 9215, 9217, 9219, 9221, 9223, 9225, 9227, 9229, 9231, 9233, 9235, 9237, 9239, 9241, 9243, 9245, 9247, 9249, 9251, 9253, 9255, 9257, 9259, 9261, 9280, 9282, 9284, 9286, 9288, 9290, 9292, 9314, 9331, 9333, 9335, 9337, 9339, 9393, 9395, 9397, 9399, 9401, 9403, 9407, 9409, 9411, 9413, 9415, 9417, 9435, 9437, 9439, 9441, 9443, 9445, 9447, 9449, 9451, 9453, 9455, 9457, 9459, 9461, 9463, 9465, 9467, 9469, 9471, 9478, 9480, 9482, 9498, 9517, 9519, 9521, 9523, 9525, 9533, 9535, 9537, 9539, 9541, 9543, 9545, 9547, 9549, 9551, 9553, 9555, 9557, 9559, 9561, 9563, 9565, 9567, 9590, 9592, 9594, 9596, 9598, 9609, 9611, 9613, 9615, 9617, 9619, 9671, 9673, 9675, 9677, 9679, 9690, 9692, 9694, 9696, 9698, 9700, 9702, 9704, 9706, 9708, 9710, 9712, 9714, 9716, 9718, 9720, 9722, 9724, 9726, 9728, 9730, 9732, 9734, 9736, 9738, 9740, 9742, 9744, 9746, 9748, 9750, 9752, 9754, 9756, 9758, 9766, 9768, 9770, 9772, 9774, 9776, 9778, 9780, 9782, 9784, 9786, 9788, 9790, 9792, 9794, 9796, 9798, 9800, 9802, 9804, 9806, 9808, 9816, 9818, 9820, 9822, 9824, 9826, 9828, 9830, 9832, 9834, 9836, 9838, 9840, 9842, 9844, 9846, 9848, 9850, 9852, 9854, 9856, 9907, 9909, 9911, 9913, 9915, 9917, 9919, 9921, 9923, 9925, 9927, 9929, 9931, 9933, 9935, 9937, 9939, 9941, 9993, 9995, 9997, 9999, 10001, 10003, 10005, 10007, 10009, 10011, 10013, 10015, 10017, 10068, 10070, 10085, 10087, 10112, 10114, 10116, 10118, 10166, 10168, 10170, 10172, 10192, 10194, 10196, 10198, 10200, 10202, 10204, 10206, 10208, 10210, 10212, 10214, 10216, 10218, 10220, 10222, 10224, 10226, 10228, 10230, 10232, 10234, 10236, 10238, 10240, 10242, 10244, 10246, 10248, 10250, 10252, 10254, 10256, 10258, 10260, 10262, 10264, 10266, 10268, 10270, 10272, 10274, 10276, 10278, 10280, 10298, 10300, 10302, 10304, 10306, 10308, 10310, 10312, 10314, 10316, 10318, 10320, 10322, 10324, 10326, 10328, 10330, 10345, 10347, 10349, 10351, 10353, 10355, 10357, 10359, 10361, 10363, 10417, 10419, 10421, 10423, 10425, 10427, 10429, 10431, 10433, 10435, 10437, 10439, 10441, 10443, 10461, 10463, 10465, 10467, 10469, 10471, 10473, 10475, 10477, 10479, 10481, 10483, 10485, 10487, 10489, 10491, 10536, 10538, 10540, 10542, 10544, 10546, 10548, 10550, 10552, 10578, 10580, 10582, 10584, 10586, 10588, 10590, 10592, 10594, 10598, 10600, 10602, 10604, 10606, 10608, 10610, 10612, 10614, 10616, 10618, 10620, 10622, 10624, 10626, 10628, 10630, 10632, 10634, 10636, 10638, 10640, 10642, 10666, 10668, 10670, 10672, 10674, 10676, 10678, 10680, 10682, 10684, 10748, 10750, 10752, 10754, 10756, 10758, 10760, 10762, 10764, 10766, 10768, 10825, 10827, 10829, 10831, 10833, 10835, 10848, 10860, 10862, 10864, 10866, 10868, 10870, 10872, 10874, 10876, 10878, 10895, 10906, 10908, 10910, 10912, 10914, 10919, 10980, 10982, 10984, 10986, 10988, 10990, 10992, 10994, 10996, 10998, 11000, 11065, 11067, 11069, 11071, 11073, 11075, 11115, 11124, 11126, 11128, 11130, 11132, 11134, 11136, 11138, 11140, 11142, 11203, 11205, 11248, 11250, 11256, 11258, 11260, 11262, 11264, 11266, 11268, 11270, 11281, 11283, 11285, 11287, 11289, 11291, 11346, 11348, 11350, 11352, 11354, 11356, 11358, 11360, 11362, 11364, 11366, 11368, 11370, 11372, 11416, 11418, 11420, 11422, 11424, 11426, 11450, 11452, 11454, 11456, 11458, 11460, 11504, 11506, 11508, 11510, 11512, 11514, 11516, 11518, 11520, 11528, 11530, 11532, 11534, 11536, 11538, 11540, 11542, 11544, 11546, 11587, 11589, 11591, 11593, 11610, 11612, 11630, 11632, 11634, 11636, 11638, 11640, 11642, 11644, 11646, 11648, 11650, 11652, 11654, 11656, 11658, 11660, 11662, 11664, 11666, 11710, 11712, 11714, 11717, 11719, 11721, 11723, 11725, 11727, 11773, 11775, 11777, 11779, 11781, 11840, 11842, 11844, 11846, 11848, 11860, 11862, 11864, 11866, 11868, 11870, 11872, 11874, 11876, 11895, 11897, 11899, 11901, 11945, 11947, 11949, 11951, 11953, 11955, 11957, 11959, 11961, 11963, 11965, 11967, 11969, 11971, 11995, 11997, 11999, 12001, 12003, 12005, 12007, 12009, 12011, 12013, 12035, 12057, 12059, 12061, 12063, 12065, 12067, 12069, 12071, 12089, 12091, 12093, 12095, 12097, 12099, 12101, 12103, 12105, 12107, 12109, 12111, 12113, 12118, 12120, 12122, 12124, 12126, 12128, 12130, 12132, 12134, 12136, 12138, 12140, 12142, 12144, 12146, 12165, 12167, 12169, 12171, 12173, 12175, 12177, 12179, 12181, 12183, 12185, 12187, 12189, 12191, 12193, 12195, 12197, 12199, 12201, 12203, 12205, 12207, 12209, 12211, 12213, 12229, 12242, 12244, 12254, 12256, 12258, 12260, 12262, 12264, 12266, 12268, 12270, 12272, 12274, 12276, 12278, 12280, 12282, 12284, 12286, 12288, 12290, 12292, 12294, 12351, 12353, 12355, 12357, 12359, 12361, 12363, 12365, 12367, 12369, 12371, 12373, 12375, 12377, 12379, 12381, 12385, 12389, 12391, 12393, 12395, 12397, 12399, 12401, 12403, 12415, 12471, 12473, 12475, 12477, 12479, 12481, 12483, 12493, 12495, 12497, 12556, 12558, 12560, 12562, 12564, 12566, 12568, 12570, 12622, 12662, 12664, 12666, 12668, 12670, 12730, 12732, 12734, 12736, 12738, 12778, 12780, 12782, 12784, 12786, 12843, 12845, 12847, 12849, 12851, 12853, 12855, 12857, 12859, 12861, 12901, 12903, 12905, 12907, 12909, 12969, 12971, 12973, 12975, 12977, 12979, 12981, 12983, 12985, 12987, 12989, 12991, 12993, 12995, 12997, 12999, 13001, 13003, 13005, 13045, 13047, 13049, 13051, 13053, 13110, 13112, 13114, 13116, 13118, 13120, 13122, 13124, 13126, 13128, 13130, 13132, 13151, 13153, 13155, 13157, 13159, 13161, 13163, 13177, 13179, 13181, 13183, 13185, 13187, 13189, 13233, 13235, 13237, 13284, 13286, 13288, 13290, 13292, 13294, 13297, 13299, 13343, 13345, 13347, 13349, 13393, 13395, 13451, 13453, 13479, 13496, 13498, 13500, 13502, 13504, 13562, 13564, 13566, 13568, 13570, 13572, 13574, 13576, 13578, 13600, 13602, 13604, 13606, 13608, 13610, 13612, 13614, 13616, 13628, 13630, 13632, 13634, 13636, 13658, 13662, 13664, 13666, 13668, 13670, 13672, 13674, 13676, 13678, 13680, 13682, 13684, 13686, 13688, 13746, 13748, 13750, 13752, 13754, 13756, 13768, 13770, 13772, 13774, 13776, 13778, 13780, 13782, 13784, 13786, 13788, 13790, 13792, 13794, 13796, 13798, 13806, 13808, 13810, 13812, 13814, 13866, 13868, 13870, 13872, 13874, 13876, 13878, 13936, 13938, 13989, 13991, 13993, 13995, 14000, 14002, 14004, 14006, 14008, 14016, 14018, 14020, 14022, 14024, 14026, 14028, 14030, 14032, 14034, 14054, 14056, 14058, 14060, 14062, 14064, 14066, 14068, 14070, 14072, 14074, 14076, 14078, 14080, 14082, 14084, 14086, 14088, 14090, 14092, 14094, 14096, 14098, 14100, 14102, 14104, 14106, 14108, 14110, 14112, 14172, 14174, 14176, 14178, 14184, 14186, 14188, 14232, 14288, 14290, 14295, 14297, 14356, 14358, 14409, 14411, 14413, 14415, 14428, 14430, 14432, 14434, 14436, 14438, 14440, 14442, 14446, 14489, 14491, 14493, 14495, 14497, 14499, 14501, 14522, 14524, 14526, 14528, 14530, 14532, 14534, 14550, 14552, 14554, 14556, 14558, 14560, 14562, 14582, 14584, 14586, 14602, 14646, 14648, 14692, 14694, 14696, 14698, 14700, 14702, 14723, 14725, 14727, 14729, 14737, 14739, 14741, 14746, 14748, 14750, 14752, 14754, 14756, 14758, 14760, 14815, 14817, 14875, 14877, 14879, 14881, 14883, 14885, 14907, 14909, 14911, 14913, 14915, 14917, 14919, 14921, 14923, 14925, 14927, 14950, 14952, 14954, 15006, 15008, 15010, 15030, 15032, 15037, 15039, 15041, 15043, 15045, 15047, 15049, 15051, 15053, 15055, 15057, 15059, 15061, 15063, 15065, 15067, 15069, 15071, 15073, 15075, 15077, 15079, 15081, 15083, 15085, 15093, 15095, 15097, 15099, 15101, 15103, 15105, 15107, 15109, 15111, 15113, 15115, 15117, 15119, 15121, 15123, 15131, 15133, 15135, 15137, 15139, 15141, 15143, 15145, 15147, 15149, 15151, 15153, 15155, 15157, 15159, 15218, 15220, 15222, 15224, 15226, 15228, 15230, 15244, 15246, 15248, 15250, 15252, 15254, 15256, 15258, 15260, 15262, 15264, 15266, 15327, 15329, 15332, 15334, 15336, 15338, 15340, 15342, 15344, 15346, 15348, 15374, 15376, 15378, 15380, 15382, 15384, 15438, 15451, 15453, 15455, 15457, 15459, 15461, 15463, 15465, 15467, 15469, 15471, 15473, 15481, 15483, 15485, 15487, 15489, 15491, 15493, 15495, 15497, 15508, 15510, 15512, 15514, 15516, 15518, 15520, 15522, 15530, 15532, 15534, 15557, 15559, 15617, 15619, 15621, 15679, 15681, 15683, 15685, 15687, 15689, 15691, 15693, 15695, 15697, 15699, 15701, 15703, 15705, 15707, 15761, 15763, 15765, 15767, 15769, 15771, 15773, 15775, 15777, 15779, 15781, 15783, 15785, 15787, 15789, 15791, 15793, 15798, 15800, 15802, 15804, 15806, 15808, 15810, 15823, 15825, 15827, 15829, 15831, 15833, 15835, 15893, 15895, 15938, 15940, 15942, 15944, 15946, 15948, 15950, 15952, 15954, 15956, 15958, 15960, 15962, 15964, 15966, 15968, 15970, 15972, 15974, 15976, 15978, 15980, 15982, 15984, 15986, 15988, 15990, 15992, 15994, 15996, 15998, 16000, 16009, 16011, 16013, 16015, 16017, 16019, 16021, 16023, 16025, 16027, 16029, 16031, 16033, 16035, 16037, 16039, 16041, 16043, 16045, 16047, 16049, 16051, 16059, 16061, 16063, 16065, 16067, 16069, 16071, 16073, 16075, 16077, 16080, 16082, 16084, 16086, 16088, 16090, 16092, 16097, 16099, 16101, 16103, 16105, 16107, 16109, 16128, 16137, 16139, 16141, 16143, 16145, 16147, 16149, 16151, 16153, 16169, 16171, 16173, 16175, 16177, 16222, 16224, 16233, 16235, 16237, 16239, 16241, 16243, 16245, 16247, 16249, 16251, 16253, 16255, 16257, 16265, 16267, 16269, 16271, 16273, 16275, 16277, 16279, 16281, 16335, 16380, 16382, 16384, 16386, 16388, 16390, 16408, 16410, 16412, 16414, 16416, 16418, 16420, 16422, 16448, 16450, 16452, 16454, 16456, 16458, 16500, 16502, 16504, 16506, 16508, 16510, 16512, 16514, 16516, 16518, 16566, 16568, 16570, 16572, 16574, 16632, 16634, 16636, 16638, 16681, 16683, 16685, 16687, 16745, 16747, 16749, 16751, 16753, 16755, 16757, 16759, 16802, 16804, 16806, 16808, 16828, 16830, 16832, 16834, 16836, 16838, 16899, 16901, 16903, 16905, 16907, 16909, 16921, 16924, 16926, 16928, 16942, 16944, 16946, 16948, 16959, 16961, 16963, 16965, 16967, 16969, 16989, 16991, 17049, 17051, 17053, 17055, 17057, 17059, 17061, 17122, 17124, 17126, 17128, 17130, 17151, 17159, 17161, 17163, 17165, 17167, 17169, 17171, 17173, 17178, 17180, 17182, 17184, 17186, 17188, 17195, 17197, 17199, 17201, 17203, 17205, 17207, 17209, 17219, 17221, 17223, 17225, 17227, 17229, 17231, 17233, 17244, 17246, 17248, 17250, 17252, 17254, 17256, 17258, 17260, 17291, 17293, 17295, 17297, 17299, 17301, 17303, 17311, 17313, 17315, 17317, 17319, 17321, 17323, 17325, 17334, 17336, 17343, 17345, 17347, 17349, 17364, 17366, 17368, 17370, 17372, 17374, 17386, 17388, 17390, 17392, 17394, 17396, 17398, 17400, 17402, 17404, 17406, 17408, 17410, 17412, 17414, 17416, 17418, 17420, 17440, 17442, 17444, 17446, 17448, 17450, 17452, 17454, 17456, 17458, 17460, 17462, 17464, 17466, 17468, 17470, 17472, 17474, 17476, 17478, 17489, 17491, 17493, 17495, 17497, 17502, 17504, 17506, 17508, 17510, 17512, 17514, 17516, 17518, 17520, 17522, 17524, 17526, 17528, 17530, 17551, 17553, 17555, 17557, 17559, 17561, 17563, 17565, 17567, 17569, 17571, 17573, 17577, 17579, 17581, 17593, 17595, 17597, 17599, 17660, 17662, 17664, 17666, 17668, 17670, 17672, 17674, 17676, 17692, 17710, 17718, 17726, 17728, 17730, 17732, 17734, 17738, 17740, 17742, 17748, 17750, 17752, 17754, 17756, 17760, 17762, 17764, 17766, 17768, 17770, 17772, 17774, 17776, 17778, 17780, 17782, 17784, 17835, 17886, 17888, 17903, 17905, 17907, 17909, 17911, 17913, 17915, 17958, 17960, 17962, 17964, 17966, 17968, 17970, 17972, 17990, 17992, 17994, 17996, 17998, 18000, 18002, 18004, 18062, 18064, 18122, 18124, 18126, 18128, 18130, 18132, 18134, 18185, 18187, 18189, 18191, 18193, 18247, 18298, 18300, 18344, 18346, 18348, 18350, 18352, 18398, 18400, 18402, 18404, 18406, 18408, 18410, 18412, 18414, 18463, 18465, 18467, 18469, 18517, 18519, 18521, 18576, 18578, 18580, 18582, 18584, 18640, 18696, 18698, 18700, 18702, 18704, 18706, 18708, 18710, 18712, 18714, 18716, 18718, 18726, 18728, 18730, 18732, 18783, 18785, 18787, 18795, 18797, 18799, 18801, 18803, 18805, 18807, 18809, 18811, 18813, 18815, 18817, 18819, 18821, 18823, 18825, 18845, 18849, 18851, 18853, 18855, 18857, 18859, 18861, 18863, 18865, 18867, 18869, 18871, 18878, 18880, 18882, 18884, 18886, 18888, 18890, 18892, 18894, 18896, 18898, 18900, 18902, 18904, 18906, 18908, 18910, 18921, 18923, 18925, 18927, 18929, 18931, 18933, 18935, 18937, 18939, 18997, 18999, 19001, 19003, 19014, 19016, 19018, 19020, 19022, 19024, 19026, 19028, 19030, 19032, 19034, 19050, 19052, 19054, 19056, 19118, 19120, 19122, 19124, 19126, 19128, 19130, 19132, 19134, 19136, 19138, 19140, 19159, 19161, 19163, 19165, 19225, 19227, 19229, 19231, 19233, 19235, 19237, 19239, 19241, 19243, 19245, 19247, 19249, 19251, 19253, 19273, 19275, 19277, 19279, 19281, 19283, 19285, 19287, 19289, 19291, 19293, 19312, 19314, 19316, 19318, 19320, 19322, 19324, 19326, 19328, 19336, 19338, 19340, 19342, 19344, 19346, 19348, 19350, 19352, 19354, 19356, 19358, 19360, 19362, 19364, 19366, 19368, 19370, 19372, 19374, 19376, 19378, 19380, 19382, 19384, 19386, 19405, 19407, 19409, 19411, 19413, 19415, 19417, 19419, 19421, 19423, 19425, 19441, 19443, 19445, 19447, 19449, 19451, 19453, 19455, 19457, 19479, 19481, 19483, 19485, 19487, 19551, 19553, 19555, 19557, 19580, 19582, 19584, 19586, 19588, 19602, 19604, 19606, 19608, 19610, 19612, 19614, 19668, 19675, 19677, 19679, 19681, 19683, 19685, 19687, 19689, 19691, 19693, 19695, 19697, 19699, 19701, 19703, 19705, 19707, 19724, 19743, 19745, 19747, 19760, 19762, 19764, 19766, 19768, 19770, 19772, 19774, 19776, 19778, 19780, 19782, 19784, 19786, 19788, 19790, 19792, 19851, 19853, 19855, 19857, 19859, 19861, 19863, 19922, 19924, 19926, 19928, 19930, 19932, 19934, 19936, 19991, 19993, 19995, 19997, 20010, 20012, 20014, 20016, 20018, 20020, 20022, 20024, 20026, 20028, 20030, 20032, 20034, 20036, 20038, 20040, 20042, 20044, 20063, 20065, 20067, 20069, 20071, 20073, 20075, 20077, 20079, 20081, 20083, 20085, 20087, 20089, 20091, 20093, 20095, 20097, 20105, 20107, 20109, 20111, 20172, 20174, 20176, 20178, 20180, 20241, 20243, 20245, 20293, 20295, 20297, 20299, 20301, 20305, 20307, 20309, 20311, 20313, 20333, 20351, 20353, 20355, 20357, 20359, 20361, 20363, 20365, 20367, 20369, 20371, 20373, 20375, 20377, 20381, 20383, 20385, 20387, 20389, 20391, 20393, 20395, 20397, 20399, 20401, 20421, 20423, 20425, 20427, 20488, 20490, 20512, 20514, 20516, 20518, 20520, 20522, 20524, 20526, 20528, 20530, 20532, 20534, 20536, 20538, 20540, 20542, 20544, 20546, 20548, 20550, 20552, 20554, 20556, 20558, 20560, 20615, 20636, 20638, 20640, 20642, 20644, 20646, 20648, 20650, 20652, 20654, 20656, 20658, 20660, 20662, 20708, 20710, 20712, 20714, 20716, 20735, 20737, 20739, 20741, 20743, 20745, 20747, 20749, 20751, 20753, 20755, 20757, 20759, 20761, 20763, 20765, 20767, 20769, 20771, 20773, 20824, 20826, 20828, 20830, 20832, 20834, 20836, 20838, 20897, 20942, 20944, 20946, 20948, 20950, 20952, 20954, 20956, 20958, 20960, 20962, 20964, 20966, 20968, 20970, 20972, 21016, 21018, 21020, 21022, 21080, 21082, 21101, 21103, 21105, 21107, 21109, 21111, 21113, 21125, 21127, 21135, 21137, 21139, 21155, 21157, 21159, 21178, 21180, 21182, 21208, 21210, 21212, 21214, 21216, 21223, 21225, 21227, 21229, 21231, 21233, 21235, 21237, 21239, 21257, 21259, 21261, 21263, 21265, 21322, 21324, 21343, 21345, 21347, 21349, 21351, 21353, 21355, 21357, 21359, 21361, 21363, 21365, 21423, 21425, 21446, 21448, 21450, 21452, 21454, 21513, 21515, 21517, 21519, 21521, 21523, 21525, 21527, 21529, 21531, 21533, 21552, 21554, 21556, 21558, 21560, 21562, 21564, 21566, 21568, 21570, 21572, 21574, 21576, 21578, 21580, 21582, 21584, 21586, 21588, 21590, 21592, 21612, 21614, 21616, 21618, 21620, 21678, 21680, 21682, 21684, 21686, 21688, 21690, 21692, 21694, 21696, 21698, 21700, 21702, 21704, 21706, 21708, 21710, 21712, 21714, 21716, 21718, 21720, 21722, 21724, 21726, 21728, 21730, 21732, 21734, 21736, 21738, 21796, 21798, 21800, 21802, 21804, 21806, 21808, 21866, 21868, 21870, 21872, 21874, 21928, 21985, 21987, 22031, 22033, 22035, 22055, 22057, 22059, 22061, 22063, 22065, 22067, 22069, 22071, 22073, 22075, 22087, 22089, 22091, 22093, 22095, 22097, 22099, 22101, 22103, 22105, 22107, 22109, 22111, 22113, 22124, 22126, 22128, 22130, 22132, 22134, 22136, 22138, 22140, 22142, 22154, 22156, 22159, 22161, 22180, 22182, 22233, 22235, 22237, 22239, 22241, 22243, 22245, 22247, 22306, 22308, 22310, 22312, 22314, 22316, 22318, 22320, 22322, 22324, 22326, 22328, 22330, 22332, 22334, 22336, 22338, 22346, 22348, 22350, 22352, 22354, 22356, 22358, 22360, 22400, 22402, 22404, 22406, 22408, 22456, 22458, 22460, 22515, 22517, 22519, 22521, 22523, 22579, 22635, 22637, 22639, 22641, 22643, 22670, 22676, 22678, 22680, 22682, 22684, 22686, 22688, 22690, 22692, 22700, 22702, 22704, 22706, 22725, 22727, 22729, 22731, 22733, 22735, 22737, 22739, 22741, 22743, 22745, 22747, 22749, 22751, 22753, 22755, 22757, 22759, 22761, 22763, 22765, 22767, 22769, 22771, 22773, 22775, 22777, 22779, 22781, 22783, 22785, 22787, 22789, 22791, 22793, 22795, 22797, 22799, 22807, 22809, 22811, 22813, 22815, 22817, 22819, 22821, 22823, 22825, 22838, 22840, 22842, 22844, 22846, 22848, 22850, 22852, 22854, 22856, 22858, 22860, 22863, 22865, 22867, 22869, 22871, 22873, 22875, 22920, 22922, 22924, 22926, 22928, 22930, 22932, 22934, 22985, 22987, 22989, 23010, 23012, 23014, 23016, 23018, 23020, 23022, 23024, 23026, 23028, 23030, 23032, 23075, 23077, 23079, 23081, 23083, 23085, 23106, 23108, 23110, 23112, 23114, 23116, 23118, 23120, 23122, 23168, 23170, 23172, 23193, 23195, 23197, 23254, 23256, 23258, 23260, 23262, 23264, 23272, 23274, 23276, 23278, 23280, 23282, 23284, 23286, 23288, 23290, 23292, 23294, 23296, 23298, 23300, 23302, 23304, 23306, 23308, 23310, 23312, 23314, 23316, 23318, 23345, 23347, 23349, 23351, 23396, 23398, 23400, 23402, 23421, 23423, 23425, 23427, 23429, 23431, 23433, 23435, 23437, 23439, 23450, 23452, 23454, 23456, 23458, 23460, 23462, 23464, 23466, 23468, 23519, 23521, 23523, 23525, 23527, 23544, 23546, 23563, 23565, 23609, 23611, 23613, 23615, 23673, 23675, 23677, 23737, 23739, 23741, 23743, 23745, 23747, 23749, 23751, 23753, 23755, 23757, 23759, 23761, 23763, 23765, 23777, 23779, 23781, 23783, 23785, 23787, 23789, 23791, 23793, 23795, 23797, 23799, 23801, 23803, 23805, 23807, 23809, 23811, 23813, 23815, 23817, 23819, 23821, 23823, 23825, 23827, 23840, 23842, 23844, 23887, 23889, 23891, 23893, 23895, 23897, 23899, 23901, 23903, 23905, 23907, 23950, 23952, 24012, 24014, 24017, 24019, 24021, 24023, 24025, 24027, 24029, 24031, 24033, 24035, 24049, 24051, 24053, 24055, 24057, 24059, 24061, 24063, 24082, 24084, 24086, 24088, 24090, 24092, 24094, 24146, 24148, 24150, 24152, 24154, 24156, 24158, 24160, 24162, 24164, 24214, 24229, 24231, 24292, 24294, 24311, 24313, 24315, 24334, 24336, 24338, 24340, 24342, 24344, 24346, 24348, 24350, 24352, 24354, 24356, 24358, 24360, 24362, 24364, 24366, 24427, 24429, 24431, 24433, 24435, 24437, 24439, 24441, 24443, 24445, 24447, 24449, 24483, 24485, 24487, 24489, 24491, 24493, 24495, 24497, 24499, 24519, 24521, 24523, 24525, 24527, 24529, 24531, 24533, 24535, 24537, 24592, 24594, 24596, 24598, 24600, 24602, 24604, 24606, 24608, 24610, 24612, 24614, 24654, 24656, 24714, 24716, 24720, 24722, 24724, 24726, 24770, 24772, 24798, 24802, 24804, 24806, 24808, 24810, 24812, 24814, 24816, 24818, 24820, 24822, 24865, 24867, 24869, 24871, 24873, 24875, 24877, 24879, 24881, 24883, 24885, 24948, 24950, 24952, 24954, 24956, 24968, 24970, 24972, 25026, 25028, 25030, 25032, 25034, 25036, 25038, 25040, 25042, 25044, 25050, 25052, 25054, 25056, 25058, 25060, 25062, 25064, 25066, 25124, 25126, 25128, 25130, 25179, 25181, 25239, 25297, 25299, 25301, 25303, 25305, 25307, 25309, 25311, 25313, 25315, 25317, 25374, 25376, 25425, 25427, 25429, 25431, 25433, 25435, 25437, 25439, 25441, 25443, 25504, 25547, 25549, 25551, 25553, 25555, 25557, 25610, 25612, 25614, 25616, 25618, 25620, 25622, 25624, 25626, 25628, 25686, 25688, 25707, 25709, 25711, 25713, 25715, 25717, 25719, 25721, 25723, 25725, 25727, 25729, 25731, 25733, 25735, 25737, 25795, 25797, 25799, 25801, 25803, 25805, 25807, 25865, 25867, 25869, 25871, 25873, 25930, 25932, 25976, 25978, 25980, 25982, 25984, 25990, 25992, 25994, 25996, 26057, 26059, 26061, 26063, 26065, 26069, 26071, 26073, 26075, 26077, 26138, 26142, 26144, 26146, 26148, 26167, 26169, 26228, 26230, 26232, 26234, 26236, 26238, 26240, 26242, 26244, 26246, 26248, 26250, 26252, 26254, 26298, 26300, 26302, 26304, 26346, 26390, 26392, 26394, 26447, 26449, 26451, 26453, 26455, 26457, 26519, 26521, 26523, 26525, 26527, 26529, 26531, 26533, 26535, 26537, 26539, 26541, 26543, 26545, 26589, 26591, 26611, 26668, 26670, 26672, 26674, 26676, 26678, 26680, 26682, 26684, 26689, 26691, 26693, 26695, 26697, 26699, 26701, 26703, 26705, 26707, 26709, 26711, 26713, 26757, 26813, 26815, 26817, 26819, 26821, 26823, 26825, 26827, 26829, 26831, 26847, 26849, 26851, 26853, 26855, 26871, 26929, 26931, 26933, 26935, 26937, 26981, 26983, 27039, 27041, 27043, 27087, 27089, 27091, 27149, 27151, 27153, 27155, 27157, 27201, 27203, 27259, 27261, 27274, 27276, 27278, 27280, 27282, 27284, 27286, 27288, 27290, 27292, 27294, 27296, 27298, 27300, 27308, 27310, 27312, 27314, 27358, 27360, 27362, 27364, 27366, 27368, 27370, 27414, 27416, 27418, 27470, 27528, 27530, 27532, 27534, 27536, 27538, 27587, 27589, 27591, 27593, 27595, 27597, 27599, 27601, 27603, 27605, 27607, 27609, 27611, 27613, 27615, 27632, 27634, 27636, 27638, 27640, 27642, 27644, 27646, 27699, 27701, 27703, 27705, 27707, 27764, 27766, 27768, 27770, 27823, 27825, 27827, 27829, 27831, 27833, 27835, 27837, 27839, 27841, 27843, 27890, 27892, 27894, 27896, 27898, 27910, 27912, 27914, 27968, 27970, 27972, 27974, 27976, 27978, 27980, 28017, 28019, 28037, 28039, 28041, 28043, 28045, 28047, 28065, 28067, 28109, 28111, 28131, 28133, 28135, 28137, 28139, 28141, 28143, 28145, 28147, 28149, 28151, 28153, 28155, 28157, 28159, 28161, 28163, 28165, 28167, 28169, 28186, 28188, 28190, 28192, 28194, 28201, 28203, 28205, 28207, 28209, 28211, 28213, 28226, 28228, 28230, 28232, 28234, 28236, 28238, 28240, 28261, 28263, 28265, 28267, 28278, 28280, 28282, 28284, 28286, 28288, 28290, 28292, 28294, 28296, 28298, 28308, 28310, 28312, 28314, 28316, 28318, 28320, 28333, 28335, 28337, 28339, 28341, 28343, 28345, 28347, 28349, 28351, 28353, 28355, 28368, 28370, 28372, 28374, 28376, 28378, 28380, 28382, 28384, 28445, 28447, 28449, 28451, 28503, 28505, 28559, 28561, 28563, 28565, 28567, 28569, 28571, 28573, 28575, 28577, 28579, 28581, 28583, 28585, 28587, 28601, 28603, 28605, 28607, 28609, 28611, 28613, 28615, 28617, 28620, 28622, 28624, 28626, 28628, 28668, 28670, 28672, 28674, 28676, 28678, 28680, 28682, 28684, 28706, 28708, 28767, 28769, 28771, 28773, 28775, 28780, 28782, 28784, 28786, 28788, 28790, 28792, 28794, 28813, 28815, 28817, 28819, 28821, 28823, 28825, 28827, 28829, 28831, 28833, 28835, 28837, 28839, 28841, 28843, 28845, 28853, 28914, 28916, 28918, 28920, 28922, 28924, 28926, 28928, 28930, 28991, 28993, 28995, 28997, 28999, 29001, 29003, 29005, 29007, 29029, 29031, 29041, 29043, 29090, 29092, 29094, 29096, 29098, 29157, 29159, 29161, 29163, 29165, 29167, 29169, 29171, 29173, 29181, 29183, 29185, 29187, 29189, 29191, 29193, 29195, 29197, 29199, 29201, 29203, 29217, 29219, 29221, 29223, 29225, 29227, 29229, 29231, 29233, 29235, 29237, 29297, 29299, 29301, 29303, 29305, 29307, 29309, 29351, 29409, 29411, 29413, 29415, 29417, 29419, 29421, 29423, 29425, 29427, 29429, 29431, 29433, 29435, 29437, 29439, 29459, 29461, 29463, 29465, 29467, 29469, 29471, 29473, 29475, 29477, 29479, 29481, 29483, 29485, 29487, 29489, 29491, 29493, 29551, 29553, 29601, 29603, 29605, 29607, 29609, 29611, 29613, 29627, 29629, 29649, 29651, 29653, 29655, 29657, 29659, 29661, 29663, 29665, 29667, 29669, 29671, 29673, 29732, 29734, 29736, 29738, 29740, 29742, 29744, 29746, 29748, 29750, 29758, 29760, 29762, 29764, 29766, 29768, 29770, 29772, 29774, 29791, 29793, 29795, 29797, 29799, 29801, 29803, 29805, 29807, 29809, 29811, 29813, 29815, 29817, 29819, 29821, 29823, 29839, 29841, 29847, 29849, 29851, 29853, 29855, 29857, 29859, 29861, 29863, 29865, 29867, 29869, 29871, 29915, 29917, 29919, 29921, 29978, 29999, 30001, 30003, 30005, 30007, 30009, 30011, 30013, 30015, 30017, 30019, 30021, 30023, 30025, 30027, 30029, 30031, 30050, 30068, 30070, 30072, 30074, 30076, 30078, 30080, 30082, 30084, 30086, 30088, 30090, 30092, 30094, 30096, 30147, 30149, 30151, 30153, 30155, 30157, 30159, 30161, 30223, 30225, 30227, 30272, 30274, 30276, 30278, 30280, 30282, 30284, 30286, 30288, 30290, 30292, 30294, 30296, 30298, 30300, 30302, 30304, 30306, 30316, 30318, 30320, 30322, 30324, 30377, 30379, 30381, 30383, 30385, 30404, 30406, 30408, 30410, 30412, 30414, 30416, 30428, 30430, 30438, 30440, 30442, 30458, 30460, 30462, 30481, 30483, 30485, 30511, 30513, 30515, 30517, 30519, 30526, 30528, 30530, 30532, 30534, 30536, 30538, 30540, 30542, 30560, 30562, 30564, 30566, 30568, 30570, 30572, 30574, 30576, 30578, 30635, 30637, 30639, 30641, 30649, 30651, 30709, 30711, 30713, 30715, 30717, 30719, 30721, 30723, 30725, 30727, 30729, 30740, 30742, 30744, 30746, 30748, 30750, 30752, 30754, 30756, 30758, 30774, 30776, 30795, 30797, 30848, 30850, 30911, 30913, 30927, 30929, 30931, 30952, 30954, 30956, 30958, 30960, 31019, 31021, 31023, 31025, 31027, 31029, 31031, 31033, 31035, 31037, 31039, 31058, 31060, 31062, 31064, 31066, 31068, 31070, 31072, 31074, 31076, 31085, 31087, 31089, 31091, 31093, 31095, 31097, 31099, 31101, 31103, 31105, 31107, 31109, 31111, 31113, 31115, 31117, 31119, 31121, 31123, 31125, 31127, 31129, 31131, 31133, 31135, 31137, 31139, 31141, 31143, 31145, 31147, 31149, 31151, 31153, 31155, 31157, 31159, 31161, 31163, 31165, 31167, 31169, 31171, 31173, 31175, 31177, 31179, 31181, 31183, 31185, 31187, 31189, 31191, 31193, 31195, 31197, 31199, 31213, 31215, 31217, 31219, 31221, 31223, 31225, 31227, 31229, 31231, 31233, 31235, 31275, 31277, 31279, 31281, 31283, 31285, 31287, 31289, 31291, 31312, 31318, 31320, 31322, 31324, 31326, 31328, 31330, 31332, 31345, 31347, 31349, 31351, 31353, 31355, 31357, 31359, 31361, 31363, 31365, 31367, 31384, 31386, 31432, 31434, 31436, 31438, 31440, 31442, 31444, 31446, 31448, 31450, 31452, 31454, 31456, 31474, 31476, 31478, 31480, 31482, 31484, 31486, 31488, 31509, 31511, 31513, 31570, 31572, 31584, 31586, 31588, 31590, 31592, 31594, 31596, 31598, 31600, 31602, 31653, 31655, 31657, 31659, 31661, 31663, 31665, 31667, 31669, 31671, 31673, 31675, 31677, 31679, 31706, 31708, 31710, 31712, 31714, 31716, 31718, 31720, 31722, 31724, 31788, 31790, 31794, 31796, 31798, 31800, 31802, 31804, 31806, 31808, 31851, 31853, 31855, 31857, 31859, 31861, 31863, 31911, 31913, 31915, 31917, 31919, 31921, 31923, 31925, 31927, 31929, 31931, 31933, 31935, 31937, 31939, 31941, 31943, 31945, 31947, 31991, 31993, 31995, 32052, 32054, 32056, 32058, 32060, 32072, 32074, 32076, 32130, 32132, 32134, 32136, 32138, 32140, 32142, 32144, 32146, 32148, 32150, 32157, 32159, 32161, 32163, 32165, 32167, 32169, 32171, 32173, 32227, 32229, 32231, 32233, 32259, 32261, 32263, 32265, 32284, 32286, 32288, 32290, 32292, 32294, 32296, 32301, 32303, 32305, 32307, 32309, 32311, 32313, 32315, 32317, 32319, 32321, 32323, 32331, 32333, 32335, 32396, 32398, 32400, 32457, 32459, 32461, 32463, 32519, 32521, 32533, 32535, 32547, 32549, 32551, 32553, 32555, 32557, 32559, 32561, 32563, 32565, 32567, 32569, 32571, 32573, 32575, 32577, 32598, 32607, 32609, 32611, 32613, 32615, 32617, 32619, 32621, 32623, 32625, 32627, 32629, 32631, 32633, 32635, 32637, 32639, 32641, 32643, 32645, 32655, 32657, 32659, 32661, 32705, 32763, 32765, 32777, 32779, 32781, 32783, 32785, 32826, 32866, 32868, 32870, 32872, 32874, 32876, 32892, 32894, 32896, 32956, 32958, 32960, 32962, 32964, 32984, 33006, 33008, 33066, 33068, 33070, 33128, 33130, 33132, 33148, 33206, 33208, 33210, 33212, 33256, 33258, 33260, 33318, 33320, 33322, 33324, 33368, 33370, 33372, 33417, 33419, 33422, 33424, 33426, 33428, 33430, 33432, 33434, 33436, 33455, 33457, 33459, 33461, 33463, 33465, 33467, 33469, 33471, 33473, 33475, 33477, 33479, 33481, 33483, 33502, 33504, 33549, 33551, 33566, 33568, 33570, 33572, 33574, 33587, 33589, 33591, 33593, 33595, 33597, 33599, 33601, 33603, 33605, 33607, 33609, 33611, 33627, 33685, 33687, 33689, 33691, 33693, 33736, 33738, 33740, 33742, 33744, 33746, 33748, 33750, 33784, 33786, 33788, 33790, 33792, 33794, 33796, 33798, 33800, 33802, 33804, 33822, 33824, 33877, 33879, 33935, 33937, 33939, 33941, 33997, 33999, 34001, 34003, 34019, 34021, 34023, 34025, 34083, 34085, 34087, 34089, 34091, 34093, 34095, 34097, 34099, 34101, 34103, 34105, 34107, 34109, 34111, 34113, 34133, 34135, 34137, 34139, 34141, 34143, 34145, 34147, 34149, 34151, 34153, 34155, 34157, 34159, 34161, 34163, 34165, 34167, 34169, 34237, 34239, 34245, 34247, 34249, 34251, 34253, 34255, 34298, 34300, 34302, 34304, 34306, 34314, 34316, 34318, 34320, 34322, 34324, 34326, 34328, 34330, 34332, 34334, 34336, 34338, 34372, 34374, 34376, 34378, 34380, 34382, 34384, 34402, 34404, 34457, 34459, 34515, 34517, 34519, 34521, 34577, 34579, 34581, 34583, 34599, 34601, 34603, 34650, 34652, 34654, 34656, 34658, 34670, 34672, 34674, 34728, 34730, 34732, 34734, 34736, 34738, 34740, 34742, 34744, 34746, 34770, 34775, 34777, 34779, 34781, 34783, 34785, 34787, 34789, 34791, 34817, 34819, 34821, 34823, 34825, 34827, 34829, 34831, 34833, 34835, 34837, 34839, 34841, 34843, 34845, 34847, 34849, 34851, 34853, 34855, 34867, 34871, 34873, 34925, 34927, 34929, 34931, 34933, 34935, 34937, 34998, 35000, 35002, 35004, 35006, 35019, 35021, 35041, 35043, 35045, 35047, 35049, 35051, 35053, 35055, 35057, 35059, 35061, 35063, 35065, 35085, 35087, 35089, 35091, 35093, 35095, 35097, 35099, 35103, 35105, 35107, 35109, 35111, 35113, 35153, 35155, 35157, 35159, 35161, 35163, 35179, 35181, 35183, 35228, 35230, 35232, 35234, 35236, 35238, 35240, 35294, 35296, 35298, 35300, 35302, 35304, 35306, 35308, 35310, 35312, 35314, 35331, 35333, 35335, 35337, 35358, 35360, 35362, 35364, 35366, 35423, 35425, 35427, 35429, 35431, 35433, 35435, 35437, 35481, 35483, 35485, 35487, 35489, 35491, 35493, 35495, 35539, 35541, 35543, 35545, 35547, 35549, 35551, 35553, 35555, 35557, 35559, 35561, 35563, 35565, 35567, 35569, 35571, 35573, 35575, 35577, 35579, 35581, 35583, 35585, 35587, 35589, 35591, 35593, 35612, 35621, 35623, 35625, 35627, 35629, 35631, 35633, 35635, 35637, 35639, 35647, 35649, 35651, 35653, 35655, 35657, 35659, 35661, 35669, 35671, 35673, 35675, 35677, 35679, 35681, 35683, 35685, 35687, 35689, 35691, 35693, 35695, 35697, 35699, 35701, 35703, 35705, 35707, 35763, 35765, 35767, 35769, 35771, 35773, 35775, 35792, 35794, 35796, 35798, 35800, 35802, 35804, 35808, 35810, 35812, 35814, 35816, 35818, 35836, 35838, 35840, 35842, 35851, 35853, 35855, 35857, 35859, 35861, 35863, 35865, 35867, 35869, 35871, 35882, 35884, 35886, 35888, 35890, 35892, 35894, 35896, 35898, 35900, 35903, 35905, 35913, 35915, 35917, 35919, 35921, 35923, 35942, 35947, 35949, 35951, 35995, 35997, 35999, 36001, 36003, 36005, 36007, 36009, 36011, 36024, 36026, 36028, 36030, 36041, 36043, 36045, 36047, 36049, 36051, 36073, 36075, 36077, 36079, 36081, 36083, 36085, 36087, 36089, 36091, 36093, 36095, 36097, 36099, 36101, 36103, 36105, 36107, 36109, 36111, 36113, 36121, 36123, 36125, 36127, 36129, 36131, 36133, 36135, 36137, 36139, 36141, 36143, 36145, 36147, 36198, 36200, 36202, 36204, 36206, 36208, 36210, 36212, 36214, 36216, 36218, 36220, 36222, 36241, 36246, 36248, 36250, 36294, 36296, 36298, 36300, 36302, 36304, 36306, 36308, 36310, 36312, 36314, 36316, 36318, 36320, 36322, 36338, 36340, 36342, 36344, 36346, 36348, 36350, 36352, 36354, 36399, 36401, 36415, 36417, 36419, 36421, 36423, 36425, 36427, 36429, 36431, 36433, 36435, 36494, 36496, 36498, 36500, 36502, 36504, 36565, 36567, 36569, 36571, 36573, 36575, 36581, 36583, 36585, 36587, 36589, 36606, 36608, 36610, 36612, 36614, 36616, 36618, 36620, 36622, 36624, 36626, 36628, 36630, 36632, 36634, 36636, 36638, 36640, 36642, 36644, 36656, 36658, 36660, 36662, 36664, 36666, 36668, 36670, 36672, 36719, 36721, 36723, 36737, 36739, 36741, 36743, 36745, 36761, 36763, 36765, 36767, 36769, 36771, 36773, 36829, 36831, 36833, 36835, 36837, 36839, 36841, 36843, 36899, 36901, 36913, 36915, 36917, 36919, 36921, 36923, 36925, 36927, 36929, 36931, 36933, 36941, 36943, 36945, 36947, 36949, 36951, 36953, 36955, 36957, 36959, 36961, 36963, 36965, 36967, 36969, 36971, 36986, 37030, 37032, 37034, 37036, 37038, 37040, 37042, 37044, 37046, 37048, 37068, 37070, 37072, 37074, 37076, 37078, 37080, 37082, 37084, 37086, 37088, 37090, 37092, 37108, 37110, 37112, 37114, 37116, 37118, 37120, 37122, 37124, 37126, 37128, 37130, 37132, 37134, 37154, 37156, 37158, 37174, 37176, 37210, 37212, 37214, 37216, 37250, 37252, 37254, 37256, 37258, 37260, 37262, 37264, 37266, 37284, 37286, 37288, 37290, 37292, 37299, 37306, 37308, 37310, 37312, 37314, 37370, 37372, 37374, 37376, 37378, 37380, 37382, 37384, 37386, 37388, 37390, 37392, 37436, 37438, 37440, 37442, 37444, 37446, 37448, 37450, 37472, 37494, 37496, 37498, 37500, 37502, 37504, 37506, 37508, 37510, 37512, 37514, 37516, 37518, 37520, 37554, 37556, 37558, 37560, 37562, 37564, 37566, 37568, 37602, 37604, 37606, 37608, 37610, 37612, 37614, 37616, 37618, 37620, 37622, 37624, 37626, 37644, 37646, 37648, 37650, 37658, 37660, 37662, 37664, 37666, 37668, 37670, 37672, 37674, 37708, 37710, 37712, 37714, 37716, 37718, 37720, 37736, 37738, 37740, 37742, 37744, 37746, 37748, 37754, 37756, 37758, 37760, 37762, 37764, 37766, 37768, 37770, 37772, 37774, 37776, 37778, 37780, 37782, 37784, 37806, 37808, 37814, 37816, 37818, 37820, 37838, 37840, 37842, 37844, 37846, 37848, 37850, 37852, 37854, 37856, 37858, 37860, 37862, 37864, 37866, 37868, 37870, 37882, 37884, 37886, 37888, 37912, 37914, 37916, 37918, 37920, 37922, 37948, 37950, 37952, 37954, 37956, 37958, 37960, 37962, 37964, 37981, 37983, 37985, 37987, 37989, 37991, 37993, 37995, 37997, 37999, 38001, 38003, 38005, 38007, 38009, 38011, 38013, 38015, 38017, 38019, 38021, 38023, 38025, 38027, 38033, 38035, 38037, 38039, 38098, 38100, 38102, 38104, 38106, 38108, 38110, 38112, 38114, 38116, 38118, 38120, 38122, 38124, 38126, 38128, 38130, 38132, 38134, 38136, 38138, 38140, 38142, 38144, 38146, 38148, 38150, 38152, 38154, 38156, 38158, 38160, 38162, 38164, 38166, 38168, 38170, 38172, 38174, 38176, 38178, 38180, 38182, 38184, 38186, 38188, 38190, 38192, 38194, 38238, 38296, 38298, 38300, 38354, 38356, 38358, 38360, 38362, 38364, 38366, 38368, 38370, 38427, 38429, 38431, 38433, 38435, 38437, 38439, 38441, 38498, 38500, 38502, 38504, 38506, 38559, 38561, 38563, 38565, 38567, 38569, 38571, 38573, 38575, 38577, 38628, 38630, 38632, 38634, 38636, 38638, 38640, 38642, 38644, 38646, 38648, 38650, 38652, 38695, 38697, 38699, 38701, 38703, 38705, 38707, 38709 }; static const short _char_ref_indicies[] = { 0, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 4, 5, 1, 1, 6, 7, 1, 1, 1, 1, 8, 9, 10, 11, 12, 1, 13, 14, 15, 16, 1, 17, 1, 18, 1, 19, 1, 20, 1, 21, 1, 22, 1, 23, 1, 24, 1, 25, 1, 26, 1, 27, 1, 28, 1, 29, 1, 30, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 31, 1, 32, 1, 33, 1, 34, 1, 35, 1, 36, 1, 37, 1, 38, 1, 39, 1, 40, 1, 41, 1, 42, 1, 43, 1, 44, 1, 45, 1, 46, 1, 47, 1, 48, 1, 49, 1, 50, 1, 51, 1, 1, 1, 1, 1, 1, 1, 1, 52, 1, 53, 1, 54, 1, 55, 1, 56, 1, 57, 1, 58, 1, 59, 1, 60, 1, 61, 1, 62, 1, 63, 1, 64, 1, 65, 1, 66, 1, 67, 1, 68, 1, 69, 1, 70, 1, 71, 1, 72, 1, 73, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 74, 1, 75, 1, 76, 1, 77, 1, 78, 1, 79, 1, 80, 1, 81, 1, 82, 1, 83, 1, 84, 1, 85, 1, 86, 1, 87, 1, 88, 1, 89, 90, 1, 1, 1, 1, 1, 1, 1, 1, 91, 1, 1, 92, 93, 1, 94, 1, 95, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 96, 1, 97, 1, 98, 1, 99, 1, 100, 1, 101, 1, 102, 1, 103, 1, 104, 105, 1, 106, 1, 107, 1, 108, 1, 109, 1, 110, 1, 111, 1, 112, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 113, 1, 114, 1, 115, 1, 116, 1, 117, 1, 118, 1, 119, 1, 120, 1, 121, 1, 122, 1, 123, 1, 124, 1, 125, 1, 126, 1, 127, 1, 128, 1, 129, 1, 130, 1, 131, 1, 132, 1, 133, 1, 134, 1, 135, 1, 136, 1, 137, 1, 138, 1, 139, 1, 140, 1, 141, 1, 142, 1, 143, 1, 144, 1, 145, 1, 146, 1, 147, 1, 1, 1, 1, 1, 1, 148, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 149, 1, 150, 151, 152, 153, 1, 154, 155, 1, 1, 156, 1, 1, 157, 1, 1, 158, 159, 1, 160, 1, 161, 1, 162, 1, 163, 1, 164, 1, 165, 1, 166, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 167, 1, 1, 1, 1, 1, 1, 1, 1, 168, 1, 169, 1, 170, 1, 171, 1, 172, 1, 173, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 174, 1, 175, 1, 176, 1, 177, 1, 178, 1, 179, 1, 180, 1, 181, 1, 182, 1, 183, 1, 184, 1, 185, 1, 186, 1, 187, 1, 188, 1, 189, 1, 190, 1, 191, 1, 192, 1, 193, 1, 194, 1, 195, 1, 196, 1, 197, 1, 1, 1, 198, 1, 1, 1, 199, 1, 1, 1, 1, 1, 200, 1, 201, 1, 202, 1, 203, 1, 204, 1, 205, 1, 206, 1, 207, 1, 208, 1, 209, 1, 210, 1, 211, 1, 212, 1, 213, 1, 214, 1, 215, 1, 216, 1, 217, 1, 218, 1, 219, 1, 1, 1, 1, 1, 1, 1, 1, 1, 220, 1, 221, 1, 222, 1, 223, 1, 224, 1, 225, 1, 226, 1, 227, 1, 228, 1, 229, 1, 230, 1, 231, 1, 232, 1, 233, 1, 234, 1, 235, 1, 236, 1, 237, 1, 238, 1, 239, 1, 240, 1, 241, 1, 1, 1, 1, 1, 1, 1, 1, 242, 1, 1, 243, 1, 1, 1, 244, 1, 245, 1, 246, 1, 247, 1, 248, 1, 249, 1, 250, 1, 251, 1, 252, 1, 253, 1, 254, 1, 255, 1, 256, 1, 257, 1, 258, 1, 259, 1, 260, 1, 261, 1, 262, 1, 263, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 264, 1, 265, 1, 266, 1, 267, 1, 268, 1, 269, 1, 270, 1, 271, 1, 272, 1, 273, 1, 274, 1, 275, 1, 276, 1, 277, 1, 278, 1, 279, 1, 280, 1, 281, 1, 282, 1, 283, 1, 284, 1, 285, 1, 286, 1, 287, 1, 288, 1, 289, 1, 290, 1, 291, 1, 292, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 293, 1, 294, 1, 295, 1, 296, 1, 297, 1, 298, 1, 299, 1, 300, 1, 301, 1, 302, 1, 303, 1, 304, 1, 305, 1, 306, 1, 307, 1, 308, 1, 309, 1, 310, 1, 311, 1, 312, 1, 1, 1, 1, 313, 1, 314, 1, 315, 1, 316, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 317, 1, 318, 1, 319, 1, 320, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 321, 1, 322, 1, 323, 1, 324, 1, 325, 1, 326, 1, 327, 1, 328, 1, 329, 1, 330, 1, 331, 1, 332, 1, 333, 1, 334, 1, 335, 1, 336, 1, 337, 1, 338, 1, 339, 1, 340, 1, 341, 1, 342, 1, 343, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 344, 1, 345, 1, 346, 1, 347, 1, 348, 1, 349, 1, 350, 1, 351, 1, 352, 1, 353, 1, 354, 1, 355, 1, 356, 1, 357, 1, 358, 1, 359, 1, 360, 1, 361, 1, 362, 1, 363, 1, 364, 1, 365, 1, 366, 1, 367, 1, 368, 1, 369, 1, 370, 1, 371, 1, 372, 1, 373, 1, 374, 1, 375, 1, 376, 1, 377, 1, 378, 1, 379, 1, 380, 1, 381, 1, 382, 1, 383, 1, 384, 1, 385, 1, 386, 1, 387, 1, 388, 1, 389, 1, 1, 1, 1, 1, 1, 1, 390, 1, 391, 1, 392, 1, 393, 1, 394, 1, 1, 1, 1, 1, 395, 1, 1, 1, 1, 1, 1, 1, 1, 396, 1, 1, 1, 1, 1, 1, 397, 1, 1, 1, 1, 1, 1, 398, 1, 399, 1, 400, 401, 1, 1, 402, 1, 1, 1, 1, 1, 403, 1, 1, 1, 404, 1, 405, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 406, 1, 407, 1, 408, 1, 409, 1, 410, 1, 411, 1, 412, 1, 413, 1, 414, 1, 415, 1, 416, 1, 417, 1, 418, 1, 419, 1, 420, 1, 421, 1, 422, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 423, 424, 1, 425, 1, 426, 1, 427, 1, 428, 1, 429, 1, 430, 1, 431, 1, 432, 1, 433, 1, 434, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 435, 1, 436, 1, 437, 1, 438, 1, 439, 1, 440, 1, 441, 1, 442, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 443, 1, 444, 1, 445, 1, 446, 1, 447, 1, 448, 1, 1, 1, 1, 449, 1, 450, 1, 1, 1, 1, 1, 1, 1, 1, 1, 451, 1, 452, 1, 453, 1, 454, 1, 455, 1, 456, 1, 457, 1, 458, 1, 459, 1, 1, 460, 1, 1, 461, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 462, 1, 463, 1, 464, 1, 465, 1, 466, 1, 467, 1, 468, 1, 469, 470, 1, 471, 1, 472, 1, 473, 1, 474, 1, 475, 1, 476, 1, 477, 1, 478, 1, 479, 1, 480, 1, 481, 1, 482, 1, 483, 1, 484, 1, 485, 1, 486, 1, 487, 1, 488, 1, 489, 1, 490, 1, 491, 1, 492, 1, 493, 1, 494, 1, 495, 1, 496, 1, 497, 1, 498, 1, 499, 1, 500, 1, 501, 1, 502, 1, 503, 1, 504, 1, 505, 1, 506, 1, 1, 1, 507, 508, 1, 509, 1, 510, 1, 511, 1, 512, 1, 1, 1, 1, 1, 1, 1, 1, 513, 514, 1, 515, 1, 516, 1, 517, 1, 518, 1, 519, 1, 520, 1, 521, 1, 522, 1, 523, 1, 524, 1, 525, 1, 526, 527, 1, 1, 1, 1, 1, 1, 1, 528, 1, 1, 1, 1, 1, 529, 1, 1, 530, 531, 1, 532, 1, 533, 1, 534, 1, 535, 1, 536, 1, 537, 1, 538, 1, 539, 1, 540, 1, 541, 1, 542, 1, 543, 1, 544, 1, 545, 1, 546, 1, 547, 1, 548, 1, 1, 549, 1, 550, 1, 551, 1, 552, 1, 553, 1, 554, 1, 555, 1, 556, 1, 557, 1, 558, 1, 1, 1, 1, 1, 1, 1, 1, 1, 559, 1, 560, 1, 561, 1, 562, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 563, 1, 564, 1, 565, 1, 566, 1, 567, 1, 568, 1, 569, 1, 570, 1, 571, 1, 572, 1, 573, 1, 574, 1, 575, 1, 576, 1, 577, 1, 578, 1, 579, 1, 580, 1, 581, 1, 582, 1, 583, 1, 584, 1, 585, 1, 1, 1, 1, 1, 586, 1, 587, 1, 588, 1, 589, 1, 590, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 591, 1, 592, 1, 593, 1, 594, 1, 595, 1, 596, 1, 597, 1, 598, 1, 599, 1, 600, 1, 601, 1, 602, 1, 603, 1, 604, 1, 605, 1, 606, 1, 607, 1, 608, 1, 609, 1, 610, 1, 611, 1, 612, 1, 613, 1, 614, 1, 615, 1, 616, 1, 617, 1, 618, 1, 619, 1, 620, 1, 621, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 622, 1, 623, 1, 624, 1, 625, 1, 626, 1, 627, 1, 628, 1, 629, 1, 630, 1, 631, 1, 632, 1, 1, 633, 1, 634, 1, 635, 1, 636, 1, 637, 1, 638, 1, 639, 1, 640, 1, 641, 1, 642, 1, 643, 1, 644, 1, 645, 1, 646, 1, 647, 1, 648, 1, 649, 1, 650, 1, 651, 1, 652, 1, 653, 1, 654, 1, 655, 1, 656, 1, 657, 1, 658, 1, 659, 1, 660, 661, 1, 1, 1, 1, 1, 1, 1, 1, 1, 662, 1, 1, 1, 1, 1, 663, 1, 664, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 665, 1, 666, 1, 667, 1, 668, 1, 669, 1, 670, 1, 1, 1, 1, 1, 1, 671, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 672, 1, 673, 1, 674, 1, 675, 1, 676, 1, 677, 1, 678, 1, 679, 1, 680, 1, 681, 1, 682, 1, 683, 1, 684, 1, 685, 1, 686, 1, 687, 1, 688, 1, 689, 1, 690, 1, 691, 1, 692, 1, 693, 1, 694, 1, 695, 1, 696, 1, 697, 1, 698, 1, 699, 1, 700, 1, 701, 1, 702, 1, 703, 1, 704, 1, 705, 1, 706, 1, 707, 1, 708, 1, 709, 1, 710, 1, 711, 1, 712, 1, 713, 1, 714, 1, 715, 1, 716, 1, 717, 1, 718, 1, 719, 1, 1, 1, 1, 1, 1, 720, 1, 721, 1, 722, 1, 723, 1, 724, 1, 725, 1, 726, 1, 727, 1, 728, 1, 729, 1, 730, 1, 731, 1, 732, 1, 733, 1, 734, 1, 735, 1, 736, 1, 737, 1, 738, 1, 739, 1, 740, 1, 741, 1, 742, 1, 743, 1, 744, 1, 1, 1, 1, 1, 1, 745, 1, 746, 1, 747, 1, 748, 1, 749, 1, 750, 1, 751, 1, 1, 1, 1, 1, 752, 1, 753, 1, 754, 1, 755, 1, 756, 1, 757, 1, 758, 1, 759, 1, 760, 1, 761, 1, 762, 1, 763, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 764, 1, 765, 1, 766, 1, 767, 1, 768, 1, 769, 1, 770, 1, 771, 1, 1, 1, 1, 1, 772, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 773, 1, 774, 775, 1, 776, 777, 1, 1, 1, 1, 778, 779, 1, 780, 781, 782, 1, 783, 784, 785, 1, 1, 786, 1, 787, 1, 788, 1, 789, 1, 790, 1, 791, 1, 792, 1, 793, 1, 794, 1, 1, 1, 1, 1, 1, 1, 795, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 796, 1, 797, 1, 798, 1, 799, 1, 800, 1, 801, 1, 802, 1, 803, 1, 804, 1, 805, 1, 806, 1, 807, 1, 808, 1, 809, 1, 810, 1, 811, 1, 812, 1, 813, 1, 814, 1, 815, 1, 816, 1, 817, 1, 818, 1, 819, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 820, 1, 821, 1, 822, 1, 823, 1, 824, 1, 825, 1, 826, 1, 1, 827, 1, 828, 1, 829, 1, 830, 1, 831, 1, 832, 1, 833, 1, 834, 1, 835, 1, 836, 1, 837, 1, 838, 1, 839, 1, 840, 1, 841, 1, 842, 1, 843, 1, 844, 1, 845, 1, 846, 1, 847, 1, 848, 1, 849, 1, 850, 1, 851, 1, 852, 1, 853, 1, 854, 1, 1, 1, 1, 1, 1, 1, 1, 855, 1, 856, 1, 857, 1, 858, 1, 859, 1, 860, 1, 861, 1, 862, 1, 863, 1, 864, 1, 865, 1, 866, 1, 867, 1, 868, 1, 1, 1, 1, 1, 1, 1, 869, 1, 870, 1, 871, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 872, 1, 873, 1, 874, 1, 875, 1, 876, 1, 877, 1, 878, 1, 879, 1, 880, 1, 881, 1, 882, 1, 883, 1, 884, 1, 885, 1, 886, 1, 1, 1, 1, 1, 887, 1, 888, 1, 889, 1, 890, 1, 891, 1, 892, 1, 893, 1, 894, 1, 895, 1, 896, 1, 1, 1, 1, 1, 1, 897, 1, 898, 1, 899, 1, 900, 1, 901, 1, 902, 1, 903, 1, 904, 1, 905, 1, 906, 1, 907, 1, 908, 1, 909, 1, 910, 1, 911, 1, 912, 1, 1, 913, 1, 1, 914, 1, 1, 1, 1, 1, 915, 1, 1, 1, 916, 1, 917, 1, 918, 1, 919, 1, 920, 1, 921, 1, 922, 1, 923, 1, 924, 1, 925, 1, 1, 926, 1, 927, 1, 928, 1, 929, 1, 930, 1, 931, 1, 932, 1, 933, 1, 934, 1, 935, 1, 936, 1, 937, 1, 938, 1, 939, 1, 940, 1, 941, 1, 942, 1, 943, 1, 944, 1, 945, 1, 946, 1, 947, 1, 948, 1, 949, 1, 950, 1, 951, 1, 952, 1, 953, 1, 954, 1, 1, 955, 1, 956, 1, 957, 1, 958, 1, 959, 1, 960, 1, 961, 1, 962, 1, 963, 1, 964, 1, 965, 1, 966, 1, 967, 1, 968, 1, 969, 1, 970, 1, 971, 1, 972, 1, 973, 1, 1, 1, 1, 1, 1, 1, 1, 1, 974, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 975, 976, 977, 978, 1, 979, 980, 1, 1, 1, 1, 1, 1, 1, 981, 1, 1, 982, 983, 984, 1, 985, 1, 986, 1, 987, 1, 988, 1, 989, 1, 990, 1, 991, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 992, 1, 993, 1, 994, 1, 995, 1, 996, 1, 997, 1, 998, 1, 999, 1, 1, 1, 1000, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1001, 1, 1002, 1, 1003, 1, 1004, 1, 1005, 1, 1006, 1, 1007, 1, 1008, 1, 1009, 1, 1010, 1, 1011, 1, 1012, 1, 1013, 1, 1014, 1, 1015, 1, 1016, 1, 1017, 1, 1018, 1, 1019, 1, 1020, 1, 1021, 1, 1022, 1, 1023, 1, 1024, 1025, 1026, 1, 1, 1, 1, 1027, 1, 1, 1, 1, 1, 1, 1028, 1029, 1, 1030, 1, 1031, 1, 1032, 1, 1033, 1, 1034, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1035, 1, 1036, 1, 1037, 1, 1038, 1, 1039, 1, 1040, 1, 1041, 1, 1042, 1, 1043, 1, 1044, 1, 1045, 1, 1046, 1, 1047, 1, 1048, 1, 1049, 1, 1050, 1, 1051, 1, 1052, 1, 1053, 1, 1054, 1, 1055, 1, 1056, 1, 1057, 1, 1058, 1, 1059, 1, 1060, 1, 1061, 1, 1062, 1, 1063, 1, 1064, 1, 1065, 1, 1066, 1, 1067, 1, 1068, 1, 1069, 1, 1070, 1, 1071, 1, 1072, 1, 1073, 1, 1074, 1, 1075, 1, 1076, 1, 1077, 1, 1078, 1, 1079, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1080, 1, 1081, 1, 1, 1082, 1, 1, 1083, 1, 1, 1, 1, 1, 1084, 1, 1, 1, 1085, 1, 1086, 1, 1087, 1, 1088, 1, 1089, 1, 1090, 1, 1091, 1, 1092, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1093, 1, 1094, 1, 1095, 1, 1096, 1, 1097, 1, 1098, 1, 1099, 1, 1100, 1, 1101, 1, 1102, 1, 1103, 1, 1104, 1, 1105, 1, 1106, 1, 1107, 1, 1108, 1, 1109, 1, 1110, 1, 1111, 1, 1112, 1, 1113, 1, 1114, 1, 1115, 1, 1116, 1, 1117, 1, 1118, 1, 1119, 1, 1120, 1, 1121, 1, 1122, 1, 1123, 1, 1124, 1, 1125, 1, 1126, 1, 1127, 1, 1128, 1, 1129, 1, 1130, 1, 1131, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1132, 1, 1133, 1, 1134, 1, 1135, 1, 1136, 1, 1137, 1, 1138, 1, 1139, 1, 1140, 1, 1141, 1142, 1, 1143, 1, 1144, 1, 1145, 1, 1146, 1, 1147, 1, 1148, 1, 1149, 1, 1150, 1, 1151, 1, 1152, 1, 1153, 1, 1154, 1, 1155, 1, 1156, 1, 1, 1, 1, 1157, 1, 1, 1, 1, 1158, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1159, 1, 1160, 1161, 1, 1162, 1163, 1, 1, 1, 1, 1, 1164, 1165, 1166, 1, 1, 1, 1167, 1168, 1169, 1, 1170, 1, 1171, 1, 1172, 1, 1173, 1, 1174, 1, 1175, 1, 1176, 1, 1177, 1, 1178, 1, 1179, 1, 1180, 1, 1181, 1, 1182, 1, 1183, 1, 1184, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1185, 1, 1186, 1, 1187, 1, 1188, 1, 1189, 1, 1190, 1, 1191, 1, 1192, 1, 1193, 1, 1194, 1, 1195, 1, 1196, 1, 1197, 1, 1198, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1199, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1200, 1, 1201, 1, 1, 1, 1202, 1, 1203, 1, 1204, 1, 1205, 1, 1206, 1, 1207, 1, 1208, 1, 1209, 1, 1210, 1, 1211, 1, 1212, 1, 1213, 1, 1214, 1, 1215, 1, 1216, 1, 1217, 1, 1218, 1, 1219, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1220, 1, 1221, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1222, 1, 1223, 1, 1224, 1, 1225, 1, 1226, 1, 1227, 1, 1228, 1, 1229, 1, 1230, 1, 1231, 1, 1232, 1, 1233, 1, 1234, 1, 1235, 1, 1236, 1, 1237, 1, 1238, 1, 1239, 1, 1240, 1, 1241, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1242, 1, 1243, 1, 1244, 1, 1245, 1, 1246, 1, 1247, 1, 1248, 1, 1249, 1, 1250, 1, 1251, 1, 1252, 1, 1253, 1, 1, 1, 1, 1, 1, 1, 1, 1254, 1, 1, 1, 1255, 1, 1256, 1, 1257, 1, 1258, 1, 1259, 1, 1260, 1, 1261, 1, 1262, 1, 1263, 1, 1264, 1, 1265, 1, 1266, 1, 1267, 1, 1268, 1, 1269, 1, 1270, 1, 1271, 1, 1272, 1, 1273, 1, 1274, 1, 1275, 1, 1276, 1, 1277, 1, 1, 1278, 1, 1, 1, 1, 1, 1, 1, 1, 1279, 1, 1, 1, 1280, 1, 1281, 1, 1282, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1283, 1, 1284, 1, 1285, 1, 1286, 1, 1287, 1, 1288, 1, 1289, 1, 1290, 1, 1291, 1, 1292, 1, 1293, 1, 1294, 1, 1295, 1, 1296, 1, 1297, 1, 1298, 1, 1299, 1, 1300, 1, 1301, 1, 1302, 1, 1303, 1, 1304, 1, 1305, 1, 1306, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1307, 1, 1308, 1, 1, 1309, 1, 1, 1, 1, 1, 1, 1, 1, 1310, 1, 1, 1, 1311, 1, 1312, 1, 1313, 1, 1314, 1, 1315, 1, 1316, 1, 1317, 1, 1318, 1, 1319, 1, 1320, 1, 1321, 1, 1322, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1323, 1, 1324, 1, 1325, 1, 1326, 1, 1327, 1, 1328, 1, 1329, 1, 1330, 1, 1331, 1, 1332, 1, 1333, 1, 1334, 1, 1335, 1, 1336, 1, 1337, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1338, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1339, 1, 1340, 1, 1341, 1342, 1, 1, 1, 1, 1, 1343, 1344, 1, 1345, 1, 1, 1, 1346, 1347, 1, 1348, 1, 1349, 1, 1350, 1, 1351, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1352, 1353, 1, 1354, 1, 1355, 1, 1356, 1, 1357, 1, 1358, 1, 1359, 1, 1360, 1, 1361, 1, 1362, 1, 1363, 1, 1364, 1, 1365, 1, 1366, 1, 1367, 1, 1368, 1, 1369, 1, 1370, 1, 1371, 1, 1372, 1, 1373, 1, 1374, 1, 1375, 1, 1376, 1, 1, 1, 1377, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1378, 1, 1379, 1, 1380, 1, 1381, 1, 1382, 1, 1383, 1, 1384, 1, 1385, 1, 1386, 1, 1387, 1, 1388, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1389, 1, 1390, 1, 1391, 1, 1392, 1393, 1, 1394, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1395, 1, 1396, 1397, 1398, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1399, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1400, 1, 1401, 1, 1, 1, 1402, 1, 1403, 1, 1404, 1, 1405, 1, 1406, 1, 1407, 1, 1408, 1, 1409, 1, 1410, 1, 1411, 1, 1412, 1, 1413, 1, 1414, 1, 1415, 1, 1416, 1, 1417, 1, 1, 1, 1, 1, 1, 1418, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1419, 1, 1420, 1, 1421, 1, 1422, 1, 1423, 1, 1424, 1, 1425, 1, 1426, 1, 1427, 1, 1428, 1, 1429, 1, 1430, 1, 1431, 1, 1432, 1, 1433, 1, 1434, 1, 1435, 1, 1436, 1, 1437, 1, 1438, 1, 1439, 1, 1440, 1, 1441, 1, 1442, 1, 1443, 1, 1444, 1, 1445, 1, 1446, 1, 1447, 1, 1448, 1, 1449, 1, 1450, 1, 1451, 1, 1452, 1, 1453, 1, 1454, 1, 1455, 1, 1456, 1, 1457, 1, 1458, 1, 1459, 1, 1460, 1, 1461, 1, 1462, 1, 1463, 1, 1464, 1, 1465, 1, 1466, 1, 1467, 1, 1468, 1, 1469, 1, 1470, 1, 1471, 1, 1, 1, 1, 1, 1, 1472, 1, 1473, 1, 1474, 1, 1475, 1, 1476, 1, 1477, 1, 1478, 1, 1479, 1, 1480, 1, 1481, 1, 1482, 1, 1483, 1, 1484, 1, 1485, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1486, 1, 1487, 1, 1488, 1, 1489, 1, 1490, 1, 1491, 1, 1492, 1, 1493, 1, 1494, 1, 1495, 1, 1496, 1, 1497, 1, 1498, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1499, 1, 1500, 1, 1501, 1, 1, 1, 1, 1, 1502, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1503, 1, 1504, 1, 1505, 1, 1506, 1, 1507, 1, 1508, 1, 1509, 1, 1510, 1, 1511, 1, 1512, 1, 1513, 1, 1514, 1, 1515, 1, 1516, 1, 1517, 1, 1518, 1, 1519, 1, 1520, 1, 1521, 1, 1, 1, 1, 1, 1, 1522, 1, 1, 1523, 1, 1524, 1, 1525, 1, 1526, 1, 1527, 1, 1528, 1, 1529, 1, 1530, 1, 1531, 1, 1532, 1, 1533, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1534, 1, 1535, 1, 1536, 1, 1537, 1, 1538, 1, 1539, 1, 1540, 1, 1541, 1, 1542, 1, 1543, 1, 1544, 1, 1545, 1, 1546, 1, 1547, 1, 1548, 1, 1549, 1, 1550, 1, 1551, 1, 1552, 1, 1553, 1, 1554, 1, 1555, 1, 1556, 1, 1557, 1, 1558, 1, 1559, 1, 1560, 1, 1, 1, 1, 1, 1, 1561, 1, 1562, 1, 1563, 1, 1564, 1, 1565, 1, 1566, 1, 1567, 1, 1568, 1, 1569, 1, 1570, 1, 1, 1, 1, 1, 1, 1571, 1, 1572, 1, 1573, 1, 1574, 1, 1575, 1, 1576, 1, 1577, 1, 1578, 1, 1579, 1, 1580, 1, 1581, 1, 1582, 1, 1583, 1, 1584, 1, 1585, 1, 1586, 1, 1587, 1, 1588, 1, 1589, 1, 1590, 1, 1591, 1592, 1593, 1, 1, 1, 1, 1594, 1, 1, 1, 1, 1, 1, 1595, 1596, 1, 1597, 1, 1598, 1, 1599, 1, 1600, 1, 1601, 1, 1602, 1, 1603, 1, 1604, 1, 1605, 1, 1606, 1, 1607, 1, 1608, 1, 1609, 1, 1610, 1, 1611, 1, 1612, 1, 1613, 1, 1614, 1, 1615, 1, 1616, 1, 1617, 1, 1618, 1, 1619, 1, 1620, 1, 1621, 1, 1622, 1, 1623, 1, 1624, 1, 1625, 1, 1626, 1, 1627, 1, 1628, 1, 1629, 1, 1630, 1, 1631, 1, 1632, 1, 1633, 1, 1634, 1, 1635, 1, 1636, 1, 1637, 1, 1638, 1, 1639, 1, 1640, 1, 1641, 1, 1642, 1, 1643, 1, 1644, 1, 1645, 1, 1646, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1647, 1, 1648, 1, 1649, 1, 1650, 1, 1651, 1, 1652, 1, 1653, 1, 1654, 1, 1655, 1, 1656, 1, 1657, 1, 1658, 1, 1659, 1, 1660, 1, 1661, 1, 1662, 1, 1, 1, 1, 1, 1, 1663, 1, 1664, 1, 1665, 1, 1, 1, 1, 1, 1666, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1667, 1, 1, 1, 1, 1, 1668, 1, 1669, 1, 1670, 1, 1671, 1, 1672, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1673, 1, 1674, 1, 1675, 1, 1676, 1, 1677, 1, 1678, 1, 1679, 1, 1680, 1, 1681, 1, 1682, 1, 1683, 1, 1684, 1, 1685, 1, 1686, 1, 1687, 1, 1688, 1, 1689, 1, 1690, 1, 1691, 1, 1692, 1, 1693, 1, 1694, 1, 1695, 1, 1696, 1, 1697, 1, 1698, 1, 1699, 1, 1700, 1, 1701, 1, 1702, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1703, 1, 1704, 1, 1705, 1, 1706, 1, 1707, 1, 1708, 1, 1709, 1, 1710, 1, 1711, 1, 1712, 1, 1713, 1, 1714, 1, 1715, 1, 1716, 1, 1717, 1, 1718, 1, 1719, 1, 1720, 1, 1721, 1, 1722, 1, 1723, 1, 1724, 1, 1725, 1, 1726, 1, 1727, 1, 1728, 1, 1729, 1, 1730, 1, 1731, 1, 1732, 1, 1733, 1, 1, 1, 1, 1, 1734, 1, 1735, 1, 1736, 1, 1737, 1, 1738, 1, 1739, 1, 1740, 1, 1741, 1, 1742, 1, 1743, 1, 1744, 1, 1745, 1, 1746, 1, 1747, 1, 1748, 1, 1749, 1, 1750, 1, 1751, 1, 1752, 1, 1753, 1, 1754, 1, 1, 1, 1, 1755, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1756, 1, 1757, 1, 1758, 1, 1759, 1, 1760, 1, 1761, 1, 1762, 1, 1763, 1, 1764, 1, 1765, 1, 1766, 1, 1767, 1768, 1, 1, 1769, 1, 1, 1, 1, 1, 1770, 1, 1, 1, 1771, 1, 1772, 1, 1773, 1, 1774, 1, 1775, 1, 1776, 1, 1777, 1, 1, 1, 1, 1, 1, 1, 1778, 1, 1779, 1, 1780, 1, 1781, 1, 1782, 1, 1783, 1, 1784, 1, 1785, 1, 1786, 1, 1787, 1, 1788, 1, 1789, 1, 1790, 1, 1791, 1, 1792, 1, 1793, 1, 1794, 1, 1795, 1, 1796, 1, 1797, 1, 1798, 1, 1799, 1, 1800, 1, 1801, 1, 1802, 1, 1803, 1, 1804, 1, 1805, 1, 1806, 1, 1807, 1, 1808, 1, 1809, 1, 1810, 1, 1811, 1, 1812, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1813, 1, 1814, 1, 1815, 1816, 1, 1, 1, 1, 1, 1, 1, 1, 1817, 1, 1, 1, 1818, 1819, 1820, 1, 1821, 1, 1822, 1, 1823, 1, 1824, 1, 1825, 1, 1826, 1, 1827, 1, 1828, 1, 1829, 1, 1, 1, 1830, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1831, 1, 1832, 1, 1833, 1, 1834, 1, 1835, 1, 1836, 1, 1837, 1, 1838, 1, 1839, 1, 1840, 1, 1841, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1842, 1, 1, 1, 1843, 1, 1844, 1, 1845, 1, 1846, 1, 1847, 1, 1848, 1, 1849, 1, 1, 1, 1, 1, 1, 1850, 1, 1851, 1, 1852, 1, 1853, 1, 1854, 1, 1855, 1, 1856, 1, 1857, 1, 1858, 1, 1859, 1, 1860, 1, 1861, 1, 1862, 1, 1863, 1, 1864, 1, 1865, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1866, 1, 1867, 1, 1868, 1, 1869, 1, 1870, 1, 1871, 1, 1872, 1, 1873, 1, 1874, 1, 1875, 1, 1876, 1, 1877, 1, 1878, 1, 1879, 1, 1880, 1, 1881, 1, 1882, 1, 1883, 1, 1884, 1, 1885, 1, 1886, 1, 1887, 1, 1888, 1, 1889, 1, 1890, 1, 1891, 1, 1892, 1, 1893, 1, 1894, 1, 1895, 1, 1896, 1, 1, 1, 1, 1897, 1, 1898, 1, 1899, 1, 1900, 1, 1901, 1, 1902, 1, 1903, 1, 1904, 1, 1905, 1, 1906, 1, 1907, 1, 1908, 1, 1909, 1, 1910, 1, 1911, 1, 1912, 1, 1913, 1, 1914, 1, 1915, 1, 1916, 1, 1917, 1, 1918, 1, 1919, 1, 1920, 1, 1921, 1, 1922, 1, 1923, 1, 1924, 1, 1925, 1, 1926, 1, 1927, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1928, 1, 1929, 1, 1, 1, 1930, 1, 1931, 1, 1932, 1, 1933, 1, 1934, 1, 1935, 1, 1936, 1, 1937, 1, 1938, 1, 1939, 1, 1940, 1, 1941, 1, 1942, 1, 1943, 1, 1944, 1, 1945, 1, 1946, 1, 1947, 1, 1948, 1, 1949, 1, 1950, 1, 1951, 1, 1952, 1, 1, 1, 1, 1, 1, 1, 1953, 1954, 1955, 1, 1956, 1957, 1, 1, 1, 1958, 1, 1959, 1, 1960, 1, 1961, 1962, 1963, 1, 1964, 1, 1965, 1, 1, 1, 1, 1, 1966, 1, 1967, 1, 1968, 1, 1969, 1, 1970, 1, 1971, 1, 1972, 1, 1973, 1, 1974, 1, 1975, 1, 1976, 1, 1977, 1, 1978, 1, 1979, 1, 1980, 1, 1981, 1, 1982, 1, 1983, 1, 1984, 1, 1985, 1, 1986, 1, 1987, 1, 1988, 1, 1989, 1, 1990, 1, 1991, 1, 1992, 1, 1993, 1, 1994, 1, 1995, 1, 1996, 1, 1997, 1, 1, 1, 1, 1998, 1, 1, 1, 1, 1, 1, 1999, 1, 2000, 1, 2001, 1, 2002, 1, 2003, 1, 2004, 1, 2005, 1, 2006, 1, 2007, 1, 2008, 1, 2009, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2010, 1, 2011, 1, 2012, 1, 2013, 1, 2014, 1, 2015, 1, 2016, 1, 2017, 1, 2018, 1, 2019, 1, 2020, 1, 2021, 1, 2022, 1, 2023, 1, 2024, 1, 2025, 1, 2026, 1, 2027, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2028, 2029, 2030, 1, 1, 1, 1, 2031, 1, 1, 1, 1, 1, 1, 2032, 2033, 1, 2034, 1, 2035, 1, 2036, 1, 2037, 1, 2038, 1, 2039, 1, 2040, 1, 2041, 1, 2042, 1, 2043, 1, 2044, 1, 2045, 1, 2046, 1, 2047, 1, 2048, 1, 2049, 1, 2050, 1, 2051, 1, 2052, 1, 2053, 1, 2054, 1, 2055, 1, 2056, 1, 2057, 1, 2058, 1, 2059, 1, 2060, 1, 2061, 1, 2062, 1, 2063, 1, 2064, 1, 2065, 1, 2066, 1, 2067, 1, 2068, 1, 2069, 1, 2070, 1, 2071, 1, 2072, 1, 2073, 1, 2074, 1, 2075, 1, 2076, 1, 2077, 2078, 1, 2079, 1, 2080, 1, 2081, 1, 2082, 1, 2083, 1, 2084, 1, 2085, 1, 2086, 1, 2087, 1, 2088, 1, 2089, 1, 2090, 1, 2091, 1, 2092, 1, 2093, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2094, 1, 2095, 1, 2096, 1, 2097, 1, 2098, 1, 2099, 1, 2100, 1, 2101, 1, 2102, 1, 2103, 1, 2104, 1, 1, 1, 1, 1, 1, 2105, 1, 1, 2106, 1, 2107, 1, 2108, 1, 2109, 1, 2110, 1, 2111, 1, 2112, 1, 2113, 1, 2114, 1, 2115, 1, 2116, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2117, 1, 2118, 1, 1, 1, 1, 2119, 1, 1, 1, 1, 1, 1, 2120, 2121, 1, 2122, 1, 2123, 1, 2124, 1, 2125, 1, 2126, 1, 2127, 1, 2128, 1, 2129, 1, 2130, 1, 2131, 1, 2132, 1, 2133, 1, 2134, 1, 2135, 1, 2136, 1, 2137, 1, 2138, 1, 2139, 1, 2140, 1, 2141, 1, 2142, 1, 2143, 1, 2144, 1, 2145, 1, 2146, 1, 2147, 1, 2148, 1, 2149, 1, 2150, 1, 2151, 1, 2152, 1, 2153, 1, 2154, 1, 2155, 1, 2156, 1, 2157, 1, 2158, 1, 1, 1, 1, 2159, 1, 2160, 1, 2161, 1, 2162, 1, 2163, 1, 2164, 1, 2165, 1, 2166, 1, 2167, 1, 2168, 1, 2169, 1, 2170, 1, 2171, 1, 2172, 1, 2173, 1, 2174, 1, 2175, 1, 2176, 1, 2177, 1, 2178, 1, 2179, 1, 2180, 1, 2181, 1, 2182, 1, 2183, 1, 2184, 1, 2185, 1, 2186, 1, 2187, 1, 2188, 1, 2189, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2190, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2191, 1, 2192, 1, 2193, 1, 2194, 1, 2195, 1, 2196, 1, 2197, 1, 2198, 1, 2199, 1, 2200, 1, 2201, 1, 2202, 1, 2203, 1, 2204, 1, 2205, 1, 2206, 1, 2207, 1, 1, 1, 2208, 1, 2209, 1, 2210, 1, 2211, 1, 2212, 1, 2213, 1, 2214, 1, 2215, 1, 2216, 1, 2217, 1, 2218, 1, 2219, 1, 2220, 1, 2221, 1, 2222, 1, 2223, 1, 2224, 1, 2225, 1, 2226, 1, 2227, 1, 2228, 1, 2229, 1, 2230, 1, 2231, 1, 2232, 1, 2233, 1, 1, 1, 1, 1, 1, 2234, 1, 1, 2235, 1, 2236, 1, 2237, 1, 2238, 1, 2239, 1, 2240, 1, 2241, 1, 2242, 1, 2243, 1, 2244, 1, 1, 1, 2245, 1, 2246, 1, 2247, 1, 2248, 1, 2249, 1, 2250, 1, 2251, 1, 2252, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2253, 1, 2254, 1, 2255, 1, 2256, 1, 2257, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2258, 1, 2259, 1, 2260, 1, 2261, 1, 2262, 1, 2263, 1, 2264, 1, 2265, 1, 2266, 1, 2267, 1, 2268, 1, 2269, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2270, 1, 2271, 1, 2272, 1, 2273, 1, 2274, 1, 2275, 1, 2276, 2277, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2278, 1, 2279, 1, 2280, 1, 2281, 1, 2282, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2283, 1, 2284, 1, 2285, 1, 2286, 1, 2287, 1, 2288, 1, 2289, 1, 2290, 1, 2291, 1, 2292, 1, 2293, 1, 2294, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2295, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2296, 2297, 1, 2298, 1, 2299, 1, 2300, 1, 2301, 1, 2302, 1, 2303, 1, 2304, 1, 2305, 1, 2306, 1, 2307, 1, 2308, 1, 2309, 1, 2310, 1, 2311, 1, 2312, 1, 2313, 1, 2314, 1, 2315, 1, 2316, 1, 2317, 1, 2318, 1, 2319, 1, 2320, 1, 2321, 1, 2322, 1, 2323, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2324, 1, 2325, 1, 2326, 1, 2327, 1, 2328, 1, 2329, 1, 2330, 1, 2331, 1, 2332, 1, 2333, 1, 2334, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2335, 2336, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2337, 1, 2338, 1, 2339, 1, 2340, 1, 2341, 1, 2342, 1, 2343, 1, 2344, 1, 2345, 1, 2346, 1, 2347, 1, 2348, 1, 2349, 1, 2350, 1, 2351, 1, 2352, 1, 2353, 1, 2354, 1, 2355, 1, 2356, 1, 2357, 1, 2358, 1, 2359, 1, 2360, 1, 2361, 1, 2362, 1, 2363, 1, 2364, 1, 2365, 1, 2366, 1, 2367, 1, 2368, 1, 2369, 1, 2370, 1, 2371, 1, 2372, 1, 2373, 1, 2374, 1, 2375, 1, 2376, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2377, 1, 2378, 2379, 1, 2380, 2381, 1, 1, 1, 1, 1, 2382, 1, 2383, 2384, 1, 2385, 2386, 2387, 2388, 2389, 1, 2390, 1, 2391, 1, 2392, 1, 2393, 1, 2394, 1, 2395, 1, 2396, 1, 2397, 1, 2398, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2399, 1, 2400, 1, 2401, 1, 2402, 1, 2403, 1, 2404, 1, 2405, 1, 2406, 1, 2407, 1, 2408, 1, 2409, 1, 2410, 1, 2411, 1, 2412, 1, 2413, 1, 2414, 1, 1, 1, 2415, 1, 1, 1, 2416, 1, 2417, 1, 2418, 1, 2419, 1, 2420, 1, 2421, 1, 2422, 1, 2423, 1, 2424, 1, 2425, 1, 2426, 1, 2427, 1, 2428, 1, 2429, 1, 2430, 1, 2431, 1, 2432, 1, 2433, 1, 2434, 1, 2435, 1, 2436, 1, 2437, 1, 2438, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2439, 1, 2440, 1, 2441, 1, 2442, 1, 2443, 1, 2444, 1, 2445, 1, 2446, 1, 2447, 1, 2448, 1, 2449, 1, 2450, 1, 2451, 1, 2452, 1, 2453, 1, 2454, 1, 2455, 1, 2456, 1, 2457, 1, 1, 1, 1, 1, 1, 1, 1, 2458, 1, 2459, 1, 2460, 1, 2461, 1, 2462, 1, 2463, 1, 2464, 1, 2465, 2466, 1, 2467, 1, 2468, 1, 2469, 1, 2470, 1, 2471, 1, 2472, 1, 2473, 1, 2474, 1, 2475, 1, 2476, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2477, 1, 2478, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2479, 1, 2480, 1, 2481, 1, 2482, 1, 2483, 1, 2484, 1, 1, 1, 1, 1, 2485, 1, 2486, 1, 2487, 1, 2488, 1, 2489, 1, 2490, 1, 2491, 1, 2492, 1, 2493, 1, 2494, 1, 2495, 1, 2496, 1, 2497, 1, 2498, 1, 2499, 1, 2500, 1, 2501, 1, 2502, 1, 1, 2503, 1, 2504, 2505, 1, 1, 2506, 1, 1, 2507, 1, 1, 2508, 2509, 1, 2510, 1, 2511, 1, 2512, 1, 2513, 1, 2514, 1, 2515, 1, 2516, 1, 2517, 1, 2518, 1, 2519, 1, 2520, 1, 2521, 1, 2522, 1, 2523, 1, 2524, 1, 2525, 1, 2526, 1, 2527, 1, 2528, 1, 2529, 1, 2530, 1, 2531, 1, 2532, 1, 1, 1, 1, 1, 1, 2533, 1, 2534, 1, 2535, 1, 2536, 1, 2537, 1, 2538, 1, 2539, 1, 2540, 1, 2541, 1, 2542, 1, 2543, 1, 2544, 1, 2545, 1, 2546, 1, 2547, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2548, 1, 1, 1, 2549, 1, 1, 1, 1, 1, 2550, 1, 2551, 1, 2552, 1, 2553, 1, 2554, 1, 2555, 1, 2556, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2557, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2558, 2559, 1, 2560, 1, 2561, 1, 2562, 1, 2563, 1, 2564, 1, 2565, 1, 2566, 1, 2567, 1, 2568, 1, 2569, 1, 2570, 1, 2571, 1, 2572, 1, 2573, 1, 2574, 1, 2575, 1, 2576, 1, 2577, 1, 2578, 1, 2579, 1, 2580, 1, 2581, 1, 2582, 1, 2583, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2584, 1, 2585, 1, 2586, 1, 2587, 1, 2588, 1, 2589, 1, 2590, 1, 2591, 1, 2592, 1, 2593, 1, 2594, 1, 2595, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2596, 1, 2597, 1, 2598, 1, 2599, 1, 1, 1, 1, 1, 2600, 1, 2601, 1, 2602, 1, 2603, 1, 2604, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2605, 1, 1, 1, 1, 1, 1, 1, 1, 2606, 1, 1, 1, 2607, 1, 2608, 1, 2609, 1, 2610, 1, 2611, 1, 2612, 1, 2613, 1, 2614, 1, 2615, 1, 2616, 1, 2617, 1, 2618, 1, 1, 2619, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2620, 1, 2621, 1, 2622, 2623, 1, 2624, 2625, 1, 1, 1, 1, 1, 2626, 1, 1, 2627, 2628, 1, 2629, 1, 2630, 1, 2631, 1, 2632, 1, 2633, 1, 2634, 1, 2635, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2636, 1, 1, 1, 2637, 1, 2638, 1, 2639, 1, 2640, 1, 2641, 1, 2642, 1, 2643, 1, 2644, 1, 2645, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2646, 1, 2647, 1, 2648, 1, 2649, 1, 1, 1, 2650, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2651, 1, 2652, 1, 2653, 1, 2654, 1, 2655, 1, 2656, 1, 2657, 1, 2658, 1, 2659, 1, 2660, 1, 2661, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2662, 1, 2663, 1, 2664, 1, 2665, 1, 2666, 1, 2667, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2668, 1, 2669, 1, 1, 1, 1, 2670, 1, 2671, 1, 2672, 1, 2673, 1, 2674, 1, 2675, 1, 2676, 1, 2677, 1, 2678, 1, 2679, 1, 2680, 1, 2681, 1, 2682, 1, 2683, 1, 2684, 1, 2685, 1, 2686, 1, 2687, 1, 2688, 1, 2689, 1, 2690, 1, 2691, 1, 2692, 1, 2693, 1, 2694, 1, 2695, 1, 2696, 1, 2697, 1, 2698, 1, 2699, 1, 2700, 1, 2701, 1, 2702, 1, 2703, 1, 2704, 1, 2705, 1, 2706, 1, 2707, 1, 2708, 2709, 1, 2710, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2711, 2712, 2713, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2714, 1, 2715, 1, 1, 1, 2716, 1, 2717, 1, 2718, 1, 2719, 1, 2720, 1, 2721, 1, 2722, 1, 2723, 1, 2724, 1, 2725, 1, 2726, 1, 2727, 1, 2728, 1, 2729, 1, 2730, 1, 2731, 1, 1, 1, 1, 1, 1, 2732, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2733, 1, 2734, 1, 2735, 1, 2736, 1, 2737, 1, 2738, 1, 2739, 1, 2740, 1, 2741, 1, 2742, 1, 2743, 1, 2744, 1, 2745, 1, 2746, 1, 2747, 1, 2748, 1, 2749, 1, 2750, 1, 2751, 1, 2752, 1, 2753, 1, 2754, 1, 2755, 1, 2756, 1, 2757, 1, 2758, 1, 2759, 1, 2760, 1, 2761, 1, 2762, 1, 2763, 1, 2764, 1, 2765, 1, 2766, 1, 2767, 1, 2768, 1, 2769, 1, 2770, 1, 2771, 1, 2772, 1, 2773, 1, 2774, 1, 2775, 1, 2776, 1, 2777, 1, 2778, 1, 2779, 1, 2780, 1, 2781, 1, 2782, 1, 2783, 1, 2784, 1, 1, 1, 1, 1, 1, 2785, 1, 2786, 1, 2787, 1, 2788, 1, 2789, 1, 2790, 1, 2791, 1, 2792, 1, 2793, 1, 2794, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2795, 1, 2796, 1, 2797, 1, 1, 1, 1, 1, 2798, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2799, 1, 2800, 1, 2801, 1, 2802, 1, 2803, 1, 2804, 1, 2805, 1, 2806, 1, 2807, 1, 2808, 1, 2809, 1, 2810, 1, 2811, 1, 2812, 1, 2813, 1, 2814, 1, 2815, 1, 2816, 1, 2817, 1, 1, 1, 1, 1, 1, 2818, 1, 1, 2819, 1, 2820, 1, 2821, 1, 2822, 1, 2823, 1, 2824, 1, 2825, 1, 2826, 1, 2827, 1, 2828, 1, 2829, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2830, 1, 2831, 1, 2832, 1, 2833, 1, 2834, 1, 2835, 1, 2836, 1, 2837, 1, 2838, 1, 2839, 1, 2840, 1, 2841, 1, 2842, 1, 2843, 1, 2844, 1, 2845, 1, 2846, 1, 2847, 1, 2848, 1, 2849, 1, 2850, 1, 2851, 1, 2852, 1, 2853, 1, 2854, 1, 2855, 1, 2856, 1, 1, 1, 1, 1, 1, 2857, 1, 2858, 1, 2859, 1, 2860, 1, 2861, 1, 2862, 1, 2863, 1, 2864, 1, 2865, 1, 2866, 1, 1, 1, 1, 1, 1, 2867, 1, 2868, 1, 2869, 1, 2870, 1, 2871, 1, 2872, 1, 2873, 1, 2874, 1, 2875, 1, 2876, 1, 1, 1, 1, 2877, 1, 2878, 1, 2879, 1, 2880, 1, 2881, 1, 2882, 1, 2883, 1, 2884, 1, 2885, 1, 2886, 1, 2887, 1, 2888, 1, 2889, 1, 2890, 1, 2891, 1, 2892, 1, 2893, 1, 2894, 1, 2895, 1, 2896, 1, 2897, 1, 2898, 1, 2899, 1, 2900, 1, 1, 1, 1, 2901, 1, 2902, 1, 2903, 1, 2904, 1, 2905, 1, 2906, 1, 2907, 1, 2908, 1, 2909, 1, 2910, 1, 2911, 1, 2912, 1, 2913, 1, 2914, 1, 2915, 1, 1, 1, 1, 1, 1, 2916, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2917, 1, 2918, 1, 1, 2919, 1, 2920, 2921, 1, 1, 1, 2922, 1, 2923, 1, 2924, 1, 2925, 2926, 2927, 1, 2928, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2929, 1, 2930, 1, 2931, 1, 2932, 1, 2933, 1, 2934, 1, 2935, 1, 2936, 1, 2937, 1, 2938, 1, 2939, 1, 2940, 1, 2941, 1, 2942, 1, 2943, 1, 2944, 1, 2945, 1, 2946, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2947, 1, 1, 1, 2948, 1, 1, 1, 2949, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2950, 1, 2951, 1, 2952, 1, 2953, 1, 2954, 1, 2955, 1, 2956, 1, 2957, 1, 2958, 1, 2959, 1, 2960, 1, 2961, 1, 2962, 1, 2963, 1, 2964, 1, 2965, 1, 2966, 1, 2967, 1, 2968, 1, 1, 1, 1, 1, 1, 1, 2969, 1, 1, 1, 1, 1, 2970, 1, 1, 2971, 1, 2972, 1, 2973, 1, 2974, 1, 2975, 1, 2976, 1, 2977, 1, 2978, 1, 2979, 1, 2980, 1, 2981, 1, 2982, 1, 2983, 1, 2984, 1, 2985, 1, 2986, 1, 2987, 1, 2988, 1, 2989, 1, 2990, 1, 2991, 1, 2992, 1, 2993, 1, 2994, 1, 2995, 1, 2996, 1, 2997, 1, 2998, 1, 2999, 1, 3000, 1, 3001, 1, 3002, 1, 3003, 1, 3004, 1, 3005, 1, 3006, 1, 3007, 1, 3008, 1, 3009, 1, 3010, 1, 3011, 1, 3012, 1, 3013, 1, 3014, 1, 3015, 1, 3016, 1, 3017, 1, 3018, 1, 3019, 1, 3020, 1, 3021, 1, 3022, 1, 3023, 1, 3024, 1, 1, 3025, 1, 3026, 1, 3027, 1, 3028, 1, 3029, 1, 3030, 1, 3031, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3032, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3033, 1, 3034, 1, 3035, 1, 3036, 1, 3037, 1, 3038, 1, 3039, 1, 3040, 1, 3041, 1, 3042, 1, 3043, 1, 3044, 1, 3045, 1, 3046, 1, 3047, 1, 3048, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3049, 1, 3050, 1, 3051, 1, 3052, 1, 3053, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3054, 1, 3055, 1, 3056, 1, 3057, 1, 3058, 1, 3059, 1, 3060, 1, 3061, 1, 3062, 1, 3063, 1, 3064, 1, 3065, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3066, 1, 3067, 1, 3068, 1, 3069, 1, 3070, 1, 3071, 1, 3072, 1, 3073, 1, 3074, 1, 3075, 1, 3076, 1, 3077, 1, 3078, 1, 3079, 1, 3080, 1, 3081, 1, 3082, 1, 3083, 3084, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3085, 1, 1, 3086, 1, 3087, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3088, 1, 3089, 1, 3090, 1, 3091, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3092, 1, 3093, 1, 3094, 1, 3095, 1, 3096, 1, 3097, 1, 3098, 1, 1, 1, 1, 3099, 1, 3100, 1, 3101, 1, 3102, 1, 3103, 1, 3104, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3105, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3106, 3107, 1, 3108, 1, 3109, 1, 3110, 1, 3111, 1, 3112, 1, 3113, 1, 3114, 1, 3115, 1, 3116, 1, 3117, 1, 3118, 1, 3119, 1, 3120, 1, 3121, 1, 3122, 1, 3123, 1, 3124, 1, 3125, 1, 3126, 1, 3127, 1, 3128, 1, 3129, 1, 3130, 1, 3131, 1, 3132, 1, 3133, 1, 3134, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3135, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3136, 1, 3137, 1, 3138, 1, 3139, 1, 3140, 1, 3141, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3142, 1, 3143, 1, 3144, 1, 3145, 1, 3146, 1, 3147, 1, 3148, 1, 3149, 1, 3150, 1, 3151, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3152, 3153, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3154, 1, 3155, 1, 1, 3156, 1, 3157, 3158, 1, 1, 1, 1, 1, 3159, 1, 1, 3160, 3161, 1, 3162, 1, 3163, 1, 3164, 1, 3165, 1, 3166, 1, 3167, 1, 3168, 1, 3169, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3170, 1, 3171, 1, 3172, 1, 3173, 1, 3174, 1, 3175, 1, 3176, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3177, 1, 3178, 1, 3179, 1, 3180, 1, 1, 1, 3181, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3182, 1, 3183, 1, 3184, 1, 3185, 1, 3186, 1, 3187, 1, 3188, 1, 3189, 1, 3190, 1, 3191, 1, 3192, 1, 3193, 1, 3194, 1, 1, 1, 3195, 1, 3196, 1, 3197, 1, 3198, 1, 3199, 1, 3200, 1, 3201, 1, 3202, 1, 3203, 1, 3204, 1, 3205, 1, 3206, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3207, 1, 3208, 1, 3209, 1, 3210, 1, 3211, 1, 3212, 1, 3213, 1, 3214, 1, 3215, 1, 3216, 1, 3217, 1, 3218, 1, 3219, 1, 3220, 1, 3221, 1, 3222, 1, 3223, 1, 3224, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3225, 3226, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3227, 1, 3228, 1, 3229, 1, 3230, 1, 3231, 1, 3232, 1, 3233, 1, 3234, 1, 3235, 1, 3236, 1, 3237, 1, 3238, 1, 3239, 1, 3240, 1, 3241, 1, 3242, 1, 3243, 1, 3244, 1, 3245, 1, 3246, 1, 3247, 1, 3248, 1, 3249, 1, 3250, 1, 3251, 1, 3252, 1, 3253, 1, 3254, 1, 3255, 1, 3256, 1, 3257, 1, 3258, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3259, 1, 3260, 1, 3261, 1, 3262, 1, 3263, 1, 3264, 1, 3265, 1, 3266, 3267, 3268, 3269, 1, 3270, 3271, 1, 1, 1, 1, 1, 3272, 3273, 3274, 3275, 1, 3276, 3277, 3278, 3279, 1, 3280, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3281, 1, 3282, 1, 3283, 1, 3284, 1, 3285, 1, 3286, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3287, 1, 3288, 1, 3289, 1, 3290, 1, 3291, 1, 3292, 1, 3293, 1, 3294, 1, 3295, 1, 3296, 1, 3297, 1, 3298, 1, 3299, 1, 3300, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3301, 1, 3302, 1, 3303, 1, 3304, 1, 3305, 1, 3306, 1, 3307, 1, 3308, 1, 3309, 1, 3310, 1, 3311, 1, 3312, 1, 3313, 1, 3314, 1, 3315, 1, 3316, 1, 3317, 1, 3318, 1, 3319, 1, 3320, 1, 1, 1, 1, 3321, 1, 3322, 1, 3323, 1, 3324, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3325, 1, 3326, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3327, 1, 3328, 1, 3329, 1, 3330, 1, 3331, 1, 3332, 1, 1, 1, 1, 1, 3333, 1, 3334, 1, 3335, 1, 3336, 1, 3337, 1, 3338, 1, 3339, 1, 3340, 1, 3341, 1, 3342, 1, 3343, 1, 3344, 1, 3345, 1, 3346, 1, 3347, 1, 3348, 1, 3349, 1, 3350, 1, 3351, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3352, 1, 3353, 1, 3354, 1, 3355, 1, 3356, 1, 3357, 1, 1, 1, 1, 1, 1, 1, 1, 3358, 1, 3359, 1, 3360, 1, 3361, 1, 3362, 1, 3363, 1, 3364, 1, 1, 3365, 3366, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3367, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3368, 1, 1, 3369, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3370, 1, 1, 3371, 1, 3372, 1, 3373, 1, 3374, 1, 3375, 1, 3376, 1, 1, 1, 1, 1, 1, 3377, 1, 3378, 1, 3379, 1, 3380, 1, 3381, 1, 3382, 1, 3383, 1, 3384, 1, 3385, 1, 3386, 1, 3387, 1, 3388, 1, 3389, 1, 3390, 1, 3391, 1, 3392, 1, 3393, 1, 3394, 1, 3395, 1, 3396, 1, 3397, 1, 3398, 1, 3399, 1, 3400, 1, 3401, 1, 3402, 1, 3403, 1, 3404, 1, 3405, 1, 3406, 1, 3407, 1, 3408, 1, 3409, 1, 3410, 1, 3411, 1, 3412, 1, 3413, 1, 1, 1, 1, 1, 3414, 1, 3415, 1, 3416, 1, 3417, 1, 3418, 1, 3419, 1, 3420, 1, 3421, 1, 3422, 1, 3423, 1, 3424, 1, 3425, 1, 3426, 1, 3427, 1, 3428, 1, 3429, 1, 3430, 1, 3431, 1, 3432, 1, 3433, 1, 3434, 1, 3435, 1, 3436, 1, 1, 1, 1, 1, 3437, 1, 3438, 1, 3439, 1, 3440, 1, 3441, 1, 3442, 1, 3443, 1, 3444, 1, 3445, 1, 3446, 1, 3447, 1, 3448, 1, 3449, 1, 3450, 1, 3451, 1, 3452, 1, 3453, 1, 3454, 1, 3455, 1, 3456, 1, 3457, 1, 3458, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3459, 1, 3460, 1, 3461, 1, 3462, 1, 3463, 1, 3464, 1, 3465, 1, 3466, 1, 3467, 1, 3468, 1, 3469, 1, 3470, 1, 3471, 1, 3472, 1, 3473, 1, 3474, 1, 3475, 1, 3476, 1, 3477, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3478, 3479, 3480, 3481, 3482, 1, 1, 1, 1, 1, 1, 1, 1, 3483, 1, 1, 1, 3484, 1, 1, 3485, 1, 3486, 1, 3487, 1, 3488, 1, 3489, 1, 3490, 1, 3491, 1, 3492, 1, 3493, 1, 3494, 1, 3495, 1, 3496, 1, 3497, 1, 3498, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3499, 1, 3500, 1, 3501, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3502, 1, 3503, 1, 3504, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3505, 1, 1, 1, 1, 3506, 1, 3507, 1, 3508, 1, 3509, 1, 3510, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3511, 1, 3512, 1, 3513, 1, 3514, 1, 3515, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3516, 1, 1, 1, 1, 1, 1, 3517, 3518, 1, 3519, 1, 3520, 1, 3521, 1, 3522, 1, 3523, 1, 3524, 1, 3525, 1, 3526, 1, 3527, 1, 3528, 1, 3529, 1, 3530, 1, 3531, 1, 3532, 1, 3533, 1, 3534, 1, 3535, 1, 3536, 1, 3537, 1, 3538, 1, 3539, 1, 3540, 1, 3541, 1, 3542, 1, 3543, 1, 3544, 1, 3545, 1, 3546, 1, 3547, 1, 3548, 1, 3549, 1, 3550, 1, 3551, 1, 3552, 1, 3553, 1, 3554, 1, 3555, 1, 3556, 1, 3557, 1, 3558, 1, 3559, 1, 3560, 1, 3561, 1, 3562, 1, 3563, 1, 3564, 3565, 1, 1, 1, 1, 1, 1, 1, 1, 3566, 1, 1, 1, 3567, 1, 3568, 1, 3569, 1, 3570, 1, 3571, 1, 3572, 1, 3573, 1, 3574, 1, 3575, 1, 3576, 1, 3577, 1, 3578, 1, 3579, 1, 3580, 1, 3581, 1, 3582, 1, 3583, 1, 3584, 1, 1, 3585, 1, 1, 1, 1, 1, 3586, 1, 1, 1, 3587, 1, 3588, 1, 3589, 1, 3590, 1, 3591, 1, 3592, 1, 3593, 1, 3594, 1, 3595, 1, 3596, 1, 3597, 1, 1, 1, 1, 1, 1, 1, 3598, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3599, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3600, 1, 3601, 1, 1, 3602, 1, 1, 1, 1, 1, 1, 1, 1, 3603, 1, 1, 1, 3604, 1, 3605, 1, 3606, 1, 3607, 1, 3608, 1, 3609, 1, 3610, 1, 3611, 1, 3612, 1, 3613, 1, 3614, 1, 3615, 1, 3616, 1, 3617, 1, 3618, 1, 3619, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3620, 1, 3621, 1, 3622, 1, 3623, 1, 3624, 1, 3625, 1, 3626, 1, 3627, 1, 3628, 1, 3629, 1, 3630, 1, 3631, 1, 3632, 1, 3633, 1, 3634, 1, 3635, 1, 3636, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3637, 1, 3638, 3639, 3640, 3641, 1, 1, 1, 1, 1, 1, 1, 1, 3642, 1, 1, 1, 3643, 1, 3644, 1, 3645, 1, 3646, 1, 3647, 1, 3648, 1, 3649, 1, 3650, 1, 3651, 1, 3652, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3653, 1, 3654, 1, 3655, 1, 3656, 1, 3657, 1, 3658, 1, 3659, 1, 3660, 1, 3661, 1, 3662, 1, 3663, 1, 3664, 1, 3665, 1, 3666, 1, 3667, 1, 3668, 1, 3669, 1, 3670, 1, 3671, 1, 3672, 1, 3673, 1, 3674, 1, 3675, 1, 3676, 1, 3677, 1, 3678, 1, 3679, 1, 3680, 1, 3681, 1, 3682, 1, 3683, 1, 3684, 1, 3685, 1, 3686, 3687, 3688, 1, 3689, 3690, 3691, 1, 1, 1, 1, 3692, 3693, 3694, 3695, 3696, 1, 3697, 3698, 3699, 3700, 1, 3701, 1, 3702, 1, 3703, 1, 3704, 1, 3705, 1, 3706, 1, 3707, 1, 3708, 1, 3709, 1, 3710, 1, 3711, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3712, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3713, 1, 1, 1, 1, 3714, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3715, 1, 1, 1, 3716, 1, 3717, 1, 3718, 1, 3719, 1, 3720, 1, 3721, 1, 3722, 1, 3723, 1, 3724, 1, 3725, 1, 3726, 1, 3727, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3728, 1, 3729, 1, 3730, 1, 3731, 1, 3732, 1, 3733, 1, 3734, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3735, 1, 3736, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3737, 1, 3738, 1, 3739, 1, 3740, 1, 3741, 1, 3742, 1, 3743, 1, 3744, 1, 3745, 1, 3746, 1, 3747, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3748, 1, 3749, 1, 1, 1, 1, 1, 1, 1, 1, 3750, 1, 3751, 1, 3752, 1, 3753, 1, 3754, 1, 3755, 1, 1, 3756, 1, 3757, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3758, 1, 1, 3759, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3760, 1, 1, 3761, 1, 3762, 1, 3763, 1, 3764, 1, 3765, 1, 3766, 1, 3767, 1, 3768, 1, 3769, 1, 3770, 1, 3771, 1, 3772, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3773, 1, 1, 1, 1, 1, 1, 3774, 3775, 1, 1, 1, 1, 3776, 3777, 1, 1, 1, 1, 1, 1, 3778, 1, 3779, 1, 3780, 1, 3781, 1, 3782, 1, 3783, 1, 3784, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3785, 1, 3786, 3787, 3788, 3789, 3790, 3791, 3792, 3793, 1, 3794, 1, 3795, 1, 3796, 1, 3797, 1, 3798, 1, 3799, 1, 3800, 1, 3801, 1, 3802, 1, 3803, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3804, 1, 3805, 1, 3806, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3807, 1, 3808, 1, 3809, 1, 1, 1, 3810, 1, 3811, 1, 3812, 1, 3813, 1, 3814, 1, 3815, 1, 3816, 1, 3817, 1, 3818, 1, 1, 1, 1, 1, 1, 1, 1, 3819, 1, 3820, 1, 3821, 1, 3822, 1, 3823, 1, 3824, 1, 3825, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3826, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3827, 1, 1, 1, 3828, 1, 1, 1, 3829, 1, 1, 1, 1, 1, 3830, 3831, 1, 3832, 1, 3833, 1, 3834, 1, 3835, 1, 3836, 1, 3837, 1, 3838, 1, 3839, 1, 3840, 1, 3841, 1, 3842, 1, 3843, 1, 3844, 1, 3845, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3846, 1, 3847, 1, 3848, 1, 3849, 1, 3850, 1, 3851, 1, 3852, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3853, 1, 1, 1, 1, 3854, 1, 3855, 1, 3856, 1, 3857, 1, 3858, 1, 3859, 1, 3860, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3861, 1, 3862, 1, 3863, 1, 3864, 1, 3865, 1, 3866, 1, 3867, 1, 3868, 1, 3869, 1, 3870, 1, 1, 1, 1, 1, 3871, 1, 3872, 1, 3873, 1, 3874, 1, 3875, 1, 3876, 1, 3877, 1, 3878, 1, 3879, 1, 3880, 1, 3881, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3882, 3883, 3884, 3885, 3886, 3887, 1, 1, 3888, 1, 3889, 3890, 1, 3891, 3892, 3893, 1, 3894, 3895, 1, 3896, 1, 3897, 1, 3898, 1, 3899, 1, 3900, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3901, 1, 3902, 1, 3903, 1, 3904, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3905, 1, 1, 3906, 1, 3907, 1, 3908, 1, 3909, 1, 3910, 1, 3911, 1, 3912, 1, 3913, 1, 3914, 1, 3915, 1, 3916, 1, 3917, 1, 3918, 1, 3919, 1, 3920, 1, 3921, 1, 3922, 1, 3923, 1, 3924, 1, 3925, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3926, 1, 3927, 1, 3928, 1, 3929, 3930, 1, 3931, 1, 3932, 1, 3933, 1, 3934, 1, 3935, 1, 3936, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3937, 1, 3938, 1, 3939, 1, 3940, 1, 3941, 1, 3942, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3943, 1, 3944, 1, 3945, 1, 3946, 1, 3947, 1, 3948, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3949, 1, 3950, 1, 3951, 1, 3952, 1, 3953, 1, 3954, 1, 3955, 1, 3956, 1, 3957, 1, 3958, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3959, 1, 1, 3960, 1, 3961, 1, 3962, 1, 3963, 1, 3964, 1, 3965, 1, 3966, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3967, 1, 3968, 1, 3969, 1, 3970, 1, 3971, 1, 3972, 1, 3973, 1, 3974, 1, 3975, 1, 3976, 1, 3977, 1, 3978, 1, 3979, 1, 3980, 1, 3981, 1, 1, 1, 1, 1, 1, 3982, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3983, 1, 3984, 1, 3985, 1, 3986, 1, 3987, 1, 3988, 1, 3989, 1, 3990, 1, 3991, 1, 3992, 1, 3993, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3994, 1, 1, 1, 3995, 3996, 3997, 3998, 3999, 1, 4000, 1, 1, 1, 1, 1, 1, 1, 4001, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4002, 1, 4003, 1, 4004, 1, 4005, 1, 4006, 1, 4007, 1, 4008, 1, 4009, 1, 4010, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4011, 1, 1, 1, 4012, 1, 4013, 1, 4014, 1, 4015, 1, 4016, 1, 4017, 1, 4018, 1, 4019, 1, 4020, 1, 4021, 1, 4022, 1, 4023, 1, 4024, 1, 4025, 1, 1, 4026, 1, 4027, 1, 4028, 1, 4029, 1, 4030, 1, 4031, 1, 4032, 1, 4033, 1, 4034, 1, 4035, 1, 4036, 1, 4037, 1, 4038, 1, 4039, 1, 4040, 1, 4041, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4042, 1, 4043, 1, 4044, 1, 4045, 1, 4046, 1, 4047, 1, 4048, 1, 4049, 1, 4050, 1, 4051, 1, 4052, 1, 4053, 1, 4054, 1, 4055, 1, 4056, 1, 4057, 1, 4058, 1, 4059, 1, 4060, 1, 4061, 1, 4062, 1, 4063, 1, 4064, 1, 4065, 1, 4066, 1, 4067, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4068, 1, 1, 1, 4069, 1, 4070, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4071, 1, 4072, 1, 4073, 1, 1, 1, 1, 1, 1, 4074, 4075, 1, 4076, 1, 4077, 1, 4078, 1, 4079, 1, 4080, 1, 4081, 1, 4082, 1, 4083, 1, 4084, 1, 4085, 1, 4086, 1, 4087, 1, 4088, 1, 4089, 1, 4090, 1, 4091, 1, 4092, 1, 4093, 1, 4094, 1, 4095, 1, 4096, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4097, 1, 1, 1, 1, 1, 1, 1, 4098, 1, 1, 1, 1, 1, 4099, 1, 4100, 1, 4101, 1, 4102, 1, 4103, 1, 4104, 1, 4105, 1, 4106, 1, 4107, 1, 4108, 1, 4109, 1, 4110, 1, 4111, 1, 4112, 1, 4113, 1, 4114, 1, 4115, 1, 4116, 1, 4117, 1, 4118, 1, 4119, 1, 4120, 1, 4121, 1, 4122, 1, 4123, 1, 4124, 1, 4125, 1, 4126, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4127, 1, 4128, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4129, 1, 4130, 1, 4131, 1, 4132, 1, 4133, 1, 4134, 1, 4135, 1, 4136, 1, 1, 1, 4137, 1, 1, 4138, 4139, 1, 4140, 1, 4141, 1, 4142, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4143, 1, 4144, 1, 4145, 1, 4146, 1, 4147, 1, 4148, 1, 4149, 1, 4150, 1, 4151, 1, 1, 1, 4152, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4153, 4154, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4155, 1, 4156, 1, 1, 1, 4157, 1, 1, 1, 1, 4158, 1, 1, 4159, 1, 1, 1, 4160, 4161, 4162, 1, 4163, 1, 1, 1, 1, 1, 4164, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4165, 1, 1, 1, 1, 1, 4166, 1, 4167, 1, 4168, 1, 4169, 1, 4170, 1, 4171, 1, 1, 1, 1, 1, 1, 1, 1, 4172, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4173, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4174, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4175, 1, 4176, 1, 4177, 1, 4178, 1, 4179, 1, 4180, 1, 1, 1, 1, 1, 4181, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4182, 1, 1, 1, 1, 1, 4183, 1, 4184, 1, 4185, 1, 4186, 1, 4187, 1, 4188, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4189, 1, 1, 1, 4190, 1, 1, 1, 1, 1, 4191, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4192, 1, 1, 1, 4193, 1, 1, 1, 1, 1, 4194, 1, 4195, 1, 4196, 1, 4197, 1, 4198, 1, 4199, 1, 4200, 1, 4201, 1, 4202, 1, 4203, 1, 4204, 1, 1, 1, 1, 1, 4205, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4206, 1, 1, 1, 1, 1, 4207, 1, 4208, 1, 4209, 1, 4210, 1, 4211, 1, 4212, 1, 1, 1, 1, 1, 1, 1, 1, 4213, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4214, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4215, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4216, 1, 4217, 1, 4218, 1, 4219, 1, 4220, 1, 4221, 1, 4222, 1, 4223, 1, 4224, 1, 4225, 1, 4226, 1, 4227, 1, 4228, 1, 4229, 1, 4230, 1, 4231, 1, 4232, 1, 4233, 1, 4234, 1, 4235, 1, 1, 1, 1, 1, 4236, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4237, 1, 1, 1, 1, 1, 4238, 1, 4239, 1, 4240, 1, 4241, 1, 4242, 1, 4243, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4244, 1, 1, 1, 4245, 1, 1, 1, 1, 1, 4246, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4247, 1, 1, 1, 4248, 1, 1, 1, 1, 1, 4249, 1, 4250, 1, 4251, 1, 4252, 1, 4253, 1, 4254, 1, 4255, 1, 4256, 1, 4257, 1, 4258, 1, 4259, 1, 4260, 1, 4261, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4262, 1, 4263, 1, 4264, 1, 4265, 1, 4266, 1, 4267, 1, 4268, 1, 4269, 1, 4270, 1, 1, 1, 4271, 1, 1, 1, 1, 1, 4272, 1, 4273, 1, 4274, 1, 4275, 1, 4276, 1, 4277, 1, 4278, 1, 4279, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4280, 1, 4281, 1, 4282, 1, 4283, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4284, 1, 1, 1, 1, 1, 4285, 1, 4286, 1, 4287, 1, 4288, 1, 4289, 1, 4290, 1, 4291, 4292, 1, 4293, 1, 4294, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4295, 1, 4296, 1, 4297, 1, 4298, 1, 4299, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4300, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4301, 1, 4302, 1, 4303, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4304, 1, 4305, 1, 4306, 1, 4307, 4308, 4309, 4310, 1, 4311, 4312, 1, 1, 4313, 1, 1, 4314, 1, 1, 4315, 4316, 4317, 4318, 1, 4319, 1, 4320, 1, 4321, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4322, 1, 4323, 1, 4324, 1, 4325, 1, 4326, 1, 4327, 1, 4328, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4329, 4330, 4331, 4332, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4333, 1, 4334, 1, 4335, 1, 4336, 1, 4337, 1, 4338, 1, 4339, 1, 4340, 1, 4341, 1, 4342, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4343, 1, 4344, 1, 4345, 1, 4346, 1, 4347, 1, 4348, 1, 4349, 1, 4350, 1, 4351, 1, 4352, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4353, 1, 4354, 1, 4355, 1, 4356, 1, 4357, 1, 4358, 1, 1, 1, 4359, 1, 1, 1, 4360, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4361, 1, 4362, 1, 4363, 1, 4364, 1, 4365, 1, 4366, 1, 4367, 1, 4368, 1, 4369, 1, 4370, 1, 4371, 1, 4372, 1, 4373, 1, 4374, 1, 4375, 1, 4376, 1, 4377, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4378, 1, 4379, 1, 4380, 1, 4381, 1, 4382, 1, 4383, 1, 4384, 1, 1, 1, 1, 1, 1, 1, 1, 4385, 4386, 1, 4387, 1, 4388, 1, 4389, 1, 4390, 1, 4391, 1, 4392, 1, 4393, 1, 4394, 1, 4396, 4395, 4397, 4395, 4398, 4395, 4399, 4395, 4400, 4395, 4401, 1, 4402, 1, 4403, 1, 4404, 1, 1, 1, 4405, 1, 4406, 1, 4407, 1, 4408, 1, 4409, 1, 4410, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4411, 1, 4412, 1, 4413, 1, 4414, 1, 4415, 1, 4416, 1, 4417, 1, 4418, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4419, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4420, 1, 4421, 4422, 1, 1, 1, 1, 1, 1, 4423, 1, 1, 1, 1, 1, 4424, 1, 4425, 1, 4426, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4427, 1, 1, 1, 1, 1, 1, 4428, 1, 4429, 1, 4430, 1, 4431, 1, 4432, 1, 1, 4433, 1, 4434, 1, 4435, 1, 4436, 1, 4437, 1, 4438, 1, 1, 1, 1, 1, 4439, 1, 4440, 1, 4441, 1, 4442, 1, 4443, 1, 4444, 1, 4445, 1, 4446, 1, 4447, 1, 4448, 1, 4449, 4450, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4451, 1, 4452, 4453, 1, 4454, 1, 4455, 1, 4456, 1, 4457, 1, 4458, 1, 4459, 1, 4460, 1, 4461, 1, 4462, 1, 4463, 1, 4464, 1, 4465, 1, 4466, 1, 4467, 1, 4468, 1, 4469, 1, 4470, 1, 4471, 1, 4472, 1, 4473, 1, 4474, 1, 4475, 1, 4476, 1, 4477, 1, 4478, 1, 4479, 1, 4480, 1, 4481, 1, 4482, 1, 4483, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4484, 1, 4485, 1, 4486, 1, 4487, 1, 4488, 4489, 4490, 1, 4491, 1, 4492, 1, 4493, 1, 4494, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4495, 1, 4496, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4497, 1, 4498, 1, 4499, 1, 1, 4500, 1, 4501, 1, 4502, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4503, 1, 4504, 1, 4505, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4506, 1, 1, 1, 1, 1, 4507, 1, 4508, 1, 4509, 1, 4510, 1, 4511, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4512, 1, 4513, 1, 4514, 1, 4515, 1, 4516, 1, 4517, 1, 4518, 1, 4519, 1, 4520, 1, 4521, 1, 4522, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4523, 1, 4524, 1, 4525, 1, 4526, 1, 4527, 1, 4528, 1, 4529, 1, 4530, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4531, 1, 1, 1, 1, 1, 1, 4532, 1, 4533, 1, 4534, 1, 4535, 1, 4536, 1, 4538, 4537, 4539, 4537, 4540, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4541, 1, 4542, 1, 4543, 1, 4544, 1, 4545, 1, 4546, 1, 4547, 1, 4548, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4549, 1, 4550, 1, 4551, 1, 4552, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4553, 1, 4554, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4555, 1, 4556, 1, 4557, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4558, 1, 4559, 1, 4560, 1, 4561, 1, 4562, 1, 4563, 1, 4564, 4565, 1, 1, 1, 1, 1, 1, 4566, 1, 1, 1, 4567, 1, 4568, 1, 1, 1, 4569, 4570, 1, 4571, 1, 4572, 1, 4573, 1, 4574, 1, 1, 1, 1, 1, 4575, 1, 4576, 1, 4577, 1, 4578, 1, 1, 4579, 1, 4580, 1, 4581, 1, 4582, 1, 4583, 1, 4584, 1, 4585, 1, 4586, 1, 4587, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4588, 1, 4589, 1, 4590, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4591, 4592, 4593, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4594, 1, 1, 1, 4595, 1, 4596, 1, 4597, 1, 4598, 1, 4599, 1, 4600, 1, 4601, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4602, 1, 4603, 1, 4604, 1, 4605, 1, 4606, 1, 4607, 1, 4608, 1, 4609, 1, 4610, 1, 4611, 1, 4612, 1, 4613, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4614, 1, 1, 1, 1, 1, 4615, 1, 1, 1, 4616, 1, 4617, 1, 4618, 1, 4619, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4620, 1, 4621, 1, 4622, 1, 4623, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4624, 4625, 1, 4626, 1, 4627, 1, 1, 4628, 1, 4629, 1, 4630, 1, 4631, 1, 4632, 1, 4633, 1, 4634, 1, 4635, 1, 4636, 1, 4637, 1, 4638, 1, 4639, 1, 4640, 1, 4641, 1, 4642, 1, 4643, 1, 4644, 1, 4645, 1, 4646, 1, 4647, 1, 4648, 1, 4649, 1, 4650, 1, 4651, 1, 4652, 1, 4653, 1, 1, 1, 1, 1, 4654, 1, 4655, 1, 4656, 1, 4657, 1, 4658, 1, 4659, 1, 4660, 1, 4661, 1, 4662, 1, 4663, 1, 4664, 1, 4665, 1, 4666, 1, 4667, 1, 4668, 1, 4669, 1, 4670, 1, 1, 1, 1, 1, 4671, 1, 4672, 1, 4673, 1, 4674, 1, 4675, 1, 4676, 1, 4677, 1, 4678, 1, 4679, 1, 4680, 1, 4681, 1, 4682, 1, 4683, 1, 4684, 1, 4685, 1, 4686, 1, 1, 1, 1, 1, 1, 4687, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4688, 4689, 4690, 4691, 4692, 4693, 1, 4694, 4695, 4696, 1, 4697, 1, 1, 4698, 1, 1, 4699, 4700, 4701, 4702, 1, 4703, 1, 1, 4704, 1, 4705, 1, 4706, 1, 4707, 1, 4708, 1, 4709, 1, 4710, 1, 4711, 1, 1, 1, 1, 4712, 1, 1, 1, 1, 1, 4713, 4714, 1, 4715, 1, 4716, 1, 4717, 1, 4718, 1, 4719, 1, 4720, 1, 4721, 1, 4722, 1, 4723, 1, 4724, 1, 4725, 1, 4726, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4727, 1, 4728, 1, 4729, 4730, 1, 4731, 1, 4732, 1, 4733, 1, 4734, 1, 4735, 1, 4736, 1, 4737, 1, 4738, 1, 4739, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4740, 1, 4741, 1, 4742, 1, 4743, 1, 4744, 1, 4745, 1, 4746, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4747, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4748, 1, 4749, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4750, 1, 4751, 1, 4752, 1, 4753, 1, 4754, 1, 4755, 1, 4756, 1, 4757, 1, 4758, 1, 4759, 1, 4760, 1, 4761, 1, 4762, 1, 1, 1, 1, 4763, 4764, 1, 4765, 1, 4766, 1, 4767, 1, 4768, 1, 4769, 1, 4770, 1, 4771, 1, 4772, 1, 4773, 1, 1, 1, 1, 1, 1, 1, 1, 4774, 1, 4775, 1, 4776, 1, 4777, 1, 4778, 1, 4779, 1, 4780, 1, 4781, 1, 4782, 1, 1, 1, 1, 1, 4783, 1, 4784, 1, 4785, 1, 4786, 1, 1, 1, 4787, 1, 4788, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4789, 1, 1, 4790, 1, 4791, 1, 4792, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4793, 1, 1, 1, 4794, 1, 4795, 1, 4796, 1, 4797, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4798, 1, 4799, 1, 4800, 1, 4801, 1, 4802, 1, 4803, 1, 4804, 1, 4805, 1, 4806, 1, 4807, 1, 4808, 1, 4809, 1, 4810, 1, 4811, 1, 4812, 1, 4813, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4814, 1, 1, 1, 1, 1, 4815, 1, 4816, 1, 4817, 1, 4819, 4818, 4820, 4818, 4821, 4818, 4822, 4818, 4823, 4818, 4824, 4818, 4825, 4818, 4826, 1, 4827, 1, 4828, 1, 4829, 1, 4830, 1, 4831, 1, 4832, 1, 4833, 1, 1, 4834, 1, 4835, 1, 4836, 1, 4837, 1, 4838, 1, 4839, 1, 4840, 1, 4841, 1, 1, 1, 4842, 1, 1, 1, 4843, 4844, 1, 4845, 1, 4846, 1, 4847, 1, 4848, 1, 4849, 1, 4850, 1, 4851, 1, 4852, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4853, 1, 1, 1, 1, 1, 1, 1, 4854, 1, 1, 4855, 1, 1, 4856, 1, 4857, 1, 4858, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4859, 1, 4860, 1, 4861, 1, 4862, 1, 4863, 1, 4864, 1, 4865, 1, 4866, 1, 4867, 1, 4868, 1, 4869, 1, 4870, 1, 4871, 1, 4872, 1, 4873, 1, 4874, 1, 4875, 1, 4876, 1, 4877, 1, 4878, 1, 4879, 1, 4880, 1, 4881, 1, 4882, 1, 4883, 1, 4884, 1, 4885, 1, 4886, 1, 4887, 1, 4888, 1, 4889, 1, 4890, 1, 4891, 1, 1, 4892, 1, 1, 1, 4893, 1, 4894, 1, 4895, 1, 4896, 1, 4897, 1, 4898, 1, 4899, 1, 4900, 1, 4901, 1, 4902, 1, 4903, 1, 4904, 1, 4905, 1, 4906, 1, 4907, 1, 4908, 1, 4909, 1, 4910, 1, 4911, 1, 4912, 1, 4913, 1, 4914, 1, 4915, 1, 1, 1, 1, 1, 4916, 1, 4917, 1, 4918, 1, 4919, 1, 4920, 1, 4921, 1, 4922, 1, 4923, 1, 4924, 1, 4925, 1, 4926, 4927, 1, 4928, 1, 4929, 1, 4930, 1, 4931, 1, 4932, 1, 4933, 1, 4934, 1, 1, 4935, 1, 4936, 1, 4937, 1, 4938, 1, 4939, 1, 4940, 1, 4941, 1, 4942, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4943, 1, 1, 1, 1, 4944, 1, 4945, 1, 1, 1, 1, 1, 1, 4946, 1, 4947, 1, 4948, 1, 4949, 1, 4950, 1, 4951, 1, 4952, 1, 4953, 1, 4954, 1, 4955, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4956, 1, 4957, 1, 4958, 1, 4959, 1, 4960, 1, 4961, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4962, 1, 4963, 1, 4964, 1, 1, 1, 1, 1, 1, 4965, 1, 4966, 1, 4967, 1, 4968, 1, 4969, 1, 4970, 1, 4971, 1, 4972, 1, 4973, 1, 4974, 1, 4975, 1, 4976, 1, 4977, 1, 4978, 1, 1, 1, 1, 1, 4979, 1, 4980, 1, 4981, 1, 4982, 1, 4983, 1, 4984, 1, 4985, 1, 4986, 1, 4987, 1, 4988, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4989, 1, 4990, 4991, 4992, 4993, 4994, 1, 1, 1, 1, 4995, 4996, 4997, 4998, 4999, 5000, 5001, 5002, 5003, 5004, 1, 1, 5005, 1, 5006, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5007, 1, 5008, 1, 5009, 1, 5010, 1, 5011, 1, 5012, 1, 5013, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5014, 1, 5015, 1, 5016, 1, 5017, 1, 5018, 1, 5019, 1, 5020, 1, 5021, 1, 5022, 1, 1, 1, 1, 1, 1, 1, 5023, 1, 1, 1, 1, 1, 5024, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5025, 1, 5026, 1, 5027, 1, 5028, 1, 5029, 1, 5030, 1, 5031, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5032, 1, 5033, 1, 5034, 1, 5035, 1, 5036, 1, 5037, 1, 5038, 1, 5039, 1, 5040, 1, 5041, 1, 5042, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5043, 1, 5044, 1, 5045, 1, 5046, 1, 5047, 1, 5048, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5049, 5050, 1, 5051, 1, 5052, 1, 5053, 1, 5054, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5055, 1, 5056, 1, 5057, 1, 5058, 1, 5059, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5060, 1, 1, 5061, 1, 1, 1, 1, 1, 1, 5062, 1, 5063, 1, 5064, 1, 5065, 1, 5066, 1, 5067, 1, 5068, 1, 5069, 1, 5070, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5071, 1, 5072, 1, 5073, 1, 5074, 1, 5075, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5076, 1, 1, 5077, 1, 5078, 1, 5079, 1, 5080, 1, 5081, 1, 5082, 1, 5083, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5084, 1, 1, 5085, 1, 5086, 1, 5087, 1, 5088, 1, 5089, 1, 5090, 1, 5091, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5092, 1, 5093, 5094, 1, 5095, 1, 5096, 1, 5097, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5098, 1, 5099, 1, 5100, 1, 5101, 1, 5102, 1, 1, 1, 1, 1, 1, 1, 1, 5103, 1, 5104, 1, 5105, 1, 5106, 1, 5107, 1, 5108, 1, 5109, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5110, 1, 1, 1, 1, 1, 1, 5111, 1, 5112, 1, 5113, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5114, 1, 5115, 1, 5116, 1, 5117, 1, 5118, 1, 5119, 1, 5120, 1, 5121, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5122, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5123, 1, 5124, 1, 5125, 1, 5126, 1, 5127, 1, 5128, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5129, 1, 5130, 5131, 1, 5132, 1, 1, 1, 1, 1, 5133, 1, 5134, 1, 5135, 1, 5136, 1, 5137, 1, 5138, 1, 5139, 1, 5140, 1, 5141, 1, 1, 5142, 1, 5143, 1, 5144, 1, 5145, 1, 5146, 1, 5147, 1, 5148, 1, 1, 1, 1, 5149, 1, 5150, 1, 5151, 1, 5152, 1, 5153, 1, 5154, 1, 5155, 1, 5156, 1, 5157, 1, 1, 1, 5158, 1, 1, 1, 5159, 1, 5160, 1, 5161, 1, 5162, 1, 5163, 1, 5164, 1, 5165, 1, 5166, 1, 5167, 1, 1, 1, 1, 1, 1, 1, 1, 5168, 1, 5169, 1, 5170, 1, 5171, 1, 5172, 1, 5173, 1, 5174, 1, 5175, 1, 5176, 1, 5177, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5178, 1, 5179, 1, 5180, 1, 5181, 1, 5182, 1, 5183, 1, 5184, 1, 5185, 5186, 1, 1, 1, 1, 5187, 1, 5188, 1, 5189, 1, 5190, 1, 5191, 1, 5192, 1, 5193, 1, 5194, 1, 5195, 1, 1, 1, 1, 1, 1, 5196, 1, 5197, 1, 5198, 1, 1, 1, 1, 5199, 1, 5200, 1, 5201, 1, 5202, 1, 5203, 1, 1, 1, 1, 1, 5204, 1, 1, 1, 1, 1, 1, 5205, 1, 5206, 1, 5207, 1, 5208, 1, 5209, 1, 5210, 1, 5211, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5212, 1, 5213, 1, 5214, 1, 5215, 1, 5216, 1, 5217, 1, 5218, 1, 5219, 1, 5220, 1, 5221, 1, 5222, 1, 5223, 1, 5224, 1, 5225, 1, 5226, 1, 5227, 1, 5228, 1, 5229, 1, 5230, 1, 5231, 1, 5232, 5233, 1, 1, 5234, 5235, 1, 5236, 1, 5237, 5238, 5239, 1, 5240, 5241, 1, 5242, 1, 5243, 1, 5244, 1, 5245, 1, 5246, 1, 5247, 1, 5248, 1, 5249, 1, 5250, 1, 5251, 1, 5252, 1, 5253, 1, 5254, 1, 5255, 1, 5256, 1, 5257, 1, 5258, 1, 5259, 1, 5260, 1, 5261, 1, 1, 5262, 1, 1, 1, 1, 1, 5263, 1, 5264, 1, 5265, 1, 5266, 1, 5267, 1, 5268, 1, 1, 5269, 1, 5270, 1, 5271, 1, 5272, 1, 5273, 1, 5274, 1, 5275, 1, 5276, 1, 5277, 1, 5278, 1, 5279, 1, 5280, 1, 5281, 1, 5282, 1, 5283, 1, 5284, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5285, 1, 1, 1, 1, 1, 1, 1, 5286, 1, 5287, 1, 5288, 1, 5289, 1, 5290, 1, 5291, 1, 5292, 1, 5293, 1, 5294, 1, 5295, 1, 5296, 1, 5297, 1, 5298, 1, 5299, 1, 5300, 1, 5301, 1, 5302, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5303, 1, 5304, 1, 5305, 1, 5306, 1, 5307, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5308, 1, 5309, 1, 5310, 1, 5311, 1, 5312, 1, 5313, 1, 5314, 1, 5315, 1, 5316, 1, 5317, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5318, 1, 5319, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5320, 1, 5321, 5322, 5323, 5324, 5325, 1, 5326, 1, 5327, 5328, 5329, 5330, 5331, 1, 5332, 1, 5333, 1, 5334, 1, 5335, 1, 5336, 1, 5337, 1, 5338, 1, 5339, 1, 5340, 1, 5341, 5342, 1, 1, 5343, 1, 5344, 1, 5345, 1, 5346, 1, 5347, 1, 5348, 1, 5349, 1, 5350, 1, 5351, 1, 5352, 1, 5353, 1, 5354, 1, 5355, 1, 5356, 1, 5357, 1, 5358, 1, 5359, 1, 5360, 1, 5361, 1, 5362, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5363, 5364, 5365, 5366, 5367, 5368, 5369, 1, 5370, 5371, 1, 5372, 1, 5373, 5374, 1, 1, 5375, 5376, 5377, 1, 5378, 1, 5379, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5380, 1, 5381, 1, 5382, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5383, 1, 1, 5384, 1, 5385, 1, 5386, 1, 5387, 1, 5388, 1, 5389, 1, 5390, 1, 5391, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5392, 1, 5393, 1, 5394, 1, 5395, 1, 5396, 1, 5397, 1, 5398, 1, 5399, 1, 5400, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5401, 1, 5402, 1, 5403, 1, 5404, 1, 5405, 1, 5406, 1, 5407, 1, 5408, 1, 5409, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5410, 1, 1, 1, 1, 5411, 1, 5412, 1, 5413, 1, 5414, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5415, 1, 5416, 1, 5417, 1, 5418, 1, 5419, 1, 5420, 1, 5421, 1, 5422, 1, 5423, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5424, 5425, 1, 1, 1, 1, 1, 1, 1, 5426, 1, 5427, 1, 5428, 1, 5429, 1, 5430, 1, 5431, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5432, 1, 5433, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5434, 1, 5435, 1, 5436, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5437, 1, 5438, 1, 5439, 1, 5440, 1, 5441, 1, 5442, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5443, 1, 5444, 1, 5445, 1, 5446, 1, 5447, 1, 5448, 1, 5449, 1, 5450, 1, 5451, 1, 5452, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5453, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5454, 1, 1, 1, 1, 1, 1, 1, 1, 5455, 1, 5456, 1, 5457, 1, 5458, 1, 5459, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5460, 1, 1, 1, 5461, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5462, 1, 5463, 1, 5464, 1, 5465, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5466, 1, 5467, 1, 5468, 1, 5469, 1, 5470, 1, 5471, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5472, 1, 5473, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5474, 1, 5475, 1, 5476, 1, 5477, 1, 5478, 1, 5479, 1, 5480, 1, 5481, 1, 5482, 1, 5483, 1, 5484, 1, 5485, 1, 5486, 1, 1, 1, 1, 1, 5487, 1, 5488, 1, 5489, 1, 5490, 1, 5491, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5492, 1, 1, 1, 1, 1, 1, 5493, 1, 5494, 1, 5495, 1, 5497, 5496, 5496, 5496, 5496, 5496, 5498, 5496, 5499, 5496, 5500, 5496, 5501, 5496, 5502, 5496, 5503, 5496, 5504, 5496, 5505, 5496, 5506, 5496, 5507, 5496, 5508, 5496, 5509, 5496, 5510, 5496, 5511, 5496, 5512, 5496, 5513, 5496, 5514, 5496, 5496, 5515, 5516, 5496, 5496, 5496, 5496, 5496, 5496, 5517, 5496, 5496, 5496, 5496, 5496, 5496, 5518, 5496, 5519, 5496, 5520, 5496, 5521, 5496, 5522, 5496, 5523, 5496, 5524, 5496, 5525, 5496, 5526, 5496, 5527, 5496, 5528, 5496, 5529, 5496, 5530, 5496, 5531, 5496, 5532, 5496, 5496, 5496, 5496, 5533, 5496, 5534, 5496, 5535, 5496, 5536, 5496, 5537, 5496, 5538, 5496, 5539, 5496, 5540, 5496, 5541, 5496, 5542, 5496, 5543, 5496, 5544, 5496, 5545, 5496, 5546, 5496, 5547, 5496, 5548, 5496, 5549, 5496, 5550, 1, 1, 1, 1, 1, 1, 1, 1, 5551, 1, 5552, 1, 5553, 1, 5554, 1, 5555, 1, 5556, 1, 5557, 1, 5558, 1, 5559, 1, 5560, 1, 5561, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5562, 5563, 5564, 1, 5565, 5566, 1, 1, 1, 1, 5567, 1, 1, 1, 5568, 1, 1, 1, 5569, 1, 1, 1, 1, 1, 5570, 1, 5571, 1, 5572, 1, 5573, 1, 5574, 1, 1, 5575, 5576, 1, 1, 1, 1, 5577, 1, 5578, 1, 5579, 1, 5580, 1, 5581, 1, 5582, 1, 5583, 1, 5584, 1, 5585, 1, 5586, 1, 5587, 1, 5588, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5589, 1, 5590, 1, 5591, 1, 5592, 1, 5593, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5594, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5595, 1, 5596, 1, 5597, 1, 5598, 1, 5599, 1, 5600, 1, 5601, 1, 5602, 1, 5603, 1, 5604, 1, 5605, 1, 5606, 1, 5607, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5608, 1, 1, 1, 1, 1, 5609, 1, 5610, 1, 5611, 1, 5612, 1, 5613, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5614, 1, 5615, 1, 5616, 1, 5617, 1, 5618, 1, 5619, 1, 5620, 1, 5621, 1, 5622, 1, 5623, 1, 5624, 1, 5625, 1, 5626, 1, 5627, 1, 5628, 1, 5629, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5630, 1, 5631, 1, 5632, 1, 5633, 1, 5634, 1, 5635, 1, 5636, 1, 5637, 1, 5638, 1, 5639, 1, 5640, 1, 5641, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5642, 1, 5643, 5644, 1, 5645, 1, 5646, 1, 5647, 1, 5648, 1, 5649, 1, 5650, 1, 5651, 1, 5652, 1, 5653, 1, 5654, 1, 1, 1, 1, 1, 5655, 1, 5656, 1, 5657, 1, 5658, 1, 5659, 1, 5660, 1, 5661, 1, 5662, 1, 5663, 1, 5664, 1, 5665, 1, 5666, 1, 5667, 1, 5668, 1, 5669, 1, 5670, 1, 5671, 1, 5672, 1, 5673, 1, 5674, 1, 5675, 1, 5676, 1, 5677, 1, 5678, 1, 5679, 1, 5680, 1, 5681, 1, 1, 1, 1, 1, 1, 1, 1, 5682, 1, 1, 1, 1, 1, 1, 1, 5683, 1, 5684, 1, 5685, 1, 5686, 1, 5687, 1, 5688, 1, 5689, 1, 5690, 1, 5691, 1, 5692, 1, 5693, 1, 5694, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5695, 1, 5696, 1, 5697, 1, 5698, 1, 5699, 1, 5700, 1, 5701, 1, 5702, 1, 5703, 1, 5704, 1, 5705, 1, 5706, 5707, 5708, 1, 5709, 5710, 1, 1, 5711, 5712, 5713, 5714, 5715, 1, 5716, 5717, 5718, 1, 5719, 1, 5720, 1, 5721, 1, 5722, 1, 5723, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5724, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5725, 1, 5726, 1, 5727, 1, 5728, 1, 5729, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5730, 1, 5731, 1, 5732, 1, 5733, 1, 5734, 1, 5735, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5736, 1, 5737, 1, 5738, 1, 5739, 1, 5740, 1, 5741, 1, 5742, 1, 5743, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5744, 1, 1, 1, 1, 5745, 5746, 1, 5747, 1, 1, 1, 1, 5748, 1, 5749, 1, 5750, 1, 5751, 1, 5752, 1, 5753, 1, 5754, 1, 5755, 1, 5756, 1, 5757, 1, 5758, 1, 5759, 1, 5760, 1, 5761, 1, 5762, 1, 5763, 1, 5764, 1, 5765, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5766, 5767, 1, 5768, 1, 1, 1, 5769, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5770, 1, 5771, 1, 5772, 1, 5773, 1, 1, 1, 1, 1, 1, 5774, 1, 1, 1, 5775, 1, 5776, 1, 5777, 1, 5778, 1, 5779, 1, 5780, 1, 5781, 1, 5782, 1, 5783, 1, 5784, 1, 5785, 1, 5786, 1, 5787, 1, 5788, 1, 5789, 1, 5790, 1, 5791, 1, 5792, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5793, 1, 1, 5794, 1, 1, 1, 1, 1, 1, 1, 1, 5795, 1, 1, 1, 1, 5796, 1, 5797, 1, 5798, 1, 5799, 1, 5800, 1, 5801, 1, 5802, 1, 5803, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5804, 1, 5805, 1, 5806, 1, 5807, 1, 5808, 1, 5809, 1, 5810, 1, 5811, 1, 5812, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5813, 1, 5814, 1, 1, 1, 1, 1, 1, 5815, 1, 1, 1, 5816, 1, 5817, 1, 5818, 1, 5819, 1, 5820, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5821, 1, 5822, 1, 5823, 1, 5824, 1, 5825, 1, 5826, 1, 5827, 1, 5828, 1, 5829, 1, 5830, 1, 5831, 1, 5832, 1, 5833, 1, 5834, 1, 5835, 1, 5836, 1, 5837, 1, 5838, 1, 5839, 1, 1, 1, 5840, 1, 1, 1, 1, 1, 1, 1, 1, 5841, 1, 1, 1, 5842, 1, 5843, 1, 5844, 1, 5845, 1, 5846, 1, 5847, 1, 5848, 1, 5849, 1, 5850, 1, 5851, 1, 5852, 1, 5853, 1, 5854, 1, 5855, 1, 5856, 1, 5857, 1, 5858, 1, 5859, 1, 5860, 1, 1, 1, 1, 1, 5861, 1, 5862, 1, 5863, 1, 5864, 1, 5865, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5866, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5867, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5868, 1, 1, 5869, 1, 5870, 1, 5871, 1, 5872, 1, 5873, 1, 5874, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5875, 1, 5876, 1, 5877, 1, 5878, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5879, 1, 5880, 1, 5881, 1, 5882, 1, 5883, 1, 5884, 1, 5885, 1, 5886, 1, 5887, 1, 5888, 1, 5889, 1, 5890, 1, 1, 5891, 1, 1, 1, 1, 1, 1, 5892, 1, 5893, 1, 1, 1, 5894, 1, 5895, 1, 5896, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5897, 1, 5898, 1, 5899, 1, 5900, 1, 5901, 1, 5902, 1, 5903, 1, 5904, 1, 5905, 1, 5906, 1, 5907, 1, 5908, 1, 5909, 1, 5910, 1, 5911, 1, 5912, 1, 5913, 1, 5914, 1, 5915, 1, 5916, 1, 5917, 1, 5918, 1, 5919, 1, 5920, 1, 5921, 1, 5922, 1, 5923, 1, 5924, 1, 1, 5925, 5926, 5927, 1, 5928, 1, 1, 1, 1, 5929, 1, 1, 1, 5930, 1, 5931, 1, 5932, 1, 5933, 1, 5934, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5935, 1, 5936, 1, 5937, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5938, 1, 5939, 1, 5940, 1, 5941, 1, 5942, 1, 5943, 1, 5944, 1, 5945, 1, 5946, 1, 5947, 1, 5948, 1, 5949, 1, 5950, 1, 5951, 1, 5952, 1, 5953, 1, 5954, 1, 5955, 1, 5956, 1, 5957, 1, 5958, 1, 5959, 1, 5960, 1, 5961, 1, 5962, 1, 5963, 5964, 1, 1, 5965, 1, 1, 5966, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5967, 5968, 5969, 5970, 5971, 5972, 5973, 5974, 1, 5975, 1, 5976, 5977, 5978, 5979, 5980, 1, 5981, 5982, 5983, 5984, 5985, 1, 5986, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5987, 1, 5988, 1, 5989, 1, 5990, 1, 5991, 1, 5992, 1, 5993, 1, 5994, 1, 5995, 1, 5996, 1, 5997, 1, 5998, 1, 5999, 1, 6000, 1, 6001, 1, 6002, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6003, 1, 6004, 1, 6005, 1, 6006, 1, 6007, 1, 6008, 1, 6009, 1, 6010, 1, 1, 1, 1, 1, 6011, 6012, 1, 6013, 6014, 6015, 1, 6016, 1, 6017, 1, 6018, 1, 6019, 1, 6020, 1, 6021, 1, 6022, 1, 6023, 1, 6024, 1, 6025, 1, 6026, 1, 6027, 1, 6028, 1, 6029, 1, 6030, 1, 6031, 1, 6032, 1, 6033, 1, 6034, 1, 6035, 1, 6036, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6037, 1, 1, 1, 1, 1, 1, 1, 6038, 1, 6039, 1, 6040, 1, 6041, 1, 6042, 1, 6043, 1, 6044, 1, 6045, 1, 6046, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6047, 1, 1, 1, 6048, 1, 6049, 1, 1, 1, 6050, 1, 1, 1, 6051, 1, 1, 6052, 6053, 1, 6054, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6055, 1, 6056, 1, 6057, 1, 6058, 1, 6059, 1, 6060, 1, 6061, 1, 6062, 1, 6063, 1, 6064, 1, 6065, 1, 6066, 1, 6067, 1, 6068, 1, 6069, 1, 6070, 1, 6071, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6072, 1, 1, 1, 6073, 1, 6074, 1, 6075, 1, 6076, 1, 6077, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6078, 1, 6079, 1, 6080, 6081, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6082, 1, 6083, 1, 6084, 1, 6085, 1, 6086, 1, 6087, 1, 6088, 1, 6089, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6090, 1, 6091, 1, 6092, 1, 1, 1, 1, 1, 6093, 1, 6094, 1, 6095, 1, 6096, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6097, 1, 6098, 1, 6099, 1, 6100, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6101, 1, 6102, 1, 6103, 1, 6104, 1, 1, 1, 6105, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6106, 1, 1, 1, 6107, 1, 6108, 1, 6109, 1, 6110, 1, 6111, 1, 6112, 1, 1, 1, 1, 6113, 1, 6114, 1, 6115, 1, 6116, 1, 6117, 1, 6118, 1, 6119, 1, 6120, 1, 6121, 1, 6122, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6123, 6124, 6125, 1, 6126, 1, 6127, 1, 6128, 1, 6129, 1, 6130, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6131, 1, 6132, 1, 6133, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6134, 1, 6135, 1, 6136, 1, 6137, 1, 6138, 1, 6139, 1, 6140, 1, 6141, 1, 6142, 1, 6143, 1, 6144, 1, 6145, 1, 6146, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6147, 6148, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6149, 1, 6150, 1, 6151, 1, 6152, 1, 1, 1, 1, 1, 1, 6153, 1, 1, 1, 6154, 1, 1, 1, 1, 1, 6155, 1, 6156, 1, 6157, 1, 6158, 1, 6159, 1, 6160, 1, 6161, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6162, 1, 6163, 1, 6164, 1, 6165, 1, 6166, 1, 6167, 1, 6168, 1, 6169, 1, 6170, 1, 6171, 1, 6172, 1, 6173, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6174, 1, 6175, 1, 6176, 1, 6177, 1, 6178, 1, 6179, 1, 6180, 1, 6181, 1, 6182, 1, 6183, 1, 6184, 1, 6185, 1, 6186, 1, 6187, 1, 6188, 1, 6189, 1, 6190, 1, 6191, 1, 6192, 1, 6193, 1, 6194, 1, 6195, 1, 1, 1, 1, 1, 1, 6196, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6197, 1, 6198, 1, 6199, 1, 6200, 1, 6201, 1, 6202, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6203, 1, 6204, 1, 6205, 1, 6206, 1, 6207, 1, 6208, 1, 6209, 1, 6210, 1, 6211, 1, 6212, 1, 6213, 1, 6214, 1, 6215, 1, 6216, 1, 6217, 1, 6218, 1, 6219, 1, 6220, 1, 6221, 1, 6222, 1, 6223, 1, 6224, 1, 6225, 1, 6226, 1, 6227, 1, 6228, 1, 6229, 1, 6230, 1, 6231, 1, 6232, 1, 6233, 1, 6234, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6235, 1, 6236, 1, 6237, 1, 6238, 1, 6239, 1, 6240, 1, 6241, 1, 6242, 1, 6243, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6244, 6245, 1, 1, 6246, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6247, 1, 6248, 1, 6249, 1, 6250, 1, 6251, 1, 6252, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6253, 1, 6254, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6255, 1, 6256, 1, 6257, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6258, 1, 6259, 1, 6260, 1, 6261, 1, 1, 6262, 6263, 1, 6264, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6265, 1, 6266, 1, 6267, 1, 6268, 1, 6269, 1, 6270, 1, 6271, 1, 6272, 1, 6273, 1, 6274, 1, 6275, 1, 6276, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6277, 1, 6278, 1, 6279, 1, 6280, 1, 6281, 1, 6282, 1, 6283, 1, 6284, 1, 6285, 1, 6286, 1, 6287, 1, 6288, 1, 6289, 1, 6290, 1, 6291, 1, 1, 6292, 1, 1, 1, 1, 1, 6293, 1, 6294, 1, 6295, 1, 6296, 1, 6297, 1, 6298, 1, 6299, 1, 6300, 1, 6301, 1, 6302, 1, 6303, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6304, 1, 6305, 1, 6306, 6307, 1, 6308, 1, 6309, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6310, 1, 6311, 1, 6312, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6313, 1, 6314, 1, 6315, 1, 6316, 1, 6317, 1, 6318, 1, 6319, 1, 6320, 1, 6321, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6322, 1, 6323, 1, 1, 1, 1, 6324, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6325, 1, 6326, 1, 6327, 1, 6328, 1, 6329, 1, 6330, 1, 6331, 1, 6332, 1, 6333, 1, 6334, 1, 6335, 1, 6336, 1, 6337, 1, 6338, 1, 6339, 1, 6340, 1, 6341, 1, 6342, 1, 1, 1, 1, 1, 6343, 1, 6344, 1, 6345, 1, 6346, 1, 6347, 1, 6348, 1, 6349, 1, 6350, 1, 6351, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6352, 1, 6353, 1, 6354, 1, 6355, 1, 6356, 1, 6357, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6358, 1, 1, 1, 6359, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6360, 1, 6361, 1, 6362, 1, 6363, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6364, 1, 6365, 1, 6366, 1, 6367, 1, 6368, 1, 6369, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6370, 1, 6371, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6372, 1, 6373, 1, 6374, 1, 6375, 1, 6376, 1, 6377, 6378, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6379, 6380, 6381, 1, 1, 1, 6382, 1, 1, 6383, 1, 1, 6384, 1, 6385, 1, 1, 1, 6386, 1, 6387, 1, 6388, 1, 6389, 1, 6390, 1, 6391, 1, 6392, 1, 6393, 1, 6394, 1, 6395, 6396, 1, 1, 1, 1, 6397, 1, 6398, 1, 6399, 1, 6400, 1, 6401, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6402, 1, 6403, 1, 6404, 1, 6405, 1, 6406, 1, 6407, 1, 6408, 1, 6409, 1, 6410, 1, 6411, 1, 6412, 1, 6413, 1, 6414, 1, 6415, 1, 6416, 1, 6417, 1, 6418, 1, 6419, 1, 6420, 1, 6421, 1, 6422, 1, 6423, 1, 6424, 1, 6425, 1, 6426, 1, 6427, 1, 6428, 1, 6429, 1, 6430, 1, 6431, 1, 6432, 1, 6433, 1, 6434, 1, 6435, 1, 6436, 1, 6437, 1, 6438, 1, 6439, 1, 6440, 1, 1, 1, 1, 1, 6441, 1, 6442, 1, 6443, 1, 6444, 1, 6445, 1, 6446, 1, 6447, 1, 6448, 1, 6449, 1, 6450, 1, 6451, 1, 1, 1, 1, 6452, 1, 1, 1, 1, 1, 6453, 1, 6454, 1, 6455, 1, 6456, 1, 6457, 1, 6458, 1, 6459, 1, 6460, 1, 6461, 1, 6462, 1, 6463, 1, 6464, 1, 6465, 6466, 1, 6467, 1, 6468, 1, 6469, 1, 6470, 1, 6471, 1, 6472, 1, 6473, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6474, 6475, 1, 6476, 1, 6477, 1, 6478, 1, 6479, 1, 6480, 1, 6481, 1, 6482, 1, 6483, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6484, 1, 6485, 1, 6486, 1, 6487, 1, 6488, 1, 1, 1, 1, 6489, 1, 1, 1, 1, 6490, 1, 1, 1, 1, 1, 1, 6491, 1, 6492, 1, 6493, 1, 6494, 1, 6495, 1, 6496, 1, 6497, 1, 6498, 1, 6499, 1, 6500, 1, 6501, 1, 6502, 1, 6503, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6504, 1, 6505, 1, 6506, 1, 6507, 1, 6508, 1, 6509, 1, 6510, 1, 6511, 1, 1, 1, 1, 6512, 6513, 1, 1, 1, 1, 1, 1, 1, 6514, 1, 1, 6515, 1, 6516, 1, 6517, 1, 6518, 1, 6519, 1, 6520, 1, 6521, 1, 6522, 1, 6523, 1, 6524, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6525, 1, 6526, 1, 6527, 1, 6528, 1, 6529, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6530, 1, 6531, 1, 6532, 1, 6533, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6534, 1, 6535, 1, 6536, 1, 6537, 1, 6538, 1, 6539, 1, 6541, 6540, 6540, 6540, 6540, 6540, 6542, 6540, 6543, 6540, 6544, 6540, 6545, 6540, 6546, 6540, 6547, 6540, 6548, 6540, 6549, 6540, 6550, 6540, 6551, 6540, 6552, 6540, 6553, 6540, 6554, 6540, 6555, 6540, 6556, 6540, 6557, 6540, 6558, 6540, 6559, 6540, 6560, 6540, 6561, 6540, 6562, 6540, 6563, 6540, 6564, 6540, 6565, 6540, 6566, 6540, 6540, 6540, 6540, 6540, 6540, 6540, 6540, 6540, 6540, 6540, 6540, 6540, 6540, 6540, 6540, 6540, 6540, 6540, 6540, 6540, 6540, 6540, 6540, 6567, 6540, 6568, 6540, 6569, 6540, 6570, 6540, 6571, 6540, 6540, 6540, 6540, 6540, 6540, 6540, 6540, 6540, 6540, 6540, 6540, 6540, 6540, 6540, 6540, 6540, 6540, 6540, 6540, 6540, 6540, 6540, 6540, 6540, 6540, 6540, 6540, 6540, 6540, 6540, 6540, 6540, 6540, 6540, 6540, 6540, 6540, 6540, 6540, 6540, 6572, 6573, 6540, 6574, 6540, 6575, 6540, 6576, 1, 6577, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6578, 1, 6579, 1, 6580, 1, 6581, 1, 6582, 1, 6583, 1, 6584, 1, 6585, 1, 6586, 1, 6587, 1, 6588, 1, 1, 1, 1, 1, 1, 1, 1, 6589, 1, 6590, 1, 6591, 1, 6592, 1, 6593, 1, 6594, 1, 6595, 1, 6596, 1, 6597, 1, 6598, 1, 6599, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6600, 1, 6601, 6602, 6603, 6604, 1, 6605, 6606, 1, 1, 6607, 1, 6608, 6609, 6610, 1, 1, 6611, 1, 6612, 1, 6613, 1, 6614, 1, 6615, 1, 6616, 1, 6617, 1, 1, 1, 1, 1, 1, 1, 1, 6618, 1, 1, 1, 6619, 1, 6620, 1, 6621, 1, 6622, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6623, 1, 6624, 1, 6625, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6626, 1, 6627, 1, 6628, 1, 6629, 1, 6630, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6631, 1, 6632, 1, 6633, 1, 6634, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6635, 1, 1, 1, 1, 1, 1, 1, 6636, 1, 1, 1, 1, 1, 1, 1, 1, 6637, 1, 6638, 1, 6639, 1, 6640, 1, 6641, 1, 6642, 1, 6643, 1, 6644, 1, 6645, 1, 6646, 1, 6647, 1, 6648, 1, 6649, 1, 6650, 1, 6651, 1, 6652, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6653, 1, 6654, 1, 6655, 1, 6656, 1, 6657, 1, 6658, 1, 6659, 1, 6660, 1, 6661, 1, 6662, 1, 6663, 1, 6664, 1, 6665, 1, 6666, 1, 6667, 1, 6668, 1, 6669, 1, 6670, 1, 6671, 1, 6672, 1, 6673, 1, 6674, 1, 6675, 1, 6676, 1, 6677, 1, 6678, 1, 6679, 6680, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6681, 1, 6682, 1, 6683, 1, 6684, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6685, 1, 6686, 6687, 1, 6688, 1, 6689, 1, 6690, 1, 6691, 1, 6692, 1, 6693, 1, 6694, 1, 6695, 1, 6696, 1, 6697, 1, 6698, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6699, 1, 6700, 1, 6701, 1, 6702, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6703, 1, 6704, 1, 6705, 6706, 1, 6707, 1, 6708, 1, 6709, 1, 6710, 1, 6711, 1, 6712, 1, 6713, 1, 6714, 1, 6715, 1, 6716, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6717, 1, 6718, 1, 6719, 1, 6720, 1, 6721, 1, 6722, 1, 6723, 1, 6724, 1, 6725, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6726, 1, 6727, 1, 6728, 1, 6729, 1, 6730, 1, 6731, 1, 6732, 1, 6733, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6734, 6735, 1, 6736, 1, 6737, 1, 6738, 1, 6739, 1, 6740, 1, 6741, 1, 6742, 1, 6743, 1, 6744, 1, 6745, 1, 1, 1, 1, 6746, 1, 1, 1, 1, 1, 6747, 1, 1, 1, 6748, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6749, 6750, 6751, 6752, 6753, 6754, 6755, 6756, 6757, 6758, 1, 6759, 6760, 1, 6761, 6762, 1, 6763, 6764, 6765, 6766, 6767, 6768, 1, 6769, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6770, 1, 6771, 1, 6772, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6773, 1, 6774, 1, 6775, 1, 1, 1, 1, 1, 1, 6776, 1, 1, 1, 1, 1, 1, 1, 6777, 1, 6778, 1, 6779, 1, 6780, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6781, 1, 6782, 1, 6783, 1, 6784, 1, 6785, 1, 6786, 1, 6787, 1, 6788, 1, 6789, 1, 6790, 1, 6791, 1, 6792, 1, 6793, 1, 6794, 1, 6795, 1, 6796, 1, 6797, 1, 6798, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6799, 1, 6800, 1, 6801, 1, 6802, 1, 6803, 1, 6804, 1, 6805, 1, 6806, 1, 6807, 1, 6808, 1, 6809, 1, 6810, 1, 6811, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6812, 1, 6813, 1, 6814, 1, 6815, 1, 6816, 1, 6817, 1, 6818, 1, 6819, 1, 6820, 1, 6821, 6822, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6823, 1, 6824, 1, 1, 1, 6825, 1, 6826, 1, 6827, 1, 6828, 1, 6829, 1, 6830, 1, 6831, 1, 6832, 1, 6833, 1, 6834, 1, 6835, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6836, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6837, 1, 1, 1, 1, 1, 6838, 6839, 1, 6840, 1, 6841, 1, 6842, 1, 6843, 1, 6844, 1, 6845, 1, 6846, 1, 6847, 1, 6848, 1, 6849, 1, 6850, 1, 6851, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6852, 1, 6853, 1, 6854, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6855, 1, 6856, 1, 6857, 1, 6858, 1, 6859, 1, 6860, 1, 6861, 1, 6862, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6863, 1, 6864, 1, 6865, 1, 1, 1, 6866, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6867, 1, 1, 1, 1, 1, 6868, 1, 1, 1, 6869, 1, 6870, 1, 6871, 1, 6872, 1, 6873, 1, 6874, 1, 6875, 1, 6876, 1, 6877, 1, 6878, 1, 6879, 1, 6880, 1, 6881, 1, 6882, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6883, 1, 6884, 1, 6885, 1, 6886, 1, 6887, 1, 6888, 1, 6889, 1, 6890, 1, 6891, 1, 6892, 1, 6893, 1, 6894, 1, 1, 1, 1, 1, 6895, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6896, 1, 1, 6897, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6898, 1, 6899, 1, 1, 1, 1, 6900, 1, 6901, 1, 6902, 1, 6903, 1, 6904, 1, 6905, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6906, 1, 6907, 1, 6908, 1, 6909, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6910, 1, 6911, 1, 6912, 1, 6913, 1, 6914, 1, 6915, 1, 6916, 1, 6917, 1, 6918, 1, 6919, 1, 6920, 1, 1, 1, 6921, 1, 6922, 1, 6923, 1, 6924, 1, 6925, 1, 6926, 1, 6927, 1, 6928, 1, 6929, 1, 6930, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6931, 1, 6932, 1, 6933, 1, 6934, 1, 6935, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6936, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6937, 6938, 1, 6939, 1, 6940, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6941, 1, 6942, 1, 6943, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6944, 1, 6945, 1, 6946, 1, 6947, 1, 6948, 1, 6949, 1, 6950, 1, 6951, 1, 6952, 1, 6953, 1, 6954, 1, 6955, 1, 6956, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6957, 1, 6958, 1, 6959, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6960, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6961, 1, 6962, 1, 6963, 1, 6964, 1, 6965, 1, 6966, 1, 6967, 1, 6968, 1, 6969, 1, 6970, 1, 6971, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6972, 1, 1, 6973, 1, 6974, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6975, 1, 6976, 1, 6977, 1, 6978, 1, 6979, 1, 6980, 1, 6981, 1, 1, 1, 6982, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6983, 1, 1, 6984, 6985, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6986, 6987, 1, 6988, 1, 6989, 1, 6990, 1, 6991, 1, 6992, 1, 6993, 1, 6994, 1, 6995, 1, 6996, 1, 6997, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6998, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6999, 1, 7000, 1, 7001, 1, 7002, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7003, 1, 7004, 1, 7005, 1, 7006, 1, 7007, 1, 7008, 1, 7009, 1, 7010, 1, 7011, 1, 7012, 1, 7013, 1, 7014, 1, 7015, 1, 7016, 1, 7017, 1, 7018, 1, 7019, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7020, 1, 7021, 1, 7022, 1, 7023, 1, 7024, 1, 7025, 1, 7026, 1, 7027, 1, 7028, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7029, 1, 7030, 1, 7031, 1, 7032, 1, 7033, 1, 7034, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7035, 1, 7036, 1, 7037, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7038, 1, 7039, 1, 7040, 1, 7041, 1, 7042, 1, 7043, 1, 1, 1, 7044, 1, 7045, 1, 7046, 1, 7048, 7047, 7049, 7047, 7047, 7047, 7047, 7047, 7047, 7047, 7047, 7047, 7050, 7047, 7047, 7047, 7047, 7047, 7047, 7047, 7047, 7047, 7047, 7047, 7047, 7047, 7047, 7047, 7047, 7047, 7047, 7047, 7047, 7047, 7047, 7047, 7047, 7047, 7047, 7047, 7047, 7047, 7047, 7051, 7047, 7047, 7047, 7047, 7047, 7047, 7047, 7047, 7047, 7047, 7047, 7047, 7047, 7047, 7047, 7047, 7047, 7052, 7047, 7053, 7047, 7054, 7047, 7055, 7047, 7056, 7047, 7057, 7058, 7059, 7047, 7060, 7047, 7061, 7047, 7062, 7047, 7063, 7047, 7064, 7047, 7047, 7047, 7047, 7047, 7047, 7047, 7047, 7047, 7047, 7047, 7047, 7047, 7047, 7047, 7047, 7047, 7047, 7047, 7047, 7047, 7047, 7047, 7047, 7047, 7047, 7047, 7047, 7047, 7047, 7047, 7047, 7047, 7047, 7047, 7047, 7047, 7047, 7047, 7047, 7047, 7047, 7047, 7047, 7047, 7047, 7047, 7047, 7047, 7047, 7047, 7047, 7047, 7047, 7047, 7047, 7047, 7047, 7065, 7047, 7066, 7067, 7068, 7047, 7069, 7047, 7070, 7047, 7071, 7047, 7072, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7073, 1, 1, 7074, 1, 7075, 1, 7076, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7077, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7078, 7079, 1, 7080, 1, 7081, 1, 7082, 1, 7083, 1, 7084, 1, 7085, 1, 7086, 1, 7087, 1, 7088, 1, 7089, 1, 7090, 1, 7091, 1, 7092, 1, 7093, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7094, 1, 7095, 1, 7096, 1, 7097, 1, 7098, 1, 7099, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7100, 1, 7101, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7102, 1, 7103, 1, 7104, 1, 7105, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7106, 1, 1, 1, 1, 1, 1, 1, 7107, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7108, 1, 7109, 1, 7110, 1, 7111, 1, 7112, 1, 7113, 1, 7114, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7115, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7116, 1, 7117, 1, 7118, 1, 7119, 1, 7120, 1, 7121, 1, 7122, 1, 7123, 1, 7124, 1, 7125, 1, 7126, 1, 7127, 1, 7128, 1, 7129, 1, 7130, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7131, 1, 7132, 1, 7133, 1, 1, 1, 1, 7134, 7135, 1, 1, 1, 7136, 1, 1, 7137, 7138, 1, 1, 1, 7139, 1, 7140, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7141, 1, 7142, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7143, 1, 7144, 1, 7145, 1, 7146, 1, 7147, 1, 7148, 1, 7149, 1, 7150, 1, 7151, 1, 7152, 1, 1, 7153, 1, 7154, 1, 7155, 1, 7156, 1, 7157, 1, 7158, 1, 7159, 1, 7160, 1, 7161, 1, 7162, 1, 7163, 1, 7164, 1, 7165, 1, 7166, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7167, 1, 7168, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7169, 1, 7170, 1, 7171, 1, 7172, 1, 7173, 1, 7174, 1, 7175, 1, 7176, 1, 7177, 1, 7178, 1, 7179, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7180, 1, 7181, 1, 7182, 1, 7183, 1, 7184, 1, 7185, 7186, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7187, 1, 7188, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7189, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7190, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7191, 1, 7192, 1, 7193, 1, 7194, 1, 7195, 1, 7196, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7197, 1, 7198, 1, 7199, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7200, 1, 7201, 1, 7202, 1, 7203, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7204, 1, 7205, 1, 7206, 1, 7207, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7208, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7209, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7210, 1, 7211, 1, 7212, 1, 7213, 1, 7214, 1, 7215, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7216, 1, 7217, 1, 7218, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7219, 1, 7220, 1, 7221, 1, 7222, 1, 1, 7223, 1, 1, 1, 1, 1, 7224, 1, 7225, 1, 7226, 1, 7227, 1, 7228, 1, 7229, 1, 7230, 1, 7231, 1, 7232, 1, 7233, 1, 7234, 1, 7235, 1, 7236, 1, 7237, 1, 7238, 1, 1, 1, 1, 1, 7239, 1, 7240, 1, 7241, 1, 7242, 1, 7243, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7244, 1, 7245, 1, 7246, 1, 7247, 1, 7248, 1, 7249, 1, 7250, 1, 7251, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7252, 1, 7253, 1, 7254, 1, 7255, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7256, 1, 7257, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7258, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7259, 1, 7260, 1, 7261, 1, 7262, 1, 7263, 1, 7264, 1, 7265, 1, 1, 1, 7266, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7267, 1, 1, 7268, 1, 1, 7269, 1, 7270, 1, 1, 7271, 1, 1, 1, 1, 1, 7272, 7273, 1, 7274, 1, 7275, 1, 7276, 1, 7277, 1, 7278, 1, 7279, 1, 7280, 1, 7281, 1, 7282, 1, 7283, 1, 7284, 1, 7285, 1, 7286, 1, 7287, 1, 7288, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7289, 1, 7290, 1, 7291, 1, 7292, 1, 7293, 1, 7294, 1, 7295, 1, 7296, 1, 7297, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7298, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7299, 1, 7300, 1, 7301, 1, 7302, 1, 7303, 1, 7304, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7305, 1, 7306, 1, 7307, 1, 7308, 1, 7309, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7310, 1, 7311, 1, 7312, 1, 7313, 1, 7314, 1, 7315, 1, 7316, 1, 7317, 1, 7318, 1, 7319, 1, 7320, 1, 7321, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7322, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7323, 1, 7324, 1, 7325, 1, 7326, 1, 7327, 1, 7328, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7329, 1, 7330, 1, 7331, 1, 7332, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7333, 1, 7334, 1, 7335, 1, 7336, 1, 7337, 1, 7338, 1, 7339, 1, 7340, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7341, 1, 7342, 7343, 7344, 7345, 7346, 7347, 7348, 1, 1, 7349, 7350, 1, 7351, 7352, 1, 7353, 7354, 7355, 7356, 7357, 1, 7358, 1, 7359, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7360, 1, 7361, 1, 7362, 1, 7363, 1, 7364, 1, 7365, 1, 7366, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7367, 1, 7368, 1, 7369, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7370, 1, 7371, 1, 7372, 7373, 1, 1, 1, 1, 1, 1, 7374, 1, 1, 1, 1, 1, 7375, 1, 1, 1, 7376, 1, 7377, 1, 7378, 1, 7379, 1, 7380, 1, 7381, 1, 7382, 1, 7383, 1, 7384, 1, 7385, 1, 7386, 1, 7387, 1, 7388, 1, 7389, 1, 7390, 1, 7391, 1, 7392, 1, 7393, 1, 7394, 1, 7395, 1, 7396, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7397, 1, 7398, 1, 7399, 1, 7400, 1, 7401, 1, 7402, 1, 1, 7403, 1, 7404, 1, 7405, 1, 7406, 1, 7407, 1, 7408, 1, 7409, 1, 7410, 1, 7411, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7412, 1, 7413, 1, 7414, 1, 7415, 1, 7416, 1, 7417, 1, 7418, 1, 7419, 1, 7420, 1, 7421, 1, 1, 1, 1, 1, 7422, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7423, 1, 7424, 1, 7425, 1, 7426, 1, 7427, 1, 1, 1, 1, 1, 1, 1, 1, 7428, 1, 7429, 1, 7430, 1, 7431, 1, 7432, 1, 7433, 1, 7434, 1, 7435, 1, 7436, 1, 7437, 1, 7438, 1, 7439, 1, 1, 1, 7440, 1, 1, 1, 7441, 1, 7442, 1, 7443, 1, 7444, 1, 7445, 1, 7446, 1, 7447, 1, 7448, 7449, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7450, 1, 7451, 1, 7452, 1, 7453, 1, 7454, 1, 7455, 1, 7456, 1, 7457, 1, 7458, 1, 7459, 1, 7460, 1, 7461, 1, 7462, 1, 1, 1, 7463, 1, 1, 1, 1, 1, 1, 7464, 1, 7465, 1, 7466, 1, 7467, 1, 7468, 1, 7469, 1, 7470, 1, 7471, 1, 7472, 1, 7473, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7474, 1, 1, 7475, 1, 1, 1, 1, 7476, 1, 1, 1, 1, 1, 7477, 1, 1, 1, 7478, 1, 1, 7479, 1, 7480, 1, 7481, 1, 7482, 1, 7483, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7484, 7485, 1, 1, 1, 1, 1, 1, 7486, 1, 7487, 1, 7488, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7489, 1, 7490, 1, 7491, 1, 7492, 1, 7493, 1, 7494, 1, 7495, 1, 7496, 1, 7497, 1, 7498, 1, 7499, 1, 7500, 1, 7501, 1, 7502, 1, 7503, 1, 7504, 1, 1, 1, 1, 1, 1, 1, 1, 7505, 1, 1, 7506, 1, 7507, 1, 7508, 1, 7509, 1, 7510, 1, 7511, 1, 7512, 1, 7513, 1, 7514, 1, 7515, 7516, 1, 7517, 1, 7518, 1, 7519, 1, 7520, 1, 7521, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7522, 1, 7523, 1, 7524, 1, 7525, 1, 7526, 1, 7527, 1, 7528, 1, 7529, 1, 7530, 1, 7531, 1, 7532, 1, 7533, 7534, 1, 7535, 7536, 1, 1, 7537, 7538, 1, 7539, 1, 1, 7540, 7541, 1, 7542, 1, 7543, 1, 7544, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7545, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7546, 7547, 1, 7549, 7548, 7550, 7548, 7551, 7548, 7552, 7548, 7553, 1, 1, 7554, 1, 7555, 1, 7556, 1, 7557, 1, 7558, 1, 7559, 1, 7560, 1, 7561, 1, 7562, 1, 1, 1, 1, 1, 7563, 1, 1, 1, 7564, 1, 1, 7565, 1, 1, 1, 7566, 1, 7567, 1, 7568, 1, 7569, 1, 7570, 1, 7571, 1, 7572, 1, 7573, 1, 7574, 1, 7575, 1, 7576, 1, 7577, 1, 7578, 1, 7579, 1, 7580, 1, 7581, 1, 7582, 1, 7583, 1, 1, 1, 7584, 1, 7585, 1, 7586, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7587, 1, 7588, 1, 7589, 1, 7590, 1, 7591, 1, 7592, 1, 7593, 1, 7594, 1, 7595, 1, 7596, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7597, 1, 7598, 1, 7599, 1, 7600, 1, 7601, 1, 7602, 1, 7603, 1, 7604, 1, 7605, 1, 7606, 1, 7607, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7608, 1, 7609, 1, 7610, 1, 1, 1, 1, 1, 1, 1, 7611, 1, 7612, 1, 7613, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7614, 1, 7615, 1, 7616, 1, 7617, 1, 7618, 1, 7619, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7620, 7621, 7622, 7623, 7624, 1, 1, 1, 1, 1, 1, 1, 7625, 1, 1, 1, 1, 1, 7626, 7627, 1, 7628, 1, 7629, 1, 7630, 1, 7631, 1, 7632, 1, 7633, 1, 7634, 1, 7635, 1, 7636, 1, 1, 1, 1, 1, 7637, 1, 7638, 1, 7639, 1, 7640, 1, 7641, 1, 7642, 1, 7643, 1, 7644, 1, 7645, 1, 7646, 1, 7647, 1, 7648, 1, 7649, 1, 1, 1, 1, 1, 1, 7650, 1, 1, 1, 1, 7651, 1, 7652, 1, 7653, 1, 7654, 1, 7655, 1, 7656, 1, 7657, 1, 7658, 1, 7659, 1, 7660, 1, 7661, 1, 7662, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7663, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7664, 1, 7665, 1, 7666, 1, 1, 1, 7667, 1, 1, 1, 1, 7668, 7669, 1, 1, 1, 7670, 1, 7671, 1, 7672, 1, 7673, 1, 7674, 1, 7675, 1, 7676, 1, 7677, 1, 7678, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7679, 1, 7680, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7681, 1, 7682, 1, 7683, 1, 1, 1, 1, 1, 1, 1, 1, 7684, 1, 1, 1, 1, 7685, 1, 7686, 1, 7687, 1, 7688, 1, 7689, 1, 7690, 1, 7691, 1, 7692, 1, 7693, 1, 7694, 1, 7695, 1, 7696, 1, 7697, 1, 7698, 1, 7699, 1, 7700, 1, 7701, 1, 1, 1, 7702, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7703, 1, 7704, 1, 7705, 1, 7706, 1, 7707, 1, 7708, 1, 7709, 1, 7710, 1, 7711, 1, 7712, 1, 7713, 1, 7714, 1, 7715, 1, 7716, 1, 7717, 1, 7718, 1, 7719, 1, 7720, 1, 7721, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7722, 1, 7723, 1, 7724, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7725, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7726, 1, 7727, 1, 7728, 1, 7729, 1, 7730, 1, 7731, 1, 7732, 1, 7733, 1, 7734, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7735, 1, 7736, 1, 7737, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7738, 1, 1, 1, 1, 1, 1, 7739, 1, 7740, 1, 7741, 1, 7742, 1, 7743, 1, 7744, 1, 7745, 1, 7746, 1, 7747, 1, 7748, 1, 7749, 1, 7750, 1, 7751, 1, 7752, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7753, 1, 7754, 1, 7755, 1, 7756, 1, 7757, 1, 7758, 1, 7759, 1, 7760, 1, 7761, 1, 7762, 1, 7763, 1, 1, 1, 1, 1, 7764, 1, 7765, 1, 7766, 1, 7767, 1, 7768, 1, 7769, 1, 7770, 1, 7771, 1, 7772, 1, 7773, 1, 1, 7774, 1, 1, 1, 1, 1, 7775, 7776, 1, 1, 7777, 1, 7778, 1, 7779, 1, 7780, 1, 7781, 1, 7782, 1, 7783, 1, 7784, 1, 7785, 1, 7786, 1, 7787, 1, 7788, 1, 7789, 1, 7790, 1, 7791, 1, 7792, 1, 7793, 1, 7794, 1, 7795, 1, 1, 1, 7796, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7797, 1, 7798, 1, 7799, 1, 1, 1, 7800, 1, 7801, 1, 7802, 1, 7803, 1, 7804, 1, 7805, 1, 7806, 1, 7807, 1, 7808, 1, 7809, 1, 7810, 1, 7811, 1, 7812, 1, 7813, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7814, 1, 7815, 1, 7816, 1, 7817, 1, 7818, 7819, 1, 1, 1, 1, 1, 7820, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7821, 7822, 7823, 7824, 7825, 7826, 1, 7827, 7828, 1, 1, 7829, 7830, 7831, 7832, 7833, 1, 7834, 7835, 7836, 7837, 1, 1, 7838, 1, 7839, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7840, 1, 7841, 1, 7842, 1, 7843, 1, 7844, 1, 7845, 1, 7846, 1, 7847, 1, 7848, 1, 7849, 1, 7850, 1, 7851, 1, 7852, 1, 7853, 1, 7854, 1, 7855, 1, 7856, 1, 7857, 1, 7858, 7859, 7860, 1, 1, 1, 1, 1, 1, 1, 1, 7861, 1, 1, 7862, 7863, 1, 7864, 1, 7865, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7866, 1, 7867, 1, 7868, 1, 7869, 1, 7870, 1, 7871, 1, 7872, 1, 7873, 1, 7874, 1, 7875, 1, 7876, 1, 7877, 1, 7878, 1, 7879, 1, 7880, 1, 7881, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7882, 7883, 1, 1, 1, 1, 1, 1, 7884, 1, 7885, 1, 7886, 1, 7887, 1, 7888, 1, 7889, 1, 7890, 1, 7891, 1, 7892, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7893, 7894, 7895, 1, 1, 7896, 1, 7897, 1, 1, 1, 7898, 1, 1, 1, 7899, 1, 1, 7900, 7901, 1, 1, 7902, 1, 7903, 1, 7904, 1, 7905, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7906, 1, 7907, 1, 7908, 1, 7909, 1, 7910, 1, 7911, 1, 7912, 1, 7913, 1, 7914, 1, 7915, 1, 7916, 1, 7917, 1, 7918, 1, 7919, 1, 7920, 1, 7921, 1, 7922, 1, 7923, 1, 7924, 1, 1, 1, 1, 1, 1, 1, 7925, 1, 7926, 1, 7927, 1, 7928, 1, 7929, 1, 7930, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7931, 1, 7932, 1, 7933, 1, 7934, 1, 7935, 1, 7936, 7937, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7938, 1, 7939, 1, 7940, 1, 7941, 1, 7942, 1, 7943, 1, 7944, 1, 7945, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7946, 1, 7947, 1, 7948, 1, 1, 1, 1, 1, 7949, 1, 7950, 1, 7951, 1, 7952, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7953, 1, 7954, 1, 7955, 1, 7956, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7957, 1, 7958, 1, 7959, 1, 7960, 1, 1, 1, 7961, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7962, 1, 1, 1, 7963, 1, 7964, 1, 7965, 1, 7966, 1, 7967, 1, 7968, 1, 1, 1, 1, 7969, 1, 7970, 1, 7971, 1, 7972, 1, 7973, 1, 7974, 1, 7975, 1, 7976, 1, 7977, 1, 7978, 1, 1, 1, 1, 1, 1, 1, 1, 7979, 1, 1, 1, 1, 7980, 1, 7981, 1, 7982, 1, 7983, 1, 7984, 1, 7985, 1, 7986, 1, 7987, 1, 7988, 1, 7989, 1, 7990, 1, 7991, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 7992, 1, 7993, 1, 7994, 1, 7995, 1, 7996, 1, 7997, 1, 1, 1, 7998, 1, 7999, 1, 8000, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8001, 1, 1, 1, 1, 1, 1, 8002, 1, 1, 8003, 1, 8004, 1, 8005, 1, 8006, 1, 8007, 1, 8008, 1, 8009, 1, 8010, 1, 8011, 1, 8012, 1, 8013, 1, 8014, 1, 1, 8015, 1, 1, 1, 1, 1, 8016, 1, 8017, 1, 8018, 1, 8019, 1, 8020, 1, 8021, 1, 8022, 1, 8023, 1, 8024, 1, 8025, 1, 8026, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8027, 1, 8028, 1, 8029, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8030, 1, 8031, 1, 8032, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8033, 1, 8034, 1, 8035, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8036, 1, 8037, 1, 8038, 1, 1, 1, 1, 1, 1, 8039, 1, 1, 1, 1, 8040, 1, 8041, 1, 8042, 1, 8043, 1, 1, 1, 1, 1, 1, 8044, 1, 1, 1, 8045, 1, 1, 1, 1, 1, 8046, 8047, 8048, 1, 8049, 1, 8050, 1, 8051, 1, 8052, 1, 8053, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8054, 1, 8055, 1, 8056, 1, 8057, 1, 8058, 1, 8059, 1, 8060, 1, 8061, 1, 8062, 1, 8063, 1, 8064, 1, 8065, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8066, 1, 8067, 1, 8068, 1, 8069, 1, 8070, 1, 8071, 1, 8072, 1, 8073, 1, 8074, 1, 8075, 1, 8076, 1, 1, 1, 1, 1, 1, 8077, 1, 8078, 1, 8079, 1, 8080, 1, 8081, 1, 8082, 1, 8083, 1, 8084, 1, 8085, 1, 8086, 1, 8087, 1, 8088, 1, 8089, 1, 8090, 1, 8091, 1, 8092, 1, 8093, 1, 8094, 1, 8095, 1, 8096, 1, 8097, 1, 8098, 1, 8099, 1, 8100, 1, 8101, 1, 8102, 1, 8103, 1, 8104, 1, 8105, 1, 8106, 1, 8107, 1, 8108, 1, 8109, 1, 8110, 1, 8111, 1, 8112, 1, 8113, 1, 8114, 1, 8115, 1, 8116, 1, 8117, 1, 8118, 1, 8119, 1, 8120, 1, 8121, 1, 8122, 1, 8123, 1, 8124, 1, 8125, 1, 8126, 1, 8127, 1, 8128, 1, 8129, 1, 8130, 1, 8131, 1, 8132, 1, 8133, 1, 8134, 1, 8135, 1, 1, 1, 1, 1, 1, 8136, 1, 1, 1, 1, 8137, 1, 8138, 1, 8139, 1, 8140, 1, 8141, 1, 8142, 1, 8143, 1, 8144, 1, 8145, 1, 8146, 1, 8147, 1, 8148, 1, 8149, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8150, 1, 8151, 1, 8152, 1, 8153, 1, 8154, 1, 8155, 1, 8156, 1, 8157, 1, 8158, 1, 8159, 8160, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8161, 1, 1, 1, 8162, 1, 8163, 1, 1, 1, 8164, 1, 8165, 1, 8166, 1, 8167, 1, 8168, 1, 8169, 1, 8170, 1, 8171, 1, 8172, 1, 1, 1, 1, 8173, 1, 1, 1, 1, 1, 8174, 1, 8175, 1, 8176, 1, 8177, 1, 8178, 1, 8179, 1, 8180, 1, 8181, 1, 8182, 1, 8183, 1, 8184, 1, 8185, 1, 8186, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8187, 1, 8188, 1, 8189, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8190, 1, 8191, 1, 8192, 1, 8193, 1, 8194, 1, 8195, 1, 8196, 1, 8197, 1, 8198, 1, 8199, 1, 8200, 1, 8201, 1, 8202, 1, 8203, 1, 8204, 1, 1, 1, 1, 8205, 1, 1, 1, 1, 1, 1, 1, 1, 8206, 1, 8207, 1, 8208, 1, 8209, 1, 8210, 1, 8211, 1, 8212, 1, 8213, 1, 8214, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8215, 1, 8216, 1, 8217, 1, 8218, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8219, 1, 8220, 1, 8221, 8222, 1, 1, 1, 1, 1, 1, 1, 1, 8223, 1, 8224, 1, 8225, 1, 8226, 1, 8227, 1, 8228, 1, 8229, 1, 8230, 1, 8231, 1, 8232, 1, 8233, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8234, 8235, 1, 1, 1, 1, 1, 8236, 1, 8237, 1, 8238, 1, 8239, 1, 8240, 1, 8241, 1, 8242, 1, 8243, 1, 8244, 1, 8245, 1, 8246, 1, 8247, 1, 8248, 1, 8249, 1, 8250, 8251, 8252, 8253, 8254, 8255, 1, 8256, 8257, 1, 1, 8258, 8259, 1, 8260, 8261, 8262, 8263, 8264, 8265, 8266, 1, 8267, 1, 1, 8268, 1, 8269, 1, 8270, 1, 8271, 1, 8272, 1, 8273, 1, 8274, 1, 8275, 1, 8276, 1, 8277, 1, 8278, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8279, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8280, 1, 8281, 1, 8282, 1, 1, 1, 8283, 1, 1, 1, 1, 8284, 1, 8285, 1, 1, 8286, 1, 1, 1, 1, 1, 8287, 1, 8288, 1, 8289, 1, 8290, 1, 8291, 1, 8292, 1, 8293, 1, 8294, 1, 8295, 1, 8296, 1, 8297, 1, 8298, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8299, 1, 8300, 1, 8301, 1, 8302, 1, 8303, 1, 8304, 1, 8305, 1, 8306, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8307, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8308, 1, 8309, 1, 8310, 1, 8311, 1, 8312, 1, 8313, 1, 8314, 1, 8315, 1, 8316, 1, 8317, 1, 8318, 1, 8319, 1, 8320, 1, 8321, 1, 8322, 1, 8323, 1, 8324, 1, 8325, 1, 8326, 1, 8327, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8328, 1, 1, 8329, 1, 8330, 1, 8331, 1, 8332, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8333, 1, 8334, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8335, 1, 1, 1, 1, 1, 8336, 8337, 1, 1, 1, 8338, 1, 8339, 1, 8340, 1, 8341, 1, 8342, 1, 8343, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8344, 1, 8345, 1, 8346, 1, 8347, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8348, 1, 8349, 1, 8350, 1, 8351, 1, 8352, 1, 8353, 1, 8354, 1, 8355, 1, 8356, 1, 8357, 1, 8358, 1, 8359, 1, 1, 1, 1, 8360, 1, 8361, 1, 8362, 1, 8363, 1, 8364, 1, 8365, 1, 8366, 1, 8367, 1, 8368, 1, 8369, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8370, 1, 8371, 1, 8372, 1, 8373, 1, 8374, 1, 8375, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8376, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8377, 1, 8378, 1, 8379, 1, 8380, 1, 8381, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8382, 1, 8383, 1, 8384, 1, 8385, 1, 8386, 1, 8387, 1, 8388, 1, 8389, 1, 1, 8390, 1, 8391, 1, 8392, 1, 8393, 1, 8394, 1, 8395, 1, 8396, 1, 8397, 1, 8398, 1, 8399, 1, 8400, 1, 8401, 1, 8402, 1, 1, 1, 1, 1, 8403, 1, 8404, 1, 8405, 1, 8406, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8407, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8408, 1, 8409, 1, 8410, 1, 8411, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8412, 8413, 1, 8414, 1, 1, 1, 1, 8415, 1, 8416, 1, 8417, 1, 8418, 1, 8419, 1, 8420, 1, 8421, 1, 8422, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8423, 1, 8424, 1, 8425, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8426, 1, 8427, 1, 8428, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8429, 1, 8430, 1, 8431, 1, 8432, 1, 8433, 1, 8434, 1, 8435, 1, 8436, 1, 8437, 1, 8438, 1, 8439, 1, 8440, 1, 8441, 1, 8442, 1, 8443, 1, 8444, 1, 8445, 1, 1, 1, 8446, 1, 1, 1, 8447, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8448, 1, 8449, 1, 1, 1, 1, 1, 1, 8450, 1, 8451, 1, 8452, 1, 8453, 1, 8454, 1, 8455, 1, 8456, 1, 8457, 1, 8458, 1, 8459, 1, 8460, 1, 8461, 1, 8462, 1, 8463, 1, 8464, 1, 8465, 1, 8466, 1, 8467, 1, 8468, 1, 8469, 1, 8470, 1, 1, 1, 1, 1, 1, 1, 8471, 1, 8472, 1, 8473, 1, 8474, 1, 8475, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8476, 1, 8477, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8478, 1, 8479, 1, 8480, 1, 1, 1, 1, 1, 8481, 1, 1, 1, 8482, 1, 8483, 1, 8484, 1, 8485, 1, 8486, 1, 8487, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8488, 1, 8489, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8490, 1, 8491, 1, 8492, 1, 8493, 1, 8494, 1, 8495, 1, 8496, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8497, 1, 8498, 1, 8499, 1, 8500, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8501, 1, 8502, 1, 8503, 1, 8504, 1, 8505, 1, 8506, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8507, 1, 8508, 1, 8509, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8510, 1, 8511, 1, 8512, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8513, 1, 8514, 1, 8515, 1, 8516, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8517, 1, 8518, 1, 8519, 1, 8520, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8521, 1, 8522, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8523, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8524, 1, 8525, 1, 8526, 1, 8527, 1, 8528, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8529, 1, 8530, 1, 8531, 1, 8532, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8533, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8534, 1, 8535, 1, 8536, 1, 8537, 1, 8538, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8539, 1, 8540, 1, 8541, 1, 8542, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8543, 1, 1, 1, 1, 8544, 1, 8545, 1, 8546, 8547, 1, 8548, 1, 8549, 1, 8550, 1, 8551, 1, 8552, 1, 8553, 1, 8554, 1, 8555, 1, 8556, 1, 1, 1, 1, 1, 1, 1, 8557, 1, 1, 1, 1, 1, 1, 8558, 1, 8559, 1, 8560, 1, 8561, 1, 8562, 1, 8563, 1, 8564, 1, 8565, 1, 8566, 1, 8567, 1, 8568, 1, 8569, 1, 8570, 1, 8571, 1, 8572, 1, 8573, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8574, 1, 8575, 1, 8576, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8577, 1, 8578, 1, 8579, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8580, 1, 8581, 1, 8582, 1, 8583, 1, 8584, 1, 8585, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8586, 1, 8587, 1, 8588, 1, 8589, 1, 8590, 1, 8591, 1, 8592, 1, 8593, 1, 8594, 1, 8595, 1, 8596, 1, 8597, 1, 8598, 1, 8599, 8600, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8601, 8602, 1, 8603, 1, 8604, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8605, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8606, 8607, 1, 1, 1, 1, 1, 1, 1, 8608, 8609, 1, 8610, 1, 8611, 8612, 1, 8613, 1, 8614, 1, 8615, 1, 8616, 1, 8617, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8618, 1, 8619, 1, 8620, 1, 8621, 1, 8622, 1, 8623, 1, 8624, 1, 8625, 1, 8626, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8627, 1, 8628, 1, 8629, 1, 8630, 1, 8631, 1, 8632, 1, 8633, 1, 8634, 1, 8635, 1, 8636, 1, 8637, 1, 8638, 1, 1, 1, 8639, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8640, 1, 8641, 1, 8642, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8643, 1, 1, 1, 1, 1, 1, 1, 1, 8644, 1, 8645, 1, 8646, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8647, 1, 8648, 1, 8649, 1, 8650, 1, 8651, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8652, 1, 8653, 1, 8654, 1, 8655, 1, 8656, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8657, 1, 8658, 1, 8659, 1, 8660, 1, 8661, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8662, 1, 8663, 1, 8664, 1, 1, 1, 1, 1, 1, 1, 1, 8665, 1, 1, 1, 1, 8666, 1, 8667, 1, 8668, 1, 8669, 1, 8670, 1, 8671, 1, 8672, 1, 8673, 1, 8674, 1, 8675, 1, 8676, 1, 8677, 1, 8678, 1, 8679, 1, 8680, 1, 8681, 1, 8682, 1, 1, 1, 8683, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8684, 1, 8685, 1, 8686, 1, 8687, 1, 8688, 1, 8689, 1, 8690, 1, 8691, 1, 8692, 1, 8693, 1, 8694, 1, 8695, 1, 8696, 1, 8697, 1, 8698, 1, 8699, 1, 8700, 1, 8701, 1, 8702, 1, 8703, 8704, 8705, 1, 1, 1, 1, 1, 1, 1, 8706, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8707, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8708, 8709, 1, 1, 8710, 1, 1, 1, 8711, 8712, 8713, 1, 8714, 1, 1, 8715, 1, 8716, 1, 8717, 1, 1, 1, 8718, 1, 8719, 1, 8720, 1, 8721, 1, 8722, 1, 8723, 1, 8724, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8725, 1, 8726, 1, 8727, 1, 8728, 1, 8729, 1, 8730, 1, 1, 1, 1, 1, 8731, 1, 8732, 1, 8733, 1, 8734, 1, 8735, 1, 8736, 1, 8737, 1, 8738, 1, 8739, 1, 8740, 1, 8741, 1, 8742, 1, 8743, 1, 8744, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8745, 1, 8746, 1, 8747, 1, 8748, 1, 8749, 1, 8750, 1, 8751, 1, 8752, 1, 1, 1, 8753, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8754, 1, 8755, 1, 8756, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8757, 1, 1, 1, 1, 1, 1, 1, 1, 8758, 1, 8759, 1, 8760, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8761, 1, 8762, 1, 8763, 1, 8764, 1, 8765, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8766, 1, 8767, 1, 8768, 1, 8769, 1, 8770, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8771, 1, 8772, 1, 8773, 1, 8774, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8775, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8776, 1, 8777, 1, 8778, 1, 8779, 1, 8780, 1, 8781, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8782, 1, 8783, 1, 8784, 1, 8785, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8786, 1, 8787, 1, 8788, 1, 8789, 1, 8790, 1, 8791, 1, 8792, 1, 8793, 1, 8794, 1, 8795, 1, 8796, 8797, 8798, 8799, 8800, 8801, 1, 8802, 8803, 1, 1, 1, 1, 1, 8804, 8805, 1, 8806, 8807, 1, 1, 1, 8808, 1, 8809, 1, 1, 8810, 1, 8811, 1, 8812, 1, 8813, 1, 8814, 1, 8815, 1, 8816, 1, 8817, 1, 8818, 1, 8819, 1, 1, 1, 8820, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8821, 1, 8822, 1, 8823, 1, 8824, 1, 8825, 1, 8826, 1, 8827, 1, 8828, 1, 8829, 1, 8830, 1, 8831, 1, 8832, 1, 8833, 1, 8834, 1, 8835, 1, 8836, 1, 8837, 1, 8838, 1, 8839, 1, 8840, 1, 8841, 1, 1, 1, 8842, 1, 8843, 1, 1, 1, 8844, 1, 8845, 1, 8846, 1, 8847, 1, 8848, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8849, 1, 8850, 1, 8851, 1, 8852, 1, 8853, 1, 8854, 1, 8855, 1, 8856, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8857, 1, 1, 8858, 1, 8859, 1, 8860, 1, 8861, 1, 8862, 1, 8863, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8864, 1, 8865, 1, 8866, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8867, 1, 8868, 1, 8869, 1, 8870, 1, 8871, 1, 8872, 1, 8873, 1, 8874, 1, 8875, 1, 8876, 1, 8877, 1, 8878, 1, 8879, 1, 8880, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8881, 1, 8882, 1, 8883, 1, 8884, 1, 8885, 1, 8886, 1, 8887, 1, 8888, 1, 8889, 8890, 8891, 1, 8892, 1, 8893, 1, 8894, 1, 8895, 1, 8896, 1, 8898, 8897, 8897, 8897, 8897, 8897, 8897, 8897, 8897, 8897, 8897, 8897, 8897, 8897, 8897, 8897, 8897, 8897, 8897, 8897, 8897, 8897, 8897, 8897, 8897, 8897, 8897, 8897, 8897, 8897, 8897, 8897, 8897, 8897, 8897, 8897, 8897, 8897, 8899, 8897, 8900, 8897, 8901, 8897, 8902, 8897, 8903, 1, 8904, 1, 8905, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8906, 1, 1, 8907, 1, 8908, 1, 8909, 1, 8910, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8911, 8912, 1, 1, 8913, 1, 8914, 1, 8915, 1, 8916, 1, 8917, 1, 8918, 1, 8919, 1, 8920, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8921, 1, 8922, 1, 8923, 1, 8924, 1, 8925, 1, 8926, 1, 8927, 1, 8928, 1, 8929, 1, 8930, 1, 8931, 1, 8932, 1, 1, 1, 1, 1, 1, 1, 8933, 1, 1, 1, 1, 1, 1, 8934, 1, 8935, 1, 8936, 1, 8937, 1, 8938, 1, 1, 8939, 8940, 1, 1, 1, 1, 1, 1, 1, 8941, 1, 1, 8942, 1, 1, 8943, 8944, 1, 8945, 1, 8946, 1, 8947, 1, 8948, 1, 8949, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8950, 1, 1, 1, 1, 1, 1, 1, 8951, 1, 1, 1, 1, 8952, 8953, 1, 8954, 1, 8955, 1, 8956, 1, 8957, 1, 8958, 1, 8959, 1, 8960, 1, 8961, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8962, 1, 8963, 1, 8964, 1, 8965, 1, 8966, 1, 8967, 1, 8968, 1, 8969, 1, 8970, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8971, 1, 8972, 1, 8973, 1, 8974, 1, 8975, 1, 8976, 1, 8977, 1, 8978, 1, 8979, 1, 8980, 1, 8981, 1, 8982, 1, 8983, 1, 8984, 1, 8985, 1, 8986, 1, 8987, 1, 8988, 1, 8989, 1, 8990, 1, 8991, 1, 8992, 1, 8993, 1, 8994, 1, 8995, 1, 8996, 1, 8997, 1, 8998, 1, 8999, 1, 1, 1, 1, 9000, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9001, 1, 9002, 1, 1, 1, 1, 1, 1, 9003, 1, 9004, 1, 9005, 1, 9006, 1, 9007, 1, 9008, 1, 9009, 1, 9010, 1, 9011, 1, 9012, 1, 9013, 1, 1, 1, 1, 1, 9014, 1, 9015, 1, 9016, 1, 9017, 1, 9018, 1, 9019, 1, 9020, 1, 9021, 1, 9022, 1, 1, 1, 1, 1, 9023, 1, 9024, 1, 9025, 1, 9026, 1, 9027, 1, 9028, 1, 9029, 1, 9030, 1, 9031, 1, 9032, 1, 9033, 1, 9034, 1, 9035, 1, 9036, 1, 9037, 1, 9038, 1, 9039, 1, 9040, 1, 9041, 1, 9042, 1, 9043, 1, 1, 1, 1, 1, 1, 9044, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9045, 9046, 9047, 9048, 1, 9049, 9050, 9051, 1, 1, 1, 9052, 9053, 1, 9054, 9055, 1, 9056, 9057, 9058, 9059, 1, 9060, 1, 9061, 1, 9062, 1, 9063, 1, 9064, 1, 9065, 1, 9066, 1, 9067, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9068, 1, 9069, 1, 9070, 1, 9071, 1, 9072, 1, 9073, 1, 9074, 1, 9075, 1, 9076, 1, 9077, 1, 9078, 1, 9079, 1, 9080, 1, 9081, 1, 9082, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9083, 1, 9084, 1, 9085, 1, 9086, 1, 9087, 9088, 1, 1, 1, 1, 1, 9089, 1, 9090, 1, 9091, 1, 9092, 1, 9093, 1, 9094, 1, 9095, 1, 9096, 1, 9097, 1, 9098, 1, 9099, 1, 9100, 1, 1, 1, 1, 1, 1, 1, 1, 9101, 1, 9102, 1, 9103, 1, 9104, 1, 9105, 1, 9106, 1, 9107, 1, 9108, 1, 9109, 1, 9110, 1, 9111, 9112, 1, 9113, 1, 9114, 1, 1, 1, 1, 1, 9115, 1, 9116, 1, 9117, 1, 9118, 1, 9119, 1, 9120, 1, 9121, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9122, 1, 9123, 1, 1, 9124, 1, 9125, 1, 9126, 1, 9127, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9128, 1, 9129, 1, 9130, 1, 9131, 1, 9132, 1, 9133, 1, 9134, 1, 9135, 1, 9136, 1, 9137, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9138, 1, 9139, 1, 9140, 1, 9141, 1, 9142, 1, 1, 1, 1, 1, 1, 1, 1, 9143, 1, 9144, 1, 9145, 1, 9146, 1, 9147, 1, 9148, 1, 9149, 1, 1, 9150, 1, 1, 1, 9151, 1, 1, 1, 9152, 1, 1, 1, 1, 1, 1, 9153, 1, 9154, 1, 9155, 1, 9156, 1, 9157, 1, 9158, 1, 9159, 1, 9160, 1, 9161, 1, 9162, 1, 9163, 1, 9164, 1, 9165, 1, 9166, 1, 9167, 1, 9168, 1, 9169, 1, 9170, 1, 9171, 1, 9172, 1, 9173, 1, 9174, 1, 9175, 1, 1, 1, 1, 1, 9176, 1, 9177, 1, 9178, 1, 9179, 1, 9180, 1, 9181, 1, 9182, 1, 9183, 1, 9184, 1, 9185, 1, 9186, 1, 9187, 1, 9188, 1, 9189, 1, 9190, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9191, 1, 1, 1, 9192, 1, 9193, 1, 9194, 1, 9195, 1, 9196, 1, 9197, 1, 9198, 1, 9199, 1, 9200, 1, 9201, 1, 9202, 1, 9203, 1, 9204, 1, 9205, 1, 1, 1, 1, 1, 9206, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9207, 1, 9208, 1, 1, 9209, 1, 9210, 1, 9211, 1, 9212, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9213, 1, 9214, 1, 9215, 1, 9216, 1, 9217, 1, 9218, 1, 9219, 1, 9220, 1, 9221, 1, 9222, 1, 9223, 1, 9224, 1, 9225, 1, 9226, 1, 9227, 1, 9228, 1, 1, 1, 1, 9229, 1, 1, 1, 1, 1, 1, 1, 1, 9230, 1, 9231, 1, 9232, 1, 9233, 1, 9234, 1, 9235, 1, 9236, 1, 9237, 1, 9238, 1, 9239, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9240, 1, 9241, 1, 9242, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9243, 1, 9244, 1, 9245, 1, 9246, 1, 9247, 1, 9248, 1, 9249, 1, 9250, 1, 9251, 1, 9252, 1, 9253, 1, 9254, 9255, 1, 9256, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9257, 1, 9258, 9259, 9260, 9261, 1, 1, 1, 1, 1, 9262, 1, 9263, 9264, 9265, 1, 9266, 9267, 1, 1, 1, 1, 1, 1, 9268, 1, 9269, 1, 9270, 1, 9271, 1, 9272, 1, 9273, 1, 9274, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9275, 1, 9276, 1, 9277, 1, 9278, 1, 9279, 1, 9280, 1, 9281, 1, 1, 1, 9282, 1, 9283, 1, 9284, 1, 9285, 1, 9286, 1, 9287, 1, 1, 1, 1, 1, 9288, 1, 1, 9289, 1, 9290, 1, 9291, 9292, 9293, 1, 9294, 1, 9295, 1, 9296, 1, 9297, 1, 9298, 1, 9299, 1, 9300, 1, 9301, 1, 9302, 1, 9303, 1, 9304, 1, 9305, 1, 9306, 1, 9307, 1, 9308, 1, 9309, 1, 9310, 1, 9311, 1, 9312, 1, 9313, 9314, 1, 1, 1, 1, 1, 1, 1, 1, 9315, 1, 9316, 1, 9317, 1, 9318, 1, 9319, 1, 9320, 1, 9321, 1, 9322, 1, 9323, 1, 9324, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9325, 1, 9326, 1, 9327, 1, 9328, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9329, 1, 9330, 1, 9331, 1, 9332, 1, 9333, 1, 9334, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9335, 1, 9336, 1, 9337, 1, 9338, 1, 9339, 1, 9340, 1, 9341, 1, 9342, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9343, 1, 9344, 1, 9345, 1, 9346, 1, 9347, 1, 9348, 1, 9349, 1, 9350, 1, 9351, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9352, 1, 9353, 1, 9354, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9355, 1, 9356, 1, 9357, 1, 9358, 1, 9359, 1, 9360, 1, 9361, 1, 9362, 1, 9363, 1, 9364, 1, 9365, 1, 9366, 1, 1, 1, 1, 1, 9367, 1, 9368, 1, 9369, 1, 9370, 1, 9371, 1, 9372, 1, 9373, 1, 9374, 1, 9375, 1, 9376, 1, 9377, 1, 9378, 1, 9379, 1, 9380, 1, 9381, 1, 9382, 1, 9383, 1, 1, 1, 1, 1, 1, 9384, 1, 1, 1, 1, 1, 9385, 1, 9386, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9387, 1, 1, 9388, 1, 9389, 1, 9390, 1, 9391, 1, 9392, 1, 9393, 1, 9394, 1, 9395, 1, 9396, 1, 9397, 1, 9398, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9399, 1, 9400, 1, 9401, 1, 9402, 1, 9403, 1, 9404, 1, 9405, 1, 9406, 1, 9407, 1, 9408, 1, 9409, 1, 9410, 1, 9411, 1, 9412, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9413, 1, 9414, 1, 9415, 1, 9416, 1, 9417, 1, 9418, 1, 9419, 1, 9420, 1, 9421, 1, 9422, 1, 9423, 1, 9424, 1, 9425, 1, 9426, 1, 9427, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9428, 1, 9429, 1, 9430, 1, 9431, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9432, 1, 9433, 1, 9434, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9435, 1, 9436, 1, 9437, 1, 9438, 1, 9439, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9440, 1, 9441, 1, 9442, 1, 9443, 1, 9444, 1, 9445, 1, 9446, 1, 9447, 1, 9448, 1, 9449, 1, 9450, 9451, 1, 1, 1, 1, 1, 1, 1, 1, 9452, 9453, 1, 9454, 9455, 1, 9456, 1, 9457, 1, 9458, 1, 9459, 1, 9460, 1, 1, 1, 1, 9461, 1, 9462, 1, 1, 1, 1, 9463, 1, 9464, 1, 9465, 1, 9466, 1, 9467, 1, 9468, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9469, 1, 9470, 1, 9471, 1, 9472, 1, 9473, 1, 9474, 1, 9475, 1, 9476, 1, 9477, 1, 9478, 1, 9479, 1, 9480, 1, 9481, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9482, 1, 9483, 1, 9484, 1, 9485, 1, 9486, 1, 9487, 1, 9488, 1, 9489, 1, 9490, 9491, 1, 9492, 1, 9493, 9494, 1, 1, 9495, 9496, 9497, 9498, 1, 1, 9499, 9500, 1, 9501, 9502, 9503, 1, 9504, 1, 1, 1, 1, 1, 1, 1, 9505, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9506, 1, 9507, 1, 9508, 1, 9509, 1, 9510, 1, 9511, 1, 9512, 1, 9513, 1, 9514, 1, 9515, 1, 9516, 1, 9517, 1, 9518, 1, 9519, 1, 9520, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9521, 1, 9522, 1, 9523, 1, 9524, 1, 9525, 1, 9526, 1, 9527, 1, 9528, 1, 9529, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9530, 1, 9531, 1, 9532, 1, 9533, 1, 9534, 1, 9535, 1, 9536, 1, 9537, 1, 9538, 1, 9539, 1, 9540, 1, 9541, 1, 9542, 1, 9543, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9544, 1, 1, 1, 9545, 1, 9546, 1, 9547, 1, 9548, 1, 9549, 1, 1, 1, 1, 1, 9550, 1, 9551, 1, 9552, 1, 9553, 1, 9554, 1, 9555, 1, 9556, 1, 9557, 1, 9558, 1, 9559, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9560, 1, 9561, 1, 9562, 1, 9563, 1, 9564, 1, 9565, 1, 9566, 1, 9567, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9568, 1, 9569, 1, 9570, 1, 9571, 1, 9572, 1, 9573, 1, 9574, 1, 9575, 1, 1, 1, 9576, 1, 9577, 1, 9578, 1, 9579, 1, 9580, 1, 9581, 1, 9582, 1, 9583, 1, 9584, 1, 9585, 1, 9586, 1, 9587, 1, 9588, 1, 9589, 1, 9590, 1, 9591, 1, 9592, 1, 9593, 1, 9594, 9595, 1, 1, 9596, 1, 1, 1, 1, 1, 9597, 1, 1, 1, 9598, 1, 9599, 1, 9600, 1, 9601, 1, 1, 1, 9602, 1, 9603, 1, 9604, 1, 9605, 1, 9606, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9607, 1, 9608, 1, 9609, 1, 9610, 1, 9611, 1, 9612, 1, 9613, 1, 9614, 1, 9615, 1, 9616, 1, 9617, 1, 9618, 1, 9619, 1, 9620, 1, 9621, 1, 9622, 1, 9623, 1, 9624, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9625, 1, 9626, 1, 9627, 1, 9628, 1, 9629, 1, 9630, 9631, 9632, 9633, 1, 9634, 9635, 1, 1, 1, 1, 1, 9636, 1, 1, 1, 9637, 1, 1, 1, 9638, 1, 9639, 1, 9640, 1, 9641, 1, 9642, 1, 9643, 1, 9644, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9645, 1, 9646, 1, 9647, 1, 9648, 1, 9649, 1, 9650, 1, 9651, 1, 9652, 1, 9653, 1, 9654, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9655, 1, 9656, 1, 9657, 1, 9658, 1, 9659, 1, 9660, 1, 9661, 1, 9662, 1, 9663, 1, 9664, 1, 9665, 1, 9666, 1, 9667, 1, 9668, 1, 9669, 1, 9670, 1, 9671, 1, 9672, 1, 9673, 1, 9674, 1, 9675, 1, 9676, 1, 9677, 1, 9678, 1, 9679, 1, 1, 1, 9680, 1, 9681, 1, 9682, 1, 9683, 1, 9684, 9685, 9686, 9687, 9688, 9689, 9690, 9691, 9692, 9693, 9694, 9695, 9696, 9697, 9698, 9699, 9700, 9701, 9702, 9703, 9704, 9705, 9706, 9707, 9708, 9709, 1, 1, 1, 1, 1, 1, 9710, 9711, 9712, 9713, 9714, 9715, 9716, 9717, 9718, 9719, 9720, 9721, 9722, 9723, 9724, 9725, 9726, 9727, 9728, 9729, 9730, 9731, 9732, 9733, 9734, 9735, 1, 9737, 9736, 9739, 9738, 9741, 9740, 9743, 9742, 9745, 9744, 9747, 9746, 9749, 9748, 9751, 9750, 9753, 9752, 9755, 9754, 9757, 9756, 9759, 9758, 9761, 9760, 9763, 9762, 9765, 9764, 9767, 9766, 9769, 9768, 9771, 9770, 9773, 9772, 9775, 9774, 9777, 9776, 9779, 9778, 9781, 9780, 9783, 9782, 9785, 9784, 9787, 9786, 9789, 9788, 9791, 9790, 9793, 9792, 9795, 9794, 9797, 9796, 9799, 9798, 9801, 9800, 9803, 9802, 9805, 9804, 9807, 9806, 9809, 9808, 9811, 9810, 9813, 9812, 9815, 9814, 9817, 9816, 9819, 9818, 9821, 9820, 9823, 9822, 9825, 9824, 9827, 9826, 9829, 9828, 9831, 9830, 9833, 9832, 9832, 9832, 9832, 9832, 9832, 9832, 9832, 9832, 9832, 9832, 9832, 9832, 9832, 9832, 9832, 9832, 9832, 9832, 9832, 9832, 9832, 9832, 9832, 9832, 9832, 9832, 9832, 9832, 9832, 9832, 9832, 9832, 9832, 9832, 9832, 9832, 9832, 9832, 9832, 9832, 9834, 9832, 9836, 9835, 9835, 9835, 9835, 9835, 9835, 9835, 9835, 9835, 9835, 9835, 9835, 9835, 9835, 9835, 9835, 9835, 9835, 9835, 9835, 9835, 9835, 9835, 9835, 9835, 9835, 9835, 9835, 9835, 9835, 9835, 9835, 9835, 9835, 9835, 9835, 9835, 9835, 9835, 9835, 9835, 9835, 9835, 9835, 9835, 9835, 9835, 9835, 9835, 9835, 9835, 9835, 9835, 9835, 9835, 9837, 9835, 9839, 9838, 9841, 9840, 9843, 9842, 9842, 9842, 9842, 9842, 9842, 9842, 9842, 9842, 9842, 9842, 9842, 9842, 9842, 9842, 9842, 9842, 9842, 9842, 9842, 9842, 9842, 9842, 9842, 9842, 9842, 9842, 9842, 9842, 9842, 9842, 9842, 9842, 9842, 9842, 9842, 9842, 9842, 9842, 9842, 9842, 9842, 9842, 9842, 9842, 9842, 9842, 9842, 9842, 9842, 9842, 9844, 9842, 9846, 9845, 9848, 9847, 9850, 9849, 9852, 9851, 9854, 9853, 9856, 9855, 9858, 9857, 9860, 9859, 9862, 9861, 9861, 9861, 9861, 9861, 9861, 9861, 9861, 9861, 9861, 9861, 9861, 9861, 9861, 9861, 9861, 9861, 9861, 9861, 9861, 9861, 9861, 9861, 9861, 9861, 9861, 9861, 9861, 9861, 9861, 9861, 9861, 9861, 9861, 9861, 9861, 9861, 9861, 9861, 9863, 9864, 9861, 9861, 9861, 9861, 9861, 9861, 9861, 9865, 9861, 9861, 9861, 9861, 9866, 9867, 9861, 9869, 9868, 9871, 9870, 9873, 9872, 9875, 9874, 9877, 9876, 9879, 9878, 9881, 9880, 9883, 9882, 9882, 9882, 9882, 9882, 9882, 9882, 9882, 9882, 9882, 9882, 9882, 9882, 9882, 9882, 9882, 9882, 9882, 9882, 9882, 9882, 9882, 9882, 9882, 9882, 9882, 9882, 9882, 9882, 9882, 9882, 9882, 9882, 9882, 9882, 9882, 9882, 9882, 9882, 9884, 9885, 9882, 9882, 9882, 9886, 9887, 9882, 9882, 9888, 9882, 9882, 9882, 9882, 9889, 9890, 9882, 9892, 9891, 9894, 9893, 9896, 9895, 9898, 9897, 9900, 9899, 9899, 9899, 9899, 9899, 9899, 9899, 9899, 9899, 9899, 9899, 9899, 9899, 9899, 9899, 9899, 9899, 9899, 9899, 9899, 9899, 9899, 9899, 9899, 9899, 9899, 9899, 9899, 9899, 9899, 9899, 9899, 9899, 9899, 9899, 9899, 9899, 9899, 9899, 9899, 9899, 9899, 9899, 9899, 9899, 9901, 9899, 9899, 9899, 9899, 9902, 9899, 9904, 9903, 9906, 9905, 9908, 9907, 9910, 9909, 9912, 9911, 9914, 9913, 9916, 9915, 9918, 9917, 9920, 9919, 9922, 9921, 9921, 9921, 9921, 9921, 9921, 9921, 9921, 9921, 9921, 9921, 9921, 9921, 9921, 9921, 9921, 9921, 9921, 9921, 9921, 9921, 9921, 9921, 9921, 9921, 9921, 9921, 9921, 9921, 9921, 9921, 9921, 9921, 9921, 9921, 9921, 9921, 9921, 9921, 9921, 9921, 9921, 9921, 9921, 9921, 9921, 9921, 9921, 9923, 9921, 9925, 9924, 9927, 9926, 9929, 9928, 9931, 9930, 9933, 9932, 9935, 9934, 9937, 9936, 9939, 9938, 9941, 9940, 9943, 9942, 9945, 9944, 9947, 9946, 9949, 9948, 9948, 9948, 9948, 9948, 9948, 9948, 9948, 9948, 9948, 9948, 9948, 9948, 9948, 9948, 9948, 9948, 9948, 9948, 9948, 9948, 9948, 9948, 9948, 9948, 9948, 9948, 9948, 9948, 9948, 9948, 9948, 9948, 9948, 9948, 9948, 9948, 9948, 9950, 9948, 9951, 9948, 9953, 9952, 9955, 9954, 9957, 9956, 9959, 9958, 9961, 9960, 9963, 9962, 9965, 9964, 9967, 9966, 0 }; static const short _char_ref_trans_targs[] = { 2, 0, 5, 6, 10, 15, 19, 21, 25, 29, 33, 35, 41, 53, 56, 63, 67, 3, 4, 7624, 7625, 7, 8, 9, 7626, 11, 12, 13, 14, 7623, 16, 18, 17, 7627, 7623, 20, 7623, 22, 23, 24, 7628, 26, 27, 28, 7623, 30, 31, 32, 7623, 34, 7623, 36, 39, 37, 38, 7623, 40, 7623, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 7623, 54, 55, 7629, 57, 59, 58, 7623, 60, 61, 62, 7623, 64, 65, 66, 7630, 68, 7631, 70, 83, 85, 101, 103, 106, 110, 113, 71, 78, 72, 73, 74, 75, 76, 77, 7623, 79, 80, 7623, 81, 82, 7623, 84, 7623, 86, 91, 99, 87, 88, 89, 90, 7623, 92, 93, 94, 95, 96, 97, 98, 7623, 100, 7623, 102, 7623, 104, 105, 7623, 107, 108, 109, 7623, 111, 112, 7623, 114, 115, 116, 117, 7623, 119, 122, 124, 152, 168, 171, 184, 186, 188, 210, 256, 320, 324, 327, 120, 121, 7623, 123, 7632, 125, 129, 147, 126, 127, 128, 7623, 7623, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 7623, 148, 149, 150, 151, 7623, 153, 157, 160, 163, 154, 155, 156, 7623, 158, 159, 7633, 161, 162, 7623, 164, 165, 166, 167, 7623, 169, 170, 7623, 172, 177, 173, 174, 175, 176, 7623, 178, 179, 180, 181, 182, 183, 7623, 185, 7623, 187, 7623, 189, 190, 191, 192, 193, 196, 201, 205, 194, 195, 7623, 197, 198, 199, 200, 7623, 202, 203, 204, 7623, 206, 207, 208, 209, 7623, 211, 212, 233, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 7623, 234, 235, 236, 237, 238, 239, 240, 251, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 7623, 252, 253, 254, 255, 7623, 257, 261, 283, 291, 258, 259, 7623, 260, 7623, 262, 268, 271, 263, 264, 265, 266, 267, 7623, 269, 270, 7623, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 7623, 284, 285, 7623, 286, 287, 288, 289, 290, 7623, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 7623, 321, 322, 323, 7623, 325, 326, 7623, 328, 7623, 329, 330, 331, 7623, 333, 340, 343, 346, 349, 359, 365, 369, 371, 423, 649, 7623, 334, 335, 336, 337, 338, 339, 7623, 341, 342, 7623, 344, 345, 7623, 347, 348, 7623, 350, 354, 356, 351, 352, 353, 7623, 355, 7623, 357, 358, 7623, 360, 364, 361, 362, 363, 7623, 7623, 366, 7623, 367, 368, 7623, 370, 7623, 372, 412, 373, 408, 374, 375, 376, 377, 378, 379, 380, 381, 386, 398, 403, 382, 383, 384, 385, 7623, 387, 388, 389, 7623, 390, 391, 392, 393, 394, 395, 396, 397, 7623, 399, 400, 401, 402, 7623, 404, 405, 406, 407, 7623, 409, 410, 411, 7623, 413, 414, 415, 416, 417, 418, 419, 420, 421, 422, 7623, 424, 426, 435, 558, 425, 7623, 7623, 427, 430, 428, 429, 7623, 431, 432, 433, 434, 7623, 436, 437, 438, 439, 454, 464, 518, 531, 547, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 7623, 455, 456, 457, 7623, 458, 459, 460, 461, 462, 463, 7623, 465, 486, 466, 467, 468, 473, 483, 469, 470, 471, 472, 7623, 474, 475, 476, 477, 478, 479, 480, 481, 482, 7623, 484, 485, 7623, 487, 488, 489, 508, 490, 491, 492, 493, 498, 494, 495, 496, 497, 7623, 499, 500, 501, 502, 503, 504, 505, 506, 507, 7623, 509, 510, 511, 512, 513, 514, 515, 516, 517, 7623, 519, 520, 521, 522, 523, 528, 524, 525, 526, 527, 7623, 529, 530, 7623, 532, 533, 538, 534, 535, 536, 537, 7623, 539, 540, 541, 542, 543, 544, 545, 546, 7623, 548, 549, 550, 551, 552, 553, 554, 555, 556, 557, 7623, 559, 560, 575, 580, 613, 636, 644, 561, 562, 563, 564, 7623, 565, 568, 566, 567, 7623, 569, 570, 571, 572, 573, 574, 7623, 576, 577, 578, 579, 7623, 581, 582, 583, 584, 595, 604, 585, 586, 587, 588, 589, 590, 591, 592, 593, 594, 7623, 596, 597, 598, 599, 600, 601, 602, 603, 7623, 605, 606, 607, 608, 609, 7623, 610, 611, 612, 7623, 614, 615, 616, 617, 618, 627, 619, 620, 621, 622, 623, 624, 625, 626, 7623, 628, 629, 630, 631, 632, 7623, 633, 634, 635, 7623, 637, 638, 7623, 639, 640, 641, 642, 643, 7623, 645, 646, 647, 648, 7623, 650, 652, 651, 7623, 653, 654, 655, 7623, 657, 659, 660, 664, 672, 675, 677, 681, 687, 720, 726, 732, 749, 754, 756, 758, 658, 7623, 7634, 661, 662, 663, 7635, 665, 669, 671, 666, 667, 668, 7623, 670, 7636, 7623, 673, 674, 7623, 676, 7623, 678, 679, 680, 7637, 682, 683, 684, 685, 686, 7623, 688, 691, 689, 690, 7623, 692, 693, 694, 705, 695, 696, 697, 698, 699, 700, 701, 702, 703, 704, 7623, 706, 707, 708, 709, 710, 711, 712, 713, 714, 715, 716, 717, 718, 719, 7623, 721, 724, 722, 723, 7623, 725, 7623, 727, 728, 729, 730, 731, 7623, 733, 734, 741, 735, 7623, 736, 737, 738, 739, 740, 7623, 742, 743, 744, 745, 746, 747, 748, 7623, 750, 752, 751, 7623, 753, 7623, 755, 7623, 757, 7638, 759, 763, 760, 761, 762, 7623, 764, 765, 766, 767, 768, 769, 770, 771, 772, 7623, 774, 776, 778, 809, 824, 775, 7623, 777, 7623, 779, 780, 781, 782, 783, 794, 784, 785, 786, 787, 788, 789, 790, 791, 792, 793, 7623, 795, 796, 797, 798, 799, 800, 801, 802, 803, 804, 805, 806, 807, 808, 7623, 810, 812, 816, 811, 7623, 813, 814, 815, 7623, 817, 818, 819, 820, 821, 822, 823, 7623, 825, 826, 7623, 828, 7639, 831, 836, 841, 850, 853, 855, 856, 859, 909, 912, 829, 830, 7623, 832, 833, 834, 7623, 835, 7623, 837, 838, 839, 840, 7623, 842, 846, 849, 843, 844, 845, 7623, 847, 848, 7623, 7623, 851, 852, 7623, 854, 7623, 7623, 857, 858, 7623, 860, 861, 862, 863, 864, 865, 874, 883, 890, 894, 904, 866, 867, 868, 869, 7623, 870, 871, 872, 873, 7623, 875, 876, 877, 878, 879, 880, 881, 882, 7623, 884, 885, 886, 887, 888, 889, 7623, 891, 892, 893, 7623, 895, 896, 897, 898, 899, 900, 901, 902, 903, 7623, 905, 906, 907, 908, 7623, 910, 911, 7623, 7623, 914, 919, 924, 928, 930, 941, 956, 963, 915, 916, 917, 918, 7623, 920, 923, 921, 922, 7623, 7623, 925, 926, 927, 7623, 929, 7623, 931, 932, 933, 934, 935, 936, 937, 938, 939, 940, 7623, 942, 944, 943, 7623, 945, 946, 947, 948, 949, 950, 951, 952, 953, 954, 955, 7623, 957, 959, 958, 7623, 960, 961, 962, 7623, 964, 965, 966, 974, 967, 968, 969, 970, 971, 972, 973, 7623, 975, 976, 977, 978, 7623, 980, 983, 987, 990, 994, 998, 1001, 1003, 1007, 1023, 1055, 1063, 1066, 1071, 981, 982, 7623, 984, 985, 986, 7623, 988, 989, 7623, 991, 992, 993, 7640, 995, 997, 996, 7641, 7623, 999, 1000, 7623, 1002, 7623, 1004, 1005, 1006, 7642, 7623, 1008, 1018, 1009, 1011, 1010, 7623, 1012, 1013, 1014, 1015, 1016, 1017, 7623, 1019, 1020, 1021, 1022, 7623, 1024, 1038, 7623, 1025, 1026, 1030, 1027, 1028, 1029, 7623, 1031, 1032, 1033, 1034, 1035, 1036, 1037, 7623, 1039, 1040, 1041, 1042, 1043, 1044, 1045, 1050, 1046, 1047, 1048, 1049, 7623, 1051, 1052, 1053, 1054, 7623, 1056, 1059, 1061, 1057, 1058, 7623, 1060, 7623, 1062, 7623, 1064, 1065, 7623, 1067, 1068, 1069, 1070, 7623, 1072, 1075, 1073, 1074, 7623, 7643, 1077, 1082, 1084, 1087, 1094, 1078, 1081, 1079, 1080, 7623, 7623, 1083, 7623, 1085, 1086, 7623, 1088, 1090, 1089, 7623, 1091, 1092, 1093, 7623, 1095, 1096, 1097, 7623, 1099, 1102, 1105, 1109, 1115, 1117, 1120, 1100, 1101, 7623, 1103, 1104, 7623, 1106, 1107, 1108, 7623, 1110, 1114, 1111, 1112, 1113, 7623, 7623, 1116, 7623, 1118, 1119, 7623, 1121, 1122, 7623, 1124, 7644, 1127, 1148, 1158, 1383, 1385, 1394, 1399, 1484, 1492, 1125, 1126, 7623, 1128, 1132, 1136, 1138, 1146, 1129, 1130, 1131, 7623, 1133, 1134, 1135, 7623, 1137, 7623, 1139, 1140, 1141, 1142, 1143, 1144, 1145, 7623, 1147, 7623, 1149, 1153, 1157, 1150, 1151, 1152, 7623, 1154, 1155, 1156, 7623, 7623, 1159, 1334, 1160, 1161, 1190, 1197, 1230, 1235, 1251, 1280, 1310, 1319, 1324, 1162, 1173, 1163, 1164, 1165, 1166, 1167, 1168, 1169, 1170, 1171, 1172, 7623, 1174, 1175, 1176, 7623, 1177, 1180, 1178, 1179, 7623, 1181, 1182, 1183, 1184, 1185, 1186, 1187, 1188, 1189, 7623, 1191, 1192, 1193, 1194, 1195, 1196, 7623, 1198, 1199, 1210, 1200, 1201, 1202, 1203, 1204, 1205, 1206, 1207, 1208, 1209, 7623, 1211, 1212, 1221, 1213, 1214, 1215, 1216, 1217, 1218, 1219, 1220, 7623, 1222, 1223, 1224, 1225, 1226, 7623, 1227, 1228, 1229, 7623, 1231, 1232, 1233, 1234, 7623, 1236, 1237, 1238, 1239, 1240, 1245, 1241, 1242, 1243, 1244, 7623, 1246, 1247, 1248, 1249, 1250, 7623, 1252, 1265, 1253, 7623, 1254, 1259, 1255, 1256, 1257, 1258, 7623, 1260, 1261, 1262, 1263, 1264, 7623, 1266, 1267, 1268, 1269, 1270, 1271, 7623, 1272, 1275, 1273, 1274, 7623, 1276, 1277, 1278, 1279, 7623, 1281, 1282, 1292, 1301, 1283, 1284, 1285, 1286, 1287, 1288, 1289, 1290, 1291, 7623, 1293, 1294, 1295, 1296, 1297, 1298, 1299, 1300, 7623, 1302, 1303, 1304, 1305, 1306, 7623, 1307, 1308, 1309, 7623, 1311, 1312, 1313, 1314, 1315, 7623, 1316, 1317, 1318, 7623, 1320, 1321, 1322, 1323, 7623, 1325, 1326, 1327, 1328, 1329, 1330, 1331, 1332, 1333, 7623, 1335, 1336, 1348, 1357, 1364, 1368, 1378, 1337, 1338, 1339, 1340, 1341, 1342, 1343, 1344, 1345, 1346, 1347, 7623, 1349, 1350, 1351, 1352, 1353, 1354, 1355, 1356, 7623, 1358, 1359, 1360, 1361, 1362, 1363, 7623, 1365, 1366, 1367, 7623, 1369, 1370, 1371, 1372, 1373, 1374, 1375, 1376, 1377, 7623, 1379, 1380, 1381, 1382, 7623, 1384, 7623, 7623, 1386, 1387, 1388, 1389, 1390, 1391, 1392, 1393, 7623, 1395, 1396, 1397, 1398, 7623, 1400, 1460, 1462, 1401, 1402, 1421, 1431, 1450, 1403, 1404, 1405, 1406, 1411, 1407, 1408, 1409, 1410, 7623, 1412, 1413, 1414, 1415, 1416, 1417, 1418, 1419, 1420, 7623, 1422, 1423, 1424, 1425, 1426, 1427, 1428, 1429, 1430, 7623, 1432, 1433, 1434, 1435, 1440, 1436, 1437, 1438, 1439, 7623, 1441, 1442, 1443, 1444, 1445, 1446, 1447, 1448, 1449, 7623, 1451, 1452, 1453, 1454, 1455, 1456, 1457, 1458, 1459, 7623, 1461, 7623, 1463, 1464, 1465, 1474, 1466, 1467, 1468, 1469, 1470, 1471, 1472, 1473, 7623, 1475, 1476, 1477, 1478, 1479, 1480, 1481, 1482, 1483, 7623, 1485, 1487, 1488, 1486, 7623, 7623, 1489, 1490, 1491, 7623, 7623, 1494, 1496, 1498, 1515, 1517, 1525, 1528, 1531, 1495, 7623, 1497, 7623, 1499, 1508, 1500, 1501, 1502, 1503, 1504, 1505, 1506, 1507, 7623, 1509, 1510, 1511, 1512, 1513, 1514, 7623, 1516, 7623, 1518, 1519, 1520, 1521, 1522, 1523, 1524, 7623, 1526, 1527, 7623, 1529, 1530, 7623, 7623, 1533, 1536, 1541, 1551, 1629, 1631, 2018, 2021, 2025, 1534, 1535, 7623, 1537, 1538, 1539, 1540, 7623, 1542, 1546, 1550, 1543, 1544, 1545, 7623, 1547, 1548, 1549, 7623, 7623, 1552, 1598, 1624, 1553, 1554, 1555, 1556, 1557, 1558, 1569, 1585, 1559, 1560, 1561, 1562, 1563, 1564, 1565, 1566, 1567, 1568, 7623, 1570, 1571, 1572, 1579, 1573, 1574, 1575, 1576, 1577, 1578, 7623, 1580, 1581, 1582, 1583, 1584, 7623, 1586, 1587, 1588, 1589, 1590, 1591, 1592, 1593, 1594, 1595, 1596, 1597, 7623, 1599, 1600, 1601, 1602, 1616, 1603, 1604, 1605, 1606, 1607, 1608, 1609, 1610, 1611, 1612, 1613, 1614, 1615, 7623, 1617, 1618, 1619, 1620, 1621, 1622, 1623, 7623, 1625, 1626, 1627, 1628, 7623, 1630, 7623, 1632, 1637, 1651, 1653, 1633, 1634, 1635, 1636, 7623, 1638, 1639, 1640, 1641, 1642, 1643, 1644, 1645, 1646, 1647, 1648, 1649, 1650, 7623, 1652, 7623, 7623, 1654, 1668, 1685, 1706, 1753, 1770, 1823, 1851, 1874, 1908, 1983, 2007, 1655, 1663, 1656, 1657, 1658, 1659, 1660, 1661, 1662, 7623, 1664, 1665, 1666, 1667, 7623, 1669, 1670, 1671, 1672, 1673, 1674, 1675, 1676, 1677, 1678, 1679, 1680, 1681, 1682, 1683, 1684, 7623, 1686, 1692, 1701, 1687, 1688, 1689, 1690, 1691, 7623, 1693, 1694, 1695, 7623, 1696, 1697, 1698, 1699, 1700, 7623, 1702, 1703, 1704, 1705, 7623, 1707, 1708, 1709, 1710, 1711, 1712, 7623, 1713, 1718, 1727, 1734, 1738, 1748, 1714, 1715, 1716, 1717, 7623, 1719, 1720, 1721, 1722, 1723, 1724, 1725, 1726, 7623, 1728, 1729, 1730, 1731, 1732, 1733, 7623, 1735, 1736, 1737, 7623, 1739, 1740, 1741, 1742, 1743, 1744, 1745, 1746, 1747, 7623, 1749, 1750, 1751, 1752, 7623, 1754, 1755, 1756, 1757, 1765, 1758, 1759, 1760, 1761, 1762, 1763, 1764, 7623, 1766, 1767, 1768, 1769, 7623, 1771, 1772, 1790, 1773, 1774, 1775, 1776, 1777, 1778, 1779, 1780, 1781, 7623, 1782, 1785, 1783, 1784, 7623, 1786, 1787, 1788, 1789, 7623, 1791, 7623, 1792, 1797, 1804, 1808, 1818, 1793, 1794, 1795, 1796, 7623, 1798, 1799, 1800, 1801, 1802, 1803, 7623, 1805, 1806, 1807, 7623, 1809, 1810, 1811, 1812, 1813, 1814, 1815, 1816, 1817, 7623, 1819, 1820, 1821, 1822, 7623, 1824, 1825, 1826, 1827, 1828, 1829, 1843, 1830, 1831, 1832, 1833, 1834, 1835, 1836, 1837, 1838, 1839, 1840, 1841, 1842, 7623, 1844, 1845, 1846, 1847, 1848, 1849, 1850, 7623, 1852, 1853, 1854, 1855, 1856, 1857, 1858, 7623, 1859, 1864, 1860, 1861, 1862, 1863, 7623, 1865, 1866, 1867, 1868, 1869, 1870, 1871, 1872, 1873, 7623, 1875, 1888, 1876, 1877, 1878, 1879, 1880, 1881, 1882, 1883, 1884, 1885, 1886, 1887, 7623, 1889, 1890, 1891, 1892, 1893, 1894, 1895, 1896, 1897, 1898, 1899, 7623, 1900, 1903, 1901, 1902, 7623, 1904, 1905, 1906, 1907, 7623, 1909, 1936, 1910, 1911, 1912, 1913, 1914, 1915, 1916, 1925, 1917, 1918, 1919, 7623, 1920, 1921, 1922, 1923, 1924, 7623, 1926, 1927, 1928, 1929, 1930, 7623, 1931, 1932, 1933, 1934, 1935, 7623, 1937, 1946, 1972, 1938, 1939, 1940, 7623, 1941, 1942, 1943, 1944, 1945, 7623, 1947, 1948, 1949, 1950, 1951, 7623, 1952, 1957, 1967, 1953, 1954, 1955, 1956, 7623, 1958, 1959, 1960, 1961, 1962, 1963, 1964, 1965, 1966, 7623, 1968, 1969, 1970, 1971, 7623, 1973, 1974, 1975, 1976, 1977, 7623, 1978, 1979, 1980, 1981, 1982, 7623, 1984, 1985, 1986, 1987, 7623, 1988, 1993, 2002, 1989, 1990, 1991, 1992, 7623, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 7623, 2003, 2004, 2005, 2006, 7623, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 7623, 2019, 2020, 7623, 2022, 2023, 2024, 7645, 7623, 2027, 2031, 2035, 2039, 2044, 2046, 2050, 2062, 2065, 2089, 2090, 2096, 2103, 2105, 2028, 2029, 2030, 7623, 2032, 2033, 2034, 7646, 2036, 2038, 2037, 7647, 7623, 2040, 2041, 2042, 2043, 7623, 2045, 7623, 2047, 2048, 2049, 7648, 2051, 2054, 2057, 2052, 2053, 7623, 2055, 2056, 7623, 2058, 2059, 2060, 2061, 7623, 2063, 2064, 7623, 2066, 2067, 2068, 2069, 2070, 2071, 2072, 2073, 2084, 2074, 2075, 2076, 2077, 2078, 2079, 2080, 2081, 2082, 2083, 7623, 2085, 2086, 2087, 2088, 7623, 7623, 2091, 2093, 2092, 7623, 2094, 2095, 7649, 2097, 2098, 2100, 2099, 7650, 2101, 2102, 7623, 2104, 7651, 2106, 2107, 2108, 2118, 2109, 2111, 2110, 7623, 2112, 2113, 2114, 2115, 7623, 2116, 2117, 7623, 2119, 2120, 2121, 2122, 2123, 2124, 2125, 2126, 2127, 2128, 7623, 2130, 2137, 2139, 2141, 2143, 2144, 2152, 2166, 2210, 2131, 2132, 2133, 2134, 2135, 2136, 7623, 2138, 7623, 2140, 7623, 2142, 7623, 7623, 2145, 2146, 2147, 2148, 2149, 2150, 2151, 7623, 2153, 2164, 2154, 2155, 2156, 2157, 2158, 2159, 2160, 2161, 2162, 2163, 7623, 2165, 7623, 7623, 2167, 2193, 2196, 2168, 2169, 2170, 2171, 2172, 7623, 2173, 2178, 2188, 2174, 2175, 2176, 2177, 7623, 2179, 2180, 2181, 2182, 2183, 2184, 2185, 2186, 2187, 7623, 2189, 2190, 2191, 2192, 7623, 2194, 2195, 7623, 2197, 2201, 2198, 2199, 2200, 7623, 2202, 2203, 2204, 2205, 2206, 2207, 7623, 2208, 2209, 7623, 2211, 2213, 2212, 7623, 7623, 2215, 2217, 2219, 2222, 2216, 7652, 2218, 7623, 2220, 2221, 7623, 2223, 2224, 7623, 2226, 2230, 2231, 2242, 2252, 2288, 2290, 2292, 2442, 2455, 2465, 2469, 2227, 2228, 2229, 7623, 7653, 2232, 2236, 2238, 2233, 2234, 2235, 7623, 2237, 7623, 2239, 7623, 2240, 2241, 7623, 2243, 2247, 2251, 2244, 2245, 2246, 7623, 2248, 2249, 2250, 7623, 7623, 7623, 2253, 2254, 2255, 2256, 2257, 2258, 2275, 2259, 2265, 2260, 2261, 2262, 2263, 2264, 7623, 2266, 2267, 2268, 2269, 2270, 2271, 2272, 2273, 2274, 7623, 2276, 2277, 2278, 2279, 2280, 2281, 2282, 2283, 2284, 2285, 2286, 2287, 7623, 2289, 7623, 2291, 7623, 2293, 2294, 2295, 2296, 2324, 2331, 2364, 2369, 2398, 2428, 2437, 2297, 2308, 2298, 2299, 2300, 2301, 2302, 2303, 2304, 2305, 2306, 2307, 7623, 2309, 2310, 2311, 7623, 2312, 2315, 2313, 2314, 7623, 2316, 2317, 2318, 2319, 2320, 2321, 2322, 2323, 7623, 2325, 2326, 2327, 2328, 2329, 2330, 7623, 2332, 2333, 2344, 2334, 2335, 2336, 2337, 2338, 2339, 2340, 2341, 2342, 2343, 7623, 2345, 2346, 2355, 2347, 2348, 2349, 2350, 2351, 2352, 2353, 2354, 7623, 2356, 2357, 2358, 2359, 2360, 7623, 2361, 2362, 2363, 7623, 2365, 2366, 2367, 2368, 7623, 2370, 2383, 2371, 7623, 2372, 2377, 2373, 2374, 2375, 2376, 7623, 2378, 2379, 2380, 2381, 2382, 7623, 2384, 2385, 2386, 2387, 2388, 2389, 7623, 2390, 2393, 2391, 2392, 7623, 2394, 2395, 2396, 2397, 7623, 2399, 2400, 2410, 2419, 2401, 2402, 2403, 2404, 2405, 2406, 2407, 2408, 2409, 7623, 2411, 2412, 2413, 2414, 2415, 2416, 2417, 2418, 7623, 2420, 2421, 2422, 2423, 2424, 7623, 2425, 2426, 2427, 7623, 2429, 2430, 2431, 2432, 2433, 7623, 2434, 2435, 2436, 7623, 2438, 2439, 2440, 2441, 7623, 2443, 2445, 2444, 7623, 2446, 2447, 2448, 2449, 2450, 2451, 2452, 2453, 2454, 7623, 2456, 2457, 2458, 2459, 2460, 2461, 2462, 2463, 2464, 7623, 2466, 2468, 2467, 7623, 7623, 2470, 2471, 2472, 2473, 2474, 2475, 2476, 2477, 2478, 7623, 2480, 2487, 2492, 2497, 2510, 2512, 2551, 2555, 2565, 2568, 2614, 2617, 2620, 2481, 2485, 2482, 2483, 2484, 7623, 2486, 7623, 2488, 2489, 2490, 2491, 7623, 2493, 2494, 2495, 2496, 7623, 7623, 2498, 2502, 2506, 2509, 2499, 2500, 2501, 7623, 2503, 2504, 2505, 7623, 2507, 2508, 7623, 7623, 2511, 7623, 2513, 2514, 2515, 2516, 2525, 2534, 2544, 2517, 2518, 2519, 2520, 2521, 2522, 2523, 2524, 7623, 2526, 2527, 2528, 2529, 2530, 2531, 2532, 2533, 7623, 2535, 2536, 2537, 2538, 2539, 2540, 2541, 2542, 2543, 7623, 2545, 2546, 2547, 2548, 2549, 2550, 7623, 2552, 2553, 2554, 7623, 2556, 2557, 2558, 2559, 2560, 2561, 2562, 2563, 2564, 7623, 2566, 2567, 7623, 2569, 2571, 2570, 7623, 2572, 2573, 2574, 7623, 2575, 2587, 2609, 2576, 2577, 2578, 2579, 2580, 2581, 2582, 2583, 2584, 2585, 2586, 7623, 2588, 2589, 2598, 2590, 2591, 2592, 7623, 2593, 2594, 2595, 2596, 2597, 7623, 2599, 2600, 2601, 2602, 2603, 7623, 2604, 2605, 2606, 2607, 2608, 7623, 2610, 2611, 2612, 2613, 7623, 2615, 2616, 7623, 2618, 2619, 7623, 2621, 2630, 2661, 2662, 7623, 2622, 2623, 2624, 7623, 2625, 2626, 2627, 2628, 2629, 7623, 2631, 2656, 2632, 2633, 2634, 2635, 7623, 2636, 2641, 2651, 2637, 2638, 2639, 2640, 7623, 2642, 2643, 2644, 2645, 2646, 2647, 2648, 2649, 2650, 7623, 2652, 2653, 2654, 2655, 7623, 2657, 2658, 2659, 2660, 7623, 7623, 7623, 2663, 2673, 2664, 2665, 2666, 2667, 7623, 2668, 2669, 2670, 2671, 2672, 7623, 2674, 2675, 7623, 2677, 2680, 2684, 2690, 2693, 2703, 2705, 2729, 2752, 2755, 2763, 2678, 2679, 7654, 2681, 2682, 2683, 7623, 2685, 2688, 2686, 2687, 7623, 2689, 7623, 2691, 2692, 7623, 7623, 2694, 2698, 2702, 2695, 2696, 2697, 7623, 2699, 2700, 2701, 7623, 7623, 2704, 7623, 2706, 2715, 2707, 2713, 2708, 2709, 2710, 2711, 2712, 7623, 2714, 7623, 2716, 2723, 2717, 2718, 2719, 2720, 2721, 2722, 7623, 2724, 2725, 2726, 2727, 2728, 7623, 2730, 2731, 2732, 7623, 2733, 2738, 2747, 2734, 2735, 2736, 2737, 7623, 2739, 2740, 2741, 2742, 2743, 2744, 2745, 2746, 7623, 2748, 2749, 2750, 2751, 7623, 2753, 2754, 7623, 2756, 2757, 2758, 2759, 2760, 2761, 2762, 7623, 2764, 2766, 2765, 7623, 2767, 2768, 2769, 7623, 2771, 2781, 2788, 2792, 2797, 2799, 2803, 2807, 2839, 2845, 2932, 2936, 2939, 2944, 2772, 2775, 2773, 2774, 7655, 2776, 7623, 2777, 2778, 2779, 2780, 7623, 2782, 2783, 2785, 2784, 7623, 2786, 2787, 7623, 2789, 2791, 2790, 7656, 7623, 2793, 2794, 2795, 2796, 7623, 2798, 7623, 2800, 2801, 2802, 7657, 2804, 2805, 2806, 7623, 2808, 2832, 2809, 2810, 2811, 2821, 2812, 2814, 2813, 7623, 2815, 2816, 2817, 2818, 7623, 2819, 2820, 7623, 2822, 2823, 2824, 2825, 2826, 2827, 2828, 2829, 2830, 2831, 7623, 2833, 2834, 7623, 2835, 2836, 2837, 2838, 7623, 2840, 2843, 2841, 2842, 7623, 2844, 7623, 2846, 2863, 2872, 2883, 2891, 2896, 2905, 2927, 2847, 2848, 2849, 2850, 7623, 2851, 2854, 2852, 2853, 7623, 2855, 2856, 2857, 2858, 2859, 2860, 2861, 2862, 7623, 2864, 2865, 2866, 2867, 2868, 2869, 2870, 2871, 7623, 2873, 2874, 2875, 2876, 2877, 2878, 2879, 2880, 2881, 2882, 7623, 2884, 2885, 7623, 2886, 2887, 2888, 2889, 2890, 7623, 2892, 2893, 2894, 2895, 7623, 2897, 2898, 2899, 2900, 2901, 2902, 2903, 2904, 7623, 2906, 2907, 2908, 2917, 2909, 2910, 2911, 2912, 2913, 2914, 2915, 2916, 7623, 2918, 2919, 2920, 2921, 2922, 2923, 2924, 2925, 2926, 7623, 2928, 7623, 2929, 2930, 2931, 7623, 2933, 2934, 2935, 7623, 2937, 2938, 7623, 2940, 2941, 2942, 2943, 7623, 2945, 7658, 2947, 2951, 2954, 2956, 2961, 3003, 3005, 3008, 3011, 2948, 2949, 2950, 7623, 2952, 2953, 7623, 2955, 7623, 2957, 2958, 2959, 7623, 2960, 7623, 2962, 2963, 7623, 2964, 2967, 2993, 2965, 2966, 7623, 7623, 2968, 2969, 2970, 2971, 2972, 2975, 2979, 2988, 2973, 2974, 7623, 2976, 2977, 2978, 7623, 2980, 2981, 2982, 2983, 2984, 2985, 2986, 2987, 7623, 2989, 2990, 2991, 2992, 7623, 2994, 2995, 2996, 2997, 2998, 2999, 3000, 3001, 3002, 7623, 3004, 7623, 3006, 3007, 7623, 3009, 3010, 7623, 3012, 3013, 3014, 3015, 7623, 3017, 3021, 3025, 3027, 3030, 3018, 3019, 3020, 7623, 3022, 3023, 3024, 7623, 3026, 7623, 3028, 3029, 7623, 3031, 3032, 7623, 3034, 3036, 3037, 3040, 3035, 7623, 7623, 3038, 3039, 7623, 3041, 3042, 7623, 3044, 3047, 3050, 3053, 3057, 3062, 3064, 3067, 3070, 3045, 3046, 7623, 3048, 3049, 7623, 3051, 3052, 7623, 3054, 3055, 3056, 7659, 3058, 3061, 3059, 3060, 7623, 7623, 3063, 7623, 3065, 3066, 7623, 3068, 3069, 7623, 3071, 3072, 7623, 3074, 3077, 3082, 3088, 3091, 3106, 3108, 3111, 3075, 3076, 7623, 3078, 3079, 3080, 3081, 7623, 3083, 3087, 3084, 3085, 3086, 7623, 7623, 3089, 3090, 7623, 3092, 3104, 3093, 3094, 3095, 3096, 3097, 3098, 3099, 3100, 3101, 3102, 3103, 7623, 3105, 7623, 3107, 7623, 3109, 3110, 7623, 3112, 3113, 7623, 3115, 3119, 3124, 3132, 3135, 3137, 3141, 3152, 3158, 3199, 3205, 3222, 3225, 3234, 3238, 3240, 3116, 3117, 3118, 7660, 3120, 3121, 3122, 3123, 7623, 7623, 3125, 3126, 3127, 3129, 3131, 7623, 7623, 3128, 7661, 3130, 7662, 7623, 3133, 3134, 7663, 7623, 3136, 7623, 3138, 3139, 3140, 7664, 3142, 3149, 3143, 3147, 3144, 3145, 3146, 7623, 3148, 7623, 3150, 3151, 7623, 3153, 7665, 3154, 3156, 3155, 7623, 3157, 7623, 3159, 3170, 7623, 3160, 3163, 3164, 3169, 3161, 3162, 7623, 7623, 3165, 3166, 3167, 3168, 7623, 7623, 7623, 3171, 3172, 3174, 3186, 3191, 3195, 7623, 3173, 7623, 3175, 3176, 7623, 3177, 3178, 3179, 3180, 3181, 3182, 3183, 3184, 3185, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 3187, 7623, 3188, 3189, 7623, 3190, 7623, 3192, 3194, 3193, 7623, 7623, 3196, 3197, 3198, 7623, 3200, 3203, 3201, 3202, 7623, 3204, 7623, 7623, 3206, 3207, 3211, 3212, 3214, 3216, 7623, 3208, 3209, 3210, 7623, 7623, 3213, 7623, 3215, 7623, 3217, 3218, 3219, 7623, 3220, 3221, 7623, 3223, 3224, 7666, 3226, 3228, 3229, 3227, 7623, 7623, 3230, 3231, 7623, 3232, 3233, 7623, 3235, 3236, 3237, 7667, 3239, 7668, 3241, 3247, 3242, 3243, 3244, 3245, 3246, 7623, 3248, 3249, 7623, 3251, 3254, 3287, 3294, 3299, 3303, 3328, 3330, 3388, 3393, 3442, 3450, 3523, 3528, 3535, 3551, 3252, 3253, 7623, 3255, 3278, 3256, 3257, 3261, 3268, 3273, 3258, 3259, 3260, 7623, 3262, 3263, 3264, 3265, 3266, 3267, 7623, 3269, 3270, 3271, 3272, 7623, 3274, 3275, 7623, 3276, 3277, 7623, 3279, 3282, 3280, 3281, 7623, 3283, 3284, 7623, 3285, 3286, 7623, 3288, 3289, 7623, 3290, 3291, 3292, 3293, 7623, 3295, 3298, 3296, 3297, 7623, 7623, 3300, 3301, 3302, 7623, 3304, 3309, 3314, 3317, 3321, 3305, 3306, 3307, 7623, 3308, 7623, 3310, 3311, 3312, 3313, 7623, 3315, 3316, 7623, 3318, 3319, 3320, 7623, 3322, 3323, 3324, 7623, 7623, 3325, 3326, 3327, 7623, 3329, 7623, 3331, 3332, 3340, 3353, 3361, 3375, 3380, 3383, 3333, 3335, 3338, 3334, 7623, 3336, 3337, 7623, 3339, 7623, 3341, 3344, 3348, 3342, 3343, 7623, 3345, 3346, 3347, 7623, 3349, 3350, 3351, 3352, 7623, 3354, 3358, 3355, 3356, 3357, 7623, 3359, 3360, 7623, 3362, 3363, 3364, 3365, 3366, 3367, 3368, 3369, 3373, 3370, 3371, 3372, 7623, 3374, 7623, 3376, 3377, 3378, 3379, 7623, 3381, 3382, 7623, 3384, 3385, 3386, 3387, 7623, 3389, 3390, 3391, 3392, 7623, 3394, 3433, 3439, 3395, 3431, 3396, 3397, 3404, 3410, 3398, 3399, 3400, 3401, 3402, 3403, 7623, 3405, 3406, 3407, 3408, 3409, 7623, 3411, 3412, 3413, 3414, 3415, 3416, 3417, 7623, 3418, 3422, 3426, 3419, 3420, 3421, 7623, 3423, 3424, 3425, 7623, 3427, 3428, 3429, 3430, 7623, 3432, 7623, 3434, 3437, 3435, 3436, 7623, 7623, 3438, 7623, 3440, 3441, 7623, 3443, 3448, 7623, 3444, 3445, 3446, 3447, 7623, 3449, 7623, 3451, 3453, 3457, 3461, 3452, 7623, 7623, 3454, 3455, 3456, 7623, 3458, 3459, 3460, 7623, 3462, 3467, 3472, 3477, 3484, 3487, 3492, 3497, 3502, 3506, 3511, 3516, 3463, 3464, 3465, 3466, 7623, 7623, 7623, 7623, 7623, 3468, 3469, 3470, 3471, 7623, 7623, 7623, 7623, 3473, 3474, 3475, 3476, 7623, 7623, 7623, 7623, 7623, 3478, 3479, 3480, 3481, 3482, 3483, 7623, 7623, 7623, 7623, 7623, 7623, 3485, 3486, 7623, 3488, 3489, 3490, 3491, 7623, 7623, 7623, 7623, 7623, 3493, 3494, 3495, 3496, 7623, 7623, 7623, 7623, 3498, 3499, 3500, 3501, 7623, 3503, 3504, 3505, 7623, 3507, 3508, 3509, 3510, 7623, 3512, 3513, 3514, 3515, 7623, 7623, 7623, 7623, 7623, 3517, 3518, 3519, 3520, 3521, 3522, 7623, 7623, 7623, 7623, 7623, 7623, 3524, 3525, 3526, 3527, 7623, 3529, 3532, 3530, 3531, 7623, 3533, 3534, 7669, 3536, 3538, 3541, 3544, 3537, 7623, 3539, 3540, 7623, 3542, 7623, 3543, 7623, 3545, 7623, 3546, 3547, 7623, 3548, 3549, 3550, 7623, 3552, 3556, 3553, 7623, 3554, 3555, 7623, 3557, 7623, 3558, 3559, 7623, 7623, 3560, 7623, 3562, 3590, 3608, 3611, 3625, 3627, 3638, 3687, 3694, 3731, 3738, 3746, 3750, 3833, 3843, 3563, 3567, 3585, 3564, 3565, 3566, 7623, 7623, 3568, 3571, 3576, 3581, 3584, 3569, 3570, 7623, 3572, 3573, 3574, 3575, 7623, 3577, 3579, 3578, 7623, 3580, 7623, 3582, 3583, 7623, 7623, 3586, 3588, 3587, 7623, 3589, 7623, 3591, 3597, 3600, 3603, 3592, 3594, 3593, 7623, 3595, 3596, 7623, 3598, 3599, 7670, 3601, 3602, 7623, 3604, 3605, 7623, 3606, 3607, 7623, 3609, 3610, 7623, 3612, 3614, 3619, 3613, 7671, 3615, 3616, 3617, 3618, 7623, 7672, 7623, 3621, 3622, 3623, 3624, 7623, 3626, 7623, 3628, 3630, 3637, 3629, 7623, 3631, 3632, 7623, 3633, 3634, 3635, 3636, 7623, 7623, 3639, 7623, 3640, 3641, 3674, 3675, 3680, 3683, 7623, 7623, 3642, 3644, 3643, 7623, 3645, 3646, 3660, 3647, 3648, 3649, 3650, 3651, 3655, 3652, 3653, 3654, 7623, 3656, 3657, 3658, 3659, 7623, 3661, 3662, 3663, 3666, 3670, 7623, 7623, 3664, 3665, 7623, 3667, 3668, 3669, 7623, 3671, 3672, 3673, 7623, 7623, 3676, 3677, 3678, 3679, 7623, 3681, 3682, 7623, 3684, 3685, 3686, 7623, 3688, 3689, 3690, 7623, 3691, 3692, 3693, 7623, 3695, 3700, 3716, 3724, 3696, 3697, 7623, 3698, 7623, 3699, 7623, 3701, 3704, 3702, 7623, 3703, 7623, 7623, 3705, 3707, 3706, 7623, 3708, 3709, 3713, 3710, 3711, 3712, 7623, 3714, 3715, 7623, 3717, 3721, 7623, 3718, 3719, 3720, 7623, 3722, 3723, 7623, 3725, 3726, 7673, 7623, 3727, 3728, 7623, 7623, 3730, 7623, 3732, 3735, 3733, 3734, 7623, 3736, 3737, 7623, 3739, 3741, 3740, 7623, 3742, 3744, 7623, 3743, 7623, 7623, 3745, 7623, 3747, 3748, 3749, 7623, 3751, 3757, 3762, 3767, 3784, 3827, 3830, 3752, 3753, 3754, 3755, 3756, 7623, 7623, 3758, 3760, 3759, 7623, 3761, 7623, 3763, 3764, 3765, 7623, 3766, 7623, 7623, 3768, 3773, 3778, 3781, 3783, 3769, 3770, 3771, 3772, 7623, 3774, 3776, 3775, 7623, 3777, 7623, 3779, 3780, 7623, 3782, 7623, 7623, 3785, 3789, 3809, 3811, 3786, 3787, 7623, 3788, 7623, 3790, 3791, 3801, 3804, 3792, 3793, 3797, 3794, 3795, 3796, 7623, 3798, 3799, 3800, 7623, 3802, 3803, 7623, 3805, 3806, 3807, 3808, 7623, 3810, 7674, 3812, 3813, 3814, 3815, 3816, 3817, 3818, 3822, 3819, 3820, 3821, 7623, 3823, 3824, 3825, 3826, 7623, 3828, 3829, 7623, 3831, 3832, 7623, 3834, 3840, 3835, 3836, 3837, 3838, 3839, 7623, 3841, 3842, 7623, 3844, 3845, 3846, 3847, 7623, 3849, 3852, 3855, 3869, 3878, 3884, 3897, 3906, 3912, 3917, 3950, 3953, 3961, 4034, 4048, 4058, 4065, 4072, 4078, 3850, 3851, 7623, 3853, 3854, 7623, 3856, 3860, 3864, 3866, 3857, 3858, 3859, 7623, 3861, 3862, 3863, 7623, 3865, 7623, 3867, 7623, 3868, 7623, 3870, 3875, 3871, 3872, 3873, 3874, 7623, 3876, 3877, 7623, 3879, 3883, 3880, 3881, 3882, 7623, 7623, 7623, 3885, 3892, 3886, 3890, 3887, 3888, 3889, 7623, 3891, 7623, 3893, 3894, 3895, 3896, 7623, 7675, 3898, 3901, 3899, 3900, 7623, 3902, 3903, 3904, 3905, 7623, 3907, 3911, 3908, 3909, 3910, 7623, 7623, 3913, 3914, 3915, 3916, 7623, 7623, 3918, 3928, 3929, 3934, 3937, 3919, 7623, 3920, 3927, 3921, 3922, 7623, 3923, 3924, 3925, 3926, 7623, 7623, 7623, 3930, 3931, 3932, 3933, 7623, 3935, 3936, 7623, 7623, 3938, 3947, 3939, 7676, 7623, 3941, 3942, 3943, 3944, 3945, 3946, 7623, 3948, 3949, 7623, 3951, 3952, 7623, 3954, 3955, 3958, 3956, 3957, 7623, 3959, 3960, 7623, 3962, 3966, 3968, 3989, 4001, 3963, 3964, 3965, 7623, 3967, 7623, 7623, 3969, 3974, 3979, 3983, 3970, 7623, 3971, 3972, 3973, 7623, 3975, 3976, 3977, 3978, 7623, 3980, 3981, 3982, 7623, 3984, 3985, 3986, 3987, 3988, 7623, 3990, 3991, 3992, 3993, 3994, 3995, 3996, 3997, 3998, 3999, 4000, 7623, 4002, 4003, 4008, 4018, 4004, 4005, 4006, 4007, 7623, 4009, 4010, 4011, 4012, 4013, 4014, 4015, 4016, 4017, 7623, 4019, 4020, 4021, 4022, 4023, 4024, 4025, 4029, 4026, 4027, 4028, 7623, 4030, 4031, 4032, 4033, 7623, 4035, 4041, 4036, 4037, 4038, 4039, 4040, 7623, 4042, 4045, 4043, 4044, 7623, 4046, 4047, 7623, 4049, 4052, 4054, 4050, 4051, 7623, 7623, 4053, 7623, 4055, 4056, 4057, 7623, 4059, 4062, 4060, 4061, 7623, 4063, 7623, 4064, 7623, 4066, 4069, 4067, 4068, 7623, 4070, 4071, 7623, 4073, 4074, 4075, 4076, 4077, 7623, 4079, 4081, 4080, 7623, 4082, 4083, 4084, 4085, 4086, 7623, 4088, 4094, 4102, 4114, 4117, 4118, 4123, 4131, 4143, 4159, 4163, 4169, 4183, 4223, 4230, 4238, 4240, 4244, 4089, 4092, 4090, 4091, 7623, 4093, 7623, 4095, 4098, 4096, 4097, 7677, 4099, 4100, 4101, 7623, 4103, 4107, 4109, 4113, 4104, 4105, 4106, 7623, 4108, 7623, 7678, 4110, 4111, 4112, 7623, 7623, 4115, 4116, 7623, 7623, 4119, 4122, 4120, 4121, 7623, 7623, 7623, 4124, 4127, 4125, 4126, 7679, 7623, 4128, 4129, 4130, 7623, 7623, 4132, 4138, 4139, 4133, 4134, 4135, 4136, 4137, 7623, 7623, 7623, 4140, 4141, 4142, 7623, 4144, 4147, 4154, 4145, 4146, 7623, 4148, 4149, 7623, 4150, 4153, 4151, 4152, 7623, 7623, 4155, 4156, 7623, 4157, 4158, 7623, 7623, 4160, 4161, 7623, 4162, 7623, 4164, 4167, 4165, 4166, 7623, 4168, 7623, 4170, 4174, 4177, 4171, 7623, 4172, 4173, 7623, 4175, 4176, 7623, 4178, 7623, 4179, 4182, 4180, 4181, 7623, 7623, 4184, 4192, 4206, 4217, 4185, 4188, 4186, 4187, 7623, 4189, 4190, 4191, 7623, 4193, 4195, 4194, 7623, 4196, 4197, 4198, 4199, 4202, 4200, 4201, 7623, 4203, 4204, 4205, 7623, 4207, 4210, 4213, 4208, 4209, 7623, 4211, 4212, 7623, 4214, 7623, 4215, 4216, 7623, 4218, 4219, 4220, 4221, 4222, 7623, 4224, 4227, 4225, 4226, 7623, 4228, 4229, 7623, 4231, 4233, 4236, 4232, 7623, 4234, 4235, 7623, 4237, 7623, 4239, 7680, 7623, 4241, 4242, 7681, 4243, 7623, 4245, 4247, 4250, 4246, 7623, 4248, 4249, 7623, 4251, 4259, 4252, 4253, 4254, 4255, 4256, 4257, 4258, 7623, 4260, 4261, 4262, 4263, 4264, 4265, 4266, 4267, 7623, 4269, 4281, 4283, 4288, 4300, 4304, 4308, 4317, 4320, 4329, 4336, 4362, 4270, 4271, 4272, 4273, 4274, 4275, 4276, 4277, 4278, 4279, 4280, 7623, 4282, 7623, 4284, 4285, 4286, 4287, 7623, 4289, 4293, 4299, 4290, 4291, 4292, 7623, 4294, 4296, 4295, 7623, 4297, 4298, 7623, 7623, 4301, 4302, 4303, 7623, 4305, 4306, 4307, 7623, 4309, 4311, 4314, 4310, 7623, 4312, 4313, 7623, 4315, 4316, 7623, 4318, 4319, 7623, 4321, 4323, 4322, 7623, 4324, 4327, 4325, 4326, 7623, 7623, 4328, 7623, 4330, 4331, 4332, 4333, 4334, 4335, 7623, 4337, 4359, 4338, 4357, 4339, 4344, 4347, 4350, 4352, 4355, 7682, 4340, 7683, 4341, 4342, 4343, 7623, 7623, 7623, 7623, 4345, 4346, 7623, 7623, 7684, 4348, 4349, 7623, 7623, 4351, 7623, 4353, 4354, 7623, 7623, 4356, 7623, 4358, 7623, 4360, 4361, 7623, 4363, 4364, 7623, 4366, 4368, 4378, 4383, 4388, 4391, 4411, 4413, 4415, 4419, 4422, 4426, 4440, 4443, 4447, 7685, 4500, 7623, 4367, 7623, 4369, 4373, 4377, 4370, 4371, 4372, 7623, 4374, 4375, 7623, 4376, 7623, 7623, 4379, 4380, 4381, 4382, 7623, 4384, 4387, 4385, 4386, 7623, 7623, 4389, 4390, 7623, 7623, 4392, 4393, 4400, 7623, 7623, 4394, 4395, 7623, 4396, 4397, 4398, 4399, 7623, 7623, 4401, 4403, 4408, 4402, 7623, 4404, 4405, 7623, 4406, 7623, 4407, 7623, 7623, 4409, 4410, 7623, 4412, 7623, 7623, 4414, 7623, 4416, 4417, 4418, 7623, 4420, 4421, 7623, 7623, 4423, 4424, 4425, 7623, 7623, 7623, 4427, 4428, 4434, 4437, 7623, 4429, 7623, 4430, 4431, 4432, 4433, 7623, 7623, 4435, 7623, 4436, 7623, 4438, 4439, 7623, 4441, 4442, 7623, 4444, 4445, 4446, 7623, 4448, 4450, 4449, 7623, 4451, 7623, 4452, 4453, 7623, 7623, 7623, 4455, 4456, 7623, 4457, 7623, 4459, 4460, 7623, 4462, 4463, 4464, 7623, 4466, 4467, 4468, 4469, 7623, 4471, 4479, 4482, 4493, 4497, 4472, 4477, 4473, 4474, 4475, 4476, 7623, 4478, 7623, 4480, 4481, 7623, 4483, 4484, 4488, 4485, 4486, 4487, 7623, 4489, 4490, 4491, 4492, 7623, 4494, 4495, 4496, 7623, 4498, 4499, 7623, 4501, 4508, 4502, 4503, 4504, 4505, 4506, 4507, 7623, 4509, 7623, 4511, 4514, 4534, 4537, 4541, 4557, 4559, 4571, 4606, 4617, 4512, 4513, 7623, 4515, 4519, 4521, 4525, 4516, 4517, 4518, 7623, 4520, 7623, 4522, 4523, 4524, 7623, 4526, 4529, 4527, 4528, 7623, 7623, 4530, 4533, 4531, 4532, 7623, 7623, 4535, 4536, 7623, 4538, 4539, 4540, 7623, 4542, 4549, 4553, 4543, 4544, 4545, 7623, 4546, 4547, 4548, 7623, 4550, 4551, 4552, 7623, 4554, 4555, 4556, 7623, 4558, 7623, 4560, 4561, 4566, 4562, 4563, 4564, 4565, 7623, 4567, 4568, 4569, 4570, 7623, 4572, 4575, 4579, 4600, 4602, 4573, 4574, 7623, 4576, 4577, 4578, 7623, 4580, 4581, 4590, 4582, 4583, 4584, 4585, 4586, 4587, 4588, 4589, 7623, 4591, 4592, 4593, 4594, 4595, 4596, 4597, 4598, 4599, 7623, 4601, 7623, 4603, 4604, 4605, 7623, 4607, 4609, 4613, 4608, 7623, 4610, 4611, 4612, 7623, 4614, 4615, 4616, 7623, 4618, 4622, 4619, 4620, 4621, 7623, 4623, 4624, 4625, 7623, 4627, 4631, 4635, 4640, 4643, 4647, 4661, 4665, 4686, 4723, 4733, 4737, 4741, 4753, 4758, 4628, 4629, 4630, 7686, 7623, 4632, 4634, 4633, 7687, 7623, 4636, 4638, 4637, 7623, 4639, 7688, 4641, 4642, 7623, 7623, 4644, 4645, 4646, 7689, 7623, 4648, 4654, 4658, 4649, 4652, 4650, 4651, 7623, 4653, 7623, 4655, 4656, 4657, 7623, 4659, 4660, 7623, 4662, 4663, 4664, 7623, 4666, 4681, 4683, 4667, 4669, 4679, 4668, 7623, 4670, 4671, 4675, 7623, 4672, 4673, 4674, 7623, 4676, 4677, 4678, 7623, 4680, 7623, 4682, 7623, 4684, 4685, 7623, 7623, 4687, 4691, 4697, 4701, 4688, 4689, 4690, 7623, 4692, 4693, 7623, 4694, 4695, 4696, 7623, 4698, 4699, 4700, 7623, 7623, 4702, 4705, 4714, 4719, 4703, 4704, 7623, 4706, 4710, 4707, 4708, 4709, 7623, 4711, 4712, 4713, 7623, 4715, 4716, 4717, 4718, 7623, 4720, 4721, 4722, 7623, 4724, 4726, 4729, 4731, 4725, 7623, 4727, 4728, 7623, 4730, 7623, 4732, 7623, 4734, 4735, 4736, 7623, 4738, 4739, 4740, 7690, 4742, 4744, 4743, 7623, 4745, 7623, 4746, 4747, 4750, 4752, 7623, 4748, 4749, 7623, 7623, 4751, 7623, 7623, 7623, 4754, 4755, 4756, 4757, 7623, 4759, 4762, 4760, 4761, 7623, 7691, 4764, 4769, 4771, 4775, 4778, 4785, 4765, 4768, 4766, 4767, 7623, 7623, 4770, 7623, 4772, 4773, 4774, 7623, 4776, 4777, 7623, 4779, 4781, 4780, 7623, 4782, 4783, 4784, 7623, 4786, 4787, 4788, 7623, 4790, 4795, 4801, 4803, 4808, 4811, 4814, 4817, 4791, 4792, 4793, 7623, 4794, 7623, 4796, 4800, 4797, 4798, 4799, 7623, 7623, 4802, 7623, 4804, 4805, 4806, 4807, 7623, 4809, 4810, 7623, 4812, 4813, 7623, 4815, 4816, 7623, 4818, 4819, 7623, 4821, 4831, 4835, 4837, 4840, 4891, 4909, 4923, 4942, 5060, 5070, 5072, 5081, 5084, 5101, 5114, 5128, 5215, 5220, 5238, 7693, 5290, 5301, 4822, 4825, 4827, 4823, 4824, 7623, 4826, 7623, 4828, 4829, 4830, 7623, 4832, 4833, 4834, 7623, 7623, 4836, 7623, 4838, 4839, 7623, 4841, 4845, 4851, 4855, 4859, 4864, 4865, 4867, 4885, 4842, 4843, 4844, 7623, 4846, 4847, 4848, 4849, 4850, 7623, 4852, 4853, 4854, 7623, 4856, 4857, 4858, 7623, 4860, 7623, 4861, 4862, 7623, 4863, 7623, 7623, 4866, 7692, 4868, 7623, 4869, 4872, 4874, 4876, 4878, 4880, 4883, 7623, 4870, 4871, 7623, 4873, 7623, 4875, 7623, 4877, 7623, 4879, 7623, 4881, 4882, 7623, 4884, 7623, 7623, 4886, 4889, 4887, 4888, 7623, 7623, 4890, 7623, 4892, 4895, 4898, 4893, 4894, 7623, 4896, 4897, 7623, 4899, 4903, 4900, 4901, 4902, 7623, 7623, 4904, 4905, 7623, 4906, 4907, 4908, 7623, 7623, 4910, 4914, 4920, 4922, 4911, 4912, 4913, 7623, 4915, 4918, 4916, 4917, 7623, 4919, 7623, 4921, 7623, 7623, 4924, 4926, 4930, 4940, 4925, 7623, 4927, 4928, 7623, 4929, 7623, 4931, 4935, 4932, 4933, 4934, 7623, 4936, 4937, 4938, 4939, 7623, 4941, 7623, 7623, 4943, 5016, 5017, 5024, 4944, 4945, 4954, 4967, 4977, 5006, 4946, 4947, 4948, 4949, 7623, 4950, 4951, 4952, 4953, 7623, 4955, 4956, 4957, 4958, 4959, 4960, 4961, 4965, 4962, 4963, 4964, 7623, 4966, 7623, 4968, 4969, 4970, 4971, 4972, 4973, 4974, 4975, 4976, 7623, 4978, 4979, 4980, 4981, 4982, 4988, 4996, 4983, 4984, 4985, 4986, 7623, 4987, 7623, 4989, 4990, 4991, 4992, 4993, 4994, 4995, 7623, 4997, 4998, 4999, 5000, 5001, 5002, 5003, 5004, 5005, 7623, 5007, 5008, 5009, 5010, 5011, 5012, 5013, 5014, 5015, 7623, 7623, 7623, 5018, 5019, 7623, 5020, 5021, 5022, 5023, 7623, 7623, 5025, 5027, 5032, 5035, 5026, 7623, 5028, 5029, 7623, 5030, 7623, 5031, 7623, 7623, 5033, 5034, 7623, 5036, 5042, 5045, 5054, 5057, 5037, 5038, 5039, 5040, 5041, 7623, 5043, 5044, 7623, 5046, 5047, 5050, 5048, 5049, 7623, 5051, 5052, 5053, 7623, 5055, 5056, 7623, 5058, 5059, 7623, 5061, 5065, 5069, 5062, 5063, 5064, 7623, 5066, 5067, 5068, 7623, 7623, 7623, 5071, 7623, 5073, 5078, 5074, 5075, 5076, 7623, 7623, 5077, 7623, 5079, 5080, 7623, 5082, 5083, 7623, 7623, 5085, 5088, 5094, 5098, 5086, 5087, 7623, 5089, 5090, 5091, 5092, 5093, 7623, 5095, 5096, 5097, 7623, 5099, 5100, 7623, 5102, 5106, 5103, 5104, 5105, 7623, 5107, 5108, 5109, 7623, 5110, 5111, 5112, 5113, 7623, 5115, 5116, 5122, 5125, 7623, 5117, 7623, 5118, 5119, 5120, 5121, 7623, 7623, 5123, 7623, 5124, 7623, 5126, 5127, 7623, 5129, 5134, 5137, 5174, 5190, 5197, 5202, 5209, 5130, 5132, 5131, 7623, 5133, 7623, 5135, 5136, 7623, 5138, 5139, 5158, 5164, 5140, 5141, 5142, 5143, 5148, 5144, 5145, 5146, 5147, 7623, 5149, 5150, 5151, 5152, 5153, 5154, 5155, 5156, 5157, 7623, 5159, 5160, 5161, 5162, 5163, 7623, 5165, 5166, 5167, 5168, 5169, 5170, 5171, 5172, 5173, 7623, 5175, 5176, 5177, 5178, 5179, 5180, 5181, 5185, 5182, 5183, 5184, 7623, 5186, 5187, 5188, 5189, 7623, 5191, 5193, 5194, 5192, 7623, 7623, 5195, 5196, 7623, 5198, 5199, 5200, 5201, 7623, 5203, 5206, 5204, 5205, 7623, 5207, 5208, 7623, 7623, 5210, 5214, 5211, 5212, 5213, 7623, 7623, 5216, 5217, 7623, 5218, 5219, 7623, 5221, 5224, 5230, 5234, 5235, 5222, 5223, 7623, 5225, 5226, 5227, 5228, 5229, 7623, 5231, 5232, 7623, 5233, 7623, 7623, 5236, 5237, 7623, 5239, 5243, 5245, 5246, 5250, 5255, 5240, 5241, 5242, 7623, 5244, 7623, 7623, 5247, 7623, 5248, 5249, 7623, 7623, 5251, 5252, 7623, 5253, 7623, 5254, 7623, 5256, 5257, 5258, 7623, 7623, 5260, 5261, 7623, 5262, 7623, 5264, 5265, 7623, 5267, 5268, 5269, 7623, 5271, 5272, 5273, 7623, 5275, 5276, 5277, 7623, 5279, 5280, 5281, 5282, 7623, 5284, 5287, 5285, 5286, 7623, 7623, 5288, 5289, 7623, 7623, 5291, 5292, 5297, 5293, 5294, 5295, 5296, 7623, 5298, 5299, 5300, 7623, 5302, 5309, 5303, 5304, 5305, 5306, 5307, 5308, 7623, 5310, 7623, 5312, 5316, 5342, 5348, 5352, 5364, 5366, 5368, 5386, 5391, 5396, 5403, 5404, 5411, 5313, 5314, 5315, 7623, 5317, 5318, 5324, 5338, 7694, 5319, 5320, 7623, 7623, 5321, 5322, 5323, 7623, 7623, 5325, 5326, 5327, 7623, 5328, 5332, 5336, 5329, 5330, 5331, 7623, 5333, 5334, 5335, 7623, 5337, 7623, 5339, 5340, 5341, 7623, 5343, 5347, 5344, 5345, 5346, 7623, 7623, 5349, 5350, 5351, 7623, 5353, 5354, 5355, 5356, 5357, 5358, 5359, 5360, 5361, 5362, 5363, 7623, 5365, 7623, 5367, 7623, 5369, 5371, 5380, 5370, 7695, 7623, 5372, 5375, 5378, 5373, 5374, 7623, 5376, 5377, 7623, 5379, 7696, 5381, 5382, 7623, 5383, 5384, 7623, 7623, 5385, 7623, 5387, 5389, 5388, 7623, 5390, 7623, 5392, 5393, 5394, 5395, 7623, 5397, 5401, 5398, 5399, 5400, 7623, 5402, 7623, 7623, 5405, 5407, 5406, 7623, 5408, 5409, 5410, 7623, 7623, 5412, 5418, 5413, 5414, 5415, 5416, 5417, 7623, 5419, 5420, 7623, 5422, 5426, 5448, 5458, 5467, 5493, 5499, 5518, 5522, 5551, 5553, 5569, 5579, 5583, 5586, 5630, 5633, 5652, 5676, 5698, 5760, 5788, 5795, 5838, 5423, 5424, 7623, 7623, 5425, 7623, 5427, 5445, 5446, 5428, 5429, 5430, 5435, 5431, 5432, 5433, 5434, 7623, 5436, 5437, 5438, 5439, 5440, 5441, 5442, 5443, 5444, 7623, 7623, 7623, 5447, 7623, 5449, 5450, 5451, 5452, 5453, 5454, 5455, 5456, 5457, 7623, 5459, 5463, 5460, 5461, 5462, 7623, 5464, 5465, 5466, 7623, 5468, 5471, 5475, 5477, 5487, 5469, 5470, 7623, 5472, 5473, 5474, 7623, 5476, 7623, 7623, 5478, 5479, 5481, 5483, 7623, 5480, 7623, 5482, 7623, 5484, 5485, 5486, 7623, 5488, 5489, 7623, 5490, 5491, 7623, 5492, 7623, 5494, 5495, 7697, 5496, 5497, 7623, 5498, 7623, 5500, 5505, 5509, 5515, 5517, 5501, 5502, 7623, 5503, 5504, 7623, 5506, 5507, 5508, 7623, 5510, 5511, 7623, 5512, 5513, 5514, 7623, 5516, 7623, 7623, 5519, 5520, 5521, 7623, 7623, 5523, 5526, 5533, 5536, 5540, 5546, 5524, 5525, 7623, 5527, 5528, 5530, 5529, 7623, 7623, 5531, 5532, 7623, 5534, 5535, 7623, 5537, 5538, 5539, 7623, 5541, 5544, 5542, 5543, 7623, 5545, 7623, 5547, 5548, 5549, 7623, 5550, 7623, 5552, 7623, 5554, 5555, 5564, 5567, 7623, 7623, 5556, 5563, 7623, 5557, 5558, 7623, 5559, 5560, 5561, 5562, 7623, 7623, 5565, 5566, 7623, 7623, 5568, 7623, 5570, 5573, 5576, 5571, 5572, 7623, 5574, 5575, 7623, 5577, 5578, 7623, 7623, 5580, 5582, 7623, 5581, 7623, 7623, 5584, 5585, 7623, 5587, 5590, 5591, 5594, 5596, 5623, 5626, 5588, 5589, 7623, 7623, 5592, 5593, 7623, 5595, 7623, 7623, 5597, 5614, 5621, 5598, 5599, 5604, 5600, 5601, 5602, 5603, 7623, 5605, 5606, 5607, 5608, 5609, 5610, 5611, 5612, 5613, 7623, 7623, 5615, 5616, 7623, 5617, 5618, 5619, 5620, 7623, 7623, 5622, 7623, 5624, 5625, 7623, 7623, 5627, 5628, 7623, 5629, 7623, 5631, 5632, 7623, 5634, 7698, 5635, 7623, 7623, 5637, 7623, 5638, 5639, 5642, 7623, 5640, 5641, 7623, 5643, 5644, 5645, 7623, 7623, 7623, 5647, 7623, 5648, 5649, 5650, 5651, 7623, 7623, 7623, 5653, 5663, 5668, 5654, 7623, 5655, 5660, 5662, 5656, 5657, 5658, 5659, 7623, 5661, 7623, 7623, 5664, 5665, 5666, 5667, 7623, 7623, 5669, 5672, 5670, 5671, 7623, 7623, 5673, 7623, 5674, 5675, 7623, 5677, 5680, 5685, 5694, 5678, 5679, 7623, 5681, 5682, 7623, 5683, 5684, 7623, 7623, 5686, 5687, 5688, 5689, 5690, 5691, 5692, 5693, 7623, 5695, 5696, 7623, 5697, 7623, 5699, 5705, 5720, 5724, 5727, 5730, 5737, 7623, 5700, 5703, 5704, 5701, 5702, 7623, 7623, 7623, 5706, 5707, 5708, 5709, 5712, 5710, 5711, 7623, 5713, 5714, 5715, 5716, 5717, 5718, 5719, 7623, 5721, 7623, 5722, 7623, 5723, 7623, 5725, 5726, 7623, 5728, 5729, 7623, 5731, 5732, 5733, 5735, 5734, 7623, 5736, 7623, 5738, 5747, 5751, 7623, 5739, 5740, 5741, 7623, 7623, 5742, 5743, 7623, 5744, 5745, 7623, 5746, 7623, 5748, 7623, 5749, 5750, 7623, 7623, 5752, 5753, 5754, 7623, 7623, 5755, 5756, 7623, 5757, 5758, 7623, 5759, 7623, 5761, 5763, 5766, 5768, 5762, 7623, 5764, 5765, 7699, 5767, 7623, 5769, 5770, 5771, 5772, 5773, 5774, 5775, 5781, 5776, 5777, 5778, 7623, 5779, 5780, 7623, 5782, 5783, 5784, 5785, 7623, 5786, 5787, 7623, 7623, 5789, 7623, 5790, 5793, 5791, 5792, 7623, 5794, 7623, 5796, 5800, 5804, 5806, 5810, 5813, 5818, 5827, 5835, 5797, 5798, 5799, 7623, 5801, 5802, 5803, 7623, 5805, 7623, 5807, 5808, 5809, 7623, 5811, 5812, 7623, 7623, 5814, 5815, 5816, 5817, 7623, 5819, 5822, 5823, 5820, 5821, 7623, 7623, 7623, 5824, 5825, 5826, 7623, 5828, 5831, 5829, 5830, 7623, 5832, 5833, 5834, 7623, 5836, 5837, 7623, 5839, 5842, 5849, 5840, 5841, 7623, 5843, 5844, 5846, 5845, 7623, 7623, 5847, 5848, 7623, 5850, 5851, 5852, 7623, 5854, 5855, 5861, 5865, 5881, 5885, 5890, 5897, 5902, 5905, 5920, 5936, 5939, 5948, 5969, 5977, 5986, 5988, 7623, 5856, 5859, 5857, 5858, 7700, 5860, 7623, 5862, 5864, 5863, 7623, 7701, 7623, 5866, 5869, 5873, 5875, 5877, 5867, 5868, 7623, 5870, 5871, 5872, 7623, 5874, 7623, 5876, 7623, 5878, 5879, 5880, 7623, 5882, 5883, 5884, 7623, 5886, 5889, 5887, 5888, 7623, 7623, 5891, 5893, 5896, 5892, 7623, 5894, 5895, 7702, 7623, 5898, 5901, 5899, 5900, 7623, 7623, 5903, 5904, 7623, 5906, 5909, 5916, 5919, 5907, 5908, 7623, 5910, 5912, 5911, 7623, 5913, 5914, 5915, 7623, 5917, 5918, 7623, 7623, 5921, 5924, 5927, 5922, 5923, 7623, 5925, 5926, 7623, 5928, 5932, 5933, 5929, 5930, 5931, 7623, 7623, 5934, 5935, 7623, 5937, 5938, 7623, 5940, 5942, 5945, 5941, 7623, 5943, 5944, 7623, 5946, 5947, 7623, 7623, 5949, 5952, 5957, 5961, 5963, 5968, 5950, 5951, 7623, 7623, 5953, 7703, 7704, 5954, 7623, 5955, 5956, 7623, 5958, 5959, 5960, 7623, 5962, 7623, 5964, 5965, 5966, 5967, 7623, 7623, 5970, 5972, 5975, 5971, 7623, 5973, 5974, 7705, 5976, 7623, 5978, 5979, 5981, 5980, 7706, 5982, 5983, 7623, 5984, 5985, 7623, 5987, 7707, 5989, 5990, 5991, 7623, 5993, 6004, 6006, 6022, 6024, 6034, 6043, 6072, 6073, 6084, 6160, 6164, 5994, 7623, 7708, 5999, 6003, 7623, 5996, 5997, 5998, 7623, 6000, 6002, 6001, 7623, 7623, 7623, 6005, 7623, 6007, 6008, 6011, 6014, 6017, 6018, 6009, 6010, 7623, 6012, 6013, 7623, 6015, 6016, 7623, 7623, 6019, 6020, 6021, 7623, 6023, 7623, 6025, 6027, 6031, 7623, 6026, 7623, 6028, 6029, 6030, 7623, 6032, 6033, 7623, 7623, 6035, 6042, 6036, 6037, 6038, 6039, 6040, 6041, 7623, 7623, 6044, 6051, 6045, 6046, 6049, 6047, 7623, 6048, 7623, 6050, 7623, 6052, 7623, 6053, 6057, 6058, 6061, 6064, 6065, 6066, 6069, 6054, 6055, 6056, 7623, 7623, 6059, 6060, 7623, 6062, 6063, 7623, 7623, 7623, 7709, 6067, 6068, 7623, 6070, 6071, 7623, 7623, 6074, 6080, 6082, 6075, 6076, 6077, 6078, 6079, 7623, 6081, 7623, 6083, 7710, 7623, 6085, 6086, 6088, 6091, 6124, 6128, 6135, 6153, 6156, 7623, 6087, 7623, 6089, 6090, 7623, 7623, 6092, 7623, 6093, 6099, 6106, 6108, 6121, 6094, 6095, 6096, 6097, 6098, 7623, 6100, 6101, 6102, 6103, 6104, 6105, 7623, 6107, 7623, 6109, 6115, 6118, 6110, 6111, 6112, 6113, 6114, 7623, 6116, 6117, 7623, 6119, 6120, 7623, 6122, 6123, 7623, 6125, 6126, 7623, 6127, 7623, 6129, 6130, 6132, 7623, 6131, 7623, 6133, 6134, 7623, 6136, 6137, 6150, 7623, 6138, 6142, 6146, 6139, 6140, 6141, 7623, 6143, 6144, 6145, 7623, 6147, 6148, 6149, 7623, 7623, 6151, 6152, 7623, 6154, 6155, 7623, 6157, 6158, 6159, 7623, 6161, 6163, 6162, 7623, 7623, 6165, 6166, 6167, 6168, 7623, 6170, 6172, 6175, 6178, 6183, 6186, 6171, 7623, 6173, 6174, 7623, 6176, 6177, 7623, 6179, 6180, 6181, 6182, 7623, 6184, 6185, 7623, 6187, 6199, 6204, 6188, 6189, 6196, 6190, 6191, 6192, 6193, 6194, 6195, 7623, 6197, 6198, 7623, 6200, 6201, 7623, 6202, 6203, 7623, 7711, 6206, 6216, 6220, 6223, 6278, 6296, 6310, 6324, 6337, 6347, 6355, 6442, 6450, 6459, 6463, 6484, 6495, 6499, 6512, 6529, 6535, 6207, 6210, 6212, 6208, 6209, 7623, 6211, 7623, 6213, 6214, 6215, 7623, 6217, 6218, 6219, 7623, 6221, 6222, 7623, 6224, 6229, 6232, 6238, 6244, 6246, 6268, 6225, 6226, 7623, 6227, 6228, 7623, 6230, 6231, 7623, 6233, 6234, 6235, 6236, 6237, 7623, 6239, 7623, 6240, 6241, 6242, 7623, 7623, 6243, 7623, 6245, 7712, 6247, 7623, 6248, 6250, 6253, 6254, 6256, 6258, 6260, 6262, 6265, 6267, 6249, 7623, 7623, 6251, 6252, 7623, 7623, 6255, 7623, 6257, 7623, 6259, 7623, 6261, 7623, 6263, 6264, 7623, 6266, 7623, 7623, 6269, 6272, 6270, 6271, 7623, 6273, 7623, 6274, 6275, 6276, 6277, 7623, 6279, 6282, 6285, 6280, 6281, 7623, 6283, 6284, 7623, 6286, 6290, 6287, 6288, 6289, 7623, 7623, 6291, 6292, 7623, 6293, 6294, 6295, 7623, 7623, 6297, 6301, 6307, 6309, 6298, 6299, 6300, 7623, 6302, 6305, 6303, 6304, 7623, 6306, 7623, 6308, 7623, 7623, 6311, 6313, 6318, 6322, 6312, 7623, 6314, 6315, 6316, 6317, 7623, 6319, 6320, 7623, 6321, 7623, 6323, 7623, 6325, 6335, 7713, 6326, 7623, 6327, 6330, 6334, 6328, 6329, 7623, 6331, 6332, 6333, 7623, 7623, 6336, 7623, 6338, 6342, 6346, 6339, 6340, 6341, 7623, 6343, 6344, 6345, 7623, 7623, 6348, 6353, 6349, 6350, 6351, 7623, 7623, 6352, 7623, 7623, 6354, 7623, 6356, 6430, 6432, 6357, 6358, 6359, 6368, 6381, 6399, 6410, 6420, 6360, 6361, 6362, 6363, 7623, 6364, 6365, 6366, 6367, 7623, 6369, 6370, 6371, 6372, 6373, 6374, 6375, 6379, 6376, 6377, 6378, 7623, 6380, 7623, 6382, 6383, 6384, 6385, 6391, 6386, 6387, 6388, 6389, 6390, 7623, 6392, 6393, 6394, 6395, 6396, 6397, 6398, 7623, 6400, 6401, 6402, 6403, 6404, 6405, 6406, 6407, 6408, 6409, 7623, 6411, 6412, 6413, 6414, 6415, 6416, 6417, 6418, 6419, 7623, 6421, 6422, 6423, 6424, 6425, 6426, 6427, 6428, 6429, 7623, 6431, 7623, 6433, 6434, 6435, 6436, 6437, 6438, 6439, 6440, 6441, 7623, 6443, 6446, 6449, 6444, 6445, 7623, 6447, 6448, 7623, 7623, 6451, 6452, 6453, 6454, 7623, 6455, 6456, 6457, 6458, 7623, 6460, 6461, 6462, 7623, 6464, 6469, 6472, 6479, 6465, 6467, 6466, 7623, 6468, 7623, 6470, 6471, 7623, 6473, 6475, 6476, 6474, 7623, 7623, 6477, 6478, 7623, 6480, 6481, 6482, 6483, 7623, 6485, 6489, 6486, 7623, 6487, 6488, 7623, 6490, 6491, 6492, 6493, 6494, 7623, 6496, 6497, 6498, 7623, 6500, 6504, 6506, 6507, 6501, 6502, 6503, 7623, 6505, 7623, 7623, 6508, 6509, 7623, 6510, 7623, 6511, 7623, 6513, 6517, 6521, 6514, 6515, 6516, 7623, 6518, 6519, 6520, 7623, 6522, 7623, 6523, 6524, 6525, 7623, 7623, 6526, 6527, 6528, 7623, 6530, 6531, 6532, 6533, 6534, 7623, 7623, 6537, 6542, 6546, 6580, 6585, 6612, 6617, 6640, 6666, 6670, 6698, 6709, 6718, 6748, 6752, 6767, 6789, 6913, 6928, 6538, 6539, 6540, 6541, 7623, 6543, 6544, 6545, 7623, 7623, 6547, 6548, 6553, 6556, 6560, 6563, 6570, 6576, 6579, 7623, 6549, 6550, 7623, 6551, 6552, 7623, 6554, 6555, 7623, 7623, 6557, 6558, 6559, 7623, 6561, 6562, 7623, 6564, 6565, 6567, 7623, 6566, 7623, 6568, 6569, 7623, 6571, 6572, 6573, 6574, 6575, 7623, 6577, 6578, 7623, 7623, 6581, 6582, 7623, 6583, 6584, 7623, 7623, 6586, 6589, 6596, 6597, 6599, 6603, 6610, 6587, 6588, 7623, 6590, 6591, 6593, 6592, 7623, 7623, 6594, 6595, 7623, 7714, 6598, 7623, 6600, 6601, 6602, 7623, 6604, 6605, 6609, 6606, 6607, 6608, 7623, 7623, 6611, 7623, 6613, 7623, 6614, 6615, 6616, 7623, 6618, 6621, 6626, 7715, 6619, 6620, 7623, 6622, 6625, 6623, 6624, 7623, 7623, 6627, 6628, 6629, 6632, 6630, 6631, 7623, 6633, 6634, 6635, 6636, 6637, 6638, 6639, 7623, 6641, 6646, 6642, 6643, 7623, 6644, 6645, 7623, 7623, 7623, 6647, 6650, 6652, 6654, 6656, 6658, 6662, 6648, 6649, 7623, 7623, 6651, 7623, 7623, 6653, 7623, 7623, 6655, 7623, 6657, 7623, 6659, 6660, 6661, 7623, 6663, 6664, 6665, 7623, 6667, 6668, 6669, 7623, 6671, 6685, 6691, 6695, 6672, 6682, 6673, 6674, 6675, 6676, 6677, 6678, 6679, 6680, 6681, 7623, 6683, 6684, 7623, 6686, 6687, 6688, 6689, 6690, 7623, 6692, 6693, 7623, 6694, 7623, 7623, 6696, 7623, 6697, 7623, 6699, 6703, 6707, 6700, 6701, 6702, 7623, 7623, 6704, 7623, 6705, 6706, 7623, 6708, 7623, 6710, 6711, 6717, 6712, 6713, 7623, 6714, 6715, 6716, 7623, 7623, 6719, 6726, 6742, 6720, 6723, 6721, 7623, 6722, 7623, 6724, 7623, 6725, 7623, 6727, 6728, 6735, 7623, 6729, 6730, 7623, 6731, 6732, 7623, 6733, 6734, 7623, 7623, 6736, 6737, 7623, 6738, 6739, 7623, 6740, 6741, 7623, 7623, 6743, 6747, 6744, 6745, 6746, 7623, 7623, 7623, 6749, 6750, 6751, 7623, 6753, 6755, 6759, 6763, 6754, 7623, 6756, 6757, 6758, 7623, 6760, 6761, 6762, 7623, 6764, 6765, 6766, 7623, 6768, 6771, 6769, 7623, 6770, 7623, 6772, 6787, 6773, 6774, 6775, 6776, 6777, 6784, 6778, 6779, 6780, 6781, 6782, 6783, 7623, 6785, 6786, 7623, 6788, 7623, 6790, 6829, 6862, 6863, 6865, 7623, 6791, 6792, 6795, 6799, 6803, 6806, 6810, 6814, 7623, 6793, 6794, 7623, 7623, 6796, 6797, 6798, 7623, 6800, 6801, 6802, 7623, 6804, 6805, 7623, 7623, 6807, 6808, 6809, 7623, 6811, 6812, 6813, 7623, 6815, 6824, 6826, 6816, 7623, 6817, 6820, 6818, 7623, 6819, 7623, 6821, 6822, 7623, 6823, 7623, 6825, 7623, 6827, 6828, 7623, 7623, 6830, 7623, 6831, 6837, 6844, 6846, 6859, 6832, 6833, 6834, 6835, 6836, 7623, 6838, 6839, 6840, 6841, 6842, 6843, 7623, 6845, 7623, 6847, 6853, 6856, 6848, 6849, 6850, 6851, 6852, 7623, 6854, 6855, 7623, 6857, 6858, 7623, 6860, 6861, 7623, 7623, 6864, 7623, 7716, 7717, 7718, 7623, 6866, 6867, 6873, 6877, 6883, 6887, 6891, 6894, 6898, 7623, 6868, 6870, 6869, 7623, 6871, 6872, 7623, 7623, 6874, 6875, 6876, 7623, 6878, 6879, 6881, 6880, 7623, 6882, 7623, 6884, 6885, 6886, 7623, 6888, 6889, 6890, 7623, 6892, 6893, 7623, 7623, 6895, 6896, 6897, 7623, 6899, 6908, 6910, 6900, 7623, 6901, 6904, 6902, 7623, 6903, 7623, 6905, 6906, 7623, 6907, 7623, 6909, 7623, 6911, 6912, 7623, 7623, 6914, 6917, 6924, 6915, 6916, 7623, 6918, 6919, 6921, 6920, 7623, 7623, 6922, 6923, 7623, 6925, 6926, 6927, 7623, 6929, 6930, 7719, 6932, 6938, 6941, 6951, 6954, 6959, 6961, 6999, 7011, 7027, 7032, 7085, 7096, 6933, 6937, 6934, 6935, 6936, 7623, 7623, 6939, 6940, 7623, 6942, 6946, 6950, 6943, 6944, 6945, 7623, 6947, 6948, 6949, 7623, 7623, 6952, 6953, 7623, 6955, 6956, 6957, 6958, 7623, 6960, 7623, 6962, 6976, 6991, 6997, 6963, 6970, 6964, 6965, 6966, 7623, 6967, 6968, 6969, 7623, 6971, 7623, 6972, 6975, 6973, 6974, 7623, 7623, 6977, 6988, 6978, 6979, 6985, 6980, 6981, 6982, 6983, 6984, 7623, 6986, 6987, 7623, 6989, 6990, 7623, 6992, 6994, 6993, 7623, 6995, 6996, 7623, 6998, 7720, 7000, 7003, 7009, 7001, 7002, 7623, 7004, 7721, 7623, 7623, 7006, 7007, 7623, 7623, 7010, 7623, 7012, 7014, 7025, 7013, 7623, 7623, 7015, 7018, 7021, 7016, 7017, 7623, 7019, 7020, 7623, 7623, 7022, 7023, 7024, 7623, 7026, 7623, 7028, 7029, 7030, 7031, 7623, 7033, 7036, 7079, 7034, 7035, 7623, 7037, 7060, 7063, 7064, 7069, 7073, 7075, 7038, 7039, 7040, 7041, 7623, 7042, 7046, 7052, 7053, 7043, 7044, 7045, 7623, 7047, 7048, 7049, 7623, 7050, 7051, 7623, 7623, 7054, 7055, 7056, 7057, 7623, 7058, 7059, 7623, 7061, 7062, 7623, 7623, 7065, 7066, 7067, 7068, 7623, 7070, 7071, 7072, 7623, 7074, 7623, 7076, 7077, 7078, 7623, 7080, 7081, 7082, 7083, 7084, 7623, 7086, 7089, 7092, 7087, 7088, 7623, 7623, 7090, 7091, 7623, 7093, 7094, 7095, 7623, 7097, 7100, 7098, 7099, 7623, 7101, 7102, 7103, 7104, 7105, 7114, 7106, 7107, 7108, 7109, 7110, 7111, 7112, 7113, 7623, 7115, 7116, 7117, 7118, 7119, 7120, 7121, 7122, 7123, 7623, 7125, 7128, 7131, 7137, 7144, 7148, 7159, 7165, 7169, 7177, 7190, 7194, 7200, 7248, 7264, 7267, 7278, 7283, 7126, 7127, 7623, 7129, 7130, 7623, 7132, 7135, 7133, 7134, 7722, 7136, 7623, 7138, 7139, 7141, 7140, 7623, 7142, 7143, 7623, 7145, 7147, 7146, 7723, 7623, 7149, 7152, 7156, 7150, 7151, 7623, 7153, 7154, 7155, 7623, 7157, 7158, 7623, 7160, 7164, 7161, 7162, 7163, 7623, 7623, 7166, 7167, 7168, 7724, 7170, 7174, 7171, 7172, 7173, 7623, 7623, 7175, 7176, 7623, 7178, 7187, 7179, 7184, 7180, 7181, 7623, 7182, 7183, 7623, 7185, 7186, 7623, 7188, 7189, 7623, 7191, 7725, 7192, 7193, 7623, 7195, 7198, 7196, 7197, 7623, 7199, 7623, 7201, 7206, 7215, 7231, 7234, 7240, 7202, 7203, 7204, 7205, 7623, 7207, 7208, 7209, 7210, 7211, 7212, 7213, 7214, 7623, 7216, 7217, 7218, 7219, 7220, 7221, 7222, 7226, 7223, 7224, 7225, 7623, 7227, 7228, 7229, 7230, 7623, 7232, 7233, 7623, 7235, 7623, 7236, 7237, 7623, 7238, 7239, 7623, 7241, 7242, 7243, 7244, 7245, 7246, 7247, 7623, 7249, 7258, 7261, 7250, 7255, 7251, 7252, 7623, 7253, 7254, 7623, 7256, 7257, 7623, 7259, 7260, 7623, 7262, 7263, 7623, 7265, 7266, 7623, 7268, 7271, 7275, 7269, 7270, 7623, 7272, 7273, 7274, 7623, 7276, 7623, 7277, 7623, 7279, 7282, 7280, 7281, 7623, 7726, 7284, 7285, 7286, 7287, 7288, 7623, 7290, 7293, 7297, 7301, 7381, 7383, 7387, 7403, 7405, 7409, 7414, 7417, 7421, 7425, 7437, 7291, 7292, 7623, 7294, 7295, 7623, 7296, 7623, 7298, 7299, 7300, 7623, 7302, 7306, 7303, 7304, 7305, 7623, 7307, 7314, 7319, 7326, 7335, 7338, 7360, 7308, 7309, 7310, 7311, 7312, 7313, 7623, 7315, 7316, 7317, 7318, 7623, 7320, 7321, 7322, 7323, 7324, 7325, 7623, 7327, 7329, 7330, 7328, 7623, 7623, 7331, 7332, 7333, 7334, 7623, 7623, 7336, 7337, 7623, 7339, 7343, 7340, 7341, 7342, 7623, 7344, 7352, 7345, 7346, 7347, 7348, 7349, 7350, 7623, 7351, 7623, 7353, 7354, 7355, 7356, 7357, 7358, 7623, 7359, 7623, 7361, 7365, 7362, 7363, 7364, 7623, 7366, 7367, 7368, 7369, 7370, 7371, 7372, 7376, 7373, 7374, 7375, 7623, 7377, 7378, 7379, 7380, 7623, 7382, 7623, 7384, 7385, 7386, 7623, 7388, 7394, 7398, 7623, 7389, 7392, 7390, 7391, 7623, 7393, 7623, 7395, 7396, 7397, 7623, 7399, 7402, 7400, 7401, 7623, 7623, 7404, 7623, 7406, 7407, 7408, 7623, 7410, 7411, 7412, 7413, 7623, 7623, 7415, 7416, 7623, 7418, 7419, 7420, 7623, 7422, 7423, 7424, 7623, 7426, 7428, 7427, 7623, 7429, 7433, 7430, 7431, 7432, 7623, 7623, 7434, 7435, 7436, 7623, 7623, 7438, 7439, 7440, 7441, 7442, 7623, 7444, 7448, 7460, 7462, 7465, 7466, 7471, 7445, 7446, 7447, 7623, 7449, 7456, 7450, 7453, 7451, 7452, 7623, 7454, 7623, 7455, 7623, 7457, 7458, 7459, 7623, 7461, 7623, 7463, 7464, 7623, 7623, 7623, 7467, 7468, 7469, 7470, 7623, 7472, 7473, 7623, 7475, 7483, 7487, 7489, 7496, 7497, 7504, 7507, 7510, 7523, 7530, 7537, 7545, 7548, 7476, 7478, 7481, 7477, 7623, 7479, 7480, 7623, 7482, 7623, 7484, 7485, 7486, 7623, 7488, 7623, 7490, 7493, 7491, 7492, 7623, 7494, 7495, 7623, 7623, 7498, 7501, 7499, 7500, 7623, 7502, 7503, 7623, 7505, 7506, 7623, 7508, 7509, 7623, 7511, 7514, 7519, 7512, 7513, 7623, 7515, 7516, 7623, 7517, 7518, 7623, 7520, 7521, 7522, 7623, 7524, 7527, 7525, 7526, 7623, 7528, 7529, 7623, 7531, 7533, 7532, 7623, 7534, 7535, 7536, 7623, 7538, 7542, 7539, 7540, 7541, 7623, 7543, 7544, 7623, 7546, 7547, 7623, 7549, 7550, 7551, 7552, 7623, 7554, 7559, 7564, 7565, 7567, 7570, 7573, 7576, 7555, 7556, 7558, 7557, 7727, 7623, 7560, 7563, 7561, 7562, 7623, 7623, 7728, 7566, 7623, 7568, 7569, 7623, 7571, 7572, 7623, 7574, 7575, 7623, 7577, 7579, 7578, 7623, 7729, 7581, 7586, 7592, 7595, 7602, 7604, 7607, 7613, 7616, 7619, 7582, 7583, 7584, 7585, 7623, 7587, 7591, 7588, 7589, 7590, 7623, 7623, 7593, 7594, 7623, 7596, 7600, 7597, 7598, 7599, 7623, 7601, 7623, 7603, 7623, 7605, 7606, 7623, 7608, 7609, 7610, 7611, 7612, 7623, 7614, 7615, 7623, 7617, 7618, 7623, 7620, 7621, 7623, 7622, 7623, 1, 69, 118, 332, 656, 773, 827, 913, 979, 1076, 1098, 1123, 1493, 1532, 2026, 2129, 2214, 2225, 2479, 2676, 2770, 2946, 3016, 3033, 3043, 3073, 3114, 3250, 3561, 3848, 4087, 4268, 4365, 4510, 4626, 4763, 4789, 4820, 5311, 5421, 5853, 5992, 6169, 6205, 6536, 6931, 7124, 7289, 7443, 7474, 7553, 7580, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 3620, 7623, 7623, 3729, 7623, 7623, 7623, 7623, 7623, 7623, 3940, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 4454, 4458, 4461, 4465, 4470, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 5259, 5263, 5266, 5270, 5274, 5278, 5283, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 5636, 5646, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 5995, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7005, 7008, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623, 7623 }; static const short _char_ref_trans_actions[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 13, 0, 0, 0, 0, 17, 0, 19, 0, 0, 0, 0, 0, 0, 0, 23, 0, 0, 0, 25, 0, 27, 0, 0, 0, 0, 29, 0, 31, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 37, 0, 0, 0, 39, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 45, 0, 0, 47, 0, 0, 49, 0, 51, 0, 0, 0, 0, 0, 0, 0, 53, 0, 0, 0, 0, 0, 0, 0, 55, 0, 57, 0, 59, 0, 0, 61, 0, 0, 0, 63, 0, 0, 65, 0, 0, 0, 0, 67, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 69, 0, 0, 0, 0, 0, 0, 0, 0, 73, 75, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 77, 0, 0, 0, 0, 79, 0, 0, 0, 0, 0, 0, 0, 81, 0, 0, 0, 0, 0, 85, 0, 0, 0, 0, 87, 0, 0, 89, 0, 0, 0, 0, 0, 0, 91, 0, 0, 0, 0, 0, 0, 93, 0, 95, 0, 97, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 99, 0, 0, 0, 0, 101, 0, 0, 0, 103, 0, 0, 0, 0, 105, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 107, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 109, 0, 0, 0, 0, 111, 0, 0, 0, 0, 0, 0, 113, 0, 115, 0, 0, 0, 0, 0, 0, 0, 0, 117, 0, 0, 119, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 121, 0, 0, 123, 0, 0, 0, 0, 0, 125, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 127, 0, 0, 0, 129, 0, 0, 131, 0, 133, 0, 0, 0, 135, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 137, 0, 0, 0, 0, 0, 0, 139, 0, 0, 141, 0, 0, 143, 0, 0, 145, 0, 0, 0, 0, 0, 0, 147, 0, 149, 0, 0, 151, 0, 0, 0, 0, 0, 153, 155, 0, 157, 0, 0, 159, 0, 161, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 163, 0, 0, 0, 165, 0, 0, 0, 0, 0, 0, 0, 0, 167, 0, 0, 0, 0, 169, 0, 0, 0, 0, 171, 0, 0, 0, 173, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 175, 0, 0, 0, 0, 0, 177, 179, 0, 0, 0, 0, 181, 0, 0, 0, 0, 183, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 185, 0, 0, 0, 187, 0, 0, 0, 0, 0, 0, 189, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 191, 0, 0, 0, 0, 0, 0, 0, 0, 0, 193, 0, 0, 195, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 197, 0, 0, 0, 0, 0, 0, 0, 0, 0, 199, 0, 0, 0, 0, 0, 0, 0, 0, 0, 201, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 203, 0, 0, 205, 0, 0, 0, 0, 0, 0, 0, 207, 0, 0, 0, 0, 0, 0, 0, 0, 209, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 211, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 213, 0, 0, 0, 0, 215, 0, 0, 0, 0, 0, 0, 217, 0, 0, 0, 0, 219, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 221, 0, 0, 0, 0, 0, 0, 0, 0, 223, 0, 0, 0, 0, 0, 225, 0, 0, 0, 227, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 229, 0, 0, 0, 0, 0, 231, 0, 0, 0, 233, 0, 0, 235, 0, 0, 0, 0, 0, 237, 0, 0, 0, 0, 239, 0, 0, 0, 241, 0, 0, 0, 243, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 245, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 251, 0, 0, 255, 0, 0, 257, 0, 259, 0, 0, 0, 0, 0, 0, 0, 0, 0, 263, 0, 0, 0, 0, 265, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 267, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 269, 0, 0, 0, 0, 271, 0, 273, 0, 0, 0, 0, 0, 275, 0, 0, 0, 0, 277, 0, 0, 0, 0, 0, 279, 0, 0, 0, 0, 0, 0, 0, 281, 0, 0, 0, 283, 0, 285, 0, 287, 0, 0, 0, 0, 0, 0, 0, 291, 0, 0, 0, 0, 0, 0, 0, 0, 0, 293, 0, 0, 0, 0, 0, 0, 295, 0, 297, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 299, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 301, 0, 0, 0, 0, 303, 0, 0, 0, 305, 0, 0, 0, 0, 0, 0, 0, 307, 0, 0, 309, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 311, 0, 0, 0, 315, 0, 317, 0, 0, 0, 0, 319, 0, 0, 0, 0, 0, 0, 321, 0, 0, 323, 325, 0, 0, 327, 0, 329, 331, 0, 0, 333, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 335, 0, 0, 0, 0, 337, 0, 0, 0, 0, 0, 0, 0, 0, 339, 0, 0, 0, 0, 0, 0, 341, 0, 0, 0, 343, 0, 0, 0, 0, 0, 0, 0, 0, 0, 345, 0, 0, 0, 0, 347, 0, 0, 349, 351, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 353, 0, 0, 0, 0, 355, 357, 0, 0, 0, 359, 0, 361, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 363, 0, 0, 0, 365, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 367, 0, 0, 0, 369, 0, 0, 0, 371, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 373, 0, 0, 0, 0, 375, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 377, 0, 0, 0, 379, 0, 0, 381, 0, 0, 0, 0, 0, 0, 0, 0, 387, 0, 0, 389, 0, 391, 0, 0, 0, 0, 395, 0, 0, 0, 0, 0, 397, 0, 0, 0, 0, 0, 0, 399, 0, 0, 0, 0, 401, 0, 0, 403, 0, 0, 0, 0, 0, 0, 405, 0, 0, 0, 0, 0, 0, 0, 407, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 409, 0, 0, 0, 0, 411, 0, 0, 0, 0, 0, 413, 0, 415, 0, 417, 0, 0, 419, 0, 0, 0, 0, 421, 0, 0, 0, 0, 423, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 427, 429, 0, 431, 0, 0, 433, 0, 0, 0, 435, 0, 0, 0, 437, 0, 0, 0, 439, 0, 0, 0, 0, 0, 0, 0, 0, 0, 441, 0, 0, 443, 0, 0, 0, 445, 0, 0, 0, 0, 0, 447, 449, 0, 451, 0, 0, 453, 0, 0, 455, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 457, 0, 0, 0, 0, 0, 0, 0, 0, 461, 0, 0, 0, 463, 0, 465, 0, 0, 0, 0, 0, 0, 0, 467, 0, 469, 0, 0, 0, 0, 0, 0, 471, 0, 0, 0, 473, 475, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 477, 0, 0, 0, 479, 0, 0, 0, 0, 481, 0, 0, 0, 0, 0, 0, 0, 0, 0, 483, 0, 0, 0, 0, 0, 0, 485, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 487, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 489, 0, 0, 0, 0, 0, 491, 0, 0, 0, 493, 0, 0, 0, 0, 495, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 497, 0, 0, 0, 0, 0, 499, 0, 0, 0, 501, 0, 0, 0, 0, 0, 0, 503, 0, 0, 0, 0, 0, 505, 0, 0, 0, 0, 0, 0, 507, 0, 0, 0, 0, 509, 0, 0, 0, 0, 511, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 513, 0, 0, 0, 0, 0, 0, 0, 0, 515, 0, 0, 0, 0, 0, 517, 0, 0, 0, 519, 0, 0, 0, 0, 0, 521, 0, 0, 0, 523, 0, 0, 0, 0, 525, 0, 0, 0, 0, 0, 0, 0, 0, 0, 527, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 529, 0, 0, 0, 0, 0, 0, 0, 0, 531, 0, 0, 0, 0, 0, 0, 533, 0, 0, 0, 535, 0, 0, 0, 0, 0, 0, 0, 0, 0, 537, 0, 0, 0, 0, 539, 0, 541, 543, 0, 0, 0, 0, 0, 0, 0, 0, 545, 0, 0, 0, 0, 547, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 549, 0, 0, 0, 0, 0, 0, 0, 0, 0, 551, 0, 0, 0, 0, 0, 0, 0, 0, 0, 553, 0, 0, 0, 0, 0, 0, 0, 0, 0, 555, 0, 0, 0, 0, 0, 0, 0, 0, 0, 557, 0, 0, 0, 0, 0, 0, 0, 0, 0, 559, 0, 561, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 563, 0, 0, 0, 0, 0, 0, 0, 0, 0, 565, 0, 0, 0, 0, 567, 569, 0, 0, 0, 571, 573, 0, 0, 0, 0, 0, 0, 0, 0, 0, 575, 0, 577, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 579, 0, 0, 0, 0, 0, 0, 581, 0, 583, 0, 0, 0, 0, 0, 0, 0, 585, 0, 0, 587, 0, 0, 589, 591, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 593, 0, 0, 0, 0, 595, 0, 0, 0, 0, 0, 0, 597, 0, 0, 0, 599, 601, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 603, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 605, 0, 0, 0, 0, 0, 607, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 609, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 611, 0, 0, 0, 0, 0, 0, 0, 613, 0, 0, 0, 0, 615, 0, 617, 0, 0, 0, 0, 0, 0, 0, 0, 619, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 621, 0, 623, 625, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 627, 0, 0, 0, 0, 629, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 631, 0, 0, 0, 0, 0, 0, 0, 0, 633, 0, 0, 0, 635, 0, 0, 0, 0, 0, 637, 0, 0, 0, 0, 639, 0, 0, 0, 0, 0, 0, 641, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 643, 0, 0, 0, 0, 0, 0, 0, 0, 645, 0, 0, 0, 0, 0, 0, 647, 0, 0, 0, 649, 0, 0, 0, 0, 0, 0, 0, 0, 0, 651, 0, 0, 0, 0, 653, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 655, 0, 0, 0, 0, 657, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 659, 0, 0, 0, 0, 661, 0, 0, 0, 0, 663, 0, 665, 0, 0, 0, 0, 0, 0, 0, 0, 0, 667, 0, 0, 0, 0, 0, 0, 669, 0, 0, 0, 671, 0, 0, 0, 0, 0, 0, 0, 0, 0, 673, 0, 0, 0, 0, 675, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 677, 0, 0, 0, 0, 0, 0, 0, 679, 0, 0, 0, 0, 0, 0, 0, 681, 0, 0, 0, 0, 0, 0, 683, 0, 0, 0, 0, 0, 0, 0, 0, 0, 685, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 687, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 689, 0, 0, 0, 0, 691, 0, 0, 0, 0, 693, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 695, 0, 0, 0, 0, 0, 697, 0, 0, 0, 0, 0, 699, 0, 0, 0, 0, 0, 701, 0, 0, 0, 0, 0, 0, 703, 0, 0, 0, 0, 0, 705, 0, 0, 0, 0, 0, 707, 0, 0, 0, 0, 0, 0, 0, 709, 0, 0, 0, 0, 0, 0, 0, 0, 0, 711, 0, 0, 0, 0, 713, 0, 0, 0, 0, 0, 715, 0, 0, 0, 0, 0, 717, 0, 0, 0, 0, 719, 0, 0, 0, 0, 0, 0, 0, 721, 0, 0, 0, 0, 0, 0, 0, 0, 723, 0, 0, 0, 0, 725, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 727, 0, 0, 729, 0, 0, 0, 0, 733, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 735, 0, 0, 0, 0, 0, 0, 0, 0, 741, 0, 0, 0, 0, 743, 0, 745, 0, 0, 0, 0, 0, 0, 0, 0, 0, 749, 0, 0, 751, 0, 0, 0, 0, 753, 0, 0, 755, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 757, 0, 0, 0, 0, 759, 761, 0, 0, 0, 763, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 769, 0, 0, 0, 0, 0, 0, 0, 0, 0, 773, 0, 0, 0, 0, 775, 0, 0, 777, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 779, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 781, 0, 783, 0, 785, 0, 787, 789, 0, 0, 0, 0, 0, 0, 0, 791, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 793, 0, 795, 797, 0, 0, 0, 0, 0, 0, 0, 0, 799, 0, 0, 0, 0, 0, 0, 0, 801, 0, 0, 0, 0, 0, 0, 0, 0, 0, 803, 0, 0, 0, 0, 805, 0, 0, 807, 0, 0, 0, 0, 0, 809, 0, 0, 0, 0, 0, 0, 811, 0, 0, 813, 0, 0, 0, 815, 817, 0, 0, 0, 0, 0, 0, 0, 821, 0, 0, 823, 0, 0, 825, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 827, 0, 0, 0, 0, 0, 0, 0, 831, 0, 833, 0, 835, 0, 0, 837, 0, 0, 0, 0, 0, 0, 839, 0, 0, 0, 841, 843, 845, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 847, 0, 0, 0, 0, 0, 0, 0, 0, 0, 849, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 851, 0, 853, 0, 855, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 857, 0, 0, 0, 859, 0, 0, 0, 0, 861, 0, 0, 0, 0, 0, 0, 0, 0, 863, 0, 0, 0, 0, 0, 0, 865, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 867, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 869, 0, 0, 0, 0, 0, 871, 0, 0, 0, 873, 0, 0, 0, 0, 875, 0, 0, 0, 877, 0, 0, 0, 0, 0, 0, 879, 0, 0, 0, 0, 0, 881, 0, 0, 0, 0, 0, 0, 883, 0, 0, 0, 0, 885, 0, 0, 0, 0, 887, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 889, 0, 0, 0, 0, 0, 0, 0, 0, 891, 0, 0, 0, 0, 0, 893, 0, 0, 0, 895, 0, 0, 0, 0, 0, 897, 0, 0, 0, 899, 0, 0, 0, 0, 901, 0, 0, 0, 903, 0, 0, 0, 0, 0, 0, 0, 0, 0, 905, 0, 0, 0, 0, 0, 0, 0, 0, 0, 907, 0, 0, 0, 909, 911, 0, 0, 0, 0, 0, 0, 0, 0, 0, 913, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 915, 0, 917, 0, 0, 0, 0, 919, 0, 0, 0, 0, 921, 923, 0, 0, 0, 0, 0, 0, 0, 925, 0, 0, 0, 927, 0, 0, 929, 931, 0, 933, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 935, 0, 0, 0, 0, 0, 0, 0, 0, 937, 0, 0, 0, 0, 0, 0, 0, 0, 0, 939, 0, 0, 0, 0, 0, 0, 941, 0, 0, 0, 943, 0, 0, 0, 0, 0, 0, 0, 0, 0, 945, 0, 0, 947, 0, 0, 0, 949, 0, 0, 0, 951, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 953, 0, 0, 0, 0, 0, 0, 955, 0, 0, 0, 0, 0, 957, 0, 0, 0, 0, 0, 959, 0, 0, 0, 0, 0, 961, 0, 0, 0, 0, 963, 0, 0, 965, 0, 0, 967, 0, 0, 0, 0, 969, 0, 0, 0, 971, 0, 0, 0, 0, 0, 973, 0, 0, 0, 0, 0, 0, 975, 0, 0, 0, 0, 0, 0, 0, 977, 0, 0, 0, 0, 0, 0, 0, 0, 0, 979, 0, 0, 0, 0, 981, 0, 0, 0, 0, 983, 985, 987, 0, 0, 0, 0, 0, 0, 989, 0, 0, 0, 0, 0, 991, 0, 0, 993, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 997, 0, 0, 0, 0, 999, 0, 1001, 0, 0, 1003, 1005, 0, 0, 0, 0, 0, 0, 1007, 0, 0, 0, 1009, 1011, 0, 1013, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1015, 0, 1017, 0, 0, 0, 0, 0, 0, 0, 0, 1019, 0, 0, 0, 0, 0, 1021, 0, 0, 0, 1023, 0, 0, 0, 0, 0, 0, 0, 1025, 0, 0, 0, 0, 0, 0, 0, 0, 1027, 0, 0, 0, 0, 1029, 0, 0, 1031, 0, 0, 0, 0, 0, 0, 0, 1033, 0, 0, 0, 1035, 0, 0, 0, 1037, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1041, 0, 0, 0, 0, 1043, 0, 0, 0, 0, 1045, 0, 0, 1047, 0, 0, 0, 0, 1051, 0, 0, 0, 0, 1053, 0, 1055, 0, 0, 0, 0, 0, 0, 0, 1059, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1061, 0, 0, 0, 0, 1063, 0, 0, 1065, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1067, 0, 0, 1069, 0, 0, 0, 0, 1071, 0, 0, 0, 0, 1073, 0, 1075, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1077, 0, 0, 0, 0, 1079, 0, 0, 0, 0, 0, 0, 0, 0, 1081, 0, 0, 0, 0, 0, 0, 0, 0, 1083, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1085, 0, 0, 1087, 0, 0, 0, 0, 0, 1089, 0, 0, 0, 0, 1091, 0, 0, 0, 0, 0, 0, 0, 0, 1093, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1095, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1097, 0, 1099, 0, 0, 0, 1101, 0, 0, 0, 1103, 0, 0, 1105, 0, 0, 0, 0, 1107, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1111, 0, 0, 1113, 0, 1115, 0, 0, 0, 1117, 0, 1119, 0, 0, 1121, 0, 0, 0, 0, 0, 1123, 1125, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1127, 0, 0, 0, 1129, 0, 0, 0, 0, 0, 0, 0, 0, 1131, 0, 0, 0, 0, 1133, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1135, 0, 1137, 0, 0, 1139, 0, 0, 1141, 0, 0, 0, 0, 1143, 0, 0, 0, 0, 0, 0, 0, 0, 1145, 0, 0, 0, 1147, 0, 1149, 0, 0, 1151, 0, 0, 1153, 0, 0, 0, 0, 0, 1155, 1157, 0, 0, 1159, 0, 0, 1161, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1163, 0, 0, 1165, 0, 0, 1167, 0, 0, 0, 0, 0, 0, 0, 0, 1171, 1173, 0, 1175, 0, 0, 1177, 0, 0, 1179, 0, 0, 1181, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1183, 0, 0, 0, 0, 1185, 0, 0, 0, 0, 0, 1187, 1189, 0, 0, 1191, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1193, 0, 1195, 0, 1197, 0, 0, 1199, 0, 0, 1201, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1205, 1207, 0, 0, 0, 0, 0, 1209, 1211, 0, 0, 0, 0, 1217, 0, 0, 0, 1221, 0, 1223, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1227, 0, 1229, 0, 0, 1231, 0, 0, 0, 0, 0, 1233, 0, 1235, 0, 0, 1239, 0, 0, 0, 0, 0, 0, 1241, 1243, 0, 0, 0, 0, 1245, 1247, 1249, 0, 0, 0, 0, 0, 0, 1251, 0, 1253, 0, 0, 1255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1257, 1259, 1261, 1263, 1265, 1267, 1269, 1271, 0, 1273, 0, 0, 1275, 0, 1277, 0, 0, 0, 1279, 1281, 0, 0, 0, 1283, 0, 0, 0, 0, 1285, 0, 1287, 1289, 0, 0, 0, 0, 0, 0, 1291, 0, 0, 0, 1293, 1295, 0, 1297, 0, 1299, 0, 0, 0, 1301, 0, 0, 1303, 0, 0, 0, 0, 0, 0, 0, 1307, 1309, 0, 0, 1311, 0, 0, 1313, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1319, 0, 0, 1321, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1323, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1325, 0, 0, 0, 0, 0, 0, 1327, 0, 0, 0, 0, 1329, 0, 0, 1331, 0, 0, 1333, 0, 0, 0, 0, 1335, 0, 0, 1337, 0, 0, 1339, 0, 0, 1341, 0, 0, 0, 0, 1343, 0, 0, 0, 0, 1345, 1347, 0, 0, 0, 1349, 0, 0, 0, 0, 0, 0, 0, 0, 1351, 0, 1353, 0, 0, 0, 0, 1355, 0, 0, 1357, 0, 0, 0, 1359, 0, 0, 0, 1361, 1363, 0, 0, 0, 1365, 0, 1367, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1369, 0, 0, 1371, 0, 1373, 0, 0, 0, 0, 0, 1375, 0, 0, 0, 1377, 0, 0, 0, 0, 1379, 0, 0, 0, 0, 0, 1381, 0, 0, 1383, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1385, 0, 1387, 0, 0, 0, 0, 1389, 0, 0, 1391, 0, 0, 0, 0, 1393, 0, 0, 0, 0, 1395, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1397, 0, 0, 0, 0, 0, 1399, 0, 0, 0, 0, 0, 0, 0, 1401, 0, 0, 0, 0, 0, 0, 1403, 0, 0, 0, 1405, 0, 0, 0, 0, 1407, 0, 1409, 0, 0, 0, 0, 1411, 1413, 0, 1415, 0, 0, 1417, 0, 0, 1419, 0, 0, 0, 0, 1421, 0, 1423, 0, 0, 0, 0, 0, 1425, 1427, 0, 0, 0, 1429, 0, 0, 0, 1431, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1433, 1435, 1437, 1439, 1441, 0, 0, 0, 0, 1443, 1445, 1447, 1449, 0, 0, 0, 0, 1451, 1453, 1455, 1457, 1459, 0, 0, 0, 0, 0, 0, 1461, 1463, 1465, 1467, 1469, 1471, 0, 0, 1473, 0, 0, 0, 0, 1475, 1477, 1479, 1481, 1483, 0, 0, 0, 0, 1485, 1487, 1489, 1491, 0, 0, 0, 0, 1493, 0, 0, 0, 1495, 0, 0, 0, 0, 1497, 0, 0, 0, 0, 1499, 1501, 1503, 1505, 1507, 0, 0, 0, 0, 0, 0, 1509, 1511, 1513, 1515, 1517, 1519, 0, 0, 0, 0, 1521, 0, 0, 0, 0, 1523, 0, 0, 0, 0, 0, 0, 0, 0, 1527, 0, 0, 1529, 0, 1531, 0, 1533, 0, 1535, 0, 0, 1537, 0, 0, 0, 1539, 0, 0, 0, 1541, 0, 0, 1543, 0, 1545, 0, 0, 1547, 1549, 0, 1551, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1553, 1555, 0, 0, 0, 0, 0, 0, 0, 1557, 0, 0, 0, 0, 1559, 0, 0, 0, 1561, 0, 1563, 0, 0, 1565, 1567, 0, 0, 0, 1569, 0, 1571, 0, 0, 0, 0, 0, 0, 0, 1573, 0, 0, 1575, 0, 0, 0, 0, 0, 1579, 0, 0, 1581, 0, 0, 1583, 0, 0, 1585, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1589, 5, 4469, 0, 0, 0, 0, 1593, 0, 1595, 0, 0, 0, 0, 1597, 0, 0, 1599, 0, 0, 0, 0, 1601, 1603, 0, 1605, 0, 0, 0, 0, 0, 0, 1607, 1609, 0, 0, 0, 1611, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1613, 0, 0, 0, 0, 1615, 0, 0, 0, 0, 0, 1617, 1619, 0, 0, 1621, 0, 0, 0, 1623, 0, 0, 0, 1625, 1627, 0, 0, 0, 0, 1629, 0, 0, 1631, 0, 0, 0, 1633, 0, 0, 0, 1635, 0, 0, 0, 1637, 0, 0, 0, 0, 0, 0, 1639, 0, 1641, 0, 1643, 0, 0, 0, 1645, 0, 1647, 1649, 0, 0, 0, 1651, 0, 0, 0, 0, 0, 0, 1653, 0, 0, 1655, 0, 0, 1657, 0, 0, 0, 1659, 0, 0, 1661, 0, 0, 5, 1663, 0, 0, 1665, 4471, 0, 1669, 0, 0, 0, 0, 1671, 0, 0, 1673, 0, 0, 0, 1675, 0, 0, 1677, 0, 1679, 1681, 0, 1683, 0, 0, 0, 1685, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1687, 1689, 0, 0, 0, 1691, 0, 1693, 0, 0, 0, 1695, 0, 1697, 1699, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1701, 0, 0, 0, 1703, 0, 1705, 0, 0, 1707, 0, 1709, 1711, 0, 0, 0, 0, 0, 0, 1713, 0, 1715, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1717, 0, 0, 0, 1719, 0, 0, 1721, 0, 0, 0, 0, 1723, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1727, 0, 0, 0, 0, 1729, 0, 0, 1731, 0, 0, 1733, 0, 0, 0, 0, 0, 0, 0, 1735, 0, 0, 1737, 0, 0, 0, 0, 1739, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1741, 0, 0, 1743, 0, 0, 0, 0, 0, 0, 0, 1745, 0, 0, 0, 1747, 0, 1749, 0, 1751, 0, 1753, 0, 0, 0, 0, 0, 0, 1755, 0, 0, 1757, 0, 0, 0, 0, 0, 1759, 1761, 1763, 0, 0, 0, 0, 0, 0, 0, 1765, 0, 1767, 0, 0, 0, 0, 1769, 0, 0, 0, 0, 0, 1773, 0, 0, 0, 0, 1775, 0, 0, 0, 0, 0, 1777, 1779, 0, 0, 0, 0, 1781, 1783, 0, 0, 0, 0, 0, 0, 1785, 0, 0, 0, 0, 1787, 0, 0, 0, 0, 1789, 1791, 1793, 0, 0, 0, 0, 1795, 0, 0, 1797, 1799, 0, 0, 0, 5, 4473, 0, 0, 0, 0, 0, 0, 1803, 0, 0, 1805, 0, 0, 1807, 0, 0, 0, 0, 0, 1809, 0, 0, 1811, 0, 0, 0, 0, 0, 0, 0, 0, 1813, 0, 1815, 1817, 0, 0, 0, 0, 0, 1819, 0, 0, 0, 1821, 0, 0, 0, 0, 1823, 0, 0, 0, 1825, 0, 0, 0, 0, 0, 1827, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1829, 0, 0, 0, 0, 0, 0, 0, 0, 1831, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1833, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1835, 0, 0, 0, 0, 1837, 0, 0, 0, 0, 0, 0, 0, 1839, 0, 0, 0, 0, 1841, 0, 0, 1843, 0, 0, 0, 0, 0, 1845, 1847, 0, 1849, 0, 0, 0, 1851, 0, 0, 0, 0, 1853, 0, 1855, 0, 1857, 0, 0, 0, 0, 1859, 0, 0, 1861, 0, 0, 0, 0, 0, 1863, 0, 0, 0, 1865, 0, 0, 0, 0, 0, 1867, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1869, 0, 1871, 0, 0, 0, 0, 0, 0, 0, 0, 1875, 0, 0, 0, 0, 0, 0, 0, 1877, 0, 1879, 0, 0, 0, 0, 1883, 1885, 0, 0, 1887, 1889, 0, 0, 0, 0, 1891, 1893, 1895, 0, 0, 0, 0, 0, 1899, 0, 0, 0, 1901, 1903, 0, 0, 0, 0, 0, 0, 0, 0, 1905, 1907, 1909, 0, 0, 0, 1911, 0, 0, 0, 0, 0, 1913, 0, 0, 1915, 0, 0, 0, 0, 1917, 1919, 0, 0, 1925, 0, 0, 1921, 1923, 0, 0, 1927, 0, 1929, 0, 0, 0, 0, 1931, 0, 1933, 0, 0, 0, 0, 1935, 0, 0, 1937, 0, 0, 1939, 0, 1941, 0, 0, 0, 0, 1943, 1945, 0, 0, 0, 0, 0, 0, 0, 0, 1947, 0, 0, 0, 1949, 0, 0, 0, 1951, 0, 0, 0, 0, 0, 0, 0, 1953, 0, 0, 0, 1955, 0, 0, 0, 0, 0, 1957, 0, 0, 1959, 0, 1961, 0, 0, 1963, 0, 0, 0, 0, 0, 1965, 0, 0, 0, 0, 1967, 0, 0, 1969, 0, 0, 0, 0, 1971, 0, 0, 1973, 0, 1975, 0, 0, 1977, 0, 0, 0, 0, 1983, 0, 0, 0, 0, 1985, 0, 0, 1987, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1989, 0, 0, 0, 0, 0, 0, 0, 0, 1991, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1993, 0, 1995, 0, 0, 0, 0, 1997, 0, 0, 0, 0, 0, 0, 1999, 0, 0, 0, 2001, 0, 0, 2003, 2005, 0, 0, 0, 2007, 0, 0, 0, 2009, 0, 0, 0, 0, 2011, 0, 0, 2013, 0, 0, 2015, 0, 0, 2017, 0, 0, 0, 2019, 0, 0, 0, 0, 2021, 2023, 0, 2025, 0, 0, 0, 0, 0, 0, 2027, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2031, 2035, 2037, 2039, 0, 0, 2041, 2043, 0, 0, 0, 2047, 2049, 0, 2051, 0, 0, 2053, 2055, 0, 2057, 0, 2059, 0, 0, 2061, 0, 0, 2063, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 2065, 0, 2067, 0, 0, 0, 0, 0, 0, 2069, 0, 0, 2071, 0, 2073, 2075, 0, 0, 0, 0, 2077, 0, 0, 0, 0, 2079, 2081, 0, 0, 2083, 2085, 0, 0, 0, 2087, 2089, 0, 0, 2091, 0, 0, 0, 0, 2093, 2095, 0, 0, 0, 0, 2097, 0, 0, 2099, 0, 2101, 0, 2103, 2105, 0, 0, 2107, 0, 2109, 2111, 0, 2113, 0, 0, 0, 2115, 0, 0, 2117, 2119, 0, 0, 0, 2121, 2123, 2125, 0, 0, 0, 0, 2127, 0, 2129, 0, 0, 0, 0, 2131, 2133, 0, 2135, 0, 2137, 0, 0, 2139, 0, 0, 2141, 0, 0, 0, 2143, 0, 0, 0, 2145, 0, 2147, 0, 0, 2149, 2151, 4475, 0, 0, 2155, 0, 2157, 0, 0, 2159, 0, 0, 0, 2161, 0, 0, 0, 0, 2163, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2165, 0, 2167, 0, 0, 2169, 0, 0, 0, 0, 0, 0, 2171, 0, 0, 0, 0, 2173, 0, 0, 0, 2175, 0, 0, 2177, 0, 0, 0, 0, 0, 0, 0, 0, 2179, 0, 2181, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2183, 0, 0, 0, 0, 0, 0, 0, 2185, 0, 2187, 0, 0, 0, 2189, 0, 0, 0, 0, 2191, 2193, 0, 0, 0, 0, 2195, 2197, 0, 0, 2199, 0, 0, 0, 2201, 0, 0, 0, 0, 0, 0, 2203, 0, 0, 0, 2205, 0, 0, 0, 2207, 0, 0, 0, 2209, 0, 2211, 0, 0, 0, 0, 0, 0, 0, 2213, 0, 0, 0, 0, 2215, 0, 0, 0, 0, 0, 0, 0, 2217, 0, 0, 0, 2219, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2221, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2223, 0, 2225, 0, 0, 0, 2227, 0, 0, 0, 0, 2229, 0, 0, 0, 2231, 0, 0, 0, 2233, 0, 0, 0, 0, 0, 2235, 0, 0, 0, 2237, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2241, 0, 0, 0, 0, 2245, 0, 0, 0, 2247, 0, 0, 0, 0, 2251, 2253, 0, 0, 0, 0, 2257, 0, 0, 0, 0, 0, 0, 0, 2259, 0, 2261, 0, 0, 0, 2263, 0, 0, 2265, 0, 0, 0, 2267, 0, 0, 0, 0, 0, 0, 0, 2269, 0, 0, 0, 2271, 0, 0, 0, 2273, 0, 0, 0, 2275, 0, 2277, 0, 2279, 0, 0, 2281, 2283, 0, 0, 0, 0, 0, 0, 0, 2285, 0, 0, 2287, 0, 0, 0, 2289, 0, 0, 0, 2291, 2293, 0, 0, 0, 0, 0, 0, 2295, 0, 0, 0, 0, 0, 2297, 0, 0, 0, 2299, 0, 0, 0, 0, 2301, 0, 0, 0, 2303, 0, 0, 0, 0, 0, 2305, 0, 0, 2307, 0, 2309, 0, 2311, 0, 0, 0, 2313, 0, 0, 0, 0, 0, 0, 0, 2317, 0, 2319, 0, 0, 0, 0, 2321, 0, 0, 2323, 2325, 0, 2327, 2329, 2331, 0, 0, 0, 0, 2333, 0, 0, 0, 0, 2335, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2339, 2341, 0, 2343, 0, 0, 0, 2345, 0, 0, 2347, 0, 0, 0, 2349, 0, 0, 0, 2351, 0, 0, 0, 2353, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2355, 0, 2357, 0, 0, 0, 0, 0, 2359, 2361, 0, 2363, 0, 0, 0, 0, 2365, 0, 0, 2367, 0, 0, 2369, 0, 0, 2371, 0, 0, 2373, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 2375, 0, 2377, 0, 0, 0, 2379, 0, 0, 0, 2381, 2383, 0, 2385, 0, 0, 2387, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2389, 0, 0, 0, 0, 0, 2391, 0, 0, 0, 2393, 0, 0, 0, 2395, 0, 2397, 0, 0, 2399, 0, 2401, 2403, 0, 0, 0, 2407, 0, 0, 0, 0, 0, 0, 0, 2409, 0, 0, 2411, 0, 2413, 0, 2415, 0, 2417, 0, 2419, 0, 0, 2421, 0, 2423, 2425, 0, 0, 0, 0, 2427, 2429, 0, 2431, 0, 0, 0, 0, 0, 2433, 0, 0, 2435, 0, 0, 0, 0, 0, 2437, 2439, 0, 0, 2441, 0, 0, 0, 2443, 2445, 0, 0, 0, 0, 0, 0, 0, 2447, 0, 0, 0, 0, 2449, 0, 2451, 0, 2453, 2455, 0, 0, 0, 0, 0, 2457, 0, 0, 2459, 0, 2461, 0, 0, 0, 0, 0, 2463, 0, 0, 0, 0, 2465, 0, 2467, 2469, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2471, 0, 0, 0, 0, 2473, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2475, 0, 2477, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2479, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2481, 0, 2483, 0, 0, 0, 0, 0, 0, 0, 2485, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2487, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2489, 2491, 2493, 0, 0, 2495, 0, 0, 0, 0, 2497, 2499, 0, 0, 0, 0, 0, 2501, 0, 0, 2503, 0, 2505, 0, 2507, 2509, 0, 0, 2511, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2513, 0, 0, 2515, 0, 0, 0, 0, 0, 2517, 0, 0, 0, 2519, 0, 0, 2521, 0, 0, 2523, 0, 0, 0, 0, 0, 0, 2525, 0, 0, 0, 2527, 2529, 2531, 0, 2533, 0, 0, 0, 0, 0, 2535, 2537, 0, 2539, 0, 0, 2541, 0, 0, 2543, 2545, 0, 0, 0, 0, 0, 0, 2547, 0, 0, 0, 0, 0, 2549, 0, 0, 0, 2551, 0, 0, 2553, 0, 0, 0, 0, 0, 2555, 0, 0, 0, 2557, 0, 0, 0, 0, 2559, 0, 0, 0, 0, 2561, 0, 2563, 0, 0, 0, 0, 2565, 2567, 0, 2569, 0, 2571, 0, 0, 2573, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2575, 0, 2577, 0, 0, 2579, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2581, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2583, 0, 0, 0, 0, 0, 2585, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2587, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2589, 0, 0, 0, 0, 2591, 0, 0, 0, 0, 2593, 2595, 0, 0, 2597, 0, 0, 0, 0, 2599, 0, 0, 0, 0, 2601, 0, 0, 2603, 2605, 0, 0, 0, 0, 0, 2607, 2609, 0, 0, 2611, 0, 0, 2613, 0, 0, 0, 0, 0, 0, 0, 2615, 0, 0, 0, 0, 0, 2617, 0, 0, 2619, 0, 2621, 2623, 0, 0, 2625, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2627, 0, 2629, 2631, 0, 2633, 0, 0, 2635, 2637, 0, 0, 2639, 0, 2641, 0, 2643, 0, 0, 0, 2645, 4477, 0, 0, 2649, 0, 2651, 0, 0, 2653, 0, 0, 0, 2655, 0, 0, 0, 2657, 0, 0, 0, 2659, 0, 0, 0, 0, 2661, 0, 0, 0, 0, 2663, 2665, 0, 0, 2667, 2669, 0, 0, 0, 0, 0, 0, 0, 2671, 0, 0, 0, 2673, 0, 0, 0, 0, 0, 0, 0, 0, 2675, 0, 2677, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2679, 0, 0, 0, 0, 0, 0, 0, 2683, 2685, 0, 0, 0, 2687, 2689, 0, 0, 0, 2691, 0, 0, 0, 0, 0, 0, 2693, 0, 0, 0, 2695, 0, 2697, 0, 0, 0, 2699, 0, 0, 0, 0, 0, 2701, 2703, 0, 0, 0, 2705, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2707, 0, 2709, 0, 2711, 0, 0, 0, 0, 0, 2715, 0, 0, 0, 0, 0, 2717, 0, 0, 2719, 0, 0, 0, 0, 2723, 0, 0, 2725, 2727, 0, 2729, 0, 0, 0, 2731, 0, 2733, 0, 0, 0, 0, 2735, 0, 0, 0, 0, 0, 2737, 0, 2739, 2741, 0, 0, 0, 2743, 0, 0, 0, 2745, 2747, 0, 0, 0, 0, 0, 0, 0, 2749, 0, 0, 2751, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2753, 2755, 0, 2757, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2759, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2761, 2763, 2765, 0, 2767, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2769, 0, 0, 0, 0, 0, 2771, 0, 0, 0, 2773, 0, 0, 0, 0, 0, 0, 0, 2775, 0, 0, 0, 2777, 0, 2779, 2781, 0, 0, 0, 0, 2783, 0, 2785, 0, 2787, 0, 0, 0, 2789, 0, 0, 2791, 0, 0, 2793, 0, 2795, 0, 0, 0, 0, 0, 2799, 0, 2801, 0, 0, 0, 0, 0, 0, 0, 2803, 0, 0, 2805, 0, 0, 0, 2807, 0, 0, 2809, 0, 0, 0, 2811, 0, 2813, 2815, 0, 0, 0, 2817, 2819, 0, 0, 0, 0, 0, 0, 0, 0, 2821, 0, 0, 0, 0, 2823, 2825, 0, 0, 2827, 0, 0, 2829, 0, 0, 0, 2831, 0, 0, 0, 0, 2833, 0, 2835, 0, 0, 0, 2837, 0, 2839, 0, 2841, 0, 0, 0, 0, 2843, 2845, 0, 0, 2847, 0, 0, 2849, 0, 0, 0, 0, 2851, 2853, 0, 0, 2855, 2857, 0, 2859, 0, 0, 0, 0, 0, 2861, 0, 0, 2863, 0, 0, 2865, 2867, 0, 0, 2869, 0, 2871, 2873, 0, 0, 2875, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2877, 2879, 0, 0, 2881, 0, 2883, 2885, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2887, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2889, 2891, 0, 0, 2893, 0, 0, 0, 0, 2895, 2897, 0, 2899, 0, 0, 2901, 2903, 0, 0, 2905, 0, 2907, 0, 0, 2909, 0, 5, 0, 2911, 4479, 0, 2915, 0, 0, 0, 2917, 0, 0, 2919, 0, 0, 0, 2921, 2923, 2925, 0, 2927, 0, 0, 0, 0, 2929, 2931, 2933, 0, 0, 0, 0, 2935, 0, 0, 0, 0, 0, 0, 0, 2937, 0, 2939, 2941, 0, 0, 0, 0, 2943, 2945, 0, 0, 0, 0, 2947, 2949, 0, 2951, 0, 0, 2953, 0, 0, 0, 0, 0, 0, 2955, 0, 0, 2957, 0, 0, 2959, 2961, 0, 0, 0, 0, 0, 0, 0, 0, 2963, 0, 0, 2965, 0, 2967, 0, 0, 0, 0, 0, 0, 0, 2969, 0, 0, 0, 0, 0, 2971, 2973, 2975, 0, 0, 0, 0, 0, 0, 0, 2977, 0, 0, 0, 0, 0, 0, 0, 2979, 0, 2981, 0, 2983, 0, 2985, 0, 0, 2987, 0, 0, 2989, 0, 0, 0, 0, 0, 2991, 0, 2993, 0, 0, 0, 2995, 0, 0, 0, 2997, 2999, 0, 0, 3001, 0, 0, 3003, 0, 3005, 0, 3007, 0, 0, 3009, 3011, 0, 0, 0, 3013, 3015, 0, 0, 3017, 0, 0, 3019, 0, 3021, 0, 0, 0, 0, 0, 3023, 0, 0, 0, 0, 3027, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3029, 0, 0, 3031, 0, 0, 0, 0, 3033, 0, 0, 3035, 3037, 0, 3039, 0, 0, 0, 0, 3041, 0, 3043, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3045, 0, 0, 0, 3047, 0, 3049, 0, 0, 0, 3051, 0, 0, 3053, 3055, 0, 0, 0, 0, 3057, 0, 0, 0, 0, 0, 3059, 3061, 3063, 0, 0, 0, 3065, 0, 0, 0, 0, 3067, 0, 0, 0, 3069, 0, 0, 3071, 0, 0, 0, 0, 0, 3073, 0, 0, 0, 0, 3075, 3077, 0, 0, 3079, 0, 0, 0, 3081, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3083, 0, 0, 0, 0, 0, 0, 3087, 0, 0, 0, 3089, 0, 3093, 0, 0, 0, 0, 0, 0, 0, 3095, 0, 0, 0, 3097, 0, 3099, 0, 3101, 0, 0, 0, 3103, 0, 0, 0, 3105, 0, 0, 0, 0, 3107, 3109, 0, 0, 0, 0, 3111, 0, 0, 0, 3115, 0, 0, 0, 0, 3117, 3119, 0, 0, 3121, 0, 0, 0, 0, 0, 0, 3123, 0, 0, 0, 3125, 0, 0, 0, 3127, 0, 0, 3129, 3131, 0, 0, 0, 0, 0, 3133, 0, 0, 3135, 0, 0, 0, 0, 0, 0, 3137, 3139, 0, 0, 3141, 0, 0, 3143, 0, 0, 0, 0, 3145, 0, 0, 3147, 0, 0, 3149, 3151, 0, 0, 0, 0, 0, 0, 0, 0, 3153, 3155, 0, 0, 0, 0, 3157, 0, 0, 3159, 0, 0, 0, 3165, 0, 3167, 0, 0, 0, 0, 3169, 3171, 0, 0, 0, 0, 3173, 0, 0, 0, 0, 3177, 0, 0, 0, 0, 0, 0, 0, 3181, 0, 0, 3183, 0, 0, 0, 0, 0, 3187, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3189, 5, 0, 0, 4481, 0, 0, 0, 3193, 0, 0, 0, 3195, 3197, 3199, 0, 3201, 0, 0, 0, 0, 0, 0, 0, 0, 3203, 0, 0, 3205, 0, 0, 3207, 3209, 0, 0, 0, 3211, 0, 3213, 0, 0, 0, 3215, 0, 3217, 0, 0, 0, 3219, 0, 0, 3221, 3223, 0, 0, 0, 0, 0, 0, 0, 0, 3225, 3227, 0, 0, 0, 0, 0, 0, 3229, 0, 3231, 0, 3233, 0, 3235, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3237, 3239, 0, 0, 3241, 0, 0, 3243, 3245, 3247, 0, 0, 0, 3251, 0, 0, 3253, 3255, 0, 0, 0, 0, 0, 0, 0, 0, 3257, 0, 3259, 0, 0, 3263, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3265, 0, 3267, 0, 0, 3269, 3271, 0, 3273, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3275, 0, 0, 0, 0, 0, 0, 3277, 0, 3279, 0, 0, 0, 0, 0, 0, 0, 0, 3281, 0, 0, 3283, 0, 0, 3285, 0, 0, 3287, 0, 0, 3289, 0, 3291, 0, 0, 0, 3293, 0, 3295, 0, 0, 3297, 0, 0, 0, 3299, 0, 0, 0, 0, 0, 0, 3301, 0, 0, 0, 3303, 0, 0, 0, 3305, 3307, 0, 0, 3309, 0, 0, 3311, 0, 0, 0, 3313, 0, 0, 0, 3315, 3317, 0, 0, 0, 0, 3319, 0, 0, 0, 0, 0, 0, 0, 3321, 0, 0, 3323, 0, 0, 3325, 0, 0, 0, 0, 3327, 0, 0, 3329, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3331, 0, 0, 3333, 0, 0, 3335, 0, 0, 3337, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3341, 0, 3343, 0, 0, 0, 3345, 0, 0, 0, 3347, 0, 0, 3349, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3351, 0, 0, 3353, 0, 0, 3355, 0, 0, 0, 0, 0, 3357, 0, 3359, 0, 0, 0, 3361, 3363, 0, 3365, 0, 0, 0, 3369, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3371, 3373, 0, 0, 3375, 3377, 0, 3379, 0, 3381, 0, 3383, 0, 3385, 0, 0, 3387, 0, 3389, 3391, 0, 0, 0, 0, 3393, 0, 3395, 0, 0, 0, 0, 3397, 0, 0, 0, 0, 0, 3399, 0, 0, 3401, 0, 0, 0, 0, 0, 3403, 3405, 0, 0, 3407, 0, 0, 0, 3409, 3411, 0, 0, 0, 0, 0, 0, 0, 3413, 0, 0, 0, 0, 3415, 0, 3417, 0, 3419, 3421, 0, 0, 0, 0, 0, 3423, 0, 0, 0, 0, 3425, 0, 0, 3427, 0, 3429, 0, 3431, 0, 0, 0, 0, 3433, 0, 0, 0, 0, 0, 3435, 0, 0, 0, 3437, 3439, 0, 3441, 0, 0, 0, 0, 0, 0, 3445, 0, 0, 0, 3447, 3449, 0, 0, 0, 0, 0, 3451, 3453, 0, 3455, 3457, 0, 3459, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3461, 0, 0, 0, 0, 3463, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3465, 0, 3467, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3469, 0, 0, 0, 0, 0, 0, 0, 3471, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3473, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3475, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3477, 0, 3479, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3481, 0, 0, 0, 0, 0, 3483, 0, 0, 3485, 3487, 0, 0, 0, 0, 3489, 0, 0, 0, 0, 3491, 0, 0, 0, 3493, 0, 0, 0, 0, 0, 0, 0, 3495, 0, 3497, 0, 0, 3499, 0, 0, 0, 0, 3501, 3503, 0, 0, 3505, 0, 0, 0, 0, 3507, 0, 0, 0, 3509, 0, 0, 3511, 0, 0, 0, 0, 0, 3513, 0, 0, 0, 3515, 0, 0, 0, 0, 0, 0, 0, 3517, 0, 3519, 3521, 0, 0, 3523, 0, 3525, 0, 3527, 0, 0, 0, 0, 0, 0, 3529, 0, 0, 0, 3531, 0, 3533, 0, 0, 0, 3535, 3537, 0, 0, 0, 3539, 0, 0, 0, 0, 0, 3541, 3543, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3545, 0, 0, 0, 3547, 3549, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3551, 0, 0, 3553, 0, 0, 3555, 0, 0, 3557, 3559, 0, 0, 0, 3561, 0, 0, 3563, 0, 0, 0, 3565, 0, 3567, 0, 0, 3569, 0, 0, 0, 0, 0, 3571, 0, 0, 3573, 3575, 0, 0, 3577, 0, 0, 3579, 3581, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3583, 0, 0, 0, 0, 3585, 3587, 0, 0, 3589, 0, 0, 3593, 0, 0, 0, 3595, 0, 0, 0, 0, 0, 0, 3597, 3599, 0, 3601, 0, 3603, 0, 0, 0, 3605, 0, 0, 0, 0, 0, 0, 3607, 0, 0, 0, 0, 3609, 3611, 0, 0, 0, 0, 0, 0, 3613, 0, 0, 0, 0, 0, 0, 0, 3615, 0, 0, 0, 0, 3619, 0, 0, 3621, 3623, 3625, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3627, 3629, 0, 3631, 3633, 0, 3635, 3637, 0, 3639, 0, 3641, 0, 0, 0, 3643, 0, 0, 0, 3645, 0, 0, 0, 3647, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3649, 0, 0, 3651, 0, 0, 0, 0, 0, 3653, 0, 0, 3655, 0, 3657, 3659, 0, 3661, 0, 3663, 0, 0, 0, 0, 0, 0, 3665, 3667, 0, 3669, 0, 0, 3671, 0, 3673, 0, 0, 0, 0, 0, 3675, 0, 0, 0, 3677, 3679, 0, 0, 0, 0, 0, 0, 3681, 0, 3683, 0, 3685, 0, 3687, 0, 0, 0, 3689, 0, 0, 3691, 0, 0, 3693, 0, 0, 3695, 3697, 0, 0, 3699, 0, 0, 3701, 0, 0, 3703, 3705, 0, 0, 0, 0, 0, 3707, 3709, 3711, 0, 0, 0, 3713, 0, 0, 0, 0, 0, 3715, 0, 0, 0, 3717, 0, 0, 0, 3719, 0, 0, 0, 3721, 0, 0, 0, 3723, 0, 3725, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3727, 0, 0, 3729, 0, 3731, 0, 0, 0, 0, 0, 3733, 0, 0, 0, 0, 0, 0, 0, 0, 3735, 0, 0, 3737, 3739, 0, 0, 0, 3741, 0, 0, 0, 3743, 0, 0, 3745, 3747, 0, 0, 0, 3749, 0, 0, 0, 3751, 0, 0, 0, 0, 3753, 0, 0, 0, 3755, 0, 3757, 0, 0, 3759, 0, 3761, 0, 3763, 0, 0, 3765, 3767, 0, 3769, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3771, 0, 0, 0, 0, 0, 0, 3773, 0, 3775, 0, 0, 0, 0, 0, 0, 0, 0, 3777, 0, 0, 3779, 0, 0, 3781, 0, 0, 3783, 3785, 0, 3787, 0, 0, 0, 3795, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3797, 0, 0, 0, 3799, 0, 0, 3801, 3803, 0, 0, 0, 3805, 0, 0, 0, 0, 3807, 0, 3809, 0, 0, 0, 3811, 0, 0, 0, 3813, 0, 0, 3815, 3817, 0, 0, 0, 3819, 0, 0, 0, 0, 3821, 0, 0, 0, 3823, 0, 3825, 0, 0, 3827, 0, 3829, 0, 3831, 0, 0, 3833, 3835, 0, 0, 0, 0, 0, 3837, 0, 0, 0, 0, 3839, 3841, 0, 0, 3843, 0, 0, 0, 3845, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3849, 3851, 0, 0, 3853, 0, 0, 0, 0, 0, 0, 3855, 0, 0, 0, 3857, 3859, 0, 0, 3861, 0, 0, 0, 0, 3863, 0, 3865, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3867, 0, 0, 0, 3869, 0, 3871, 0, 0, 0, 0, 3873, 3875, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3877, 0, 0, 3879, 0, 0, 3881, 0, 0, 0, 3883, 0, 0, 3885, 0, 0, 0, 0, 0, 0, 0, 3889, 0, 5, 4483, 3893, 0, 0, 3895, 3897, 0, 3899, 0, 0, 0, 0, 3901, 3903, 0, 0, 0, 0, 0, 3905, 0, 0, 3907, 3909, 0, 0, 0, 3911, 0, 3913, 0, 0, 0, 0, 3915, 0, 0, 0, 0, 0, 3917, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3919, 0, 0, 0, 0, 0, 0, 0, 3921, 0, 0, 0, 3923, 0, 0, 3925, 3927, 0, 0, 0, 0, 3929, 0, 0, 3931, 0, 0, 3933, 3935, 0, 0, 0, 0, 3937, 0, 0, 0, 3939, 0, 3941, 0, 0, 0, 3943, 0, 0, 0, 0, 0, 3945, 0, 0, 0, 0, 0, 3947, 3949, 0, 0, 3951, 0, 0, 0, 3953, 0, 0, 0, 0, 3955, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3957, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3959, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3961, 0, 0, 3963, 0, 0, 0, 0, 0, 0, 3967, 0, 0, 0, 0, 3969, 0, 0, 3971, 0, 0, 0, 0, 3975, 0, 0, 0, 0, 0, 3977, 0, 0, 0, 3979, 0, 0, 3981, 0, 0, 0, 0, 0, 3983, 3985, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3989, 3991, 0, 0, 3993, 0, 0, 0, 0, 0, 0, 3995, 0, 0, 3997, 0, 0, 3999, 0, 0, 4001, 0, 0, 0, 0, 4003, 0, 0, 0, 0, 4007, 0, 4009, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4011, 0, 0, 0, 0, 0, 0, 0, 0, 4013, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4015, 0, 0, 0, 0, 4017, 0, 0, 4019, 0, 4021, 0, 0, 4023, 0, 0, 4025, 0, 0, 0, 0, 0, 0, 0, 4027, 0, 0, 0, 0, 0, 0, 0, 4029, 0, 0, 4031, 0, 0, 4033, 0, 0, 4035, 0, 0, 4037, 0, 0, 4039, 0, 0, 0, 0, 0, 4041, 0, 0, 0, 4043, 0, 4045, 0, 4047, 0, 0, 0, 0, 4049, 0, 0, 0, 0, 0, 0, 4053, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4055, 0, 0, 4057, 0, 4059, 0, 0, 0, 4061, 0, 0, 0, 0, 0, 4063, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4065, 0, 0, 0, 0, 4067, 0, 0, 0, 0, 0, 0, 4069, 0, 0, 0, 0, 4071, 4073, 0, 0, 0, 0, 4075, 4077, 0, 0, 4079, 0, 0, 0, 0, 0, 4081, 0, 0, 0, 0, 0, 0, 0, 0, 4083, 0, 4085, 0, 0, 0, 0, 0, 0, 4087, 0, 4089, 0, 0, 0, 0, 0, 4091, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4093, 0, 0, 0, 0, 4095, 0, 4097, 0, 0, 0, 4099, 0, 0, 0, 4101, 0, 0, 0, 0, 4103, 0, 4105, 0, 0, 0, 4107, 0, 0, 0, 0, 4109, 4111, 0, 4113, 0, 0, 0, 4115, 0, 0, 0, 0, 4117, 4119, 0, 0, 4121, 0, 0, 0, 4123, 0, 0, 0, 4125, 0, 0, 0, 4127, 0, 0, 0, 0, 0, 4129, 4131, 0, 0, 0, 4133, 4135, 0, 0, 0, 0, 0, 4137, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4139, 0, 0, 0, 0, 0, 0, 4141, 0, 4143, 0, 4145, 0, 0, 0, 4147, 0, 4149, 0, 0, 4151, 4153, 4155, 0, 0, 0, 0, 4157, 0, 0, 4159, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4161, 0, 0, 4163, 0, 4165, 0, 0, 0, 4167, 0, 4169, 0, 0, 0, 0, 4171, 0, 0, 4173, 4175, 0, 0, 0, 0, 4177, 0, 0, 4179, 0, 0, 4181, 0, 0, 4183, 0, 0, 0, 0, 0, 4185, 0, 0, 4187, 0, 0, 4189, 0, 0, 0, 4191, 0, 0, 0, 0, 4193, 0, 0, 4195, 0, 0, 0, 4197, 0, 0, 0, 4199, 0, 0, 0, 0, 0, 4201, 0, 0, 4203, 0, 0, 4205, 0, 0, 0, 0, 4207, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4211, 0, 0, 0, 0, 4213, 4215, 0, 0, 4219, 0, 0, 4221, 0, 0, 4223, 0, 0, 4225, 0, 0, 0, 4227, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4231, 0, 0, 0, 0, 0, 4233, 4235, 0, 0, 4237, 0, 0, 0, 0, 0, 4239, 0, 4241, 0, 4243, 0, 0, 4245, 0, 0, 0, 0, 0, 4247, 0, 0, 4249, 0, 0, 4251, 0, 0, 4253, 0, 4255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4257, 7, 4259, 9, 4261, 11, 4263, 15, 4265, 21, 4267, 35, 4269, 41, 4271, 43, 4273, 71, 4275, 83, 4277, 247, 4279, 249, 4281, 253, 4283, 261, 4285, 289, 4287, 313, 4289, 383, 4291, 385, 4293, 393, 4295, 425, 4297, 459, 4299, 731, 4301, 737, 4303, 739, 4305, 747, 4307, 765, 4309, 767, 4311, 771, 4313, 819, 4315, 829, 4317, 995, 4319, 1039, 4321, 1049, 4323, 1057, 4325, 1109, 4327, 1169, 4329, 1203, 4331, 1213, 4333, 1215, 4335, 1219, 4337, 1225, 4339, 1237, 4341, 1305, 4343, 1315, 4345, 1317, 4347, 1525, 4349, 1577, 4351, 1587, 4353, 1591, 0, 4355, 1667, 0, 4357, 1725, 4359, 1771, 4361, 1801, 0, 4363, 1873, 4365, 1881, 4367, 1897, 4369, 1979, 4371, 1981, 4373, 2029, 4375, 2033, 4377, 2045, 4379, 2153, 0, 0, 0, 0, 0, 4381, 2239, 4383, 2243, 4385, 2249, 4387, 2255, 4389, 2315, 4391, 2337, 4393, 2405, 4395, 2647, 0, 0, 0, 0, 0, 0, 0, 4397, 2681, 4399, 2713, 4401, 2721, 4403, 2797, 4405, 2913, 0, 0, 4407, 3025, 4409, 3085, 4411, 3091, 4413, 3113, 4415, 3161, 4417, 3163, 4419, 3175, 4421, 3179, 4423, 3185, 4425, 3191, 0, 4427, 3249, 4429, 3261, 4431, 3339, 4433, 3367, 4435, 3443, 4437, 3591, 4439, 3617, 4441, 3789, 4443, 3791, 4445, 3793, 4447, 3847, 4449, 3887, 4451, 3891, 0, 0, 4453, 3965, 4455, 3973, 4457, 3987, 4459, 4005, 4461, 4051, 4463, 4209, 4465, 4217, 4467, 4229 }; static const short _char_ref_to_state_actions[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; static const short _char_ref_from_state_actions[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; static const short _char_ref_eof_trans[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4396, 4396, 4396, 4396, 4396, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4538, 4538, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4819, 4819, 4819, 4819, 4819, 4819, 4819, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5497, 5497, 5497, 5497, 5497, 5497, 5497, 5497, 5497, 5497, 5497, 5497, 5497, 5497, 5497, 5497, 5497, 5497, 5497, 5497, 5497, 5497, 5497, 5497, 5497, 5497, 5497, 5497, 5497, 5497, 5497, 5497, 5497, 5497, 5497, 5497, 5497, 5497, 5497, 5497, 5497, 5497, 5497, 5497, 5497, 5497, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6541, 6541, 6541, 6541, 6541, 6541, 6541, 6541, 6541, 6541, 6541, 6541, 6541, 6541, 6541, 6541, 6541, 6541, 6541, 6541, 6541, 6541, 6541, 6541, 6541, 6541, 6541, 6541, 6541, 6541, 6541, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7048, 7048, 7048, 7048, 7048, 7048, 7048, 7048, 7048, 7048, 7048, 7048, 7048, 7048, 7048, 7048, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7549, 7549, 7549, 7549, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8898, 8898, 8898, 8898, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9737, 9739, 9741, 9743, 9745, 9747, 9749, 9751, 9753, 9755, 9757, 9759, 9761, 9763, 9765, 9767, 9769, 9771, 9773, 9775, 9777, 9779, 9781, 9783, 9785, 9787, 9789, 9791, 9793, 9795, 9797, 9799, 9801, 9803, 9805, 9807, 9809, 9811, 9813, 9815, 9817, 9819, 9821, 9823, 9825, 9827, 9829, 9831, 9833, 9836, 9839, 9841, 9843, 9846, 9848, 9850, 9852, 9854, 9856, 9858, 9860, 9862, 9869, 9871, 9873, 9875, 9877, 9879, 9881, 9883, 9892, 9894, 9896, 9898, 9900, 9904, 9906, 9908, 9910, 9912, 9914, 9916, 9918, 9920, 9922, 9925, 9927, 9929, 9931, 9933, 9935, 9937, 9939, 9941, 9943, 9945, 9947, 9949, 9953, 9955, 9957, 9959, 9961, 9963, 9965, 9967 }; static const int char_ref_start = 7623; static const int char_ref_en_valid_named_ref = 7623; #line 2469 "char_ref.rl" // clang-format on static bool consume_named_ref(struct GumboInternalParser* parser, Utf8Iterator* input, bool is_in_attribute, OneOrTwoCodepoints* output) { assert(output->first == kGumboNoChar); const char* p = utf8iterator_get_char_pointer(input); const char* pe = utf8iterator_get_end_pointer(input); const char* eof = pe; const char* te = 0; const char *ts, *start; int cs, act; // clang-format off #line 13985 "char_ref.c" { cs = char_ref_start; ts = 0; te = 0; act = 0; } #line 2484 "char_ref.rl" // Avoid unused variable warnings. (void) act; (void) ts; (void) char_ref_en_valid_named_ref; start = p; #line 14001 "char_ref.c" { int _slen; int _trans; const short *_acts; unsigned int _nacts; const char *_keys; const short *_inds; if ( p == pe ) goto _test_eof; if ( cs == 0 ) goto _out; _resume: _acts = _char_ref_actions + _char_ref_from_state_actions[cs]; _nacts = (unsigned int) *_acts++; while ( _nacts-- > 0 ) { switch ( *_acts++ ) { case 1: #line 1 "NONE" {ts = p;} break; #line 14023 "char_ref.c" } } _keys = _char_ref_trans_keys + (cs<<1); _inds = _char_ref_indicies + _char_ref_index_offsets[cs]; _slen = _char_ref_key_spans[cs]; _trans = _inds[ _slen > 0 && _keys[0] <=(*p) && (*p) <= _keys[1] ? (*p) - _keys[0] : _slen ]; _eof_trans: cs = _char_ref_trans_targs[_trans]; if ( _char_ref_trans_actions[_trans] == 0 ) goto _again; _acts = _char_ref_actions + _char_ref_trans_actions[_trans]; _nacts = (unsigned int) *_acts++; while ( _nacts-- > 0 ) { switch ( *(_acts++) ) { case 2: #line 1 "NONE" {te = p+1;} break; case 3: #line 233 "char_ref.rl" {te = p+1;{ output->first = 0xc6; {p++; goto _out; } }} break; case 4: #line 235 "char_ref.rl" {te = p+1;{ output->first = 0x26; {p++; goto _out; } }} break; case 5: #line 237 "char_ref.rl" {te = p+1;{ output->first = 0xc1; {p++; goto _out; } }} break; case 6: #line 239 "char_ref.rl" {te = p+1;{ output->first = 0x0102; {p++; goto _out; } }} break; case 7: #line 240 "char_ref.rl" {te = p+1;{ output->first = 0xc2; {p++; goto _out; } }} break; case 8: #line 242 "char_ref.rl" {te = p+1;{ output->first = 0x0410; {p++; goto _out; } }} break; case 9: #line 243 "char_ref.rl" {te = p+1;{ output->first = 0x0001d504; {p++; goto _out; } }} break; case 10: #line 244 "char_ref.rl" {te = p+1;{ output->first = 0xc0; {p++; goto _out; } }} break; case 11: #line 246 "char_ref.rl" {te = p+1;{ output->first = 0x0391; {p++; goto _out; } }} break; case 12: #line 247 "char_ref.rl" {te = p+1;{ output->first = 0x0100; {p++; goto _out; } }} break; case 13: #line 248 "char_ref.rl" {te = p+1;{ output->first = 0x2a53; {p++; goto _out; } }} break; case 14: #line 249 "char_ref.rl" {te = p+1;{ output->first = 0x0104; {p++; goto _out; } }} break; case 15: #line 250 "char_ref.rl" {te = p+1;{ output->first = 0x0001d538; {p++; goto _out; } }} break; case 16: #line 251 "char_ref.rl" {te = p+1;{ output->first = 0x2061; {p++; goto _out; } }} break; case 17: #line 252 "char_ref.rl" {te = p+1;{ output->first = 0xc5; {p++; goto _out; } }} break; case 18: #line 254 "char_ref.rl" {te = p+1;{ output->first = 0x0001d49c; {p++; goto _out; } }} break; case 19: #line 255 "char_ref.rl" {te = p+1;{ output->first = 0x2254; {p++; goto _out; } }} break; case 20: #line 256 "char_ref.rl" {te = p+1;{ output->first = 0xc3; {p++; goto _out; } }} break; case 21: #line 258 "char_ref.rl" {te = p+1;{ output->first = 0xc4; {p++; goto _out; } }} break; case 22: #line 260 "char_ref.rl" {te = p+1;{ output->first = 0x2216; {p++; goto _out; } }} break; case 23: #line 261 "char_ref.rl" {te = p+1;{ output->first = 0x2ae7; {p++; goto _out; } }} break; case 24: #line 262 "char_ref.rl" {te = p+1;{ output->first = 0x2306; {p++; goto _out; } }} break; case 25: #line 263 "char_ref.rl" {te = p+1;{ output->first = 0x0411; {p++; goto _out; } }} break; case 26: #line 264 "char_ref.rl" {te = p+1;{ output->first = 0x2235; {p++; goto _out; } }} break; case 27: #line 265 "char_ref.rl" {te = p+1;{ output->first = 0x212c; {p++; goto _out; } }} break; case 28: #line 266 "char_ref.rl" {te = p+1;{ output->first = 0x0392; {p++; goto _out; } }} break; case 29: #line 267 "char_ref.rl" {te = p+1;{ output->first = 0x0001d505; {p++; goto _out; } }} break; case 30: #line 268 "char_ref.rl" {te = p+1;{ output->first = 0x0001d539; {p++; goto _out; } }} break; case 31: #line 269 "char_ref.rl" {te = p+1;{ output->first = 0x02d8; {p++; goto _out; } }} break; case 32: #line 270 "char_ref.rl" {te = p+1;{ output->first = 0x212c; {p++; goto _out; } }} break; case 33: #line 271 "char_ref.rl" {te = p+1;{ output->first = 0x224e; {p++; goto _out; } }} break; case 34: #line 272 "char_ref.rl" {te = p+1;{ output->first = 0x0427; {p++; goto _out; } }} break; case 35: #line 273 "char_ref.rl" {te = p+1;{ output->first = 0xa9; {p++; goto _out; } }} break; case 36: #line 275 "char_ref.rl" {te = p+1;{ output->first = 0x0106; {p++; goto _out; } }} break; case 37: #line 276 "char_ref.rl" {te = p+1;{ output->first = 0x22d2; {p++; goto _out; } }} break; case 38: #line 277 "char_ref.rl" {te = p+1;{ output->first = 0x2145; {p++; goto _out; } }} break; case 39: #line 278 "char_ref.rl" {te = p+1;{ output->first = 0x212d; {p++; goto _out; } }} break; case 40: #line 279 "char_ref.rl" {te = p+1;{ output->first = 0x010c; {p++; goto _out; } }} break; case 41: #line 280 "char_ref.rl" {te = p+1;{ output->first = 0xc7; {p++; goto _out; } }} break; case 42: #line 282 "char_ref.rl" {te = p+1;{ output->first = 0x0108; {p++; goto _out; } }} break; case 43: #line 283 "char_ref.rl" {te = p+1;{ output->first = 0x2230; {p++; goto _out; } }} break; case 44: #line 284 "char_ref.rl" {te = p+1;{ output->first = 0x010a; {p++; goto _out; } }} break; case 45: #line 285 "char_ref.rl" {te = p+1;{ output->first = 0xb8; {p++; goto _out; } }} break; case 46: #line 286 "char_ref.rl" {te = p+1;{ output->first = 0xb7; {p++; goto _out; } }} break; case 47: #line 287 "char_ref.rl" {te = p+1;{ output->first = 0x212d; {p++; goto _out; } }} break; case 48: #line 288 "char_ref.rl" {te = p+1;{ output->first = 0x03a7; {p++; goto _out; } }} break; case 49: #line 289 "char_ref.rl" {te = p+1;{ output->first = 0x2299; {p++; goto _out; } }} break; case 50: #line 290 "char_ref.rl" {te = p+1;{ output->first = 0x2296; {p++; goto _out; } }} break; case 51: #line 291 "char_ref.rl" {te = p+1;{ output->first = 0x2295; {p++; goto _out; } }} break; case 52: #line 292 "char_ref.rl" {te = p+1;{ output->first = 0x2297; {p++; goto _out; } }} break; case 53: #line 293 "char_ref.rl" {te = p+1;{ output->first = 0x2232; {p++; goto _out; } }} break; case 54: #line 294 "char_ref.rl" {te = p+1;{ output->first = 0x201d; {p++; goto _out; } }} break; case 55: #line 295 "char_ref.rl" {te = p+1;{ output->first = 0x2019; {p++; goto _out; } }} break; case 56: #line 296 "char_ref.rl" {te = p+1;{ output->first = 0x2237; {p++; goto _out; } }} break; case 57: #line 297 "char_ref.rl" {te = p+1;{ output->first = 0x2a74; {p++; goto _out; } }} break; case 58: #line 298 "char_ref.rl" {te = p+1;{ output->first = 0x2261; {p++; goto _out; } }} break; case 59: #line 299 "char_ref.rl" {te = p+1;{ output->first = 0x222f; {p++; goto _out; } }} break; case 60: #line 300 "char_ref.rl" {te = p+1;{ output->first = 0x222e; {p++; goto _out; } }} break; case 61: #line 301 "char_ref.rl" {te = p+1;{ output->first = 0x2102; {p++; goto _out; } }} break; case 62: #line 302 "char_ref.rl" {te = p+1;{ output->first = 0x2210; {p++; goto _out; } }} break; case 63: #line 303 "char_ref.rl" {te = p+1;{ output->first = 0x2233; {p++; goto _out; } }} break; case 64: #line 304 "char_ref.rl" {te = p+1;{ output->first = 0x2a2f; {p++; goto _out; } }} break; case 65: #line 305 "char_ref.rl" {te = p+1;{ output->first = 0x0001d49e; {p++; goto _out; } }} break; case 66: #line 306 "char_ref.rl" {te = p+1;{ output->first = 0x22d3; {p++; goto _out; } }} break; case 67: #line 307 "char_ref.rl" {te = p+1;{ output->first = 0x224d; {p++; goto _out; } }} break; case 68: #line 308 "char_ref.rl" {te = p+1;{ output->first = 0x2145; {p++; goto _out; } }} break; case 69: #line 309 "char_ref.rl" {te = p+1;{ output->first = 0x2911; {p++; goto _out; } }} break; case 70: #line 310 "char_ref.rl" {te = p+1;{ output->first = 0x0402; {p++; goto _out; } }} break; case 71: #line 311 "char_ref.rl" {te = p+1;{ output->first = 0x0405; {p++; goto _out; } }} break; case 72: #line 312 "char_ref.rl" {te = p+1;{ output->first = 0x040f; {p++; goto _out; } }} break; case 73: #line 313 "char_ref.rl" {te = p+1;{ output->first = 0x2021; {p++; goto _out; } }} break; case 74: #line 314 "char_ref.rl" {te = p+1;{ output->first = 0x21a1; {p++; goto _out; } }} break; case 75: #line 315 "char_ref.rl" {te = p+1;{ output->first = 0x2ae4; {p++; goto _out; } }} break; case 76: #line 316 "char_ref.rl" {te = p+1;{ output->first = 0x010e; {p++; goto _out; } }} break; case 77: #line 317 "char_ref.rl" {te = p+1;{ output->first = 0x0414; {p++; goto _out; } }} break; case 78: #line 318 "char_ref.rl" {te = p+1;{ output->first = 0x2207; {p++; goto _out; } }} break; case 79: #line 319 "char_ref.rl" {te = p+1;{ output->first = 0x0394; {p++; goto _out; } }} break; case 80: #line 320 "char_ref.rl" {te = p+1;{ output->first = 0x0001d507; {p++; goto _out; } }} break; case 81: #line 321 "char_ref.rl" {te = p+1;{ output->first = 0xb4; {p++; goto _out; } }} break; case 82: #line 322 "char_ref.rl" {te = p+1;{ output->first = 0x02d9; {p++; goto _out; } }} break; case 83: #line 323 "char_ref.rl" {te = p+1;{ output->first = 0x02dd; {p++; goto _out; } }} break; case 84: #line 324 "char_ref.rl" {te = p+1;{ output->first = 0x60; {p++; goto _out; } }} break; case 85: #line 325 "char_ref.rl" {te = p+1;{ output->first = 0x02dc; {p++; goto _out; } }} break; case 86: #line 326 "char_ref.rl" {te = p+1;{ output->first = 0x22c4; {p++; goto _out; } }} break; case 87: #line 327 "char_ref.rl" {te = p+1;{ output->first = 0x2146; {p++; goto _out; } }} break; case 88: #line 328 "char_ref.rl" {te = p+1;{ output->first = 0x0001d53b; {p++; goto _out; } }} break; case 89: #line 329 "char_ref.rl" {te = p+1;{ output->first = 0xa8; {p++; goto _out; } }} break; case 90: #line 330 "char_ref.rl" {te = p+1;{ output->first = 0x20dc; {p++; goto _out; } }} break; case 91: #line 331 "char_ref.rl" {te = p+1;{ output->first = 0x2250; {p++; goto _out; } }} break; case 92: #line 332 "char_ref.rl" {te = p+1;{ output->first = 0x222f; {p++; goto _out; } }} break; case 93: #line 333 "char_ref.rl" {te = p+1;{ output->first = 0xa8; {p++; goto _out; } }} break; case 94: #line 334 "char_ref.rl" {te = p+1;{ output->first = 0x21d3; {p++; goto _out; } }} break; case 95: #line 335 "char_ref.rl" {te = p+1;{ output->first = 0x21d0; {p++; goto _out; } }} break; case 96: #line 336 "char_ref.rl" {te = p+1;{ output->first = 0x21d4; {p++; goto _out; } }} break; case 97: #line 337 "char_ref.rl" {te = p+1;{ output->first = 0x2ae4; {p++; goto _out; } }} break; case 98: #line 338 "char_ref.rl" {te = p+1;{ output->first = 0x27f8; {p++; goto _out; } }} break; case 99: #line 339 "char_ref.rl" {te = p+1;{ output->first = 0x27fa; {p++; goto _out; } }} break; case 100: #line 340 "char_ref.rl" {te = p+1;{ output->first = 0x27f9; {p++; goto _out; } }} break; case 101: #line 341 "char_ref.rl" {te = p+1;{ output->first = 0x21d2; {p++; goto _out; } }} break; case 102: #line 342 "char_ref.rl" {te = p+1;{ output->first = 0x22a8; {p++; goto _out; } }} break; case 103: #line 343 "char_ref.rl" {te = p+1;{ output->first = 0x21d1; {p++; goto _out; } }} break; case 104: #line 344 "char_ref.rl" {te = p+1;{ output->first = 0x21d5; {p++; goto _out; } }} break; case 105: #line 345 "char_ref.rl" {te = p+1;{ output->first = 0x2225; {p++; goto _out; } }} break; case 106: #line 346 "char_ref.rl" {te = p+1;{ output->first = 0x2193; {p++; goto _out; } }} break; case 107: #line 347 "char_ref.rl" {te = p+1;{ output->first = 0x2913; {p++; goto _out; } }} break; case 108: #line 348 "char_ref.rl" {te = p+1;{ output->first = 0x21f5; {p++; goto _out; } }} break; case 109: #line 349 "char_ref.rl" {te = p+1;{ output->first = 0x0311; {p++; goto _out; } }} break; case 110: #line 350 "char_ref.rl" {te = p+1;{ output->first = 0x2950; {p++; goto _out; } }} break; case 111: #line 351 "char_ref.rl" {te = p+1;{ output->first = 0x295e; {p++; goto _out; } }} break; case 112: #line 352 "char_ref.rl" {te = p+1;{ output->first = 0x21bd; {p++; goto _out; } }} break; case 113: #line 353 "char_ref.rl" {te = p+1;{ output->first = 0x2956; {p++; goto _out; } }} break; case 114: #line 354 "char_ref.rl" {te = p+1;{ output->first = 0x295f; {p++; goto _out; } }} break; case 115: #line 355 "char_ref.rl" {te = p+1;{ output->first = 0x21c1; {p++; goto _out; } }} break; case 116: #line 356 "char_ref.rl" {te = p+1;{ output->first = 0x2957; {p++; goto _out; } }} break; case 117: #line 357 "char_ref.rl" {te = p+1;{ output->first = 0x22a4; {p++; goto _out; } }} break; case 118: #line 358 "char_ref.rl" {te = p+1;{ output->first = 0x21a7; {p++; goto _out; } }} break; case 119: #line 359 "char_ref.rl" {te = p+1;{ output->first = 0x21d3; {p++; goto _out; } }} break; case 120: #line 360 "char_ref.rl" {te = p+1;{ output->first = 0x0001d49f; {p++; goto _out; } }} break; case 121: #line 361 "char_ref.rl" {te = p+1;{ output->first = 0x0110; {p++; goto _out; } }} break; case 122: #line 362 "char_ref.rl" {te = p+1;{ output->first = 0x014a; {p++; goto _out; } }} break; case 123: #line 363 "char_ref.rl" {te = p+1;{ output->first = 0xd0; {p++; goto _out; } }} break; case 124: #line 365 "char_ref.rl" {te = p+1;{ output->first = 0xc9; {p++; goto _out; } }} break; case 125: #line 367 "char_ref.rl" {te = p+1;{ output->first = 0x011a; {p++; goto _out; } }} break; case 126: #line 368 "char_ref.rl" {te = p+1;{ output->first = 0xca; {p++; goto _out; } }} break; case 127: #line 370 "char_ref.rl" {te = p+1;{ output->first = 0x042d; {p++; goto _out; } }} break; case 128: #line 371 "char_ref.rl" {te = p+1;{ output->first = 0x0116; {p++; goto _out; } }} break; case 129: #line 372 "char_ref.rl" {te = p+1;{ output->first = 0x0001d508; {p++; goto _out; } }} break; case 130: #line 373 "char_ref.rl" {te = p+1;{ output->first = 0xc8; {p++; goto _out; } }} break; case 131: #line 375 "char_ref.rl" {te = p+1;{ output->first = 0x2208; {p++; goto _out; } }} break; case 132: #line 376 "char_ref.rl" {te = p+1;{ output->first = 0x0112; {p++; goto _out; } }} break; case 133: #line 377 "char_ref.rl" {te = p+1;{ output->first = 0x25fb; {p++; goto _out; } }} break; case 134: #line 378 "char_ref.rl" {te = p+1;{ output->first = 0x25ab; {p++; goto _out; } }} break; case 135: #line 379 "char_ref.rl" {te = p+1;{ output->first = 0x0118; {p++; goto _out; } }} break; case 136: #line 380 "char_ref.rl" {te = p+1;{ output->first = 0x0001d53c; {p++; goto _out; } }} break; case 137: #line 381 "char_ref.rl" {te = p+1;{ output->first = 0x0395; {p++; goto _out; } }} break; case 138: #line 382 "char_ref.rl" {te = p+1;{ output->first = 0x2a75; {p++; goto _out; } }} break; case 139: #line 383 "char_ref.rl" {te = p+1;{ output->first = 0x2242; {p++; goto _out; } }} break; case 140: #line 384 "char_ref.rl" {te = p+1;{ output->first = 0x21cc; {p++; goto _out; } }} break; case 141: #line 385 "char_ref.rl" {te = p+1;{ output->first = 0x2130; {p++; goto _out; } }} break; case 142: #line 386 "char_ref.rl" {te = p+1;{ output->first = 0x2a73; {p++; goto _out; } }} break; case 143: #line 387 "char_ref.rl" {te = p+1;{ output->first = 0x0397; {p++; goto _out; } }} break; case 144: #line 388 "char_ref.rl" {te = p+1;{ output->first = 0xcb; {p++; goto _out; } }} break; case 145: #line 390 "char_ref.rl" {te = p+1;{ output->first = 0x2203; {p++; goto _out; } }} break; case 146: #line 391 "char_ref.rl" {te = p+1;{ output->first = 0x2147; {p++; goto _out; } }} break; case 147: #line 392 "char_ref.rl" {te = p+1;{ output->first = 0x0424; {p++; goto _out; } }} break; case 148: #line 393 "char_ref.rl" {te = p+1;{ output->first = 0x0001d509; {p++; goto _out; } }} break; case 149: #line 394 "char_ref.rl" {te = p+1;{ output->first = 0x25fc; {p++; goto _out; } }} break; case 150: #line 395 "char_ref.rl" {te = p+1;{ output->first = 0x25aa; {p++; goto _out; } }} break; case 151: #line 396 "char_ref.rl" {te = p+1;{ output->first = 0x0001d53d; {p++; goto _out; } }} break; case 152: #line 397 "char_ref.rl" {te = p+1;{ output->first = 0x2200; {p++; goto _out; } }} break; case 153: #line 398 "char_ref.rl" {te = p+1;{ output->first = 0x2131; {p++; goto _out; } }} break; case 154: #line 399 "char_ref.rl" {te = p+1;{ output->first = 0x2131; {p++; goto _out; } }} break; case 155: #line 400 "char_ref.rl" {te = p+1;{ output->first = 0x0403; {p++; goto _out; } }} break; case 156: #line 401 "char_ref.rl" {te = p+1;{ output->first = 0x3e; {p++; goto _out; } }} break; case 157: #line 403 "char_ref.rl" {te = p+1;{ output->first = 0x0393; {p++; goto _out; } }} break; case 158: #line 404 "char_ref.rl" {te = p+1;{ output->first = 0x03dc; {p++; goto _out; } }} break; case 159: #line 405 "char_ref.rl" {te = p+1;{ output->first = 0x011e; {p++; goto _out; } }} break; case 160: #line 406 "char_ref.rl" {te = p+1;{ output->first = 0x0122; {p++; goto _out; } }} break; case 161: #line 407 "char_ref.rl" {te = p+1;{ output->first = 0x011c; {p++; goto _out; } }} break; case 162: #line 408 "char_ref.rl" {te = p+1;{ output->first = 0x0413; {p++; goto _out; } }} break; case 163: #line 409 "char_ref.rl" {te = p+1;{ output->first = 0x0120; {p++; goto _out; } }} break; case 164: #line 410 "char_ref.rl" {te = p+1;{ output->first = 0x0001d50a; {p++; goto _out; } }} break; case 165: #line 411 "char_ref.rl" {te = p+1;{ output->first = 0x22d9; {p++; goto _out; } }} break; case 166: #line 412 "char_ref.rl" {te = p+1;{ output->first = 0x0001d53e; {p++; goto _out; } }} break; case 167: #line 413 "char_ref.rl" {te = p+1;{ output->first = 0x2265; {p++; goto _out; } }} break; case 168: #line 414 "char_ref.rl" {te = p+1;{ output->first = 0x22db; {p++; goto _out; } }} break; case 169: #line 415 "char_ref.rl" {te = p+1;{ output->first = 0x2267; {p++; goto _out; } }} break; case 170: #line 416 "char_ref.rl" {te = p+1;{ output->first = 0x2aa2; {p++; goto _out; } }} break; case 171: #line 417 "char_ref.rl" {te = p+1;{ output->first = 0x2277; {p++; goto _out; } }} break; case 172: #line 418 "char_ref.rl" {te = p+1;{ output->first = 0x2a7e; {p++; goto _out; } }} break; case 173: #line 419 "char_ref.rl" {te = p+1;{ output->first = 0x2273; {p++; goto _out; } }} break; case 174: #line 420 "char_ref.rl" {te = p+1;{ output->first = 0x0001d4a2; {p++; goto _out; } }} break; case 175: #line 421 "char_ref.rl" {te = p+1;{ output->first = 0x226b; {p++; goto _out; } }} break; case 176: #line 422 "char_ref.rl" {te = p+1;{ output->first = 0x042a; {p++; goto _out; } }} break; case 177: #line 423 "char_ref.rl" {te = p+1;{ output->first = 0x02c7; {p++; goto _out; } }} break; case 178: #line 424 "char_ref.rl" {te = p+1;{ output->first = 0x5e; {p++; goto _out; } }} break; case 179: #line 425 "char_ref.rl" {te = p+1;{ output->first = 0x0124; {p++; goto _out; } }} break; case 180: #line 426 "char_ref.rl" {te = p+1;{ output->first = 0x210c; {p++; goto _out; } }} break; case 181: #line 427 "char_ref.rl" {te = p+1;{ output->first = 0x210b; {p++; goto _out; } }} break; case 182: #line 428 "char_ref.rl" {te = p+1;{ output->first = 0x210d; {p++; goto _out; } }} break; case 183: #line 429 "char_ref.rl" {te = p+1;{ output->first = 0x2500; {p++; goto _out; } }} break; case 184: #line 430 "char_ref.rl" {te = p+1;{ output->first = 0x210b; {p++; goto _out; } }} break; case 185: #line 431 "char_ref.rl" {te = p+1;{ output->first = 0x0126; {p++; goto _out; } }} break; case 186: #line 432 "char_ref.rl" {te = p+1;{ output->first = 0x224e; {p++; goto _out; } }} break; case 187: #line 433 "char_ref.rl" {te = p+1;{ output->first = 0x224f; {p++; goto _out; } }} break; case 188: #line 434 "char_ref.rl" {te = p+1;{ output->first = 0x0415; {p++; goto _out; } }} break; case 189: #line 435 "char_ref.rl" {te = p+1;{ output->first = 0x0132; {p++; goto _out; } }} break; case 190: #line 436 "char_ref.rl" {te = p+1;{ output->first = 0x0401; {p++; goto _out; } }} break; case 191: #line 437 "char_ref.rl" {te = p+1;{ output->first = 0xcd; {p++; goto _out; } }} break; case 192: #line 439 "char_ref.rl" {te = p+1;{ output->first = 0xce; {p++; goto _out; } }} break; case 193: #line 441 "char_ref.rl" {te = p+1;{ output->first = 0x0418; {p++; goto _out; } }} break; case 194: #line 442 "char_ref.rl" {te = p+1;{ output->first = 0x0130; {p++; goto _out; } }} break; case 195: #line 443 "char_ref.rl" {te = p+1;{ output->first = 0x2111; {p++; goto _out; } }} break; case 196: #line 444 "char_ref.rl" {te = p+1;{ output->first = 0xcc; {p++; goto _out; } }} break; case 197: #line 446 "char_ref.rl" {te = p+1;{ output->first = 0x2111; {p++; goto _out; } }} break; case 198: #line 447 "char_ref.rl" {te = p+1;{ output->first = 0x012a; {p++; goto _out; } }} break; case 199: #line 448 "char_ref.rl" {te = p+1;{ output->first = 0x2148; {p++; goto _out; } }} break; case 200: #line 449 "char_ref.rl" {te = p+1;{ output->first = 0x21d2; {p++; goto _out; } }} break; case 201: #line 450 "char_ref.rl" {te = p+1;{ output->first = 0x222c; {p++; goto _out; } }} break; case 202: #line 451 "char_ref.rl" {te = p+1;{ output->first = 0x222b; {p++; goto _out; } }} break; case 203: #line 452 "char_ref.rl" {te = p+1;{ output->first = 0x22c2; {p++; goto _out; } }} break; case 204: #line 453 "char_ref.rl" {te = p+1;{ output->first = 0x2063; {p++; goto _out; } }} break; case 205: #line 454 "char_ref.rl" {te = p+1;{ output->first = 0x2062; {p++; goto _out; } }} break; case 206: #line 455 "char_ref.rl" {te = p+1;{ output->first = 0x012e; {p++; goto _out; } }} break; case 207: #line 456 "char_ref.rl" {te = p+1;{ output->first = 0x0001d540; {p++; goto _out; } }} break; case 208: #line 457 "char_ref.rl" {te = p+1;{ output->first = 0x0399; {p++; goto _out; } }} break; case 209: #line 458 "char_ref.rl" {te = p+1;{ output->first = 0x2110; {p++; goto _out; } }} break; case 210: #line 459 "char_ref.rl" {te = p+1;{ output->first = 0x0128; {p++; goto _out; } }} break; case 211: #line 460 "char_ref.rl" {te = p+1;{ output->first = 0x0406; {p++; goto _out; } }} break; case 212: #line 461 "char_ref.rl" {te = p+1;{ output->first = 0xcf; {p++; goto _out; } }} break; case 213: #line 463 "char_ref.rl" {te = p+1;{ output->first = 0x0134; {p++; goto _out; } }} break; case 214: #line 464 "char_ref.rl" {te = p+1;{ output->first = 0x0419; {p++; goto _out; } }} break; case 215: #line 465 "char_ref.rl" {te = p+1;{ output->first = 0x0001d50d; {p++; goto _out; } }} break; case 216: #line 466 "char_ref.rl" {te = p+1;{ output->first = 0x0001d541; {p++; goto _out; } }} break; case 217: #line 467 "char_ref.rl" {te = p+1;{ output->first = 0x0001d4a5; {p++; goto _out; } }} break; case 218: #line 468 "char_ref.rl" {te = p+1;{ output->first = 0x0408; {p++; goto _out; } }} break; case 219: #line 469 "char_ref.rl" {te = p+1;{ output->first = 0x0404; {p++; goto _out; } }} break; case 220: #line 470 "char_ref.rl" {te = p+1;{ output->first = 0x0425; {p++; goto _out; } }} break; case 221: #line 471 "char_ref.rl" {te = p+1;{ output->first = 0x040c; {p++; goto _out; } }} break; case 222: #line 472 "char_ref.rl" {te = p+1;{ output->first = 0x039a; {p++; goto _out; } }} break; case 223: #line 473 "char_ref.rl" {te = p+1;{ output->first = 0x0136; {p++; goto _out; } }} break; case 224: #line 474 "char_ref.rl" {te = p+1;{ output->first = 0x041a; {p++; goto _out; } }} break; case 225: #line 475 "char_ref.rl" {te = p+1;{ output->first = 0x0001d50e; {p++; goto _out; } }} break; case 226: #line 476 "char_ref.rl" {te = p+1;{ output->first = 0x0001d542; {p++; goto _out; } }} break; case 227: #line 477 "char_ref.rl" {te = p+1;{ output->first = 0x0001d4a6; {p++; goto _out; } }} break; case 228: #line 478 "char_ref.rl" {te = p+1;{ output->first = 0x0409; {p++; goto _out; } }} break; case 229: #line 479 "char_ref.rl" {te = p+1;{ output->first = 0x3c; {p++; goto _out; } }} break; case 230: #line 481 "char_ref.rl" {te = p+1;{ output->first = 0x0139; {p++; goto _out; } }} break; case 231: #line 482 "char_ref.rl" {te = p+1;{ output->first = 0x039b; {p++; goto _out; } }} break; case 232: #line 483 "char_ref.rl" {te = p+1;{ output->first = 0x27ea; {p++; goto _out; } }} break; case 233: #line 484 "char_ref.rl" {te = p+1;{ output->first = 0x2112; {p++; goto _out; } }} break; case 234: #line 485 "char_ref.rl" {te = p+1;{ output->first = 0x219e; {p++; goto _out; } }} break; case 235: #line 486 "char_ref.rl" {te = p+1;{ output->first = 0x013d; {p++; goto _out; } }} break; case 236: #line 487 "char_ref.rl" {te = p+1;{ output->first = 0x013b; {p++; goto _out; } }} break; case 237: #line 488 "char_ref.rl" {te = p+1;{ output->first = 0x041b; {p++; goto _out; } }} break; case 238: #line 489 "char_ref.rl" {te = p+1;{ output->first = 0x27e8; {p++; goto _out; } }} break; case 239: #line 490 "char_ref.rl" {te = p+1;{ output->first = 0x2190; {p++; goto _out; } }} break; case 240: #line 491 "char_ref.rl" {te = p+1;{ output->first = 0x21e4; {p++; goto _out; } }} break; case 241: #line 492 "char_ref.rl" {te = p+1;{ output->first = 0x21c6; {p++; goto _out; } }} break; case 242: #line 493 "char_ref.rl" {te = p+1;{ output->first = 0x2308; {p++; goto _out; } }} break; case 243: #line 494 "char_ref.rl" {te = p+1;{ output->first = 0x27e6; {p++; goto _out; } }} break; case 244: #line 495 "char_ref.rl" {te = p+1;{ output->first = 0x2961; {p++; goto _out; } }} break; case 245: #line 496 "char_ref.rl" {te = p+1;{ output->first = 0x21c3; {p++; goto _out; } }} break; case 246: #line 497 "char_ref.rl" {te = p+1;{ output->first = 0x2959; {p++; goto _out; } }} break; case 247: #line 498 "char_ref.rl" {te = p+1;{ output->first = 0x230a; {p++; goto _out; } }} break; case 248: #line 499 "char_ref.rl" {te = p+1;{ output->first = 0x2194; {p++; goto _out; } }} break; case 249: #line 500 "char_ref.rl" {te = p+1;{ output->first = 0x294e; {p++; goto _out; } }} break; case 250: #line 501 "char_ref.rl" {te = p+1;{ output->first = 0x22a3; {p++; goto _out; } }} break; case 251: #line 502 "char_ref.rl" {te = p+1;{ output->first = 0x21a4; {p++; goto _out; } }} break; case 252: #line 503 "char_ref.rl" {te = p+1;{ output->first = 0x295a; {p++; goto _out; } }} break; case 253: #line 504 "char_ref.rl" {te = p+1;{ output->first = 0x22b2; {p++; goto _out; } }} break; case 254: #line 505 "char_ref.rl" {te = p+1;{ output->first = 0x29cf; {p++; goto _out; } }} break; case 255: #line 506 "char_ref.rl" {te = p+1;{ output->first = 0x22b4; {p++; goto _out; } }} break; case 256: #line 507 "char_ref.rl" {te = p+1;{ output->first = 0x2951; {p++; goto _out; } }} break; case 257: #line 508 "char_ref.rl" {te = p+1;{ output->first = 0x2960; {p++; goto _out; } }} break; case 258: #line 509 "char_ref.rl" {te = p+1;{ output->first = 0x21bf; {p++; goto _out; } }} break; case 259: #line 510 "char_ref.rl" {te = p+1;{ output->first = 0x2958; {p++; goto _out; } }} break; case 260: #line 511 "char_ref.rl" {te = p+1;{ output->first = 0x21bc; {p++; goto _out; } }} break; case 261: #line 512 "char_ref.rl" {te = p+1;{ output->first = 0x2952; {p++; goto _out; } }} break; case 262: #line 513 "char_ref.rl" {te = p+1;{ output->first = 0x21d0; {p++; goto _out; } }} break; case 263: #line 514 "char_ref.rl" {te = p+1;{ output->first = 0x21d4; {p++; goto _out; } }} break; case 264: #line 515 "char_ref.rl" {te = p+1;{ output->first = 0x22da; {p++; goto _out; } }} break; case 265: #line 516 "char_ref.rl" {te = p+1;{ output->first = 0x2266; {p++; goto _out; } }} break; case 266: #line 517 "char_ref.rl" {te = p+1;{ output->first = 0x2276; {p++; goto _out; } }} break; case 267: #line 518 "char_ref.rl" {te = p+1;{ output->first = 0x2aa1; {p++; goto _out; } }} break; case 268: #line 519 "char_ref.rl" {te = p+1;{ output->first = 0x2a7d; {p++; goto _out; } }} break; case 269: #line 520 "char_ref.rl" {te = p+1;{ output->first = 0x2272; {p++; goto _out; } }} break; case 270: #line 521 "char_ref.rl" {te = p+1;{ output->first = 0x0001d50f; {p++; goto _out; } }} break; case 271: #line 522 "char_ref.rl" {te = p+1;{ output->first = 0x22d8; {p++; goto _out; } }} break; case 272: #line 523 "char_ref.rl" {te = p+1;{ output->first = 0x21da; {p++; goto _out; } }} break; case 273: #line 524 "char_ref.rl" {te = p+1;{ output->first = 0x013f; {p++; goto _out; } }} break; case 274: #line 525 "char_ref.rl" {te = p+1;{ output->first = 0x27f5; {p++; goto _out; } }} break; case 275: #line 526 "char_ref.rl" {te = p+1;{ output->first = 0x27f7; {p++; goto _out; } }} break; case 276: #line 527 "char_ref.rl" {te = p+1;{ output->first = 0x27f6; {p++; goto _out; } }} break; case 277: #line 528 "char_ref.rl" {te = p+1;{ output->first = 0x27f8; {p++; goto _out; } }} break; case 278: #line 529 "char_ref.rl" {te = p+1;{ output->first = 0x27fa; {p++; goto _out; } }} break; case 279: #line 530 "char_ref.rl" {te = p+1;{ output->first = 0x27f9; {p++; goto _out; } }} break; case 280: #line 531 "char_ref.rl" {te = p+1;{ output->first = 0x0001d543; {p++; goto _out; } }} break; case 281: #line 532 "char_ref.rl" {te = p+1;{ output->first = 0x2199; {p++; goto _out; } }} break; case 282: #line 533 "char_ref.rl" {te = p+1;{ output->first = 0x2198; {p++; goto _out; } }} break; case 283: #line 534 "char_ref.rl" {te = p+1;{ output->first = 0x2112; {p++; goto _out; } }} break; case 284: #line 535 "char_ref.rl" {te = p+1;{ output->first = 0x21b0; {p++; goto _out; } }} break; case 285: #line 536 "char_ref.rl" {te = p+1;{ output->first = 0x0141; {p++; goto _out; } }} break; case 286: #line 537 "char_ref.rl" {te = p+1;{ output->first = 0x226a; {p++; goto _out; } }} break; case 287: #line 538 "char_ref.rl" {te = p+1;{ output->first = 0x2905; {p++; goto _out; } }} break; case 288: #line 539 "char_ref.rl" {te = p+1;{ output->first = 0x041c; {p++; goto _out; } }} break; case 289: #line 540 "char_ref.rl" {te = p+1;{ output->first = 0x205f; {p++; goto _out; } }} break; case 290: #line 541 "char_ref.rl" {te = p+1;{ output->first = 0x2133; {p++; goto _out; } }} break; case 291: #line 542 "char_ref.rl" {te = p+1;{ output->first = 0x0001d510; {p++; goto _out; } }} break; case 292: #line 543 "char_ref.rl" {te = p+1;{ output->first = 0x2213; {p++; goto _out; } }} break; case 293: #line 544 "char_ref.rl" {te = p+1;{ output->first = 0x0001d544; {p++; goto _out; } }} break; case 294: #line 545 "char_ref.rl" {te = p+1;{ output->first = 0x2133; {p++; goto _out; } }} break; case 295: #line 546 "char_ref.rl" {te = p+1;{ output->first = 0x039c; {p++; goto _out; } }} break; case 296: #line 547 "char_ref.rl" {te = p+1;{ output->first = 0x040a; {p++; goto _out; } }} break; case 297: #line 548 "char_ref.rl" {te = p+1;{ output->first = 0x0143; {p++; goto _out; } }} break; case 298: #line 549 "char_ref.rl" {te = p+1;{ output->first = 0x0147; {p++; goto _out; } }} break; case 299: #line 550 "char_ref.rl" {te = p+1;{ output->first = 0x0145; {p++; goto _out; } }} break; case 300: #line 551 "char_ref.rl" {te = p+1;{ output->first = 0x041d; {p++; goto _out; } }} break; case 301: #line 552 "char_ref.rl" {te = p+1;{ output->first = 0x200b; {p++; goto _out; } }} break; case 302: #line 553 "char_ref.rl" {te = p+1;{ output->first = 0x200b; {p++; goto _out; } }} break; case 303: #line 554 "char_ref.rl" {te = p+1;{ output->first = 0x200b; {p++; goto _out; } }} break; case 304: #line 555 "char_ref.rl" {te = p+1;{ output->first = 0x200b; {p++; goto _out; } }} break; case 305: #line 556 "char_ref.rl" {te = p+1;{ output->first = 0x226b; {p++; goto _out; } }} break; case 306: #line 557 "char_ref.rl" {te = p+1;{ output->first = 0x226a; {p++; goto _out; } }} break; case 307: #line 558 "char_ref.rl" {te = p+1;{ output->first = 0x0a; {p++; goto _out; } }} break; case 308: #line 559 "char_ref.rl" {te = p+1;{ output->first = 0x0001d511; {p++; goto _out; } }} break; case 309: #line 560 "char_ref.rl" {te = p+1;{ output->first = 0x2060; {p++; goto _out; } }} break; case 310: #line 561 "char_ref.rl" {te = p+1;{ output->first = 0xa0; {p++; goto _out; } }} break; case 311: #line 562 "char_ref.rl" {te = p+1;{ output->first = 0x2115; {p++; goto _out; } }} break; case 312: #line 563 "char_ref.rl" {te = p+1;{ output->first = 0x2aec; {p++; goto _out; } }} break; case 313: #line 564 "char_ref.rl" {te = p+1;{ output->first = 0x2262; {p++; goto _out; } }} break; case 314: #line 565 "char_ref.rl" {te = p+1;{ output->first = 0x226d; {p++; goto _out; } }} break; case 315: #line 566 "char_ref.rl" {te = p+1;{ output->first = 0x2226; {p++; goto _out; } }} break; case 316: #line 567 "char_ref.rl" {te = p+1;{ output->first = 0x2209; {p++; goto _out; } }} break; case 317: #line 568 "char_ref.rl" {te = p+1;{ output->first = 0x2260; {p++; goto _out; } }} break; case 318: #line 569 "char_ref.rl" {te = p+1;{ output->first = 0x2242; output->second = 0x0338; {p++; goto _out; } }} break; case 319: #line 570 "char_ref.rl" {te = p+1;{ output->first = 0x2204; {p++; goto _out; } }} break; case 320: #line 571 "char_ref.rl" {te = p+1;{ output->first = 0x226f; {p++; goto _out; } }} break; case 321: #line 572 "char_ref.rl" {te = p+1;{ output->first = 0x2271; {p++; goto _out; } }} break; case 322: #line 573 "char_ref.rl" {te = p+1;{ output->first = 0x2267; output->second = 0x0338; {p++; goto _out; } }} break; case 323: #line 574 "char_ref.rl" {te = p+1;{ output->first = 0x226b; output->second = 0x0338; {p++; goto _out; } }} break; case 324: #line 575 "char_ref.rl" {te = p+1;{ output->first = 0x2279; {p++; goto _out; } }} break; case 325: #line 576 "char_ref.rl" {te = p+1;{ output->first = 0x2a7e; output->second = 0x0338; {p++; goto _out; } }} break; case 326: #line 577 "char_ref.rl" {te = p+1;{ output->first = 0x2275; {p++; goto _out; } }} break; case 327: #line 578 "char_ref.rl" {te = p+1;{ output->first = 0x224e; output->second = 0x0338; {p++; goto _out; } }} break; case 328: #line 579 "char_ref.rl" {te = p+1;{ output->first = 0x224f; output->second = 0x0338; {p++; goto _out; } }} break; case 329: #line 580 "char_ref.rl" {te = p+1;{ output->first = 0x22ea; {p++; goto _out; } }} break; case 330: #line 581 "char_ref.rl" {te = p+1;{ output->first = 0x29cf; output->second = 0x0338; {p++; goto _out; } }} break; case 331: #line 582 "char_ref.rl" {te = p+1;{ output->first = 0x22ec; {p++; goto _out; } }} break; case 332: #line 583 "char_ref.rl" {te = p+1;{ output->first = 0x226e; {p++; goto _out; } }} break; case 333: #line 584 "char_ref.rl" {te = p+1;{ output->first = 0x2270; {p++; goto _out; } }} break; case 334: #line 585 "char_ref.rl" {te = p+1;{ output->first = 0x2278; {p++; goto _out; } }} break; case 335: #line 586 "char_ref.rl" {te = p+1;{ output->first = 0x226a; output->second = 0x0338; {p++; goto _out; } }} break; case 336: #line 587 "char_ref.rl" {te = p+1;{ output->first = 0x2a7d; output->second = 0x0338; {p++; goto _out; } }} break; case 337: #line 588 "char_ref.rl" {te = p+1;{ output->first = 0x2274; {p++; goto _out; } }} break; case 338: #line 589 "char_ref.rl" {te = p+1;{ output->first = 0x2aa2; output->second = 0x0338; {p++; goto _out; } }} break; case 339: #line 590 "char_ref.rl" {te = p+1;{ output->first = 0x2aa1; output->second = 0x0338; {p++; goto _out; } }} break; case 340: #line 591 "char_ref.rl" {te = p+1;{ output->first = 0x2280; {p++; goto _out; } }} break; case 341: #line 592 "char_ref.rl" {te = p+1;{ output->first = 0x2aaf; output->second = 0x0338; {p++; goto _out; } }} break; case 342: #line 593 "char_ref.rl" {te = p+1;{ output->first = 0x22e0; {p++; goto _out; } }} break; case 343: #line 594 "char_ref.rl" {te = p+1;{ output->first = 0x220c; {p++; goto _out; } }} break; case 344: #line 595 "char_ref.rl" {te = p+1;{ output->first = 0x22eb; {p++; goto _out; } }} break; case 345: #line 596 "char_ref.rl" {te = p+1;{ output->first = 0x29d0; output->second = 0x0338; {p++; goto _out; } }} break; case 346: #line 597 "char_ref.rl" {te = p+1;{ output->first = 0x22ed; {p++; goto _out; } }} break; case 347: #line 598 "char_ref.rl" {te = p+1;{ output->first = 0x228f; output->second = 0x0338; {p++; goto _out; } }} break; case 348: #line 599 "char_ref.rl" {te = p+1;{ output->first = 0x22e2; {p++; goto _out; } }} break; case 349: #line 600 "char_ref.rl" {te = p+1;{ output->first = 0x2290; output->second = 0x0338; {p++; goto _out; } }} break; case 350: #line 601 "char_ref.rl" {te = p+1;{ output->first = 0x22e3; {p++; goto _out; } }} break; case 351: #line 602 "char_ref.rl" {te = p+1;{ output->first = 0x2282; output->second = 0x20d2; {p++; goto _out; } }} break; case 352: #line 603 "char_ref.rl" {te = p+1;{ output->first = 0x2288; {p++; goto _out; } }} break; case 353: #line 604 "char_ref.rl" {te = p+1;{ output->first = 0x2281; {p++; goto _out; } }} break; case 354: #line 605 "char_ref.rl" {te = p+1;{ output->first = 0x2ab0; output->second = 0x0338; {p++; goto _out; } }} break; case 355: #line 606 "char_ref.rl" {te = p+1;{ output->first = 0x22e1; {p++; goto _out; } }} break; case 356: #line 607 "char_ref.rl" {te = p+1;{ output->first = 0x227f; output->second = 0x0338; {p++; goto _out; } }} break; case 357: #line 608 "char_ref.rl" {te = p+1;{ output->first = 0x2283; output->second = 0x20d2; {p++; goto _out; } }} break; case 358: #line 609 "char_ref.rl" {te = p+1;{ output->first = 0x2289; {p++; goto _out; } }} break; case 359: #line 610 "char_ref.rl" {te = p+1;{ output->first = 0x2241; {p++; goto _out; } }} break; case 360: #line 611 "char_ref.rl" {te = p+1;{ output->first = 0x2244; {p++; goto _out; } }} break; case 361: #line 612 "char_ref.rl" {te = p+1;{ output->first = 0x2247; {p++; goto _out; } }} break; case 362: #line 613 "char_ref.rl" {te = p+1;{ output->first = 0x2249; {p++; goto _out; } }} break; case 363: #line 614 "char_ref.rl" {te = p+1;{ output->first = 0x2224; {p++; goto _out; } }} break; case 364: #line 615 "char_ref.rl" {te = p+1;{ output->first = 0x0001d4a9; {p++; goto _out; } }} break; case 365: #line 616 "char_ref.rl" {te = p+1;{ output->first = 0xd1; {p++; goto _out; } }} break; case 366: #line 618 "char_ref.rl" {te = p+1;{ output->first = 0x039d; {p++; goto _out; } }} break; case 367: #line 619 "char_ref.rl" {te = p+1;{ output->first = 0x0152; {p++; goto _out; } }} break; case 368: #line 620 "char_ref.rl" {te = p+1;{ output->first = 0xd3; {p++; goto _out; } }} break; case 369: #line 622 "char_ref.rl" {te = p+1;{ output->first = 0xd4; {p++; goto _out; } }} break; case 370: #line 624 "char_ref.rl" {te = p+1;{ output->first = 0x041e; {p++; goto _out; } }} break; case 371: #line 625 "char_ref.rl" {te = p+1;{ output->first = 0x0150; {p++; goto _out; } }} break; case 372: #line 626 "char_ref.rl" {te = p+1;{ output->first = 0x0001d512; {p++; goto _out; } }} break; case 373: #line 627 "char_ref.rl" {te = p+1;{ output->first = 0xd2; {p++; goto _out; } }} break; case 374: #line 629 "char_ref.rl" {te = p+1;{ output->first = 0x014c; {p++; goto _out; } }} break; case 375: #line 630 "char_ref.rl" {te = p+1;{ output->first = 0x03a9; {p++; goto _out; } }} break; case 376: #line 631 "char_ref.rl" {te = p+1;{ output->first = 0x039f; {p++; goto _out; } }} break; case 377: #line 632 "char_ref.rl" {te = p+1;{ output->first = 0x0001d546; {p++; goto _out; } }} break; case 378: #line 633 "char_ref.rl" {te = p+1;{ output->first = 0x201c; {p++; goto _out; } }} break; case 379: #line 634 "char_ref.rl" {te = p+1;{ output->first = 0x2018; {p++; goto _out; } }} break; case 380: #line 635 "char_ref.rl" {te = p+1;{ output->first = 0x2a54; {p++; goto _out; } }} break; case 381: #line 636 "char_ref.rl" {te = p+1;{ output->first = 0x0001d4aa; {p++; goto _out; } }} break; case 382: #line 637 "char_ref.rl" {te = p+1;{ output->first = 0xd8; {p++; goto _out; } }} break; case 383: #line 639 "char_ref.rl" {te = p+1;{ output->first = 0xd5; {p++; goto _out; } }} break; case 384: #line 641 "char_ref.rl" {te = p+1;{ output->first = 0x2a37; {p++; goto _out; } }} break; case 385: #line 642 "char_ref.rl" {te = p+1;{ output->first = 0xd6; {p++; goto _out; } }} break; case 386: #line 644 "char_ref.rl" {te = p+1;{ output->first = 0x203e; {p++; goto _out; } }} break; case 387: #line 645 "char_ref.rl" {te = p+1;{ output->first = 0x23de; {p++; goto _out; } }} break; case 388: #line 646 "char_ref.rl" {te = p+1;{ output->first = 0x23b4; {p++; goto _out; } }} break; case 389: #line 647 "char_ref.rl" {te = p+1;{ output->first = 0x23dc; {p++; goto _out; } }} break; case 390: #line 648 "char_ref.rl" {te = p+1;{ output->first = 0x2202; {p++; goto _out; } }} break; case 391: #line 649 "char_ref.rl" {te = p+1;{ output->first = 0x041f; {p++; goto _out; } }} break; case 392: #line 650 "char_ref.rl" {te = p+1;{ output->first = 0x0001d513; {p++; goto _out; } }} break; case 393: #line 651 "char_ref.rl" {te = p+1;{ output->first = 0x03a6; {p++; goto _out; } }} break; case 394: #line 652 "char_ref.rl" {te = p+1;{ output->first = 0x03a0; {p++; goto _out; } }} break; case 395: #line 653 "char_ref.rl" {te = p+1;{ output->first = 0xb1; {p++; goto _out; } }} break; case 396: #line 654 "char_ref.rl" {te = p+1;{ output->first = 0x210c; {p++; goto _out; } }} break; case 397: #line 655 "char_ref.rl" {te = p+1;{ output->first = 0x2119; {p++; goto _out; } }} break; case 398: #line 656 "char_ref.rl" {te = p+1;{ output->first = 0x2abb; {p++; goto _out; } }} break; case 399: #line 657 "char_ref.rl" {te = p+1;{ output->first = 0x227a; {p++; goto _out; } }} break; case 400: #line 658 "char_ref.rl" {te = p+1;{ output->first = 0x2aaf; {p++; goto _out; } }} break; case 401: #line 659 "char_ref.rl" {te = p+1;{ output->first = 0x227c; {p++; goto _out; } }} break; case 402: #line 660 "char_ref.rl" {te = p+1;{ output->first = 0x227e; {p++; goto _out; } }} break; case 403: #line 661 "char_ref.rl" {te = p+1;{ output->first = 0x2033; {p++; goto _out; } }} break; case 404: #line 662 "char_ref.rl" {te = p+1;{ output->first = 0x220f; {p++; goto _out; } }} break; case 405: #line 663 "char_ref.rl" {te = p+1;{ output->first = 0x2237; {p++; goto _out; } }} break; case 406: #line 664 "char_ref.rl" {te = p+1;{ output->first = 0x221d; {p++; goto _out; } }} break; case 407: #line 665 "char_ref.rl" {te = p+1;{ output->first = 0x0001d4ab; {p++; goto _out; } }} break; case 408: #line 666 "char_ref.rl" {te = p+1;{ output->first = 0x03a8; {p++; goto _out; } }} break; case 409: #line 667 "char_ref.rl" {te = p+1;{ output->first = 0x22; {p++; goto _out; } }} break; case 410: #line 669 "char_ref.rl" {te = p+1;{ output->first = 0x0001d514; {p++; goto _out; } }} break; case 411: #line 670 "char_ref.rl" {te = p+1;{ output->first = 0x211a; {p++; goto _out; } }} break; case 412: #line 671 "char_ref.rl" {te = p+1;{ output->first = 0x0001d4ac; {p++; goto _out; } }} break; case 413: #line 672 "char_ref.rl" {te = p+1;{ output->first = 0x2910; {p++; goto _out; } }} break; case 414: #line 673 "char_ref.rl" {te = p+1;{ output->first = 0xae; {p++; goto _out; } }} break; case 415: #line 675 "char_ref.rl" {te = p+1;{ output->first = 0x0154; {p++; goto _out; } }} break; case 416: #line 676 "char_ref.rl" {te = p+1;{ output->first = 0x27eb; {p++; goto _out; } }} break; case 417: #line 677 "char_ref.rl" {te = p+1;{ output->first = 0x21a0; {p++; goto _out; } }} break; case 418: #line 678 "char_ref.rl" {te = p+1;{ output->first = 0x2916; {p++; goto _out; } }} break; case 419: #line 679 "char_ref.rl" {te = p+1;{ output->first = 0x0158; {p++; goto _out; } }} break; case 420: #line 680 "char_ref.rl" {te = p+1;{ output->first = 0x0156; {p++; goto _out; } }} break; case 421: #line 681 "char_ref.rl" {te = p+1;{ output->first = 0x0420; {p++; goto _out; } }} break; case 422: #line 682 "char_ref.rl" {te = p+1;{ output->first = 0x211c; {p++; goto _out; } }} break; case 423: #line 683 "char_ref.rl" {te = p+1;{ output->first = 0x220b; {p++; goto _out; } }} break; case 424: #line 684 "char_ref.rl" {te = p+1;{ output->first = 0x21cb; {p++; goto _out; } }} break; case 425: #line 685 "char_ref.rl" {te = p+1;{ output->first = 0x296f; {p++; goto _out; } }} break; case 426: #line 686 "char_ref.rl" {te = p+1;{ output->first = 0x211c; {p++; goto _out; } }} break; case 427: #line 687 "char_ref.rl" {te = p+1;{ output->first = 0x03a1; {p++; goto _out; } }} break; case 428: #line 688 "char_ref.rl" {te = p+1;{ output->first = 0x27e9; {p++; goto _out; } }} break; case 429: #line 689 "char_ref.rl" {te = p+1;{ output->first = 0x2192; {p++; goto _out; } }} break; case 430: #line 690 "char_ref.rl" {te = p+1;{ output->first = 0x21e5; {p++; goto _out; } }} break; case 431: #line 691 "char_ref.rl" {te = p+1;{ output->first = 0x21c4; {p++; goto _out; } }} break; case 432: #line 692 "char_ref.rl" {te = p+1;{ output->first = 0x2309; {p++; goto _out; } }} break; case 433: #line 693 "char_ref.rl" {te = p+1;{ output->first = 0x27e7; {p++; goto _out; } }} break; case 434: #line 694 "char_ref.rl" {te = p+1;{ output->first = 0x295d; {p++; goto _out; } }} break; case 435: #line 695 "char_ref.rl" {te = p+1;{ output->first = 0x21c2; {p++; goto _out; } }} break; case 436: #line 696 "char_ref.rl" {te = p+1;{ output->first = 0x2955; {p++; goto _out; } }} break; case 437: #line 697 "char_ref.rl" {te = p+1;{ output->first = 0x230b; {p++; goto _out; } }} break; case 438: #line 698 "char_ref.rl" {te = p+1;{ output->first = 0x22a2; {p++; goto _out; } }} break; case 439: #line 699 "char_ref.rl" {te = p+1;{ output->first = 0x21a6; {p++; goto _out; } }} break; case 440: #line 700 "char_ref.rl" {te = p+1;{ output->first = 0x295b; {p++; goto _out; } }} break; case 441: #line 701 "char_ref.rl" {te = p+1;{ output->first = 0x22b3; {p++; goto _out; } }} break; case 442: #line 702 "char_ref.rl" {te = p+1;{ output->first = 0x29d0; {p++; goto _out; } }} break; case 443: #line 703 "char_ref.rl" {te = p+1;{ output->first = 0x22b5; {p++; goto _out; } }} break; case 444: #line 704 "char_ref.rl" {te = p+1;{ output->first = 0x294f; {p++; goto _out; } }} break; case 445: #line 705 "char_ref.rl" {te = p+1;{ output->first = 0x295c; {p++; goto _out; } }} break; case 446: #line 706 "char_ref.rl" {te = p+1;{ output->first = 0x21be; {p++; goto _out; } }} break; case 447: #line 707 "char_ref.rl" {te = p+1;{ output->first = 0x2954; {p++; goto _out; } }} break; case 448: #line 708 "char_ref.rl" {te = p+1;{ output->first = 0x21c0; {p++; goto _out; } }} break; case 449: #line 709 "char_ref.rl" {te = p+1;{ output->first = 0x2953; {p++; goto _out; } }} break; case 450: #line 710 "char_ref.rl" {te = p+1;{ output->first = 0x21d2; {p++; goto _out; } }} break; case 451: #line 711 "char_ref.rl" {te = p+1;{ output->first = 0x211d; {p++; goto _out; } }} break; case 452: #line 712 "char_ref.rl" {te = p+1;{ output->first = 0x2970; {p++; goto _out; } }} break; case 453: #line 713 "char_ref.rl" {te = p+1;{ output->first = 0x21db; {p++; goto _out; } }} break; case 454: #line 714 "char_ref.rl" {te = p+1;{ output->first = 0x211b; {p++; goto _out; } }} break; case 455: #line 715 "char_ref.rl" {te = p+1;{ output->first = 0x21b1; {p++; goto _out; } }} break; case 456: #line 716 "char_ref.rl" {te = p+1;{ output->first = 0x29f4; {p++; goto _out; } }} break; case 457: #line 717 "char_ref.rl" {te = p+1;{ output->first = 0x0429; {p++; goto _out; } }} break; case 458: #line 718 "char_ref.rl" {te = p+1;{ output->first = 0x0428; {p++; goto _out; } }} break; case 459: #line 719 "char_ref.rl" {te = p+1;{ output->first = 0x042c; {p++; goto _out; } }} break; case 460: #line 720 "char_ref.rl" {te = p+1;{ output->first = 0x015a; {p++; goto _out; } }} break; case 461: #line 721 "char_ref.rl" {te = p+1;{ output->first = 0x2abc; {p++; goto _out; } }} break; case 462: #line 722 "char_ref.rl" {te = p+1;{ output->first = 0x0160; {p++; goto _out; } }} break; case 463: #line 723 "char_ref.rl" {te = p+1;{ output->first = 0x015e; {p++; goto _out; } }} break; case 464: #line 724 "char_ref.rl" {te = p+1;{ output->first = 0x015c; {p++; goto _out; } }} break; case 465: #line 725 "char_ref.rl" {te = p+1;{ output->first = 0x0421; {p++; goto _out; } }} break; case 466: #line 726 "char_ref.rl" {te = p+1;{ output->first = 0x0001d516; {p++; goto _out; } }} break; case 467: #line 727 "char_ref.rl" {te = p+1;{ output->first = 0x2193; {p++; goto _out; } }} break; case 468: #line 728 "char_ref.rl" {te = p+1;{ output->first = 0x2190; {p++; goto _out; } }} break; case 469: #line 729 "char_ref.rl" {te = p+1;{ output->first = 0x2192; {p++; goto _out; } }} break; case 470: #line 730 "char_ref.rl" {te = p+1;{ output->first = 0x2191; {p++; goto _out; } }} break; case 471: #line 731 "char_ref.rl" {te = p+1;{ output->first = 0x03a3; {p++; goto _out; } }} break; case 472: #line 732 "char_ref.rl" {te = p+1;{ output->first = 0x2218; {p++; goto _out; } }} break; case 473: #line 733 "char_ref.rl" {te = p+1;{ output->first = 0x0001d54a; {p++; goto _out; } }} break; case 474: #line 734 "char_ref.rl" {te = p+1;{ output->first = 0x221a; {p++; goto _out; } }} break; case 475: #line 735 "char_ref.rl" {te = p+1;{ output->first = 0x25a1; {p++; goto _out; } }} break; case 476: #line 736 "char_ref.rl" {te = p+1;{ output->first = 0x2293; {p++; goto _out; } }} break; case 477: #line 737 "char_ref.rl" {te = p+1;{ output->first = 0x228f; {p++; goto _out; } }} break; case 478: #line 738 "char_ref.rl" {te = p+1;{ output->first = 0x2291; {p++; goto _out; } }} break; case 479: #line 739 "char_ref.rl" {te = p+1;{ output->first = 0x2290; {p++; goto _out; } }} break; case 480: #line 740 "char_ref.rl" {te = p+1;{ output->first = 0x2292; {p++; goto _out; } }} break; case 481: #line 741 "char_ref.rl" {te = p+1;{ output->first = 0x2294; {p++; goto _out; } }} break; case 482: #line 742 "char_ref.rl" {te = p+1;{ output->first = 0x0001d4ae; {p++; goto _out; } }} break; case 483: #line 743 "char_ref.rl" {te = p+1;{ output->first = 0x22c6; {p++; goto _out; } }} break; case 484: #line 744 "char_ref.rl" {te = p+1;{ output->first = 0x22d0; {p++; goto _out; } }} break; case 485: #line 745 "char_ref.rl" {te = p+1;{ output->first = 0x22d0; {p++; goto _out; } }} break; case 486: #line 746 "char_ref.rl" {te = p+1;{ output->first = 0x2286; {p++; goto _out; } }} break; case 487: #line 747 "char_ref.rl" {te = p+1;{ output->first = 0x227b; {p++; goto _out; } }} break; case 488: #line 748 "char_ref.rl" {te = p+1;{ output->first = 0x2ab0; {p++; goto _out; } }} break; case 489: #line 749 "char_ref.rl" {te = p+1;{ output->first = 0x227d; {p++; goto _out; } }} break; case 490: #line 750 "char_ref.rl" {te = p+1;{ output->first = 0x227f; {p++; goto _out; } }} break; case 491: #line 751 "char_ref.rl" {te = p+1;{ output->first = 0x220b; {p++; goto _out; } }} break; case 492: #line 752 "char_ref.rl" {te = p+1;{ output->first = 0x2211; {p++; goto _out; } }} break; case 493: #line 753 "char_ref.rl" {te = p+1;{ output->first = 0x22d1; {p++; goto _out; } }} break; case 494: #line 754 "char_ref.rl" {te = p+1;{ output->first = 0x2283; {p++; goto _out; } }} break; case 495: #line 755 "char_ref.rl" {te = p+1;{ output->first = 0x2287; {p++; goto _out; } }} break; case 496: #line 756 "char_ref.rl" {te = p+1;{ output->first = 0x22d1; {p++; goto _out; } }} break; case 497: #line 757 "char_ref.rl" {te = p+1;{ output->first = 0xde; {p++; goto _out; } }} break; case 498: #line 759 "char_ref.rl" {te = p+1;{ output->first = 0x2122; {p++; goto _out; } }} break; case 499: #line 760 "char_ref.rl" {te = p+1;{ output->first = 0x040b; {p++; goto _out; } }} break; case 500: #line 761 "char_ref.rl" {te = p+1;{ output->first = 0x0426; {p++; goto _out; } }} break; case 501: #line 762 "char_ref.rl" {te = p+1;{ output->first = 0x09; {p++; goto _out; } }} break; case 502: #line 763 "char_ref.rl" {te = p+1;{ output->first = 0x03a4; {p++; goto _out; } }} break; case 503: #line 764 "char_ref.rl" {te = p+1;{ output->first = 0x0164; {p++; goto _out; } }} break; case 504: #line 765 "char_ref.rl" {te = p+1;{ output->first = 0x0162; {p++; goto _out; } }} break; case 505: #line 766 "char_ref.rl" {te = p+1;{ output->first = 0x0422; {p++; goto _out; } }} break; case 506: #line 767 "char_ref.rl" {te = p+1;{ output->first = 0x0001d517; {p++; goto _out; } }} break; case 507: #line 768 "char_ref.rl" {te = p+1;{ output->first = 0x2234; {p++; goto _out; } }} break; case 508: #line 769 "char_ref.rl" {te = p+1;{ output->first = 0x0398; {p++; goto _out; } }} break; case 509: #line 770 "char_ref.rl" {te = p+1;{ output->first = 0x205f; output->second = 0x200a; {p++; goto _out; } }} break; case 510: #line 771 "char_ref.rl" {te = p+1;{ output->first = 0x2009; {p++; goto _out; } }} break; case 511: #line 772 "char_ref.rl" {te = p+1;{ output->first = 0x223c; {p++; goto _out; } }} break; case 512: #line 773 "char_ref.rl" {te = p+1;{ output->first = 0x2243; {p++; goto _out; } }} break; case 513: #line 774 "char_ref.rl" {te = p+1;{ output->first = 0x2245; {p++; goto _out; } }} break; case 514: #line 775 "char_ref.rl" {te = p+1;{ output->first = 0x2248; {p++; goto _out; } }} break; case 515: #line 776 "char_ref.rl" {te = p+1;{ output->first = 0x0001d54b; {p++; goto _out; } }} break; case 516: #line 777 "char_ref.rl" {te = p+1;{ output->first = 0x20db; {p++; goto _out; } }} break; case 517: #line 778 "char_ref.rl" {te = p+1;{ output->first = 0x0001d4af; {p++; goto _out; } }} break; case 518: #line 779 "char_ref.rl" {te = p+1;{ output->first = 0x0166; {p++; goto _out; } }} break; case 519: #line 780 "char_ref.rl" {te = p+1;{ output->first = 0xda; {p++; goto _out; } }} break; case 520: #line 782 "char_ref.rl" {te = p+1;{ output->first = 0x219f; {p++; goto _out; } }} break; case 521: #line 783 "char_ref.rl" {te = p+1;{ output->first = 0x2949; {p++; goto _out; } }} break; case 522: #line 784 "char_ref.rl" {te = p+1;{ output->first = 0x040e; {p++; goto _out; } }} break; case 523: #line 785 "char_ref.rl" {te = p+1;{ output->first = 0x016c; {p++; goto _out; } }} break; case 524: #line 786 "char_ref.rl" {te = p+1;{ output->first = 0xdb; {p++; goto _out; } }} break; case 525: #line 788 "char_ref.rl" {te = p+1;{ output->first = 0x0423; {p++; goto _out; } }} break; case 526: #line 789 "char_ref.rl" {te = p+1;{ output->first = 0x0170; {p++; goto _out; } }} break; case 527: #line 790 "char_ref.rl" {te = p+1;{ output->first = 0x0001d518; {p++; goto _out; } }} break; case 528: #line 791 "char_ref.rl" {te = p+1;{ output->first = 0xd9; {p++; goto _out; } }} break; case 529: #line 793 "char_ref.rl" {te = p+1;{ output->first = 0x016a; {p++; goto _out; } }} break; case 530: #line 794 "char_ref.rl" {te = p+1;{ output->first = 0x5f; {p++; goto _out; } }} break; case 531: #line 795 "char_ref.rl" {te = p+1;{ output->first = 0x23df; {p++; goto _out; } }} break; case 532: #line 796 "char_ref.rl" {te = p+1;{ output->first = 0x23b5; {p++; goto _out; } }} break; case 533: #line 797 "char_ref.rl" {te = p+1;{ output->first = 0x23dd; {p++; goto _out; } }} break; case 534: #line 798 "char_ref.rl" {te = p+1;{ output->first = 0x22c3; {p++; goto _out; } }} break; case 535: #line 799 "char_ref.rl" {te = p+1;{ output->first = 0x228e; {p++; goto _out; } }} break; case 536: #line 800 "char_ref.rl" {te = p+1;{ output->first = 0x0172; {p++; goto _out; } }} break; case 537: #line 801 "char_ref.rl" {te = p+1;{ output->first = 0x0001d54c; {p++; goto _out; } }} break; case 538: #line 802 "char_ref.rl" {te = p+1;{ output->first = 0x2191; {p++; goto _out; } }} break; case 539: #line 803 "char_ref.rl" {te = p+1;{ output->first = 0x2912; {p++; goto _out; } }} break; case 540: #line 804 "char_ref.rl" {te = p+1;{ output->first = 0x21c5; {p++; goto _out; } }} break; case 541: #line 805 "char_ref.rl" {te = p+1;{ output->first = 0x2195; {p++; goto _out; } }} break; case 542: #line 806 "char_ref.rl" {te = p+1;{ output->first = 0x296e; {p++; goto _out; } }} break; case 543: #line 807 "char_ref.rl" {te = p+1;{ output->first = 0x22a5; {p++; goto _out; } }} break; case 544: #line 808 "char_ref.rl" {te = p+1;{ output->first = 0x21a5; {p++; goto _out; } }} break; case 545: #line 809 "char_ref.rl" {te = p+1;{ output->first = 0x21d1; {p++; goto _out; } }} break; case 546: #line 810 "char_ref.rl" {te = p+1;{ output->first = 0x21d5; {p++; goto _out; } }} break; case 547: #line 811 "char_ref.rl" {te = p+1;{ output->first = 0x2196; {p++; goto _out; } }} break; case 548: #line 812 "char_ref.rl" {te = p+1;{ output->first = 0x2197; {p++; goto _out; } }} break; case 549: #line 813 "char_ref.rl" {te = p+1;{ output->first = 0x03d2; {p++; goto _out; } }} break; case 550: #line 814 "char_ref.rl" {te = p+1;{ output->first = 0x03a5; {p++; goto _out; } }} break; case 551: #line 815 "char_ref.rl" {te = p+1;{ output->first = 0x016e; {p++; goto _out; } }} break; case 552: #line 816 "char_ref.rl" {te = p+1;{ output->first = 0x0001d4b0; {p++; goto _out; } }} break; case 553: #line 817 "char_ref.rl" {te = p+1;{ output->first = 0x0168; {p++; goto _out; } }} break; case 554: #line 818 "char_ref.rl" {te = p+1;{ output->first = 0xdc; {p++; goto _out; } }} break; case 555: #line 820 "char_ref.rl" {te = p+1;{ output->first = 0x22ab; {p++; goto _out; } }} break; case 556: #line 821 "char_ref.rl" {te = p+1;{ output->first = 0x2aeb; {p++; goto _out; } }} break; case 557: #line 822 "char_ref.rl" {te = p+1;{ output->first = 0x0412; {p++; goto _out; } }} break; case 558: #line 823 "char_ref.rl" {te = p+1;{ output->first = 0x22a9; {p++; goto _out; } }} break; case 559: #line 824 "char_ref.rl" {te = p+1;{ output->first = 0x2ae6; {p++; goto _out; } }} break; case 560: #line 825 "char_ref.rl" {te = p+1;{ output->first = 0x22c1; {p++; goto _out; } }} break; case 561: #line 826 "char_ref.rl" {te = p+1;{ output->first = 0x2016; {p++; goto _out; } }} break; case 562: #line 827 "char_ref.rl" {te = p+1;{ output->first = 0x2016; {p++; goto _out; } }} break; case 563: #line 828 "char_ref.rl" {te = p+1;{ output->first = 0x2223; {p++; goto _out; } }} break; case 564: #line 829 "char_ref.rl" {te = p+1;{ output->first = 0x7c; {p++; goto _out; } }} break; case 565: #line 830 "char_ref.rl" {te = p+1;{ output->first = 0x2758; {p++; goto _out; } }} break; case 566: #line 831 "char_ref.rl" {te = p+1;{ output->first = 0x2240; {p++; goto _out; } }} break; case 567: #line 832 "char_ref.rl" {te = p+1;{ output->first = 0x200a; {p++; goto _out; } }} break; case 568: #line 833 "char_ref.rl" {te = p+1;{ output->first = 0x0001d519; {p++; goto _out; } }} break; case 569: #line 834 "char_ref.rl" {te = p+1;{ output->first = 0x0001d54d; {p++; goto _out; } }} break; case 570: #line 835 "char_ref.rl" {te = p+1;{ output->first = 0x0001d4b1; {p++; goto _out; } }} break; case 571: #line 836 "char_ref.rl" {te = p+1;{ output->first = 0x22aa; {p++; goto _out; } }} break; case 572: #line 837 "char_ref.rl" {te = p+1;{ output->first = 0x0174; {p++; goto _out; } }} break; case 573: #line 838 "char_ref.rl" {te = p+1;{ output->first = 0x22c0; {p++; goto _out; } }} break; case 574: #line 839 "char_ref.rl" {te = p+1;{ output->first = 0x0001d51a; {p++; goto _out; } }} break; case 575: #line 840 "char_ref.rl" {te = p+1;{ output->first = 0x0001d54e; {p++; goto _out; } }} break; case 576: #line 841 "char_ref.rl" {te = p+1;{ output->first = 0x0001d4b2; {p++; goto _out; } }} break; case 577: #line 842 "char_ref.rl" {te = p+1;{ output->first = 0x0001d51b; {p++; goto _out; } }} break; case 578: #line 843 "char_ref.rl" {te = p+1;{ output->first = 0x039e; {p++; goto _out; } }} break; case 579: #line 844 "char_ref.rl" {te = p+1;{ output->first = 0x0001d54f; {p++; goto _out; } }} break; case 580: #line 845 "char_ref.rl" {te = p+1;{ output->first = 0x0001d4b3; {p++; goto _out; } }} break; case 581: #line 846 "char_ref.rl" {te = p+1;{ output->first = 0x042f; {p++; goto _out; } }} break; case 582: #line 847 "char_ref.rl" {te = p+1;{ output->first = 0x0407; {p++; goto _out; } }} break; case 583: #line 848 "char_ref.rl" {te = p+1;{ output->first = 0x042e; {p++; goto _out; } }} break; case 584: #line 849 "char_ref.rl" {te = p+1;{ output->first = 0xdd; {p++; goto _out; } }} break; case 585: #line 851 "char_ref.rl" {te = p+1;{ output->first = 0x0176; {p++; goto _out; } }} break; case 586: #line 852 "char_ref.rl" {te = p+1;{ output->first = 0x042b; {p++; goto _out; } }} break; case 587: #line 853 "char_ref.rl" {te = p+1;{ output->first = 0x0001d51c; {p++; goto _out; } }} break; case 588: #line 854 "char_ref.rl" {te = p+1;{ output->first = 0x0001d550; {p++; goto _out; } }} break; case 589: #line 855 "char_ref.rl" {te = p+1;{ output->first = 0x0001d4b4; {p++; goto _out; } }} break; case 590: #line 856 "char_ref.rl" {te = p+1;{ output->first = 0x0178; {p++; goto _out; } }} break; case 591: #line 857 "char_ref.rl" {te = p+1;{ output->first = 0x0416; {p++; goto _out; } }} break; case 592: #line 858 "char_ref.rl" {te = p+1;{ output->first = 0x0179; {p++; goto _out; } }} break; case 593: #line 859 "char_ref.rl" {te = p+1;{ output->first = 0x017d; {p++; goto _out; } }} break; case 594: #line 860 "char_ref.rl" {te = p+1;{ output->first = 0x0417; {p++; goto _out; } }} break; case 595: #line 861 "char_ref.rl" {te = p+1;{ output->first = 0x017b; {p++; goto _out; } }} break; case 596: #line 862 "char_ref.rl" {te = p+1;{ output->first = 0x200b; {p++; goto _out; } }} break; case 597: #line 863 "char_ref.rl" {te = p+1;{ output->first = 0x0396; {p++; goto _out; } }} break; case 598: #line 864 "char_ref.rl" {te = p+1;{ output->first = 0x2128; {p++; goto _out; } }} break; case 599: #line 865 "char_ref.rl" {te = p+1;{ output->first = 0x2124; {p++; goto _out; } }} break; case 600: #line 866 "char_ref.rl" {te = p+1;{ output->first = 0x0001d4b5; {p++; goto _out; } }} break; case 601: #line 867 "char_ref.rl" {te = p+1;{ output->first = 0xe1; {p++; goto _out; } }} break; case 602: #line 869 "char_ref.rl" {te = p+1;{ output->first = 0x0103; {p++; goto _out; } }} break; case 603: #line 870 "char_ref.rl" {te = p+1;{ output->first = 0x223e; {p++; goto _out; } }} break; case 604: #line 871 "char_ref.rl" {te = p+1;{ output->first = 0x223e; output->second = 0x0333; {p++; goto _out; } }} break; case 605: #line 872 "char_ref.rl" {te = p+1;{ output->first = 0x223f; {p++; goto _out; } }} break; case 606: #line 873 "char_ref.rl" {te = p+1;{ output->first = 0xe2; {p++; goto _out; } }} break; case 607: #line 875 "char_ref.rl" {te = p+1;{ output->first = 0xb4; {p++; goto _out; } }} break; case 608: #line 877 "char_ref.rl" {te = p+1;{ output->first = 0x0430; {p++; goto _out; } }} break; case 609: #line 878 "char_ref.rl" {te = p+1;{ output->first = 0xe6; {p++; goto _out; } }} break; case 610: #line 880 "char_ref.rl" {te = p+1;{ output->first = 0x2061; {p++; goto _out; } }} break; case 611: #line 881 "char_ref.rl" {te = p+1;{ output->first = 0x0001d51e; {p++; goto _out; } }} break; case 612: #line 882 "char_ref.rl" {te = p+1;{ output->first = 0xe0; {p++; goto _out; } }} break; case 613: #line 884 "char_ref.rl" {te = p+1;{ output->first = 0x2135; {p++; goto _out; } }} break; case 614: #line 885 "char_ref.rl" {te = p+1;{ output->first = 0x2135; {p++; goto _out; } }} break; case 615: #line 886 "char_ref.rl" {te = p+1;{ output->first = 0x03b1; {p++; goto _out; } }} break; case 616: #line 887 "char_ref.rl" {te = p+1;{ output->first = 0x0101; {p++; goto _out; } }} break; case 617: #line 888 "char_ref.rl" {te = p+1;{ output->first = 0x2a3f; {p++; goto _out; } }} break; case 618: #line 889 "char_ref.rl" {te = p+1;{ output->first = 0x26; {p++; goto _out; } }} break; case 619: #line 891 "char_ref.rl" {te = p+1;{ output->first = 0x2227; {p++; goto _out; } }} break; case 620: #line 892 "char_ref.rl" {te = p+1;{ output->first = 0x2a55; {p++; goto _out; } }} break; case 621: #line 893 "char_ref.rl" {te = p+1;{ output->first = 0x2a5c; {p++; goto _out; } }} break; case 622: #line 894 "char_ref.rl" {te = p+1;{ output->first = 0x2a58; {p++; goto _out; } }} break; case 623: #line 895 "char_ref.rl" {te = p+1;{ output->first = 0x2a5a; {p++; goto _out; } }} break; case 624: #line 896 "char_ref.rl" {te = p+1;{ output->first = 0x2220; {p++; goto _out; } }} break; case 625: #line 897 "char_ref.rl" {te = p+1;{ output->first = 0x29a4; {p++; goto _out; } }} break; case 626: #line 898 "char_ref.rl" {te = p+1;{ output->first = 0x2220; {p++; goto _out; } }} break; case 627: #line 899 "char_ref.rl" {te = p+1;{ output->first = 0x2221; {p++; goto _out; } }} break; case 628: #line 900 "char_ref.rl" {te = p+1;{ output->first = 0x29a8; {p++; goto _out; } }} break; case 629: #line 901 "char_ref.rl" {te = p+1;{ output->first = 0x29a9; {p++; goto _out; } }} break; case 630: #line 902 "char_ref.rl" {te = p+1;{ output->first = 0x29aa; {p++; goto _out; } }} break; case 631: #line 903 "char_ref.rl" {te = p+1;{ output->first = 0x29ab; {p++; goto _out; } }} break; case 632: #line 904 "char_ref.rl" {te = p+1;{ output->first = 0x29ac; {p++; goto _out; } }} break; case 633: #line 905 "char_ref.rl" {te = p+1;{ output->first = 0x29ad; {p++; goto _out; } }} break; case 634: #line 906 "char_ref.rl" {te = p+1;{ output->first = 0x29ae; {p++; goto _out; } }} break; case 635: #line 907 "char_ref.rl" {te = p+1;{ output->first = 0x29af; {p++; goto _out; } }} break; case 636: #line 908 "char_ref.rl" {te = p+1;{ output->first = 0x221f; {p++; goto _out; } }} break; case 637: #line 909 "char_ref.rl" {te = p+1;{ output->first = 0x22be; {p++; goto _out; } }} break; case 638: #line 910 "char_ref.rl" {te = p+1;{ output->first = 0x299d; {p++; goto _out; } }} break; case 639: #line 911 "char_ref.rl" {te = p+1;{ output->first = 0x2222; {p++; goto _out; } }} break; case 640: #line 912 "char_ref.rl" {te = p+1;{ output->first = 0xc5; {p++; goto _out; } }} break; case 641: #line 913 "char_ref.rl" {te = p+1;{ output->first = 0x237c; {p++; goto _out; } }} break; case 642: #line 914 "char_ref.rl" {te = p+1;{ output->first = 0x0105; {p++; goto _out; } }} break; case 643: #line 915 "char_ref.rl" {te = p+1;{ output->first = 0x0001d552; {p++; goto _out; } }} break; case 644: #line 916 "char_ref.rl" {te = p+1;{ output->first = 0x2248; {p++; goto _out; } }} break; case 645: #line 917 "char_ref.rl" {te = p+1;{ output->first = 0x2a70; {p++; goto _out; } }} break; case 646: #line 918 "char_ref.rl" {te = p+1;{ output->first = 0x2a6f; {p++; goto _out; } }} break; case 647: #line 919 "char_ref.rl" {te = p+1;{ output->first = 0x224a; {p++; goto _out; } }} break; case 648: #line 920 "char_ref.rl" {te = p+1;{ output->first = 0x224b; {p++; goto _out; } }} break; case 649: #line 921 "char_ref.rl" {te = p+1;{ output->first = 0x27; {p++; goto _out; } }} break; case 650: #line 922 "char_ref.rl" {te = p+1;{ output->first = 0x2248; {p++; goto _out; } }} break; case 651: #line 923 "char_ref.rl" {te = p+1;{ output->first = 0x224a; {p++; goto _out; } }} break; case 652: #line 924 "char_ref.rl" {te = p+1;{ output->first = 0xe5; {p++; goto _out; } }} break; case 653: #line 926 "char_ref.rl" {te = p+1;{ output->first = 0x0001d4b6; {p++; goto _out; } }} break; case 654: #line 927 "char_ref.rl" {te = p+1;{ output->first = 0x2a; {p++; goto _out; } }} break; case 655: #line 928 "char_ref.rl" {te = p+1;{ output->first = 0x2248; {p++; goto _out; } }} break; case 656: #line 929 "char_ref.rl" {te = p+1;{ output->first = 0x224d; {p++; goto _out; } }} break; case 657: #line 930 "char_ref.rl" {te = p+1;{ output->first = 0xe3; {p++; goto _out; } }} break; case 658: #line 932 "char_ref.rl" {te = p+1;{ output->first = 0xe4; {p++; goto _out; } }} break; case 659: #line 934 "char_ref.rl" {te = p+1;{ output->first = 0x2233; {p++; goto _out; } }} break; case 660: #line 935 "char_ref.rl" {te = p+1;{ output->first = 0x2a11; {p++; goto _out; } }} break; case 661: #line 936 "char_ref.rl" {te = p+1;{ output->first = 0x2aed; {p++; goto _out; } }} break; case 662: #line 937 "char_ref.rl" {te = p+1;{ output->first = 0x224c; {p++; goto _out; } }} break; case 663: #line 938 "char_ref.rl" {te = p+1;{ output->first = 0x03f6; {p++; goto _out; } }} break; case 664: #line 939 "char_ref.rl" {te = p+1;{ output->first = 0x2035; {p++; goto _out; } }} break; case 665: #line 940 "char_ref.rl" {te = p+1;{ output->first = 0x223d; {p++; goto _out; } }} break; case 666: #line 941 "char_ref.rl" {te = p+1;{ output->first = 0x22cd; {p++; goto _out; } }} break; case 667: #line 942 "char_ref.rl" {te = p+1;{ output->first = 0x22bd; {p++; goto _out; } }} break; case 668: #line 943 "char_ref.rl" {te = p+1;{ output->first = 0x2305; {p++; goto _out; } }} break; case 669: #line 944 "char_ref.rl" {te = p+1;{ output->first = 0x2305; {p++; goto _out; } }} break; case 670: #line 945 "char_ref.rl" {te = p+1;{ output->first = 0x23b5; {p++; goto _out; } }} break; case 671: #line 946 "char_ref.rl" {te = p+1;{ output->first = 0x23b6; {p++; goto _out; } }} break; case 672: #line 947 "char_ref.rl" {te = p+1;{ output->first = 0x224c; {p++; goto _out; } }} break; case 673: #line 948 "char_ref.rl" {te = p+1;{ output->first = 0x0431; {p++; goto _out; } }} break; case 674: #line 949 "char_ref.rl" {te = p+1;{ output->first = 0x201e; {p++; goto _out; } }} break; case 675: #line 950 "char_ref.rl" {te = p+1;{ output->first = 0x2235; {p++; goto _out; } }} break; case 676: #line 951 "char_ref.rl" {te = p+1;{ output->first = 0x2235; {p++; goto _out; } }} break; case 677: #line 952 "char_ref.rl" {te = p+1;{ output->first = 0x29b0; {p++; goto _out; } }} break; case 678: #line 953 "char_ref.rl" {te = p+1;{ output->first = 0x03f6; {p++; goto _out; } }} break; case 679: #line 954 "char_ref.rl" {te = p+1;{ output->first = 0x212c; {p++; goto _out; } }} break; case 680: #line 955 "char_ref.rl" {te = p+1;{ output->first = 0x03b2; {p++; goto _out; } }} break; case 681: #line 956 "char_ref.rl" {te = p+1;{ output->first = 0x2136; {p++; goto _out; } }} break; case 682: #line 957 "char_ref.rl" {te = p+1;{ output->first = 0x226c; {p++; goto _out; } }} break; case 683: #line 958 "char_ref.rl" {te = p+1;{ output->first = 0x0001d51f; {p++; goto _out; } }} break; case 684: #line 959 "char_ref.rl" {te = p+1;{ output->first = 0x22c2; {p++; goto _out; } }} break; case 685: #line 960 "char_ref.rl" {te = p+1;{ output->first = 0x25ef; {p++; goto _out; } }} break; case 686: #line 961 "char_ref.rl" {te = p+1;{ output->first = 0x22c3; {p++; goto _out; } }} break; case 687: #line 962 "char_ref.rl" {te = p+1;{ output->first = 0x2a00; {p++; goto _out; } }} break; case 688: #line 963 "char_ref.rl" {te = p+1;{ output->first = 0x2a01; {p++; goto _out; } }} break; case 689: #line 964 "char_ref.rl" {te = p+1;{ output->first = 0x2a02; {p++; goto _out; } }} break; case 690: #line 965 "char_ref.rl" {te = p+1;{ output->first = 0x2a06; {p++; goto _out; } }} break; case 691: #line 966 "char_ref.rl" {te = p+1;{ output->first = 0x2605; {p++; goto _out; } }} break; case 692: #line 967 "char_ref.rl" {te = p+1;{ output->first = 0x25bd; {p++; goto _out; } }} break; case 693: #line 968 "char_ref.rl" {te = p+1;{ output->first = 0x25b3; {p++; goto _out; } }} break; case 694: #line 969 "char_ref.rl" {te = p+1;{ output->first = 0x2a04; {p++; goto _out; } }} break; case 695: #line 970 "char_ref.rl" {te = p+1;{ output->first = 0x22c1; {p++; goto _out; } }} break; case 696: #line 971 "char_ref.rl" {te = p+1;{ output->first = 0x22c0; {p++; goto _out; } }} break; case 697: #line 972 "char_ref.rl" {te = p+1;{ output->first = 0x290d; {p++; goto _out; } }} break; case 698: #line 973 "char_ref.rl" {te = p+1;{ output->first = 0x29eb; {p++; goto _out; } }} break; case 699: #line 974 "char_ref.rl" {te = p+1;{ output->first = 0x25aa; {p++; goto _out; } }} break; case 700: #line 975 "char_ref.rl" {te = p+1;{ output->first = 0x25b4; {p++; goto _out; } }} break; case 701: #line 976 "char_ref.rl" {te = p+1;{ output->first = 0x25be; {p++; goto _out; } }} break; case 702: #line 977 "char_ref.rl" {te = p+1;{ output->first = 0x25c2; {p++; goto _out; } }} break; case 703: #line 978 "char_ref.rl" {te = p+1;{ output->first = 0x25b8; {p++; goto _out; } }} break; case 704: #line 979 "char_ref.rl" {te = p+1;{ output->first = 0x2423; {p++; goto _out; } }} break; case 705: #line 980 "char_ref.rl" {te = p+1;{ output->first = 0x2592; {p++; goto _out; } }} break; case 706: #line 981 "char_ref.rl" {te = p+1;{ output->first = 0x2591; {p++; goto _out; } }} break; case 707: #line 982 "char_ref.rl" {te = p+1;{ output->first = 0x2593; {p++; goto _out; } }} break; case 708: #line 983 "char_ref.rl" {te = p+1;{ output->first = 0x2588; {p++; goto _out; } }} break; case 709: #line 984 "char_ref.rl" {te = p+1;{ output->first = 0x3d; output->second = 0x20e5; {p++; goto _out; } }} break; case 710: #line 985 "char_ref.rl" {te = p+1;{ output->first = 0x2261; output->second = 0x20e5; {p++; goto _out; } }} break; case 711: #line 986 "char_ref.rl" {te = p+1;{ output->first = 0x2310; {p++; goto _out; } }} break; case 712: #line 987 "char_ref.rl" {te = p+1;{ output->first = 0x0001d553; {p++; goto _out; } }} break; case 713: #line 988 "char_ref.rl" {te = p+1;{ output->first = 0x22a5; {p++; goto _out; } }} break; case 714: #line 989 "char_ref.rl" {te = p+1;{ output->first = 0x22a5; {p++; goto _out; } }} break; case 715: #line 990 "char_ref.rl" {te = p+1;{ output->first = 0x22c8; {p++; goto _out; } }} break; case 716: #line 991 "char_ref.rl" {te = p+1;{ output->first = 0x2557; {p++; goto _out; } }} break; case 717: #line 992 "char_ref.rl" {te = p+1;{ output->first = 0x2554; {p++; goto _out; } }} break; case 718: #line 993 "char_ref.rl" {te = p+1;{ output->first = 0x2556; {p++; goto _out; } }} break; case 719: #line 994 "char_ref.rl" {te = p+1;{ output->first = 0x2553; {p++; goto _out; } }} break; case 720: #line 995 "char_ref.rl" {te = p+1;{ output->first = 0x2550; {p++; goto _out; } }} break; case 721: #line 996 "char_ref.rl" {te = p+1;{ output->first = 0x2566; {p++; goto _out; } }} break; case 722: #line 997 "char_ref.rl" {te = p+1;{ output->first = 0x2569; {p++; goto _out; } }} break; case 723: #line 998 "char_ref.rl" {te = p+1;{ output->first = 0x2564; {p++; goto _out; } }} break; case 724: #line 999 "char_ref.rl" {te = p+1;{ output->first = 0x2567; {p++; goto _out; } }} break; case 725: #line 1000 "char_ref.rl" {te = p+1;{ output->first = 0x255d; {p++; goto _out; } }} break; case 726: #line 1001 "char_ref.rl" {te = p+1;{ output->first = 0x255a; {p++; goto _out; } }} break; case 727: #line 1002 "char_ref.rl" {te = p+1;{ output->first = 0x255c; {p++; goto _out; } }} break; case 728: #line 1003 "char_ref.rl" {te = p+1;{ output->first = 0x2559; {p++; goto _out; } }} break; case 729: #line 1004 "char_ref.rl" {te = p+1;{ output->first = 0x2551; {p++; goto _out; } }} break; case 730: #line 1005 "char_ref.rl" {te = p+1;{ output->first = 0x256c; {p++; goto _out; } }} break; case 731: #line 1006 "char_ref.rl" {te = p+1;{ output->first = 0x2563; {p++; goto _out; } }} break; case 732: #line 1007 "char_ref.rl" {te = p+1;{ output->first = 0x2560; {p++; goto _out; } }} break; case 733: #line 1008 "char_ref.rl" {te = p+1;{ output->first = 0x256b; {p++; goto _out; } }} break; case 734: #line 1009 "char_ref.rl" {te = p+1;{ output->first = 0x2562; {p++; goto _out; } }} break; case 735: #line 1010 "char_ref.rl" {te = p+1;{ output->first = 0x255f; {p++; goto _out; } }} break; case 736: #line 1011 "char_ref.rl" {te = p+1;{ output->first = 0x29c9; {p++; goto _out; } }} break; case 737: #line 1012 "char_ref.rl" {te = p+1;{ output->first = 0x2555; {p++; goto _out; } }} break; case 738: #line 1013 "char_ref.rl" {te = p+1;{ output->first = 0x2552; {p++; goto _out; } }} break; case 739: #line 1014 "char_ref.rl" {te = p+1;{ output->first = 0x2510; {p++; goto _out; } }} break; case 740: #line 1015 "char_ref.rl" {te = p+1;{ output->first = 0x250c; {p++; goto _out; } }} break; case 741: #line 1016 "char_ref.rl" {te = p+1;{ output->first = 0x2500; {p++; goto _out; } }} break; case 742: #line 1017 "char_ref.rl" {te = p+1;{ output->first = 0x2565; {p++; goto _out; } }} break; case 743: #line 1018 "char_ref.rl" {te = p+1;{ output->first = 0x2568; {p++; goto _out; } }} break; case 744: #line 1019 "char_ref.rl" {te = p+1;{ output->first = 0x252c; {p++; goto _out; } }} break; case 745: #line 1020 "char_ref.rl" {te = p+1;{ output->first = 0x2534; {p++; goto _out; } }} break; case 746: #line 1021 "char_ref.rl" {te = p+1;{ output->first = 0x229f; {p++; goto _out; } }} break; case 747: #line 1022 "char_ref.rl" {te = p+1;{ output->first = 0x229e; {p++; goto _out; } }} break; case 748: #line 1023 "char_ref.rl" {te = p+1;{ output->first = 0x22a0; {p++; goto _out; } }} break; case 749: #line 1024 "char_ref.rl" {te = p+1;{ output->first = 0x255b; {p++; goto _out; } }} break; case 750: #line 1025 "char_ref.rl" {te = p+1;{ output->first = 0x2558; {p++; goto _out; } }} break; case 751: #line 1026 "char_ref.rl" {te = p+1;{ output->first = 0x2518; {p++; goto _out; } }} break; case 752: #line 1027 "char_ref.rl" {te = p+1;{ output->first = 0x2514; {p++; goto _out; } }} break; case 753: #line 1028 "char_ref.rl" {te = p+1;{ output->first = 0x2502; {p++; goto _out; } }} break; case 754: #line 1029 "char_ref.rl" {te = p+1;{ output->first = 0x256a; {p++; goto _out; } }} break; case 755: #line 1030 "char_ref.rl" {te = p+1;{ output->first = 0x2561; {p++; goto _out; } }} break; case 756: #line 1031 "char_ref.rl" {te = p+1;{ output->first = 0x255e; {p++; goto _out; } }} break; case 757: #line 1032 "char_ref.rl" {te = p+1;{ output->first = 0x253c; {p++; goto _out; } }} break; case 758: #line 1033 "char_ref.rl" {te = p+1;{ output->first = 0x2524; {p++; goto _out; } }} break; case 759: #line 1034 "char_ref.rl" {te = p+1;{ output->first = 0x251c; {p++; goto _out; } }} break; case 760: #line 1035 "char_ref.rl" {te = p+1;{ output->first = 0x2035; {p++; goto _out; } }} break; case 761: #line 1036 "char_ref.rl" {te = p+1;{ output->first = 0x02d8; {p++; goto _out; } }} break; case 762: #line 1037 "char_ref.rl" {te = p+1;{ output->first = 0xa6; {p++; goto _out; } }} break; case 763: #line 1039 "char_ref.rl" {te = p+1;{ output->first = 0x0001d4b7; {p++; goto _out; } }} break; case 764: #line 1040 "char_ref.rl" {te = p+1;{ output->first = 0x204f; {p++; goto _out; } }} break; case 765: #line 1041 "char_ref.rl" {te = p+1;{ output->first = 0x223d; {p++; goto _out; } }} break; case 766: #line 1042 "char_ref.rl" {te = p+1;{ output->first = 0x22cd; {p++; goto _out; } }} break; case 767: #line 1043 "char_ref.rl" {te = p+1;{ output->first = 0x5c; {p++; goto _out; } }} break; case 768: #line 1044 "char_ref.rl" {te = p+1;{ output->first = 0x29c5; {p++; goto _out; } }} break; case 769: #line 1045 "char_ref.rl" {te = p+1;{ output->first = 0x27c8; {p++; goto _out; } }} break; case 770: #line 1046 "char_ref.rl" {te = p+1;{ output->first = 0x2022; {p++; goto _out; } }} break; case 771: #line 1047 "char_ref.rl" {te = p+1;{ output->first = 0x2022; {p++; goto _out; } }} break; case 772: #line 1048 "char_ref.rl" {te = p+1;{ output->first = 0x224e; {p++; goto _out; } }} break; case 773: #line 1049 "char_ref.rl" {te = p+1;{ output->first = 0x2aae; {p++; goto _out; } }} break; case 774: #line 1050 "char_ref.rl" {te = p+1;{ output->first = 0x224f; {p++; goto _out; } }} break; case 775: #line 1051 "char_ref.rl" {te = p+1;{ output->first = 0x224f; {p++; goto _out; } }} break; case 776: #line 1052 "char_ref.rl" {te = p+1;{ output->first = 0x0107; {p++; goto _out; } }} break; case 777: #line 1053 "char_ref.rl" {te = p+1;{ output->first = 0x2229; {p++; goto _out; } }} break; case 778: #line 1054 "char_ref.rl" {te = p+1;{ output->first = 0x2a44; {p++; goto _out; } }} break; case 779: #line 1055 "char_ref.rl" {te = p+1;{ output->first = 0x2a49; {p++; goto _out; } }} break; case 780: #line 1056 "char_ref.rl" {te = p+1;{ output->first = 0x2a4b; {p++; goto _out; } }} break; case 781: #line 1057 "char_ref.rl" {te = p+1;{ output->first = 0x2a47; {p++; goto _out; } }} break; case 782: #line 1058 "char_ref.rl" {te = p+1;{ output->first = 0x2a40; {p++; goto _out; } }} break; case 783: #line 1059 "char_ref.rl" {te = p+1;{ output->first = 0x2229; output->second = 0xfe00; {p++; goto _out; } }} break; case 784: #line 1060 "char_ref.rl" {te = p+1;{ output->first = 0x2041; {p++; goto _out; } }} break; case 785: #line 1061 "char_ref.rl" {te = p+1;{ output->first = 0x02c7; {p++; goto _out; } }} break; case 786: #line 1062 "char_ref.rl" {te = p+1;{ output->first = 0x2a4d; {p++; goto _out; } }} break; case 787: #line 1063 "char_ref.rl" {te = p+1;{ output->first = 0x010d; {p++; goto _out; } }} break; case 788: #line 1064 "char_ref.rl" {te = p+1;{ output->first = 0xe7; {p++; goto _out; } }} break; case 789: #line 1066 "char_ref.rl" {te = p+1;{ output->first = 0x0109; {p++; goto _out; } }} break; case 790: #line 1067 "char_ref.rl" {te = p+1;{ output->first = 0x2a4c; {p++; goto _out; } }} break; case 791: #line 1068 "char_ref.rl" {te = p+1;{ output->first = 0x2a50; {p++; goto _out; } }} break; case 792: #line 1069 "char_ref.rl" {te = p+1;{ output->first = 0x010b; {p++; goto _out; } }} break; case 793: #line 1070 "char_ref.rl" {te = p+1;{ output->first = 0xb8; {p++; goto _out; } }} break; case 794: #line 1072 "char_ref.rl" {te = p+1;{ output->first = 0x29b2; {p++; goto _out; } }} break; case 795: #line 1073 "char_ref.rl" {te = p+1;{ output->first = 0xa2; {p++; goto _out; } }} break; case 796: #line 1075 "char_ref.rl" {te = p+1;{ output->first = 0xb7; {p++; goto _out; } }} break; case 797: #line 1076 "char_ref.rl" {te = p+1;{ output->first = 0x0001d520; {p++; goto _out; } }} break; case 798: #line 1077 "char_ref.rl" {te = p+1;{ output->first = 0x0447; {p++; goto _out; } }} break; case 799: #line 1078 "char_ref.rl" {te = p+1;{ output->first = 0x2713; {p++; goto _out; } }} break; case 800: #line 1079 "char_ref.rl" {te = p+1;{ output->first = 0x2713; {p++; goto _out; } }} break; case 801: #line 1080 "char_ref.rl" {te = p+1;{ output->first = 0x03c7; {p++; goto _out; } }} break; case 802: #line 1081 "char_ref.rl" {te = p+1;{ output->first = 0x25cb; {p++; goto _out; } }} break; case 803: #line 1082 "char_ref.rl" {te = p+1;{ output->first = 0x29c3; {p++; goto _out; } }} break; case 804: #line 1083 "char_ref.rl" {te = p+1;{ output->first = 0x02c6; {p++; goto _out; } }} break; case 805: #line 1084 "char_ref.rl" {te = p+1;{ output->first = 0x2257; {p++; goto _out; } }} break; case 806: #line 1085 "char_ref.rl" {te = p+1;{ output->first = 0x21ba; {p++; goto _out; } }} break; case 807: #line 1086 "char_ref.rl" {te = p+1;{ output->first = 0x21bb; {p++; goto _out; } }} break; case 808: #line 1087 "char_ref.rl" {te = p+1;{ output->first = 0xae; {p++; goto _out; } }} break; case 809: #line 1088 "char_ref.rl" {te = p+1;{ output->first = 0x24c8; {p++; goto _out; } }} break; case 810: #line 1089 "char_ref.rl" {te = p+1;{ output->first = 0x229b; {p++; goto _out; } }} break; case 811: #line 1090 "char_ref.rl" {te = p+1;{ output->first = 0x229a; {p++; goto _out; } }} break; case 812: #line 1091 "char_ref.rl" {te = p+1;{ output->first = 0x229d; {p++; goto _out; } }} break; case 813: #line 1092 "char_ref.rl" {te = p+1;{ output->first = 0x2257; {p++; goto _out; } }} break; case 814: #line 1093 "char_ref.rl" {te = p+1;{ output->first = 0x2a10; {p++; goto _out; } }} break; case 815: #line 1094 "char_ref.rl" {te = p+1;{ output->first = 0x2aef; {p++; goto _out; } }} break; case 816: #line 1095 "char_ref.rl" {te = p+1;{ output->first = 0x29c2; {p++; goto _out; } }} break; case 817: #line 1096 "char_ref.rl" {te = p+1;{ output->first = 0x2663; {p++; goto _out; } }} break; case 818: #line 1097 "char_ref.rl" {te = p+1;{ output->first = 0x2663; {p++; goto _out; } }} break; case 819: #line 1098 "char_ref.rl" {te = p+1;{ output->first = 0x3a; {p++; goto _out; } }} break; case 820: #line 1099 "char_ref.rl" {te = p+1;{ output->first = 0x2254; {p++; goto _out; } }} break; case 821: #line 1100 "char_ref.rl" {te = p+1;{ output->first = 0x2254; {p++; goto _out; } }} break; case 822: #line 1101 "char_ref.rl" {te = p+1;{ output->first = 0x2c; {p++; goto _out; } }} break; case 823: #line 1102 "char_ref.rl" {te = p+1;{ output->first = 0x40; {p++; goto _out; } }} break; case 824: #line 1103 "char_ref.rl" {te = p+1;{ output->first = 0x2201; {p++; goto _out; } }} break; case 825: #line 1104 "char_ref.rl" {te = p+1;{ output->first = 0x2218; {p++; goto _out; } }} break; case 826: #line 1105 "char_ref.rl" {te = p+1;{ output->first = 0x2201; {p++; goto _out; } }} break; case 827: #line 1106 "char_ref.rl" {te = p+1;{ output->first = 0x2102; {p++; goto _out; } }} break; case 828: #line 1107 "char_ref.rl" {te = p+1;{ output->first = 0x2245; {p++; goto _out; } }} break; case 829: #line 1108 "char_ref.rl" {te = p+1;{ output->first = 0x2a6d; {p++; goto _out; } }} break; case 830: #line 1109 "char_ref.rl" {te = p+1;{ output->first = 0x222e; {p++; goto _out; } }} break; case 831: #line 1110 "char_ref.rl" {te = p+1;{ output->first = 0x0001d554; {p++; goto _out; } }} break; case 832: #line 1111 "char_ref.rl" {te = p+1;{ output->first = 0x2210; {p++; goto _out; } }} break; case 833: #line 1112 "char_ref.rl" {te = p+1;{ output->first = 0xa9; {p++; goto _out; } }} break; case 834: #line 1114 "char_ref.rl" {te = p+1;{ output->first = 0x2117; {p++; goto _out; } }} break; case 835: #line 1115 "char_ref.rl" {te = p+1;{ output->first = 0x21b5; {p++; goto _out; } }} break; case 836: #line 1116 "char_ref.rl" {te = p+1;{ output->first = 0x2717; {p++; goto _out; } }} break; case 837: #line 1117 "char_ref.rl" {te = p+1;{ output->first = 0x0001d4b8; {p++; goto _out; } }} break; case 838: #line 1118 "char_ref.rl" {te = p+1;{ output->first = 0x2acf; {p++; goto _out; } }} break; case 839: #line 1119 "char_ref.rl" {te = p+1;{ output->first = 0x2ad1; {p++; goto _out; } }} break; case 840: #line 1120 "char_ref.rl" {te = p+1;{ output->first = 0x2ad0; {p++; goto _out; } }} break; case 841: #line 1121 "char_ref.rl" {te = p+1;{ output->first = 0x2ad2; {p++; goto _out; } }} break; case 842: #line 1122 "char_ref.rl" {te = p+1;{ output->first = 0x22ef; {p++; goto _out; } }} break; case 843: #line 1123 "char_ref.rl" {te = p+1;{ output->first = 0x2938; {p++; goto _out; } }} break; case 844: #line 1124 "char_ref.rl" {te = p+1;{ output->first = 0x2935; {p++; goto _out; } }} break; case 845: #line 1125 "char_ref.rl" {te = p+1;{ output->first = 0x22de; {p++; goto _out; } }} break; case 846: #line 1126 "char_ref.rl" {te = p+1;{ output->first = 0x22df; {p++; goto _out; } }} break; case 847: #line 1127 "char_ref.rl" {te = p+1;{ output->first = 0x21b6; {p++; goto _out; } }} break; case 848: #line 1128 "char_ref.rl" {te = p+1;{ output->first = 0x293d; {p++; goto _out; } }} break; case 849: #line 1129 "char_ref.rl" {te = p+1;{ output->first = 0x222a; {p++; goto _out; } }} break; case 850: #line 1130 "char_ref.rl" {te = p+1;{ output->first = 0x2a48; {p++; goto _out; } }} break; case 851: #line 1131 "char_ref.rl" {te = p+1;{ output->first = 0x2a46; {p++; goto _out; } }} break; case 852: #line 1132 "char_ref.rl" {te = p+1;{ output->first = 0x2a4a; {p++; goto _out; } }} break; case 853: #line 1133 "char_ref.rl" {te = p+1;{ output->first = 0x228d; {p++; goto _out; } }} break; case 854: #line 1134 "char_ref.rl" {te = p+1;{ output->first = 0x2a45; {p++; goto _out; } }} break; case 855: #line 1135 "char_ref.rl" {te = p+1;{ output->first = 0x222a; output->second = 0xfe00; {p++; goto _out; } }} break; case 856: #line 1136 "char_ref.rl" {te = p+1;{ output->first = 0x21b7; {p++; goto _out; } }} break; case 857: #line 1137 "char_ref.rl" {te = p+1;{ output->first = 0x293c; {p++; goto _out; } }} break; case 858: #line 1138 "char_ref.rl" {te = p+1;{ output->first = 0x22de; {p++; goto _out; } }} break; case 859: #line 1139 "char_ref.rl" {te = p+1;{ output->first = 0x22df; {p++; goto _out; } }} break; case 860: #line 1140 "char_ref.rl" {te = p+1;{ output->first = 0x22ce; {p++; goto _out; } }} break; case 861: #line 1141 "char_ref.rl" {te = p+1;{ output->first = 0x22cf; {p++; goto _out; } }} break; case 862: #line 1142 "char_ref.rl" {te = p+1;{ output->first = 0xa4; {p++; goto _out; } }} break; case 863: #line 1144 "char_ref.rl" {te = p+1;{ output->first = 0x21b6; {p++; goto _out; } }} break; case 864: #line 1145 "char_ref.rl" {te = p+1;{ output->first = 0x21b7; {p++; goto _out; } }} break; case 865: #line 1146 "char_ref.rl" {te = p+1;{ output->first = 0x22ce; {p++; goto _out; } }} break; case 866: #line 1147 "char_ref.rl" {te = p+1;{ output->first = 0x22cf; {p++; goto _out; } }} break; case 867: #line 1148 "char_ref.rl" {te = p+1;{ output->first = 0x2232; {p++; goto _out; } }} break; case 868: #line 1149 "char_ref.rl" {te = p+1;{ output->first = 0x2231; {p++; goto _out; } }} break; case 869: #line 1150 "char_ref.rl" {te = p+1;{ output->first = 0x232d; {p++; goto _out; } }} break; case 870: #line 1151 "char_ref.rl" {te = p+1;{ output->first = 0x21d3; {p++; goto _out; } }} break; case 871: #line 1152 "char_ref.rl" {te = p+1;{ output->first = 0x2965; {p++; goto _out; } }} break; case 872: #line 1153 "char_ref.rl" {te = p+1;{ output->first = 0x2020; {p++; goto _out; } }} break; case 873: #line 1154 "char_ref.rl" {te = p+1;{ output->first = 0x2138; {p++; goto _out; } }} break; case 874: #line 1155 "char_ref.rl" {te = p+1;{ output->first = 0x2193; {p++; goto _out; } }} break; case 875: #line 1156 "char_ref.rl" {te = p+1;{ output->first = 0x2010; {p++; goto _out; } }} break; case 876: #line 1157 "char_ref.rl" {te = p+1;{ output->first = 0x22a3; {p++; goto _out; } }} break; case 877: #line 1158 "char_ref.rl" {te = p+1;{ output->first = 0x290f; {p++; goto _out; } }} break; case 878: #line 1159 "char_ref.rl" {te = p+1;{ output->first = 0x02dd; {p++; goto _out; } }} break; case 879: #line 1160 "char_ref.rl" {te = p+1;{ output->first = 0x010f; {p++; goto _out; } }} break; case 880: #line 1161 "char_ref.rl" {te = p+1;{ output->first = 0x0434; {p++; goto _out; } }} break; case 881: #line 1162 "char_ref.rl" {te = p+1;{ output->first = 0x2146; {p++; goto _out; } }} break; case 882: #line 1163 "char_ref.rl" {te = p+1;{ output->first = 0x2021; {p++; goto _out; } }} break; case 883: #line 1164 "char_ref.rl" {te = p+1;{ output->first = 0x21ca; {p++; goto _out; } }} break; case 884: #line 1165 "char_ref.rl" {te = p+1;{ output->first = 0x2a77; {p++; goto _out; } }} break; case 885: #line 1166 "char_ref.rl" {te = p+1;{ output->first = 0xb0; {p++; goto _out; } }} break; case 886: #line 1168 "char_ref.rl" {te = p+1;{ output->first = 0x03b4; {p++; goto _out; } }} break; case 887: #line 1169 "char_ref.rl" {te = p+1;{ output->first = 0x29b1; {p++; goto _out; } }} break; case 888: #line 1170 "char_ref.rl" {te = p+1;{ output->first = 0x297f; {p++; goto _out; } }} break; case 889: #line 1171 "char_ref.rl" {te = p+1;{ output->first = 0x0001d521; {p++; goto _out; } }} break; case 890: #line 1172 "char_ref.rl" {te = p+1;{ output->first = 0x21c3; {p++; goto _out; } }} break; case 891: #line 1173 "char_ref.rl" {te = p+1;{ output->first = 0x21c2; {p++; goto _out; } }} break; case 892: #line 1174 "char_ref.rl" {te = p+1;{ output->first = 0x22c4; {p++; goto _out; } }} break; case 893: #line 1175 "char_ref.rl" {te = p+1;{ output->first = 0x22c4; {p++; goto _out; } }} break; case 894: #line 1176 "char_ref.rl" {te = p+1;{ output->first = 0x2666; {p++; goto _out; } }} break; case 895: #line 1177 "char_ref.rl" {te = p+1;{ output->first = 0x2666; {p++; goto _out; } }} break; case 896: #line 1178 "char_ref.rl" {te = p+1;{ output->first = 0xa8; {p++; goto _out; } }} break; case 897: #line 1179 "char_ref.rl" {te = p+1;{ output->first = 0x03dd; {p++; goto _out; } }} break; case 898: #line 1180 "char_ref.rl" {te = p+1;{ output->first = 0x22f2; {p++; goto _out; } }} break; case 899: #line 1181 "char_ref.rl" {te = p+1;{ output->first = 0xf7; {p++; goto _out; } }} break; case 900: #line 1182 "char_ref.rl" {te = p+1;{ output->first = 0xf7; {p++; goto _out; } }} break; case 901: #line 1184 "char_ref.rl" {te = p+1;{ output->first = 0x22c7; {p++; goto _out; } }} break; case 902: #line 1185 "char_ref.rl" {te = p+1;{ output->first = 0x22c7; {p++; goto _out; } }} break; case 903: #line 1186 "char_ref.rl" {te = p+1;{ output->first = 0x0452; {p++; goto _out; } }} break; case 904: #line 1187 "char_ref.rl" {te = p+1;{ output->first = 0x231e; {p++; goto _out; } }} break; case 905: #line 1188 "char_ref.rl" {te = p+1;{ output->first = 0x230d; {p++; goto _out; } }} break; case 906: #line 1189 "char_ref.rl" {te = p+1;{ output->first = 0x24; {p++; goto _out; } }} break; case 907: #line 1190 "char_ref.rl" {te = p+1;{ output->first = 0x0001d555; {p++; goto _out; } }} break; case 908: #line 1191 "char_ref.rl" {te = p+1;{ output->first = 0x02d9; {p++; goto _out; } }} break; case 909: #line 1192 "char_ref.rl" {te = p+1;{ output->first = 0x2250; {p++; goto _out; } }} break; case 910: #line 1193 "char_ref.rl" {te = p+1;{ output->first = 0x2251; {p++; goto _out; } }} break; case 911: #line 1194 "char_ref.rl" {te = p+1;{ output->first = 0x2238; {p++; goto _out; } }} break; case 912: #line 1195 "char_ref.rl" {te = p+1;{ output->first = 0x2214; {p++; goto _out; } }} break; case 913: #line 1196 "char_ref.rl" {te = p+1;{ output->first = 0x22a1; {p++; goto _out; } }} break; case 914: #line 1197 "char_ref.rl" {te = p+1;{ output->first = 0x2306; {p++; goto _out; } }} break; case 915: #line 1198 "char_ref.rl" {te = p+1;{ output->first = 0x2193; {p++; goto _out; } }} break; case 916: #line 1199 "char_ref.rl" {te = p+1;{ output->first = 0x21ca; {p++; goto _out; } }} break; case 917: #line 1200 "char_ref.rl" {te = p+1;{ output->first = 0x21c3; {p++; goto _out; } }} break; case 918: #line 1201 "char_ref.rl" {te = p+1;{ output->first = 0x21c2; {p++; goto _out; } }} break; case 919: #line 1202 "char_ref.rl" {te = p+1;{ output->first = 0x2910; {p++; goto _out; } }} break; case 920: #line 1203 "char_ref.rl" {te = p+1;{ output->first = 0x231f; {p++; goto _out; } }} break; case 921: #line 1204 "char_ref.rl" {te = p+1;{ output->first = 0x230c; {p++; goto _out; } }} break; case 922: #line 1205 "char_ref.rl" {te = p+1;{ output->first = 0x0001d4b9; {p++; goto _out; } }} break; case 923: #line 1206 "char_ref.rl" {te = p+1;{ output->first = 0x0455; {p++; goto _out; } }} break; case 924: #line 1207 "char_ref.rl" {te = p+1;{ output->first = 0x29f6; {p++; goto _out; } }} break; case 925: #line 1208 "char_ref.rl" {te = p+1;{ output->first = 0x0111; {p++; goto _out; } }} break; case 926: #line 1209 "char_ref.rl" {te = p+1;{ output->first = 0x22f1; {p++; goto _out; } }} break; case 927: #line 1210 "char_ref.rl" {te = p+1;{ output->first = 0x25bf; {p++; goto _out; } }} break; case 928: #line 1211 "char_ref.rl" {te = p+1;{ output->first = 0x25be; {p++; goto _out; } }} break; case 929: #line 1212 "char_ref.rl" {te = p+1;{ output->first = 0x21f5; {p++; goto _out; } }} break; case 930: #line 1213 "char_ref.rl" {te = p+1;{ output->first = 0x296f; {p++; goto _out; } }} break; case 931: #line 1214 "char_ref.rl" {te = p+1;{ output->first = 0x29a6; {p++; goto _out; } }} break; case 932: #line 1215 "char_ref.rl" {te = p+1;{ output->first = 0x045f; {p++; goto _out; } }} break; case 933: #line 1216 "char_ref.rl" {te = p+1;{ output->first = 0x27ff; {p++; goto _out; } }} break; case 934: #line 1217 "char_ref.rl" {te = p+1;{ output->first = 0x2a77; {p++; goto _out; } }} break; case 935: #line 1218 "char_ref.rl" {te = p+1;{ output->first = 0x2251; {p++; goto _out; } }} break; case 936: #line 1219 "char_ref.rl" {te = p+1;{ output->first = 0xe9; {p++; goto _out; } }} break; case 937: #line 1221 "char_ref.rl" {te = p+1;{ output->first = 0x2a6e; {p++; goto _out; } }} break; case 938: #line 1222 "char_ref.rl" {te = p+1;{ output->first = 0x011b; {p++; goto _out; } }} break; case 939: #line 1223 "char_ref.rl" {te = p+1;{ output->first = 0x2256; {p++; goto _out; } }} break; case 940: #line 1224 "char_ref.rl" {te = p+1;{ output->first = 0xea; {p++; goto _out; } }} break; case 941: #line 1226 "char_ref.rl" {te = p+1;{ output->first = 0x2255; {p++; goto _out; } }} break; case 942: #line 1227 "char_ref.rl" {te = p+1;{ output->first = 0x044d; {p++; goto _out; } }} break; case 943: #line 1228 "char_ref.rl" {te = p+1;{ output->first = 0x0117; {p++; goto _out; } }} break; case 944: #line 1229 "char_ref.rl" {te = p+1;{ output->first = 0x2147; {p++; goto _out; } }} break; case 945: #line 1230 "char_ref.rl" {te = p+1;{ output->first = 0x2252; {p++; goto _out; } }} break; case 946: #line 1231 "char_ref.rl" {te = p+1;{ output->first = 0x0001d522; {p++; goto _out; } }} break; case 947: #line 1232 "char_ref.rl" {te = p+1;{ output->first = 0x2a9a; {p++; goto _out; } }} break; case 948: #line 1233 "char_ref.rl" {te = p+1;{ output->first = 0xe8; {p++; goto _out; } }} break; case 949: #line 1235 "char_ref.rl" {te = p+1;{ output->first = 0x2a96; {p++; goto _out; } }} break; case 950: #line 1236 "char_ref.rl" {te = p+1;{ output->first = 0x2a98; {p++; goto _out; } }} break; case 951: #line 1237 "char_ref.rl" {te = p+1;{ output->first = 0x2a99; {p++; goto _out; } }} break; case 952: #line 1238 "char_ref.rl" {te = p+1;{ output->first = 0x23e7; {p++; goto _out; } }} break; case 953: #line 1239 "char_ref.rl" {te = p+1;{ output->first = 0x2113; {p++; goto _out; } }} break; case 954: #line 1240 "char_ref.rl" {te = p+1;{ output->first = 0x2a95; {p++; goto _out; } }} break; case 955: #line 1241 "char_ref.rl" {te = p+1;{ output->first = 0x2a97; {p++; goto _out; } }} break; case 956: #line 1242 "char_ref.rl" {te = p+1;{ output->first = 0x0113; {p++; goto _out; } }} break; case 957: #line 1243 "char_ref.rl" {te = p+1;{ output->first = 0x2205; {p++; goto _out; } }} break; case 958: #line 1244 "char_ref.rl" {te = p+1;{ output->first = 0x2205; {p++; goto _out; } }} break; case 959: #line 1245 "char_ref.rl" {te = p+1;{ output->first = 0x2205; {p++; goto _out; } }} break; case 960: #line 1246 "char_ref.rl" {te = p+1;{ output->first = 0x2004; {p++; goto _out; } }} break; case 961: #line 1247 "char_ref.rl" {te = p+1;{ output->first = 0x2005; {p++; goto _out; } }} break; case 962: #line 1248 "char_ref.rl" {te = p+1;{ output->first = 0x2003; {p++; goto _out; } }} break; case 963: #line 1249 "char_ref.rl" {te = p+1;{ output->first = 0x014b; {p++; goto _out; } }} break; case 964: #line 1250 "char_ref.rl" {te = p+1;{ output->first = 0x2002; {p++; goto _out; } }} break; case 965: #line 1251 "char_ref.rl" {te = p+1;{ output->first = 0x0119; {p++; goto _out; } }} break; case 966: #line 1252 "char_ref.rl" {te = p+1;{ output->first = 0x0001d556; {p++; goto _out; } }} break; case 967: #line 1253 "char_ref.rl" {te = p+1;{ output->first = 0x22d5; {p++; goto _out; } }} break; case 968: #line 1254 "char_ref.rl" {te = p+1;{ output->first = 0x29e3; {p++; goto _out; } }} break; case 969: #line 1255 "char_ref.rl" {te = p+1;{ output->first = 0x2a71; {p++; goto _out; } }} break; case 970: #line 1256 "char_ref.rl" {te = p+1;{ output->first = 0x03b5; {p++; goto _out; } }} break; case 971: #line 1257 "char_ref.rl" {te = p+1;{ output->first = 0x03b5; {p++; goto _out; } }} break; case 972: #line 1258 "char_ref.rl" {te = p+1;{ output->first = 0x03f5; {p++; goto _out; } }} break; case 973: #line 1259 "char_ref.rl" {te = p+1;{ output->first = 0x2256; {p++; goto _out; } }} break; case 974: #line 1260 "char_ref.rl" {te = p+1;{ output->first = 0x2255; {p++; goto _out; } }} break; case 975: #line 1261 "char_ref.rl" {te = p+1;{ output->first = 0x2242; {p++; goto _out; } }} break; case 976: #line 1262 "char_ref.rl" {te = p+1;{ output->first = 0x2a96; {p++; goto _out; } }} break; case 977: #line 1263 "char_ref.rl" {te = p+1;{ output->first = 0x2a95; {p++; goto _out; } }} break; case 978: #line 1264 "char_ref.rl" {te = p+1;{ output->first = 0x3d; {p++; goto _out; } }} break; case 979: #line 1265 "char_ref.rl" {te = p+1;{ output->first = 0x225f; {p++; goto _out; } }} break; case 980: #line 1266 "char_ref.rl" {te = p+1;{ output->first = 0x2261; {p++; goto _out; } }} break; case 981: #line 1267 "char_ref.rl" {te = p+1;{ output->first = 0x2a78; {p++; goto _out; } }} break; case 982: #line 1268 "char_ref.rl" {te = p+1;{ output->first = 0x29e5; {p++; goto _out; } }} break; case 983: #line 1269 "char_ref.rl" {te = p+1;{ output->first = 0x2253; {p++; goto _out; } }} break; case 984: #line 1270 "char_ref.rl" {te = p+1;{ output->first = 0x2971; {p++; goto _out; } }} break; case 985: #line 1271 "char_ref.rl" {te = p+1;{ output->first = 0x212f; {p++; goto _out; } }} break; case 986: #line 1272 "char_ref.rl" {te = p+1;{ output->first = 0x2250; {p++; goto _out; } }} break; case 987: #line 1273 "char_ref.rl" {te = p+1;{ output->first = 0x2242; {p++; goto _out; } }} break; case 988: #line 1274 "char_ref.rl" {te = p+1;{ output->first = 0x03b7; {p++; goto _out; } }} break; case 989: #line 1275 "char_ref.rl" {te = p+1;{ output->first = 0xf0; {p++; goto _out; } }} break; case 990: #line 1277 "char_ref.rl" {te = p+1;{ output->first = 0xeb; {p++; goto _out; } }} break; case 991: #line 1279 "char_ref.rl" {te = p+1;{ output->first = 0x20ac; {p++; goto _out; } }} break; case 992: #line 1280 "char_ref.rl" {te = p+1;{ output->first = 0x21; {p++; goto _out; } }} break; case 993: #line 1281 "char_ref.rl" {te = p+1;{ output->first = 0x2203; {p++; goto _out; } }} break; case 994: #line 1282 "char_ref.rl" {te = p+1;{ output->first = 0x2130; {p++; goto _out; } }} break; case 995: #line 1283 "char_ref.rl" {te = p+1;{ output->first = 0x2147; {p++; goto _out; } }} break; case 996: #line 1284 "char_ref.rl" {te = p+1;{ output->first = 0x2252; {p++; goto _out; } }} break; case 997: #line 1285 "char_ref.rl" {te = p+1;{ output->first = 0x0444; {p++; goto _out; } }} break; case 998: #line 1286 "char_ref.rl" {te = p+1;{ output->first = 0x2640; {p++; goto _out; } }} break; case 999: #line 1287 "char_ref.rl" {te = p+1;{ output->first = 0xfb03; {p++; goto _out; } }} break; case 1000: #line 1288 "char_ref.rl" {te = p+1;{ output->first = 0xfb00; {p++; goto _out; } }} break; case 1001: #line 1289 "char_ref.rl" {te = p+1;{ output->first = 0xfb04; {p++; goto _out; } }} break; case 1002: #line 1290 "char_ref.rl" {te = p+1;{ output->first = 0x0001d523; {p++; goto _out; } }} break; case 1003: #line 1291 "char_ref.rl" {te = p+1;{ output->first = 0xfb01; {p++; goto _out; } }} break; case 1004: #line 1292 "char_ref.rl" {te = p+1;{ output->first = 0x66; output->second = 0x6a; {p++; goto _out; } }} break; case 1005: #line 1293 "char_ref.rl" {te = p+1;{ output->first = 0x266d; {p++; goto _out; } }} break; case 1006: #line 1294 "char_ref.rl" {te = p+1;{ output->first = 0xfb02; {p++; goto _out; } }} break; case 1007: #line 1295 "char_ref.rl" {te = p+1;{ output->first = 0x25b1; {p++; goto _out; } }} break; case 1008: #line 1296 "char_ref.rl" {te = p+1;{ output->first = 0x0192; {p++; goto _out; } }} break; case 1009: #line 1297 "char_ref.rl" {te = p+1;{ output->first = 0x0001d557; {p++; goto _out; } }} break; case 1010: #line 1298 "char_ref.rl" {te = p+1;{ output->first = 0x2200; {p++; goto _out; } }} break; case 1011: #line 1299 "char_ref.rl" {te = p+1;{ output->first = 0x22d4; {p++; goto _out; } }} break; case 1012: #line 1300 "char_ref.rl" {te = p+1;{ output->first = 0x2ad9; {p++; goto _out; } }} break; case 1013: #line 1301 "char_ref.rl" {te = p+1;{ output->first = 0x2a0d; {p++; goto _out; } }} break; case 1014: #line 1302 "char_ref.rl" {te = p+1;{ output->first = 0xbd; {p++; goto _out; } }} break; case 1015: #line 1304 "char_ref.rl" {te = p+1;{ output->first = 0x2153; {p++; goto _out; } }} break; case 1016: #line 1305 "char_ref.rl" {te = p+1;{ output->first = 0xbc; {p++; goto _out; } }} break; case 1017: #line 1307 "char_ref.rl" {te = p+1;{ output->first = 0x2155; {p++; goto _out; } }} break; case 1018: #line 1308 "char_ref.rl" {te = p+1;{ output->first = 0x2159; {p++; goto _out; } }} break; case 1019: #line 1309 "char_ref.rl" {te = p+1;{ output->first = 0x215b; {p++; goto _out; } }} break; case 1020: #line 1310 "char_ref.rl" {te = p+1;{ output->first = 0x2154; {p++; goto _out; } }} break; case 1021: #line 1311 "char_ref.rl" {te = p+1;{ output->first = 0x2156; {p++; goto _out; } }} break; case 1022: #line 1312 "char_ref.rl" {te = p+1;{ output->first = 0xbe; {p++; goto _out; } }} break; case 1023: #line 1314 "char_ref.rl" {te = p+1;{ output->first = 0x2157; {p++; goto _out; } }} break; case 1024: #line 1315 "char_ref.rl" {te = p+1;{ output->first = 0x215c; {p++; goto _out; } }} break; case 1025: #line 1316 "char_ref.rl" {te = p+1;{ output->first = 0x2158; {p++; goto _out; } }} break; case 1026: #line 1317 "char_ref.rl" {te = p+1;{ output->first = 0x215a; {p++; goto _out; } }} break; case 1027: #line 1318 "char_ref.rl" {te = p+1;{ output->first = 0x215d; {p++; goto _out; } }} break; case 1028: #line 1319 "char_ref.rl" {te = p+1;{ output->first = 0x215e; {p++; goto _out; } }} break; case 1029: #line 1320 "char_ref.rl" {te = p+1;{ output->first = 0x2044; {p++; goto _out; } }} break; case 1030: #line 1321 "char_ref.rl" {te = p+1;{ output->first = 0x2322; {p++; goto _out; } }} break; case 1031: #line 1322 "char_ref.rl" {te = p+1;{ output->first = 0x0001d4bb; {p++; goto _out; } }} break; case 1032: #line 1323 "char_ref.rl" {te = p+1;{ output->first = 0x2267; {p++; goto _out; } }} break; case 1033: #line 1324 "char_ref.rl" {te = p+1;{ output->first = 0x2a8c; {p++; goto _out; } }} break; case 1034: #line 1325 "char_ref.rl" {te = p+1;{ output->first = 0x01f5; {p++; goto _out; } }} break; case 1035: #line 1326 "char_ref.rl" {te = p+1;{ output->first = 0x03b3; {p++; goto _out; } }} break; case 1036: #line 1327 "char_ref.rl" {te = p+1;{ output->first = 0x03dd; {p++; goto _out; } }} break; case 1037: #line 1328 "char_ref.rl" {te = p+1;{ output->first = 0x2a86; {p++; goto _out; } }} break; case 1038: #line 1329 "char_ref.rl" {te = p+1;{ output->first = 0x011f; {p++; goto _out; } }} break; case 1039: #line 1330 "char_ref.rl" {te = p+1;{ output->first = 0x011d; {p++; goto _out; } }} break; case 1040: #line 1331 "char_ref.rl" {te = p+1;{ output->first = 0x0433; {p++; goto _out; } }} break; case 1041: #line 1332 "char_ref.rl" {te = p+1;{ output->first = 0x0121; {p++; goto _out; } }} break; case 1042: #line 1333 "char_ref.rl" {te = p+1;{ output->first = 0x2265; {p++; goto _out; } }} break; case 1043: #line 1334 "char_ref.rl" {te = p+1;{ output->first = 0x22db; {p++; goto _out; } }} break; case 1044: #line 1335 "char_ref.rl" {te = p+1;{ output->first = 0x2265; {p++; goto _out; } }} break; case 1045: #line 1336 "char_ref.rl" {te = p+1;{ output->first = 0x2267; {p++; goto _out; } }} break; case 1046: #line 1337 "char_ref.rl" {te = p+1;{ output->first = 0x2a7e; {p++; goto _out; } }} break; case 1047: #line 1338 "char_ref.rl" {te = p+1;{ output->first = 0x2a7e; {p++; goto _out; } }} break; case 1048: #line 1339 "char_ref.rl" {te = p+1;{ output->first = 0x2aa9; {p++; goto _out; } }} break; case 1049: #line 1340 "char_ref.rl" {te = p+1;{ output->first = 0x2a80; {p++; goto _out; } }} break; case 1050: #line 1341 "char_ref.rl" {te = p+1;{ output->first = 0x2a82; {p++; goto _out; } }} break; case 1051: #line 1342 "char_ref.rl" {te = p+1;{ output->first = 0x2a84; {p++; goto _out; } }} break; case 1052: #line 1343 "char_ref.rl" {te = p+1;{ output->first = 0x22db; output->second = 0xfe00; {p++; goto _out; } }} break; case 1053: #line 1344 "char_ref.rl" {te = p+1;{ output->first = 0x2a94; {p++; goto _out; } }} break; case 1054: #line 1345 "char_ref.rl" {te = p+1;{ output->first = 0x0001d524; {p++; goto _out; } }} break; case 1055: #line 1346 "char_ref.rl" {te = p+1;{ output->first = 0x226b; {p++; goto _out; } }} break; case 1056: #line 1347 "char_ref.rl" {te = p+1;{ output->first = 0x22d9; {p++; goto _out; } }} break; case 1057: #line 1348 "char_ref.rl" {te = p+1;{ output->first = 0x2137; {p++; goto _out; } }} break; case 1058: #line 1349 "char_ref.rl" {te = p+1;{ output->first = 0x0453; {p++; goto _out; } }} break; case 1059: #line 1350 "char_ref.rl" {te = p+1;{ output->first = 0x2277; {p++; goto _out; } }} break; case 1060: #line 1351 "char_ref.rl" {te = p+1;{ output->first = 0x2a92; {p++; goto _out; } }} break; case 1061: #line 1352 "char_ref.rl" {te = p+1;{ output->first = 0x2aa5; {p++; goto _out; } }} break; case 1062: #line 1353 "char_ref.rl" {te = p+1;{ output->first = 0x2aa4; {p++; goto _out; } }} break; case 1063: #line 1354 "char_ref.rl" {te = p+1;{ output->first = 0x2269; {p++; goto _out; } }} break; case 1064: #line 1355 "char_ref.rl" {te = p+1;{ output->first = 0x2a8a; {p++; goto _out; } }} break; case 1065: #line 1356 "char_ref.rl" {te = p+1;{ output->first = 0x2a8a; {p++; goto _out; } }} break; case 1066: #line 1357 "char_ref.rl" {te = p+1;{ output->first = 0x2a88; {p++; goto _out; } }} break; case 1067: #line 1358 "char_ref.rl" {te = p+1;{ output->first = 0x2a88; {p++; goto _out; } }} break; case 1068: #line 1359 "char_ref.rl" {te = p+1;{ output->first = 0x2269; {p++; goto _out; } }} break; case 1069: #line 1360 "char_ref.rl" {te = p+1;{ output->first = 0x22e7; {p++; goto _out; } }} break; case 1070: #line 1361 "char_ref.rl" {te = p+1;{ output->first = 0x0001d558; {p++; goto _out; } }} break; case 1071: #line 1362 "char_ref.rl" {te = p+1;{ output->first = 0x60; {p++; goto _out; } }} break; case 1072: #line 1363 "char_ref.rl" {te = p+1;{ output->first = 0x210a; {p++; goto _out; } }} break; case 1073: #line 1364 "char_ref.rl" {te = p+1;{ output->first = 0x2273; {p++; goto _out; } }} break; case 1074: #line 1365 "char_ref.rl" {te = p+1;{ output->first = 0x2a8e; {p++; goto _out; } }} break; case 1075: #line 1366 "char_ref.rl" {te = p+1;{ output->first = 0x2a90; {p++; goto _out; } }} break; case 1076: #line 1367 "char_ref.rl" {te = p+1;{ output->first = 0x3e; {p++; goto _out; } }} break; case 1077: #line 1369 "char_ref.rl" {te = p+1;{ output->first = 0x2aa7; {p++; goto _out; } }} break; case 1078: #line 1370 "char_ref.rl" {te = p+1;{ output->first = 0x2a7a; {p++; goto _out; } }} break; case 1079: #line 1371 "char_ref.rl" {te = p+1;{ output->first = 0x22d7; {p++; goto _out; } }} break; case 1080: #line 1372 "char_ref.rl" {te = p+1;{ output->first = 0x2995; {p++; goto _out; } }} break; case 1081: #line 1373 "char_ref.rl" {te = p+1;{ output->first = 0x2a7c; {p++; goto _out; } }} break; case 1082: #line 1374 "char_ref.rl" {te = p+1;{ output->first = 0x2a86; {p++; goto _out; } }} break; case 1083: #line 1375 "char_ref.rl" {te = p+1;{ output->first = 0x2978; {p++; goto _out; } }} break; case 1084: #line 1376 "char_ref.rl" {te = p+1;{ output->first = 0x22d7; {p++; goto _out; } }} break; case 1085: #line 1377 "char_ref.rl" {te = p+1;{ output->first = 0x22db; {p++; goto _out; } }} break; case 1086: #line 1378 "char_ref.rl" {te = p+1;{ output->first = 0x2a8c; {p++; goto _out; } }} break; case 1087: #line 1379 "char_ref.rl" {te = p+1;{ output->first = 0x2277; {p++; goto _out; } }} break; case 1088: #line 1380 "char_ref.rl" {te = p+1;{ output->first = 0x2273; {p++; goto _out; } }} break; case 1089: #line 1381 "char_ref.rl" {te = p+1;{ output->first = 0x2269; output->second = 0xfe00; {p++; goto _out; } }} break; case 1090: #line 1382 "char_ref.rl" {te = p+1;{ output->first = 0x2269; output->second = 0xfe00; {p++; goto _out; } }} break; case 1091: #line 1383 "char_ref.rl" {te = p+1;{ output->first = 0x21d4; {p++; goto _out; } }} break; case 1092: #line 1384 "char_ref.rl" {te = p+1;{ output->first = 0x200a; {p++; goto _out; } }} break; case 1093: #line 1385 "char_ref.rl" {te = p+1;{ output->first = 0xbd; {p++; goto _out; } }} break; case 1094: #line 1386 "char_ref.rl" {te = p+1;{ output->first = 0x210b; {p++; goto _out; } }} break; case 1095: #line 1387 "char_ref.rl" {te = p+1;{ output->first = 0x044a; {p++; goto _out; } }} break; case 1096: #line 1388 "char_ref.rl" {te = p+1;{ output->first = 0x2194; {p++; goto _out; } }} break; case 1097: #line 1389 "char_ref.rl" {te = p+1;{ output->first = 0x2948; {p++; goto _out; } }} break; case 1098: #line 1390 "char_ref.rl" {te = p+1;{ output->first = 0x21ad; {p++; goto _out; } }} break; case 1099: #line 1391 "char_ref.rl" {te = p+1;{ output->first = 0x210f; {p++; goto _out; } }} break; case 1100: #line 1392 "char_ref.rl" {te = p+1;{ output->first = 0x0125; {p++; goto _out; } }} break; case 1101: #line 1393 "char_ref.rl" {te = p+1;{ output->first = 0x2665; {p++; goto _out; } }} break; case 1102: #line 1394 "char_ref.rl" {te = p+1;{ output->first = 0x2665; {p++; goto _out; } }} break; case 1103: #line 1395 "char_ref.rl" {te = p+1;{ output->first = 0x2026; {p++; goto _out; } }} break; case 1104: #line 1396 "char_ref.rl" {te = p+1;{ output->first = 0x22b9; {p++; goto _out; } }} break; case 1105: #line 1397 "char_ref.rl" {te = p+1;{ output->first = 0x0001d525; {p++; goto _out; } }} break; case 1106: #line 1398 "char_ref.rl" {te = p+1;{ output->first = 0x2925; {p++; goto _out; } }} break; case 1107: #line 1399 "char_ref.rl" {te = p+1;{ output->first = 0x2926; {p++; goto _out; } }} break; case 1108: #line 1400 "char_ref.rl" {te = p+1;{ output->first = 0x21ff; {p++; goto _out; } }} break; case 1109: #line 1401 "char_ref.rl" {te = p+1;{ output->first = 0x223b; {p++; goto _out; } }} break; case 1110: #line 1402 "char_ref.rl" {te = p+1;{ output->first = 0x21a9; {p++; goto _out; } }} break; case 1111: #line 1403 "char_ref.rl" {te = p+1;{ output->first = 0x21aa; {p++; goto _out; } }} break; case 1112: #line 1404 "char_ref.rl" {te = p+1;{ output->first = 0x0001d559; {p++; goto _out; } }} break; case 1113: #line 1405 "char_ref.rl" {te = p+1;{ output->first = 0x2015; {p++; goto _out; } }} break; case 1114: #line 1406 "char_ref.rl" {te = p+1;{ output->first = 0x0001d4bd; {p++; goto _out; } }} break; case 1115: #line 1407 "char_ref.rl" {te = p+1;{ output->first = 0x210f; {p++; goto _out; } }} break; case 1116: #line 1408 "char_ref.rl" {te = p+1;{ output->first = 0x0127; {p++; goto _out; } }} break; case 1117: #line 1409 "char_ref.rl" {te = p+1;{ output->first = 0x2043; {p++; goto _out; } }} break; case 1118: #line 1410 "char_ref.rl" {te = p+1;{ output->first = 0x2010; {p++; goto _out; } }} break; case 1119: #line 1411 "char_ref.rl" {te = p+1;{ output->first = 0xed; {p++; goto _out; } }} break; case 1120: #line 1413 "char_ref.rl" {te = p+1;{ output->first = 0x2063; {p++; goto _out; } }} break; case 1121: #line 1414 "char_ref.rl" {te = p+1;{ output->first = 0xee; {p++; goto _out; } }} break; case 1122: #line 1416 "char_ref.rl" {te = p+1;{ output->first = 0x0438; {p++; goto _out; } }} break; case 1123: #line 1417 "char_ref.rl" {te = p+1;{ output->first = 0x0435; {p++; goto _out; } }} break; case 1124: #line 1418 "char_ref.rl" {te = p+1;{ output->first = 0xa1; {p++; goto _out; } }} break; case 1125: #line 1420 "char_ref.rl" {te = p+1;{ output->first = 0x21d4; {p++; goto _out; } }} break; case 1126: #line 1421 "char_ref.rl" {te = p+1;{ output->first = 0x0001d526; {p++; goto _out; } }} break; case 1127: #line 1422 "char_ref.rl" {te = p+1;{ output->first = 0xec; {p++; goto _out; } }} break; case 1128: #line 1424 "char_ref.rl" {te = p+1;{ output->first = 0x2148; {p++; goto _out; } }} break; case 1129: #line 1425 "char_ref.rl" {te = p+1;{ output->first = 0x2a0c; {p++; goto _out; } }} break; case 1130: #line 1426 "char_ref.rl" {te = p+1;{ output->first = 0x222d; {p++; goto _out; } }} break; case 1131: #line 1427 "char_ref.rl" {te = p+1;{ output->first = 0x29dc; {p++; goto _out; } }} break; case 1132: #line 1428 "char_ref.rl" {te = p+1;{ output->first = 0x2129; {p++; goto _out; } }} break; case 1133: #line 1429 "char_ref.rl" {te = p+1;{ output->first = 0x0133; {p++; goto _out; } }} break; case 1134: #line 1430 "char_ref.rl" {te = p+1;{ output->first = 0x012b; {p++; goto _out; } }} break; case 1135: #line 1431 "char_ref.rl" {te = p+1;{ output->first = 0x2111; {p++; goto _out; } }} break; case 1136: #line 1432 "char_ref.rl" {te = p+1;{ output->first = 0x2110; {p++; goto _out; } }} break; case 1137: #line 1433 "char_ref.rl" {te = p+1;{ output->first = 0x2111; {p++; goto _out; } }} break; case 1138: #line 1434 "char_ref.rl" {te = p+1;{ output->first = 0x0131; {p++; goto _out; } }} break; case 1139: #line 1435 "char_ref.rl" {te = p+1;{ output->first = 0x22b7; {p++; goto _out; } }} break; case 1140: #line 1436 "char_ref.rl" {te = p+1;{ output->first = 0x01b5; {p++; goto _out; } }} break; case 1141: #line 1437 "char_ref.rl" {te = p+1;{ output->first = 0x2208; {p++; goto _out; } }} break; case 1142: #line 1438 "char_ref.rl" {te = p+1;{ output->first = 0x2105; {p++; goto _out; } }} break; case 1143: #line 1439 "char_ref.rl" {te = p+1;{ output->first = 0x221e; {p++; goto _out; } }} break; case 1144: #line 1440 "char_ref.rl" {te = p+1;{ output->first = 0x29dd; {p++; goto _out; } }} break; case 1145: #line 1441 "char_ref.rl" {te = p+1;{ output->first = 0x0131; {p++; goto _out; } }} break; case 1146: #line 1442 "char_ref.rl" {te = p+1;{ output->first = 0x222b; {p++; goto _out; } }} break; case 1147: #line 1443 "char_ref.rl" {te = p+1;{ output->first = 0x22ba; {p++; goto _out; } }} break; case 1148: #line 1444 "char_ref.rl" {te = p+1;{ output->first = 0x2124; {p++; goto _out; } }} break; case 1149: #line 1445 "char_ref.rl" {te = p+1;{ output->first = 0x22ba; {p++; goto _out; } }} break; case 1150: #line 1446 "char_ref.rl" {te = p+1;{ output->first = 0x2a17; {p++; goto _out; } }} break; case 1151: #line 1447 "char_ref.rl" {te = p+1;{ output->first = 0x2a3c; {p++; goto _out; } }} break; case 1152: #line 1448 "char_ref.rl" {te = p+1;{ output->first = 0x0451; {p++; goto _out; } }} break; case 1153: #line 1449 "char_ref.rl" {te = p+1;{ output->first = 0x012f; {p++; goto _out; } }} break; case 1154: #line 1450 "char_ref.rl" {te = p+1;{ output->first = 0x0001d55a; {p++; goto _out; } }} break; case 1155: #line 1451 "char_ref.rl" {te = p+1;{ output->first = 0x03b9; {p++; goto _out; } }} break; case 1156: #line 1452 "char_ref.rl" {te = p+1;{ output->first = 0x2a3c; {p++; goto _out; } }} break; case 1157: #line 1453 "char_ref.rl" {te = p+1;{ output->first = 0xbf; {p++; goto _out; } }} break; case 1158: #line 1455 "char_ref.rl" {te = p+1;{ output->first = 0x0001d4be; {p++; goto _out; } }} break; case 1159: #line 1456 "char_ref.rl" {te = p+1;{ output->first = 0x2208; {p++; goto _out; } }} break; case 1160: #line 1457 "char_ref.rl" {te = p+1;{ output->first = 0x22f9; {p++; goto _out; } }} break; case 1161: #line 1458 "char_ref.rl" {te = p+1;{ output->first = 0x22f5; {p++; goto _out; } }} break; case 1162: #line 1459 "char_ref.rl" {te = p+1;{ output->first = 0x22f4; {p++; goto _out; } }} break; case 1163: #line 1460 "char_ref.rl" {te = p+1;{ output->first = 0x22f3; {p++; goto _out; } }} break; case 1164: #line 1461 "char_ref.rl" {te = p+1;{ output->first = 0x2208; {p++; goto _out; } }} break; case 1165: #line 1462 "char_ref.rl" {te = p+1;{ output->first = 0x2062; {p++; goto _out; } }} break; case 1166: #line 1463 "char_ref.rl" {te = p+1;{ output->first = 0x0129; {p++; goto _out; } }} break; case 1167: #line 1464 "char_ref.rl" {te = p+1;{ output->first = 0x0456; {p++; goto _out; } }} break; case 1168: #line 1465 "char_ref.rl" {te = p+1;{ output->first = 0xef; {p++; goto _out; } }} break; case 1169: #line 1467 "char_ref.rl" {te = p+1;{ output->first = 0x0135; {p++; goto _out; } }} break; case 1170: #line 1468 "char_ref.rl" {te = p+1;{ output->first = 0x0439; {p++; goto _out; } }} break; case 1171: #line 1469 "char_ref.rl" {te = p+1;{ output->first = 0x0001d527; {p++; goto _out; } }} break; case 1172: #line 1470 "char_ref.rl" {te = p+1;{ output->first = 0x0237; {p++; goto _out; } }} break; case 1173: #line 1471 "char_ref.rl" {te = p+1;{ output->first = 0x0001d55b; {p++; goto _out; } }} break; case 1174: #line 1472 "char_ref.rl" {te = p+1;{ output->first = 0x0001d4bf; {p++; goto _out; } }} break; case 1175: #line 1473 "char_ref.rl" {te = p+1;{ output->first = 0x0458; {p++; goto _out; } }} break; case 1176: #line 1474 "char_ref.rl" {te = p+1;{ output->first = 0x0454; {p++; goto _out; } }} break; case 1177: #line 1475 "char_ref.rl" {te = p+1;{ output->first = 0x03ba; {p++; goto _out; } }} break; case 1178: #line 1476 "char_ref.rl" {te = p+1;{ output->first = 0x03f0; {p++; goto _out; } }} break; case 1179: #line 1477 "char_ref.rl" {te = p+1;{ output->first = 0x0137; {p++; goto _out; } }} break; case 1180: #line 1478 "char_ref.rl" {te = p+1;{ output->first = 0x043a; {p++; goto _out; } }} break; case 1181: #line 1479 "char_ref.rl" {te = p+1;{ output->first = 0x0001d528; {p++; goto _out; } }} break; case 1182: #line 1480 "char_ref.rl" {te = p+1;{ output->first = 0x0138; {p++; goto _out; } }} break; case 1183: #line 1481 "char_ref.rl" {te = p+1;{ output->first = 0x0445; {p++; goto _out; } }} break; case 1184: #line 1482 "char_ref.rl" {te = p+1;{ output->first = 0x045c; {p++; goto _out; } }} break; case 1185: #line 1483 "char_ref.rl" {te = p+1;{ output->first = 0x0001d55c; {p++; goto _out; } }} break; case 1186: #line 1484 "char_ref.rl" {te = p+1;{ output->first = 0x0001d4c0; {p++; goto _out; } }} break; case 1187: #line 1485 "char_ref.rl" {te = p+1;{ output->first = 0x21da; {p++; goto _out; } }} break; case 1188: #line 1486 "char_ref.rl" {te = p+1;{ output->first = 0x21d0; {p++; goto _out; } }} break; case 1189: #line 1487 "char_ref.rl" {te = p+1;{ output->first = 0x291b; {p++; goto _out; } }} break; case 1190: #line 1488 "char_ref.rl" {te = p+1;{ output->first = 0x290e; {p++; goto _out; } }} break; case 1191: #line 1489 "char_ref.rl" {te = p+1;{ output->first = 0x2266; {p++; goto _out; } }} break; case 1192: #line 1490 "char_ref.rl" {te = p+1;{ output->first = 0x2a8b; {p++; goto _out; } }} break; case 1193: #line 1491 "char_ref.rl" {te = p+1;{ output->first = 0x2962; {p++; goto _out; } }} break; case 1194: #line 1492 "char_ref.rl" {te = p+1;{ output->first = 0x013a; {p++; goto _out; } }} break; case 1195: #line 1493 "char_ref.rl" {te = p+1;{ output->first = 0x29b4; {p++; goto _out; } }} break; case 1196: #line 1494 "char_ref.rl" {te = p+1;{ output->first = 0x2112; {p++; goto _out; } }} break; case 1197: #line 1495 "char_ref.rl" {te = p+1;{ output->first = 0x03bb; {p++; goto _out; } }} break; case 1198: #line 1496 "char_ref.rl" {te = p+1;{ output->first = 0x27e8; {p++; goto _out; } }} break; case 1199: #line 1497 "char_ref.rl" {te = p+1;{ output->first = 0x2991; {p++; goto _out; } }} break; case 1200: #line 1498 "char_ref.rl" {te = p+1;{ output->first = 0x27e8; {p++; goto _out; } }} break; case 1201: #line 1499 "char_ref.rl" {te = p+1;{ output->first = 0x2a85; {p++; goto _out; } }} break; case 1202: #line 1500 "char_ref.rl" {te = p+1;{ output->first = 0xab; {p++; goto _out; } }} break; case 1203: #line 1502 "char_ref.rl" {te = p+1;{ output->first = 0x2190; {p++; goto _out; } }} break; case 1204: #line 1503 "char_ref.rl" {te = p+1;{ output->first = 0x21e4; {p++; goto _out; } }} break; case 1205: #line 1504 "char_ref.rl" {te = p+1;{ output->first = 0x291f; {p++; goto _out; } }} break; case 1206: #line 1505 "char_ref.rl" {te = p+1;{ output->first = 0x291d; {p++; goto _out; } }} break; case 1207: #line 1506 "char_ref.rl" {te = p+1;{ output->first = 0x21a9; {p++; goto _out; } }} break; case 1208: #line 1507 "char_ref.rl" {te = p+1;{ output->first = 0x21ab; {p++; goto _out; } }} break; case 1209: #line 1508 "char_ref.rl" {te = p+1;{ output->first = 0x2939; {p++; goto _out; } }} break; case 1210: #line 1509 "char_ref.rl" {te = p+1;{ output->first = 0x2973; {p++; goto _out; } }} break; case 1211: #line 1510 "char_ref.rl" {te = p+1;{ output->first = 0x21a2; {p++; goto _out; } }} break; case 1212: #line 1511 "char_ref.rl" {te = p+1;{ output->first = 0x2aab; {p++; goto _out; } }} break; case 1213: #line 1512 "char_ref.rl" {te = p+1;{ output->first = 0x2919; {p++; goto _out; } }} break; case 1214: #line 1513 "char_ref.rl" {te = p+1;{ output->first = 0x2aad; {p++; goto _out; } }} break; case 1215: #line 1514 "char_ref.rl" {te = p+1;{ output->first = 0x2aad; output->second = 0xfe00; {p++; goto _out; } }} break; case 1216: #line 1515 "char_ref.rl" {te = p+1;{ output->first = 0x290c; {p++; goto _out; } }} break; case 1217: #line 1516 "char_ref.rl" {te = p+1;{ output->first = 0x2772; {p++; goto _out; } }} break; case 1218: #line 1517 "char_ref.rl" {te = p+1;{ output->first = 0x7b; {p++; goto _out; } }} break; case 1219: #line 1518 "char_ref.rl" {te = p+1;{ output->first = 0x5b; {p++; goto _out; } }} break; case 1220: #line 1519 "char_ref.rl" {te = p+1;{ output->first = 0x298b; {p++; goto _out; } }} break; case 1221: #line 1520 "char_ref.rl" {te = p+1;{ output->first = 0x298f; {p++; goto _out; } }} break; case 1222: #line 1521 "char_ref.rl" {te = p+1;{ output->first = 0x298d; {p++; goto _out; } }} break; case 1223: #line 1522 "char_ref.rl" {te = p+1;{ output->first = 0x013e; {p++; goto _out; } }} break; case 1224: #line 1523 "char_ref.rl" {te = p+1;{ output->first = 0x013c; {p++; goto _out; } }} break; case 1225: #line 1524 "char_ref.rl" {te = p+1;{ output->first = 0x2308; {p++; goto _out; } }} break; case 1226: #line 1525 "char_ref.rl" {te = p+1;{ output->first = 0x7b; {p++; goto _out; } }} break; case 1227: #line 1526 "char_ref.rl" {te = p+1;{ output->first = 0x043b; {p++; goto _out; } }} break; case 1228: #line 1527 "char_ref.rl" {te = p+1;{ output->first = 0x2936; {p++; goto _out; } }} break; case 1229: #line 1528 "char_ref.rl" {te = p+1;{ output->first = 0x201c; {p++; goto _out; } }} break; case 1230: #line 1529 "char_ref.rl" {te = p+1;{ output->first = 0x201e; {p++; goto _out; } }} break; case 1231: #line 1530 "char_ref.rl" {te = p+1;{ output->first = 0x2967; {p++; goto _out; } }} break; case 1232: #line 1531 "char_ref.rl" {te = p+1;{ output->first = 0x294b; {p++; goto _out; } }} break; case 1233: #line 1532 "char_ref.rl" {te = p+1;{ output->first = 0x21b2; {p++; goto _out; } }} break; case 1234: #line 1533 "char_ref.rl" {te = p+1;{ output->first = 0x2264; {p++; goto _out; } }} break; case 1235: #line 1534 "char_ref.rl" {te = p+1;{ output->first = 0x2190; {p++; goto _out; } }} break; case 1236: #line 1535 "char_ref.rl" {te = p+1;{ output->first = 0x21a2; {p++; goto _out; } }} break; case 1237: #line 1536 "char_ref.rl" {te = p+1;{ output->first = 0x21bd; {p++; goto _out; } }} break; case 1238: #line 1537 "char_ref.rl" {te = p+1;{ output->first = 0x21bc; {p++; goto _out; } }} break; case 1239: #line 1538 "char_ref.rl" {te = p+1;{ output->first = 0x21c7; {p++; goto _out; } }} break; case 1240: #line 1539 "char_ref.rl" {te = p+1;{ output->first = 0x2194; {p++; goto _out; } }} break; case 1241: #line 1540 "char_ref.rl" {te = p+1;{ output->first = 0x21c6; {p++; goto _out; } }} break; case 1242: #line 1541 "char_ref.rl" {te = p+1;{ output->first = 0x21cb; {p++; goto _out; } }} break; case 1243: #line 1542 "char_ref.rl" {te = p+1;{ output->first = 0x21ad; {p++; goto _out; } }} break; case 1244: #line 1543 "char_ref.rl" {te = p+1;{ output->first = 0x22cb; {p++; goto _out; } }} break; case 1245: #line 1544 "char_ref.rl" {te = p+1;{ output->first = 0x22da; {p++; goto _out; } }} break; case 1246: #line 1545 "char_ref.rl" {te = p+1;{ output->first = 0x2264; {p++; goto _out; } }} break; case 1247: #line 1546 "char_ref.rl" {te = p+1;{ output->first = 0x2266; {p++; goto _out; } }} break; case 1248: #line 1547 "char_ref.rl" {te = p+1;{ output->first = 0x2a7d; {p++; goto _out; } }} break; case 1249: #line 1548 "char_ref.rl" {te = p+1;{ output->first = 0x2a7d; {p++; goto _out; } }} break; case 1250: #line 1549 "char_ref.rl" {te = p+1;{ output->first = 0x2aa8; {p++; goto _out; } }} break; case 1251: #line 1550 "char_ref.rl" {te = p+1;{ output->first = 0x2a7f; {p++; goto _out; } }} break; case 1252: #line 1551 "char_ref.rl" {te = p+1;{ output->first = 0x2a81; {p++; goto _out; } }} break; case 1253: #line 1552 "char_ref.rl" {te = p+1;{ output->first = 0x2a83; {p++; goto _out; } }} break; case 1254: #line 1553 "char_ref.rl" {te = p+1;{ output->first = 0x22da; output->second = 0xfe00; {p++; goto _out; } }} break; case 1255: #line 1554 "char_ref.rl" {te = p+1;{ output->first = 0x2a93; {p++; goto _out; } }} break; case 1256: #line 1555 "char_ref.rl" {te = p+1;{ output->first = 0x2a85; {p++; goto _out; } }} break; case 1257: #line 1556 "char_ref.rl" {te = p+1;{ output->first = 0x22d6; {p++; goto _out; } }} break; case 1258: #line 1557 "char_ref.rl" {te = p+1;{ output->first = 0x22da; {p++; goto _out; } }} break; case 1259: #line 1558 "char_ref.rl" {te = p+1;{ output->first = 0x2a8b; {p++; goto _out; } }} break; case 1260: #line 1559 "char_ref.rl" {te = p+1;{ output->first = 0x2276; {p++; goto _out; } }} break; case 1261: #line 1560 "char_ref.rl" {te = p+1;{ output->first = 0x2272; {p++; goto _out; } }} break; case 1262: #line 1561 "char_ref.rl" {te = p+1;{ output->first = 0x297c; {p++; goto _out; } }} break; case 1263: #line 1562 "char_ref.rl" {te = p+1;{ output->first = 0x230a; {p++; goto _out; } }} break; case 1264: #line 1563 "char_ref.rl" {te = p+1;{ output->first = 0x0001d529; {p++; goto _out; } }} break; case 1265: #line 1564 "char_ref.rl" {te = p+1;{ output->first = 0x2276; {p++; goto _out; } }} break; case 1266: #line 1565 "char_ref.rl" {te = p+1;{ output->first = 0x2a91; {p++; goto _out; } }} break; case 1267: #line 1566 "char_ref.rl" {te = p+1;{ output->first = 0x21bd; {p++; goto _out; } }} break; case 1268: #line 1567 "char_ref.rl" {te = p+1;{ output->first = 0x21bc; {p++; goto _out; } }} break; case 1269: #line 1568 "char_ref.rl" {te = p+1;{ output->first = 0x296a; {p++; goto _out; } }} break; case 1270: #line 1569 "char_ref.rl" {te = p+1;{ output->first = 0x2584; {p++; goto _out; } }} break; case 1271: #line 1570 "char_ref.rl" {te = p+1;{ output->first = 0x0459; {p++; goto _out; } }} break; case 1272: #line 1571 "char_ref.rl" {te = p+1;{ output->first = 0x226a; {p++; goto _out; } }} break; case 1273: #line 1572 "char_ref.rl" {te = p+1;{ output->first = 0x21c7; {p++; goto _out; } }} break; case 1274: #line 1573 "char_ref.rl" {te = p+1;{ output->first = 0x231e; {p++; goto _out; } }} break; case 1275: #line 1574 "char_ref.rl" {te = p+1;{ output->first = 0x296b; {p++; goto _out; } }} break; case 1276: #line 1575 "char_ref.rl" {te = p+1;{ output->first = 0x25fa; {p++; goto _out; } }} break; case 1277: #line 1576 "char_ref.rl" {te = p+1;{ output->first = 0x0140; {p++; goto _out; } }} break; case 1278: #line 1577 "char_ref.rl" {te = p+1;{ output->first = 0x23b0; {p++; goto _out; } }} break; case 1279: #line 1578 "char_ref.rl" {te = p+1;{ output->first = 0x23b0; {p++; goto _out; } }} break; case 1280: #line 1579 "char_ref.rl" {te = p+1;{ output->first = 0x2268; {p++; goto _out; } }} break; case 1281: #line 1580 "char_ref.rl" {te = p+1;{ output->first = 0x2a89; {p++; goto _out; } }} break; case 1282: #line 1581 "char_ref.rl" {te = p+1;{ output->first = 0x2a89; {p++; goto _out; } }} break; case 1283: #line 1582 "char_ref.rl" {te = p+1;{ output->first = 0x2a87; {p++; goto _out; } }} break; case 1284: #line 1583 "char_ref.rl" {te = p+1;{ output->first = 0x2a87; {p++; goto _out; } }} break; case 1285: #line 1584 "char_ref.rl" {te = p+1;{ output->first = 0x2268; {p++; goto _out; } }} break; case 1286: #line 1585 "char_ref.rl" {te = p+1;{ output->first = 0x22e6; {p++; goto _out; } }} break; case 1287: #line 1586 "char_ref.rl" {te = p+1;{ output->first = 0x27ec; {p++; goto _out; } }} break; case 1288: #line 1587 "char_ref.rl" {te = p+1;{ output->first = 0x21fd; {p++; goto _out; } }} break; case 1289: #line 1588 "char_ref.rl" {te = p+1;{ output->first = 0x27e6; {p++; goto _out; } }} break; case 1290: #line 1589 "char_ref.rl" {te = p+1;{ output->first = 0x27f5; {p++; goto _out; } }} break; case 1291: #line 1590 "char_ref.rl" {te = p+1;{ output->first = 0x27f7; {p++; goto _out; } }} break; case 1292: #line 1591 "char_ref.rl" {te = p+1;{ output->first = 0x27fc; {p++; goto _out; } }} break; case 1293: #line 1592 "char_ref.rl" {te = p+1;{ output->first = 0x27f6; {p++; goto _out; } }} break; case 1294: #line 1593 "char_ref.rl" {te = p+1;{ output->first = 0x21ab; {p++; goto _out; } }} break; case 1295: #line 1594 "char_ref.rl" {te = p+1;{ output->first = 0x21ac; {p++; goto _out; } }} break; case 1296: #line 1595 "char_ref.rl" {te = p+1;{ output->first = 0x2985; {p++; goto _out; } }} break; case 1297: #line 1596 "char_ref.rl" {te = p+1;{ output->first = 0x0001d55d; {p++; goto _out; } }} break; case 1298: #line 1597 "char_ref.rl" {te = p+1;{ output->first = 0x2a2d; {p++; goto _out; } }} break; case 1299: #line 1598 "char_ref.rl" {te = p+1;{ output->first = 0x2a34; {p++; goto _out; } }} break; case 1300: #line 1599 "char_ref.rl" {te = p+1;{ output->first = 0x2217; {p++; goto _out; } }} break; case 1301: #line 1600 "char_ref.rl" {te = p+1;{ output->first = 0x5f; {p++; goto _out; } }} break; case 1302: #line 1601 "char_ref.rl" {te = p+1;{ output->first = 0x25ca; {p++; goto _out; } }} break; case 1303: #line 1602 "char_ref.rl" {te = p+1;{ output->first = 0x25ca; {p++; goto _out; } }} break; case 1304: #line 1603 "char_ref.rl" {te = p+1;{ output->first = 0x29eb; {p++; goto _out; } }} break; case 1305: #line 1604 "char_ref.rl" {te = p+1;{ output->first = 0x28; {p++; goto _out; } }} break; case 1306: #line 1605 "char_ref.rl" {te = p+1;{ output->first = 0x2993; {p++; goto _out; } }} break; case 1307: #line 1606 "char_ref.rl" {te = p+1;{ output->first = 0x21c6; {p++; goto _out; } }} break; case 1308: #line 1607 "char_ref.rl" {te = p+1;{ output->first = 0x231f; {p++; goto _out; } }} break; case 1309: #line 1608 "char_ref.rl" {te = p+1;{ output->first = 0x21cb; {p++; goto _out; } }} break; case 1310: #line 1609 "char_ref.rl" {te = p+1;{ output->first = 0x296d; {p++; goto _out; } }} break; case 1311: #line 1610 "char_ref.rl" {te = p+1;{ output->first = 0x200e; {p++; goto _out; } }} break; case 1312: #line 1611 "char_ref.rl" {te = p+1;{ output->first = 0x22bf; {p++; goto _out; } }} break; case 1313: #line 1612 "char_ref.rl" {te = p+1;{ output->first = 0x2039; {p++; goto _out; } }} break; case 1314: #line 1613 "char_ref.rl" {te = p+1;{ output->first = 0x0001d4c1; {p++; goto _out; } }} break; case 1315: #line 1614 "char_ref.rl" {te = p+1;{ output->first = 0x21b0; {p++; goto _out; } }} break; case 1316: #line 1615 "char_ref.rl" {te = p+1;{ output->first = 0x2272; {p++; goto _out; } }} break; case 1317: #line 1616 "char_ref.rl" {te = p+1;{ output->first = 0x2a8d; {p++; goto _out; } }} break; case 1318: #line 1617 "char_ref.rl" {te = p+1;{ output->first = 0x2a8f; {p++; goto _out; } }} break; case 1319: #line 1618 "char_ref.rl" {te = p+1;{ output->first = 0x5b; {p++; goto _out; } }} break; case 1320: #line 1619 "char_ref.rl" {te = p+1;{ output->first = 0x2018; {p++; goto _out; } }} break; case 1321: #line 1620 "char_ref.rl" {te = p+1;{ output->first = 0x201a; {p++; goto _out; } }} break; case 1322: #line 1621 "char_ref.rl" {te = p+1;{ output->first = 0x0142; {p++; goto _out; } }} break; case 1323: #line 1622 "char_ref.rl" {te = p+1;{ output->first = 0x3c; {p++; goto _out; } }} break; case 1324: #line 1624 "char_ref.rl" {te = p+1;{ output->first = 0x2aa6; {p++; goto _out; } }} break; case 1325: #line 1625 "char_ref.rl" {te = p+1;{ output->first = 0x2a79; {p++; goto _out; } }} break; case 1326: #line 1626 "char_ref.rl" {te = p+1;{ output->first = 0x22d6; {p++; goto _out; } }} break; case 1327: #line 1627 "char_ref.rl" {te = p+1;{ output->first = 0x22cb; {p++; goto _out; } }} break; case 1328: #line 1628 "char_ref.rl" {te = p+1;{ output->first = 0x22c9; {p++; goto _out; } }} break; case 1329: #line 1629 "char_ref.rl" {te = p+1;{ output->first = 0x2976; {p++; goto _out; } }} break; case 1330: #line 1630 "char_ref.rl" {te = p+1;{ output->first = 0x2a7b; {p++; goto _out; } }} break; case 1331: #line 1631 "char_ref.rl" {te = p+1;{ output->first = 0x2996; {p++; goto _out; } }} break; case 1332: #line 1632 "char_ref.rl" {te = p+1;{ output->first = 0x25c3; {p++; goto _out; } }} break; case 1333: #line 1633 "char_ref.rl" {te = p+1;{ output->first = 0x22b4; {p++; goto _out; } }} break; case 1334: #line 1634 "char_ref.rl" {te = p+1;{ output->first = 0x25c2; {p++; goto _out; } }} break; case 1335: #line 1635 "char_ref.rl" {te = p+1;{ output->first = 0x294a; {p++; goto _out; } }} break; case 1336: #line 1636 "char_ref.rl" {te = p+1;{ output->first = 0x2966; {p++; goto _out; } }} break; case 1337: #line 1637 "char_ref.rl" {te = p+1;{ output->first = 0x2268; output->second = 0xfe00; {p++; goto _out; } }} break; case 1338: #line 1638 "char_ref.rl" {te = p+1;{ output->first = 0x2268; output->second = 0xfe00; {p++; goto _out; } }} break; case 1339: #line 1639 "char_ref.rl" {te = p+1;{ output->first = 0x223a; {p++; goto _out; } }} break; case 1340: #line 1640 "char_ref.rl" {te = p+1;{ output->first = 0xaf; {p++; goto _out; } }} break; case 1341: #line 1642 "char_ref.rl" {te = p+1;{ output->first = 0x2642; {p++; goto _out; } }} break; case 1342: #line 1643 "char_ref.rl" {te = p+1;{ output->first = 0x2720; {p++; goto _out; } }} break; case 1343: #line 1644 "char_ref.rl" {te = p+1;{ output->first = 0x2720; {p++; goto _out; } }} break; case 1344: #line 1645 "char_ref.rl" {te = p+1;{ output->first = 0x21a6; {p++; goto _out; } }} break; case 1345: #line 1646 "char_ref.rl" {te = p+1;{ output->first = 0x21a6; {p++; goto _out; } }} break; case 1346: #line 1647 "char_ref.rl" {te = p+1;{ output->first = 0x21a7; {p++; goto _out; } }} break; case 1347: #line 1648 "char_ref.rl" {te = p+1;{ output->first = 0x21a4; {p++; goto _out; } }} break; case 1348: #line 1649 "char_ref.rl" {te = p+1;{ output->first = 0x21a5; {p++; goto _out; } }} break; case 1349: #line 1650 "char_ref.rl" {te = p+1;{ output->first = 0x25ae; {p++; goto _out; } }} break; case 1350: #line 1651 "char_ref.rl" {te = p+1;{ output->first = 0x2a29; {p++; goto _out; } }} break; case 1351: #line 1652 "char_ref.rl" {te = p+1;{ output->first = 0x043c; {p++; goto _out; } }} break; case 1352: #line 1653 "char_ref.rl" {te = p+1;{ output->first = 0x2014; {p++; goto _out; } }} break; case 1353: #line 1654 "char_ref.rl" {te = p+1;{ output->first = 0x2221; {p++; goto _out; } }} break; case 1354: #line 1655 "char_ref.rl" {te = p+1;{ output->first = 0x0001d52a; {p++; goto _out; } }} break; case 1355: #line 1656 "char_ref.rl" {te = p+1;{ output->first = 0x2127; {p++; goto _out; } }} break; case 1356: #line 1657 "char_ref.rl" {te = p+1;{ output->first = 0xb5; {p++; goto _out; } }} break; case 1357: #line 1659 "char_ref.rl" {te = p+1;{ output->first = 0x2223; {p++; goto _out; } }} break; case 1358: #line 1660 "char_ref.rl" {te = p+1;{ output->first = 0x2a; {p++; goto _out; } }} break; case 1359: #line 1661 "char_ref.rl" {te = p+1;{ output->first = 0x2af0; {p++; goto _out; } }} break; case 1360: #line 1662 "char_ref.rl" {te = p+1;{ output->first = 0xb7; {p++; goto _out; } }} break; case 1361: #line 1664 "char_ref.rl" {te = p+1;{ output->first = 0x2212; {p++; goto _out; } }} break; case 1362: #line 1665 "char_ref.rl" {te = p+1;{ output->first = 0x229f; {p++; goto _out; } }} break; case 1363: #line 1666 "char_ref.rl" {te = p+1;{ output->first = 0x2238; {p++; goto _out; } }} break; case 1364: #line 1667 "char_ref.rl" {te = p+1;{ output->first = 0x2a2a; {p++; goto _out; } }} break; case 1365: #line 1668 "char_ref.rl" {te = p+1;{ output->first = 0x2adb; {p++; goto _out; } }} break; case 1366: #line 1669 "char_ref.rl" {te = p+1;{ output->first = 0x2026; {p++; goto _out; } }} break; case 1367: #line 1670 "char_ref.rl" {te = p+1;{ output->first = 0x2213; {p++; goto _out; } }} break; case 1368: #line 1671 "char_ref.rl" {te = p+1;{ output->first = 0x22a7; {p++; goto _out; } }} break; case 1369: #line 1672 "char_ref.rl" {te = p+1;{ output->first = 0x0001d55e; {p++; goto _out; } }} break; case 1370: #line 1673 "char_ref.rl" {te = p+1;{ output->first = 0x2213; {p++; goto _out; } }} break; case 1371: #line 1674 "char_ref.rl" {te = p+1;{ output->first = 0x0001d4c2; {p++; goto _out; } }} break; case 1372: #line 1675 "char_ref.rl" {te = p+1;{ output->first = 0x223e; {p++; goto _out; } }} break; case 1373: #line 1676 "char_ref.rl" {te = p+1;{ output->first = 0x03bc; {p++; goto _out; } }} break; case 1374: #line 1677 "char_ref.rl" {te = p+1;{ output->first = 0x22b8; {p++; goto _out; } }} break; case 1375: #line 1678 "char_ref.rl" {te = p+1;{ output->first = 0x22b8; {p++; goto _out; } }} break; case 1376: #line 1679 "char_ref.rl" {te = p+1;{ output->first = 0x22d9; output->second = 0x0338; {p++; goto _out; } }} break; case 1377: #line 1680 "char_ref.rl" {te = p+1;{ output->first = 0x226b; output->second = 0x20d2; {p++; goto _out; } }} break; case 1378: #line 1681 "char_ref.rl" {te = p+1;{ output->first = 0x226b; output->second = 0x0338; {p++; goto _out; } }} break; case 1379: #line 1682 "char_ref.rl" {te = p+1;{ output->first = 0x21cd; {p++; goto _out; } }} break; case 1380: #line 1683 "char_ref.rl" {te = p+1;{ output->first = 0x21ce; {p++; goto _out; } }} break; case 1381: #line 1684 "char_ref.rl" {te = p+1;{ output->first = 0x22d8; output->second = 0x0338; {p++; goto _out; } }} break; case 1382: #line 1685 "char_ref.rl" {te = p+1;{ output->first = 0x226a; output->second = 0x20d2; {p++; goto _out; } }} break; case 1383: #line 1686 "char_ref.rl" {te = p+1;{ output->first = 0x226a; output->second = 0x0338; {p++; goto _out; } }} break; case 1384: #line 1687 "char_ref.rl" {te = p+1;{ output->first = 0x21cf; {p++; goto _out; } }} break; case 1385: #line 1688 "char_ref.rl" {te = p+1;{ output->first = 0x22af; {p++; goto _out; } }} break; case 1386: #line 1689 "char_ref.rl" {te = p+1;{ output->first = 0x22ae; {p++; goto _out; } }} break; case 1387: #line 1690 "char_ref.rl" {te = p+1;{ output->first = 0x2207; {p++; goto _out; } }} break; case 1388: #line 1691 "char_ref.rl" {te = p+1;{ output->first = 0x0144; {p++; goto _out; } }} break; case 1389: #line 1692 "char_ref.rl" {te = p+1;{ output->first = 0x2220; output->second = 0x20d2; {p++; goto _out; } }} break; case 1390: #line 1693 "char_ref.rl" {te = p+1;{ output->first = 0x2249; {p++; goto _out; } }} break; case 1391: #line 1694 "char_ref.rl" {te = p+1;{ output->first = 0x2a70; output->second = 0x0338; {p++; goto _out; } }} break; case 1392: #line 1695 "char_ref.rl" {te = p+1;{ output->first = 0x224b; output->second = 0x0338; {p++; goto _out; } }} break; case 1393: #line 1696 "char_ref.rl" {te = p+1;{ output->first = 0x0149; {p++; goto _out; } }} break; case 1394: #line 1697 "char_ref.rl" {te = p+1;{ output->first = 0x2249; {p++; goto _out; } }} break; case 1395: #line 1698 "char_ref.rl" {te = p+1;{ output->first = 0x266e; {p++; goto _out; } }} break; case 1396: #line 1699 "char_ref.rl" {te = p+1;{ output->first = 0x266e; {p++; goto _out; } }} break; case 1397: #line 1700 "char_ref.rl" {te = p+1;{ output->first = 0x2115; {p++; goto _out; } }} break; case 1398: #line 1701 "char_ref.rl" {te = p+1;{ output->first = 0xa0; {p++; goto _out; } }} break; case 1399: #line 1703 "char_ref.rl" {te = p+1;{ output->first = 0x224e; output->second = 0x0338; {p++; goto _out; } }} break; case 1400: #line 1704 "char_ref.rl" {te = p+1;{ output->first = 0x224f; output->second = 0x0338; {p++; goto _out; } }} break; case 1401: #line 1705 "char_ref.rl" {te = p+1;{ output->first = 0x2a43; {p++; goto _out; } }} break; case 1402: #line 1706 "char_ref.rl" {te = p+1;{ output->first = 0x0148; {p++; goto _out; } }} break; case 1403: #line 1707 "char_ref.rl" {te = p+1;{ output->first = 0x0146; {p++; goto _out; } }} break; case 1404: #line 1708 "char_ref.rl" {te = p+1;{ output->first = 0x2247; {p++; goto _out; } }} break; case 1405: #line 1709 "char_ref.rl" {te = p+1;{ output->first = 0x2a6d; output->second = 0x0338; {p++; goto _out; } }} break; case 1406: #line 1710 "char_ref.rl" {te = p+1;{ output->first = 0x2a42; {p++; goto _out; } }} break; case 1407: #line 1711 "char_ref.rl" {te = p+1;{ output->first = 0x043d; {p++; goto _out; } }} break; case 1408: #line 1712 "char_ref.rl" {te = p+1;{ output->first = 0x2013; {p++; goto _out; } }} break; case 1409: #line 1713 "char_ref.rl" {te = p+1;{ output->first = 0x2260; {p++; goto _out; } }} break; case 1410: #line 1714 "char_ref.rl" {te = p+1;{ output->first = 0x21d7; {p++; goto _out; } }} break; case 1411: #line 1715 "char_ref.rl" {te = p+1;{ output->first = 0x2924; {p++; goto _out; } }} break; case 1412: #line 1716 "char_ref.rl" {te = p+1;{ output->first = 0x2197; {p++; goto _out; } }} break; case 1413: #line 1717 "char_ref.rl" {te = p+1;{ output->first = 0x2197; {p++; goto _out; } }} break; case 1414: #line 1718 "char_ref.rl" {te = p+1;{ output->first = 0x2250; output->second = 0x0338; {p++; goto _out; } }} break; case 1415: #line 1719 "char_ref.rl" {te = p+1;{ output->first = 0x2262; {p++; goto _out; } }} break; case 1416: #line 1720 "char_ref.rl" {te = p+1;{ output->first = 0x2928; {p++; goto _out; } }} break; case 1417: #line 1721 "char_ref.rl" {te = p+1;{ output->first = 0x2242; output->second = 0x0338; {p++; goto _out; } }} break; case 1418: #line 1722 "char_ref.rl" {te = p+1;{ output->first = 0x2204; {p++; goto _out; } }} break; case 1419: #line 1723 "char_ref.rl" {te = p+1;{ output->first = 0x2204; {p++; goto _out; } }} break; case 1420: #line 1724 "char_ref.rl" {te = p+1;{ output->first = 0x0001d52b; {p++; goto _out; } }} break; case 1421: #line 1725 "char_ref.rl" {te = p+1;{ output->first = 0x2267; output->second = 0x0338; {p++; goto _out; } }} break; case 1422: #line 1726 "char_ref.rl" {te = p+1;{ output->first = 0x2271; {p++; goto _out; } }} break; case 1423: #line 1727 "char_ref.rl" {te = p+1;{ output->first = 0x2271; {p++; goto _out; } }} break; case 1424: #line 1728 "char_ref.rl" {te = p+1;{ output->first = 0x2267; output->second = 0x0338; {p++; goto _out; } }} break; case 1425: #line 1729 "char_ref.rl" {te = p+1;{ output->first = 0x2a7e; output->second = 0x0338; {p++; goto _out; } }} break; case 1426: #line 1730 "char_ref.rl" {te = p+1;{ output->first = 0x2a7e; output->second = 0x0338; {p++; goto _out; } }} break; case 1427: #line 1731 "char_ref.rl" {te = p+1;{ output->first = 0x2275; {p++; goto _out; } }} break; case 1428: #line 1732 "char_ref.rl" {te = p+1;{ output->first = 0x226f; {p++; goto _out; } }} break; case 1429: #line 1733 "char_ref.rl" {te = p+1;{ output->first = 0x226f; {p++; goto _out; } }} break; case 1430: #line 1734 "char_ref.rl" {te = p+1;{ output->first = 0x21ce; {p++; goto _out; } }} break; case 1431: #line 1735 "char_ref.rl" {te = p+1;{ output->first = 0x21ae; {p++; goto _out; } }} break; case 1432: #line 1736 "char_ref.rl" {te = p+1;{ output->first = 0x2af2; {p++; goto _out; } }} break; case 1433: #line 1737 "char_ref.rl" {te = p+1;{ output->first = 0x220b; {p++; goto _out; } }} break; case 1434: #line 1738 "char_ref.rl" {te = p+1;{ output->first = 0x22fc; {p++; goto _out; } }} break; case 1435: #line 1739 "char_ref.rl" {te = p+1;{ output->first = 0x22fa; {p++; goto _out; } }} break; case 1436: #line 1740 "char_ref.rl" {te = p+1;{ output->first = 0x220b; {p++; goto _out; } }} break; case 1437: #line 1741 "char_ref.rl" {te = p+1;{ output->first = 0x045a; {p++; goto _out; } }} break; case 1438: #line 1742 "char_ref.rl" {te = p+1;{ output->first = 0x21cd; {p++; goto _out; } }} break; case 1439: #line 1743 "char_ref.rl" {te = p+1;{ output->first = 0x2266; output->second = 0x0338; {p++; goto _out; } }} break; case 1440: #line 1744 "char_ref.rl" {te = p+1;{ output->first = 0x219a; {p++; goto _out; } }} break; case 1441: #line 1745 "char_ref.rl" {te = p+1;{ output->first = 0x2025; {p++; goto _out; } }} break; case 1442: #line 1746 "char_ref.rl" {te = p+1;{ output->first = 0x2270; {p++; goto _out; } }} break; case 1443: #line 1747 "char_ref.rl" {te = p+1;{ output->first = 0x219a; {p++; goto _out; } }} break; case 1444: #line 1748 "char_ref.rl" {te = p+1;{ output->first = 0x21ae; {p++; goto _out; } }} break; case 1445: #line 1749 "char_ref.rl" {te = p+1;{ output->first = 0x2270; {p++; goto _out; } }} break; case 1446: #line 1750 "char_ref.rl" {te = p+1;{ output->first = 0x2266; output->second = 0x0338; {p++; goto _out; } }} break; case 1447: #line 1751 "char_ref.rl" {te = p+1;{ output->first = 0x2a7d; output->second = 0x0338; {p++; goto _out; } }} break; case 1448: #line 1752 "char_ref.rl" {te = p+1;{ output->first = 0x2a7d; output->second = 0x0338; {p++; goto _out; } }} break; case 1449: #line 1753 "char_ref.rl" {te = p+1;{ output->first = 0x226e; {p++; goto _out; } }} break; case 1450: #line 1754 "char_ref.rl" {te = p+1;{ output->first = 0x2274; {p++; goto _out; } }} break; case 1451: #line 1755 "char_ref.rl" {te = p+1;{ output->first = 0x226e; {p++; goto _out; } }} break; case 1452: #line 1756 "char_ref.rl" {te = p+1;{ output->first = 0x22ea; {p++; goto _out; } }} break; case 1453: #line 1757 "char_ref.rl" {te = p+1;{ output->first = 0x22ec; {p++; goto _out; } }} break; case 1454: #line 1758 "char_ref.rl" {te = p+1;{ output->first = 0x2224; {p++; goto _out; } }} break; case 1455: #line 1759 "char_ref.rl" {te = p+1;{ output->first = 0x0001d55f; {p++; goto _out; } }} break; case 1456: #line 1760 "char_ref.rl" {te = p+1;{ output->first = 0xac; {p++; goto _out; } }} break; case 1457: #line 1761 "char_ref.rl" {te = p+1;{ output->first = 0x2209; {p++; goto _out; } }} break; case 1458: #line 1762 "char_ref.rl" {te = p+1;{ output->first = 0x22f9; output->second = 0x0338; {p++; goto _out; } }} break; case 1459: #line 1763 "char_ref.rl" {te = p+1;{ output->first = 0x22f5; output->second = 0x0338; {p++; goto _out; } }} break; case 1460: #line 1764 "char_ref.rl" {te = p+1;{ output->first = 0x2209; {p++; goto _out; } }} break; case 1461: #line 1765 "char_ref.rl" {te = p+1;{ output->first = 0x22f7; {p++; goto _out; } }} break; case 1462: #line 1766 "char_ref.rl" {te = p+1;{ output->first = 0x22f6; {p++; goto _out; } }} break; case 1463: #line 1767 "char_ref.rl" {te = p+1;{ output->first = 0x220c; {p++; goto _out; } }} break; case 1464: #line 1768 "char_ref.rl" {te = p+1;{ output->first = 0x220c; {p++; goto _out; } }} break; case 1465: #line 1769 "char_ref.rl" {te = p+1;{ output->first = 0x22fe; {p++; goto _out; } }} break; case 1466: #line 1770 "char_ref.rl" {te = p+1;{ output->first = 0x22fd; {p++; goto _out; } }} break; case 1467: #line 1772 "char_ref.rl" {te = p+1;{ output->first = 0x2226; {p++; goto _out; } }} break; case 1468: #line 1773 "char_ref.rl" {te = p+1;{ output->first = 0x2226; {p++; goto _out; } }} break; case 1469: #line 1774 "char_ref.rl" {te = p+1;{ output->first = 0x2afd; output->second = 0x20e5; {p++; goto _out; } }} break; case 1470: #line 1775 "char_ref.rl" {te = p+1;{ output->first = 0x2202; output->second = 0x0338; {p++; goto _out; } }} break; case 1471: #line 1776 "char_ref.rl" {te = p+1;{ output->first = 0x2a14; {p++; goto _out; } }} break; case 1472: #line 1777 "char_ref.rl" {te = p+1;{ output->first = 0x2280; {p++; goto _out; } }} break; case 1473: #line 1778 "char_ref.rl" {te = p+1;{ output->first = 0x22e0; {p++; goto _out; } }} break; case 1474: #line 1779 "char_ref.rl" {te = p+1;{ output->first = 0x2aaf; output->second = 0x0338; {p++; goto _out; } }} break; case 1475: #line 1780 "char_ref.rl" {te = p+1;{ output->first = 0x2280; {p++; goto _out; } }} break; case 1476: #line 1781 "char_ref.rl" {te = p+1;{ output->first = 0x2aaf; output->second = 0x0338; {p++; goto _out; } }} break; case 1477: #line 1782 "char_ref.rl" {te = p+1;{ output->first = 0x21cf; {p++; goto _out; } }} break; case 1478: #line 1783 "char_ref.rl" {te = p+1;{ output->first = 0x219b; {p++; goto _out; } }} break; case 1479: #line 1784 "char_ref.rl" {te = p+1;{ output->first = 0x2933; output->second = 0x0338; {p++; goto _out; } }} break; case 1480: #line 1785 "char_ref.rl" {te = p+1;{ output->first = 0x219d; output->second = 0x0338; {p++; goto _out; } }} break; case 1481: #line 1786 "char_ref.rl" {te = p+1;{ output->first = 0x219b; {p++; goto _out; } }} break; case 1482: #line 1787 "char_ref.rl" {te = p+1;{ output->first = 0x22eb; {p++; goto _out; } }} break; case 1483: #line 1788 "char_ref.rl" {te = p+1;{ output->first = 0x22ed; {p++; goto _out; } }} break; case 1484: #line 1789 "char_ref.rl" {te = p+1;{ output->first = 0x2281; {p++; goto _out; } }} break; case 1485: #line 1790 "char_ref.rl" {te = p+1;{ output->first = 0x22e1; {p++; goto _out; } }} break; case 1486: #line 1791 "char_ref.rl" {te = p+1;{ output->first = 0x2ab0; output->second = 0x0338; {p++; goto _out; } }} break; case 1487: #line 1792 "char_ref.rl" {te = p+1;{ output->first = 0x0001d4c3; {p++; goto _out; } }} break; case 1488: #line 1793 "char_ref.rl" {te = p+1;{ output->first = 0x2224; {p++; goto _out; } }} break; case 1489: #line 1794 "char_ref.rl" {te = p+1;{ output->first = 0x2226; {p++; goto _out; } }} break; case 1490: #line 1795 "char_ref.rl" {te = p+1;{ output->first = 0x2241; {p++; goto _out; } }} break; case 1491: #line 1796 "char_ref.rl" {te = p+1;{ output->first = 0x2244; {p++; goto _out; } }} break; case 1492: #line 1797 "char_ref.rl" {te = p+1;{ output->first = 0x2244; {p++; goto _out; } }} break; case 1493: #line 1798 "char_ref.rl" {te = p+1;{ output->first = 0x2224; {p++; goto _out; } }} break; case 1494: #line 1799 "char_ref.rl" {te = p+1;{ output->first = 0x2226; {p++; goto _out; } }} break; case 1495: #line 1800 "char_ref.rl" {te = p+1;{ output->first = 0x22e2; {p++; goto _out; } }} break; case 1496: #line 1801 "char_ref.rl" {te = p+1;{ output->first = 0x22e3; {p++; goto _out; } }} break; case 1497: #line 1802 "char_ref.rl" {te = p+1;{ output->first = 0x2284; {p++; goto _out; } }} break; case 1498: #line 1803 "char_ref.rl" {te = p+1;{ output->first = 0x2ac5; output->second = 0x0338; {p++; goto _out; } }} break; case 1499: #line 1804 "char_ref.rl" {te = p+1;{ output->first = 0x2288; {p++; goto _out; } }} break; case 1500: #line 1805 "char_ref.rl" {te = p+1;{ output->first = 0x2282; output->second = 0x20d2; {p++; goto _out; } }} break; case 1501: #line 1806 "char_ref.rl" {te = p+1;{ output->first = 0x2288; {p++; goto _out; } }} break; case 1502: #line 1807 "char_ref.rl" {te = p+1;{ output->first = 0x2ac5; output->second = 0x0338; {p++; goto _out; } }} break; case 1503: #line 1808 "char_ref.rl" {te = p+1;{ output->first = 0x2281; {p++; goto _out; } }} break; case 1504: #line 1809 "char_ref.rl" {te = p+1;{ output->first = 0x2ab0; output->second = 0x0338; {p++; goto _out; } }} break; case 1505: #line 1810 "char_ref.rl" {te = p+1;{ output->first = 0x2285; {p++; goto _out; } }} break; case 1506: #line 1811 "char_ref.rl" {te = p+1;{ output->first = 0x2ac6; output->second = 0x0338; {p++; goto _out; } }} break; case 1507: #line 1812 "char_ref.rl" {te = p+1;{ output->first = 0x2289; {p++; goto _out; } }} break; case 1508: #line 1813 "char_ref.rl" {te = p+1;{ output->first = 0x2283; output->second = 0x20d2; {p++; goto _out; } }} break; case 1509: #line 1814 "char_ref.rl" {te = p+1;{ output->first = 0x2289; {p++; goto _out; } }} break; case 1510: #line 1815 "char_ref.rl" {te = p+1;{ output->first = 0x2ac6; output->second = 0x0338; {p++; goto _out; } }} break; case 1511: #line 1816 "char_ref.rl" {te = p+1;{ output->first = 0x2279; {p++; goto _out; } }} break; case 1512: #line 1817 "char_ref.rl" {te = p+1;{ output->first = 0xf1; {p++; goto _out; } }} break; case 1513: #line 1819 "char_ref.rl" {te = p+1;{ output->first = 0x2278; {p++; goto _out; } }} break; case 1514: #line 1820 "char_ref.rl" {te = p+1;{ output->first = 0x22ea; {p++; goto _out; } }} break; case 1515: #line 1821 "char_ref.rl" {te = p+1;{ output->first = 0x22ec; {p++; goto _out; } }} break; case 1516: #line 1822 "char_ref.rl" {te = p+1;{ output->first = 0x22eb; {p++; goto _out; } }} break; case 1517: #line 1823 "char_ref.rl" {te = p+1;{ output->first = 0x22ed; {p++; goto _out; } }} break; case 1518: #line 1824 "char_ref.rl" {te = p+1;{ output->first = 0x03bd; {p++; goto _out; } }} break; case 1519: #line 1825 "char_ref.rl" {te = p+1;{ output->first = 0x23; {p++; goto _out; } }} break; case 1520: #line 1826 "char_ref.rl" {te = p+1;{ output->first = 0x2116; {p++; goto _out; } }} break; case 1521: #line 1827 "char_ref.rl" {te = p+1;{ output->first = 0x2007; {p++; goto _out; } }} break; case 1522: #line 1828 "char_ref.rl" {te = p+1;{ output->first = 0x22ad; {p++; goto _out; } }} break; case 1523: #line 1829 "char_ref.rl" {te = p+1;{ output->first = 0x2904; {p++; goto _out; } }} break; case 1524: #line 1830 "char_ref.rl" {te = p+1;{ output->first = 0x224d; output->second = 0x20d2; {p++; goto _out; } }} break; case 1525: #line 1831 "char_ref.rl" {te = p+1;{ output->first = 0x22ac; {p++; goto _out; } }} break; case 1526: #line 1832 "char_ref.rl" {te = p+1;{ output->first = 0x2265; output->second = 0x20d2; {p++; goto _out; } }} break; case 1527: #line 1833 "char_ref.rl" {te = p+1;{ output->first = 0x3e; output->second = 0x20d2; {p++; goto _out; } }} break; case 1528: #line 1834 "char_ref.rl" {te = p+1;{ output->first = 0x29de; {p++; goto _out; } }} break; case 1529: #line 1835 "char_ref.rl" {te = p+1;{ output->first = 0x2902; {p++; goto _out; } }} break; case 1530: #line 1836 "char_ref.rl" {te = p+1;{ output->first = 0x2264; output->second = 0x20d2; {p++; goto _out; } }} break; case 1531: #line 1837 "char_ref.rl" {te = p+1;{ output->first = 0x3c; output->second = 0x20d2; {p++; goto _out; } }} break; case 1532: #line 1838 "char_ref.rl" {te = p+1;{ output->first = 0x22b4; output->second = 0x20d2; {p++; goto _out; } }} break; case 1533: #line 1839 "char_ref.rl" {te = p+1;{ output->first = 0x2903; {p++; goto _out; } }} break; case 1534: #line 1840 "char_ref.rl" {te = p+1;{ output->first = 0x22b5; output->second = 0x20d2; {p++; goto _out; } }} break; case 1535: #line 1841 "char_ref.rl" {te = p+1;{ output->first = 0x223c; output->second = 0x20d2; {p++; goto _out; } }} break; case 1536: #line 1842 "char_ref.rl" {te = p+1;{ output->first = 0x21d6; {p++; goto _out; } }} break; case 1537: #line 1843 "char_ref.rl" {te = p+1;{ output->first = 0x2923; {p++; goto _out; } }} break; case 1538: #line 1844 "char_ref.rl" {te = p+1;{ output->first = 0x2196; {p++; goto _out; } }} break; case 1539: #line 1845 "char_ref.rl" {te = p+1;{ output->first = 0x2196; {p++; goto _out; } }} break; case 1540: #line 1846 "char_ref.rl" {te = p+1;{ output->first = 0x2927; {p++; goto _out; } }} break; case 1541: #line 1847 "char_ref.rl" {te = p+1;{ output->first = 0x24c8; {p++; goto _out; } }} break; case 1542: #line 1848 "char_ref.rl" {te = p+1;{ output->first = 0xf3; {p++; goto _out; } }} break; case 1543: #line 1850 "char_ref.rl" {te = p+1;{ output->first = 0x229b; {p++; goto _out; } }} break; case 1544: #line 1851 "char_ref.rl" {te = p+1;{ output->first = 0x229a; {p++; goto _out; } }} break; case 1545: #line 1852 "char_ref.rl" {te = p+1;{ output->first = 0xf4; {p++; goto _out; } }} break; case 1546: #line 1854 "char_ref.rl" {te = p+1;{ output->first = 0x043e; {p++; goto _out; } }} break; case 1547: #line 1855 "char_ref.rl" {te = p+1;{ output->first = 0x229d; {p++; goto _out; } }} break; case 1548: #line 1856 "char_ref.rl" {te = p+1;{ output->first = 0x0151; {p++; goto _out; } }} break; case 1549: #line 1857 "char_ref.rl" {te = p+1;{ output->first = 0x2a38; {p++; goto _out; } }} break; case 1550: #line 1858 "char_ref.rl" {te = p+1;{ output->first = 0x2299; {p++; goto _out; } }} break; case 1551: #line 1859 "char_ref.rl" {te = p+1;{ output->first = 0x29bc; {p++; goto _out; } }} break; case 1552: #line 1860 "char_ref.rl" {te = p+1;{ output->first = 0x0153; {p++; goto _out; } }} break; case 1553: #line 1861 "char_ref.rl" {te = p+1;{ output->first = 0x29bf; {p++; goto _out; } }} break; case 1554: #line 1862 "char_ref.rl" {te = p+1;{ output->first = 0x0001d52c; {p++; goto _out; } }} break; case 1555: #line 1863 "char_ref.rl" {te = p+1;{ output->first = 0x02db; {p++; goto _out; } }} break; case 1556: #line 1864 "char_ref.rl" {te = p+1;{ output->first = 0xf2; {p++; goto _out; } }} break; case 1557: #line 1866 "char_ref.rl" {te = p+1;{ output->first = 0x29c1; {p++; goto _out; } }} break; case 1558: #line 1867 "char_ref.rl" {te = p+1;{ output->first = 0x29b5; {p++; goto _out; } }} break; case 1559: #line 1868 "char_ref.rl" {te = p+1;{ output->first = 0x03a9; {p++; goto _out; } }} break; case 1560: #line 1869 "char_ref.rl" {te = p+1;{ output->first = 0x222e; {p++; goto _out; } }} break; case 1561: #line 1870 "char_ref.rl" {te = p+1;{ output->first = 0x21ba; {p++; goto _out; } }} break; case 1562: #line 1871 "char_ref.rl" {te = p+1;{ output->first = 0x29be; {p++; goto _out; } }} break; case 1563: #line 1872 "char_ref.rl" {te = p+1;{ output->first = 0x29bb; {p++; goto _out; } }} break; case 1564: #line 1873 "char_ref.rl" {te = p+1;{ output->first = 0x203e; {p++; goto _out; } }} break; case 1565: #line 1874 "char_ref.rl" {te = p+1;{ output->first = 0x29c0; {p++; goto _out; } }} break; case 1566: #line 1875 "char_ref.rl" {te = p+1;{ output->first = 0x014d; {p++; goto _out; } }} break; case 1567: #line 1876 "char_ref.rl" {te = p+1;{ output->first = 0x03c9; {p++; goto _out; } }} break; case 1568: #line 1877 "char_ref.rl" {te = p+1;{ output->first = 0x03bf; {p++; goto _out; } }} break; case 1569: #line 1878 "char_ref.rl" {te = p+1;{ output->first = 0x29b6; {p++; goto _out; } }} break; case 1570: #line 1879 "char_ref.rl" {te = p+1;{ output->first = 0x2296; {p++; goto _out; } }} break; case 1571: #line 1880 "char_ref.rl" {te = p+1;{ output->first = 0x0001d560; {p++; goto _out; } }} break; case 1572: #line 1881 "char_ref.rl" {te = p+1;{ output->first = 0x29b7; {p++; goto _out; } }} break; case 1573: #line 1882 "char_ref.rl" {te = p+1;{ output->first = 0x29b9; {p++; goto _out; } }} break; case 1574: #line 1883 "char_ref.rl" {te = p+1;{ output->first = 0x2295; {p++; goto _out; } }} break; case 1575: #line 1884 "char_ref.rl" {te = p+1;{ output->first = 0x2228; {p++; goto _out; } }} break; case 1576: #line 1885 "char_ref.rl" {te = p+1;{ output->first = 0x21bb; {p++; goto _out; } }} break; case 1577: #line 1886 "char_ref.rl" {te = p+1;{ output->first = 0x2a5d; {p++; goto _out; } }} break; case 1578: #line 1887 "char_ref.rl" {te = p+1;{ output->first = 0x2134; {p++; goto _out; } }} break; case 1579: #line 1888 "char_ref.rl" {te = p+1;{ output->first = 0x2134; {p++; goto _out; } }} break; case 1580: #line 1889 "char_ref.rl" {te = p+1;{ output->first = 0xaa; {p++; goto _out; } }} break; case 1581: #line 1891 "char_ref.rl" {te = p+1;{ output->first = 0xba; {p++; goto _out; } }} break; case 1582: #line 1893 "char_ref.rl" {te = p+1;{ output->first = 0x22b6; {p++; goto _out; } }} break; case 1583: #line 1894 "char_ref.rl" {te = p+1;{ output->first = 0x2a56; {p++; goto _out; } }} break; case 1584: #line 1895 "char_ref.rl" {te = p+1;{ output->first = 0x2a57; {p++; goto _out; } }} break; case 1585: #line 1896 "char_ref.rl" {te = p+1;{ output->first = 0x2a5b; {p++; goto _out; } }} break; case 1586: #line 1897 "char_ref.rl" {te = p+1;{ output->first = 0x2134; {p++; goto _out; } }} break; case 1587: #line 1898 "char_ref.rl" {te = p+1;{ output->first = 0xf8; {p++; goto _out; } }} break; case 1588: #line 1900 "char_ref.rl" {te = p+1;{ output->first = 0x2298; {p++; goto _out; } }} break; case 1589: #line 1901 "char_ref.rl" {te = p+1;{ output->first = 0xf5; {p++; goto _out; } }} break; case 1590: #line 1903 "char_ref.rl" {te = p+1;{ output->first = 0x2297; {p++; goto _out; } }} break; case 1591: #line 1904 "char_ref.rl" {te = p+1;{ output->first = 0x2a36; {p++; goto _out; } }} break; case 1592: #line 1905 "char_ref.rl" {te = p+1;{ output->first = 0xf6; {p++; goto _out; } }} break; case 1593: #line 1907 "char_ref.rl" {te = p+1;{ output->first = 0x233d; {p++; goto _out; } }} break; case 1594: #line 1908 "char_ref.rl" {te = p+1;{ output->first = 0x2225; {p++; goto _out; } }} break; case 1595: #line 1909 "char_ref.rl" {te = p+1;{ output->first = 0xb6; {p++; goto _out; } }} break; case 1596: #line 1911 "char_ref.rl" {te = p+1;{ output->first = 0x2225; {p++; goto _out; } }} break; case 1597: #line 1912 "char_ref.rl" {te = p+1;{ output->first = 0x2af3; {p++; goto _out; } }} break; case 1598: #line 1913 "char_ref.rl" {te = p+1;{ output->first = 0x2afd; {p++; goto _out; } }} break; case 1599: #line 1914 "char_ref.rl" {te = p+1;{ output->first = 0x2202; {p++; goto _out; } }} break; case 1600: #line 1915 "char_ref.rl" {te = p+1;{ output->first = 0x043f; {p++; goto _out; } }} break; case 1601: #line 1916 "char_ref.rl" {te = p+1;{ output->first = 0x25; {p++; goto _out; } }} break; case 1602: #line 1917 "char_ref.rl" {te = p+1;{ output->first = 0x2e; {p++; goto _out; } }} break; case 1603: #line 1918 "char_ref.rl" {te = p+1;{ output->first = 0x2030; {p++; goto _out; } }} break; case 1604: #line 1919 "char_ref.rl" {te = p+1;{ output->first = 0x22a5; {p++; goto _out; } }} break; case 1605: #line 1920 "char_ref.rl" {te = p+1;{ output->first = 0x2031; {p++; goto _out; } }} break; case 1606: #line 1921 "char_ref.rl" {te = p+1;{ output->first = 0x0001d52d; {p++; goto _out; } }} break; case 1607: #line 1922 "char_ref.rl" {te = p+1;{ output->first = 0x03c6; {p++; goto _out; } }} break; case 1608: #line 1923 "char_ref.rl" {te = p+1;{ output->first = 0x03d5; {p++; goto _out; } }} break; case 1609: #line 1924 "char_ref.rl" {te = p+1;{ output->first = 0x2133; {p++; goto _out; } }} break; case 1610: #line 1925 "char_ref.rl" {te = p+1;{ output->first = 0x260e; {p++; goto _out; } }} break; case 1611: #line 1926 "char_ref.rl" {te = p+1;{ output->first = 0x03c0; {p++; goto _out; } }} break; case 1612: #line 1927 "char_ref.rl" {te = p+1;{ output->first = 0x22d4; {p++; goto _out; } }} break; case 1613: #line 1928 "char_ref.rl" {te = p+1;{ output->first = 0x03d6; {p++; goto _out; } }} break; case 1614: #line 1929 "char_ref.rl" {te = p+1;{ output->first = 0x210f; {p++; goto _out; } }} break; case 1615: #line 1930 "char_ref.rl" {te = p+1;{ output->first = 0x210e; {p++; goto _out; } }} break; case 1616: #line 1931 "char_ref.rl" {te = p+1;{ output->first = 0x210f; {p++; goto _out; } }} break; case 1617: #line 1932 "char_ref.rl" {te = p+1;{ output->first = 0x2b; {p++; goto _out; } }} break; case 1618: #line 1933 "char_ref.rl" {te = p+1;{ output->first = 0x2a23; {p++; goto _out; } }} break; case 1619: #line 1934 "char_ref.rl" {te = p+1;{ output->first = 0x229e; {p++; goto _out; } }} break; case 1620: #line 1935 "char_ref.rl" {te = p+1;{ output->first = 0x2a22; {p++; goto _out; } }} break; case 1621: #line 1936 "char_ref.rl" {te = p+1;{ output->first = 0x2214; {p++; goto _out; } }} break; case 1622: #line 1937 "char_ref.rl" {te = p+1;{ output->first = 0x2a25; {p++; goto _out; } }} break; case 1623: #line 1938 "char_ref.rl" {te = p+1;{ output->first = 0x2a72; {p++; goto _out; } }} break; case 1624: #line 1939 "char_ref.rl" {te = p+1;{ output->first = 0xb1; {p++; goto _out; } }} break; case 1625: #line 1941 "char_ref.rl" {te = p+1;{ output->first = 0x2a26; {p++; goto _out; } }} break; case 1626: #line 1942 "char_ref.rl" {te = p+1;{ output->first = 0x2a27; {p++; goto _out; } }} break; case 1627: #line 1943 "char_ref.rl" {te = p+1;{ output->first = 0xb1; {p++; goto _out; } }} break; case 1628: #line 1944 "char_ref.rl" {te = p+1;{ output->first = 0x2a15; {p++; goto _out; } }} break; case 1629: #line 1945 "char_ref.rl" {te = p+1;{ output->first = 0x0001d561; {p++; goto _out; } }} break; case 1630: #line 1946 "char_ref.rl" {te = p+1;{ output->first = 0xa3; {p++; goto _out; } }} break; case 1631: #line 1948 "char_ref.rl" {te = p+1;{ output->first = 0x227a; {p++; goto _out; } }} break; case 1632: #line 1949 "char_ref.rl" {te = p+1;{ output->first = 0x2ab3; {p++; goto _out; } }} break; case 1633: #line 1950 "char_ref.rl" {te = p+1;{ output->first = 0x2ab7; {p++; goto _out; } }} break; case 1634: #line 1951 "char_ref.rl" {te = p+1;{ output->first = 0x227c; {p++; goto _out; } }} break; case 1635: #line 1952 "char_ref.rl" {te = p+1;{ output->first = 0x2aaf; {p++; goto _out; } }} break; case 1636: #line 1953 "char_ref.rl" {te = p+1;{ output->first = 0x227a; {p++; goto _out; } }} break; case 1637: #line 1954 "char_ref.rl" {te = p+1;{ output->first = 0x2ab7; {p++; goto _out; } }} break; case 1638: #line 1955 "char_ref.rl" {te = p+1;{ output->first = 0x227c; {p++; goto _out; } }} break; case 1639: #line 1956 "char_ref.rl" {te = p+1;{ output->first = 0x2aaf; {p++; goto _out; } }} break; case 1640: #line 1957 "char_ref.rl" {te = p+1;{ output->first = 0x2ab9; {p++; goto _out; } }} break; case 1641: #line 1958 "char_ref.rl" {te = p+1;{ output->first = 0x2ab5; {p++; goto _out; } }} break; case 1642: #line 1959 "char_ref.rl" {te = p+1;{ output->first = 0x22e8; {p++; goto _out; } }} break; case 1643: #line 1960 "char_ref.rl" {te = p+1;{ output->first = 0x227e; {p++; goto _out; } }} break; case 1644: #line 1961 "char_ref.rl" {te = p+1;{ output->first = 0x2032; {p++; goto _out; } }} break; case 1645: #line 1962 "char_ref.rl" {te = p+1;{ output->first = 0x2119; {p++; goto _out; } }} break; case 1646: #line 1963 "char_ref.rl" {te = p+1;{ output->first = 0x2ab5; {p++; goto _out; } }} break; case 1647: #line 1964 "char_ref.rl" {te = p+1;{ output->first = 0x2ab9; {p++; goto _out; } }} break; case 1648: #line 1965 "char_ref.rl" {te = p+1;{ output->first = 0x22e8; {p++; goto _out; } }} break; case 1649: #line 1966 "char_ref.rl" {te = p+1;{ output->first = 0x220f; {p++; goto _out; } }} break; case 1650: #line 1967 "char_ref.rl" {te = p+1;{ output->first = 0x232e; {p++; goto _out; } }} break; case 1651: #line 1968 "char_ref.rl" {te = p+1;{ output->first = 0x2312; {p++; goto _out; } }} break; case 1652: #line 1969 "char_ref.rl" {te = p+1;{ output->first = 0x2313; {p++; goto _out; } }} break; case 1653: #line 1970 "char_ref.rl" {te = p+1;{ output->first = 0x221d; {p++; goto _out; } }} break; case 1654: #line 1971 "char_ref.rl" {te = p+1;{ output->first = 0x221d; {p++; goto _out; } }} break; case 1655: #line 1972 "char_ref.rl" {te = p+1;{ output->first = 0x227e; {p++; goto _out; } }} break; case 1656: #line 1973 "char_ref.rl" {te = p+1;{ output->first = 0x22b0; {p++; goto _out; } }} break; case 1657: #line 1974 "char_ref.rl" {te = p+1;{ output->first = 0x0001d4c5; {p++; goto _out; } }} break; case 1658: #line 1975 "char_ref.rl" {te = p+1;{ output->first = 0x03c8; {p++; goto _out; } }} break; case 1659: #line 1976 "char_ref.rl" {te = p+1;{ output->first = 0x2008; {p++; goto _out; } }} break; case 1660: #line 1977 "char_ref.rl" {te = p+1;{ output->first = 0x0001d52e; {p++; goto _out; } }} break; case 1661: #line 1978 "char_ref.rl" {te = p+1;{ output->first = 0x2a0c; {p++; goto _out; } }} break; case 1662: #line 1979 "char_ref.rl" {te = p+1;{ output->first = 0x0001d562; {p++; goto _out; } }} break; case 1663: #line 1980 "char_ref.rl" {te = p+1;{ output->first = 0x2057; {p++; goto _out; } }} break; case 1664: #line 1981 "char_ref.rl" {te = p+1;{ output->first = 0x0001d4c6; {p++; goto _out; } }} break; case 1665: #line 1982 "char_ref.rl" {te = p+1;{ output->first = 0x210d; {p++; goto _out; } }} break; case 1666: #line 1983 "char_ref.rl" {te = p+1;{ output->first = 0x2a16; {p++; goto _out; } }} break; case 1667: #line 1984 "char_ref.rl" {te = p+1;{ output->first = 0x3f; {p++; goto _out; } }} break; case 1668: #line 1985 "char_ref.rl" {te = p+1;{ output->first = 0x225f; {p++; goto _out; } }} break; case 1669: #line 1986 "char_ref.rl" {te = p+1;{ output->first = 0x22; {p++; goto _out; } }} break; case 1670: #line 1988 "char_ref.rl" {te = p+1;{ output->first = 0x21db; {p++; goto _out; } }} break; case 1671: #line 1989 "char_ref.rl" {te = p+1;{ output->first = 0x21d2; {p++; goto _out; } }} break; case 1672: #line 1990 "char_ref.rl" {te = p+1;{ output->first = 0x291c; {p++; goto _out; } }} break; case 1673: #line 1991 "char_ref.rl" {te = p+1;{ output->first = 0x290f; {p++; goto _out; } }} break; case 1674: #line 1992 "char_ref.rl" {te = p+1;{ output->first = 0x2964; {p++; goto _out; } }} break; case 1675: #line 1993 "char_ref.rl" {te = p+1;{ output->first = 0x223d; output->second = 0x0331; {p++; goto _out; } }} break; case 1676: #line 1994 "char_ref.rl" {te = p+1;{ output->first = 0x0155; {p++; goto _out; } }} break; case 1677: #line 1995 "char_ref.rl" {te = p+1;{ output->first = 0x221a; {p++; goto _out; } }} break; case 1678: #line 1996 "char_ref.rl" {te = p+1;{ output->first = 0x29b3; {p++; goto _out; } }} break; case 1679: #line 1997 "char_ref.rl" {te = p+1;{ output->first = 0x27e9; {p++; goto _out; } }} break; case 1680: #line 1998 "char_ref.rl" {te = p+1;{ output->first = 0x2992; {p++; goto _out; } }} break; case 1681: #line 1999 "char_ref.rl" {te = p+1;{ output->first = 0x29a5; {p++; goto _out; } }} break; case 1682: #line 2000 "char_ref.rl" {te = p+1;{ output->first = 0x27e9; {p++; goto _out; } }} break; case 1683: #line 2001 "char_ref.rl" {te = p+1;{ output->first = 0xbb; {p++; goto _out; } }} break; case 1684: #line 2003 "char_ref.rl" {te = p+1;{ output->first = 0x2192; {p++; goto _out; } }} break; case 1685: #line 2004 "char_ref.rl" {te = p+1;{ output->first = 0x2975; {p++; goto _out; } }} break; case 1686: #line 2005 "char_ref.rl" {te = p+1;{ output->first = 0x21e5; {p++; goto _out; } }} break; case 1687: #line 2006 "char_ref.rl" {te = p+1;{ output->first = 0x2920; {p++; goto _out; } }} break; case 1688: #line 2007 "char_ref.rl" {te = p+1;{ output->first = 0x2933; {p++; goto _out; } }} break; case 1689: #line 2008 "char_ref.rl" {te = p+1;{ output->first = 0x291e; {p++; goto _out; } }} break; case 1690: #line 2009 "char_ref.rl" {te = p+1;{ output->first = 0x21aa; {p++; goto _out; } }} break; case 1691: #line 2010 "char_ref.rl" {te = p+1;{ output->first = 0x21ac; {p++; goto _out; } }} break; case 1692: #line 2011 "char_ref.rl" {te = p+1;{ output->first = 0x2945; {p++; goto _out; } }} break; case 1693: #line 2012 "char_ref.rl" {te = p+1;{ output->first = 0x2974; {p++; goto _out; } }} break; case 1694: #line 2013 "char_ref.rl" {te = p+1;{ output->first = 0x21a3; {p++; goto _out; } }} break; case 1695: #line 2014 "char_ref.rl" {te = p+1;{ output->first = 0x219d; {p++; goto _out; } }} break; case 1696: #line 2015 "char_ref.rl" {te = p+1;{ output->first = 0x291a; {p++; goto _out; } }} break; case 1697: #line 2016 "char_ref.rl" {te = p+1;{ output->first = 0x2236; {p++; goto _out; } }} break; case 1698: #line 2017 "char_ref.rl" {te = p+1;{ output->first = 0x211a; {p++; goto _out; } }} break; case 1699: #line 2018 "char_ref.rl" {te = p+1;{ output->first = 0x290d; {p++; goto _out; } }} break; case 1700: #line 2019 "char_ref.rl" {te = p+1;{ output->first = 0x2773; {p++; goto _out; } }} break; case 1701: #line 2020 "char_ref.rl" {te = p+1;{ output->first = 0x7d; {p++; goto _out; } }} break; case 1702: #line 2021 "char_ref.rl" {te = p+1;{ output->first = 0x5d; {p++; goto _out; } }} break; case 1703: #line 2022 "char_ref.rl" {te = p+1;{ output->first = 0x298c; {p++; goto _out; } }} break; case 1704: #line 2023 "char_ref.rl" {te = p+1;{ output->first = 0x298e; {p++; goto _out; } }} break; case 1705: #line 2024 "char_ref.rl" {te = p+1;{ output->first = 0x2990; {p++; goto _out; } }} break; case 1706: #line 2025 "char_ref.rl" {te = p+1;{ output->first = 0x0159; {p++; goto _out; } }} break; case 1707: #line 2026 "char_ref.rl" {te = p+1;{ output->first = 0x0157; {p++; goto _out; } }} break; case 1708: #line 2027 "char_ref.rl" {te = p+1;{ output->first = 0x2309; {p++; goto _out; } }} break; case 1709: #line 2028 "char_ref.rl" {te = p+1;{ output->first = 0x7d; {p++; goto _out; } }} break; case 1710: #line 2029 "char_ref.rl" {te = p+1;{ output->first = 0x0440; {p++; goto _out; } }} break; case 1711: #line 2030 "char_ref.rl" {te = p+1;{ output->first = 0x2937; {p++; goto _out; } }} break; case 1712: #line 2031 "char_ref.rl" {te = p+1;{ output->first = 0x2969; {p++; goto _out; } }} break; case 1713: #line 2032 "char_ref.rl" {te = p+1;{ output->first = 0x201d; {p++; goto _out; } }} break; case 1714: #line 2033 "char_ref.rl" {te = p+1;{ output->first = 0x201d; {p++; goto _out; } }} break; case 1715: #line 2034 "char_ref.rl" {te = p+1;{ output->first = 0x21b3; {p++; goto _out; } }} break; case 1716: #line 2035 "char_ref.rl" {te = p+1;{ output->first = 0x211c; {p++; goto _out; } }} break; case 1717: #line 2036 "char_ref.rl" {te = p+1;{ output->first = 0x211b; {p++; goto _out; } }} break; case 1718: #line 2037 "char_ref.rl" {te = p+1;{ output->first = 0x211c; {p++; goto _out; } }} break; case 1719: #line 2038 "char_ref.rl" {te = p+1;{ output->first = 0x211d; {p++; goto _out; } }} break; case 1720: #line 2039 "char_ref.rl" {te = p+1;{ output->first = 0x25ad; {p++; goto _out; } }} break; case 1721: #line 2040 "char_ref.rl" {te = p+1;{ output->first = 0xae; {p++; goto _out; } }} break; case 1722: #line 2042 "char_ref.rl" {te = p+1;{ output->first = 0x297d; {p++; goto _out; } }} break; case 1723: #line 2043 "char_ref.rl" {te = p+1;{ output->first = 0x230b; {p++; goto _out; } }} break; case 1724: #line 2044 "char_ref.rl" {te = p+1;{ output->first = 0x0001d52f; {p++; goto _out; } }} break; case 1725: #line 2045 "char_ref.rl" {te = p+1;{ output->first = 0x21c1; {p++; goto _out; } }} break; case 1726: #line 2046 "char_ref.rl" {te = p+1;{ output->first = 0x21c0; {p++; goto _out; } }} break; case 1727: #line 2047 "char_ref.rl" {te = p+1;{ output->first = 0x296c; {p++; goto _out; } }} break; case 1728: #line 2048 "char_ref.rl" {te = p+1;{ output->first = 0x03c1; {p++; goto _out; } }} break; case 1729: #line 2049 "char_ref.rl" {te = p+1;{ output->first = 0x03f1; {p++; goto _out; } }} break; case 1730: #line 2050 "char_ref.rl" {te = p+1;{ output->first = 0x2192; {p++; goto _out; } }} break; case 1731: #line 2051 "char_ref.rl" {te = p+1;{ output->first = 0x21a3; {p++; goto _out; } }} break; case 1732: #line 2052 "char_ref.rl" {te = p+1;{ output->first = 0x21c1; {p++; goto _out; } }} break; case 1733: #line 2053 "char_ref.rl" {te = p+1;{ output->first = 0x21c0; {p++; goto _out; } }} break; case 1734: #line 2054 "char_ref.rl" {te = p+1;{ output->first = 0x21c4; {p++; goto _out; } }} break; case 1735: #line 2055 "char_ref.rl" {te = p+1;{ output->first = 0x21cc; {p++; goto _out; } }} break; case 1736: #line 2056 "char_ref.rl" {te = p+1;{ output->first = 0x21c9; {p++; goto _out; } }} break; case 1737: #line 2057 "char_ref.rl" {te = p+1;{ output->first = 0x219d; {p++; goto _out; } }} break; case 1738: #line 2058 "char_ref.rl" {te = p+1;{ output->first = 0x22cc; {p++; goto _out; } }} break; case 1739: #line 2059 "char_ref.rl" {te = p+1;{ output->first = 0x02da; {p++; goto _out; } }} break; case 1740: #line 2060 "char_ref.rl" {te = p+1;{ output->first = 0x2253; {p++; goto _out; } }} break; case 1741: #line 2061 "char_ref.rl" {te = p+1;{ output->first = 0x21c4; {p++; goto _out; } }} break; case 1742: #line 2062 "char_ref.rl" {te = p+1;{ output->first = 0x21cc; {p++; goto _out; } }} break; case 1743: #line 2063 "char_ref.rl" {te = p+1;{ output->first = 0x200f; {p++; goto _out; } }} break; case 1744: #line 2064 "char_ref.rl" {te = p+1;{ output->first = 0x23b1; {p++; goto _out; } }} break; case 1745: #line 2065 "char_ref.rl" {te = p+1;{ output->first = 0x23b1; {p++; goto _out; } }} break; case 1746: #line 2066 "char_ref.rl" {te = p+1;{ output->first = 0x2aee; {p++; goto _out; } }} break; case 1747: #line 2067 "char_ref.rl" {te = p+1;{ output->first = 0x27ed; {p++; goto _out; } }} break; case 1748: #line 2068 "char_ref.rl" {te = p+1;{ output->first = 0x21fe; {p++; goto _out; } }} break; case 1749: #line 2069 "char_ref.rl" {te = p+1;{ output->first = 0x27e7; {p++; goto _out; } }} break; case 1750: #line 2070 "char_ref.rl" {te = p+1;{ output->first = 0x2986; {p++; goto _out; } }} break; case 1751: #line 2071 "char_ref.rl" {te = p+1;{ output->first = 0x0001d563; {p++; goto _out; } }} break; case 1752: #line 2072 "char_ref.rl" {te = p+1;{ output->first = 0x2a2e; {p++; goto _out; } }} break; case 1753: #line 2073 "char_ref.rl" {te = p+1;{ output->first = 0x2a35; {p++; goto _out; } }} break; case 1754: #line 2074 "char_ref.rl" {te = p+1;{ output->first = 0x29; {p++; goto _out; } }} break; case 1755: #line 2075 "char_ref.rl" {te = p+1;{ output->first = 0x2994; {p++; goto _out; } }} break; case 1756: #line 2076 "char_ref.rl" {te = p+1;{ output->first = 0x2a12; {p++; goto _out; } }} break; case 1757: #line 2077 "char_ref.rl" {te = p+1;{ output->first = 0x21c9; {p++; goto _out; } }} break; case 1758: #line 2078 "char_ref.rl" {te = p+1;{ output->first = 0x203a; {p++; goto _out; } }} break; case 1759: #line 2079 "char_ref.rl" {te = p+1;{ output->first = 0x0001d4c7; {p++; goto _out; } }} break; case 1760: #line 2080 "char_ref.rl" {te = p+1;{ output->first = 0x21b1; {p++; goto _out; } }} break; case 1761: #line 2081 "char_ref.rl" {te = p+1;{ output->first = 0x5d; {p++; goto _out; } }} break; case 1762: #line 2082 "char_ref.rl" {te = p+1;{ output->first = 0x2019; {p++; goto _out; } }} break; case 1763: #line 2083 "char_ref.rl" {te = p+1;{ output->first = 0x2019; {p++; goto _out; } }} break; case 1764: #line 2084 "char_ref.rl" {te = p+1;{ output->first = 0x22cc; {p++; goto _out; } }} break; case 1765: #line 2085 "char_ref.rl" {te = p+1;{ output->first = 0x22ca; {p++; goto _out; } }} break; case 1766: #line 2086 "char_ref.rl" {te = p+1;{ output->first = 0x25b9; {p++; goto _out; } }} break; case 1767: #line 2087 "char_ref.rl" {te = p+1;{ output->first = 0x22b5; {p++; goto _out; } }} break; case 1768: #line 2088 "char_ref.rl" {te = p+1;{ output->first = 0x25b8; {p++; goto _out; } }} break; case 1769: #line 2089 "char_ref.rl" {te = p+1;{ output->first = 0x29ce; {p++; goto _out; } }} break; case 1770: #line 2090 "char_ref.rl" {te = p+1;{ output->first = 0x2968; {p++; goto _out; } }} break; case 1771: #line 2091 "char_ref.rl" {te = p+1;{ output->first = 0x211e; {p++; goto _out; } }} break; case 1772: #line 2092 "char_ref.rl" {te = p+1;{ output->first = 0x015b; {p++; goto _out; } }} break; case 1773: #line 2093 "char_ref.rl" {te = p+1;{ output->first = 0x201a; {p++; goto _out; } }} break; case 1774: #line 2094 "char_ref.rl" {te = p+1;{ output->first = 0x227b; {p++; goto _out; } }} break; case 1775: #line 2095 "char_ref.rl" {te = p+1;{ output->first = 0x2ab4; {p++; goto _out; } }} break; case 1776: #line 2096 "char_ref.rl" {te = p+1;{ output->first = 0x2ab8; {p++; goto _out; } }} break; case 1777: #line 2097 "char_ref.rl" {te = p+1;{ output->first = 0x0161; {p++; goto _out; } }} break; case 1778: #line 2098 "char_ref.rl" {te = p+1;{ output->first = 0x227d; {p++; goto _out; } }} break; case 1779: #line 2099 "char_ref.rl" {te = p+1;{ output->first = 0x2ab0; {p++; goto _out; } }} break; case 1780: #line 2100 "char_ref.rl" {te = p+1;{ output->first = 0x015f; {p++; goto _out; } }} break; case 1781: #line 2101 "char_ref.rl" {te = p+1;{ output->first = 0x015d; {p++; goto _out; } }} break; case 1782: #line 2102 "char_ref.rl" {te = p+1;{ output->first = 0x2ab6; {p++; goto _out; } }} break; case 1783: #line 2103 "char_ref.rl" {te = p+1;{ output->first = 0x2aba; {p++; goto _out; } }} break; case 1784: #line 2104 "char_ref.rl" {te = p+1;{ output->first = 0x22e9; {p++; goto _out; } }} break; case 1785: #line 2105 "char_ref.rl" {te = p+1;{ output->first = 0x2a13; {p++; goto _out; } }} break; case 1786: #line 2106 "char_ref.rl" {te = p+1;{ output->first = 0x227f; {p++; goto _out; } }} break; case 1787: #line 2107 "char_ref.rl" {te = p+1;{ output->first = 0x0441; {p++; goto _out; } }} break; case 1788: #line 2108 "char_ref.rl" {te = p+1;{ output->first = 0x22c5; {p++; goto _out; } }} break; case 1789: #line 2109 "char_ref.rl" {te = p+1;{ output->first = 0x22a1; {p++; goto _out; } }} break; case 1790: #line 2110 "char_ref.rl" {te = p+1;{ output->first = 0x2a66; {p++; goto _out; } }} break; case 1791: #line 2111 "char_ref.rl" {te = p+1;{ output->first = 0x21d8; {p++; goto _out; } }} break; case 1792: #line 2112 "char_ref.rl" {te = p+1;{ output->first = 0x2925; {p++; goto _out; } }} break; case 1793: #line 2113 "char_ref.rl" {te = p+1;{ output->first = 0x2198; {p++; goto _out; } }} break; case 1794: #line 2114 "char_ref.rl" {te = p+1;{ output->first = 0x2198; {p++; goto _out; } }} break; case 1795: #line 2115 "char_ref.rl" {te = p+1;{ output->first = 0xa7; {p++; goto _out; } }} break; case 1796: #line 2117 "char_ref.rl" {te = p+1;{ output->first = 0x3b; {p++; goto _out; } }} break; case 1797: #line 2118 "char_ref.rl" {te = p+1;{ output->first = 0x2929; {p++; goto _out; } }} break; case 1798: #line 2119 "char_ref.rl" {te = p+1;{ output->first = 0x2216; {p++; goto _out; } }} break; case 1799: #line 2120 "char_ref.rl" {te = p+1;{ output->first = 0x2216; {p++; goto _out; } }} break; case 1800: #line 2121 "char_ref.rl" {te = p+1;{ output->first = 0x2736; {p++; goto _out; } }} break; case 1801: #line 2122 "char_ref.rl" {te = p+1;{ output->first = 0x0001d530; {p++; goto _out; } }} break; case 1802: #line 2123 "char_ref.rl" {te = p+1;{ output->first = 0x2322; {p++; goto _out; } }} break; case 1803: #line 2124 "char_ref.rl" {te = p+1;{ output->first = 0x266f; {p++; goto _out; } }} break; case 1804: #line 2125 "char_ref.rl" {te = p+1;{ output->first = 0x0449; {p++; goto _out; } }} break; case 1805: #line 2126 "char_ref.rl" {te = p+1;{ output->first = 0x0448; {p++; goto _out; } }} break; case 1806: #line 2127 "char_ref.rl" {te = p+1;{ output->first = 0x2223; {p++; goto _out; } }} break; case 1807: #line 2128 "char_ref.rl" {te = p+1;{ output->first = 0x2225; {p++; goto _out; } }} break; case 1808: #line 2129 "char_ref.rl" {te = p+1;{ output->first = 0xad; {p++; goto _out; } }} break; case 1809: #line 2131 "char_ref.rl" {te = p+1;{ output->first = 0x03c3; {p++; goto _out; } }} break; case 1810: #line 2132 "char_ref.rl" {te = p+1;{ output->first = 0x03c2; {p++; goto _out; } }} break; case 1811: #line 2133 "char_ref.rl" {te = p+1;{ output->first = 0x03c2; {p++; goto _out; } }} break; case 1812: #line 2134 "char_ref.rl" {te = p+1;{ output->first = 0x223c; {p++; goto _out; } }} break; case 1813: #line 2135 "char_ref.rl" {te = p+1;{ output->first = 0x2a6a; {p++; goto _out; } }} break; case 1814: #line 2136 "char_ref.rl" {te = p+1;{ output->first = 0x2243; {p++; goto _out; } }} break; case 1815: #line 2137 "char_ref.rl" {te = p+1;{ output->first = 0x2243; {p++; goto _out; } }} break; case 1816: #line 2138 "char_ref.rl" {te = p+1;{ output->first = 0x2a9e; {p++; goto _out; } }} break; case 1817: #line 2139 "char_ref.rl" {te = p+1;{ output->first = 0x2aa0; {p++; goto _out; } }} break; case 1818: #line 2140 "char_ref.rl" {te = p+1;{ output->first = 0x2a9d; {p++; goto _out; } }} break; case 1819: #line 2141 "char_ref.rl" {te = p+1;{ output->first = 0x2a9f; {p++; goto _out; } }} break; case 1820: #line 2142 "char_ref.rl" {te = p+1;{ output->first = 0x2246; {p++; goto _out; } }} break; case 1821: #line 2143 "char_ref.rl" {te = p+1;{ output->first = 0x2a24; {p++; goto _out; } }} break; case 1822: #line 2144 "char_ref.rl" {te = p+1;{ output->first = 0x2972; {p++; goto _out; } }} break; case 1823: #line 2145 "char_ref.rl" {te = p+1;{ output->first = 0x2190; {p++; goto _out; } }} break; case 1824: #line 2146 "char_ref.rl" {te = p+1;{ output->first = 0x2216; {p++; goto _out; } }} break; case 1825: #line 2147 "char_ref.rl" {te = p+1;{ output->first = 0x2a33; {p++; goto _out; } }} break; case 1826: #line 2148 "char_ref.rl" {te = p+1;{ output->first = 0x29e4; {p++; goto _out; } }} break; case 1827: #line 2149 "char_ref.rl" {te = p+1;{ output->first = 0x2223; {p++; goto _out; } }} break; case 1828: #line 2150 "char_ref.rl" {te = p+1;{ output->first = 0x2323; {p++; goto _out; } }} break; case 1829: #line 2151 "char_ref.rl" {te = p+1;{ output->first = 0x2aaa; {p++; goto _out; } }} break; case 1830: #line 2152 "char_ref.rl" {te = p+1;{ output->first = 0x2aac; {p++; goto _out; } }} break; case 1831: #line 2153 "char_ref.rl" {te = p+1;{ output->first = 0x2aac; output->second = 0xfe00; {p++; goto _out; } }} break; case 1832: #line 2154 "char_ref.rl" {te = p+1;{ output->first = 0x044c; {p++; goto _out; } }} break; case 1833: #line 2155 "char_ref.rl" {te = p+1;{ output->first = 0x2f; {p++; goto _out; } }} break; case 1834: #line 2156 "char_ref.rl" {te = p+1;{ output->first = 0x29c4; {p++; goto _out; } }} break; case 1835: #line 2157 "char_ref.rl" {te = p+1;{ output->first = 0x233f; {p++; goto _out; } }} break; case 1836: #line 2158 "char_ref.rl" {te = p+1;{ output->first = 0x0001d564; {p++; goto _out; } }} break; case 1837: #line 2159 "char_ref.rl" {te = p+1;{ output->first = 0x2660; {p++; goto _out; } }} break; case 1838: #line 2160 "char_ref.rl" {te = p+1;{ output->first = 0x2660; {p++; goto _out; } }} break; case 1839: #line 2161 "char_ref.rl" {te = p+1;{ output->first = 0x2225; {p++; goto _out; } }} break; case 1840: #line 2162 "char_ref.rl" {te = p+1;{ output->first = 0x2293; {p++; goto _out; } }} break; case 1841: #line 2163 "char_ref.rl" {te = p+1;{ output->first = 0x2293; output->second = 0xfe00; {p++; goto _out; } }} break; case 1842: #line 2164 "char_ref.rl" {te = p+1;{ output->first = 0x2294; {p++; goto _out; } }} break; case 1843: #line 2165 "char_ref.rl" {te = p+1;{ output->first = 0x2294; output->second = 0xfe00; {p++; goto _out; } }} break; case 1844: #line 2166 "char_ref.rl" {te = p+1;{ output->first = 0x228f; {p++; goto _out; } }} break; case 1845: #line 2167 "char_ref.rl" {te = p+1;{ output->first = 0x2291; {p++; goto _out; } }} break; case 1846: #line 2168 "char_ref.rl" {te = p+1;{ output->first = 0x228f; {p++; goto _out; } }} break; case 1847: #line 2169 "char_ref.rl" {te = p+1;{ output->first = 0x2291; {p++; goto _out; } }} break; case 1848: #line 2170 "char_ref.rl" {te = p+1;{ output->first = 0x2290; {p++; goto _out; } }} break; case 1849: #line 2171 "char_ref.rl" {te = p+1;{ output->first = 0x2292; {p++; goto _out; } }} break; case 1850: #line 2172 "char_ref.rl" {te = p+1;{ output->first = 0x2290; {p++; goto _out; } }} break; case 1851: #line 2173 "char_ref.rl" {te = p+1;{ output->first = 0x2292; {p++; goto _out; } }} break; case 1852: #line 2174 "char_ref.rl" {te = p+1;{ output->first = 0x25a1; {p++; goto _out; } }} break; case 1853: #line 2175 "char_ref.rl" {te = p+1;{ output->first = 0x25a1; {p++; goto _out; } }} break; case 1854: #line 2176 "char_ref.rl" {te = p+1;{ output->first = 0x25aa; {p++; goto _out; } }} break; case 1855: #line 2177 "char_ref.rl" {te = p+1;{ output->first = 0x25aa; {p++; goto _out; } }} break; case 1856: #line 2178 "char_ref.rl" {te = p+1;{ output->first = 0x2192; {p++; goto _out; } }} break; case 1857: #line 2179 "char_ref.rl" {te = p+1;{ output->first = 0x0001d4c8; {p++; goto _out; } }} break; case 1858: #line 2180 "char_ref.rl" {te = p+1;{ output->first = 0x2216; {p++; goto _out; } }} break; case 1859: #line 2181 "char_ref.rl" {te = p+1;{ output->first = 0x2323; {p++; goto _out; } }} break; case 1860: #line 2182 "char_ref.rl" {te = p+1;{ output->first = 0x22c6; {p++; goto _out; } }} break; case 1861: #line 2183 "char_ref.rl" {te = p+1;{ output->first = 0x2606; {p++; goto _out; } }} break; case 1862: #line 2184 "char_ref.rl" {te = p+1;{ output->first = 0x2605; {p++; goto _out; } }} break; case 1863: #line 2185 "char_ref.rl" {te = p+1;{ output->first = 0x03f5; {p++; goto _out; } }} break; case 1864: #line 2186 "char_ref.rl" {te = p+1;{ output->first = 0x03d5; {p++; goto _out; } }} break; case 1865: #line 2187 "char_ref.rl" {te = p+1;{ output->first = 0xaf; {p++; goto _out; } }} break; case 1866: #line 2188 "char_ref.rl" {te = p+1;{ output->first = 0x2282; {p++; goto _out; } }} break; case 1867: #line 2189 "char_ref.rl" {te = p+1;{ output->first = 0x2ac5; {p++; goto _out; } }} break; case 1868: #line 2190 "char_ref.rl" {te = p+1;{ output->first = 0x2abd; {p++; goto _out; } }} break; case 1869: #line 2191 "char_ref.rl" {te = p+1;{ output->first = 0x2286; {p++; goto _out; } }} break; case 1870: #line 2192 "char_ref.rl" {te = p+1;{ output->first = 0x2ac3; {p++; goto _out; } }} break; case 1871: #line 2193 "char_ref.rl" {te = p+1;{ output->first = 0x2ac1; {p++; goto _out; } }} break; case 1872: #line 2194 "char_ref.rl" {te = p+1;{ output->first = 0x2acb; {p++; goto _out; } }} break; case 1873: #line 2195 "char_ref.rl" {te = p+1;{ output->first = 0x228a; {p++; goto _out; } }} break; case 1874: #line 2196 "char_ref.rl" {te = p+1;{ output->first = 0x2abf; {p++; goto _out; } }} break; case 1875: #line 2197 "char_ref.rl" {te = p+1;{ output->first = 0x2979; {p++; goto _out; } }} break; case 1876: #line 2198 "char_ref.rl" {te = p+1;{ output->first = 0x2282; {p++; goto _out; } }} break; case 1877: #line 2199 "char_ref.rl" {te = p+1;{ output->first = 0x2286; {p++; goto _out; } }} break; case 1878: #line 2200 "char_ref.rl" {te = p+1;{ output->first = 0x2ac5; {p++; goto _out; } }} break; case 1879: #line 2201 "char_ref.rl" {te = p+1;{ output->first = 0x228a; {p++; goto _out; } }} break; case 1880: #line 2202 "char_ref.rl" {te = p+1;{ output->first = 0x2acb; {p++; goto _out; } }} break; case 1881: #line 2203 "char_ref.rl" {te = p+1;{ output->first = 0x2ac7; {p++; goto _out; } }} break; case 1882: #line 2204 "char_ref.rl" {te = p+1;{ output->first = 0x2ad5; {p++; goto _out; } }} break; case 1883: #line 2205 "char_ref.rl" {te = p+1;{ output->first = 0x2ad3; {p++; goto _out; } }} break; case 1884: #line 2206 "char_ref.rl" {te = p+1;{ output->first = 0x227b; {p++; goto _out; } }} break; case 1885: #line 2207 "char_ref.rl" {te = p+1;{ output->first = 0x2ab8; {p++; goto _out; } }} break; case 1886: #line 2208 "char_ref.rl" {te = p+1;{ output->first = 0x227d; {p++; goto _out; } }} break; case 1887: #line 2209 "char_ref.rl" {te = p+1;{ output->first = 0x2ab0; {p++; goto _out; } }} break; case 1888: #line 2210 "char_ref.rl" {te = p+1;{ output->first = 0x2aba; {p++; goto _out; } }} break; case 1889: #line 2211 "char_ref.rl" {te = p+1;{ output->first = 0x2ab6; {p++; goto _out; } }} break; case 1890: #line 2212 "char_ref.rl" {te = p+1;{ output->first = 0x22e9; {p++; goto _out; } }} break; case 1891: #line 2213 "char_ref.rl" {te = p+1;{ output->first = 0x227f; {p++; goto _out; } }} break; case 1892: #line 2214 "char_ref.rl" {te = p+1;{ output->first = 0x2211; {p++; goto _out; } }} break; case 1893: #line 2215 "char_ref.rl" {te = p+1;{ output->first = 0x266a; {p++; goto _out; } }} break; case 1894: #line 2216 "char_ref.rl" {te = p+1;{ output->first = 0xb9; {p++; goto _out; } }} break; case 1895: #line 2218 "char_ref.rl" {te = p+1;{ output->first = 0xb2; {p++; goto _out; } }} break; case 1896: #line 2220 "char_ref.rl" {te = p+1;{ output->first = 0xb3; {p++; goto _out; } }} break; case 1897: #line 2222 "char_ref.rl" {te = p+1;{ output->first = 0x2283; {p++; goto _out; } }} break; case 1898: #line 2223 "char_ref.rl" {te = p+1;{ output->first = 0x2ac6; {p++; goto _out; } }} break; case 1899: #line 2224 "char_ref.rl" {te = p+1;{ output->first = 0x2abe; {p++; goto _out; } }} break; case 1900: #line 2225 "char_ref.rl" {te = p+1;{ output->first = 0x2ad8; {p++; goto _out; } }} break; case 1901: #line 2226 "char_ref.rl" {te = p+1;{ output->first = 0x2287; {p++; goto _out; } }} break; case 1902: #line 2227 "char_ref.rl" {te = p+1;{ output->first = 0x2ac4; {p++; goto _out; } }} break; case 1903: #line 2228 "char_ref.rl" {te = p+1;{ output->first = 0x27c9; {p++; goto _out; } }} break; case 1904: #line 2229 "char_ref.rl" {te = p+1;{ output->first = 0x2ad7; {p++; goto _out; } }} break; case 1905: #line 2230 "char_ref.rl" {te = p+1;{ output->first = 0x297b; {p++; goto _out; } }} break; case 1906: #line 2231 "char_ref.rl" {te = p+1;{ output->first = 0x2ac2; {p++; goto _out; } }} break; case 1907: #line 2232 "char_ref.rl" {te = p+1;{ output->first = 0x2acc; {p++; goto _out; } }} break; case 1908: #line 2233 "char_ref.rl" {te = p+1;{ output->first = 0x228b; {p++; goto _out; } }} break; case 1909: #line 2234 "char_ref.rl" {te = p+1;{ output->first = 0x2ac0; {p++; goto _out; } }} break; case 1910: #line 2235 "char_ref.rl" {te = p+1;{ output->first = 0x2283; {p++; goto _out; } }} break; case 1911: #line 2236 "char_ref.rl" {te = p+1;{ output->first = 0x2287; {p++; goto _out; } }} break; case 1912: #line 2237 "char_ref.rl" {te = p+1;{ output->first = 0x2ac6; {p++; goto _out; } }} break; case 1913: #line 2238 "char_ref.rl" {te = p+1;{ output->first = 0x228b; {p++; goto _out; } }} break; case 1914: #line 2239 "char_ref.rl" {te = p+1;{ output->first = 0x2acc; {p++; goto _out; } }} break; case 1915: #line 2240 "char_ref.rl" {te = p+1;{ output->first = 0x2ac8; {p++; goto _out; } }} break; case 1916: #line 2241 "char_ref.rl" {te = p+1;{ output->first = 0x2ad4; {p++; goto _out; } }} break; case 1917: #line 2242 "char_ref.rl" {te = p+1;{ output->first = 0x2ad6; {p++; goto _out; } }} break; case 1918: #line 2243 "char_ref.rl" {te = p+1;{ output->first = 0x21d9; {p++; goto _out; } }} break; case 1919: #line 2244 "char_ref.rl" {te = p+1;{ output->first = 0x2926; {p++; goto _out; } }} break; case 1920: #line 2245 "char_ref.rl" {te = p+1;{ output->first = 0x2199; {p++; goto _out; } }} break; case 1921: #line 2246 "char_ref.rl" {te = p+1;{ output->first = 0x2199; {p++; goto _out; } }} break; case 1922: #line 2247 "char_ref.rl" {te = p+1;{ output->first = 0x292a; {p++; goto _out; } }} break; case 1923: #line 2248 "char_ref.rl" {te = p+1;{ output->first = 0xdf; {p++; goto _out; } }} break; case 1924: #line 2250 "char_ref.rl" {te = p+1;{ output->first = 0x2316; {p++; goto _out; } }} break; case 1925: #line 2251 "char_ref.rl" {te = p+1;{ output->first = 0x03c4; {p++; goto _out; } }} break; case 1926: #line 2252 "char_ref.rl" {te = p+1;{ output->first = 0x23b4; {p++; goto _out; } }} break; case 1927: #line 2253 "char_ref.rl" {te = p+1;{ output->first = 0x0165; {p++; goto _out; } }} break; case 1928: #line 2254 "char_ref.rl" {te = p+1;{ output->first = 0x0163; {p++; goto _out; } }} break; case 1929: #line 2255 "char_ref.rl" {te = p+1;{ output->first = 0x0442; {p++; goto _out; } }} break; case 1930: #line 2256 "char_ref.rl" {te = p+1;{ output->first = 0x20db; {p++; goto _out; } }} break; case 1931: #line 2257 "char_ref.rl" {te = p+1;{ output->first = 0x2315; {p++; goto _out; } }} break; case 1932: #line 2258 "char_ref.rl" {te = p+1;{ output->first = 0x0001d531; {p++; goto _out; } }} break; case 1933: #line 2259 "char_ref.rl" {te = p+1;{ output->first = 0x2234; {p++; goto _out; } }} break; case 1934: #line 2260 "char_ref.rl" {te = p+1;{ output->first = 0x2234; {p++; goto _out; } }} break; case 1935: #line 2261 "char_ref.rl" {te = p+1;{ output->first = 0x03b8; {p++; goto _out; } }} break; case 1936: #line 2262 "char_ref.rl" {te = p+1;{ output->first = 0x03d1; {p++; goto _out; } }} break; case 1937: #line 2263 "char_ref.rl" {te = p+1;{ output->first = 0x03d1; {p++; goto _out; } }} break; case 1938: #line 2264 "char_ref.rl" {te = p+1;{ output->first = 0x2248; {p++; goto _out; } }} break; case 1939: #line 2265 "char_ref.rl" {te = p+1;{ output->first = 0x223c; {p++; goto _out; } }} break; case 1940: #line 2266 "char_ref.rl" {te = p+1;{ output->first = 0x2009; {p++; goto _out; } }} break; case 1941: #line 2267 "char_ref.rl" {te = p+1;{ output->first = 0x2248; {p++; goto _out; } }} break; case 1942: #line 2268 "char_ref.rl" {te = p+1;{ output->first = 0x223c; {p++; goto _out; } }} break; case 1943: #line 2269 "char_ref.rl" {te = p+1;{ output->first = 0xfe; {p++; goto _out; } }} break; case 1944: #line 2271 "char_ref.rl" {te = p+1;{ output->first = 0x02dc; {p++; goto _out; } }} break; case 1945: #line 2272 "char_ref.rl" {te = p+1;{ output->first = 0xd7; {p++; goto _out; } }} break; case 1946: #line 2274 "char_ref.rl" {te = p+1;{ output->first = 0x22a0; {p++; goto _out; } }} break; case 1947: #line 2275 "char_ref.rl" {te = p+1;{ output->first = 0x2a31; {p++; goto _out; } }} break; case 1948: #line 2276 "char_ref.rl" {te = p+1;{ output->first = 0x2a30; {p++; goto _out; } }} break; case 1949: #line 2277 "char_ref.rl" {te = p+1;{ output->first = 0x222d; {p++; goto _out; } }} break; case 1950: #line 2278 "char_ref.rl" {te = p+1;{ output->first = 0x2928; {p++; goto _out; } }} break; case 1951: #line 2279 "char_ref.rl" {te = p+1;{ output->first = 0x22a4; {p++; goto _out; } }} break; case 1952: #line 2280 "char_ref.rl" {te = p+1;{ output->first = 0x2336; {p++; goto _out; } }} break; case 1953: #line 2281 "char_ref.rl" {te = p+1;{ output->first = 0x2af1; {p++; goto _out; } }} break; case 1954: #line 2282 "char_ref.rl" {te = p+1;{ output->first = 0x0001d565; {p++; goto _out; } }} break; case 1955: #line 2283 "char_ref.rl" {te = p+1;{ output->first = 0x2ada; {p++; goto _out; } }} break; case 1956: #line 2284 "char_ref.rl" {te = p+1;{ output->first = 0x2929; {p++; goto _out; } }} break; case 1957: #line 2285 "char_ref.rl" {te = p+1;{ output->first = 0x2034; {p++; goto _out; } }} break; case 1958: #line 2286 "char_ref.rl" {te = p+1;{ output->first = 0x2122; {p++; goto _out; } }} break; case 1959: #line 2287 "char_ref.rl" {te = p+1;{ output->first = 0x25b5; {p++; goto _out; } }} break; case 1960: #line 2288 "char_ref.rl" {te = p+1;{ output->first = 0x25bf; {p++; goto _out; } }} break; case 1961: #line 2289 "char_ref.rl" {te = p+1;{ output->first = 0x25c3; {p++; goto _out; } }} break; case 1962: #line 2290 "char_ref.rl" {te = p+1;{ output->first = 0x22b4; {p++; goto _out; } }} break; case 1963: #line 2291 "char_ref.rl" {te = p+1;{ output->first = 0x225c; {p++; goto _out; } }} break; case 1964: #line 2292 "char_ref.rl" {te = p+1;{ output->first = 0x25b9; {p++; goto _out; } }} break; case 1965: #line 2293 "char_ref.rl" {te = p+1;{ output->first = 0x22b5; {p++; goto _out; } }} break; case 1966: #line 2294 "char_ref.rl" {te = p+1;{ output->first = 0x25ec; {p++; goto _out; } }} break; case 1967: #line 2295 "char_ref.rl" {te = p+1;{ output->first = 0x225c; {p++; goto _out; } }} break; case 1968: #line 2296 "char_ref.rl" {te = p+1;{ output->first = 0x2a3a; {p++; goto _out; } }} break; case 1969: #line 2297 "char_ref.rl" {te = p+1;{ output->first = 0x2a39; {p++; goto _out; } }} break; case 1970: #line 2298 "char_ref.rl" {te = p+1;{ output->first = 0x29cd; {p++; goto _out; } }} break; case 1971: #line 2299 "char_ref.rl" {te = p+1;{ output->first = 0x2a3b; {p++; goto _out; } }} break; case 1972: #line 2300 "char_ref.rl" {te = p+1;{ output->first = 0x23e2; {p++; goto _out; } }} break; case 1973: #line 2301 "char_ref.rl" {te = p+1;{ output->first = 0x0001d4c9; {p++; goto _out; } }} break; case 1974: #line 2302 "char_ref.rl" {te = p+1;{ output->first = 0x0446; {p++; goto _out; } }} break; case 1975: #line 2303 "char_ref.rl" {te = p+1;{ output->first = 0x045b; {p++; goto _out; } }} break; case 1976: #line 2304 "char_ref.rl" {te = p+1;{ output->first = 0x0167; {p++; goto _out; } }} break; case 1977: #line 2305 "char_ref.rl" {te = p+1;{ output->first = 0x226c; {p++; goto _out; } }} break; case 1978: #line 2306 "char_ref.rl" {te = p+1;{ output->first = 0x219e; {p++; goto _out; } }} break; case 1979: #line 2307 "char_ref.rl" {te = p+1;{ output->first = 0x21a0; {p++; goto _out; } }} break; case 1980: #line 2308 "char_ref.rl" {te = p+1;{ output->first = 0x21d1; {p++; goto _out; } }} break; case 1981: #line 2309 "char_ref.rl" {te = p+1;{ output->first = 0x2963; {p++; goto _out; } }} break; case 1982: #line 2310 "char_ref.rl" {te = p+1;{ output->first = 0xfa; {p++; goto _out; } }} break; case 1983: #line 2312 "char_ref.rl" {te = p+1;{ output->first = 0x2191; {p++; goto _out; } }} break; case 1984: #line 2313 "char_ref.rl" {te = p+1;{ output->first = 0x045e; {p++; goto _out; } }} break; case 1985: #line 2314 "char_ref.rl" {te = p+1;{ output->first = 0x016d; {p++; goto _out; } }} break; case 1986: #line 2315 "char_ref.rl" {te = p+1;{ output->first = 0xfb; {p++; goto _out; } }} break; case 1987: #line 2317 "char_ref.rl" {te = p+1;{ output->first = 0x0443; {p++; goto _out; } }} break; case 1988: #line 2318 "char_ref.rl" {te = p+1;{ output->first = 0x21c5; {p++; goto _out; } }} break; case 1989: #line 2319 "char_ref.rl" {te = p+1;{ output->first = 0x0171; {p++; goto _out; } }} break; case 1990: #line 2320 "char_ref.rl" {te = p+1;{ output->first = 0x296e; {p++; goto _out; } }} break; case 1991: #line 2321 "char_ref.rl" {te = p+1;{ output->first = 0x297e; {p++; goto _out; } }} break; case 1992: #line 2322 "char_ref.rl" {te = p+1;{ output->first = 0x0001d532; {p++; goto _out; } }} break; case 1993: #line 2323 "char_ref.rl" {te = p+1;{ output->first = 0xf9; {p++; goto _out; } }} break; case 1994: #line 2325 "char_ref.rl" {te = p+1;{ output->first = 0x21bf; {p++; goto _out; } }} break; case 1995: #line 2326 "char_ref.rl" {te = p+1;{ output->first = 0x21be; {p++; goto _out; } }} break; case 1996: #line 2327 "char_ref.rl" {te = p+1;{ output->first = 0x2580; {p++; goto _out; } }} break; case 1997: #line 2328 "char_ref.rl" {te = p+1;{ output->first = 0x231c; {p++; goto _out; } }} break; case 1998: #line 2329 "char_ref.rl" {te = p+1;{ output->first = 0x231c; {p++; goto _out; } }} break; case 1999: #line 2330 "char_ref.rl" {te = p+1;{ output->first = 0x230f; {p++; goto _out; } }} break; case 2000: #line 2331 "char_ref.rl" {te = p+1;{ output->first = 0x25f8; {p++; goto _out; } }} break; case 2001: #line 2332 "char_ref.rl" {te = p+1;{ output->first = 0x016b; {p++; goto _out; } }} break; case 2002: #line 2333 "char_ref.rl" {te = p+1;{ output->first = 0xa8; {p++; goto _out; } }} break; case 2003: #line 2335 "char_ref.rl" {te = p+1;{ output->first = 0x0173; {p++; goto _out; } }} break; case 2004: #line 2336 "char_ref.rl" {te = p+1;{ output->first = 0x0001d566; {p++; goto _out; } }} break; case 2005: #line 2337 "char_ref.rl" {te = p+1;{ output->first = 0x2191; {p++; goto _out; } }} break; case 2006: #line 2338 "char_ref.rl" {te = p+1;{ output->first = 0x2195; {p++; goto _out; } }} break; case 2007: #line 2339 "char_ref.rl" {te = p+1;{ output->first = 0x21bf; {p++; goto _out; } }} break; case 2008: #line 2340 "char_ref.rl" {te = p+1;{ output->first = 0x21be; {p++; goto _out; } }} break; case 2009: #line 2341 "char_ref.rl" {te = p+1;{ output->first = 0x228e; {p++; goto _out; } }} break; case 2010: #line 2342 "char_ref.rl" {te = p+1;{ output->first = 0x03c5; {p++; goto _out; } }} break; case 2011: #line 2343 "char_ref.rl" {te = p+1;{ output->first = 0x03d2; {p++; goto _out; } }} break; case 2012: #line 2344 "char_ref.rl" {te = p+1;{ output->first = 0x03c5; {p++; goto _out; } }} break; case 2013: #line 2345 "char_ref.rl" {te = p+1;{ output->first = 0x21c8; {p++; goto _out; } }} break; case 2014: #line 2346 "char_ref.rl" {te = p+1;{ output->first = 0x231d; {p++; goto _out; } }} break; case 2015: #line 2347 "char_ref.rl" {te = p+1;{ output->first = 0x231d; {p++; goto _out; } }} break; case 2016: #line 2348 "char_ref.rl" {te = p+1;{ output->first = 0x230e; {p++; goto _out; } }} break; case 2017: #line 2349 "char_ref.rl" {te = p+1;{ output->first = 0x016f; {p++; goto _out; } }} break; case 2018: #line 2350 "char_ref.rl" {te = p+1;{ output->first = 0x25f9; {p++; goto _out; } }} break; case 2019: #line 2351 "char_ref.rl" {te = p+1;{ output->first = 0x0001d4ca; {p++; goto _out; } }} break; case 2020: #line 2352 "char_ref.rl" {te = p+1;{ output->first = 0x22f0; {p++; goto _out; } }} break; case 2021: #line 2353 "char_ref.rl" {te = p+1;{ output->first = 0x0169; {p++; goto _out; } }} break; case 2022: #line 2354 "char_ref.rl" {te = p+1;{ output->first = 0x25b5; {p++; goto _out; } }} break; case 2023: #line 2355 "char_ref.rl" {te = p+1;{ output->first = 0x25b4; {p++; goto _out; } }} break; case 2024: #line 2356 "char_ref.rl" {te = p+1;{ output->first = 0x21c8; {p++; goto _out; } }} break; case 2025: #line 2357 "char_ref.rl" {te = p+1;{ output->first = 0xfc; {p++; goto _out; } }} break; case 2026: #line 2359 "char_ref.rl" {te = p+1;{ output->first = 0x29a7; {p++; goto _out; } }} break; case 2027: #line 2360 "char_ref.rl" {te = p+1;{ output->first = 0x21d5; {p++; goto _out; } }} break; case 2028: #line 2361 "char_ref.rl" {te = p+1;{ output->first = 0x2ae8; {p++; goto _out; } }} break; case 2029: #line 2362 "char_ref.rl" {te = p+1;{ output->first = 0x2ae9; {p++; goto _out; } }} break; case 2030: #line 2363 "char_ref.rl" {te = p+1;{ output->first = 0x22a8; {p++; goto _out; } }} break; case 2031: #line 2364 "char_ref.rl" {te = p+1;{ output->first = 0x299c; {p++; goto _out; } }} break; case 2032: #line 2365 "char_ref.rl" {te = p+1;{ output->first = 0x03f5; {p++; goto _out; } }} break; case 2033: #line 2366 "char_ref.rl" {te = p+1;{ output->first = 0x03f0; {p++; goto _out; } }} break; case 2034: #line 2367 "char_ref.rl" {te = p+1;{ output->first = 0x2205; {p++; goto _out; } }} break; case 2035: #line 2368 "char_ref.rl" {te = p+1;{ output->first = 0x03d5; {p++; goto _out; } }} break; case 2036: #line 2369 "char_ref.rl" {te = p+1;{ output->first = 0x03d6; {p++; goto _out; } }} break; case 2037: #line 2370 "char_ref.rl" {te = p+1;{ output->first = 0x221d; {p++; goto _out; } }} break; case 2038: #line 2371 "char_ref.rl" {te = p+1;{ output->first = 0x2195; {p++; goto _out; } }} break; case 2039: #line 2372 "char_ref.rl" {te = p+1;{ output->first = 0x03f1; {p++; goto _out; } }} break; case 2040: #line 2373 "char_ref.rl" {te = p+1;{ output->first = 0x03c2; {p++; goto _out; } }} break; case 2041: #line 2374 "char_ref.rl" {te = p+1;{ output->first = 0x228a; output->second = 0xfe00; {p++; goto _out; } }} break; case 2042: #line 2375 "char_ref.rl" {te = p+1;{ output->first = 0x2acb; output->second = 0xfe00; {p++; goto _out; } }} break; case 2043: #line 2376 "char_ref.rl" {te = p+1;{ output->first = 0x228b; output->second = 0xfe00; {p++; goto _out; } }} break; case 2044: #line 2377 "char_ref.rl" {te = p+1;{ output->first = 0x2acc; output->second = 0xfe00; {p++; goto _out; } }} break; case 2045: #line 2378 "char_ref.rl" {te = p+1;{ output->first = 0x03d1; {p++; goto _out; } }} break; case 2046: #line 2379 "char_ref.rl" {te = p+1;{ output->first = 0x22b2; {p++; goto _out; } }} break; case 2047: #line 2380 "char_ref.rl" {te = p+1;{ output->first = 0x22b3; {p++; goto _out; } }} break; case 2048: #line 2381 "char_ref.rl" {te = p+1;{ output->first = 0x0432; {p++; goto _out; } }} break; case 2049: #line 2382 "char_ref.rl" {te = p+1;{ output->first = 0x22a2; {p++; goto _out; } }} break; case 2050: #line 2383 "char_ref.rl" {te = p+1;{ output->first = 0x2228; {p++; goto _out; } }} break; case 2051: #line 2384 "char_ref.rl" {te = p+1;{ output->first = 0x22bb; {p++; goto _out; } }} break; case 2052: #line 2385 "char_ref.rl" {te = p+1;{ output->first = 0x225a; {p++; goto _out; } }} break; case 2053: #line 2386 "char_ref.rl" {te = p+1;{ output->first = 0x22ee; {p++; goto _out; } }} break; case 2054: #line 2387 "char_ref.rl" {te = p+1;{ output->first = 0x7c; {p++; goto _out; } }} break; case 2055: #line 2388 "char_ref.rl" {te = p+1;{ output->first = 0x7c; {p++; goto _out; } }} break; case 2056: #line 2389 "char_ref.rl" {te = p+1;{ output->first = 0x0001d533; {p++; goto _out; } }} break; case 2057: #line 2390 "char_ref.rl" {te = p+1;{ output->first = 0x22b2; {p++; goto _out; } }} break; case 2058: #line 2391 "char_ref.rl" {te = p+1;{ output->first = 0x2282; output->second = 0x20d2; {p++; goto _out; } }} break; case 2059: #line 2392 "char_ref.rl" {te = p+1;{ output->first = 0x2283; output->second = 0x20d2; {p++; goto _out; } }} break; case 2060: #line 2393 "char_ref.rl" {te = p+1;{ output->first = 0x0001d567; {p++; goto _out; } }} break; case 2061: #line 2394 "char_ref.rl" {te = p+1;{ output->first = 0x221d; {p++; goto _out; } }} break; case 2062: #line 2395 "char_ref.rl" {te = p+1;{ output->first = 0x22b3; {p++; goto _out; } }} break; case 2063: #line 2396 "char_ref.rl" {te = p+1;{ output->first = 0x0001d4cb; {p++; goto _out; } }} break; case 2064: #line 2397 "char_ref.rl" {te = p+1;{ output->first = 0x2acb; output->second = 0xfe00; {p++; goto _out; } }} break; case 2065: #line 2398 "char_ref.rl" {te = p+1;{ output->first = 0x228a; output->second = 0xfe00; {p++; goto _out; } }} break; case 2066: #line 2399 "char_ref.rl" {te = p+1;{ output->first = 0x2acc; output->second = 0xfe00; {p++; goto _out; } }} break; case 2067: #line 2400 "char_ref.rl" {te = p+1;{ output->first = 0x228b; output->second = 0xfe00; {p++; goto _out; } }} break; case 2068: #line 2401 "char_ref.rl" {te = p+1;{ output->first = 0x299a; {p++; goto _out; } }} break; case 2069: #line 2402 "char_ref.rl" {te = p+1;{ output->first = 0x0175; {p++; goto _out; } }} break; case 2070: #line 2403 "char_ref.rl" {te = p+1;{ output->first = 0x2a5f; {p++; goto _out; } }} break; case 2071: #line 2404 "char_ref.rl" {te = p+1;{ output->first = 0x2227; {p++; goto _out; } }} break; case 2072: #line 2405 "char_ref.rl" {te = p+1;{ output->first = 0x2259; {p++; goto _out; } }} break; case 2073: #line 2406 "char_ref.rl" {te = p+1;{ output->first = 0x2118; {p++; goto _out; } }} break; case 2074: #line 2407 "char_ref.rl" {te = p+1;{ output->first = 0x0001d534; {p++; goto _out; } }} break; case 2075: #line 2408 "char_ref.rl" {te = p+1;{ output->first = 0x0001d568; {p++; goto _out; } }} break; case 2076: #line 2409 "char_ref.rl" {te = p+1;{ output->first = 0x2118; {p++; goto _out; } }} break; case 2077: #line 2410 "char_ref.rl" {te = p+1;{ output->first = 0x2240; {p++; goto _out; } }} break; case 2078: #line 2411 "char_ref.rl" {te = p+1;{ output->first = 0x2240; {p++; goto _out; } }} break; case 2079: #line 2412 "char_ref.rl" {te = p+1;{ output->first = 0x0001d4cc; {p++; goto _out; } }} break; case 2080: #line 2413 "char_ref.rl" {te = p+1;{ output->first = 0x22c2; {p++; goto _out; } }} break; case 2081: #line 2414 "char_ref.rl" {te = p+1;{ output->first = 0x25ef; {p++; goto _out; } }} break; case 2082: #line 2415 "char_ref.rl" {te = p+1;{ output->first = 0x22c3; {p++; goto _out; } }} break; case 2083: #line 2416 "char_ref.rl" {te = p+1;{ output->first = 0x25bd; {p++; goto _out; } }} break; case 2084: #line 2417 "char_ref.rl" {te = p+1;{ output->first = 0x0001d535; {p++; goto _out; } }} break; case 2085: #line 2418 "char_ref.rl" {te = p+1;{ output->first = 0x27fa; {p++; goto _out; } }} break; case 2086: #line 2419 "char_ref.rl" {te = p+1;{ output->first = 0x27f7; {p++; goto _out; } }} break; case 2087: #line 2420 "char_ref.rl" {te = p+1;{ output->first = 0x03be; {p++; goto _out; } }} break; case 2088: #line 2421 "char_ref.rl" {te = p+1;{ output->first = 0x27f8; {p++; goto _out; } }} break; case 2089: #line 2422 "char_ref.rl" {te = p+1;{ output->first = 0x27f5; {p++; goto _out; } }} break; case 2090: #line 2423 "char_ref.rl" {te = p+1;{ output->first = 0x27fc; {p++; goto _out; } }} break; case 2091: #line 2424 "char_ref.rl" {te = p+1;{ output->first = 0x22fb; {p++; goto _out; } }} break; case 2092: #line 2425 "char_ref.rl" {te = p+1;{ output->first = 0x2a00; {p++; goto _out; } }} break; case 2093: #line 2426 "char_ref.rl" {te = p+1;{ output->first = 0x0001d569; {p++; goto _out; } }} break; case 2094: #line 2427 "char_ref.rl" {te = p+1;{ output->first = 0x2a01; {p++; goto _out; } }} break; case 2095: #line 2428 "char_ref.rl" {te = p+1;{ output->first = 0x2a02; {p++; goto _out; } }} break; case 2096: #line 2429 "char_ref.rl" {te = p+1;{ output->first = 0x27f9; {p++; goto _out; } }} break; case 2097: #line 2430 "char_ref.rl" {te = p+1;{ output->first = 0x27f6; {p++; goto _out; } }} break; case 2098: #line 2431 "char_ref.rl" {te = p+1;{ output->first = 0x0001d4cd; {p++; goto _out; } }} break; case 2099: #line 2432 "char_ref.rl" {te = p+1;{ output->first = 0x2a06; {p++; goto _out; } }} break; case 2100: #line 2433 "char_ref.rl" {te = p+1;{ output->first = 0x2a04; {p++; goto _out; } }} break; case 2101: #line 2434 "char_ref.rl" {te = p+1;{ output->first = 0x25b3; {p++; goto _out; } }} break; case 2102: #line 2435 "char_ref.rl" {te = p+1;{ output->first = 0x22c1; {p++; goto _out; } }} break; case 2103: #line 2436 "char_ref.rl" {te = p+1;{ output->first = 0x22c0; {p++; goto _out; } }} break; case 2104: #line 2437 "char_ref.rl" {te = p+1;{ output->first = 0xfd; {p++; goto _out; } }} break; case 2105: #line 2439 "char_ref.rl" {te = p+1;{ output->first = 0x044f; {p++; goto _out; } }} break; case 2106: #line 2440 "char_ref.rl" {te = p+1;{ output->first = 0x0177; {p++; goto _out; } }} break; case 2107: #line 2441 "char_ref.rl" {te = p+1;{ output->first = 0x044b; {p++; goto _out; } }} break; case 2108: #line 2442 "char_ref.rl" {te = p+1;{ output->first = 0xa5; {p++; goto _out; } }} break; case 2109: #line 2444 "char_ref.rl" {te = p+1;{ output->first = 0x0001d536; {p++; goto _out; } }} break; case 2110: #line 2445 "char_ref.rl" {te = p+1;{ output->first = 0x0457; {p++; goto _out; } }} break; case 2111: #line 2446 "char_ref.rl" {te = p+1;{ output->first = 0x0001d56a; {p++; goto _out; } }} break; case 2112: #line 2447 "char_ref.rl" {te = p+1;{ output->first = 0x0001d4ce; {p++; goto _out; } }} break; case 2113: #line 2448 "char_ref.rl" {te = p+1;{ output->first = 0x044e; {p++; goto _out; } }} break; case 2114: #line 2449 "char_ref.rl" {te = p+1;{ output->first = 0xff; {p++; goto _out; } }} break; case 2115: #line 2451 "char_ref.rl" {te = p+1;{ output->first = 0x017a; {p++; goto _out; } }} break; case 2116: #line 2452 "char_ref.rl" {te = p+1;{ output->first = 0x017e; {p++; goto _out; } }} break; case 2117: #line 2453 "char_ref.rl" {te = p+1;{ output->first = 0x0437; {p++; goto _out; } }} break; case 2118: #line 2454 "char_ref.rl" {te = p+1;{ output->first = 0x017c; {p++; goto _out; } }} break; case 2119: #line 2455 "char_ref.rl" {te = p+1;{ output->first = 0x2128; {p++; goto _out; } }} break; case 2120: #line 2456 "char_ref.rl" {te = p+1;{ output->first = 0x03b6; {p++; goto _out; } }} break; case 2121: #line 2457 "char_ref.rl" {te = p+1;{ output->first = 0x0001d537; {p++; goto _out; } }} break; case 2122: #line 2458 "char_ref.rl" {te = p+1;{ output->first = 0x0436; {p++; goto _out; } }} break; case 2123: #line 2459 "char_ref.rl" {te = p+1;{ output->first = 0x21dd; {p++; goto _out; } }} break; case 2124: #line 2460 "char_ref.rl" {te = p+1;{ output->first = 0x0001d56b; {p++; goto _out; } }} break; case 2125: #line 2461 "char_ref.rl" {te = p+1;{ output->first = 0x0001d4cf; {p++; goto _out; } }} break; case 2126: #line 2462 "char_ref.rl" {te = p+1;{ output->first = 0x200d; {p++; goto _out; } }} break; case 2127: #line 2463 "char_ref.rl" {te = p+1;{ output->first = 0x200c; {p++; goto _out; } }} break; case 2128: #line 234 "char_ref.rl" {te = p;p--;{ output->first = 0xc6; {p++; goto _out; } }} break; case 2129: #line 236 "char_ref.rl" {te = p;p--;{ output->first = 0x26; {p++; goto _out; } }} break; case 2130: #line 238 "char_ref.rl" {te = p;p--;{ output->first = 0xc1; {p++; goto _out; } }} break; case 2131: #line 241 "char_ref.rl" {te = p;p--;{ output->first = 0xc2; {p++; goto _out; } }} break; case 2132: #line 245 "char_ref.rl" {te = p;p--;{ output->first = 0xc0; {p++; goto _out; } }} break; case 2133: #line 253 "char_ref.rl" {te = p;p--;{ output->first = 0xc5; {p++; goto _out; } }} break; case 2134: #line 257 "char_ref.rl" {te = p;p--;{ output->first = 0xc3; {p++; goto _out; } }} break; case 2135: #line 259 "char_ref.rl" {te = p;p--;{ output->first = 0xc4; {p++; goto _out; } }} break; case 2136: #line 274 "char_ref.rl" {te = p;p--;{ output->first = 0xa9; {p++; goto _out; } }} break; case 2137: #line 281 "char_ref.rl" {te = p;p--;{ output->first = 0xc7; {p++; goto _out; } }} break; case 2138: #line 364 "char_ref.rl" {te = p;p--;{ output->first = 0xd0; {p++; goto _out; } }} break; case 2139: #line 366 "char_ref.rl" {te = p;p--;{ output->first = 0xc9; {p++; goto _out; } }} break; case 2140: #line 369 "char_ref.rl" {te = p;p--;{ output->first = 0xca; {p++; goto _out; } }} break; case 2141: #line 374 "char_ref.rl" {te = p;p--;{ output->first = 0xc8; {p++; goto _out; } }} break; case 2142: #line 389 "char_ref.rl" {te = p;p--;{ output->first = 0xcb; {p++; goto _out; } }} break; case 2143: #line 402 "char_ref.rl" {te = p;p--;{ output->first = 0x3e; {p++; goto _out; } }} break; case 2144: #line 438 "char_ref.rl" {te = p;p--;{ output->first = 0xcd; {p++; goto _out; } }} break; case 2145: #line 440 "char_ref.rl" {te = p;p--;{ output->first = 0xce; {p++; goto _out; } }} break; case 2146: #line 445 "char_ref.rl" {te = p;p--;{ output->first = 0xcc; {p++; goto _out; } }} break; case 2147: #line 462 "char_ref.rl" {te = p;p--;{ output->first = 0xcf; {p++; goto _out; } }} break; case 2148: #line 480 "char_ref.rl" {te = p;p--;{ output->first = 0x3c; {p++; goto _out; } }} break; case 2149: #line 617 "char_ref.rl" {te = p;p--;{ output->first = 0xd1; {p++; goto _out; } }} break; case 2150: #line 621 "char_ref.rl" {te = p;p--;{ output->first = 0xd3; {p++; goto _out; } }} break; case 2151: #line 623 "char_ref.rl" {te = p;p--;{ output->first = 0xd4; {p++; goto _out; } }} break; case 2152: #line 628 "char_ref.rl" {te = p;p--;{ output->first = 0xd2; {p++; goto _out; } }} break; case 2153: #line 638 "char_ref.rl" {te = p;p--;{ output->first = 0xd8; {p++; goto _out; } }} break; case 2154: #line 640 "char_ref.rl" {te = p;p--;{ output->first = 0xd5; {p++; goto _out; } }} break; case 2155: #line 643 "char_ref.rl" {te = p;p--;{ output->first = 0xd6; {p++; goto _out; } }} break; case 2156: #line 668 "char_ref.rl" {te = p;p--;{ output->first = 0x22; {p++; goto _out; } }} break; case 2157: #line 674 "char_ref.rl" {te = p;p--;{ output->first = 0xae; {p++; goto _out; } }} break; case 2158: #line 758 "char_ref.rl" {te = p;p--;{ output->first = 0xde; {p++; goto _out; } }} break; case 2159: #line 781 "char_ref.rl" {te = p;p--;{ output->first = 0xda; {p++; goto _out; } }} break; case 2160: #line 787 "char_ref.rl" {te = p;p--;{ output->first = 0xdb; {p++; goto _out; } }} break; case 2161: #line 792 "char_ref.rl" {te = p;p--;{ output->first = 0xd9; {p++; goto _out; } }} break; case 2162: #line 819 "char_ref.rl" {te = p;p--;{ output->first = 0xdc; {p++; goto _out; } }} break; case 2163: #line 850 "char_ref.rl" {te = p;p--;{ output->first = 0xdd; {p++; goto _out; } }} break; case 2164: #line 868 "char_ref.rl" {te = p;p--;{ output->first = 0xe1; {p++; goto _out; } }} break; case 2165: #line 874 "char_ref.rl" {te = p;p--;{ output->first = 0xe2; {p++; goto _out; } }} break; case 2166: #line 876 "char_ref.rl" {te = p;p--;{ output->first = 0xb4; {p++; goto _out; } }} break; case 2167: #line 879 "char_ref.rl" {te = p;p--;{ output->first = 0xe6; {p++; goto _out; } }} break; case 2168: #line 883 "char_ref.rl" {te = p;p--;{ output->first = 0xe0; {p++; goto _out; } }} break; case 2169: #line 890 "char_ref.rl" {te = p;p--;{ output->first = 0x26; {p++; goto _out; } }} break; case 2170: #line 925 "char_ref.rl" {te = p;p--;{ output->first = 0xe5; {p++; goto _out; } }} break; case 2171: #line 931 "char_ref.rl" {te = p;p--;{ output->first = 0xe3; {p++; goto _out; } }} break; case 2172: #line 933 "char_ref.rl" {te = p;p--;{ output->first = 0xe4; {p++; goto _out; } }} break; case 2173: #line 1038 "char_ref.rl" {te = p;p--;{ output->first = 0xa6; {p++; goto _out; } }} break; case 2174: #line 1065 "char_ref.rl" {te = p;p--;{ output->first = 0xe7; {p++; goto _out; } }} break; case 2175: #line 1071 "char_ref.rl" {te = p;p--;{ output->first = 0xb8; {p++; goto _out; } }} break; case 2176: #line 1074 "char_ref.rl" {te = p;p--;{ output->first = 0xa2; {p++; goto _out; } }} break; case 2177: #line 1113 "char_ref.rl" {te = p;p--;{ output->first = 0xa9; {p++; goto _out; } }} break; case 2178: #line 1143 "char_ref.rl" {te = p;p--;{ output->first = 0xa4; {p++; goto _out; } }} break; case 2179: #line 1167 "char_ref.rl" {te = p;p--;{ output->first = 0xb0; {p++; goto _out; } }} break; case 2180: #line 1183 "char_ref.rl" {te = p;p--;{ output->first = 0xf7; {p++; goto _out; } }} break; case 2181: #line 1220 "char_ref.rl" {te = p;p--;{ output->first = 0xe9; {p++; goto _out; } }} break; case 2182: #line 1225 "char_ref.rl" {te = p;p--;{ output->first = 0xea; {p++; goto _out; } }} break; case 2183: #line 1234 "char_ref.rl" {te = p;p--;{ output->first = 0xe8; {p++; goto _out; } }} break; case 2184: #line 1276 "char_ref.rl" {te = p;p--;{ output->first = 0xf0; {p++; goto _out; } }} break; case 2185: #line 1278 "char_ref.rl" {te = p;p--;{ output->first = 0xeb; {p++; goto _out; } }} break; case 2186: #line 1303 "char_ref.rl" {te = p;p--;{ output->first = 0xbd; {p++; goto _out; } }} break; case 2187: #line 1306 "char_ref.rl" {te = p;p--;{ output->first = 0xbc; {p++; goto _out; } }} break; case 2188: #line 1313 "char_ref.rl" {te = p;p--;{ output->first = 0xbe; {p++; goto _out; } }} break; case 2189: #line 1368 "char_ref.rl" {te = p;p--;{ output->first = 0x3e; {p++; goto _out; } }} break; case 2190: #line 1412 "char_ref.rl" {te = p;p--;{ output->first = 0xed; {p++; goto _out; } }} break; case 2191: #line 1415 "char_ref.rl" {te = p;p--;{ output->first = 0xee; {p++; goto _out; } }} break; case 2192: #line 1419 "char_ref.rl" {te = p;p--;{ output->first = 0xa1; {p++; goto _out; } }} break; case 2193: #line 1423 "char_ref.rl" {te = p;p--;{ output->first = 0xec; {p++; goto _out; } }} break; case 2194: #line 1454 "char_ref.rl" {te = p;p--;{ output->first = 0xbf; {p++; goto _out; } }} break; case 2195: #line 1466 "char_ref.rl" {te = p;p--;{ output->first = 0xef; {p++; goto _out; } }} break; case 2196: #line 1501 "char_ref.rl" {te = p;p--;{ output->first = 0xab; {p++; goto _out; } }} break; case 2197: #line 1623 "char_ref.rl" {te = p;p--;{ output->first = 0x3c; {p++; goto _out; } }} break; case 2198: #line 1641 "char_ref.rl" {te = p;p--;{ output->first = 0xaf; {p++; goto _out; } }} break; case 2199: #line 1658 "char_ref.rl" {te = p;p--;{ output->first = 0xb5; {p++; goto _out; } }} break; case 2200: #line 1663 "char_ref.rl" {te = p;p--;{ output->first = 0xb7; {p++; goto _out; } }} break; case 2201: #line 1702 "char_ref.rl" {te = p;p--;{ output->first = 0xa0; {p++; goto _out; } }} break; case 2202: #line 1771 "char_ref.rl" {te = p;p--;{ output->first = 0xac; {p++; goto _out; } }} break; case 2203: #line 1818 "char_ref.rl" {te = p;p--;{ output->first = 0xf1; {p++; goto _out; } }} break; case 2204: #line 1849 "char_ref.rl" {te = p;p--;{ output->first = 0xf3; {p++; goto _out; } }} break; case 2205: #line 1853 "char_ref.rl" {te = p;p--;{ output->first = 0xf4; {p++; goto _out; } }} break; case 2206: #line 1865 "char_ref.rl" {te = p;p--;{ output->first = 0xf2; {p++; goto _out; } }} break; case 2207: #line 1890 "char_ref.rl" {te = p;p--;{ output->first = 0xaa; {p++; goto _out; } }} break; case 2208: #line 1892 "char_ref.rl" {te = p;p--;{ output->first = 0xba; {p++; goto _out; } }} break; case 2209: #line 1899 "char_ref.rl" {te = p;p--;{ output->first = 0xf8; {p++; goto _out; } }} break; case 2210: #line 1902 "char_ref.rl" {te = p;p--;{ output->first = 0xf5; {p++; goto _out; } }} break; case 2211: #line 1906 "char_ref.rl" {te = p;p--;{ output->first = 0xf6; {p++; goto _out; } }} break; case 2212: #line 1910 "char_ref.rl" {te = p;p--;{ output->first = 0xb6; {p++; goto _out; } }} break; case 2213: #line 1940 "char_ref.rl" {te = p;p--;{ output->first = 0xb1; {p++; goto _out; } }} break; case 2214: #line 1947 "char_ref.rl" {te = p;p--;{ output->first = 0xa3; {p++; goto _out; } }} break; case 2215: #line 1987 "char_ref.rl" {te = p;p--;{ output->first = 0x22; {p++; goto _out; } }} break; case 2216: #line 2002 "char_ref.rl" {te = p;p--;{ output->first = 0xbb; {p++; goto _out; } }} break; case 2217: #line 2041 "char_ref.rl" {te = p;p--;{ output->first = 0xae; {p++; goto _out; } }} break; case 2218: #line 2116 "char_ref.rl" {te = p;p--;{ output->first = 0xa7; {p++; goto _out; } }} break; case 2219: #line 2130 "char_ref.rl" {te = p;p--;{ output->first = 0xad; {p++; goto _out; } }} break; case 2220: #line 2217 "char_ref.rl" {te = p;p--;{ output->first = 0xb9; {p++; goto _out; } }} break; case 2221: #line 2219 "char_ref.rl" {te = p;p--;{ output->first = 0xb2; {p++; goto _out; } }} break; case 2222: #line 2221 "char_ref.rl" {te = p;p--;{ output->first = 0xb3; {p++; goto _out; } }} break; case 2223: #line 2249 "char_ref.rl" {te = p;p--;{ output->first = 0xdf; {p++; goto _out; } }} break; case 2224: #line 2270 "char_ref.rl" {te = p;p--;{ output->first = 0xfe; {p++; goto _out; } }} break; case 2225: #line 2273 "char_ref.rl" {te = p;p--;{ output->first = 0xd7; {p++; goto _out; } }} break; case 2226: #line 2311 "char_ref.rl" {te = p;p--;{ output->first = 0xfa; {p++; goto _out; } }} break; case 2227: #line 2316 "char_ref.rl" {te = p;p--;{ output->first = 0xfb; {p++; goto _out; } }} break; case 2228: #line 2324 "char_ref.rl" {te = p;p--;{ output->first = 0xf9; {p++; goto _out; } }} break; case 2229: #line 2334 "char_ref.rl" {te = p;p--;{ output->first = 0xa8; {p++; goto _out; } }} break; case 2230: #line 2358 "char_ref.rl" {te = p;p--;{ output->first = 0xfc; {p++; goto _out; } }} break; case 2231: #line 2438 "char_ref.rl" {te = p;p--;{ output->first = 0xfd; {p++; goto _out; } }} break; case 2232: #line 2443 "char_ref.rl" {te = p;p--;{ output->first = 0xa5; {p++; goto _out; } }} break; case 2233: #line 2450 "char_ref.rl" {te = p;p--;{ output->first = 0xff; {p++; goto _out; } }} break; case 2234: #line 1074 "char_ref.rl" {{p = ((te))-1;}{ output->first = 0xa2; {p++; goto _out; } }} break; case 2235: #line 1113 "char_ref.rl" {{p = ((te))-1;}{ output->first = 0xa9; {p++; goto _out; } }} break; case 2236: #line 1183 "char_ref.rl" {{p = ((te))-1;}{ output->first = 0xf7; {p++; goto _out; } }} break; case 2237: #line 1368 "char_ref.rl" {{p = ((te))-1;}{ output->first = 0x3e; {p++; goto _out; } }} break; case 2238: #line 1623 "char_ref.rl" {{p = ((te))-1;}{ output->first = 0x3c; {p++; goto _out; } }} break; case 2239: #line 1771 "char_ref.rl" {{p = ((te))-1;}{ output->first = 0xac; {p++; goto _out; } }} break; case 2240: #line 1910 "char_ref.rl" {{p = ((te))-1;}{ output->first = 0xb6; {p++; goto _out; } }} break; case 2241: #line 2273 "char_ref.rl" {{p = ((te))-1;}{ output->first = 0xd7; {p++; goto _out; } }} break; #line 23006 "char_ref.c" } } _again: _acts = _char_ref_actions + _char_ref_to_state_actions[cs]; _nacts = (unsigned int) *_acts++; while ( _nacts-- > 0 ) { switch ( *_acts++ ) { case 0: #line 1 "NONE" {ts = 0;} break; #line 23019 "char_ref.c" } } if ( cs == 0 ) goto _out; if ( ++p != pe ) goto _resume; _test_eof: {} if ( p == eof ) { if ( _char_ref_eof_trans[cs] > 0 ) { _trans = _char_ref_eof_trans[cs] - 1; goto _eof_trans; } } _out: {} } #line 2491 "char_ref.rl" // clang-format on if (cs >= 7623) { assert(output->first != kGumboNoChar); char last_char = *(te - 1); int len = te - start; if (last_char == ';') { bool matched = utf8iterator_maybe_consume_match(input, start, len, true); assert(matched); return true; } else if (is_in_attribute && (*te == '=' || isalnum(*te))) { output->first = kGumboNoChar; output->second = kGumboNoChar; utf8iterator_reset(input); return true; } else { GumboStringPiece bad_ref; bad_ref.length = te - start; bad_ref.data = start; add_named_reference_error( parser, input, GUMBO_ERR_NAMED_CHAR_REF_WITHOUT_SEMICOLON, bad_ref); bool matched = utf8iterator_maybe_consume_match(input, start, len, true); assert(matched); return false; } } else { output->first = kGumboNoChar; output->second = kGumboNoChar; bool status = maybe_add_invalid_named_reference(parser, input); utf8iterator_reset(input); return status; } } bool consume_char_ref(struct GumboInternalParser* parser, struct GumboInternalUtf8Iterator* input, int additional_allowed_char, bool is_in_attribute, OneOrTwoCodepoints* output) { utf8iterator_mark(input); utf8iterator_next(input); int c = utf8iterator_current(input); output->first = kGumboNoChar; output->second = kGumboNoChar; if (c == additional_allowed_char) { utf8iterator_reset(input); output->first = kGumboNoChar; return true; } switch (utf8iterator_current(input)) { case '\t': case '\n': case '\f': case ' ': case '<': case '&': case -1: utf8iterator_reset(input); return true; case '#': return consume_numeric_ref(parser, input, &output->first); default: return consume_named_ref(parser, input, is_in_attribute, output); } } ================================================ FILE: zhuishushenqi/Vendor/OCGumbo/gumbo/char_ref.h ================================================ // Copyright 2011 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // Author: jdtang@google.com (Jonathan Tang) // // Internal header for character reference handling; this should not be exposed // transitively by any public API header. This is why the functions aren't // namespaced. #ifndef GUMBO_CHAR_REF_H_ #define GUMBO_CHAR_REF_H_ #include #ifdef __cplusplus extern "C" { #endif struct GumboInternalParser; struct GumboInternalUtf8Iterator; // Value that indicates no character was produced. extern const int kGumboNoChar; // Certain named character references generate two codepoints, not one, and so // the consume_char_ref subroutine needs to return this instead of an int. The // first field will be kGumboNoChar if no character reference was found; the // second field will be kGumboNoChar if that is the case or if the character // reference returns only a single codepoint. typedef struct { int first; int second; } OneOrTwoCodepoints; // Implements the "consume a character reference" section of the spec. // This reads in characters from the input as necessary, and fills in a // OneOrTwoCodepoints struct containing the characters read. It may add parse // errors to the GumboParser's errors vector, if the spec calls for it. Pass a // space for the "additional allowed char" when the spec says "with no // additional allowed char". Returns false on parse error, true otherwise. bool consume_char_ref(struct GumboInternalParser* parser, struct GumboInternalUtf8Iterator* input, int additional_allowed_char, bool is_in_attribute, OneOrTwoCodepoints* output); #ifdef __cplusplus } #endif #endif // GUMBO_CHAR_REF_H_ ================================================ FILE: zhuishushenqi/Vendor/OCGumbo/gumbo/char_ref.rl ================================================ // Copyright 2011 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // Author: jdtang@google.com (Jonathan Tang) // // This is a Ragel state machine re-implementation of the original char_ref.c, // rewritten to improve efficiency. To generate the .c file from it, // // $ ragel -F0 char_ref.rl // // The generated source is also checked into source control so that most people // hacking on the parser do not need to install ragel. #include "char_ref.h" #include #include #include #include #include // Only for debug assertions at present. #include "error.h" #include "string_piece.h" #include "utf8.h" #include "util.h" struct GumboInternalParser; const int kGumboNoChar = -1; // Table of replacement characters. The spec specifies that any occurrence of // the first character should be replaced by the second character, and a parse // error recorded. typedef struct { int from_char; int to_char; } CharReplacement; static const CharReplacement kCharReplacements[] = { { 0x00, 0xfffd }, { 0x0d, 0x000d }, { 0x80, 0x20ac }, { 0x81, 0x0081 }, { 0x82, 0x201A }, { 0x83, 0x0192 }, { 0x84, 0x201E }, { 0x85, 0x2026 }, { 0x86, 0x2020 }, { 0x87, 0x2021 }, { 0x88, 0x02C6 }, { 0x89, 0x2030 }, { 0x8A, 0x0160 }, { 0x8B, 0x2039 }, { 0x8C, 0x0152 }, { 0x8D, 0x008D }, { 0x8E, 0x017D }, { 0x8F, 0x008F }, { 0x90, 0x0090 }, { 0x91, 0x2018 }, { 0x92, 0x2019 }, { 0x93, 0x201C }, { 0x94, 0x201D }, { 0x95, 0x2022 }, { 0x96, 0x2013 }, { 0x97, 0x2014 }, { 0x98, 0x02DC }, { 0x99, 0x2122 }, { 0x9A, 0x0161 }, { 0x9B, 0x203A }, { 0x9C, 0x0153 }, { 0x9D, 0x009D }, { 0x9E, 0x017E }, { 0x9F, 0x0178 }, // Terminator. { -1, -1 } }; static int parse_digit(int c, bool allow_hex) { if (c >= '0' && c <= '9') { return c - '0'; } if (allow_hex && c >= 'a' && c <= 'f') { return c - 'a' + 10; } if (allow_hex && c >= 'A' && c <= 'F') { return c - 'A' + 10; } return -1; } static void add_no_digit_error( struct GumboInternalParser* parser, Utf8Iterator* input) { GumboError* error = gumbo_add_error(parser); if (!error) { return; } utf8iterator_fill_error_at_mark(input, error); error->type = GUMBO_ERR_NUMERIC_CHAR_REF_NO_DIGITS; } static void add_codepoint_error( struct GumboInternalParser* parser, Utf8Iterator* input, GumboErrorType type, int codepoint) { GumboError* error = gumbo_add_error(parser); if (!error) { return; } utf8iterator_fill_error_at_mark(input, error); error->type = type; error->v.codepoint = codepoint; } static void add_named_reference_error( struct GumboInternalParser* parser, Utf8Iterator* input, GumboErrorType type, GumboStringPiece text) { GumboError* error = gumbo_add_error(parser); if (!error) { return; } utf8iterator_fill_error_at_mark(input, error); error->type = type; error->v.text = text; } static int maybe_replace_codepoint(int codepoint) { for (int i = 0; kCharReplacements[i].from_char != -1; ++i) { if (kCharReplacements[i].from_char == codepoint) { return kCharReplacements[i].to_char; } } return -1; } static bool consume_numeric_ref( struct GumboInternalParser* parser, Utf8Iterator* input, int* output) { utf8iterator_next(input); bool is_hex = false; int c = utf8iterator_current(input); if (c == 'x' || c == 'X') { is_hex = true; utf8iterator_next(input); c = utf8iterator_current(input); } int digit = parse_digit(c, is_hex); if (digit == -1) { // First digit was invalid; add a parse error and return. add_no_digit_error(parser, input); utf8iterator_reset(input); *output = kGumboNoChar; return false; } int codepoint = 0; bool status = true; do { codepoint = (codepoint * (is_hex ? 16 : 10)) + digit; utf8iterator_next(input); digit = parse_digit(utf8iterator_current(input), is_hex); } while (digit != -1); if (utf8iterator_current(input) != ';') { add_codepoint_error( parser, input, GUMBO_ERR_NUMERIC_CHAR_REF_WITHOUT_SEMICOLON, codepoint); status = false; } else { utf8iterator_next(input); } int replacement = maybe_replace_codepoint(codepoint); if (replacement != -1) { add_codepoint_error( parser, input, GUMBO_ERR_NUMERIC_CHAR_REF_INVALID, codepoint); *output = replacement; return false; } if ((codepoint >= 0xd800 && codepoint <= 0xdfff) || codepoint > 0x10ffff) { add_codepoint_error( parser, input, GUMBO_ERR_NUMERIC_CHAR_REF_INVALID, codepoint); *output = 0xfffd; return false; } if (utf8_is_invalid_code_point(codepoint) || codepoint == 0xb) { add_codepoint_error( parser, input, GUMBO_ERR_NUMERIC_CHAR_REF_INVALID, codepoint); status = false; // But return it anyway, per spec. } *output = codepoint; return status; } static bool maybe_add_invalid_named_reference( struct GumboInternalParser* parser, Utf8Iterator* input) { // The iterator will always be reset in this code path, so we don't need to // worry about consuming characters. const char* start = utf8iterator_get_char_pointer(input); int c = utf8iterator_current(input); while ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9')) { utf8iterator_next(input); c = utf8iterator_current(input); } if (c == ';') { GumboStringPiece bad_ref; bad_ref.data = start; bad_ref.length = utf8iterator_get_char_pointer(input) - start; add_named_reference_error( parser, input, GUMBO_ERR_NAMED_CHAR_REF_INVALID, bad_ref); return false; } return true; } %%{ machine char_ref; valid_named_ref := |* 'AElig;' => { output->first = 0xc6; fbreak; }; 'AElig' => { output->first = 0xc6; fbreak; }; 'AMP;' => { output->first = 0x26; fbreak; }; 'AMP' => { output->first = 0x26; fbreak; }; 'Aacute;' => { output->first = 0xc1; fbreak; }; 'Aacute' => { output->first = 0xc1; fbreak; }; 'Abreve;' => { output->first = 0x0102; fbreak; }; 'Acirc;' => { output->first = 0xc2; fbreak; }; 'Acirc' => { output->first = 0xc2; fbreak; }; 'Acy;' => { output->first = 0x0410; fbreak; }; 'Afr;' => { output->first = 0x0001d504; fbreak; }; 'Agrave;' => { output->first = 0xc0; fbreak; }; 'Agrave' => { output->first = 0xc0; fbreak; }; 'Alpha;' => { output->first = 0x0391; fbreak; }; 'Amacr;' => { output->first = 0x0100; fbreak; }; 'And;' => { output->first = 0x2a53; fbreak; }; 'Aogon;' => { output->first = 0x0104; fbreak; }; 'Aopf;' => { output->first = 0x0001d538; fbreak; }; 'ApplyFunction;' => { output->first = 0x2061; fbreak; }; 'Aring;' => { output->first = 0xc5; fbreak; }; 'Aring' => { output->first = 0xc5; fbreak; }; 'Ascr;' => { output->first = 0x0001d49c; fbreak; }; 'Assign;' => { output->first = 0x2254; fbreak; }; 'Atilde;' => { output->first = 0xc3; fbreak; }; 'Atilde' => { output->first = 0xc3; fbreak; }; 'Auml;' => { output->first = 0xc4; fbreak; }; 'Auml' => { output->first = 0xc4; fbreak; }; 'Backslash;' => { output->first = 0x2216; fbreak; }; 'Barv;' => { output->first = 0x2ae7; fbreak; }; 'Barwed;' => { output->first = 0x2306; fbreak; }; 'Bcy;' => { output->first = 0x0411; fbreak; }; 'Because;' => { output->first = 0x2235; fbreak; }; 'Bernoullis;' => { output->first = 0x212c; fbreak; }; 'Beta;' => { output->first = 0x0392; fbreak; }; 'Bfr;' => { output->first = 0x0001d505; fbreak; }; 'Bopf;' => { output->first = 0x0001d539; fbreak; }; 'Breve;' => { output->first = 0x02d8; fbreak; }; 'Bscr;' => { output->first = 0x212c; fbreak; }; 'Bumpeq;' => { output->first = 0x224e; fbreak; }; 'CHcy;' => { output->first = 0x0427; fbreak; }; 'COPY;' => { output->first = 0xa9; fbreak; }; 'COPY' => { output->first = 0xa9; fbreak; }; 'Cacute;' => { output->first = 0x0106; fbreak; }; 'Cap;' => { output->first = 0x22d2; fbreak; }; 'CapitalDifferentialD;' => { output->first = 0x2145; fbreak; }; 'Cayleys;' => { output->first = 0x212d; fbreak; }; 'Ccaron;' => { output->first = 0x010c; fbreak; }; 'Ccedil;' => { output->first = 0xc7; fbreak; }; 'Ccedil' => { output->first = 0xc7; fbreak; }; 'Ccirc;' => { output->first = 0x0108; fbreak; }; 'Cconint;' => { output->first = 0x2230; fbreak; }; 'Cdot;' => { output->first = 0x010a; fbreak; }; 'Cedilla;' => { output->first = 0xb8; fbreak; }; 'CenterDot;' => { output->first = 0xb7; fbreak; }; 'Cfr;' => { output->first = 0x212d; fbreak; }; 'Chi;' => { output->first = 0x03a7; fbreak; }; 'CircleDot;' => { output->first = 0x2299; fbreak; }; 'CircleMinus;' => { output->first = 0x2296; fbreak; }; 'CirclePlus;' => { output->first = 0x2295; fbreak; }; 'CircleTimes;' => { output->first = 0x2297; fbreak; }; 'ClockwiseContourIntegral;' => { output->first = 0x2232; fbreak; }; 'CloseCurlyDoubleQuote;' => { output->first = 0x201d; fbreak; }; 'CloseCurlyQuote;' => { output->first = 0x2019; fbreak; }; 'Colon;' => { output->first = 0x2237; fbreak; }; 'Colone;' => { output->first = 0x2a74; fbreak; }; 'Congruent;' => { output->first = 0x2261; fbreak; }; 'Conint;' => { output->first = 0x222f; fbreak; }; 'ContourIntegral;' => { output->first = 0x222e; fbreak; }; 'Copf;' => { output->first = 0x2102; fbreak; }; 'Coproduct;' => { output->first = 0x2210; fbreak; }; 'CounterClockwiseContourIntegral;' => { output->first = 0x2233; fbreak; }; 'Cross;' => { output->first = 0x2a2f; fbreak; }; 'Cscr;' => { output->first = 0x0001d49e; fbreak; }; 'Cup;' => { output->first = 0x22d3; fbreak; }; 'CupCap;' => { output->first = 0x224d; fbreak; }; 'DD;' => { output->first = 0x2145; fbreak; }; 'DDotrahd;' => { output->first = 0x2911; fbreak; }; 'DJcy;' => { output->first = 0x0402; fbreak; }; 'DScy;' => { output->first = 0x0405; fbreak; }; 'DZcy;' => { output->first = 0x040f; fbreak; }; 'Dagger;' => { output->first = 0x2021; fbreak; }; 'Darr;' => { output->first = 0x21a1; fbreak; }; 'Dashv;' => { output->first = 0x2ae4; fbreak; }; 'Dcaron;' => { output->first = 0x010e; fbreak; }; 'Dcy;' => { output->first = 0x0414; fbreak; }; 'Del;' => { output->first = 0x2207; fbreak; }; 'Delta;' => { output->first = 0x0394; fbreak; }; 'Dfr;' => { output->first = 0x0001d507; fbreak; }; 'DiacriticalAcute;' => { output->first = 0xb4; fbreak; }; 'DiacriticalDot;' => { output->first = 0x02d9; fbreak; }; 'DiacriticalDoubleAcute;' => { output->first = 0x02dd; fbreak; }; 'DiacriticalGrave;' => { output->first = 0x60; fbreak; }; 'DiacriticalTilde;' => { output->first = 0x02dc; fbreak; }; 'Diamond;' => { output->first = 0x22c4; fbreak; }; 'DifferentialD;' => { output->first = 0x2146; fbreak; }; 'Dopf;' => { output->first = 0x0001d53b; fbreak; }; 'Dot;' => { output->first = 0xa8; fbreak; }; 'DotDot;' => { output->first = 0x20dc; fbreak; }; 'DotEqual;' => { output->first = 0x2250; fbreak; }; 'DoubleContourIntegral;' => { output->first = 0x222f; fbreak; }; 'DoubleDot;' => { output->first = 0xa8; fbreak; }; 'DoubleDownArrow;' => { output->first = 0x21d3; fbreak; }; 'DoubleLeftArrow;' => { output->first = 0x21d0; fbreak; }; 'DoubleLeftRightArrow;' => { output->first = 0x21d4; fbreak; }; 'DoubleLeftTee;' => { output->first = 0x2ae4; fbreak; }; 'DoubleLongLeftArrow;' => { output->first = 0x27f8; fbreak; }; 'DoubleLongLeftRightArrow;' => { output->first = 0x27fa; fbreak; }; 'DoubleLongRightArrow;' => { output->first = 0x27f9; fbreak; }; 'DoubleRightArrow;' => { output->first = 0x21d2; fbreak; }; 'DoubleRightTee;' => { output->first = 0x22a8; fbreak; }; 'DoubleUpArrow;' => { output->first = 0x21d1; fbreak; }; 'DoubleUpDownArrow;' => { output->first = 0x21d5; fbreak; }; 'DoubleVerticalBar;' => { output->first = 0x2225; fbreak; }; 'DownArrow;' => { output->first = 0x2193; fbreak; }; 'DownArrowBar;' => { output->first = 0x2913; fbreak; }; 'DownArrowUpArrow;' => { output->first = 0x21f5; fbreak; }; 'DownBreve;' => { output->first = 0x0311; fbreak; }; 'DownLeftRightVector;' => { output->first = 0x2950; fbreak; }; 'DownLeftTeeVector;' => { output->first = 0x295e; fbreak; }; 'DownLeftVector;' => { output->first = 0x21bd; fbreak; }; 'DownLeftVectorBar;' => { output->first = 0x2956; fbreak; }; 'DownRightTeeVector;' => { output->first = 0x295f; fbreak; }; 'DownRightVector;' => { output->first = 0x21c1; fbreak; }; 'DownRightVectorBar;' => { output->first = 0x2957; fbreak; }; 'DownTee;' => { output->first = 0x22a4; fbreak; }; 'DownTeeArrow;' => { output->first = 0x21a7; fbreak; }; 'Downarrow;' => { output->first = 0x21d3; fbreak; }; 'Dscr;' => { output->first = 0x0001d49f; fbreak; }; 'Dstrok;' => { output->first = 0x0110; fbreak; }; 'ENG;' => { output->first = 0x014a; fbreak; }; 'ETH;' => { output->first = 0xd0; fbreak; }; 'ETH' => { output->first = 0xd0; fbreak; }; 'Eacute;' => { output->first = 0xc9; fbreak; }; 'Eacute' => { output->first = 0xc9; fbreak; }; 'Ecaron;' => { output->first = 0x011a; fbreak; }; 'Ecirc;' => { output->first = 0xca; fbreak; }; 'Ecirc' => { output->first = 0xca; fbreak; }; 'Ecy;' => { output->first = 0x042d; fbreak; }; 'Edot;' => { output->first = 0x0116; fbreak; }; 'Efr;' => { output->first = 0x0001d508; fbreak; }; 'Egrave;' => { output->first = 0xc8; fbreak; }; 'Egrave' => { output->first = 0xc8; fbreak; }; 'Element;' => { output->first = 0x2208; fbreak; }; 'Emacr;' => { output->first = 0x0112; fbreak; }; 'EmptySmallSquare;' => { output->first = 0x25fb; fbreak; }; 'EmptyVerySmallSquare;' => { output->first = 0x25ab; fbreak; }; 'Eogon;' => { output->first = 0x0118; fbreak; }; 'Eopf;' => { output->first = 0x0001d53c; fbreak; }; 'Epsilon;' => { output->first = 0x0395; fbreak; }; 'Equal;' => { output->first = 0x2a75; fbreak; }; 'EqualTilde;' => { output->first = 0x2242; fbreak; }; 'Equilibrium;' => { output->first = 0x21cc; fbreak; }; 'Escr;' => { output->first = 0x2130; fbreak; }; 'Esim;' => { output->first = 0x2a73; fbreak; }; 'Eta;' => { output->first = 0x0397; fbreak; }; 'Euml;' => { output->first = 0xcb; fbreak; }; 'Euml' => { output->first = 0xcb; fbreak; }; 'Exists;' => { output->first = 0x2203; fbreak; }; 'ExponentialE;' => { output->first = 0x2147; fbreak; }; 'Fcy;' => { output->first = 0x0424; fbreak; }; 'Ffr;' => { output->first = 0x0001d509; fbreak; }; 'FilledSmallSquare;' => { output->first = 0x25fc; fbreak; }; 'FilledVerySmallSquare;' => { output->first = 0x25aa; fbreak; }; 'Fopf;' => { output->first = 0x0001d53d; fbreak; }; 'ForAll;' => { output->first = 0x2200; fbreak; }; 'Fouriertrf;' => { output->first = 0x2131; fbreak; }; 'Fscr;' => { output->first = 0x2131; fbreak; }; 'GJcy;' => { output->first = 0x0403; fbreak; }; 'GT;' => { output->first = 0x3e; fbreak; }; 'GT' => { output->first = 0x3e; fbreak; }; 'Gamma;' => { output->first = 0x0393; fbreak; }; 'Gammad;' => { output->first = 0x03dc; fbreak; }; 'Gbreve;' => { output->first = 0x011e; fbreak; }; 'Gcedil;' => { output->first = 0x0122; fbreak; }; 'Gcirc;' => { output->first = 0x011c; fbreak; }; 'Gcy;' => { output->first = 0x0413; fbreak; }; 'Gdot;' => { output->first = 0x0120; fbreak; }; 'Gfr;' => { output->first = 0x0001d50a; fbreak; }; 'Gg;' => { output->first = 0x22d9; fbreak; }; 'Gopf;' => { output->first = 0x0001d53e; fbreak; }; 'GreaterEqual;' => { output->first = 0x2265; fbreak; }; 'GreaterEqualLess;' => { output->first = 0x22db; fbreak; }; 'GreaterFullEqual;' => { output->first = 0x2267; fbreak; }; 'GreaterGreater;' => { output->first = 0x2aa2; fbreak; }; 'GreaterLess;' => { output->first = 0x2277; fbreak; }; 'GreaterSlantEqual;' => { output->first = 0x2a7e; fbreak; }; 'GreaterTilde;' => { output->first = 0x2273; fbreak; }; 'Gscr;' => { output->first = 0x0001d4a2; fbreak; }; 'Gt;' => { output->first = 0x226b; fbreak; }; 'HARDcy;' => { output->first = 0x042a; fbreak; }; 'Hacek;' => { output->first = 0x02c7; fbreak; }; 'Hat;' => { output->first = 0x5e; fbreak; }; 'Hcirc;' => { output->first = 0x0124; fbreak; }; 'Hfr;' => { output->first = 0x210c; fbreak; }; 'HilbertSpace;' => { output->first = 0x210b; fbreak; }; 'Hopf;' => { output->first = 0x210d; fbreak; }; 'HorizontalLine;' => { output->first = 0x2500; fbreak; }; 'Hscr;' => { output->first = 0x210b; fbreak; }; 'Hstrok;' => { output->first = 0x0126; fbreak; }; 'HumpDownHump;' => { output->first = 0x224e; fbreak; }; 'HumpEqual;' => { output->first = 0x224f; fbreak; }; 'IEcy;' => { output->first = 0x0415; fbreak; }; 'IJlig;' => { output->first = 0x0132; fbreak; }; 'IOcy;' => { output->first = 0x0401; fbreak; }; 'Iacute;' => { output->first = 0xcd; fbreak; }; 'Iacute' => { output->first = 0xcd; fbreak; }; 'Icirc;' => { output->first = 0xce; fbreak; }; 'Icirc' => { output->first = 0xce; fbreak; }; 'Icy;' => { output->first = 0x0418; fbreak; }; 'Idot;' => { output->first = 0x0130; fbreak; }; 'Ifr;' => { output->first = 0x2111; fbreak; }; 'Igrave;' => { output->first = 0xcc; fbreak; }; 'Igrave' => { output->first = 0xcc; fbreak; }; 'Im;' => { output->first = 0x2111; fbreak; }; 'Imacr;' => { output->first = 0x012a; fbreak; }; 'ImaginaryI;' => { output->first = 0x2148; fbreak; }; 'Implies;' => { output->first = 0x21d2; fbreak; }; 'Int;' => { output->first = 0x222c; fbreak; }; 'Integral;' => { output->first = 0x222b; fbreak; }; 'Intersection;' => { output->first = 0x22c2; fbreak; }; 'InvisibleComma;' => { output->first = 0x2063; fbreak; }; 'InvisibleTimes;' => { output->first = 0x2062; fbreak; }; 'Iogon;' => { output->first = 0x012e; fbreak; }; 'Iopf;' => { output->first = 0x0001d540; fbreak; }; 'Iota;' => { output->first = 0x0399; fbreak; }; 'Iscr;' => { output->first = 0x2110; fbreak; }; 'Itilde;' => { output->first = 0x0128; fbreak; }; 'Iukcy;' => { output->first = 0x0406; fbreak; }; 'Iuml;' => { output->first = 0xcf; fbreak; }; 'Iuml' => { output->first = 0xcf; fbreak; }; 'Jcirc;' => { output->first = 0x0134; fbreak; }; 'Jcy;' => { output->first = 0x0419; fbreak; }; 'Jfr;' => { output->first = 0x0001d50d; fbreak; }; 'Jopf;' => { output->first = 0x0001d541; fbreak; }; 'Jscr;' => { output->first = 0x0001d4a5; fbreak; }; 'Jsercy;' => { output->first = 0x0408; fbreak; }; 'Jukcy;' => { output->first = 0x0404; fbreak; }; 'KHcy;' => { output->first = 0x0425; fbreak; }; 'KJcy;' => { output->first = 0x040c; fbreak; }; 'Kappa;' => { output->first = 0x039a; fbreak; }; 'Kcedil;' => { output->first = 0x0136; fbreak; }; 'Kcy;' => { output->first = 0x041a; fbreak; }; 'Kfr;' => { output->first = 0x0001d50e; fbreak; }; 'Kopf;' => { output->first = 0x0001d542; fbreak; }; 'Kscr;' => { output->first = 0x0001d4a6; fbreak; }; 'LJcy;' => { output->first = 0x0409; fbreak; }; 'LT;' => { output->first = 0x3c; fbreak; }; 'LT' => { output->first = 0x3c; fbreak; }; 'Lacute;' => { output->first = 0x0139; fbreak; }; 'Lambda;' => { output->first = 0x039b; fbreak; }; 'Lang;' => { output->first = 0x27ea; fbreak; }; 'Laplacetrf;' => { output->first = 0x2112; fbreak; }; 'Larr;' => { output->first = 0x219e; fbreak; }; 'Lcaron;' => { output->first = 0x013d; fbreak; }; 'Lcedil;' => { output->first = 0x013b; fbreak; }; 'Lcy;' => { output->first = 0x041b; fbreak; }; 'LeftAngleBracket;' => { output->first = 0x27e8; fbreak; }; 'LeftArrow;' => { output->first = 0x2190; fbreak; }; 'LeftArrowBar;' => { output->first = 0x21e4; fbreak; }; 'LeftArrowRightArrow;' => { output->first = 0x21c6; fbreak; }; 'LeftCeiling;' => { output->first = 0x2308; fbreak; }; 'LeftDoubleBracket;' => { output->first = 0x27e6; fbreak; }; 'LeftDownTeeVector;' => { output->first = 0x2961; fbreak; }; 'LeftDownVector;' => { output->first = 0x21c3; fbreak; }; 'LeftDownVectorBar;' => { output->first = 0x2959; fbreak; }; 'LeftFloor;' => { output->first = 0x230a; fbreak; }; 'LeftRightArrow;' => { output->first = 0x2194; fbreak; }; 'LeftRightVector;' => { output->first = 0x294e; fbreak; }; 'LeftTee;' => { output->first = 0x22a3; fbreak; }; 'LeftTeeArrow;' => { output->first = 0x21a4; fbreak; }; 'LeftTeeVector;' => { output->first = 0x295a; fbreak; }; 'LeftTriangle;' => { output->first = 0x22b2; fbreak; }; 'LeftTriangleBar;' => { output->first = 0x29cf; fbreak; }; 'LeftTriangleEqual;' => { output->first = 0x22b4; fbreak; }; 'LeftUpDownVector;' => { output->first = 0x2951; fbreak; }; 'LeftUpTeeVector;' => { output->first = 0x2960; fbreak; }; 'LeftUpVector;' => { output->first = 0x21bf; fbreak; }; 'LeftUpVectorBar;' => { output->first = 0x2958; fbreak; }; 'LeftVector;' => { output->first = 0x21bc; fbreak; }; 'LeftVectorBar;' => { output->first = 0x2952; fbreak; }; 'Leftarrow;' => { output->first = 0x21d0; fbreak; }; 'Leftrightarrow;' => { output->first = 0x21d4; fbreak; }; 'LessEqualGreater;' => { output->first = 0x22da; fbreak; }; 'LessFullEqual;' => { output->first = 0x2266; fbreak; }; 'LessGreater;' => { output->first = 0x2276; fbreak; }; 'LessLess;' => { output->first = 0x2aa1; fbreak; }; 'LessSlantEqual;' => { output->first = 0x2a7d; fbreak; }; 'LessTilde;' => { output->first = 0x2272; fbreak; }; 'Lfr;' => { output->first = 0x0001d50f; fbreak; }; 'Ll;' => { output->first = 0x22d8; fbreak; }; 'Lleftarrow;' => { output->first = 0x21da; fbreak; }; 'Lmidot;' => { output->first = 0x013f; fbreak; }; 'LongLeftArrow;' => { output->first = 0x27f5; fbreak; }; 'LongLeftRightArrow;' => { output->first = 0x27f7; fbreak; }; 'LongRightArrow;' => { output->first = 0x27f6; fbreak; }; 'Longleftarrow;' => { output->first = 0x27f8; fbreak; }; 'Longleftrightarrow;' => { output->first = 0x27fa; fbreak; }; 'Longrightarrow;' => { output->first = 0x27f9; fbreak; }; 'Lopf;' => { output->first = 0x0001d543; fbreak; }; 'LowerLeftArrow;' => { output->first = 0x2199; fbreak; }; 'LowerRightArrow;' => { output->first = 0x2198; fbreak; }; 'Lscr;' => { output->first = 0x2112; fbreak; }; 'Lsh;' => { output->first = 0x21b0; fbreak; }; 'Lstrok;' => { output->first = 0x0141; fbreak; }; 'Lt;' => { output->first = 0x226a; fbreak; }; 'Map;' => { output->first = 0x2905; fbreak; }; 'Mcy;' => { output->first = 0x041c; fbreak; }; 'MediumSpace;' => { output->first = 0x205f; fbreak; }; 'Mellintrf;' => { output->first = 0x2133; fbreak; }; 'Mfr;' => { output->first = 0x0001d510; fbreak; }; 'MinusPlus;' => { output->first = 0x2213; fbreak; }; 'Mopf;' => { output->first = 0x0001d544; fbreak; }; 'Mscr;' => { output->first = 0x2133; fbreak; }; 'Mu;' => { output->first = 0x039c; fbreak; }; 'NJcy;' => { output->first = 0x040a; fbreak; }; 'Nacute;' => { output->first = 0x0143; fbreak; }; 'Ncaron;' => { output->first = 0x0147; fbreak; }; 'Ncedil;' => { output->first = 0x0145; fbreak; }; 'Ncy;' => { output->first = 0x041d; fbreak; }; 'NegativeMediumSpace;' => { output->first = 0x200b; fbreak; }; 'NegativeThickSpace;' => { output->first = 0x200b; fbreak; }; 'NegativeThinSpace;' => { output->first = 0x200b; fbreak; }; 'NegativeVeryThinSpace;' => { output->first = 0x200b; fbreak; }; 'NestedGreaterGreater;' => { output->first = 0x226b; fbreak; }; 'NestedLessLess;' => { output->first = 0x226a; fbreak; }; 'NewLine;' => { output->first = 0x0a; fbreak; }; 'Nfr;' => { output->first = 0x0001d511; fbreak; }; 'NoBreak;' => { output->first = 0x2060; fbreak; }; 'NonBreakingSpace;' => { output->first = 0xa0; fbreak; }; 'Nopf;' => { output->first = 0x2115; fbreak; }; 'Not;' => { output->first = 0x2aec; fbreak; }; 'NotCongruent;' => { output->first = 0x2262; fbreak; }; 'NotCupCap;' => { output->first = 0x226d; fbreak; }; 'NotDoubleVerticalBar;' => { output->first = 0x2226; fbreak; }; 'NotElement;' => { output->first = 0x2209; fbreak; }; 'NotEqual;' => { output->first = 0x2260; fbreak; }; 'NotEqualTilde;' => { output->first = 0x2242; output->second = 0x0338; fbreak; }; 'NotExists;' => { output->first = 0x2204; fbreak; }; 'NotGreater;' => { output->first = 0x226f; fbreak; }; 'NotGreaterEqual;' => { output->first = 0x2271; fbreak; }; 'NotGreaterFullEqual;' => { output->first = 0x2267; output->second = 0x0338; fbreak; }; 'NotGreaterGreater;' => { output->first = 0x226b; output->second = 0x0338; fbreak; }; 'NotGreaterLess;' => { output->first = 0x2279; fbreak; }; 'NotGreaterSlantEqual;' => { output->first = 0x2a7e; output->second = 0x0338; fbreak; }; 'NotGreaterTilde;' => { output->first = 0x2275; fbreak; }; 'NotHumpDownHump;' => { output->first = 0x224e; output->second = 0x0338; fbreak; }; 'NotHumpEqual;' => { output->first = 0x224f; output->second = 0x0338; fbreak; }; 'NotLeftTriangle;' => { output->first = 0x22ea; fbreak; }; 'NotLeftTriangleBar;' => { output->first = 0x29cf; output->second = 0x0338; fbreak; }; 'NotLeftTriangleEqual;' => { output->first = 0x22ec; fbreak; }; 'NotLess;' => { output->first = 0x226e; fbreak; }; 'NotLessEqual;' => { output->first = 0x2270; fbreak; }; 'NotLessGreater;' => { output->first = 0x2278; fbreak; }; 'NotLessLess;' => { output->first = 0x226a; output->second = 0x0338; fbreak; }; 'NotLessSlantEqual;' => { output->first = 0x2a7d; output->second = 0x0338; fbreak; }; 'NotLessTilde;' => { output->first = 0x2274; fbreak; }; 'NotNestedGreaterGreater;' => { output->first = 0x2aa2; output->second = 0x0338; fbreak; }; 'NotNestedLessLess;' => { output->first = 0x2aa1; output->second = 0x0338; fbreak; }; 'NotPrecedes;' => { output->first = 0x2280; fbreak; }; 'NotPrecedesEqual;' => { output->first = 0x2aaf; output->second = 0x0338; fbreak; }; 'NotPrecedesSlantEqual;' => { output->first = 0x22e0; fbreak; }; 'NotReverseElement;' => { output->first = 0x220c; fbreak; }; 'NotRightTriangle;' => { output->first = 0x22eb; fbreak; }; 'NotRightTriangleBar;' => { output->first = 0x29d0; output->second = 0x0338; fbreak; }; 'NotRightTriangleEqual;' => { output->first = 0x22ed; fbreak; }; 'NotSquareSubset;' => { output->first = 0x228f; output->second = 0x0338; fbreak; }; 'NotSquareSubsetEqual;' => { output->first = 0x22e2; fbreak; }; 'NotSquareSuperset;' => { output->first = 0x2290; output->second = 0x0338; fbreak; }; 'NotSquareSupersetEqual;' => { output->first = 0x22e3; fbreak; }; 'NotSubset;' => { output->first = 0x2282; output->second = 0x20d2; fbreak; }; 'NotSubsetEqual;' => { output->first = 0x2288; fbreak; }; 'NotSucceeds;' => { output->first = 0x2281; fbreak; }; 'NotSucceedsEqual;' => { output->first = 0x2ab0; output->second = 0x0338; fbreak; }; 'NotSucceedsSlantEqual;' => { output->first = 0x22e1; fbreak; }; 'NotSucceedsTilde;' => { output->first = 0x227f; output->second = 0x0338; fbreak; }; 'NotSuperset;' => { output->first = 0x2283; output->second = 0x20d2; fbreak; }; 'NotSupersetEqual;' => { output->first = 0x2289; fbreak; }; 'NotTilde;' => { output->first = 0x2241; fbreak; }; 'NotTildeEqual;' => { output->first = 0x2244; fbreak; }; 'NotTildeFullEqual;' => { output->first = 0x2247; fbreak; }; 'NotTildeTilde;' => { output->first = 0x2249; fbreak; }; 'NotVerticalBar;' => { output->first = 0x2224; fbreak; }; 'Nscr;' => { output->first = 0x0001d4a9; fbreak; }; 'Ntilde;' => { output->first = 0xd1; fbreak; }; 'Ntilde' => { output->first = 0xd1; fbreak; }; 'Nu;' => { output->first = 0x039d; fbreak; }; 'OElig;' => { output->first = 0x0152; fbreak; }; 'Oacute;' => { output->first = 0xd3; fbreak; }; 'Oacute' => { output->first = 0xd3; fbreak; }; 'Ocirc;' => { output->first = 0xd4; fbreak; }; 'Ocirc' => { output->first = 0xd4; fbreak; }; 'Ocy;' => { output->first = 0x041e; fbreak; }; 'Odblac;' => { output->first = 0x0150; fbreak; }; 'Ofr;' => { output->first = 0x0001d512; fbreak; }; 'Ograve;' => { output->first = 0xd2; fbreak; }; 'Ograve' => { output->first = 0xd2; fbreak; }; 'Omacr;' => { output->first = 0x014c; fbreak; }; 'Omega;' => { output->first = 0x03a9; fbreak; }; 'Omicron;' => { output->first = 0x039f; fbreak; }; 'Oopf;' => { output->first = 0x0001d546; fbreak; }; 'OpenCurlyDoubleQuote;' => { output->first = 0x201c; fbreak; }; 'OpenCurlyQuote;' => { output->first = 0x2018; fbreak; }; 'Or;' => { output->first = 0x2a54; fbreak; }; 'Oscr;' => { output->first = 0x0001d4aa; fbreak; }; 'Oslash;' => { output->first = 0xd8; fbreak; }; 'Oslash' => { output->first = 0xd8; fbreak; }; 'Otilde;' => { output->first = 0xd5; fbreak; }; 'Otilde' => { output->first = 0xd5; fbreak; }; 'Otimes;' => { output->first = 0x2a37; fbreak; }; 'Ouml;' => { output->first = 0xd6; fbreak; }; 'Ouml' => { output->first = 0xd6; fbreak; }; 'OverBar;' => { output->first = 0x203e; fbreak; }; 'OverBrace;' => { output->first = 0x23de; fbreak; }; 'OverBracket;' => { output->first = 0x23b4; fbreak; }; 'OverParenthesis;' => { output->first = 0x23dc; fbreak; }; 'PartialD;' => { output->first = 0x2202; fbreak; }; 'Pcy;' => { output->first = 0x041f; fbreak; }; 'Pfr;' => { output->first = 0x0001d513; fbreak; }; 'Phi;' => { output->first = 0x03a6; fbreak; }; 'Pi;' => { output->first = 0x03a0; fbreak; }; 'PlusMinus;' => { output->first = 0xb1; fbreak; }; 'Poincareplane;' => { output->first = 0x210c; fbreak; }; 'Popf;' => { output->first = 0x2119; fbreak; }; 'Pr;' => { output->first = 0x2abb; fbreak; }; 'Precedes;' => { output->first = 0x227a; fbreak; }; 'PrecedesEqual;' => { output->first = 0x2aaf; fbreak; }; 'PrecedesSlantEqual;' => { output->first = 0x227c; fbreak; }; 'PrecedesTilde;' => { output->first = 0x227e; fbreak; }; 'Prime;' => { output->first = 0x2033; fbreak; }; 'Product;' => { output->first = 0x220f; fbreak; }; 'Proportion;' => { output->first = 0x2237; fbreak; }; 'Proportional;' => { output->first = 0x221d; fbreak; }; 'Pscr;' => { output->first = 0x0001d4ab; fbreak; }; 'Psi;' => { output->first = 0x03a8; fbreak; }; 'QUOT;' => { output->first = 0x22; fbreak; }; 'QUOT' => { output->first = 0x22; fbreak; }; 'Qfr;' => { output->first = 0x0001d514; fbreak; }; 'Qopf;' => { output->first = 0x211a; fbreak; }; 'Qscr;' => { output->first = 0x0001d4ac; fbreak; }; 'RBarr;' => { output->first = 0x2910; fbreak; }; 'REG;' => { output->first = 0xae; fbreak; }; 'REG' => { output->first = 0xae; fbreak; }; 'Racute;' => { output->first = 0x0154; fbreak; }; 'Rang;' => { output->first = 0x27eb; fbreak; }; 'Rarr;' => { output->first = 0x21a0; fbreak; }; 'Rarrtl;' => { output->first = 0x2916; fbreak; }; 'Rcaron;' => { output->first = 0x0158; fbreak; }; 'Rcedil;' => { output->first = 0x0156; fbreak; }; 'Rcy;' => { output->first = 0x0420; fbreak; }; 'Re;' => { output->first = 0x211c; fbreak; }; 'ReverseElement;' => { output->first = 0x220b; fbreak; }; 'ReverseEquilibrium;' => { output->first = 0x21cb; fbreak; }; 'ReverseUpEquilibrium;' => { output->first = 0x296f; fbreak; }; 'Rfr;' => { output->first = 0x211c; fbreak; }; 'Rho;' => { output->first = 0x03a1; fbreak; }; 'RightAngleBracket;' => { output->first = 0x27e9; fbreak; }; 'RightArrow;' => { output->first = 0x2192; fbreak; }; 'RightArrowBar;' => { output->first = 0x21e5; fbreak; }; 'RightArrowLeftArrow;' => { output->first = 0x21c4; fbreak; }; 'RightCeiling;' => { output->first = 0x2309; fbreak; }; 'RightDoubleBracket;' => { output->first = 0x27e7; fbreak; }; 'RightDownTeeVector;' => { output->first = 0x295d; fbreak; }; 'RightDownVector;' => { output->first = 0x21c2; fbreak; }; 'RightDownVectorBar;' => { output->first = 0x2955; fbreak; }; 'RightFloor;' => { output->first = 0x230b; fbreak; }; 'RightTee;' => { output->first = 0x22a2; fbreak; }; 'RightTeeArrow;' => { output->first = 0x21a6; fbreak; }; 'RightTeeVector;' => { output->first = 0x295b; fbreak; }; 'RightTriangle;' => { output->first = 0x22b3; fbreak; }; 'RightTriangleBar;' => { output->first = 0x29d0; fbreak; }; 'RightTriangleEqual;' => { output->first = 0x22b5; fbreak; }; 'RightUpDownVector;' => { output->first = 0x294f; fbreak; }; 'RightUpTeeVector;' => { output->first = 0x295c; fbreak; }; 'RightUpVector;' => { output->first = 0x21be; fbreak; }; 'RightUpVectorBar;' => { output->first = 0x2954; fbreak; }; 'RightVector;' => { output->first = 0x21c0; fbreak; }; 'RightVectorBar;' => { output->first = 0x2953; fbreak; }; 'Rightarrow;' => { output->first = 0x21d2; fbreak; }; 'Ropf;' => { output->first = 0x211d; fbreak; }; 'RoundImplies;' => { output->first = 0x2970; fbreak; }; 'Rrightarrow;' => { output->first = 0x21db; fbreak; }; 'Rscr;' => { output->first = 0x211b; fbreak; }; 'Rsh;' => { output->first = 0x21b1; fbreak; }; 'RuleDelayed;' => { output->first = 0x29f4; fbreak; }; 'SHCHcy;' => { output->first = 0x0429; fbreak; }; 'SHcy;' => { output->first = 0x0428; fbreak; }; 'SOFTcy;' => { output->first = 0x042c; fbreak; }; 'Sacute;' => { output->first = 0x015a; fbreak; }; 'Sc;' => { output->first = 0x2abc; fbreak; }; 'Scaron;' => { output->first = 0x0160; fbreak; }; 'Scedil;' => { output->first = 0x015e; fbreak; }; 'Scirc;' => { output->first = 0x015c; fbreak; }; 'Scy;' => { output->first = 0x0421; fbreak; }; 'Sfr;' => { output->first = 0x0001d516; fbreak; }; 'ShortDownArrow;' => { output->first = 0x2193; fbreak; }; 'ShortLeftArrow;' => { output->first = 0x2190; fbreak; }; 'ShortRightArrow;' => { output->first = 0x2192; fbreak; }; 'ShortUpArrow;' => { output->first = 0x2191; fbreak; }; 'Sigma;' => { output->first = 0x03a3; fbreak; }; 'SmallCircle;' => { output->first = 0x2218; fbreak; }; 'Sopf;' => { output->first = 0x0001d54a; fbreak; }; 'Sqrt;' => { output->first = 0x221a; fbreak; }; 'Square;' => { output->first = 0x25a1; fbreak; }; 'SquareIntersection;' => { output->first = 0x2293; fbreak; }; 'SquareSubset;' => { output->first = 0x228f; fbreak; }; 'SquareSubsetEqual;' => { output->first = 0x2291; fbreak; }; 'SquareSuperset;' => { output->first = 0x2290; fbreak; }; 'SquareSupersetEqual;' => { output->first = 0x2292; fbreak; }; 'SquareUnion;' => { output->first = 0x2294; fbreak; }; 'Sscr;' => { output->first = 0x0001d4ae; fbreak; }; 'Star;' => { output->first = 0x22c6; fbreak; }; 'Sub;' => { output->first = 0x22d0; fbreak; }; 'Subset;' => { output->first = 0x22d0; fbreak; }; 'SubsetEqual;' => { output->first = 0x2286; fbreak; }; 'Succeeds;' => { output->first = 0x227b; fbreak; }; 'SucceedsEqual;' => { output->first = 0x2ab0; fbreak; }; 'SucceedsSlantEqual;' => { output->first = 0x227d; fbreak; }; 'SucceedsTilde;' => { output->first = 0x227f; fbreak; }; 'SuchThat;' => { output->first = 0x220b; fbreak; }; 'Sum;' => { output->first = 0x2211; fbreak; }; 'Sup;' => { output->first = 0x22d1; fbreak; }; 'Superset;' => { output->first = 0x2283; fbreak; }; 'SupersetEqual;' => { output->first = 0x2287; fbreak; }; 'Supset;' => { output->first = 0x22d1; fbreak; }; 'THORN;' => { output->first = 0xde; fbreak; }; 'THORN' => { output->first = 0xde; fbreak; }; 'TRADE;' => { output->first = 0x2122; fbreak; }; 'TSHcy;' => { output->first = 0x040b; fbreak; }; 'TScy;' => { output->first = 0x0426; fbreak; }; 'Tab;' => { output->first = 0x09; fbreak; }; 'Tau;' => { output->first = 0x03a4; fbreak; }; 'Tcaron;' => { output->first = 0x0164; fbreak; }; 'Tcedil;' => { output->first = 0x0162; fbreak; }; 'Tcy;' => { output->first = 0x0422; fbreak; }; 'Tfr;' => { output->first = 0x0001d517; fbreak; }; 'Therefore;' => { output->first = 0x2234; fbreak; }; 'Theta;' => { output->first = 0x0398; fbreak; }; 'ThickSpace;' => { output->first = 0x205f; output->second = 0x200a; fbreak; }; 'ThinSpace;' => { output->first = 0x2009; fbreak; }; 'Tilde;' => { output->first = 0x223c; fbreak; }; 'TildeEqual;' => { output->first = 0x2243; fbreak; }; 'TildeFullEqual;' => { output->first = 0x2245; fbreak; }; 'TildeTilde;' => { output->first = 0x2248; fbreak; }; 'Topf;' => { output->first = 0x0001d54b; fbreak; }; 'TripleDot;' => { output->first = 0x20db; fbreak; }; 'Tscr;' => { output->first = 0x0001d4af; fbreak; }; 'Tstrok;' => { output->first = 0x0166; fbreak; }; 'Uacute;' => { output->first = 0xda; fbreak; }; 'Uacute' => { output->first = 0xda; fbreak; }; 'Uarr;' => { output->first = 0x219f; fbreak; }; 'Uarrocir;' => { output->first = 0x2949; fbreak; }; 'Ubrcy;' => { output->first = 0x040e; fbreak; }; 'Ubreve;' => { output->first = 0x016c; fbreak; }; 'Ucirc;' => { output->first = 0xdb; fbreak; }; 'Ucirc' => { output->first = 0xdb; fbreak; }; 'Ucy;' => { output->first = 0x0423; fbreak; }; 'Udblac;' => { output->first = 0x0170; fbreak; }; 'Ufr;' => { output->first = 0x0001d518; fbreak; }; 'Ugrave;' => { output->first = 0xd9; fbreak; }; 'Ugrave' => { output->first = 0xd9; fbreak; }; 'Umacr;' => { output->first = 0x016a; fbreak; }; 'UnderBar;' => { output->first = 0x5f; fbreak; }; 'UnderBrace;' => { output->first = 0x23df; fbreak; }; 'UnderBracket;' => { output->first = 0x23b5; fbreak; }; 'UnderParenthesis;' => { output->first = 0x23dd; fbreak; }; 'Union;' => { output->first = 0x22c3; fbreak; }; 'UnionPlus;' => { output->first = 0x228e; fbreak; }; 'Uogon;' => { output->first = 0x0172; fbreak; }; 'Uopf;' => { output->first = 0x0001d54c; fbreak; }; 'UpArrow;' => { output->first = 0x2191; fbreak; }; 'UpArrowBar;' => { output->first = 0x2912; fbreak; }; 'UpArrowDownArrow;' => { output->first = 0x21c5; fbreak; }; 'UpDownArrow;' => { output->first = 0x2195; fbreak; }; 'UpEquilibrium;' => { output->first = 0x296e; fbreak; }; 'UpTee;' => { output->first = 0x22a5; fbreak; }; 'UpTeeArrow;' => { output->first = 0x21a5; fbreak; }; 'Uparrow;' => { output->first = 0x21d1; fbreak; }; 'Updownarrow;' => { output->first = 0x21d5; fbreak; }; 'UpperLeftArrow;' => { output->first = 0x2196; fbreak; }; 'UpperRightArrow;' => { output->first = 0x2197; fbreak; }; 'Upsi;' => { output->first = 0x03d2; fbreak; }; 'Upsilon;' => { output->first = 0x03a5; fbreak; }; 'Uring;' => { output->first = 0x016e; fbreak; }; 'Uscr;' => { output->first = 0x0001d4b0; fbreak; }; 'Utilde;' => { output->first = 0x0168; fbreak; }; 'Uuml;' => { output->first = 0xdc; fbreak; }; 'Uuml' => { output->first = 0xdc; fbreak; }; 'VDash;' => { output->first = 0x22ab; fbreak; }; 'Vbar;' => { output->first = 0x2aeb; fbreak; }; 'Vcy;' => { output->first = 0x0412; fbreak; }; 'Vdash;' => { output->first = 0x22a9; fbreak; }; 'Vdashl;' => { output->first = 0x2ae6; fbreak; }; 'Vee;' => { output->first = 0x22c1; fbreak; }; 'Verbar;' => { output->first = 0x2016; fbreak; }; 'Vert;' => { output->first = 0x2016; fbreak; }; 'VerticalBar;' => { output->first = 0x2223; fbreak; }; 'VerticalLine;' => { output->first = 0x7c; fbreak; }; 'VerticalSeparator;' => { output->first = 0x2758; fbreak; }; 'VerticalTilde;' => { output->first = 0x2240; fbreak; }; 'VeryThinSpace;' => { output->first = 0x200a; fbreak; }; 'Vfr;' => { output->first = 0x0001d519; fbreak; }; 'Vopf;' => { output->first = 0x0001d54d; fbreak; }; 'Vscr;' => { output->first = 0x0001d4b1; fbreak; }; 'Vvdash;' => { output->first = 0x22aa; fbreak; }; 'Wcirc;' => { output->first = 0x0174; fbreak; }; 'Wedge;' => { output->first = 0x22c0; fbreak; }; 'Wfr;' => { output->first = 0x0001d51a; fbreak; }; 'Wopf;' => { output->first = 0x0001d54e; fbreak; }; 'Wscr;' => { output->first = 0x0001d4b2; fbreak; }; 'Xfr;' => { output->first = 0x0001d51b; fbreak; }; 'Xi;' => { output->first = 0x039e; fbreak; }; 'Xopf;' => { output->first = 0x0001d54f; fbreak; }; 'Xscr;' => { output->first = 0x0001d4b3; fbreak; }; 'YAcy;' => { output->first = 0x042f; fbreak; }; 'YIcy;' => { output->first = 0x0407; fbreak; }; 'YUcy;' => { output->first = 0x042e; fbreak; }; 'Yacute;' => { output->first = 0xdd; fbreak; }; 'Yacute' => { output->first = 0xdd; fbreak; }; 'Ycirc;' => { output->first = 0x0176; fbreak; }; 'Ycy;' => { output->first = 0x042b; fbreak; }; 'Yfr;' => { output->first = 0x0001d51c; fbreak; }; 'Yopf;' => { output->first = 0x0001d550; fbreak; }; 'Yscr;' => { output->first = 0x0001d4b4; fbreak; }; 'Yuml;' => { output->first = 0x0178; fbreak; }; 'ZHcy;' => { output->first = 0x0416; fbreak; }; 'Zacute;' => { output->first = 0x0179; fbreak; }; 'Zcaron;' => { output->first = 0x017d; fbreak; }; 'Zcy;' => { output->first = 0x0417; fbreak; }; 'Zdot;' => { output->first = 0x017b; fbreak; }; 'ZeroWidthSpace;' => { output->first = 0x200b; fbreak; }; 'Zeta;' => { output->first = 0x0396; fbreak; }; 'Zfr;' => { output->first = 0x2128; fbreak; }; 'Zopf;' => { output->first = 0x2124; fbreak; }; 'Zscr;' => { output->first = 0x0001d4b5; fbreak; }; 'aacute;' => { output->first = 0xe1; fbreak; }; 'aacute' => { output->first = 0xe1; fbreak; }; 'abreve;' => { output->first = 0x0103; fbreak; }; 'ac;' => { output->first = 0x223e; fbreak; }; 'acE;' => { output->first = 0x223e; output->second = 0x0333; fbreak; }; 'acd;' => { output->first = 0x223f; fbreak; }; 'acirc;' => { output->first = 0xe2; fbreak; }; 'acirc' => { output->first = 0xe2; fbreak; }; 'acute;' => { output->first = 0xb4; fbreak; }; 'acute' => { output->first = 0xb4; fbreak; }; 'acy;' => { output->first = 0x0430; fbreak; }; 'aelig;' => { output->first = 0xe6; fbreak; }; 'aelig' => { output->first = 0xe6; fbreak; }; 'af;' => { output->first = 0x2061; fbreak; }; 'afr;' => { output->first = 0x0001d51e; fbreak; }; 'agrave;' => { output->first = 0xe0; fbreak; }; 'agrave' => { output->first = 0xe0; fbreak; }; 'alefsym;' => { output->first = 0x2135; fbreak; }; 'aleph;' => { output->first = 0x2135; fbreak; }; 'alpha;' => { output->first = 0x03b1; fbreak; }; 'amacr;' => { output->first = 0x0101; fbreak; }; 'amalg;' => { output->first = 0x2a3f; fbreak; }; 'amp;' => { output->first = 0x26; fbreak; }; 'amp' => { output->first = 0x26; fbreak; }; 'and;' => { output->first = 0x2227; fbreak; }; 'andand;' => { output->first = 0x2a55; fbreak; }; 'andd;' => { output->first = 0x2a5c; fbreak; }; 'andslope;' => { output->first = 0x2a58; fbreak; }; 'andv;' => { output->first = 0x2a5a; fbreak; }; 'ang;' => { output->first = 0x2220; fbreak; }; 'ange;' => { output->first = 0x29a4; fbreak; }; 'angle;' => { output->first = 0x2220; fbreak; }; 'angmsd;' => { output->first = 0x2221; fbreak; }; 'angmsdaa;' => { output->first = 0x29a8; fbreak; }; 'angmsdab;' => { output->first = 0x29a9; fbreak; }; 'angmsdac;' => { output->first = 0x29aa; fbreak; }; 'angmsdad;' => { output->first = 0x29ab; fbreak; }; 'angmsdae;' => { output->first = 0x29ac; fbreak; }; 'angmsdaf;' => { output->first = 0x29ad; fbreak; }; 'angmsdag;' => { output->first = 0x29ae; fbreak; }; 'angmsdah;' => { output->first = 0x29af; fbreak; }; 'angrt;' => { output->first = 0x221f; fbreak; }; 'angrtvb;' => { output->first = 0x22be; fbreak; }; 'angrtvbd;' => { output->first = 0x299d; fbreak; }; 'angsph;' => { output->first = 0x2222; fbreak; }; 'angst;' => { output->first = 0xc5; fbreak; }; 'angzarr;' => { output->first = 0x237c; fbreak; }; 'aogon;' => { output->first = 0x0105; fbreak; }; 'aopf;' => { output->first = 0x0001d552; fbreak; }; 'ap;' => { output->first = 0x2248; fbreak; }; 'apE;' => { output->first = 0x2a70; fbreak; }; 'apacir;' => { output->first = 0x2a6f; fbreak; }; 'ape;' => { output->first = 0x224a; fbreak; }; 'apid;' => { output->first = 0x224b; fbreak; }; 'apos;' => { output->first = 0x27; fbreak; }; 'approx;' => { output->first = 0x2248; fbreak; }; 'approxeq;' => { output->first = 0x224a; fbreak; }; 'aring;' => { output->first = 0xe5; fbreak; }; 'aring' => { output->first = 0xe5; fbreak; }; 'ascr;' => { output->first = 0x0001d4b6; fbreak; }; 'ast;' => { output->first = 0x2a; fbreak; }; 'asymp;' => { output->first = 0x2248; fbreak; }; 'asympeq;' => { output->first = 0x224d; fbreak; }; 'atilde;' => { output->first = 0xe3; fbreak; }; 'atilde' => { output->first = 0xe3; fbreak; }; 'auml;' => { output->first = 0xe4; fbreak; }; 'auml' => { output->first = 0xe4; fbreak; }; 'awconint;' => { output->first = 0x2233; fbreak; }; 'awint;' => { output->first = 0x2a11; fbreak; }; 'bNot;' => { output->first = 0x2aed; fbreak; }; 'backcong;' => { output->first = 0x224c; fbreak; }; 'backepsilon;' => { output->first = 0x03f6; fbreak; }; 'backprime;' => { output->first = 0x2035; fbreak; }; 'backsim;' => { output->first = 0x223d; fbreak; }; 'backsimeq;' => { output->first = 0x22cd; fbreak; }; 'barvee;' => { output->first = 0x22bd; fbreak; }; 'barwed;' => { output->first = 0x2305; fbreak; }; 'barwedge;' => { output->first = 0x2305; fbreak; }; 'bbrk;' => { output->first = 0x23b5; fbreak; }; 'bbrktbrk;' => { output->first = 0x23b6; fbreak; }; 'bcong;' => { output->first = 0x224c; fbreak; }; 'bcy;' => { output->first = 0x0431; fbreak; }; 'bdquo;' => { output->first = 0x201e; fbreak; }; 'becaus;' => { output->first = 0x2235; fbreak; }; 'because;' => { output->first = 0x2235; fbreak; }; 'bemptyv;' => { output->first = 0x29b0; fbreak; }; 'bepsi;' => { output->first = 0x03f6; fbreak; }; 'bernou;' => { output->first = 0x212c; fbreak; }; 'beta;' => { output->first = 0x03b2; fbreak; }; 'beth;' => { output->first = 0x2136; fbreak; }; 'between;' => { output->first = 0x226c; fbreak; }; 'bfr;' => { output->first = 0x0001d51f; fbreak; }; 'bigcap;' => { output->first = 0x22c2; fbreak; }; 'bigcirc;' => { output->first = 0x25ef; fbreak; }; 'bigcup;' => { output->first = 0x22c3; fbreak; }; 'bigodot;' => { output->first = 0x2a00; fbreak; }; 'bigoplus;' => { output->first = 0x2a01; fbreak; }; 'bigotimes;' => { output->first = 0x2a02; fbreak; }; 'bigsqcup;' => { output->first = 0x2a06; fbreak; }; 'bigstar;' => { output->first = 0x2605; fbreak; }; 'bigtriangledown;' => { output->first = 0x25bd; fbreak; }; 'bigtriangleup;' => { output->first = 0x25b3; fbreak; }; 'biguplus;' => { output->first = 0x2a04; fbreak; }; 'bigvee;' => { output->first = 0x22c1; fbreak; }; 'bigwedge;' => { output->first = 0x22c0; fbreak; }; 'bkarow;' => { output->first = 0x290d; fbreak; }; 'blacklozenge;' => { output->first = 0x29eb; fbreak; }; 'blacksquare;' => { output->first = 0x25aa; fbreak; }; 'blacktriangle;' => { output->first = 0x25b4; fbreak; }; 'blacktriangledown;' => { output->first = 0x25be; fbreak; }; 'blacktriangleleft;' => { output->first = 0x25c2; fbreak; }; 'blacktriangleright;' => { output->first = 0x25b8; fbreak; }; 'blank;' => { output->first = 0x2423; fbreak; }; 'blk12;' => { output->first = 0x2592; fbreak; }; 'blk14;' => { output->first = 0x2591; fbreak; }; 'blk34;' => { output->first = 0x2593; fbreak; }; 'block;' => { output->first = 0x2588; fbreak; }; 'bne;' => { output->first = 0x3d; output->second = 0x20e5; fbreak; }; 'bnequiv;' => { output->first = 0x2261; output->second = 0x20e5; fbreak; }; 'bnot;' => { output->first = 0x2310; fbreak; }; 'bopf;' => { output->first = 0x0001d553; fbreak; }; 'bot;' => { output->first = 0x22a5; fbreak; }; 'bottom;' => { output->first = 0x22a5; fbreak; }; 'bowtie;' => { output->first = 0x22c8; fbreak; }; 'boxDL;' => { output->first = 0x2557; fbreak; }; 'boxDR;' => { output->first = 0x2554; fbreak; }; 'boxDl;' => { output->first = 0x2556; fbreak; }; 'boxDr;' => { output->first = 0x2553; fbreak; }; 'boxH;' => { output->first = 0x2550; fbreak; }; 'boxHD;' => { output->first = 0x2566; fbreak; }; 'boxHU;' => { output->first = 0x2569; fbreak; }; 'boxHd;' => { output->first = 0x2564; fbreak; }; 'boxHu;' => { output->first = 0x2567; fbreak; }; 'boxUL;' => { output->first = 0x255d; fbreak; }; 'boxUR;' => { output->first = 0x255a; fbreak; }; 'boxUl;' => { output->first = 0x255c; fbreak; }; 'boxUr;' => { output->first = 0x2559; fbreak; }; 'boxV;' => { output->first = 0x2551; fbreak; }; 'boxVH;' => { output->first = 0x256c; fbreak; }; 'boxVL;' => { output->first = 0x2563; fbreak; }; 'boxVR;' => { output->first = 0x2560; fbreak; }; 'boxVh;' => { output->first = 0x256b; fbreak; }; 'boxVl;' => { output->first = 0x2562; fbreak; }; 'boxVr;' => { output->first = 0x255f; fbreak; }; 'boxbox;' => { output->first = 0x29c9; fbreak; }; 'boxdL;' => { output->first = 0x2555; fbreak; }; 'boxdR;' => { output->first = 0x2552; fbreak; }; 'boxdl;' => { output->first = 0x2510; fbreak; }; 'boxdr;' => { output->first = 0x250c; fbreak; }; 'boxh;' => { output->first = 0x2500; fbreak; }; 'boxhD;' => { output->first = 0x2565; fbreak; }; 'boxhU;' => { output->first = 0x2568; fbreak; }; 'boxhd;' => { output->first = 0x252c; fbreak; }; 'boxhu;' => { output->first = 0x2534; fbreak; }; 'boxminus;' => { output->first = 0x229f; fbreak; }; 'boxplus;' => { output->first = 0x229e; fbreak; }; 'boxtimes;' => { output->first = 0x22a0; fbreak; }; 'boxuL;' => { output->first = 0x255b; fbreak; }; 'boxuR;' => { output->first = 0x2558; fbreak; }; 'boxul;' => { output->first = 0x2518; fbreak; }; 'boxur;' => { output->first = 0x2514; fbreak; }; 'boxv;' => { output->first = 0x2502; fbreak; }; 'boxvH;' => { output->first = 0x256a; fbreak; }; 'boxvL;' => { output->first = 0x2561; fbreak; }; 'boxvR;' => { output->first = 0x255e; fbreak; }; 'boxvh;' => { output->first = 0x253c; fbreak; }; 'boxvl;' => { output->first = 0x2524; fbreak; }; 'boxvr;' => { output->first = 0x251c; fbreak; }; 'bprime;' => { output->first = 0x2035; fbreak; }; 'breve;' => { output->first = 0x02d8; fbreak; }; 'brvbar;' => { output->first = 0xa6; fbreak; }; 'brvbar' => { output->first = 0xa6; fbreak; }; 'bscr;' => { output->first = 0x0001d4b7; fbreak; }; 'bsemi;' => { output->first = 0x204f; fbreak; }; 'bsim;' => { output->first = 0x223d; fbreak; }; 'bsime;' => { output->first = 0x22cd; fbreak; }; 'bsol;' => { output->first = 0x5c; fbreak; }; 'bsolb;' => { output->first = 0x29c5; fbreak; }; 'bsolhsub;' => { output->first = 0x27c8; fbreak; }; 'bull;' => { output->first = 0x2022; fbreak; }; 'bullet;' => { output->first = 0x2022; fbreak; }; 'bump;' => { output->first = 0x224e; fbreak; }; 'bumpE;' => { output->first = 0x2aae; fbreak; }; 'bumpe;' => { output->first = 0x224f; fbreak; }; 'bumpeq;' => { output->first = 0x224f; fbreak; }; 'cacute;' => { output->first = 0x0107; fbreak; }; 'cap;' => { output->first = 0x2229; fbreak; }; 'capand;' => { output->first = 0x2a44; fbreak; }; 'capbrcup;' => { output->first = 0x2a49; fbreak; }; 'capcap;' => { output->first = 0x2a4b; fbreak; }; 'capcup;' => { output->first = 0x2a47; fbreak; }; 'capdot;' => { output->first = 0x2a40; fbreak; }; 'caps;' => { output->first = 0x2229; output->second = 0xfe00; fbreak; }; 'caret;' => { output->first = 0x2041; fbreak; }; 'caron;' => { output->first = 0x02c7; fbreak; }; 'ccaps;' => { output->first = 0x2a4d; fbreak; }; 'ccaron;' => { output->first = 0x010d; fbreak; }; 'ccedil;' => { output->first = 0xe7; fbreak; }; 'ccedil' => { output->first = 0xe7; fbreak; }; 'ccirc;' => { output->first = 0x0109; fbreak; }; 'ccups;' => { output->first = 0x2a4c; fbreak; }; 'ccupssm;' => { output->first = 0x2a50; fbreak; }; 'cdot;' => { output->first = 0x010b; fbreak; }; 'cedil;' => { output->first = 0xb8; fbreak; }; 'cedil' => { output->first = 0xb8; fbreak; }; 'cemptyv;' => { output->first = 0x29b2; fbreak; }; 'cent;' => { output->first = 0xa2; fbreak; }; 'cent' => { output->first = 0xa2; fbreak; }; 'centerdot;' => { output->first = 0xb7; fbreak; }; 'cfr;' => { output->first = 0x0001d520; fbreak; }; 'chcy;' => { output->first = 0x0447; fbreak; }; 'check;' => { output->first = 0x2713; fbreak; }; 'checkmark;' => { output->first = 0x2713; fbreak; }; 'chi;' => { output->first = 0x03c7; fbreak; }; 'cir;' => { output->first = 0x25cb; fbreak; }; 'cirE;' => { output->first = 0x29c3; fbreak; }; 'circ;' => { output->first = 0x02c6; fbreak; }; 'circeq;' => { output->first = 0x2257; fbreak; }; 'circlearrowleft;' => { output->first = 0x21ba; fbreak; }; 'circlearrowright;' => { output->first = 0x21bb; fbreak; }; 'circledR;' => { output->first = 0xae; fbreak; }; 'circledS;' => { output->first = 0x24c8; fbreak; }; 'circledast;' => { output->first = 0x229b; fbreak; }; 'circledcirc;' => { output->first = 0x229a; fbreak; }; 'circleddash;' => { output->first = 0x229d; fbreak; }; 'cire;' => { output->first = 0x2257; fbreak; }; 'cirfnint;' => { output->first = 0x2a10; fbreak; }; 'cirmid;' => { output->first = 0x2aef; fbreak; }; 'cirscir;' => { output->first = 0x29c2; fbreak; }; 'clubs;' => { output->first = 0x2663; fbreak; }; 'clubsuit;' => { output->first = 0x2663; fbreak; }; 'colon;' => { output->first = 0x3a; fbreak; }; 'colone;' => { output->first = 0x2254; fbreak; }; 'coloneq;' => { output->first = 0x2254; fbreak; }; 'comma;' => { output->first = 0x2c; fbreak; }; 'commat;' => { output->first = 0x40; fbreak; }; 'comp;' => { output->first = 0x2201; fbreak; }; 'compfn;' => { output->first = 0x2218; fbreak; }; 'complement;' => { output->first = 0x2201; fbreak; }; 'complexes;' => { output->first = 0x2102; fbreak; }; 'cong;' => { output->first = 0x2245; fbreak; }; 'congdot;' => { output->first = 0x2a6d; fbreak; }; 'conint;' => { output->first = 0x222e; fbreak; }; 'copf;' => { output->first = 0x0001d554; fbreak; }; 'coprod;' => { output->first = 0x2210; fbreak; }; 'copy;' => { output->first = 0xa9; fbreak; }; 'copy' => { output->first = 0xa9; fbreak; }; 'copysr;' => { output->first = 0x2117; fbreak; }; 'crarr;' => { output->first = 0x21b5; fbreak; }; 'cross;' => { output->first = 0x2717; fbreak; }; 'cscr;' => { output->first = 0x0001d4b8; fbreak; }; 'csub;' => { output->first = 0x2acf; fbreak; }; 'csube;' => { output->first = 0x2ad1; fbreak; }; 'csup;' => { output->first = 0x2ad0; fbreak; }; 'csupe;' => { output->first = 0x2ad2; fbreak; }; 'ctdot;' => { output->first = 0x22ef; fbreak; }; 'cudarrl;' => { output->first = 0x2938; fbreak; }; 'cudarrr;' => { output->first = 0x2935; fbreak; }; 'cuepr;' => { output->first = 0x22de; fbreak; }; 'cuesc;' => { output->first = 0x22df; fbreak; }; 'cularr;' => { output->first = 0x21b6; fbreak; }; 'cularrp;' => { output->first = 0x293d; fbreak; }; 'cup;' => { output->first = 0x222a; fbreak; }; 'cupbrcap;' => { output->first = 0x2a48; fbreak; }; 'cupcap;' => { output->first = 0x2a46; fbreak; }; 'cupcup;' => { output->first = 0x2a4a; fbreak; }; 'cupdot;' => { output->first = 0x228d; fbreak; }; 'cupor;' => { output->first = 0x2a45; fbreak; }; 'cups;' => { output->first = 0x222a; output->second = 0xfe00; fbreak; }; 'curarr;' => { output->first = 0x21b7; fbreak; }; 'curarrm;' => { output->first = 0x293c; fbreak; }; 'curlyeqprec;' => { output->first = 0x22de; fbreak; }; 'curlyeqsucc;' => { output->first = 0x22df; fbreak; }; 'curlyvee;' => { output->first = 0x22ce; fbreak; }; 'curlywedge;' => { output->first = 0x22cf; fbreak; }; 'curren;' => { output->first = 0xa4; fbreak; }; 'curren' => { output->first = 0xa4; fbreak; }; 'curvearrowleft;' => { output->first = 0x21b6; fbreak; }; 'curvearrowright;' => { output->first = 0x21b7; fbreak; }; 'cuvee;' => { output->first = 0x22ce; fbreak; }; 'cuwed;' => { output->first = 0x22cf; fbreak; }; 'cwconint;' => { output->first = 0x2232; fbreak; }; 'cwint;' => { output->first = 0x2231; fbreak; }; 'cylcty;' => { output->first = 0x232d; fbreak; }; 'dArr;' => { output->first = 0x21d3; fbreak; }; 'dHar;' => { output->first = 0x2965; fbreak; }; 'dagger;' => { output->first = 0x2020; fbreak; }; 'daleth;' => { output->first = 0x2138; fbreak; }; 'darr;' => { output->first = 0x2193; fbreak; }; 'dash;' => { output->first = 0x2010; fbreak; }; 'dashv;' => { output->first = 0x22a3; fbreak; }; 'dbkarow;' => { output->first = 0x290f; fbreak; }; 'dblac;' => { output->first = 0x02dd; fbreak; }; 'dcaron;' => { output->first = 0x010f; fbreak; }; 'dcy;' => { output->first = 0x0434; fbreak; }; 'dd;' => { output->first = 0x2146; fbreak; }; 'ddagger;' => { output->first = 0x2021; fbreak; }; 'ddarr;' => { output->first = 0x21ca; fbreak; }; 'ddotseq;' => { output->first = 0x2a77; fbreak; }; 'deg;' => { output->first = 0xb0; fbreak; }; 'deg' => { output->first = 0xb0; fbreak; }; 'delta;' => { output->first = 0x03b4; fbreak; }; 'demptyv;' => { output->first = 0x29b1; fbreak; }; 'dfisht;' => { output->first = 0x297f; fbreak; }; 'dfr;' => { output->first = 0x0001d521; fbreak; }; 'dharl;' => { output->first = 0x21c3; fbreak; }; 'dharr;' => { output->first = 0x21c2; fbreak; }; 'diam;' => { output->first = 0x22c4; fbreak; }; 'diamond;' => { output->first = 0x22c4; fbreak; }; 'diamondsuit;' => { output->first = 0x2666; fbreak; }; 'diams;' => { output->first = 0x2666; fbreak; }; 'die;' => { output->first = 0xa8; fbreak; }; 'digamma;' => { output->first = 0x03dd; fbreak; }; 'disin;' => { output->first = 0x22f2; fbreak; }; 'div;' => { output->first = 0xf7; fbreak; }; 'divide;' => { output->first = 0xf7; fbreak; }; 'divide' => { output->first = 0xf7; fbreak; }; 'divideontimes;' => { output->first = 0x22c7; fbreak; }; 'divonx;' => { output->first = 0x22c7; fbreak; }; 'djcy;' => { output->first = 0x0452; fbreak; }; 'dlcorn;' => { output->first = 0x231e; fbreak; }; 'dlcrop;' => { output->first = 0x230d; fbreak; }; 'dollar;' => { output->first = 0x24; fbreak; }; 'dopf;' => { output->first = 0x0001d555; fbreak; }; 'dot;' => { output->first = 0x02d9; fbreak; }; 'doteq;' => { output->first = 0x2250; fbreak; }; 'doteqdot;' => { output->first = 0x2251; fbreak; }; 'dotminus;' => { output->first = 0x2238; fbreak; }; 'dotplus;' => { output->first = 0x2214; fbreak; }; 'dotsquare;' => { output->first = 0x22a1; fbreak; }; 'doublebarwedge;' => { output->first = 0x2306; fbreak; }; 'downarrow;' => { output->first = 0x2193; fbreak; }; 'downdownarrows;' => { output->first = 0x21ca; fbreak; }; 'downharpoonleft;' => { output->first = 0x21c3; fbreak; }; 'downharpoonright;' => { output->first = 0x21c2; fbreak; }; 'drbkarow;' => { output->first = 0x2910; fbreak; }; 'drcorn;' => { output->first = 0x231f; fbreak; }; 'drcrop;' => { output->first = 0x230c; fbreak; }; 'dscr;' => { output->first = 0x0001d4b9; fbreak; }; 'dscy;' => { output->first = 0x0455; fbreak; }; 'dsol;' => { output->first = 0x29f6; fbreak; }; 'dstrok;' => { output->first = 0x0111; fbreak; }; 'dtdot;' => { output->first = 0x22f1; fbreak; }; 'dtri;' => { output->first = 0x25bf; fbreak; }; 'dtrif;' => { output->first = 0x25be; fbreak; }; 'duarr;' => { output->first = 0x21f5; fbreak; }; 'duhar;' => { output->first = 0x296f; fbreak; }; 'dwangle;' => { output->first = 0x29a6; fbreak; }; 'dzcy;' => { output->first = 0x045f; fbreak; }; 'dzigrarr;' => { output->first = 0x27ff; fbreak; }; 'eDDot;' => { output->first = 0x2a77; fbreak; }; 'eDot;' => { output->first = 0x2251; fbreak; }; 'eacute;' => { output->first = 0xe9; fbreak; }; 'eacute' => { output->first = 0xe9; fbreak; }; 'easter;' => { output->first = 0x2a6e; fbreak; }; 'ecaron;' => { output->first = 0x011b; fbreak; }; 'ecir;' => { output->first = 0x2256; fbreak; }; 'ecirc;' => { output->first = 0xea; fbreak; }; 'ecirc' => { output->first = 0xea; fbreak; }; 'ecolon;' => { output->first = 0x2255; fbreak; }; 'ecy;' => { output->first = 0x044d; fbreak; }; 'edot;' => { output->first = 0x0117; fbreak; }; 'ee;' => { output->first = 0x2147; fbreak; }; 'efDot;' => { output->first = 0x2252; fbreak; }; 'efr;' => { output->first = 0x0001d522; fbreak; }; 'eg;' => { output->first = 0x2a9a; fbreak; }; 'egrave;' => { output->first = 0xe8; fbreak; }; 'egrave' => { output->first = 0xe8; fbreak; }; 'egs;' => { output->first = 0x2a96; fbreak; }; 'egsdot;' => { output->first = 0x2a98; fbreak; }; 'el;' => { output->first = 0x2a99; fbreak; }; 'elinters;' => { output->first = 0x23e7; fbreak; }; 'ell;' => { output->first = 0x2113; fbreak; }; 'els;' => { output->first = 0x2a95; fbreak; }; 'elsdot;' => { output->first = 0x2a97; fbreak; }; 'emacr;' => { output->first = 0x0113; fbreak; }; 'empty;' => { output->first = 0x2205; fbreak; }; 'emptyset;' => { output->first = 0x2205; fbreak; }; 'emptyv;' => { output->first = 0x2205; fbreak; }; 'emsp13;' => { output->first = 0x2004; fbreak; }; 'emsp14;' => { output->first = 0x2005; fbreak; }; 'emsp;' => { output->first = 0x2003; fbreak; }; 'eng;' => { output->first = 0x014b; fbreak; }; 'ensp;' => { output->first = 0x2002; fbreak; }; 'eogon;' => { output->first = 0x0119; fbreak; }; 'eopf;' => { output->first = 0x0001d556; fbreak; }; 'epar;' => { output->first = 0x22d5; fbreak; }; 'eparsl;' => { output->first = 0x29e3; fbreak; }; 'eplus;' => { output->first = 0x2a71; fbreak; }; 'epsi;' => { output->first = 0x03b5; fbreak; }; 'epsilon;' => { output->first = 0x03b5; fbreak; }; 'epsiv;' => { output->first = 0x03f5; fbreak; }; 'eqcirc;' => { output->first = 0x2256; fbreak; }; 'eqcolon;' => { output->first = 0x2255; fbreak; }; 'eqsim;' => { output->first = 0x2242; fbreak; }; 'eqslantgtr;' => { output->first = 0x2a96; fbreak; }; 'eqslantless;' => { output->first = 0x2a95; fbreak; }; 'equals;' => { output->first = 0x3d; fbreak; }; 'equest;' => { output->first = 0x225f; fbreak; }; 'equiv;' => { output->first = 0x2261; fbreak; }; 'equivDD;' => { output->first = 0x2a78; fbreak; }; 'eqvparsl;' => { output->first = 0x29e5; fbreak; }; 'erDot;' => { output->first = 0x2253; fbreak; }; 'erarr;' => { output->first = 0x2971; fbreak; }; 'escr;' => { output->first = 0x212f; fbreak; }; 'esdot;' => { output->first = 0x2250; fbreak; }; 'esim;' => { output->first = 0x2242; fbreak; }; 'eta;' => { output->first = 0x03b7; fbreak; }; 'eth;' => { output->first = 0xf0; fbreak; }; 'eth' => { output->first = 0xf0; fbreak; }; 'euml;' => { output->first = 0xeb; fbreak; }; 'euml' => { output->first = 0xeb; fbreak; }; 'euro;' => { output->first = 0x20ac; fbreak; }; 'excl;' => { output->first = 0x21; fbreak; }; 'exist;' => { output->first = 0x2203; fbreak; }; 'expectation;' => { output->first = 0x2130; fbreak; }; 'exponentiale;' => { output->first = 0x2147; fbreak; }; 'fallingdotseq;' => { output->first = 0x2252; fbreak; }; 'fcy;' => { output->first = 0x0444; fbreak; }; 'female;' => { output->first = 0x2640; fbreak; }; 'ffilig;' => { output->first = 0xfb03; fbreak; }; 'fflig;' => { output->first = 0xfb00; fbreak; }; 'ffllig;' => { output->first = 0xfb04; fbreak; }; 'ffr;' => { output->first = 0x0001d523; fbreak; }; 'filig;' => { output->first = 0xfb01; fbreak; }; 'fjlig;' => { output->first = 0x66; output->second = 0x6a; fbreak; }; 'flat;' => { output->first = 0x266d; fbreak; }; 'fllig;' => { output->first = 0xfb02; fbreak; }; 'fltns;' => { output->first = 0x25b1; fbreak; }; 'fnof;' => { output->first = 0x0192; fbreak; }; 'fopf;' => { output->first = 0x0001d557; fbreak; }; 'forall;' => { output->first = 0x2200; fbreak; }; 'fork;' => { output->first = 0x22d4; fbreak; }; 'forkv;' => { output->first = 0x2ad9; fbreak; }; 'fpartint;' => { output->first = 0x2a0d; fbreak; }; 'frac12;' => { output->first = 0xbd; fbreak; }; 'frac12' => { output->first = 0xbd; fbreak; }; 'frac13;' => { output->first = 0x2153; fbreak; }; 'frac14;' => { output->first = 0xbc; fbreak; }; 'frac14' => { output->first = 0xbc; fbreak; }; 'frac15;' => { output->first = 0x2155; fbreak; }; 'frac16;' => { output->first = 0x2159; fbreak; }; 'frac18;' => { output->first = 0x215b; fbreak; }; 'frac23;' => { output->first = 0x2154; fbreak; }; 'frac25;' => { output->first = 0x2156; fbreak; }; 'frac34;' => { output->first = 0xbe; fbreak; }; 'frac34' => { output->first = 0xbe; fbreak; }; 'frac35;' => { output->first = 0x2157; fbreak; }; 'frac38;' => { output->first = 0x215c; fbreak; }; 'frac45;' => { output->first = 0x2158; fbreak; }; 'frac56;' => { output->first = 0x215a; fbreak; }; 'frac58;' => { output->first = 0x215d; fbreak; }; 'frac78;' => { output->first = 0x215e; fbreak; }; 'frasl;' => { output->first = 0x2044; fbreak; }; 'frown;' => { output->first = 0x2322; fbreak; }; 'fscr;' => { output->first = 0x0001d4bb; fbreak; }; 'gE;' => { output->first = 0x2267; fbreak; }; 'gEl;' => { output->first = 0x2a8c; fbreak; }; 'gacute;' => { output->first = 0x01f5; fbreak; }; 'gamma;' => { output->first = 0x03b3; fbreak; }; 'gammad;' => { output->first = 0x03dd; fbreak; }; 'gap;' => { output->first = 0x2a86; fbreak; }; 'gbreve;' => { output->first = 0x011f; fbreak; }; 'gcirc;' => { output->first = 0x011d; fbreak; }; 'gcy;' => { output->first = 0x0433; fbreak; }; 'gdot;' => { output->first = 0x0121; fbreak; }; 'ge;' => { output->first = 0x2265; fbreak; }; 'gel;' => { output->first = 0x22db; fbreak; }; 'geq;' => { output->first = 0x2265; fbreak; }; 'geqq;' => { output->first = 0x2267; fbreak; }; 'geqslant;' => { output->first = 0x2a7e; fbreak; }; 'ges;' => { output->first = 0x2a7e; fbreak; }; 'gescc;' => { output->first = 0x2aa9; fbreak; }; 'gesdot;' => { output->first = 0x2a80; fbreak; }; 'gesdoto;' => { output->first = 0x2a82; fbreak; }; 'gesdotol;' => { output->first = 0x2a84; fbreak; }; 'gesl;' => { output->first = 0x22db; output->second = 0xfe00; fbreak; }; 'gesles;' => { output->first = 0x2a94; fbreak; }; 'gfr;' => { output->first = 0x0001d524; fbreak; }; 'gg;' => { output->first = 0x226b; fbreak; }; 'ggg;' => { output->first = 0x22d9; fbreak; }; 'gimel;' => { output->first = 0x2137; fbreak; }; 'gjcy;' => { output->first = 0x0453; fbreak; }; 'gl;' => { output->first = 0x2277; fbreak; }; 'glE;' => { output->first = 0x2a92; fbreak; }; 'gla;' => { output->first = 0x2aa5; fbreak; }; 'glj;' => { output->first = 0x2aa4; fbreak; }; 'gnE;' => { output->first = 0x2269; fbreak; }; 'gnap;' => { output->first = 0x2a8a; fbreak; }; 'gnapprox;' => { output->first = 0x2a8a; fbreak; }; 'gne;' => { output->first = 0x2a88; fbreak; }; 'gneq;' => { output->first = 0x2a88; fbreak; }; 'gneqq;' => { output->first = 0x2269; fbreak; }; 'gnsim;' => { output->first = 0x22e7; fbreak; }; 'gopf;' => { output->first = 0x0001d558; fbreak; }; 'grave;' => { output->first = 0x60; fbreak; }; 'gscr;' => { output->first = 0x210a; fbreak; }; 'gsim;' => { output->first = 0x2273; fbreak; }; 'gsime;' => { output->first = 0x2a8e; fbreak; }; 'gsiml;' => { output->first = 0x2a90; fbreak; }; 'gt;' => { output->first = 0x3e; fbreak; }; 'gt' => { output->first = 0x3e; fbreak; }; 'gtcc;' => { output->first = 0x2aa7; fbreak; }; 'gtcir;' => { output->first = 0x2a7a; fbreak; }; 'gtdot;' => { output->first = 0x22d7; fbreak; }; 'gtlPar;' => { output->first = 0x2995; fbreak; }; 'gtquest;' => { output->first = 0x2a7c; fbreak; }; 'gtrapprox;' => { output->first = 0x2a86; fbreak; }; 'gtrarr;' => { output->first = 0x2978; fbreak; }; 'gtrdot;' => { output->first = 0x22d7; fbreak; }; 'gtreqless;' => { output->first = 0x22db; fbreak; }; 'gtreqqless;' => { output->first = 0x2a8c; fbreak; }; 'gtrless;' => { output->first = 0x2277; fbreak; }; 'gtrsim;' => { output->first = 0x2273; fbreak; }; 'gvertneqq;' => { output->first = 0x2269; output->second = 0xfe00; fbreak; }; 'gvnE;' => { output->first = 0x2269; output->second = 0xfe00; fbreak; }; 'hArr;' => { output->first = 0x21d4; fbreak; }; 'hairsp;' => { output->first = 0x200a; fbreak; }; 'half;' => { output->first = 0xbd; fbreak; }; 'hamilt;' => { output->first = 0x210b; fbreak; }; 'hardcy;' => { output->first = 0x044a; fbreak; }; 'harr;' => { output->first = 0x2194; fbreak; }; 'harrcir;' => { output->first = 0x2948; fbreak; }; 'harrw;' => { output->first = 0x21ad; fbreak; }; 'hbar;' => { output->first = 0x210f; fbreak; }; 'hcirc;' => { output->first = 0x0125; fbreak; }; 'hearts;' => { output->first = 0x2665; fbreak; }; 'heartsuit;' => { output->first = 0x2665; fbreak; }; 'hellip;' => { output->first = 0x2026; fbreak; }; 'hercon;' => { output->first = 0x22b9; fbreak; }; 'hfr;' => { output->first = 0x0001d525; fbreak; }; 'hksearow;' => { output->first = 0x2925; fbreak; }; 'hkswarow;' => { output->first = 0x2926; fbreak; }; 'hoarr;' => { output->first = 0x21ff; fbreak; }; 'homtht;' => { output->first = 0x223b; fbreak; }; 'hookleftarrow;' => { output->first = 0x21a9; fbreak; }; 'hookrightarrow;' => { output->first = 0x21aa; fbreak; }; 'hopf;' => { output->first = 0x0001d559; fbreak; }; 'horbar;' => { output->first = 0x2015; fbreak; }; 'hscr;' => { output->first = 0x0001d4bd; fbreak; }; 'hslash;' => { output->first = 0x210f; fbreak; }; 'hstrok;' => { output->first = 0x0127; fbreak; }; 'hybull;' => { output->first = 0x2043; fbreak; }; 'hyphen;' => { output->first = 0x2010; fbreak; }; 'iacute;' => { output->first = 0xed; fbreak; }; 'iacute' => { output->first = 0xed; fbreak; }; 'ic;' => { output->first = 0x2063; fbreak; }; 'icirc;' => { output->first = 0xee; fbreak; }; 'icirc' => { output->first = 0xee; fbreak; }; 'icy;' => { output->first = 0x0438; fbreak; }; 'iecy;' => { output->first = 0x0435; fbreak; }; 'iexcl;' => { output->first = 0xa1; fbreak; }; 'iexcl' => { output->first = 0xa1; fbreak; }; 'iff;' => { output->first = 0x21d4; fbreak; }; 'ifr;' => { output->first = 0x0001d526; fbreak; }; 'igrave;' => { output->first = 0xec; fbreak; }; 'igrave' => { output->first = 0xec; fbreak; }; 'ii;' => { output->first = 0x2148; fbreak; }; 'iiiint;' => { output->first = 0x2a0c; fbreak; }; 'iiint;' => { output->first = 0x222d; fbreak; }; 'iinfin;' => { output->first = 0x29dc; fbreak; }; 'iiota;' => { output->first = 0x2129; fbreak; }; 'ijlig;' => { output->first = 0x0133; fbreak; }; 'imacr;' => { output->first = 0x012b; fbreak; }; 'image;' => { output->first = 0x2111; fbreak; }; 'imagline;' => { output->first = 0x2110; fbreak; }; 'imagpart;' => { output->first = 0x2111; fbreak; }; 'imath;' => { output->first = 0x0131; fbreak; }; 'imof;' => { output->first = 0x22b7; fbreak; }; 'imped;' => { output->first = 0x01b5; fbreak; }; 'in;' => { output->first = 0x2208; fbreak; }; 'incare;' => { output->first = 0x2105; fbreak; }; 'infin;' => { output->first = 0x221e; fbreak; }; 'infintie;' => { output->first = 0x29dd; fbreak; }; 'inodot;' => { output->first = 0x0131; fbreak; }; 'int;' => { output->first = 0x222b; fbreak; }; 'intcal;' => { output->first = 0x22ba; fbreak; }; 'integers;' => { output->first = 0x2124; fbreak; }; 'intercal;' => { output->first = 0x22ba; fbreak; }; 'intlarhk;' => { output->first = 0x2a17; fbreak; }; 'intprod;' => { output->first = 0x2a3c; fbreak; }; 'iocy;' => { output->first = 0x0451; fbreak; }; 'iogon;' => { output->first = 0x012f; fbreak; }; 'iopf;' => { output->first = 0x0001d55a; fbreak; }; 'iota;' => { output->first = 0x03b9; fbreak; }; 'iprod;' => { output->first = 0x2a3c; fbreak; }; 'iquest;' => { output->first = 0xbf; fbreak; }; 'iquest' => { output->first = 0xbf; fbreak; }; 'iscr;' => { output->first = 0x0001d4be; fbreak; }; 'isin;' => { output->first = 0x2208; fbreak; }; 'isinE;' => { output->first = 0x22f9; fbreak; }; 'isindot;' => { output->first = 0x22f5; fbreak; }; 'isins;' => { output->first = 0x22f4; fbreak; }; 'isinsv;' => { output->first = 0x22f3; fbreak; }; 'isinv;' => { output->first = 0x2208; fbreak; }; 'it;' => { output->first = 0x2062; fbreak; }; 'itilde;' => { output->first = 0x0129; fbreak; }; 'iukcy;' => { output->first = 0x0456; fbreak; }; 'iuml;' => { output->first = 0xef; fbreak; }; 'iuml' => { output->first = 0xef; fbreak; }; 'jcirc;' => { output->first = 0x0135; fbreak; }; 'jcy;' => { output->first = 0x0439; fbreak; }; 'jfr;' => { output->first = 0x0001d527; fbreak; }; 'jmath;' => { output->first = 0x0237; fbreak; }; 'jopf;' => { output->first = 0x0001d55b; fbreak; }; 'jscr;' => { output->first = 0x0001d4bf; fbreak; }; 'jsercy;' => { output->first = 0x0458; fbreak; }; 'jukcy;' => { output->first = 0x0454; fbreak; }; 'kappa;' => { output->first = 0x03ba; fbreak; }; 'kappav;' => { output->first = 0x03f0; fbreak; }; 'kcedil;' => { output->first = 0x0137; fbreak; }; 'kcy;' => { output->first = 0x043a; fbreak; }; 'kfr;' => { output->first = 0x0001d528; fbreak; }; 'kgreen;' => { output->first = 0x0138; fbreak; }; 'khcy;' => { output->first = 0x0445; fbreak; }; 'kjcy;' => { output->first = 0x045c; fbreak; }; 'kopf;' => { output->first = 0x0001d55c; fbreak; }; 'kscr;' => { output->first = 0x0001d4c0; fbreak; }; 'lAarr;' => { output->first = 0x21da; fbreak; }; 'lArr;' => { output->first = 0x21d0; fbreak; }; 'lAtail;' => { output->first = 0x291b; fbreak; }; 'lBarr;' => { output->first = 0x290e; fbreak; }; 'lE;' => { output->first = 0x2266; fbreak; }; 'lEg;' => { output->first = 0x2a8b; fbreak; }; 'lHar;' => { output->first = 0x2962; fbreak; }; 'lacute;' => { output->first = 0x013a; fbreak; }; 'laemptyv;' => { output->first = 0x29b4; fbreak; }; 'lagran;' => { output->first = 0x2112; fbreak; }; 'lambda;' => { output->first = 0x03bb; fbreak; }; 'lang;' => { output->first = 0x27e8; fbreak; }; 'langd;' => { output->first = 0x2991; fbreak; }; 'langle;' => { output->first = 0x27e8; fbreak; }; 'lap;' => { output->first = 0x2a85; fbreak; }; 'laquo;' => { output->first = 0xab; fbreak; }; 'laquo' => { output->first = 0xab; fbreak; }; 'larr;' => { output->first = 0x2190; fbreak; }; 'larrb;' => { output->first = 0x21e4; fbreak; }; 'larrbfs;' => { output->first = 0x291f; fbreak; }; 'larrfs;' => { output->first = 0x291d; fbreak; }; 'larrhk;' => { output->first = 0x21a9; fbreak; }; 'larrlp;' => { output->first = 0x21ab; fbreak; }; 'larrpl;' => { output->first = 0x2939; fbreak; }; 'larrsim;' => { output->first = 0x2973; fbreak; }; 'larrtl;' => { output->first = 0x21a2; fbreak; }; 'lat;' => { output->first = 0x2aab; fbreak; }; 'latail;' => { output->first = 0x2919; fbreak; }; 'late;' => { output->first = 0x2aad; fbreak; }; 'lates;' => { output->first = 0x2aad; output->second = 0xfe00; fbreak; }; 'lbarr;' => { output->first = 0x290c; fbreak; }; 'lbbrk;' => { output->first = 0x2772; fbreak; }; 'lbrace;' => { output->first = 0x7b; fbreak; }; 'lbrack;' => { output->first = 0x5b; fbreak; }; 'lbrke;' => { output->first = 0x298b; fbreak; }; 'lbrksld;' => { output->first = 0x298f; fbreak; }; 'lbrkslu;' => { output->first = 0x298d; fbreak; }; 'lcaron;' => { output->first = 0x013e; fbreak; }; 'lcedil;' => { output->first = 0x013c; fbreak; }; 'lceil;' => { output->first = 0x2308; fbreak; }; 'lcub;' => { output->first = 0x7b; fbreak; }; 'lcy;' => { output->first = 0x043b; fbreak; }; 'ldca;' => { output->first = 0x2936; fbreak; }; 'ldquo;' => { output->first = 0x201c; fbreak; }; 'ldquor;' => { output->first = 0x201e; fbreak; }; 'ldrdhar;' => { output->first = 0x2967; fbreak; }; 'ldrushar;' => { output->first = 0x294b; fbreak; }; 'ldsh;' => { output->first = 0x21b2; fbreak; }; 'le;' => { output->first = 0x2264; fbreak; }; 'leftarrow;' => { output->first = 0x2190; fbreak; }; 'leftarrowtail;' => { output->first = 0x21a2; fbreak; }; 'leftharpoondown;' => { output->first = 0x21bd; fbreak; }; 'leftharpoonup;' => { output->first = 0x21bc; fbreak; }; 'leftleftarrows;' => { output->first = 0x21c7; fbreak; }; 'leftrightarrow;' => { output->first = 0x2194; fbreak; }; 'leftrightarrows;' => { output->first = 0x21c6; fbreak; }; 'leftrightharpoons;' => { output->first = 0x21cb; fbreak; }; 'leftrightsquigarrow;' => { output->first = 0x21ad; fbreak; }; 'leftthreetimes;' => { output->first = 0x22cb; fbreak; }; 'leg;' => { output->first = 0x22da; fbreak; }; 'leq;' => { output->first = 0x2264; fbreak; }; 'leqq;' => { output->first = 0x2266; fbreak; }; 'leqslant;' => { output->first = 0x2a7d; fbreak; }; 'les;' => { output->first = 0x2a7d; fbreak; }; 'lescc;' => { output->first = 0x2aa8; fbreak; }; 'lesdot;' => { output->first = 0x2a7f; fbreak; }; 'lesdoto;' => { output->first = 0x2a81; fbreak; }; 'lesdotor;' => { output->first = 0x2a83; fbreak; }; 'lesg;' => { output->first = 0x22da; output->second = 0xfe00; fbreak; }; 'lesges;' => { output->first = 0x2a93; fbreak; }; 'lessapprox;' => { output->first = 0x2a85; fbreak; }; 'lessdot;' => { output->first = 0x22d6; fbreak; }; 'lesseqgtr;' => { output->first = 0x22da; fbreak; }; 'lesseqqgtr;' => { output->first = 0x2a8b; fbreak; }; 'lessgtr;' => { output->first = 0x2276; fbreak; }; 'lesssim;' => { output->first = 0x2272; fbreak; }; 'lfisht;' => { output->first = 0x297c; fbreak; }; 'lfloor;' => { output->first = 0x230a; fbreak; }; 'lfr;' => { output->first = 0x0001d529; fbreak; }; 'lg;' => { output->first = 0x2276; fbreak; }; 'lgE;' => { output->first = 0x2a91; fbreak; }; 'lhard;' => { output->first = 0x21bd; fbreak; }; 'lharu;' => { output->first = 0x21bc; fbreak; }; 'lharul;' => { output->first = 0x296a; fbreak; }; 'lhblk;' => { output->first = 0x2584; fbreak; }; 'ljcy;' => { output->first = 0x0459; fbreak; }; 'll;' => { output->first = 0x226a; fbreak; }; 'llarr;' => { output->first = 0x21c7; fbreak; }; 'llcorner;' => { output->first = 0x231e; fbreak; }; 'llhard;' => { output->first = 0x296b; fbreak; }; 'lltri;' => { output->first = 0x25fa; fbreak; }; 'lmidot;' => { output->first = 0x0140; fbreak; }; 'lmoust;' => { output->first = 0x23b0; fbreak; }; 'lmoustache;' => { output->first = 0x23b0; fbreak; }; 'lnE;' => { output->first = 0x2268; fbreak; }; 'lnap;' => { output->first = 0x2a89; fbreak; }; 'lnapprox;' => { output->first = 0x2a89; fbreak; }; 'lne;' => { output->first = 0x2a87; fbreak; }; 'lneq;' => { output->first = 0x2a87; fbreak; }; 'lneqq;' => { output->first = 0x2268; fbreak; }; 'lnsim;' => { output->first = 0x22e6; fbreak; }; 'loang;' => { output->first = 0x27ec; fbreak; }; 'loarr;' => { output->first = 0x21fd; fbreak; }; 'lobrk;' => { output->first = 0x27e6; fbreak; }; 'longleftarrow;' => { output->first = 0x27f5; fbreak; }; 'longleftrightarrow;' => { output->first = 0x27f7; fbreak; }; 'longmapsto;' => { output->first = 0x27fc; fbreak; }; 'longrightarrow;' => { output->first = 0x27f6; fbreak; }; 'looparrowleft;' => { output->first = 0x21ab; fbreak; }; 'looparrowright;' => { output->first = 0x21ac; fbreak; }; 'lopar;' => { output->first = 0x2985; fbreak; }; 'lopf;' => { output->first = 0x0001d55d; fbreak; }; 'loplus;' => { output->first = 0x2a2d; fbreak; }; 'lotimes;' => { output->first = 0x2a34; fbreak; }; 'lowast;' => { output->first = 0x2217; fbreak; }; 'lowbar;' => { output->first = 0x5f; fbreak; }; 'loz;' => { output->first = 0x25ca; fbreak; }; 'lozenge;' => { output->first = 0x25ca; fbreak; }; 'lozf;' => { output->first = 0x29eb; fbreak; }; 'lpar;' => { output->first = 0x28; fbreak; }; 'lparlt;' => { output->first = 0x2993; fbreak; }; 'lrarr;' => { output->first = 0x21c6; fbreak; }; 'lrcorner;' => { output->first = 0x231f; fbreak; }; 'lrhar;' => { output->first = 0x21cb; fbreak; }; 'lrhard;' => { output->first = 0x296d; fbreak; }; 'lrm;' => { output->first = 0x200e; fbreak; }; 'lrtri;' => { output->first = 0x22bf; fbreak; }; 'lsaquo;' => { output->first = 0x2039; fbreak; }; 'lscr;' => { output->first = 0x0001d4c1; fbreak; }; 'lsh;' => { output->first = 0x21b0; fbreak; }; 'lsim;' => { output->first = 0x2272; fbreak; }; 'lsime;' => { output->first = 0x2a8d; fbreak; }; 'lsimg;' => { output->first = 0x2a8f; fbreak; }; 'lsqb;' => { output->first = 0x5b; fbreak; }; 'lsquo;' => { output->first = 0x2018; fbreak; }; 'lsquor;' => { output->first = 0x201a; fbreak; }; 'lstrok;' => { output->first = 0x0142; fbreak; }; 'lt;' => { output->first = 0x3c; fbreak; }; 'lt' => { output->first = 0x3c; fbreak; }; 'ltcc;' => { output->first = 0x2aa6; fbreak; }; 'ltcir;' => { output->first = 0x2a79; fbreak; }; 'ltdot;' => { output->first = 0x22d6; fbreak; }; 'lthree;' => { output->first = 0x22cb; fbreak; }; 'ltimes;' => { output->first = 0x22c9; fbreak; }; 'ltlarr;' => { output->first = 0x2976; fbreak; }; 'ltquest;' => { output->first = 0x2a7b; fbreak; }; 'ltrPar;' => { output->first = 0x2996; fbreak; }; 'ltri;' => { output->first = 0x25c3; fbreak; }; 'ltrie;' => { output->first = 0x22b4; fbreak; }; 'ltrif;' => { output->first = 0x25c2; fbreak; }; 'lurdshar;' => { output->first = 0x294a; fbreak; }; 'luruhar;' => { output->first = 0x2966; fbreak; }; 'lvertneqq;' => { output->first = 0x2268; output->second = 0xfe00; fbreak; }; 'lvnE;' => { output->first = 0x2268; output->second = 0xfe00; fbreak; }; 'mDDot;' => { output->first = 0x223a; fbreak; }; 'macr;' => { output->first = 0xaf; fbreak; }; 'macr' => { output->first = 0xaf; fbreak; }; 'male;' => { output->first = 0x2642; fbreak; }; 'malt;' => { output->first = 0x2720; fbreak; }; 'maltese;' => { output->first = 0x2720; fbreak; }; 'map;' => { output->first = 0x21a6; fbreak; }; 'mapsto;' => { output->first = 0x21a6; fbreak; }; 'mapstodown;' => { output->first = 0x21a7; fbreak; }; 'mapstoleft;' => { output->first = 0x21a4; fbreak; }; 'mapstoup;' => { output->first = 0x21a5; fbreak; }; 'marker;' => { output->first = 0x25ae; fbreak; }; 'mcomma;' => { output->first = 0x2a29; fbreak; }; 'mcy;' => { output->first = 0x043c; fbreak; }; 'mdash;' => { output->first = 0x2014; fbreak; }; 'measuredangle;' => { output->first = 0x2221; fbreak; }; 'mfr;' => { output->first = 0x0001d52a; fbreak; }; 'mho;' => { output->first = 0x2127; fbreak; }; 'micro;' => { output->first = 0xb5; fbreak; }; 'micro' => { output->first = 0xb5; fbreak; }; 'mid;' => { output->first = 0x2223; fbreak; }; 'midast;' => { output->first = 0x2a; fbreak; }; 'midcir;' => { output->first = 0x2af0; fbreak; }; 'middot;' => { output->first = 0xb7; fbreak; }; 'middot' => { output->first = 0xb7; fbreak; }; 'minus;' => { output->first = 0x2212; fbreak; }; 'minusb;' => { output->first = 0x229f; fbreak; }; 'minusd;' => { output->first = 0x2238; fbreak; }; 'minusdu;' => { output->first = 0x2a2a; fbreak; }; 'mlcp;' => { output->first = 0x2adb; fbreak; }; 'mldr;' => { output->first = 0x2026; fbreak; }; 'mnplus;' => { output->first = 0x2213; fbreak; }; 'models;' => { output->first = 0x22a7; fbreak; }; 'mopf;' => { output->first = 0x0001d55e; fbreak; }; 'mp;' => { output->first = 0x2213; fbreak; }; 'mscr;' => { output->first = 0x0001d4c2; fbreak; }; 'mstpos;' => { output->first = 0x223e; fbreak; }; 'mu;' => { output->first = 0x03bc; fbreak; }; 'multimap;' => { output->first = 0x22b8; fbreak; }; 'mumap;' => { output->first = 0x22b8; fbreak; }; 'nGg;' => { output->first = 0x22d9; output->second = 0x0338; fbreak; }; 'nGt;' => { output->first = 0x226b; output->second = 0x20d2; fbreak; }; 'nGtv;' => { output->first = 0x226b; output->second = 0x0338; fbreak; }; 'nLeftarrow;' => { output->first = 0x21cd; fbreak; }; 'nLeftrightarrow;' => { output->first = 0x21ce; fbreak; }; 'nLl;' => { output->first = 0x22d8; output->second = 0x0338; fbreak; }; 'nLt;' => { output->first = 0x226a; output->second = 0x20d2; fbreak; }; 'nLtv;' => { output->first = 0x226a; output->second = 0x0338; fbreak; }; 'nRightarrow;' => { output->first = 0x21cf; fbreak; }; 'nVDash;' => { output->first = 0x22af; fbreak; }; 'nVdash;' => { output->first = 0x22ae; fbreak; }; 'nabla;' => { output->first = 0x2207; fbreak; }; 'nacute;' => { output->first = 0x0144; fbreak; }; 'nang;' => { output->first = 0x2220; output->second = 0x20d2; fbreak; }; 'nap;' => { output->first = 0x2249; fbreak; }; 'napE;' => { output->first = 0x2a70; output->second = 0x0338; fbreak; }; 'napid;' => { output->first = 0x224b; output->second = 0x0338; fbreak; }; 'napos;' => { output->first = 0x0149; fbreak; }; 'napprox;' => { output->first = 0x2249; fbreak; }; 'natur;' => { output->first = 0x266e; fbreak; }; 'natural;' => { output->first = 0x266e; fbreak; }; 'naturals;' => { output->first = 0x2115; fbreak; }; 'nbsp;' => { output->first = 0xa0; fbreak; }; 'nbsp' => { output->first = 0xa0; fbreak; }; 'nbump;' => { output->first = 0x224e; output->second = 0x0338; fbreak; }; 'nbumpe;' => { output->first = 0x224f; output->second = 0x0338; fbreak; }; 'ncap;' => { output->first = 0x2a43; fbreak; }; 'ncaron;' => { output->first = 0x0148; fbreak; }; 'ncedil;' => { output->first = 0x0146; fbreak; }; 'ncong;' => { output->first = 0x2247; fbreak; }; 'ncongdot;' => { output->first = 0x2a6d; output->second = 0x0338; fbreak; }; 'ncup;' => { output->first = 0x2a42; fbreak; }; 'ncy;' => { output->first = 0x043d; fbreak; }; 'ndash;' => { output->first = 0x2013; fbreak; }; 'ne;' => { output->first = 0x2260; fbreak; }; 'neArr;' => { output->first = 0x21d7; fbreak; }; 'nearhk;' => { output->first = 0x2924; fbreak; }; 'nearr;' => { output->first = 0x2197; fbreak; }; 'nearrow;' => { output->first = 0x2197; fbreak; }; 'nedot;' => { output->first = 0x2250; output->second = 0x0338; fbreak; }; 'nequiv;' => { output->first = 0x2262; fbreak; }; 'nesear;' => { output->first = 0x2928; fbreak; }; 'nesim;' => { output->first = 0x2242; output->second = 0x0338; fbreak; }; 'nexist;' => { output->first = 0x2204; fbreak; }; 'nexists;' => { output->first = 0x2204; fbreak; }; 'nfr;' => { output->first = 0x0001d52b; fbreak; }; 'ngE;' => { output->first = 0x2267; output->second = 0x0338; fbreak; }; 'nge;' => { output->first = 0x2271; fbreak; }; 'ngeq;' => { output->first = 0x2271; fbreak; }; 'ngeqq;' => { output->first = 0x2267; output->second = 0x0338; fbreak; }; 'ngeqslant;' => { output->first = 0x2a7e; output->second = 0x0338; fbreak; }; 'nges;' => { output->first = 0x2a7e; output->second = 0x0338; fbreak; }; 'ngsim;' => { output->first = 0x2275; fbreak; }; 'ngt;' => { output->first = 0x226f; fbreak; }; 'ngtr;' => { output->first = 0x226f; fbreak; }; 'nhArr;' => { output->first = 0x21ce; fbreak; }; 'nharr;' => { output->first = 0x21ae; fbreak; }; 'nhpar;' => { output->first = 0x2af2; fbreak; }; 'ni;' => { output->first = 0x220b; fbreak; }; 'nis;' => { output->first = 0x22fc; fbreak; }; 'nisd;' => { output->first = 0x22fa; fbreak; }; 'niv;' => { output->first = 0x220b; fbreak; }; 'njcy;' => { output->first = 0x045a; fbreak; }; 'nlArr;' => { output->first = 0x21cd; fbreak; }; 'nlE;' => { output->first = 0x2266; output->second = 0x0338; fbreak; }; 'nlarr;' => { output->first = 0x219a; fbreak; }; 'nldr;' => { output->first = 0x2025; fbreak; }; 'nle;' => { output->first = 0x2270; fbreak; }; 'nleftarrow;' => { output->first = 0x219a; fbreak; }; 'nleftrightarrow;' => { output->first = 0x21ae; fbreak; }; 'nleq;' => { output->first = 0x2270; fbreak; }; 'nleqq;' => { output->first = 0x2266; output->second = 0x0338; fbreak; }; 'nleqslant;' => { output->first = 0x2a7d; output->second = 0x0338; fbreak; }; 'nles;' => { output->first = 0x2a7d; output->second = 0x0338; fbreak; }; 'nless;' => { output->first = 0x226e; fbreak; }; 'nlsim;' => { output->first = 0x2274; fbreak; }; 'nlt;' => { output->first = 0x226e; fbreak; }; 'nltri;' => { output->first = 0x22ea; fbreak; }; 'nltrie;' => { output->first = 0x22ec; fbreak; }; 'nmid;' => { output->first = 0x2224; fbreak; }; 'nopf;' => { output->first = 0x0001d55f; fbreak; }; 'not;' => { output->first = 0xac; fbreak; }; 'notin;' => { output->first = 0x2209; fbreak; }; 'notinE;' => { output->first = 0x22f9; output->second = 0x0338; fbreak; }; 'notindot;' => { output->first = 0x22f5; output->second = 0x0338; fbreak; }; 'notinva;' => { output->first = 0x2209; fbreak; }; 'notinvb;' => { output->first = 0x22f7; fbreak; }; 'notinvc;' => { output->first = 0x22f6; fbreak; }; 'notni;' => { output->first = 0x220c; fbreak; }; 'notniva;' => { output->first = 0x220c; fbreak; }; 'notnivb;' => { output->first = 0x22fe; fbreak; }; 'notnivc;' => { output->first = 0x22fd; fbreak; }; 'not' => { output->first = 0xac; fbreak; }; 'npar;' => { output->first = 0x2226; fbreak; }; 'nparallel;' => { output->first = 0x2226; fbreak; }; 'nparsl;' => { output->first = 0x2afd; output->second = 0x20e5; fbreak; }; 'npart;' => { output->first = 0x2202; output->second = 0x0338; fbreak; }; 'npolint;' => { output->first = 0x2a14; fbreak; }; 'npr;' => { output->first = 0x2280; fbreak; }; 'nprcue;' => { output->first = 0x22e0; fbreak; }; 'npre;' => { output->first = 0x2aaf; output->second = 0x0338; fbreak; }; 'nprec;' => { output->first = 0x2280; fbreak; }; 'npreceq;' => { output->first = 0x2aaf; output->second = 0x0338; fbreak; }; 'nrArr;' => { output->first = 0x21cf; fbreak; }; 'nrarr;' => { output->first = 0x219b; fbreak; }; 'nrarrc;' => { output->first = 0x2933; output->second = 0x0338; fbreak; }; 'nrarrw;' => { output->first = 0x219d; output->second = 0x0338; fbreak; }; 'nrightarrow;' => { output->first = 0x219b; fbreak; }; 'nrtri;' => { output->first = 0x22eb; fbreak; }; 'nrtrie;' => { output->first = 0x22ed; fbreak; }; 'nsc;' => { output->first = 0x2281; fbreak; }; 'nsccue;' => { output->first = 0x22e1; fbreak; }; 'nsce;' => { output->first = 0x2ab0; output->second = 0x0338; fbreak; }; 'nscr;' => { output->first = 0x0001d4c3; fbreak; }; 'nshortmid;' => { output->first = 0x2224; fbreak; }; 'nshortparallel;' => { output->first = 0x2226; fbreak; }; 'nsim;' => { output->first = 0x2241; fbreak; }; 'nsime;' => { output->first = 0x2244; fbreak; }; 'nsimeq;' => { output->first = 0x2244; fbreak; }; 'nsmid;' => { output->first = 0x2224; fbreak; }; 'nspar;' => { output->first = 0x2226; fbreak; }; 'nsqsube;' => { output->first = 0x22e2; fbreak; }; 'nsqsupe;' => { output->first = 0x22e3; fbreak; }; 'nsub;' => { output->first = 0x2284; fbreak; }; 'nsubE;' => { output->first = 0x2ac5; output->second = 0x0338; fbreak; }; 'nsube;' => { output->first = 0x2288; fbreak; }; 'nsubset;' => { output->first = 0x2282; output->second = 0x20d2; fbreak; }; 'nsubseteq;' => { output->first = 0x2288; fbreak; }; 'nsubseteqq;' => { output->first = 0x2ac5; output->second = 0x0338; fbreak; }; 'nsucc;' => { output->first = 0x2281; fbreak; }; 'nsucceq;' => { output->first = 0x2ab0; output->second = 0x0338; fbreak; }; 'nsup;' => { output->first = 0x2285; fbreak; }; 'nsupE;' => { output->first = 0x2ac6; output->second = 0x0338; fbreak; }; 'nsupe;' => { output->first = 0x2289; fbreak; }; 'nsupset;' => { output->first = 0x2283; output->second = 0x20d2; fbreak; }; 'nsupseteq;' => { output->first = 0x2289; fbreak; }; 'nsupseteqq;' => { output->first = 0x2ac6; output->second = 0x0338; fbreak; }; 'ntgl;' => { output->first = 0x2279; fbreak; }; 'ntilde;' => { output->first = 0xf1; fbreak; }; 'ntilde' => { output->first = 0xf1; fbreak; }; 'ntlg;' => { output->first = 0x2278; fbreak; }; 'ntriangleleft;' => { output->first = 0x22ea; fbreak; }; 'ntrianglelefteq;' => { output->first = 0x22ec; fbreak; }; 'ntriangleright;' => { output->first = 0x22eb; fbreak; }; 'ntrianglerighteq;' => { output->first = 0x22ed; fbreak; }; 'nu;' => { output->first = 0x03bd; fbreak; }; 'num;' => { output->first = 0x23; fbreak; }; 'numero;' => { output->first = 0x2116; fbreak; }; 'numsp;' => { output->first = 0x2007; fbreak; }; 'nvDash;' => { output->first = 0x22ad; fbreak; }; 'nvHarr;' => { output->first = 0x2904; fbreak; }; 'nvap;' => { output->first = 0x224d; output->second = 0x20d2; fbreak; }; 'nvdash;' => { output->first = 0x22ac; fbreak; }; 'nvge;' => { output->first = 0x2265; output->second = 0x20d2; fbreak; }; 'nvgt;' => { output->first = 0x3e; output->second = 0x20d2; fbreak; }; 'nvinfin;' => { output->first = 0x29de; fbreak; }; 'nvlArr;' => { output->first = 0x2902; fbreak; }; 'nvle;' => { output->first = 0x2264; output->second = 0x20d2; fbreak; }; 'nvlt;' => { output->first = 0x3c; output->second = 0x20d2; fbreak; }; 'nvltrie;' => { output->first = 0x22b4; output->second = 0x20d2; fbreak; }; 'nvrArr;' => { output->first = 0x2903; fbreak; }; 'nvrtrie;' => { output->first = 0x22b5; output->second = 0x20d2; fbreak; }; 'nvsim;' => { output->first = 0x223c; output->second = 0x20d2; fbreak; }; 'nwArr;' => { output->first = 0x21d6; fbreak; }; 'nwarhk;' => { output->first = 0x2923; fbreak; }; 'nwarr;' => { output->first = 0x2196; fbreak; }; 'nwarrow;' => { output->first = 0x2196; fbreak; }; 'nwnear;' => { output->first = 0x2927; fbreak; }; 'oS;' => { output->first = 0x24c8; fbreak; }; 'oacute;' => { output->first = 0xf3; fbreak; }; 'oacute' => { output->first = 0xf3; fbreak; }; 'oast;' => { output->first = 0x229b; fbreak; }; 'ocir;' => { output->first = 0x229a; fbreak; }; 'ocirc;' => { output->first = 0xf4; fbreak; }; 'ocirc' => { output->first = 0xf4; fbreak; }; 'ocy;' => { output->first = 0x043e; fbreak; }; 'odash;' => { output->first = 0x229d; fbreak; }; 'odblac;' => { output->first = 0x0151; fbreak; }; 'odiv;' => { output->first = 0x2a38; fbreak; }; 'odot;' => { output->first = 0x2299; fbreak; }; 'odsold;' => { output->first = 0x29bc; fbreak; }; 'oelig;' => { output->first = 0x0153; fbreak; }; 'ofcir;' => { output->first = 0x29bf; fbreak; }; 'ofr;' => { output->first = 0x0001d52c; fbreak; }; 'ogon;' => { output->first = 0x02db; fbreak; }; 'ograve;' => { output->first = 0xf2; fbreak; }; 'ograve' => { output->first = 0xf2; fbreak; }; 'ogt;' => { output->first = 0x29c1; fbreak; }; 'ohbar;' => { output->first = 0x29b5; fbreak; }; 'ohm;' => { output->first = 0x03a9; fbreak; }; 'oint;' => { output->first = 0x222e; fbreak; }; 'olarr;' => { output->first = 0x21ba; fbreak; }; 'olcir;' => { output->first = 0x29be; fbreak; }; 'olcross;' => { output->first = 0x29bb; fbreak; }; 'oline;' => { output->first = 0x203e; fbreak; }; 'olt;' => { output->first = 0x29c0; fbreak; }; 'omacr;' => { output->first = 0x014d; fbreak; }; 'omega;' => { output->first = 0x03c9; fbreak; }; 'omicron;' => { output->first = 0x03bf; fbreak; }; 'omid;' => { output->first = 0x29b6; fbreak; }; 'ominus;' => { output->first = 0x2296; fbreak; }; 'oopf;' => { output->first = 0x0001d560; fbreak; }; 'opar;' => { output->first = 0x29b7; fbreak; }; 'operp;' => { output->first = 0x29b9; fbreak; }; 'oplus;' => { output->first = 0x2295; fbreak; }; 'or;' => { output->first = 0x2228; fbreak; }; 'orarr;' => { output->first = 0x21bb; fbreak; }; 'ord;' => { output->first = 0x2a5d; fbreak; }; 'order;' => { output->first = 0x2134; fbreak; }; 'orderof;' => { output->first = 0x2134; fbreak; }; 'ordf;' => { output->first = 0xaa; fbreak; }; 'ordf' => { output->first = 0xaa; fbreak; }; 'ordm;' => { output->first = 0xba; fbreak; }; 'ordm' => { output->first = 0xba; fbreak; }; 'origof;' => { output->first = 0x22b6; fbreak; }; 'oror;' => { output->first = 0x2a56; fbreak; }; 'orslope;' => { output->first = 0x2a57; fbreak; }; 'orv;' => { output->first = 0x2a5b; fbreak; }; 'oscr;' => { output->first = 0x2134; fbreak; }; 'oslash;' => { output->first = 0xf8; fbreak; }; 'oslash' => { output->first = 0xf8; fbreak; }; 'osol;' => { output->first = 0x2298; fbreak; }; 'otilde;' => { output->first = 0xf5; fbreak; }; 'otilde' => { output->first = 0xf5; fbreak; }; 'otimes;' => { output->first = 0x2297; fbreak; }; 'otimesas;' => { output->first = 0x2a36; fbreak; }; 'ouml;' => { output->first = 0xf6; fbreak; }; 'ouml' => { output->first = 0xf6; fbreak; }; 'ovbar;' => { output->first = 0x233d; fbreak; }; 'par;' => { output->first = 0x2225; fbreak; }; 'para;' => { output->first = 0xb6; fbreak; }; 'para' => { output->first = 0xb6; fbreak; }; 'parallel;' => { output->first = 0x2225; fbreak; }; 'parsim;' => { output->first = 0x2af3; fbreak; }; 'parsl;' => { output->first = 0x2afd; fbreak; }; 'part;' => { output->first = 0x2202; fbreak; }; 'pcy;' => { output->first = 0x043f; fbreak; }; 'percnt;' => { output->first = 0x25; fbreak; }; 'period;' => { output->first = 0x2e; fbreak; }; 'permil;' => { output->first = 0x2030; fbreak; }; 'perp;' => { output->first = 0x22a5; fbreak; }; 'pertenk;' => { output->first = 0x2031; fbreak; }; 'pfr;' => { output->first = 0x0001d52d; fbreak; }; 'phi;' => { output->first = 0x03c6; fbreak; }; 'phiv;' => { output->first = 0x03d5; fbreak; }; 'phmmat;' => { output->first = 0x2133; fbreak; }; 'phone;' => { output->first = 0x260e; fbreak; }; 'pi;' => { output->first = 0x03c0; fbreak; }; 'pitchfork;' => { output->first = 0x22d4; fbreak; }; 'piv;' => { output->first = 0x03d6; fbreak; }; 'planck;' => { output->first = 0x210f; fbreak; }; 'planckh;' => { output->first = 0x210e; fbreak; }; 'plankv;' => { output->first = 0x210f; fbreak; }; 'plus;' => { output->first = 0x2b; fbreak; }; 'plusacir;' => { output->first = 0x2a23; fbreak; }; 'plusb;' => { output->first = 0x229e; fbreak; }; 'pluscir;' => { output->first = 0x2a22; fbreak; }; 'plusdo;' => { output->first = 0x2214; fbreak; }; 'plusdu;' => { output->first = 0x2a25; fbreak; }; 'pluse;' => { output->first = 0x2a72; fbreak; }; 'plusmn;' => { output->first = 0xb1; fbreak; }; 'plusmn' => { output->first = 0xb1; fbreak; }; 'plussim;' => { output->first = 0x2a26; fbreak; }; 'plustwo;' => { output->first = 0x2a27; fbreak; }; 'pm;' => { output->first = 0xb1; fbreak; }; 'pointint;' => { output->first = 0x2a15; fbreak; }; 'popf;' => { output->first = 0x0001d561; fbreak; }; 'pound;' => { output->first = 0xa3; fbreak; }; 'pound' => { output->first = 0xa3; fbreak; }; 'pr;' => { output->first = 0x227a; fbreak; }; 'prE;' => { output->first = 0x2ab3; fbreak; }; 'prap;' => { output->first = 0x2ab7; fbreak; }; 'prcue;' => { output->first = 0x227c; fbreak; }; 'pre;' => { output->first = 0x2aaf; fbreak; }; 'prec;' => { output->first = 0x227a; fbreak; }; 'precapprox;' => { output->first = 0x2ab7; fbreak; }; 'preccurlyeq;' => { output->first = 0x227c; fbreak; }; 'preceq;' => { output->first = 0x2aaf; fbreak; }; 'precnapprox;' => { output->first = 0x2ab9; fbreak; }; 'precneqq;' => { output->first = 0x2ab5; fbreak; }; 'precnsim;' => { output->first = 0x22e8; fbreak; }; 'precsim;' => { output->first = 0x227e; fbreak; }; 'prime;' => { output->first = 0x2032; fbreak; }; 'primes;' => { output->first = 0x2119; fbreak; }; 'prnE;' => { output->first = 0x2ab5; fbreak; }; 'prnap;' => { output->first = 0x2ab9; fbreak; }; 'prnsim;' => { output->first = 0x22e8; fbreak; }; 'prod;' => { output->first = 0x220f; fbreak; }; 'profalar;' => { output->first = 0x232e; fbreak; }; 'profline;' => { output->first = 0x2312; fbreak; }; 'profsurf;' => { output->first = 0x2313; fbreak; }; 'prop;' => { output->first = 0x221d; fbreak; }; 'propto;' => { output->first = 0x221d; fbreak; }; 'prsim;' => { output->first = 0x227e; fbreak; }; 'prurel;' => { output->first = 0x22b0; fbreak; }; 'pscr;' => { output->first = 0x0001d4c5; fbreak; }; 'psi;' => { output->first = 0x03c8; fbreak; }; 'puncsp;' => { output->first = 0x2008; fbreak; }; 'qfr;' => { output->first = 0x0001d52e; fbreak; }; 'qint;' => { output->first = 0x2a0c; fbreak; }; 'qopf;' => { output->first = 0x0001d562; fbreak; }; 'qprime;' => { output->first = 0x2057; fbreak; }; 'qscr;' => { output->first = 0x0001d4c6; fbreak; }; 'quaternions;' => { output->first = 0x210d; fbreak; }; 'quatint;' => { output->first = 0x2a16; fbreak; }; 'quest;' => { output->first = 0x3f; fbreak; }; 'questeq;' => { output->first = 0x225f; fbreak; }; 'quot;' => { output->first = 0x22; fbreak; }; 'quot' => { output->first = 0x22; fbreak; }; 'rAarr;' => { output->first = 0x21db; fbreak; }; 'rArr;' => { output->first = 0x21d2; fbreak; }; 'rAtail;' => { output->first = 0x291c; fbreak; }; 'rBarr;' => { output->first = 0x290f; fbreak; }; 'rHar;' => { output->first = 0x2964; fbreak; }; 'race;' => { output->first = 0x223d; output->second = 0x0331; fbreak; }; 'racute;' => { output->first = 0x0155; fbreak; }; 'radic;' => { output->first = 0x221a; fbreak; }; 'raemptyv;' => { output->first = 0x29b3; fbreak; }; 'rang;' => { output->first = 0x27e9; fbreak; }; 'rangd;' => { output->first = 0x2992; fbreak; }; 'range;' => { output->first = 0x29a5; fbreak; }; 'rangle;' => { output->first = 0x27e9; fbreak; }; 'raquo;' => { output->first = 0xbb; fbreak; }; 'raquo' => { output->first = 0xbb; fbreak; }; 'rarr;' => { output->first = 0x2192; fbreak; }; 'rarrap;' => { output->first = 0x2975; fbreak; }; 'rarrb;' => { output->first = 0x21e5; fbreak; }; 'rarrbfs;' => { output->first = 0x2920; fbreak; }; 'rarrc;' => { output->first = 0x2933; fbreak; }; 'rarrfs;' => { output->first = 0x291e; fbreak; }; 'rarrhk;' => { output->first = 0x21aa; fbreak; }; 'rarrlp;' => { output->first = 0x21ac; fbreak; }; 'rarrpl;' => { output->first = 0x2945; fbreak; }; 'rarrsim;' => { output->first = 0x2974; fbreak; }; 'rarrtl;' => { output->first = 0x21a3; fbreak; }; 'rarrw;' => { output->first = 0x219d; fbreak; }; 'ratail;' => { output->first = 0x291a; fbreak; }; 'ratio;' => { output->first = 0x2236; fbreak; }; 'rationals;' => { output->first = 0x211a; fbreak; }; 'rbarr;' => { output->first = 0x290d; fbreak; }; 'rbbrk;' => { output->first = 0x2773; fbreak; }; 'rbrace;' => { output->first = 0x7d; fbreak; }; 'rbrack;' => { output->first = 0x5d; fbreak; }; 'rbrke;' => { output->first = 0x298c; fbreak; }; 'rbrksld;' => { output->first = 0x298e; fbreak; }; 'rbrkslu;' => { output->first = 0x2990; fbreak; }; 'rcaron;' => { output->first = 0x0159; fbreak; }; 'rcedil;' => { output->first = 0x0157; fbreak; }; 'rceil;' => { output->first = 0x2309; fbreak; }; 'rcub;' => { output->first = 0x7d; fbreak; }; 'rcy;' => { output->first = 0x0440; fbreak; }; 'rdca;' => { output->first = 0x2937; fbreak; }; 'rdldhar;' => { output->first = 0x2969; fbreak; }; 'rdquo;' => { output->first = 0x201d; fbreak; }; 'rdquor;' => { output->first = 0x201d; fbreak; }; 'rdsh;' => { output->first = 0x21b3; fbreak; }; 'real;' => { output->first = 0x211c; fbreak; }; 'realine;' => { output->first = 0x211b; fbreak; }; 'realpart;' => { output->first = 0x211c; fbreak; }; 'reals;' => { output->first = 0x211d; fbreak; }; 'rect;' => { output->first = 0x25ad; fbreak; }; 'reg;' => { output->first = 0xae; fbreak; }; 'reg' => { output->first = 0xae; fbreak; }; 'rfisht;' => { output->first = 0x297d; fbreak; }; 'rfloor;' => { output->first = 0x230b; fbreak; }; 'rfr;' => { output->first = 0x0001d52f; fbreak; }; 'rhard;' => { output->first = 0x21c1; fbreak; }; 'rharu;' => { output->first = 0x21c0; fbreak; }; 'rharul;' => { output->first = 0x296c; fbreak; }; 'rho;' => { output->first = 0x03c1; fbreak; }; 'rhov;' => { output->first = 0x03f1; fbreak; }; 'rightarrow;' => { output->first = 0x2192; fbreak; }; 'rightarrowtail;' => { output->first = 0x21a3; fbreak; }; 'rightharpoondown;' => { output->first = 0x21c1; fbreak; }; 'rightharpoonup;' => { output->first = 0x21c0; fbreak; }; 'rightleftarrows;' => { output->first = 0x21c4; fbreak; }; 'rightleftharpoons;' => { output->first = 0x21cc; fbreak; }; 'rightrightarrows;' => { output->first = 0x21c9; fbreak; }; 'rightsquigarrow;' => { output->first = 0x219d; fbreak; }; 'rightthreetimes;' => { output->first = 0x22cc; fbreak; }; 'ring;' => { output->first = 0x02da; fbreak; }; 'risingdotseq;' => { output->first = 0x2253; fbreak; }; 'rlarr;' => { output->first = 0x21c4; fbreak; }; 'rlhar;' => { output->first = 0x21cc; fbreak; }; 'rlm;' => { output->first = 0x200f; fbreak; }; 'rmoust;' => { output->first = 0x23b1; fbreak; }; 'rmoustache;' => { output->first = 0x23b1; fbreak; }; 'rnmid;' => { output->first = 0x2aee; fbreak; }; 'roang;' => { output->first = 0x27ed; fbreak; }; 'roarr;' => { output->first = 0x21fe; fbreak; }; 'robrk;' => { output->first = 0x27e7; fbreak; }; 'ropar;' => { output->first = 0x2986; fbreak; }; 'ropf;' => { output->first = 0x0001d563; fbreak; }; 'roplus;' => { output->first = 0x2a2e; fbreak; }; 'rotimes;' => { output->first = 0x2a35; fbreak; }; 'rpar;' => { output->first = 0x29; fbreak; }; 'rpargt;' => { output->first = 0x2994; fbreak; }; 'rppolint;' => { output->first = 0x2a12; fbreak; }; 'rrarr;' => { output->first = 0x21c9; fbreak; }; 'rsaquo;' => { output->first = 0x203a; fbreak; }; 'rscr;' => { output->first = 0x0001d4c7; fbreak; }; 'rsh;' => { output->first = 0x21b1; fbreak; }; 'rsqb;' => { output->first = 0x5d; fbreak; }; 'rsquo;' => { output->first = 0x2019; fbreak; }; 'rsquor;' => { output->first = 0x2019; fbreak; }; 'rthree;' => { output->first = 0x22cc; fbreak; }; 'rtimes;' => { output->first = 0x22ca; fbreak; }; 'rtri;' => { output->first = 0x25b9; fbreak; }; 'rtrie;' => { output->first = 0x22b5; fbreak; }; 'rtrif;' => { output->first = 0x25b8; fbreak; }; 'rtriltri;' => { output->first = 0x29ce; fbreak; }; 'ruluhar;' => { output->first = 0x2968; fbreak; }; 'rx;' => { output->first = 0x211e; fbreak; }; 'sacute;' => { output->first = 0x015b; fbreak; }; 'sbquo;' => { output->first = 0x201a; fbreak; }; 'sc;' => { output->first = 0x227b; fbreak; }; 'scE;' => { output->first = 0x2ab4; fbreak; }; 'scap;' => { output->first = 0x2ab8; fbreak; }; 'scaron;' => { output->first = 0x0161; fbreak; }; 'sccue;' => { output->first = 0x227d; fbreak; }; 'sce;' => { output->first = 0x2ab0; fbreak; }; 'scedil;' => { output->first = 0x015f; fbreak; }; 'scirc;' => { output->first = 0x015d; fbreak; }; 'scnE;' => { output->first = 0x2ab6; fbreak; }; 'scnap;' => { output->first = 0x2aba; fbreak; }; 'scnsim;' => { output->first = 0x22e9; fbreak; }; 'scpolint;' => { output->first = 0x2a13; fbreak; }; 'scsim;' => { output->first = 0x227f; fbreak; }; 'scy;' => { output->first = 0x0441; fbreak; }; 'sdot;' => { output->first = 0x22c5; fbreak; }; 'sdotb;' => { output->first = 0x22a1; fbreak; }; 'sdote;' => { output->first = 0x2a66; fbreak; }; 'seArr;' => { output->first = 0x21d8; fbreak; }; 'searhk;' => { output->first = 0x2925; fbreak; }; 'searr;' => { output->first = 0x2198; fbreak; }; 'searrow;' => { output->first = 0x2198; fbreak; }; 'sect;' => { output->first = 0xa7; fbreak; }; 'sect' => { output->first = 0xa7; fbreak; }; 'semi;' => { output->first = 0x3b; fbreak; }; 'seswar;' => { output->first = 0x2929; fbreak; }; 'setminus;' => { output->first = 0x2216; fbreak; }; 'setmn;' => { output->first = 0x2216; fbreak; }; 'sext;' => { output->first = 0x2736; fbreak; }; 'sfr;' => { output->first = 0x0001d530; fbreak; }; 'sfrown;' => { output->first = 0x2322; fbreak; }; 'sharp;' => { output->first = 0x266f; fbreak; }; 'shchcy;' => { output->first = 0x0449; fbreak; }; 'shcy;' => { output->first = 0x0448; fbreak; }; 'shortmid;' => { output->first = 0x2223; fbreak; }; 'shortparallel;' => { output->first = 0x2225; fbreak; }; 'shy;' => { output->first = 0xad; fbreak; }; 'shy' => { output->first = 0xad; fbreak; }; 'sigma;' => { output->first = 0x03c3; fbreak; }; 'sigmaf;' => { output->first = 0x03c2; fbreak; }; 'sigmav;' => { output->first = 0x03c2; fbreak; }; 'sim;' => { output->first = 0x223c; fbreak; }; 'simdot;' => { output->first = 0x2a6a; fbreak; }; 'sime;' => { output->first = 0x2243; fbreak; }; 'simeq;' => { output->first = 0x2243; fbreak; }; 'simg;' => { output->first = 0x2a9e; fbreak; }; 'simgE;' => { output->first = 0x2aa0; fbreak; }; 'siml;' => { output->first = 0x2a9d; fbreak; }; 'simlE;' => { output->first = 0x2a9f; fbreak; }; 'simne;' => { output->first = 0x2246; fbreak; }; 'simplus;' => { output->first = 0x2a24; fbreak; }; 'simrarr;' => { output->first = 0x2972; fbreak; }; 'slarr;' => { output->first = 0x2190; fbreak; }; 'smallsetminus;' => { output->first = 0x2216; fbreak; }; 'smashp;' => { output->first = 0x2a33; fbreak; }; 'smeparsl;' => { output->first = 0x29e4; fbreak; }; 'smid;' => { output->first = 0x2223; fbreak; }; 'smile;' => { output->first = 0x2323; fbreak; }; 'smt;' => { output->first = 0x2aaa; fbreak; }; 'smte;' => { output->first = 0x2aac; fbreak; }; 'smtes;' => { output->first = 0x2aac; output->second = 0xfe00; fbreak; }; 'softcy;' => { output->first = 0x044c; fbreak; }; 'sol;' => { output->first = 0x2f; fbreak; }; 'solb;' => { output->first = 0x29c4; fbreak; }; 'solbar;' => { output->first = 0x233f; fbreak; }; 'sopf;' => { output->first = 0x0001d564; fbreak; }; 'spades;' => { output->first = 0x2660; fbreak; }; 'spadesuit;' => { output->first = 0x2660; fbreak; }; 'spar;' => { output->first = 0x2225; fbreak; }; 'sqcap;' => { output->first = 0x2293; fbreak; }; 'sqcaps;' => { output->first = 0x2293; output->second = 0xfe00; fbreak; }; 'sqcup;' => { output->first = 0x2294; fbreak; }; 'sqcups;' => { output->first = 0x2294; output->second = 0xfe00; fbreak; }; 'sqsub;' => { output->first = 0x228f; fbreak; }; 'sqsube;' => { output->first = 0x2291; fbreak; }; 'sqsubset;' => { output->first = 0x228f; fbreak; }; 'sqsubseteq;' => { output->first = 0x2291; fbreak; }; 'sqsup;' => { output->first = 0x2290; fbreak; }; 'sqsupe;' => { output->first = 0x2292; fbreak; }; 'sqsupset;' => { output->first = 0x2290; fbreak; }; 'sqsupseteq;' => { output->first = 0x2292; fbreak; }; 'squ;' => { output->first = 0x25a1; fbreak; }; 'square;' => { output->first = 0x25a1; fbreak; }; 'squarf;' => { output->first = 0x25aa; fbreak; }; 'squf;' => { output->first = 0x25aa; fbreak; }; 'srarr;' => { output->first = 0x2192; fbreak; }; 'sscr;' => { output->first = 0x0001d4c8; fbreak; }; 'ssetmn;' => { output->first = 0x2216; fbreak; }; 'ssmile;' => { output->first = 0x2323; fbreak; }; 'sstarf;' => { output->first = 0x22c6; fbreak; }; 'star;' => { output->first = 0x2606; fbreak; }; 'starf;' => { output->first = 0x2605; fbreak; }; 'straightepsilon;' => { output->first = 0x03f5; fbreak; }; 'straightphi;' => { output->first = 0x03d5; fbreak; }; 'strns;' => { output->first = 0xaf; fbreak; }; 'sub;' => { output->first = 0x2282; fbreak; }; 'subE;' => { output->first = 0x2ac5; fbreak; }; 'subdot;' => { output->first = 0x2abd; fbreak; }; 'sube;' => { output->first = 0x2286; fbreak; }; 'subedot;' => { output->first = 0x2ac3; fbreak; }; 'submult;' => { output->first = 0x2ac1; fbreak; }; 'subnE;' => { output->first = 0x2acb; fbreak; }; 'subne;' => { output->first = 0x228a; fbreak; }; 'subplus;' => { output->first = 0x2abf; fbreak; }; 'subrarr;' => { output->first = 0x2979; fbreak; }; 'subset;' => { output->first = 0x2282; fbreak; }; 'subseteq;' => { output->first = 0x2286; fbreak; }; 'subseteqq;' => { output->first = 0x2ac5; fbreak; }; 'subsetneq;' => { output->first = 0x228a; fbreak; }; 'subsetneqq;' => { output->first = 0x2acb; fbreak; }; 'subsim;' => { output->first = 0x2ac7; fbreak; }; 'subsub;' => { output->first = 0x2ad5; fbreak; }; 'subsup;' => { output->first = 0x2ad3; fbreak; }; 'succ;' => { output->first = 0x227b; fbreak; }; 'succapprox;' => { output->first = 0x2ab8; fbreak; }; 'succcurlyeq;' => { output->first = 0x227d; fbreak; }; 'succeq;' => { output->first = 0x2ab0; fbreak; }; 'succnapprox;' => { output->first = 0x2aba; fbreak; }; 'succneqq;' => { output->first = 0x2ab6; fbreak; }; 'succnsim;' => { output->first = 0x22e9; fbreak; }; 'succsim;' => { output->first = 0x227f; fbreak; }; 'sum;' => { output->first = 0x2211; fbreak; }; 'sung;' => { output->first = 0x266a; fbreak; }; 'sup1;' => { output->first = 0xb9; fbreak; }; 'sup1' => { output->first = 0xb9; fbreak; }; 'sup2;' => { output->first = 0xb2; fbreak; }; 'sup2' => { output->first = 0xb2; fbreak; }; 'sup3;' => { output->first = 0xb3; fbreak; }; 'sup3' => { output->first = 0xb3; fbreak; }; 'sup;' => { output->first = 0x2283; fbreak; }; 'supE;' => { output->first = 0x2ac6; fbreak; }; 'supdot;' => { output->first = 0x2abe; fbreak; }; 'supdsub;' => { output->first = 0x2ad8; fbreak; }; 'supe;' => { output->first = 0x2287; fbreak; }; 'supedot;' => { output->first = 0x2ac4; fbreak; }; 'suphsol;' => { output->first = 0x27c9; fbreak; }; 'suphsub;' => { output->first = 0x2ad7; fbreak; }; 'suplarr;' => { output->first = 0x297b; fbreak; }; 'supmult;' => { output->first = 0x2ac2; fbreak; }; 'supnE;' => { output->first = 0x2acc; fbreak; }; 'supne;' => { output->first = 0x228b; fbreak; }; 'supplus;' => { output->first = 0x2ac0; fbreak; }; 'supset;' => { output->first = 0x2283; fbreak; }; 'supseteq;' => { output->first = 0x2287; fbreak; }; 'supseteqq;' => { output->first = 0x2ac6; fbreak; }; 'supsetneq;' => { output->first = 0x228b; fbreak; }; 'supsetneqq;' => { output->first = 0x2acc; fbreak; }; 'supsim;' => { output->first = 0x2ac8; fbreak; }; 'supsub;' => { output->first = 0x2ad4; fbreak; }; 'supsup;' => { output->first = 0x2ad6; fbreak; }; 'swArr;' => { output->first = 0x21d9; fbreak; }; 'swarhk;' => { output->first = 0x2926; fbreak; }; 'swarr;' => { output->first = 0x2199; fbreak; }; 'swarrow;' => { output->first = 0x2199; fbreak; }; 'swnwar;' => { output->first = 0x292a; fbreak; }; 'szlig;' => { output->first = 0xdf; fbreak; }; 'szlig' => { output->first = 0xdf; fbreak; }; 'target;' => { output->first = 0x2316; fbreak; }; 'tau;' => { output->first = 0x03c4; fbreak; }; 'tbrk;' => { output->first = 0x23b4; fbreak; }; 'tcaron;' => { output->first = 0x0165; fbreak; }; 'tcedil;' => { output->first = 0x0163; fbreak; }; 'tcy;' => { output->first = 0x0442; fbreak; }; 'tdot;' => { output->first = 0x20db; fbreak; }; 'telrec;' => { output->first = 0x2315; fbreak; }; 'tfr;' => { output->first = 0x0001d531; fbreak; }; 'there4;' => { output->first = 0x2234; fbreak; }; 'therefore;' => { output->first = 0x2234; fbreak; }; 'theta;' => { output->first = 0x03b8; fbreak; }; 'thetasym;' => { output->first = 0x03d1; fbreak; }; 'thetav;' => { output->first = 0x03d1; fbreak; }; 'thickapprox;' => { output->first = 0x2248; fbreak; }; 'thicksim;' => { output->first = 0x223c; fbreak; }; 'thinsp;' => { output->first = 0x2009; fbreak; }; 'thkap;' => { output->first = 0x2248; fbreak; }; 'thksim;' => { output->first = 0x223c; fbreak; }; 'thorn;' => { output->first = 0xfe; fbreak; }; 'thorn' => { output->first = 0xfe; fbreak; }; 'tilde;' => { output->first = 0x02dc; fbreak; }; 'times;' => { output->first = 0xd7; fbreak; }; 'times' => { output->first = 0xd7; fbreak; }; 'timesb;' => { output->first = 0x22a0; fbreak; }; 'timesbar;' => { output->first = 0x2a31; fbreak; }; 'timesd;' => { output->first = 0x2a30; fbreak; }; 'tint;' => { output->first = 0x222d; fbreak; }; 'toea;' => { output->first = 0x2928; fbreak; }; 'top;' => { output->first = 0x22a4; fbreak; }; 'topbot;' => { output->first = 0x2336; fbreak; }; 'topcir;' => { output->first = 0x2af1; fbreak; }; 'topf;' => { output->first = 0x0001d565; fbreak; }; 'topfork;' => { output->first = 0x2ada; fbreak; }; 'tosa;' => { output->first = 0x2929; fbreak; }; 'tprime;' => { output->first = 0x2034; fbreak; }; 'trade;' => { output->first = 0x2122; fbreak; }; 'triangle;' => { output->first = 0x25b5; fbreak; }; 'triangledown;' => { output->first = 0x25bf; fbreak; }; 'triangleleft;' => { output->first = 0x25c3; fbreak; }; 'trianglelefteq;' => { output->first = 0x22b4; fbreak; }; 'triangleq;' => { output->first = 0x225c; fbreak; }; 'triangleright;' => { output->first = 0x25b9; fbreak; }; 'trianglerighteq;' => { output->first = 0x22b5; fbreak; }; 'tridot;' => { output->first = 0x25ec; fbreak; }; 'trie;' => { output->first = 0x225c; fbreak; }; 'triminus;' => { output->first = 0x2a3a; fbreak; }; 'triplus;' => { output->first = 0x2a39; fbreak; }; 'trisb;' => { output->first = 0x29cd; fbreak; }; 'tritime;' => { output->first = 0x2a3b; fbreak; }; 'trpezium;' => { output->first = 0x23e2; fbreak; }; 'tscr;' => { output->first = 0x0001d4c9; fbreak; }; 'tscy;' => { output->first = 0x0446; fbreak; }; 'tshcy;' => { output->first = 0x045b; fbreak; }; 'tstrok;' => { output->first = 0x0167; fbreak; }; 'twixt;' => { output->first = 0x226c; fbreak; }; 'twoheadleftarrow;' => { output->first = 0x219e; fbreak; }; 'twoheadrightarrow;' => { output->first = 0x21a0; fbreak; }; 'uArr;' => { output->first = 0x21d1; fbreak; }; 'uHar;' => { output->first = 0x2963; fbreak; }; 'uacute;' => { output->first = 0xfa; fbreak; }; 'uacute' => { output->first = 0xfa; fbreak; }; 'uarr;' => { output->first = 0x2191; fbreak; }; 'ubrcy;' => { output->first = 0x045e; fbreak; }; 'ubreve;' => { output->first = 0x016d; fbreak; }; 'ucirc;' => { output->first = 0xfb; fbreak; }; 'ucirc' => { output->first = 0xfb; fbreak; }; 'ucy;' => { output->first = 0x0443; fbreak; }; 'udarr;' => { output->first = 0x21c5; fbreak; }; 'udblac;' => { output->first = 0x0171; fbreak; }; 'udhar;' => { output->first = 0x296e; fbreak; }; 'ufisht;' => { output->first = 0x297e; fbreak; }; 'ufr;' => { output->first = 0x0001d532; fbreak; }; 'ugrave;' => { output->first = 0xf9; fbreak; }; 'ugrave' => { output->first = 0xf9; fbreak; }; 'uharl;' => { output->first = 0x21bf; fbreak; }; 'uharr;' => { output->first = 0x21be; fbreak; }; 'uhblk;' => { output->first = 0x2580; fbreak; }; 'ulcorn;' => { output->first = 0x231c; fbreak; }; 'ulcorner;' => { output->first = 0x231c; fbreak; }; 'ulcrop;' => { output->first = 0x230f; fbreak; }; 'ultri;' => { output->first = 0x25f8; fbreak; }; 'umacr;' => { output->first = 0x016b; fbreak; }; 'uml;' => { output->first = 0xa8; fbreak; }; 'uml' => { output->first = 0xa8; fbreak; }; 'uogon;' => { output->first = 0x0173; fbreak; }; 'uopf;' => { output->first = 0x0001d566; fbreak; }; 'uparrow;' => { output->first = 0x2191; fbreak; }; 'updownarrow;' => { output->first = 0x2195; fbreak; }; 'upharpoonleft;' => { output->first = 0x21bf; fbreak; }; 'upharpoonright;' => { output->first = 0x21be; fbreak; }; 'uplus;' => { output->first = 0x228e; fbreak; }; 'upsi;' => { output->first = 0x03c5; fbreak; }; 'upsih;' => { output->first = 0x03d2; fbreak; }; 'upsilon;' => { output->first = 0x03c5; fbreak; }; 'upuparrows;' => { output->first = 0x21c8; fbreak; }; 'urcorn;' => { output->first = 0x231d; fbreak; }; 'urcorner;' => { output->first = 0x231d; fbreak; }; 'urcrop;' => { output->first = 0x230e; fbreak; }; 'uring;' => { output->first = 0x016f; fbreak; }; 'urtri;' => { output->first = 0x25f9; fbreak; }; 'uscr;' => { output->first = 0x0001d4ca; fbreak; }; 'utdot;' => { output->first = 0x22f0; fbreak; }; 'utilde;' => { output->first = 0x0169; fbreak; }; 'utri;' => { output->first = 0x25b5; fbreak; }; 'utrif;' => { output->first = 0x25b4; fbreak; }; 'uuarr;' => { output->first = 0x21c8; fbreak; }; 'uuml;' => { output->first = 0xfc; fbreak; }; 'uuml' => { output->first = 0xfc; fbreak; }; 'uwangle;' => { output->first = 0x29a7; fbreak; }; 'vArr;' => { output->first = 0x21d5; fbreak; }; 'vBar;' => { output->first = 0x2ae8; fbreak; }; 'vBarv;' => { output->first = 0x2ae9; fbreak; }; 'vDash;' => { output->first = 0x22a8; fbreak; }; 'vangrt;' => { output->first = 0x299c; fbreak; }; 'varepsilon;' => { output->first = 0x03f5; fbreak; }; 'varkappa;' => { output->first = 0x03f0; fbreak; }; 'varnothing;' => { output->first = 0x2205; fbreak; }; 'varphi;' => { output->first = 0x03d5; fbreak; }; 'varpi;' => { output->first = 0x03d6; fbreak; }; 'varpropto;' => { output->first = 0x221d; fbreak; }; 'varr;' => { output->first = 0x2195; fbreak; }; 'varrho;' => { output->first = 0x03f1; fbreak; }; 'varsigma;' => { output->first = 0x03c2; fbreak; }; 'varsubsetneq;' => { output->first = 0x228a; output->second = 0xfe00; fbreak; }; 'varsubsetneqq;' => { output->first = 0x2acb; output->second = 0xfe00; fbreak; }; 'varsupsetneq;' => { output->first = 0x228b; output->second = 0xfe00; fbreak; }; 'varsupsetneqq;' => { output->first = 0x2acc; output->second = 0xfe00; fbreak; }; 'vartheta;' => { output->first = 0x03d1; fbreak; }; 'vartriangleleft;' => { output->first = 0x22b2; fbreak; }; 'vartriangleright;' => { output->first = 0x22b3; fbreak; }; 'vcy;' => { output->first = 0x0432; fbreak; }; 'vdash;' => { output->first = 0x22a2; fbreak; }; 'vee;' => { output->first = 0x2228; fbreak; }; 'veebar;' => { output->first = 0x22bb; fbreak; }; 'veeeq;' => { output->first = 0x225a; fbreak; }; 'vellip;' => { output->first = 0x22ee; fbreak; }; 'verbar;' => { output->first = 0x7c; fbreak; }; 'vert;' => { output->first = 0x7c; fbreak; }; 'vfr;' => { output->first = 0x0001d533; fbreak; }; 'vltri;' => { output->first = 0x22b2; fbreak; }; 'vnsub;' => { output->first = 0x2282; output->second = 0x20d2; fbreak; }; 'vnsup;' => { output->first = 0x2283; output->second = 0x20d2; fbreak; }; 'vopf;' => { output->first = 0x0001d567; fbreak; }; 'vprop;' => { output->first = 0x221d; fbreak; }; 'vrtri;' => { output->first = 0x22b3; fbreak; }; 'vscr;' => { output->first = 0x0001d4cb; fbreak; }; 'vsubnE;' => { output->first = 0x2acb; output->second = 0xfe00; fbreak; }; 'vsubne;' => { output->first = 0x228a; output->second = 0xfe00; fbreak; }; 'vsupnE;' => { output->first = 0x2acc; output->second = 0xfe00; fbreak; }; 'vsupne;' => { output->first = 0x228b; output->second = 0xfe00; fbreak; }; 'vzigzag;' => { output->first = 0x299a; fbreak; }; 'wcirc;' => { output->first = 0x0175; fbreak; }; 'wedbar;' => { output->first = 0x2a5f; fbreak; }; 'wedge;' => { output->first = 0x2227; fbreak; }; 'wedgeq;' => { output->first = 0x2259; fbreak; }; 'weierp;' => { output->first = 0x2118; fbreak; }; 'wfr;' => { output->first = 0x0001d534; fbreak; }; 'wopf;' => { output->first = 0x0001d568; fbreak; }; 'wp;' => { output->first = 0x2118; fbreak; }; 'wr;' => { output->first = 0x2240; fbreak; }; 'wreath;' => { output->first = 0x2240; fbreak; }; 'wscr;' => { output->first = 0x0001d4cc; fbreak; }; 'xcap;' => { output->first = 0x22c2; fbreak; }; 'xcirc;' => { output->first = 0x25ef; fbreak; }; 'xcup;' => { output->first = 0x22c3; fbreak; }; 'xdtri;' => { output->first = 0x25bd; fbreak; }; 'xfr;' => { output->first = 0x0001d535; fbreak; }; 'xhArr;' => { output->first = 0x27fa; fbreak; }; 'xharr;' => { output->first = 0x27f7; fbreak; }; 'xi;' => { output->first = 0x03be; fbreak; }; 'xlArr;' => { output->first = 0x27f8; fbreak; }; 'xlarr;' => { output->first = 0x27f5; fbreak; }; 'xmap;' => { output->first = 0x27fc; fbreak; }; 'xnis;' => { output->first = 0x22fb; fbreak; }; 'xodot;' => { output->first = 0x2a00; fbreak; }; 'xopf;' => { output->first = 0x0001d569; fbreak; }; 'xoplus;' => { output->first = 0x2a01; fbreak; }; 'xotime;' => { output->first = 0x2a02; fbreak; }; 'xrArr;' => { output->first = 0x27f9; fbreak; }; 'xrarr;' => { output->first = 0x27f6; fbreak; }; 'xscr;' => { output->first = 0x0001d4cd; fbreak; }; 'xsqcup;' => { output->first = 0x2a06; fbreak; }; 'xuplus;' => { output->first = 0x2a04; fbreak; }; 'xutri;' => { output->first = 0x25b3; fbreak; }; 'xvee;' => { output->first = 0x22c1; fbreak; }; 'xwedge;' => { output->first = 0x22c0; fbreak; }; 'yacute;' => { output->first = 0xfd; fbreak; }; 'yacute' => { output->first = 0xfd; fbreak; }; 'yacy;' => { output->first = 0x044f; fbreak; }; 'ycirc;' => { output->first = 0x0177; fbreak; }; 'ycy;' => { output->first = 0x044b; fbreak; }; 'yen;' => { output->first = 0xa5; fbreak; }; 'yen' => { output->first = 0xa5; fbreak; }; 'yfr;' => { output->first = 0x0001d536; fbreak; }; 'yicy;' => { output->first = 0x0457; fbreak; }; 'yopf;' => { output->first = 0x0001d56a; fbreak; }; 'yscr;' => { output->first = 0x0001d4ce; fbreak; }; 'yucy;' => { output->first = 0x044e; fbreak; }; 'yuml;' => { output->first = 0xff; fbreak; }; 'yuml' => { output->first = 0xff; fbreak; }; 'zacute;' => { output->first = 0x017a; fbreak; }; 'zcaron;' => { output->first = 0x017e; fbreak; }; 'zcy;' => { output->first = 0x0437; fbreak; }; 'zdot;' => { output->first = 0x017c; fbreak; }; 'zeetrf;' => { output->first = 0x2128; fbreak; }; 'zeta;' => { output->first = 0x03b6; fbreak; }; 'zfr;' => { output->first = 0x0001d537; fbreak; }; 'zhcy;' => { output->first = 0x0436; fbreak; }; 'zigrarr;' => { output->first = 0x21dd; fbreak; }; 'zopf;' => { output->first = 0x0001d56b; fbreak; }; 'zscr;' => { output->first = 0x0001d4cf; fbreak; }; 'zwj;' => { output->first = 0x200d; fbreak; }; 'zwnj;' => { output->first = 0x200c; fbreak; }; *|; }%% // clang-format off %% write data noerror nofinal; // clang-format on static bool consume_named_ref( struct GumboInternalParser* parser, Utf8Iterator* input, bool is_in_attribute, OneOrTwoCodepoints* output) { assert(output->first == kGumboNoChar); const char* p = utf8iterator_get_char_pointer(input); const char* pe = utf8iterator_get_end_pointer(input); const char* eof = pe; const char* te = 0; const char *ts, *start; int cs, act; // clang-format off %% write init; // Avoid unused variable warnings. (void) act; (void) ts; (void) char_ref_en_valid_named_ref; start = p; %% write exec; // clang-format on if (cs >= %%{ write first_final; }%%) { assert(output->first != kGumboNoChar); char last_char = *(te - 1); int len = te - start; if (last_char == ';') { bool matched = utf8iterator_maybe_consume_match(input, start, len, true); assert(matched); return true; } else if (is_in_attribute && (*te == '=' || isalnum(*te))) { output->first = kGumboNoChar; output->second = kGumboNoChar; utf8iterator_reset(input); return true; } else { GumboStringPiece bad_ref; bad_ref.length = te - start; bad_ref.data = start; add_named_reference_error( parser, input, GUMBO_ERR_NAMED_CHAR_REF_WITHOUT_SEMICOLON, bad_ref); bool matched = utf8iterator_maybe_consume_match(input, start, len, true); assert(matched); return false; } } else { output->first = kGumboNoChar; output->second = kGumboNoChar; bool status = maybe_add_invalid_named_reference(parser, input); utf8iterator_reset(input); return status; } } bool consume_char_ref( struct GumboInternalParser* parser, struct GumboInternalUtf8Iterator* input, int additional_allowed_char, bool is_in_attribute, OneOrTwoCodepoints* output) { utf8iterator_mark(input); utf8iterator_next(input); int c = utf8iterator_current(input); output->first = kGumboNoChar; output->second = kGumboNoChar; if (c == additional_allowed_char) { utf8iterator_reset(input); output->first = kGumboNoChar; return true; } switch (utf8iterator_current(input)) { case '\t': case '\n': case '\f': case ' ': case '<': case '&': case -1: utf8iterator_reset(input); return true; case '#': return consume_numeric_ref(parser, input, &output->first); default: return consume_named_ref(parser, input, is_in_attribute, output); } } ================================================ FILE: zhuishushenqi/Vendor/OCGumbo/gumbo/error.c ================================================ // Copyright 2010 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // Author: jdtang@google.com (Jonathan Tang) #include "error.h" #include #include #include #include #include "gumbo.h" #include "parser.h" #include "string_buffer.h" #include "util.h" #include "vector.h" // Prints a formatted message to a StringBuffer. This automatically resizes the // StringBuffer as necessary to fit the message. Returns the number of bytes // written. static int print_message( GumboParser* parser, GumboStringBuffer* output, const char* format, ...) { va_list args; int remaining_capacity = output->capacity - output->length; va_start(args, format); int bytes_written = vsnprintf( output->data + output->length, remaining_capacity, format, args); va_end(args); #ifdef _MSC_VER if (bytes_written == -1) { // vsnprintf returns -1 on MSVC++ if there's not enough capacity, instead of // returning the number of bytes that would've been written had there been // enough. In this case, we'll double the buffer size and hope it fits when // we retry (letting it fail and returning 0 if it doesn't), since there's // no way to smartly resize the buffer. gumbo_string_buffer_reserve(parser, output->capacity * 2, output); va_start(args, format); int result = vsnprintf( output->data + output->length, remaining_capacity, format, args); va_end(args); return result == -1 ? 0 : result; } #else // -1 in standard C99 indicates an encoding error. Return 0 and do nothing. if (bytes_written == -1) { return 0; } #endif if (bytes_written >= remaining_capacity) { gumbo_string_buffer_reserve( parser, output->capacity + bytes_written, output); remaining_capacity = output->capacity - output->length; va_start(args, format); bytes_written = vsnprintf( output->data + output->length, remaining_capacity, format, args); va_end(args); } output->length += bytes_written; return bytes_written; } static void print_tag_stack(GumboParser* parser, const GumboParserError* error, GumboStringBuffer* output) { print_message(parser, output, " Currently open tags: "); for (unsigned int i = 0; i < error->tag_stack.length; ++i) { if (i) { print_message(parser, output, ", "); } GumboTag tag = (GumboTag) error->tag_stack.data[i]; print_message(parser, output, gumbo_normalized_tagname(tag)); } gumbo_string_buffer_append_codepoint(parser, '.', output); } static void handle_parser_error(GumboParser* parser, const GumboParserError* error, GumboStringBuffer* output) { if (error->parser_state == GUMBO_INSERTION_MODE_INITIAL && error->input_type != GUMBO_TOKEN_DOCTYPE) { print_message( parser, output, "The doctype must be the first token in the document"); return; } switch (error->input_type) { case GUMBO_TOKEN_DOCTYPE: print_message(parser, output, "This is not a legal doctype"); return; case GUMBO_TOKEN_COMMENT: // Should never happen; comments are always legal. assert(0); // But just in case... print_message(parser, output, "Comments aren't legal here"); return; case GUMBO_TOKEN_CDATA: case GUMBO_TOKEN_WHITESPACE: case GUMBO_TOKEN_CHARACTER: print_message(parser, output, "Character tokens aren't legal here"); return; case GUMBO_TOKEN_NULL: print_message(parser, output, "Null bytes are not allowed in HTML5"); return; case GUMBO_TOKEN_EOF: if (error->parser_state == GUMBO_INSERTION_MODE_INITIAL) { print_message(parser, output, "You must provide a doctype"); } else { print_message(parser, output, "Premature end of file"); print_tag_stack(parser, error, output); } return; case GUMBO_TOKEN_START_TAG: case GUMBO_TOKEN_END_TAG: print_message(parser, output, "That tag isn't allowed here"); print_tag_stack(parser, error, output); // TODO(jdtang): Give more specific messaging. return; } } // Finds the preceding newline in an original source buffer from a given byte // location. Returns a character pointer to the character after that, or a // pointer to the beginning of the string if this is the first line. static const char* find_last_newline( const char* original_text, const char* error_location) { assert(error_location >= original_text); const char* c = error_location; for (; c != original_text && *c != '\n'; --c) { // There may be an error at EOF, which would be a nul byte. assert(*c || c == error_location); } return c == original_text ? c : c + 1; } // Finds the next newline in the original source buffer from a given byte // location. Returns a character pointer to that newline, or a pointer to the // terminating null byte if this is the last line. static const char* find_next_newline( const char* original_text, const char* error_location) { const char* c = error_location; for (; *c && *c != '\n'; ++c) ; return c; } GumboError* gumbo_add_error(GumboParser* parser) { int max_errors = parser->_options->max_errors; if (max_errors >= 0 && parser->_output->errors.length >= (unsigned int) max_errors) { return NULL; } GumboError* error = gumbo_parser_allocate(parser, sizeof(GumboError)); gumbo_vector_add(parser, error, &parser->_output->errors); return error; } void gumbo_error_to_string( GumboParser* parser, const GumboError* error, GumboStringBuffer* output) { print_message( parser, output, "@%d:%d: ", error->position.line, error->position.column); switch (error->type) { case GUMBO_ERR_UTF8_INVALID: print_message( parser, output, "Invalid UTF8 character 0x%x", error->v.codepoint); break; case GUMBO_ERR_UTF8_TRUNCATED: print_message(parser, output, "Input stream ends with a truncated UTF8 character 0x%x", error->v.codepoint); break; case GUMBO_ERR_NUMERIC_CHAR_REF_NO_DIGITS: print_message( parser, output, "No digits after &# in numeric character reference"); break; case GUMBO_ERR_NUMERIC_CHAR_REF_WITHOUT_SEMICOLON: print_message(parser, output, "The numeric character reference &#%d should be followed " "by a semicolon", error->v.codepoint); break; case GUMBO_ERR_NUMERIC_CHAR_REF_INVALID: print_message(parser, output, "The numeric character reference &#%d; encodes an invalid " "unicode codepoint", error->v.codepoint); break; case GUMBO_ERR_NAMED_CHAR_REF_WITHOUT_SEMICOLON: // The textual data came from one of the literal strings in the table, and // so it'll be null-terminated. print_message(parser, output, "The named character reference &%.*s should be followed by a " "semicolon", (int) error->v.text.length, error->v.text.data); break; case GUMBO_ERR_NAMED_CHAR_REF_INVALID: print_message(parser, output, "The named character reference &%.*s; is not a valid entity name", (int) error->v.text.length, error->v.text.data); break; case GUMBO_ERR_DUPLICATE_ATTR: print_message(parser, output, "Attribute %s occurs multiple times, at positions %d and %d", error->v.duplicate_attr.name, error->v.duplicate_attr.original_index, error->v.duplicate_attr.new_index); break; case GUMBO_ERR_PARSER: case GUMBO_ERR_UNACKNOWLEDGED_SELF_CLOSING_TAG: handle_parser_error(parser, &error->v.parser, output); break; default: print_message(parser, output, "Tokenizer error with an unimplemented error message"); break; } gumbo_string_buffer_append_codepoint(parser, '.', output); } void gumbo_caret_diagnostic_to_string(GumboParser* parser, const GumboError* error, const char* source_text, GumboStringBuffer* output) { gumbo_error_to_string(parser, error, output); const char* line_start = find_last_newline(source_text, error->original_text); const char* line_end = find_next_newline(source_text, error->original_text); GumboStringPiece original_line; original_line.data = line_start; original_line.length = line_end - line_start; gumbo_string_buffer_append_codepoint(parser, '\n', output); gumbo_string_buffer_append_string(parser, &original_line, output); gumbo_string_buffer_append_codepoint(parser, '\n', output); gumbo_string_buffer_reserve( parser, output->length + error->position.column, output); int num_spaces = error->position.column - 1; memset(output->data + output->length, ' ', num_spaces); output->length += num_spaces; gumbo_string_buffer_append_codepoint(parser, '^', output); gumbo_string_buffer_append_codepoint(parser, '\n', output); } void gumbo_print_caret_diagnostic( GumboParser* parser, const GumboError* error, const char* source_text) { GumboStringBuffer text; gumbo_string_buffer_init(parser, &text); gumbo_caret_diagnostic_to_string(parser, error, source_text, &text); printf("%.*s", (int) text.length, text.data); gumbo_string_buffer_destroy(parser, &text); } void gumbo_error_destroy(GumboParser* parser, GumboError* error) { if (error->type == GUMBO_ERR_PARSER || error->type == GUMBO_ERR_UNACKNOWLEDGED_SELF_CLOSING_TAG) { gumbo_vector_destroy(parser, &error->v.parser.tag_stack); } else if (error->type == GUMBO_ERR_DUPLICATE_ATTR) { gumbo_parser_deallocate(parser, (void*) error->v.duplicate_attr.name); } gumbo_parser_deallocate(parser, error); } void gumbo_init_errors(GumboParser* parser) { gumbo_vector_init(parser, 5, &parser->_output->errors); } void gumbo_destroy_errors(GumboParser* parser) { for (unsigned int i = 0; i < parser->_output->errors.length; ++i) { gumbo_error_destroy(parser, parser->_output->errors.data[i]); } gumbo_vector_destroy(parser, &parser->_output->errors); } ================================================ FILE: zhuishushenqi/Vendor/OCGumbo/gumbo/error.h ================================================ // Copyright 2010 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // Author: jdtang@google.com (Jonathan Tang) // // Error types, enums, and handling functions. #ifndef GUMBO_ERROR_H_ #define GUMBO_ERROR_H_ #ifdef _MSC_VER #define _CRT_SECURE_NO_WARNINGS #endif #include #include "gumbo.h" #include "insertion_mode.h" #include "string_buffer.h" #include "token_type.h" #ifdef __cplusplus extern "C" { #endif struct GumboInternalParser; typedef enum { GUMBO_ERR_UTF8_INVALID, GUMBO_ERR_UTF8_TRUNCATED, GUMBO_ERR_UTF8_NULL, GUMBO_ERR_NUMERIC_CHAR_REF_NO_DIGITS, GUMBO_ERR_NUMERIC_CHAR_REF_WITHOUT_SEMICOLON, GUMBO_ERR_NUMERIC_CHAR_REF_INVALID, GUMBO_ERR_NAMED_CHAR_REF_WITHOUT_SEMICOLON, GUMBO_ERR_NAMED_CHAR_REF_INVALID, GUMBO_ERR_TAG_STARTS_WITH_QUESTION, GUMBO_ERR_TAG_EOF, GUMBO_ERR_TAG_INVALID, GUMBO_ERR_CLOSE_TAG_EMPTY, GUMBO_ERR_CLOSE_TAG_EOF, GUMBO_ERR_CLOSE_TAG_INVALID, GUMBO_ERR_SCRIPT_EOF, GUMBO_ERR_ATTR_NAME_EOF, GUMBO_ERR_ATTR_NAME_INVALID, GUMBO_ERR_ATTR_DOUBLE_QUOTE_EOF, GUMBO_ERR_ATTR_SINGLE_QUOTE_EOF, GUMBO_ERR_ATTR_UNQUOTED_EOF, GUMBO_ERR_ATTR_UNQUOTED_RIGHT_BRACKET, GUMBO_ERR_ATTR_UNQUOTED_EQUALS, GUMBO_ERR_ATTR_AFTER_EOF, GUMBO_ERR_ATTR_AFTER_INVALID, GUMBO_ERR_DUPLICATE_ATTR, GUMBO_ERR_SOLIDUS_EOF, GUMBO_ERR_SOLIDUS_INVALID, GUMBO_ERR_DASHES_OR_DOCTYPE, GUMBO_ERR_COMMENT_EOF, GUMBO_ERR_COMMENT_INVALID, GUMBO_ERR_COMMENT_BANG_AFTER_DOUBLE_DASH, GUMBO_ERR_COMMENT_DASH_AFTER_DOUBLE_DASH, GUMBO_ERR_COMMENT_SPACE_AFTER_DOUBLE_DASH, GUMBO_ERR_COMMENT_END_BANG_EOF, GUMBO_ERR_DOCTYPE_EOF, GUMBO_ERR_DOCTYPE_INVALID, GUMBO_ERR_DOCTYPE_SPACE, GUMBO_ERR_DOCTYPE_RIGHT_BRACKET, GUMBO_ERR_DOCTYPE_SPACE_OR_RIGHT_BRACKET, GUMBO_ERR_DOCTYPE_END, GUMBO_ERR_PARSER, GUMBO_ERR_UNACKNOWLEDGED_SELF_CLOSING_TAG, } GumboErrorType; // Additional data for duplicated attributes. typedef struct GumboInternalDuplicateAttrError { // The name of the attribute. Owned by this struct. const char* name; // The (0-based) index within the attributes vector of the original // occurrence. unsigned int original_index; // The (0-based) index where the new occurrence would be. unsigned int new_index; } GumboDuplicateAttrError; // A simplified representation of the tokenizer state, designed to be more // useful to clients of this library than the internal representation. This // condenses the actual states used in the tokenizer state machine into a few // values that will be familiar to users of HTML. typedef enum { GUMBO_ERR_TOKENIZER_DATA, GUMBO_ERR_TOKENIZER_CHAR_REF, GUMBO_ERR_TOKENIZER_RCDATA, GUMBO_ERR_TOKENIZER_RAWTEXT, GUMBO_ERR_TOKENIZER_PLAINTEXT, GUMBO_ERR_TOKENIZER_SCRIPT, GUMBO_ERR_TOKENIZER_TAG, GUMBO_ERR_TOKENIZER_SELF_CLOSING_TAG, GUMBO_ERR_TOKENIZER_ATTR_NAME, GUMBO_ERR_TOKENIZER_ATTR_VALUE, GUMBO_ERR_TOKENIZER_MARKUP_DECLARATION, GUMBO_ERR_TOKENIZER_COMMENT, GUMBO_ERR_TOKENIZER_DOCTYPE, GUMBO_ERR_TOKENIZER_CDATA, } GumboTokenizerErrorState; // Additional data for tokenizer errors. // This records the current state and codepoint encountered - this is usually // enough to reconstruct what went wrong and provide a friendly error message. typedef struct GumboInternalTokenizerError { // The bad codepoint encountered. int codepoint; // The state that the tokenizer was in at the time. GumboTokenizerErrorState state; } GumboTokenizerError; // Additional data for parse errors. typedef struct GumboInternalParserError { // The type of input token that resulted in this error. GumboTokenType input_type; // The HTML tag of the input token. TAG_UNKNOWN if this was not a tag token. GumboTag input_tag; // The insertion mode that the parser was in at the time. GumboInsertionMode parser_state; // The tag stack at the point of the error. Note that this is an GumboVector // of GumboTag's *stored by value* - cast the void* to an GumboTag directly to // get at the tag. GumboVector /* GumboTag */ tag_stack; } GumboParserError; // The overall error struct representing an error in decoding/tokenizing/parsing // the HTML. This contains an enumerated type flag, a source position, and then // a union of fields containing data specific to the error. typedef struct GumboInternalError { // The type of error. GumboErrorType type; // The position within the source file where the error occurred. GumboSourcePosition position; // A pointer to the byte within the original source file text where the error // occurred (note that this is not the same as position.offset, as that gives // character-based instead of byte-based offsets). const char* original_text; // Type-specific error information. union { // The code point we encountered, for: // * GUMBO_ERR_UTF8_INVALID // * GUMBO_ERR_UTF8_TRUNCATED // * GUMBO_ERR_NUMERIC_CHAR_REF_WITHOUT_SEMICOLON // * GUMBO_ERR_NUMERIC_CHAR_REF_INVALID uint64_t codepoint; // Tokenizer errors. GumboTokenizerError tokenizer; // Short textual data, for: // * GUMBO_ERR_NAMED_CHAR_REF_WITHOUT_SEMICOLON // * GUMBO_ERR_NAMED_CHAR_REF_INVALID GumboStringPiece text; // Duplicate attribute data, for GUMBO_ERR_DUPLICATE_ATTR. GumboDuplicateAttrError duplicate_attr; // Parser state, for GUMBO_ERR_PARSER and // GUMBO_ERR_UNACKNOWLEDGE_SELF_CLOSING_TAG. struct GumboInternalParserError parser; } v; } GumboError; // Adds a new error to the parser's error list, and returns a pointer to it so // that clients can fill out the rest of its fields. May return NULL if we're // already over the max_errors field specified in GumboOptions. GumboError* gumbo_add_error(struct GumboInternalParser* parser); // Initializes the errors vector in the parser. void gumbo_init_errors(struct GumboInternalParser* errors); // Frees all the errors in the 'errors_' field of the parser. void gumbo_destroy_errors(struct GumboInternalParser* errors); // Frees the memory used for a single GumboError. void gumbo_error_destroy(struct GumboInternalParser* parser, GumboError* error); // Prints an error to a string. This fills an empty GumboStringBuffer with a // freshly-allocated buffer containing the error message text. The caller is // responsible for deleting the buffer. (Note that the buffer is allocated with // the allocator specified in the GumboParser config and hence should be freed // by gumbo_parser_deallocate().) void gumbo_error_to_string(struct GumboInternalParser* parser, const GumboError* error, GumboStringBuffer* output); // Prints a caret diagnostic to a string. This fills an empty GumboStringBuffer // with a freshly-allocated buffer containing the error message text. The // caller is responsible for deleting the buffer. (Note that the buffer is // allocated with the allocator specified in the GumboParser config and hence // should be freed by gumbo_parser_deallocate().) void gumbo_caret_diagnostic_to_string(struct GumboInternalParser* parser, const GumboError* error, const char* source_text, GumboStringBuffer* output); // Like gumbo_caret_diagnostic_to_string, but prints the text to stdout instead // of writing to a string. void gumbo_print_caret_diagnostic(struct GumboInternalParser* parser, const GumboError* error, const char* source_text); #ifdef __cplusplus } #endif #endif // GUMBO_ERROR_H_ ================================================ FILE: zhuishushenqi/Vendor/OCGumbo/gumbo/gumbo.h ================================================ // Copyright 2010 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // Author: jdtang@google.com (Jonathan Tang) // // We use Gumbo as a prefix for types, gumbo_ as a prefix for functions, and // GUMBO_ as a prefix for enum constants (static constants get the Google-style // kGumbo prefix). /** * @file * @mainpage Gumbo HTML Parser * * This provides a conformant, no-dependencies implementation of the HTML5 * parsing algorithm. It supports only UTF8; if you need to parse a different * encoding, run a preprocessing step to convert to UTF8. It returns a parse * tree made of the structs in this file. * * Example: * @code * GumboOutput* output = gumbo_parse(input); * do_something_with_doctype(output->document); * do_something_with_html_tree(output->root); * gumbo_destroy_output(&options, output); * @endcode * HTML5 Spec: * * http://www.whatwg.org/specs/web-apps/current-work/multipage/syntax.html */ #ifndef GUMBO_GUMBO_H_ #define GUMBO_GUMBO_H_ #ifdef _MSC_VER #define _CRT_SECURE_NO_WARNINGS #define fileno _fileno #endif #include #include #ifdef __cplusplus extern "C" { #endif /** * A struct representing a character position within the original text buffer. * Line and column numbers are 1-based and offsets are 0-based, which matches * how most editors and command-line tools work. Also, columns measure * positions in terms of characters while offsets measure by bytes; this is * because the offset field is often used to pull out a particular region of * text (which in most languages that bind to C implies pointer arithmetic on a * buffer of bytes), while the column field is often used to reference a * particular column on a printable display, which nowadays is usually UTF-8. */ typedef struct { unsigned int line; unsigned int column; unsigned int offset; } GumboSourcePosition; /** * A SourcePosition used for elements that have no source position, i.e. * parser-inserted elements. */ extern const GumboSourcePosition kGumboEmptySourcePosition; /** * A struct representing a string or part of a string. Strings within the * parser are represented by a char* and a length; the char* points into * an existing data buffer owned by some other code (often the original input). * GumboStringPieces are assumed (by convention) to be immutable, because they * may share data. Use GumboStringBuffer if you need to construct a string. * Clients should assume that it is not NUL-terminated, and should always use * explicit lengths when manipulating them. */ typedef struct { /** A pointer to the beginning of the string. NULL iff length == 0. */ const char* data; /** The length of the string fragment, in bytes. May be zero. */ size_t length; } GumboStringPiece; /** A constant to represent a 0-length null string. */ extern const GumboStringPiece kGumboEmptyString; /** * Compares two GumboStringPieces, and returns true if they're equal or false * otherwise. */ bool gumbo_string_equals( const GumboStringPiece* str1, const GumboStringPiece* str2); /** * Compares two GumboStringPieces ignoring case, and returns true if they're * equal or false otherwise. */ bool gumbo_string_equals_ignore_case( const GumboStringPiece* str1, const GumboStringPiece* str2); /** * A simple vector implementation. This stores a pointer to a data array and a * length. All elements are stored as void*; client code must cast to the * appropriate type. Overflows upon addition result in reallocation of the data * array, with the size doubling to maintain O(1) amortized cost. There is no * removal function, as this isn't needed for any of the operations within this * library. Iteration can be done through inspecting the structure directly in * a for-loop. */ typedef struct { /** Data elements. This points to a dynamically-allocated array of capacity * elements, each a void* to the element itself. */ void** data; /** Number of elements currently in the vector. */ unsigned int length; /** Current array capacity. */ unsigned int capacity; } GumboVector; /** An empty (0-length, 0-capacity) GumboVector. */ extern const GumboVector kGumboEmptyVector; /** * Returns the first index at which an element appears in this vector (testing * by pointer equality), or -1 if it never does. */ int gumbo_vector_index_of(GumboVector* vector, const void* element); /** * An enum for all the tags defined in the HTML5 standard. These correspond to * the tag names themselves. Enum constants exist only for tags which appear in * the spec itself (or for tags with special handling in the SVG and MathML * namespaces); any other tags appear as GUMBO_TAG_UNKNOWN and the actual tag * name can be obtained through original_tag. * * This is mostly for API convenience, so that clients of this library don't * need to perform a strcasecmp to find the normalized tag name. It also has * efficiency benefits, by letting the parser work with enums instead of * strings. */ typedef enum { // Load all the tags from an external source, generated from tag.in. #include "tag_enum.h" // Used for all tags that don't have special handling in HTML. Add new tags // to the end of tag.in so as to preserve backwards-compatibility. GUMBO_TAG_UNKNOWN, // A marker value to indicate the end of the enum, for iterating over it. // Also used as the terminator for varargs functions that take tags. GUMBO_TAG_LAST, } GumboTag; /** * Returns the normalized (usually all-lowercased, except for foreign content) * tag name for an GumboTag enum. Return value is static data owned by the * library. */ const char* gumbo_normalized_tagname(GumboTag tag); /** * Extracts the tag name from the original_text field of an element or token by * stripping off characters and attributes and adjusting the passed-in * GumboStringPiece appropriately. The tag name is in the original case and * shares a buffer with the original text, to simplify memory management. * Behavior is undefined if a string-piece that doesn't represent an HTML tag * ( or ) is passed in. If the string piece is completely * empty (NULL data pointer), then this function will exit successfully as a * no-op. */ void gumbo_tag_from_original_text(GumboStringPiece* text); /** * Fixes the case of SVG elements that are not all lowercase. * http://www.whatwg.org/specs/web-apps/current-work/multipage/tree-construction.html#parsing-main-inforeign * This is not done at parse time because there's no place to store a mutated * tag name. tag_name is an enum (which will be TAG_UNKNOWN for most SVG tags * without special handling), while original_tag_name is a pointer into the * original buffer. Instead, we provide this helper function that clients can * use to rename SVG tags as appropriate. * Returns the case-normalized SVG tagname if a replacement is found, or NULL if * no normalization is called for. The return value is static data and owned by * the library. */ const char* gumbo_normalize_svg_tagname(const GumboStringPiece* tagname); /** * Converts a tag name string (which may be in upper or mixed case) to a tag * enum. The `tag` version expects `tagname` to be NULL-terminated */ GumboTag gumbo_tag_enum(const char* tagname); GumboTag gumbo_tagn_enum(const char* tagname, unsigned int length); /** * Attribute namespaces. * HTML includes special handling for XLink, XML, and XMLNS namespaces on * attributes. Everything else goes in the generic "NONE" namespace. */ typedef enum { GUMBO_ATTR_NAMESPACE_NONE, GUMBO_ATTR_NAMESPACE_XLINK, GUMBO_ATTR_NAMESPACE_XML, GUMBO_ATTR_NAMESPACE_XMLNS, } GumboAttributeNamespaceEnum; /** * A struct representing a single attribute on an HTML tag. This is a * name-value pair, but also includes information about source locations and * original source text. */ typedef struct { /** * The namespace for the attribute. This will usually be * GUMBO_ATTR_NAMESPACE_NONE, but some XLink/XMLNS/XML attributes take special * values, per: * http://www.whatwg.org/specs/web-apps/current-work/multipage/tree-construction.html#adjust-foreign-attributes */ GumboAttributeNamespaceEnum attr_namespace; /** * The name of the attribute. This is in a freshly-allocated buffer to deal * with case-normalization, and is null-terminated. */ const char* name; /** * The original text of the attribute name, as a pointer into the original * source buffer. */ GumboStringPiece original_name; /** * The value of the attribute. This is in a freshly-allocated buffer to deal * with unescaping, and is null-terminated. It does not include any quotes * that surround the attribute. If the attribute has no value (for example, * 'selected' on a checkbox), this will be an empty string. */ const char* value; /** * The original text of the value of the attribute. This points into the * original source buffer. It includes any quotes that surround the * attribute, and you can look at original_value.data[0] and * original_value.data[original_value.length - 1] to determine what the quote * characters were. If the attribute has no value, this will be a 0-length * string. */ GumboStringPiece original_value; /** The starting position of the attribute name. */ GumboSourcePosition name_start; /** * The ending position of the attribute name. This is not always derivable * from the starting position of the value because of the possibility of * whitespace around the = sign. */ GumboSourcePosition name_end; /** The starting position of the attribute value. */ GumboSourcePosition value_start; /** The ending position of the attribute value. */ GumboSourcePosition value_end; } GumboAttribute; /** * Given a vector of GumboAttributes, look up the one with the specified name * and return it, or NULL if no such attribute exists. This uses a * case-insensitive match, as HTML is case-insensitive. */ GumboAttribute* gumbo_get_attribute(const GumboVector* attrs, const char* name); /** * Enum denoting the type of node. This determines the type of the node.v * union. */ typedef enum { /** Document node. v will be a GumboDocument. */ GUMBO_NODE_DOCUMENT, /** Element node. v will be a GumboElement. */ GUMBO_NODE_ELEMENT, /** Text node. v will be a GumboText. */ GUMBO_NODE_TEXT, /** CDATA node. v will be a GumboText. */ GUMBO_NODE_CDATA, /** Comment node. v will be a GumboText, excluding comment delimiters. */ GUMBO_NODE_COMMENT, /** Text node, where all contents is whitespace. v will be a GumboText. */ GUMBO_NODE_WHITESPACE, /** Template node. This is separate from GUMBO_NODE_ELEMENT because many * client libraries will want to ignore the contents of template nodes, as * the spec suggests. Recursing on GUMBO_NODE_ELEMENT will do the right thing * here, while clients that want to include template contents should also * check for GUMBO_NODE_TEMPLATE. v will be a GumboElement. */ GUMBO_NODE_TEMPLATE } GumboNodeType; /** * Forward declaration of GumboNode so it can be used recursively in * GumboNode.parent. */ typedef struct GumboInternalNode GumboNode; /** * http://www.whatwg.org/specs/web-apps/current-work/complete/dom.html#quirks-mode */ typedef enum { GUMBO_DOCTYPE_NO_QUIRKS, GUMBO_DOCTYPE_QUIRKS, GUMBO_DOCTYPE_LIMITED_QUIRKS } GumboQuirksModeEnum; /** * Namespaces. * Unlike in X(HT)ML, namespaces in HTML5 are not denoted by a prefix. Rather, * anything inside an tag is in the SVG namespace, anything inside the * tag is in the MathML namespace, and anything else is inside the HTML * namespace. No other namespaces are supported, so this can be an enum only. */ typedef enum { GUMBO_NAMESPACE_HTML, GUMBO_NAMESPACE_SVG, GUMBO_NAMESPACE_MATHML } GumboNamespaceEnum; /** * Parse flags. * We track the reasons for parser insertion of nodes and store them in a * bitvector in the node itself. This lets client code optimize out nodes that * are implied by the HTML structure of the document, or flag constructs that * may not be allowed by a style guide, or track the prevalence of incorrect or * tricky HTML code. */ typedef enum { /** * A normal node - both start and end tags appear in the source, nothing has * been reparented. */ GUMBO_INSERTION_NORMAL = 0, /** * A node inserted by the parser to fulfill some implicit insertion rule. * This is usually set in addition to some other flag giving a more specific * insertion reason; it's a generic catch-all term meaning "The start tag for * this node did not appear in the document source". */ GUMBO_INSERTION_BY_PARSER = 1 << 0, /** * A flag indicating that the end tag for this node did not appear in the * document source. Note that in some cases, you can still have * parser-inserted nodes with an explicit end tag: for example, "Text" * has GUMBO_INSERTED_BY_PARSER set on the node, but * GUMBO_INSERTED_END_TAG_IMPLICITLY is unset, as the tag actually * exists. This flag will be set only if the end tag is completely missing; * in some cases, the end tag may be misplaced (eg. a tag with text * afterwards), which will leave this flag unset and require clients to * inspect the parse errors for that case. */ GUMBO_INSERTION_IMPLICIT_END_TAG = 1 << 1, // Value 1 << 2 was for a flag that has since been removed. /** * A flag for nodes that are inserted because their presence is implied by * other tags, eg. , , , , etc. */ GUMBO_INSERTION_IMPLIED = 1 << 3, /** * A flag for nodes that are converted from their end tag equivalents. For * example,

    when no paragraph is open implies that the parser should * create a

    tag and immediately close it, while
    means the same thing * as
    . */ GUMBO_INSERTION_CONVERTED_FROM_END_TAG = 1 << 4, /** A flag for nodes that are converted from the parse of an tag. */ GUMBO_INSERTION_FROM_ISINDEX = 1 << 5, /** A flag for tags that are rewritten as . */ GUMBO_INSERTION_FROM_IMAGE = 1 << 6, /** * A flag for nodes that are cloned as a result of the reconstruction of * active formatting elements. This is set only on the clone; the initial * portion of the formatting run is a NORMAL node with an IMPLICIT_END_TAG. */ GUMBO_INSERTION_RECONSTRUCTED_FORMATTING_ELEMENT = 1 << 7, /** A flag for nodes that are cloned by the adoption agency algorithm. */ GUMBO_INSERTION_ADOPTION_AGENCY_CLONED = 1 << 8, /** A flag for nodes that are moved by the adoption agency algorithm. */ GUMBO_INSERTION_ADOPTION_AGENCY_MOVED = 1 << 9, /** * A flag for nodes that have been foster-parented out of a table (or * should've been foster-parented, if verbatim mode is set). */ GUMBO_INSERTION_FOSTER_PARENTED = 1 << 10, } GumboParseFlags; /** * Information specific to document nodes. */ typedef struct { /** * An array of GumboNodes, containing the children of this element. This will * normally consist of the element and any comment nodes found. * Pointers are owned. */ GumboVector /* GumboNode* */ children; // True if there was an explicit doctype token as opposed to it being omitted. bool has_doctype; // Fields from the doctype token, copied verbatim. const char* name; const char* public_identifier; const char* system_identifier; /** * Whether or not the document is in QuirksMode, as determined by the values * in the GumboTokenDocType template. */ GumboQuirksModeEnum doc_type_quirks_mode; } GumboDocument; /** * The struct used to represent TEXT, CDATA, COMMENT, and WHITESPACE elements. * This contains just a block of text and its position. */ typedef struct { /** * The text of this node, after entities have been parsed and decoded. For * comment/cdata nodes, this does not include the comment delimiters. */ const char* text; /** * The original text of this node, as a pointer into the original buffer. For * comment/cdata nodes, this includes the comment delimiters. */ GumboStringPiece original_text; /** * The starting position of this node. This corresponds to the position of * original_text, before entities are decoded. * */ GumboSourcePosition start_pos; } GumboText; /** * The struct used to represent all HTML elements. This contains information * about the tag, attributes, and child nodes. */ typedef struct { /** * An array of GumboNodes, containing the children of this element. Pointers * are owned. */ GumboVector /* GumboNode* */ children; /** The GumboTag enum for this element. */ GumboTag tag; /** The GumboNamespaceEnum for this element. */ GumboNamespaceEnum tag_namespace; /** * A GumboStringPiece pointing to the original tag text for this element, * pointing directly into the source buffer. If the tag was inserted * algorithmically (for example, or insertion), this will be a * zero-length string. */ GumboStringPiece original_tag; /** * A GumboStringPiece pointing to the original end tag text for this element. * If the end tag was inserted algorithmically, (for example, closing a * self-closing tag), this will be a zero-length string. */ GumboStringPiece original_end_tag; /** The source position for the start of the start tag. */ GumboSourcePosition start_pos; /** The source position for the start of the end tag. */ GumboSourcePosition end_pos; /** * An array of GumboAttributes, containing the attributes for this tag in the * order that they were parsed. Pointers are owned. */ GumboVector /* GumboAttribute* */ attributes; } GumboElement; /** * A supertype for GumboElement and GumboText, so that we can include one * generic type in lists of children and cast as necessary to subtypes. */ struct GumboInternalNode { /** The type of node that this is. */ GumboNodeType type; /** Pointer back to parent node. Not owned. */ GumboNode* parent; /** The index within the parent's children vector of this node. */ size_t index_within_parent; /** * A bitvector of flags containing information about why this element was * inserted into the parse tree, including a variety of special parse * situations. */ GumboParseFlags parse_flags; /** The actual node data. */ union { GumboDocument document; // For GUMBO_NODE_DOCUMENT. GumboElement element; // For GUMBO_NODE_ELEMENT. GumboText text; // For everything else. } v; }; /** * The type for an allocator function. Takes the 'userdata' member of the * GumboParser struct as its first argument. Semantics should be the same as * malloc, i.e. return a block of size_t bytes on success or NULL on failure. * Allocating a block of 0 bytes behaves as per malloc. */ // TODO(jdtang): Add checks throughout the codebase for out-of-memory condition. typedef void* (*GumboAllocatorFunction)(void* userdata, size_t size); /** * The type for a deallocator function. Takes the 'userdata' member of the * GumboParser struct as its first argument. */ typedef void (*GumboDeallocatorFunction)(void* userdata, void* ptr); /** * Input struct containing configuration options for the parser. * These let you specify alternate memory managers, provide different error * handling, etc. * Use kGumboDefaultOptions for sensible defaults, and only set what you need. */ typedef struct GumboInternalOptions { /** A memory allocator function. Default: malloc. */ GumboAllocatorFunction allocator; /** A memory deallocator function. Default: free. */ GumboDeallocatorFunction deallocator; /** * An opaque object that's passed in as the first argument to all callbacks * used by this library. Default: NULL. */ void* userdata; /** * The tab-stop size, for computing positions in source code that uses tabs. * Default: 8. */ int tab_stop; /** * Whether or not to stop parsing when the first error is encountered. * Default: false. */ bool stop_on_first_error; /** * The maximum number of errors before the parser stops recording them. This * is provided so that if the page is totally borked, we don't completely fill * up the errors vector and exhaust memory with useless redundant errors. Set * to -1 to disable the limit. * Default: -1 */ int max_errors; /** * The fragment context for parsing: * https://html.spec.whatwg.org/multipage/syntax.html#parsing-html-fragments * * If GUMBO_TAG_LAST is passed here, it is assumed to be "no fragment", i.e. * the regular parsing algorithm. Otherwise, pass the tag enum for the * intended parent of the parsed fragment. We use just the tag enum rather * than a full node because that's enough to set all the parsing context we * need, and it provides some additional flexibility for client code to act as * if parsing a fragment even when a full HTML tree isn't available. * * Default: GUMBO_TAG_LAST */ GumboTag fragment_context; /** * The namespace for the fragment context. This lets client code * differentiate between, say, parsing a tag in SVG vs. parsing it in * HTML. * Default: GUMBO_NAMESPACE_HTML */ GumboNamespaceEnum fragment_namespace; } GumboOptions; /** Default options struct; use this with gumbo_parse_with_options. */ extern const GumboOptions kGumboDefaultOptions; /** The output struct containing the results of the parse. */ typedef struct GumboInternalOutput { /** * Pointer to the document node. This is a GumboNode of type NODE_DOCUMENT * that contains the entire document as its child. */ GumboNode* document; /** * Pointer to the root node. This the <html> tag that forms the root of the * document. */ GumboNode* root; /** * A list of errors that occurred during the parse. * NOTE: In version 1.0 of this library, the API for errors hasn't been fully * fleshed out and may change in the future. For this reason, the GumboError * header isn't part of the public API. Contact us if you need errors * reported so we can work out something appropriate for your use-case. */ GumboVector /* GumboError */ errors; } GumboOutput; /** * Parses a buffer of UTF8 text into an GumboNode parse tree. The buffer must * live at least as long as the parse tree, as some fields (eg. original_text) * point directly into the original buffer. * * This doesn't support buffers longer than 4 gigabytes. */ GumboOutput* gumbo_parse(const char* buffer); /** * Extended version of gumbo_parse that takes an explicit options structure, * buffer, and length. */ GumboOutput* gumbo_parse_with_options( const GumboOptions* options, const char* buffer, size_t buffer_length); /** Release the memory used for the parse tree & parse errors. */ void gumbo_destroy_output(const GumboOptions* options, GumboOutput* output); #ifdef __cplusplus } #endif #endif // GUMBO_GUMBO_H_ ================================================ FILE: zhuishushenqi/Vendor/OCGumbo/gumbo/insertion_mode.h ================================================ // Copyright 2011 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // Author: jdtang@google.com (Jonathan Tang) #ifndef GUMBO_INSERTION_MODE_H_ #define GUMBO_INSERTION_MODE_H_ #ifdef __cplusplus extern "C" { #endif // http://www.whatwg.org/specs/web-apps/current-work/complete/parsing.html#insertion-mode // If new enum values are added, be sure to update the kTokenHandlers dispatch // table in parser.c. typedef enum { GUMBO_INSERTION_MODE_INITIAL, GUMBO_INSERTION_MODE_BEFORE_HTML, GUMBO_INSERTION_MODE_BEFORE_HEAD, GUMBO_INSERTION_MODE_IN_HEAD, GUMBO_INSERTION_MODE_IN_HEAD_NOSCRIPT, GUMBO_INSERTION_MODE_AFTER_HEAD, GUMBO_INSERTION_MODE_IN_BODY, GUMBO_INSERTION_MODE_TEXT, GUMBO_INSERTION_MODE_IN_TABLE, GUMBO_INSERTION_MODE_IN_TABLE_TEXT, GUMBO_INSERTION_MODE_IN_CAPTION, GUMBO_INSERTION_MODE_IN_COLUMN_GROUP, GUMBO_INSERTION_MODE_IN_TABLE_BODY, GUMBO_INSERTION_MODE_IN_ROW, GUMBO_INSERTION_MODE_IN_CELL, GUMBO_INSERTION_MODE_IN_SELECT, GUMBO_INSERTION_MODE_IN_SELECT_IN_TABLE, GUMBO_INSERTION_MODE_IN_TEMPLATE, GUMBO_INSERTION_MODE_AFTER_BODY, GUMBO_INSERTION_MODE_IN_FRAMESET, GUMBO_INSERTION_MODE_AFTER_FRAMESET, GUMBO_INSERTION_MODE_AFTER_AFTER_BODY, GUMBO_INSERTION_MODE_AFTER_AFTER_FRAMESET } GumboInsertionMode; #ifdef __cplusplus } // extern C #endif #endif // GUMBO_INSERTION_MODE_H_ ================================================ FILE: zhuishushenqi/Vendor/OCGumbo/gumbo/parser.c ================================================ // Copyright 2010 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // Author: jdtang@google.com (Jonathan Tang) #include <assert.h> #include <ctype.h> #include <stdarg.h> #include <stdlib.h> #include <string.h> #include <strings.h> #include "attribute.h" #include "error.h" #include "gumbo.h" #include "insertion_mode.h" #include "parser.h" #include "tokenizer.h" #include "tokenizer_states.h" #include "utf8.h" #include "util.h" #include "vector.h" #define AVOID_UNUSED_VARIABLE_WARNING(i) (void)(i) #define GUMBO_STRING(literal) \ { literal, sizeof(literal) - 1 } #define TERMINATOR \ { "", 0 } typedef char gumbo_tagset[GUMBO_TAG_LAST]; #define TAG(tag) [GUMBO_TAG_##tag] = (1 << GUMBO_NAMESPACE_HTML) #define TAG_SVG(tag) [GUMBO_TAG_##tag] = (1 << GUMBO_NAMESPACE_SVG) #define TAG_MATHML(tag) [GUMBO_TAG_##tag] = (1 << GUMBO_NAMESPACE_MATHML) #define TAGSET_INCLUDES(tagset, namespace, tag) \ (tag < GUMBO_TAG_LAST && tagset[(int) tag] == (1 << (int) namespace)) // selected forward declarations as it is getting hard to find // an appropriate order static bool node_html_tag_is(const GumboNode*, GumboTag); static GumboInsertionMode get_current_template_insertion_mode( const GumboParser*); static bool handle_in_template(GumboParser*, GumboToken*); static void destroy_node(GumboParser*, GumboNode*); static void* malloc_wrapper(void* unused, size_t size) { return malloc(size); } static void free_wrapper(void* unused, void* ptr) { free(ptr); } const GumboOptions kGumboDefaultOptions = {&malloc_wrapper, &free_wrapper, NULL, 8, false, -1, GUMBO_TAG_LAST, GUMBO_NAMESPACE_HTML}; static const GumboStringPiece kDoctypeHtml = GUMBO_STRING("html"); static const GumboStringPiece kPublicIdHtml4_0 = GUMBO_STRING("-//W3C//DTD HTML 4.0//EN"); static const GumboStringPiece kPublicIdHtml4_01 = GUMBO_STRING("-//W3C//DTD HTML 4.01//EN"); static const GumboStringPiece kPublicIdXhtml1_0 = GUMBO_STRING("-//W3C//DTD XHTML 1.0 Strict//EN"); static const GumboStringPiece kPublicIdXhtml1_1 = GUMBO_STRING("-//W3C//DTD XHTML 1.1//EN"); static const GumboStringPiece kSystemIdRecHtml4_0 = GUMBO_STRING("http://www.w3.org/TR/REC-html40/strict.dtd"); static const GumboStringPiece kSystemIdHtml4 = GUMBO_STRING("http://www.w3.org/TR/html4/strict.dtd"); static const GumboStringPiece kSystemIdXhtmlStrict1_1 = GUMBO_STRING("http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"); static const GumboStringPiece kSystemIdXhtml1_1 = GUMBO_STRING("http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"); static const GumboStringPiece kSystemIdLegacyCompat = GUMBO_STRING("about:legacy-compat"); // The doctype arrays have an explicit terminator because we want to pass them // to a helper function, and passing them as a pointer discards sizeof // information. The SVG arrays are used only by one-off functions, and so loops // over them use sizeof directly instead of a terminator. static const GumboStringPiece kQuirksModePublicIdPrefixes[] = { GUMBO_STRING("+//Silmaril//dtd html Pro v0r11 19970101//"), GUMBO_STRING("-//AdvaSoft Ltd//DTD HTML 3.0 asWedit + extensions//"), GUMBO_STRING("-//AS//DTD HTML 3.0 asWedit + extensions//"), GUMBO_STRING("-//IETF//DTD HTML 2.0 Level 1//"), GUMBO_STRING("-//IETF//DTD HTML 2.0 Level 2//"), GUMBO_STRING("-//IETF//DTD HTML 2.0 Strict Level 1//"), GUMBO_STRING("-//IETF//DTD HTML 2.0 Strict Level 2//"), GUMBO_STRING("-//IETF//DTD HTML 2.0 Strict//"), GUMBO_STRING("-//IETF//DTD HTML 2.0//"), GUMBO_STRING("-//IETF//DTD HTML 2.1E//"), GUMBO_STRING("-//IETF//DTD HTML 3.0//"), GUMBO_STRING("-//IETF//DTD HTML 3.2 Final//"), GUMBO_STRING("-//IETF//DTD HTML 3.2//"), GUMBO_STRING("-//IETF//DTD HTML 3//"), GUMBO_STRING("-//IETF//DTD HTML Level 0//"), GUMBO_STRING("-//IETF//DTD HTML Level 1//"), GUMBO_STRING("-//IETF//DTD HTML Level 2//"), GUMBO_STRING("-//IETF//DTD HTML Level 3//"), GUMBO_STRING("-//IETF//DTD HTML Strict Level 0//"), GUMBO_STRING("-//IETF//DTD HTML Strict Level 1//"), GUMBO_STRING("-//IETF//DTD HTML Strict Level 2//"), GUMBO_STRING("-//IETF//DTD HTML Strict Level 3//"), GUMBO_STRING("-//IETF//DTD HTML Strict//"), GUMBO_STRING("-//IETF//DTD HTML//"), GUMBO_STRING("-//Metrius//DTD Metrius Presentational//"), GUMBO_STRING("-//Microsoft//DTD Internet Explorer 2.0 HTML Strict//"), GUMBO_STRING("-//Microsoft//DTD Internet Explorer 2.0 HTML//"), GUMBO_STRING("-//Microsoft//DTD Internet Explorer 2.0 Tables//"), GUMBO_STRING("-//Microsoft//DTD Internet Explorer 3.0 HTML Strict//"), GUMBO_STRING("-//Microsoft//DTD Internet Explorer 3.0 HTML//"), GUMBO_STRING("-//Microsoft//DTD Internet Explorer 3.0 Tables//"), GUMBO_STRING("-//Netscape Comm. Corp.//DTD HTML//"), GUMBO_STRING("-//Netscape Comm. Corp.//DTD Strict HTML//"), GUMBO_STRING("-//O'Reilly and Associates//DTD HTML 2.0//"), GUMBO_STRING("-//O'Reilly and Associates//DTD HTML Extended 1.0//"), GUMBO_STRING("-//O'Reilly and Associates//DTD HTML Extended Relaxed 1.0//"), GUMBO_STRING( "-//SoftQuad Software//DTD HoTMetaL PRO 6.0::19990601::)" "extensions to HTML 4.0//"), GUMBO_STRING( "-//SoftQuad//DTD HoTMetaL PRO 4.0::19971010::" "extensions to HTML 4.0//"), GUMBO_STRING("-//Spyglass//DTD HTML 2.0 Extended//"), GUMBO_STRING("-//SQ//DTD HTML 2.0 HoTMetaL + extensions//"), GUMBO_STRING("-//Sun Microsystems Corp.//DTD HotJava HTML//"), GUMBO_STRING("-//Sun Microsystems Corp.//DTD HotJava Strict HTML//"), GUMBO_STRING("-//W3C//DTD HTML 3 1995-03-24//"), GUMBO_STRING("-//W3C//DTD HTML 3.2 Draft//"), GUMBO_STRING("-//W3C//DTD HTML 3.2 Final//"), GUMBO_STRING("-//W3C//DTD HTML 3.2//"), GUMBO_STRING("-//W3C//DTD HTML 3.2S Draft//"), GUMBO_STRING("-//W3C//DTD HTML 4.0 Frameset//"), GUMBO_STRING("-//W3C//DTD HTML 4.0 Transitional//"), GUMBO_STRING("-//W3C//DTD HTML Experimental 19960712//"), GUMBO_STRING("-//W3C//DTD HTML Experimental 970421//"), GUMBO_STRING("-//W3C//DTD W3 HTML//"), GUMBO_STRING("-//W3O//DTD W3 HTML 3.0//"), GUMBO_STRING("-//WebTechs//DTD Mozilla HTML 2.0//"), GUMBO_STRING("-//WebTechs//DTD Mozilla HTML//"), TERMINATOR}; static const GumboStringPiece kQuirksModePublicIdExactMatches[] = { GUMBO_STRING("-//W3O//DTD W3 HTML Strict 3.0//EN//"), GUMBO_STRING("-/W3C/DTD HTML 4.0 Transitional/EN"), GUMBO_STRING("HTML"), TERMINATOR}; static const GumboStringPiece kQuirksModeSystemIdExactMatches[] = { GUMBO_STRING("http://www.ibm.com/data/dtd/v11/ibmxhtml1-transitional.dtd"), TERMINATOR}; static const GumboStringPiece kLimitedQuirksPublicIdPrefixes[] = { GUMBO_STRING("-//W3C//DTD XHTML 1.0 Frameset//"), GUMBO_STRING("-//W3C//DTD XHTML 1.0 Transitional//"), TERMINATOR}; static const GumboStringPiece kLimitedQuirksRequiresSystemIdPublicIdPrefixes[] = {GUMBO_STRING("-//W3C//DTD HTML 4.01 Frameset//"), GUMBO_STRING("-//W3C//DTD HTML 4.01 Transitional//"), TERMINATOR}; // Indexed by GumboNamespaceEnum; keep in sync with that. static const char* kLegalXmlns[] = {"http://www.w3.org/1999/xhtml", "http://www.w3.org/2000/svg", "http://www.w3.org/1998/Math/MathML"}; typedef struct _ReplacementEntry { const GumboStringPiece from; const GumboStringPiece to; } ReplacementEntry; #define REPLACEMENT_ENTRY(from, to) \ { GUMBO_STRING(from), GUMBO_STRING(to) } // Static data for SVG attribute replacements. // https://html.spec.whatwg.org/multipage/syntax.html#creating-and-inserting-nodes static const ReplacementEntry kSvgAttributeReplacements[] = { REPLACEMENT_ENTRY("attributename", "attributeName"), REPLACEMENT_ENTRY("attributetype", "attributeType"), REPLACEMENT_ENTRY("basefrequency", "baseFrequency"), REPLACEMENT_ENTRY("baseprofile", "baseProfile"), REPLACEMENT_ENTRY("calcmode", "calcMode"), REPLACEMENT_ENTRY("clippathunits", "clipPathUnits"), // REPLACEMENT_ENTRY("contentscripttype", "contentScriptType"), // REPLACEMENT_ENTRY("contentstyletype", "contentStyleType"), REPLACEMENT_ENTRY("diffuseconstant", "diffuseConstant"), REPLACEMENT_ENTRY("edgemode", "edgeMode"), // REPLACEMENT_ENTRY("externalresourcesrequired", // "externalResourcesRequired"), // REPLACEMENT_ENTRY("filterres", "filterRes"), REPLACEMENT_ENTRY("filterunits", "filterUnits"), REPLACEMENT_ENTRY("glyphref", "glyphRef"), REPLACEMENT_ENTRY("gradienttransform", "gradientTransform"), REPLACEMENT_ENTRY("gradientunits", "gradientUnits"), REPLACEMENT_ENTRY("kernelmatrix", "kernelMatrix"), REPLACEMENT_ENTRY("kernelunitlength", "kernelUnitLength"), REPLACEMENT_ENTRY("keypoints", "keyPoints"), REPLACEMENT_ENTRY("keysplines", "keySplines"), REPLACEMENT_ENTRY("keytimes", "keyTimes"), REPLACEMENT_ENTRY("lengthadjust", "lengthAdjust"), REPLACEMENT_ENTRY("limitingconeangle", "limitingConeAngle"), REPLACEMENT_ENTRY("markerheight", "markerHeight"), REPLACEMENT_ENTRY("markerunits", "markerUnits"), REPLACEMENT_ENTRY("markerwidth", "markerWidth"), REPLACEMENT_ENTRY("maskcontentunits", "maskContentUnits"), REPLACEMENT_ENTRY("maskunits", "maskUnits"), REPLACEMENT_ENTRY("numoctaves", "numOctaves"), REPLACEMENT_ENTRY("pathlength", "pathLength"), REPLACEMENT_ENTRY("patterncontentunits", "patternContentUnits"), REPLACEMENT_ENTRY("patterntransform", "patternTransform"), REPLACEMENT_ENTRY("patternunits", "patternUnits"), REPLACEMENT_ENTRY("pointsatx", "pointsAtX"), REPLACEMENT_ENTRY("pointsaty", "pointsAtY"), REPLACEMENT_ENTRY("pointsatz", "pointsAtZ"), REPLACEMENT_ENTRY("preservealpha", "preserveAlpha"), REPLACEMENT_ENTRY("preserveaspectratio", "preserveAspectRatio"), REPLACEMENT_ENTRY("primitiveunits", "primitiveUnits"), REPLACEMENT_ENTRY("refx", "refX"), REPLACEMENT_ENTRY("refy", "refY"), REPLACEMENT_ENTRY("repeatcount", "repeatCount"), REPLACEMENT_ENTRY("repeatdur", "repeatDur"), REPLACEMENT_ENTRY("requiredextensions", "requiredExtensions"), REPLACEMENT_ENTRY("requiredfeatures", "requiredFeatures"), REPLACEMENT_ENTRY("specularconstant", "specularConstant"), REPLACEMENT_ENTRY("specularexponent", "specularExponent"), REPLACEMENT_ENTRY("spreadmethod", "spreadMethod"), REPLACEMENT_ENTRY("startoffset", "startOffset"), REPLACEMENT_ENTRY("stddeviation", "stdDeviation"), REPLACEMENT_ENTRY("stitchtiles", "stitchTiles"), REPLACEMENT_ENTRY("surfacescale", "surfaceScale"), REPLACEMENT_ENTRY("systemlanguage", "systemLanguage"), REPLACEMENT_ENTRY("tablevalues", "tableValues"), REPLACEMENT_ENTRY("targetx", "targetX"), REPLACEMENT_ENTRY("targety", "targetY"), REPLACEMENT_ENTRY("textlength", "textLength"), REPLACEMENT_ENTRY("viewbox", "viewBox"), REPLACEMENT_ENTRY("viewtarget", "viewTarget"), REPLACEMENT_ENTRY("xchannelselector", "xChannelSelector"), REPLACEMENT_ENTRY("ychannelselector", "yChannelSelector"), REPLACEMENT_ENTRY("zoomandpan", "zoomAndPan"), }; static const ReplacementEntry kSvgTagReplacements[] = { REPLACEMENT_ENTRY("altglyph", "altGlyph"), REPLACEMENT_ENTRY("altglyphdef", "altGlyphDef"), REPLACEMENT_ENTRY("altglyphitem", "altGlyphItem"), REPLACEMENT_ENTRY("animatecolor", "animateColor"), REPLACEMENT_ENTRY("animatemotion", "animateMotion"), REPLACEMENT_ENTRY("animatetransform", "animateTransform"), REPLACEMENT_ENTRY("clippath", "clipPath"), REPLACEMENT_ENTRY("feblend", "feBlend"), REPLACEMENT_ENTRY("fecolormatrix", "feColorMatrix"), REPLACEMENT_ENTRY("fecomponenttransfer", "feComponentTransfer"), REPLACEMENT_ENTRY("fecomposite", "feComposite"), REPLACEMENT_ENTRY("feconvolvematrix", "feConvolveMatrix"), REPLACEMENT_ENTRY("fediffuselighting", "feDiffuseLighting"), REPLACEMENT_ENTRY("fedisplacementmap", "feDisplacementMap"), REPLACEMENT_ENTRY("fedistantlight", "feDistantLight"), REPLACEMENT_ENTRY("feflood", "feFlood"), REPLACEMENT_ENTRY("fefunca", "feFuncA"), REPLACEMENT_ENTRY("fefuncb", "feFuncB"), REPLACEMENT_ENTRY("fefuncg", "feFuncG"), REPLACEMENT_ENTRY("fefuncr", "feFuncR"), REPLACEMENT_ENTRY("fegaussianblur", "feGaussianBlur"), REPLACEMENT_ENTRY("feimage", "feImage"), REPLACEMENT_ENTRY("femerge", "feMerge"), REPLACEMENT_ENTRY("femergenode", "feMergeNode"), REPLACEMENT_ENTRY("femorphology", "feMorphology"), REPLACEMENT_ENTRY("feoffset", "feOffset"), REPLACEMENT_ENTRY("fepointlight", "fePointLight"), REPLACEMENT_ENTRY("fespecularlighting", "feSpecularLighting"), REPLACEMENT_ENTRY("fespotlight", "feSpotLight"), REPLACEMENT_ENTRY("fetile", "feTile"), REPLACEMENT_ENTRY("feturbulence", "feTurbulence"), REPLACEMENT_ENTRY("foreignobject", "foreignObject"), REPLACEMENT_ENTRY("glyphref", "glyphRef"), REPLACEMENT_ENTRY("lineargradient", "linearGradient"), REPLACEMENT_ENTRY("radialgradient", "radialGradient"), REPLACEMENT_ENTRY("textpath", "textPath"), }; typedef struct _NamespacedAttributeReplacement { const char* from; const char* local_name; const GumboAttributeNamespaceEnum attr_namespace; } NamespacedAttributeReplacement; static const NamespacedAttributeReplacement kForeignAttributeReplacements[] = { {"xlink:actuate", "actuate", GUMBO_ATTR_NAMESPACE_XLINK}, {"xlink:actuate", "actuate", GUMBO_ATTR_NAMESPACE_XLINK}, {"xlink:href", "href", GUMBO_ATTR_NAMESPACE_XLINK}, {"xlink:role", "role", GUMBO_ATTR_NAMESPACE_XLINK}, {"xlink:show", "show", GUMBO_ATTR_NAMESPACE_XLINK}, {"xlink:title", "title", GUMBO_ATTR_NAMESPACE_XLINK}, {"xlink:type", "type", GUMBO_ATTR_NAMESPACE_XLINK}, {"xml:base", "base", GUMBO_ATTR_NAMESPACE_XML}, {"xml:lang", "lang", GUMBO_ATTR_NAMESPACE_XML}, {"xml:space", "space", GUMBO_ATTR_NAMESPACE_XML}, {"xmlns", "xmlns", GUMBO_ATTR_NAMESPACE_XMLNS}, {"xmlns:xlink", "xlink", GUMBO_ATTR_NAMESPACE_XMLNS}, }; // The "scope marker" for the list of active formatting elements. We use a // pointer to this as a generic marker element, since the particular element // scope doesn't matter. static const GumboNode kActiveFormattingScopeMarker; // The tag_is and tag_in function use true & false to denote start & end tags, // but for readability, we define constants for them here. static const bool kStartTag = true; static const bool kEndTag = false; // Because GumboStringPieces are immutable, we can't insert a character directly // into a text node. Instead, we accumulate all pending characters here and // flush them out to a text node whenever a new element is inserted. // // http://www.whatwg.org/specs/web-apps/current-work/complete/tokenization.html#insert-a-character typedef struct _TextNodeBufferState { // The accumulated text to be inserted into the current text node. GumboStringBuffer _buffer; // A pointer to the original text represented by this text node. Note that // because of foster parenting and other strange DOM manipulations, this may // include other non-text HTML tags in it; it is defined as the span of // original text from the first character in this text node to the last // character in this text node. const char* _start_original_text; // The source position of the start of this text node. GumboSourcePosition _start_position; // The type of node that will be inserted (TEXT, CDATA, or WHITESPACE). GumboNodeType _type; } TextNodeBufferState; typedef struct GumboInternalParserState { // http://www.whatwg.org/specs/web-apps/current-work/complete/parsing.html#insertion-mode GumboInsertionMode _insertion_mode; // Used for run_generic_parsing_algorithm, which needs to switch back to the // original insertion mode at its conclusion. GumboInsertionMode _original_insertion_mode; // http://www.whatwg.org/specs/web-apps/current-work/complete/parsing.html#the-stack-of-open-elements GumboVector /*GumboNode*/ _open_elements; // http://www.whatwg.org/specs/web-apps/current-work/complete/parsing.html#the-list-of-active-formatting-elements GumboVector /*GumboNode*/ _active_formatting_elements; // The stack of template insertion modes. // http://www.whatwg.org/specs/web-apps/current-work/multipage/parsing.html#the-insertion-mode GumboVector /*InsertionMode*/ _template_insertion_modes; // http://www.whatwg.org/specs/web-apps/current-work/complete/parsing.html#the-element-pointers GumboNode* _head_element; GumboNode* _form_element; // The element used as fragment context when parsing in fragment mode GumboNode* _fragment_ctx; // The flag for when the spec says "Reprocess the current token in..." bool _reprocess_current_token; // The flag for "acknowledge the token's self-closing flag". bool _self_closing_flag_acknowledged; // The "frameset-ok" flag from the spec. bool _frameset_ok; // The flag for "If the next token is a LINE FEED, ignore that token...". bool _ignore_next_linefeed; // The flag for "whenever a node would be inserted into the current node, it // must instead be foster parented". This is used for misnested table // content, which needs to be handled according to "in body" rules yet foster // parented outside of the table. // It would perhaps be more explicit to have this as a parameter to // handle_in_body and insert_element, but given how special-purpose this is // and the number of call-sites that would need to take the extra parameter, // it's easier just to have a state flag. bool _foster_parent_insertions; // The accumulated text node buffer state. TextNodeBufferState _text_node; // The current token. GumboToken* _current_token; // The way that the spec is written, the </body> and </html> tags are *always* // implicit, because encountering one of those tokens merely switches the // insertion mode out of "in body". So we have individual state flags for // those end tags that are then inspected by pop_current_node when the <body> // and <html> nodes are popped to set the GUMBO_INSERTION_IMPLICIT_END_TAG // flag appropriately. bool _closed_body_tag; bool _closed_html_tag; } GumboParserState; static bool token_has_attribute(const GumboToken* token, const char* name) { assert(token->type == GUMBO_TOKEN_START_TAG); return gumbo_get_attribute(&token->v.start_tag.attributes, name) != NULL; } // Checks if the value of the specified attribute is a case-insensitive match // for the specified string. static bool attribute_matches( const GumboVector* attributes, const char* name, const char* value) { const GumboAttribute* attr = gumbo_get_attribute(attributes, name); return attr ? strcasecmp(value, attr->value) == 0 : false; } // Checks if the value of the specified attribute is a case-sensitive match // for the specified string. static bool attribute_matches_case_sensitive( const GumboVector* attributes, const char* name, const char* value) { const GumboAttribute* attr = gumbo_get_attribute(attributes, name); return attr ? strcmp(value, attr->value) == 0 : false; } // Checks if the specified attribute vectors are identical. static bool all_attributes_match( const GumboVector* attr1, const GumboVector* attr2) { unsigned int num_unmatched_attr2_elements = attr2->length; for (unsigned int i = 0; i < attr1->length; ++i) { const GumboAttribute* attr = attr1->data[i]; if (attribute_matches_case_sensitive(attr2, attr->name, attr->value)) { --num_unmatched_attr2_elements; } else { return false; } } return num_unmatched_attr2_elements == 0; } static void set_frameset_not_ok(GumboParser* parser) { gumbo_debug("Setting frameset_ok to false.\n"); parser->_parser_state->_frameset_ok = false; } static GumboNode* create_node(GumboParser* parser, GumboNodeType type) { GumboNode* node = gumbo_parser_allocate(parser, sizeof(GumboNode)); node->parent = NULL; node->index_within_parent = -1; node->type = type; node->parse_flags = GUMBO_INSERTION_NORMAL; return node; } static GumboNode* new_document_node(GumboParser* parser) { GumboNode* document_node = create_node(parser, GUMBO_NODE_DOCUMENT); document_node->parse_flags = GUMBO_INSERTION_BY_PARSER; gumbo_vector_init(parser, 1, &document_node->v.document.children); // Must be initialized explicitly, as there's no guarantee that we'll see a // doc type token. GumboDocument* document = &document_node->v.document; document->has_doctype = false; document->name = NULL; document->public_identifier = NULL; document->system_identifier = NULL; return document_node; } static void output_init(GumboParser* parser) { GumboOutput* output = gumbo_parser_allocate(parser, sizeof(GumboOutput)); output->root = NULL; output->document = new_document_node(parser); parser->_output = output; gumbo_init_errors(parser); } static void parser_state_init(GumboParser* parser) { GumboParserState* parser_state = gumbo_parser_allocate(parser, sizeof(GumboParserState)); parser_state->_insertion_mode = GUMBO_INSERTION_MODE_INITIAL; parser_state->_reprocess_current_token = false; parser_state->_frameset_ok = true; parser_state->_ignore_next_linefeed = false; parser_state->_foster_parent_insertions = false; parser_state->_text_node._type = GUMBO_NODE_WHITESPACE; gumbo_string_buffer_init(parser, &parser_state->_text_node._buffer); gumbo_vector_init(parser, 10, &parser_state->_open_elements); gumbo_vector_init(parser, 5, &parser_state->_active_formatting_elements); gumbo_vector_init(parser, 5, &parser_state->_template_insertion_modes); parser_state->_head_element = NULL; parser_state->_form_element = NULL; parser_state->_fragment_ctx = NULL; parser_state->_current_token = NULL; parser_state->_closed_body_tag = false; parser_state->_closed_html_tag = false; parser->_parser_state = parser_state; } static void parser_state_destroy(GumboParser* parser) { GumboParserState* state = parser->_parser_state; if (state->_fragment_ctx) { destroy_node(parser, state->_fragment_ctx); } gumbo_vector_destroy(parser, &state->_active_formatting_elements); gumbo_vector_destroy(parser, &state->_open_elements); gumbo_vector_destroy(parser, &state->_template_insertion_modes); gumbo_string_buffer_destroy(parser, &state->_text_node._buffer); gumbo_parser_deallocate(parser, state); } static GumboNode* get_document_node(GumboParser* parser) { return parser->_output->document; } static bool is_fragment_parser(const GumboParser* parser) { return !!parser->_parser_state->_fragment_ctx; } // Returns the node at the bottom of the stack of open elements, or NULL if no // elements have been added yet. static GumboNode* get_current_node(GumboParser* parser) { GumboVector* open_elements = &parser->_parser_state->_open_elements; if (open_elements->length == 0) { assert(!parser->_output->root); return NULL; } assert(open_elements->length > 0); assert(open_elements->data != NULL); return open_elements->data[open_elements->length - 1]; } static GumboNode* get_adjusted_current_node(GumboParser* parser) { GumboParserState* state = parser->_parser_state; if (state->_open_elements.length == 1 && state->_fragment_ctx) { return state->_fragment_ctx; } return get_current_node(parser); } // Returns true if the given needle is in the given array of literal // GumboStringPieces. If exact_match is true, this requires that they match // exactly; otherwise, this performs a prefix match to check if any of the // elements in haystack start with needle. This always performs a // case-insensitive match. static bool is_in_static_list( const char* needle, const GumboStringPiece* haystack, bool exact_match) { for (unsigned int i = 0; haystack[i].length > 0; ++i) { if ((exact_match && !strcmp(needle, haystack[i].data)) || (!exact_match && !strcasecmp(needle, haystack[i].data))) { return true; } } return false; } static void set_insertion_mode(GumboParser* parser, GumboInsertionMode mode) { parser->_parser_state->_insertion_mode = mode; } // http://www.whatwg.org/specs/web-apps/current-work/complete/parsing.html#reset-the-insertion-mode-appropriately // This is a helper function that returns the appropriate insertion mode instead // of setting it. Returns GUMBO_INSERTION_MODE_INITIAL as a sentinel value to // indicate that there is no appropriate insertion mode, and the loop should // continue. static GumboInsertionMode get_appropriate_insertion_mode( const GumboParser* parser, int index) { const GumboVector* open_elements = &parser->_parser_state->_open_elements; const GumboNode* node = open_elements->data[index]; const bool is_last = index == 0; if (is_last && is_fragment_parser(parser)) { node = parser->_parser_state->_fragment_ctx; } assert(node->type == GUMBO_NODE_ELEMENT || node->type == GUMBO_NODE_TEMPLATE); if (node->v.element.tag_namespace != GUMBO_NAMESPACE_HTML) return is_last ? GUMBO_INSERTION_MODE_IN_BODY : GUMBO_INSERTION_MODE_INITIAL; switch (node->v.element.tag) { case GUMBO_TAG_SELECT: { if (is_last) { return GUMBO_INSERTION_MODE_IN_SELECT; } for (int i = index; i > 0; --i) { const GumboNode* ancestor = open_elements->data[i]; if (node_html_tag_is(ancestor, GUMBO_TAG_TEMPLATE)) { return GUMBO_INSERTION_MODE_IN_SELECT; } if (node_html_tag_is(ancestor, GUMBO_TAG_TABLE)) { return GUMBO_INSERTION_MODE_IN_SELECT_IN_TABLE; } } return GUMBO_INSERTION_MODE_IN_SELECT; } case GUMBO_TAG_TD: case GUMBO_TAG_TH: if (!is_last) return GUMBO_INSERTION_MODE_IN_CELL; break; case GUMBO_TAG_TR: return GUMBO_INSERTION_MODE_IN_ROW; case GUMBO_TAG_TBODY: case GUMBO_TAG_THEAD: case GUMBO_TAG_TFOOT: return GUMBO_INSERTION_MODE_IN_TABLE_BODY; case GUMBO_TAG_CAPTION: return GUMBO_INSERTION_MODE_IN_CAPTION; case GUMBO_TAG_COLGROUP: return GUMBO_INSERTION_MODE_IN_COLUMN_GROUP; case GUMBO_TAG_TABLE: return GUMBO_INSERTION_MODE_IN_TABLE; case GUMBO_TAG_TEMPLATE: return get_current_template_insertion_mode(parser); case GUMBO_TAG_HEAD: if (!is_last) return GUMBO_INSERTION_MODE_IN_HEAD; break; case GUMBO_TAG_BODY: return GUMBO_INSERTION_MODE_IN_BODY; case GUMBO_TAG_FRAMESET: return GUMBO_INSERTION_MODE_IN_FRAMESET; case GUMBO_TAG_HTML: return parser->_parser_state->_head_element ? GUMBO_INSERTION_MODE_AFTER_HEAD : GUMBO_INSERTION_MODE_BEFORE_HEAD; default: break; } return is_last ? GUMBO_INSERTION_MODE_IN_BODY : GUMBO_INSERTION_MODE_INITIAL; } // This performs the actual "reset the insertion mode" loop. static void reset_insertion_mode_appropriately(GumboParser* parser) { const GumboVector* open_elements = &parser->_parser_state->_open_elements; for (int i = open_elements->length; --i >= 0;) { GumboInsertionMode mode = get_appropriate_insertion_mode(parser, i); if (mode != GUMBO_INSERTION_MODE_INITIAL) { set_insertion_mode(parser, mode); return; } } // Should never get here, because is_last will be set on the last iteration // and will force GUMBO_INSERTION_MODE_IN_BODY. assert(0); } static GumboError* parser_add_parse_error( GumboParser* parser, const GumboToken* token) { gumbo_debug("Adding parse error.\n"); GumboError* error = gumbo_add_error(parser); if (!error) { return NULL; } error->type = GUMBO_ERR_PARSER; error->position = token->position; error->original_text = token->original_text.data; GumboParserError* extra_data = &error->v.parser; extra_data->input_type = token->type; extra_data->input_tag = GUMBO_TAG_UNKNOWN; if (token->type == GUMBO_TOKEN_START_TAG) { extra_data->input_tag = token->v.start_tag.tag; } else if (token->type == GUMBO_TOKEN_END_TAG) { extra_data->input_tag = token->v.end_tag; } GumboParserState* state = parser->_parser_state; extra_data->parser_state = state->_insertion_mode; gumbo_vector_init( parser, state->_open_elements.length, &extra_data->tag_stack); for (unsigned int i = 0; i < state->_open_elements.length; ++i) { const GumboNode* node = state->_open_elements.data[i]; assert( node->type == GUMBO_NODE_ELEMENT || node->type == GUMBO_NODE_TEMPLATE); gumbo_vector_add( parser, (void*) node->v.element.tag, &extra_data->tag_stack); } return error; } // Returns true if the specified token is either a start or end tag (specified // by is_start) with one of the tag types in the varargs list. Terminate the // list with GUMBO_TAG_LAST; this functions as a sentinel since no portion of // the spec references tags that are not in the spec. static bool tag_in( const GumboToken* token, bool is_start, const gumbo_tagset tags) { GumboTag token_tag; if (is_start && token->type == GUMBO_TOKEN_START_TAG) { token_tag = token->v.start_tag.tag; } else if (!is_start && token->type == GUMBO_TOKEN_END_TAG) { token_tag = token->v.end_tag; } else { return false; } return (token_tag < GUMBO_TAG_LAST && tags[(int) token_tag] != 0); } // Like tag_in, but for the single-tag case. static bool tag_is(const GumboToken* token, bool is_start, GumboTag tag) { if (is_start && token->type == GUMBO_TOKEN_START_TAG) { return token->v.start_tag.tag == tag; } else if (!is_start && token->type == GUMBO_TOKEN_END_TAG) { return token->v.end_tag == tag; } else { return false; } } // Like tag_in, but checks for the tag of a node, rather than a token. static bool node_tag_in_set(const GumboNode* node, const gumbo_tagset tags) { assert(node != NULL); if (node->type != GUMBO_NODE_ELEMENT && node->type != GUMBO_NODE_TEMPLATE) { return false; } return TAGSET_INCLUDES( tags, node->v.element.tag_namespace, node->v.element.tag); } // Like node_tag_in, but for the single-tag case. static bool node_qualified_tag_is( const GumboNode* node, GumboNamespaceEnum ns, GumboTag tag) { assert(node); return (node->type == GUMBO_NODE_ELEMENT || node->type == GUMBO_NODE_TEMPLATE) && node->v.element.tag == tag && node->v.element.tag_namespace == ns; } // Like node_tag_in, but for the single-tag case in the HTML namespace static bool node_html_tag_is(const GumboNode* node, GumboTag tag) { return node_qualified_tag_is(node, GUMBO_NAMESPACE_HTML, tag); } static void push_template_insertion_mode( GumboParser* parser, GumboInsertionMode mode) { gumbo_vector_add( parser, (void*) mode, &parser->_parser_state->_template_insertion_modes); } static void pop_template_insertion_mode(GumboParser* parser) { gumbo_vector_pop(parser, &parser->_parser_state->_template_insertion_modes); } // Returns the current template insertion mode. If the stack of template // insertion modes is empty, this returns GUMBO_INSERTION_MODE_INITIAL. static GumboInsertionMode get_current_template_insertion_mode( const GumboParser* parser) { GumboVector* template_insertion_modes = &parser->_parser_state->_template_insertion_modes; if (template_insertion_modes->length == 0) { return GUMBO_INSERTION_MODE_INITIAL; } return (GumboInsertionMode) template_insertion_modes->data[(template_insertion_modes->length - 1)]; } // http://www.whatwg.org/specs/web-apps/current-work/multipage/tree-construction.html#mathml-text-integration-point static bool is_mathml_integration_point(const GumboNode* node) { return node_tag_in_set( node, (gumbo_tagset){TAG_MATHML(MI), TAG_MATHML(MO), TAG_MATHML(MN), TAG_MATHML(MS), TAG_MATHML(MTEXT)}); } // http://www.whatwg.org/specs/web-apps/current-work/multipage/tree-construction.html#html-integration-point static bool is_html_integration_point(const GumboNode* node) { return node_tag_in_set(node, (gumbo_tagset){TAG_SVG(FOREIGNOBJECT), TAG_SVG(DESC), TAG_SVG(TITLE)}) || (node_qualified_tag_is( node, GUMBO_NAMESPACE_MATHML, GUMBO_TAG_ANNOTATION_XML) && (attribute_matches( &node->v.element.attributes, "encoding", "text/html") || attribute_matches(&node->v.element.attributes, "encoding", "application/xhtml+xml"))); } // This represents a place to insert a node, consisting of a target parent and a // child index within that parent. If the node should be inserted at the end of // the parent's child, index will be -1. typedef struct { GumboNode* target; int index; } InsertionLocation; InsertionLocation get_appropriate_insertion_location( GumboParser* parser, GumboNode* override_target) { InsertionLocation retval = {override_target, -1}; if (retval.target == NULL) { // No override target; default to the current node, but special-case the // root node since get_current_node() assumes the stack of open elements is // non-empty. retval.target = parser->_output->root != NULL ? get_current_node(parser) : get_document_node(parser); } if (!parser->_parser_state->_foster_parent_insertions || !node_tag_in_set(retval.target, (gumbo_tagset){TAG(TABLE), TAG(TBODY), TAG(TFOOT), TAG(THEAD), TAG(TR)})) { return retval; } // Foster-parenting case. int last_template_index = -1; int last_table_index = -1; GumboVector* open_elements = &parser->_parser_state->_open_elements; for (unsigned int i = 0; i < open_elements->length; ++i) { if (node_html_tag_is(open_elements->data[i], GUMBO_TAG_TEMPLATE)) { last_template_index = i; } if (node_html_tag_is(open_elements->data[i], GUMBO_TAG_TABLE)) { last_table_index = i; } } if (last_template_index != -1 && (last_table_index == -1 || last_template_index > last_table_index)) { retval.target = open_elements->data[last_template_index]; return retval; } if (last_table_index == -1) { retval.target = open_elements->data[0]; return retval; } GumboNode* last_table = open_elements->data[last_table_index]; if (last_table->parent != NULL) { retval.target = last_table->parent; retval.index = last_table->index_within_parent; return retval; } retval.target = open_elements->data[last_table_index - 1]; return retval; } // Appends a node to the end of its parent, setting the "parent" and // "index_within_parent" fields appropriately. static void append_node( GumboParser* parser, GumboNode* parent, GumboNode* node) { assert(node->parent == NULL); assert(node->index_within_parent == -1); GumboVector* children; if (parent->type == GUMBO_NODE_ELEMENT || parent->type == GUMBO_NODE_TEMPLATE) { children = &parent->v.element.children; } else { assert(parent->type == GUMBO_NODE_DOCUMENT); children = &parent->v.document.children; } node->parent = parent; node->index_within_parent = children->length; gumbo_vector_add(parser, (void*) node, children); assert(node->index_within_parent < children->length); } // Inserts a node at the specified InsertionLocation, updating the // "parent" and "index_within_parent" fields of it and all its siblings. // If the index of the location is -1, this calls append_node. static void insert_node( GumboParser* parser, GumboNode* node, InsertionLocation location) { assert(node->parent == NULL); assert(node->index_within_parent == -1); GumboNode* parent = location.target; int index = location.index; if (index != -1) { GumboVector* children = NULL; if (parent->type == GUMBO_NODE_ELEMENT || parent->type == GUMBO_NODE_TEMPLATE) { children = &parent->v.element.children; } else if (parent->type == GUMBO_NODE_DOCUMENT) { children = &parent->v.document.children; assert(children->length == 0); } else { assert(0); } assert(index >= 0); assert((unsigned int) index < children->length); node->parent = parent; node->index_within_parent = index; gumbo_vector_insert_at(parser, (void*) node, index, children); assert(node->index_within_parent < children->length); for (unsigned int i = index + 1; i < children->length; ++i) { GumboNode* sibling = children->data[i]; sibling->index_within_parent = i; assert(sibling->index_within_parent < children->length); } } else { append_node(parser, parent, node); } } static void maybe_flush_text_node_buffer(GumboParser* parser) { GumboParserState* state = parser->_parser_state; TextNodeBufferState* buffer_state = &state->_text_node; if (buffer_state->_buffer.length == 0) { return; } assert(buffer_state->_type == GUMBO_NODE_WHITESPACE || buffer_state->_type == GUMBO_NODE_TEXT || buffer_state->_type == GUMBO_NODE_CDATA); GumboNode* text_node = create_node(parser, buffer_state->_type); GumboText* text_node_data = &text_node->v.text; text_node_data->text = gumbo_string_buffer_to_string(parser, &buffer_state->_buffer); text_node_data->original_text.data = buffer_state->_start_original_text; text_node_data->original_text.length = state->_current_token->original_text.data - buffer_state->_start_original_text; text_node_data->start_pos = buffer_state->_start_position; gumbo_debug("Flushing text node buffer of %.*s.\n", (int) buffer_state->_buffer.length, buffer_state->_buffer.data); InsertionLocation location = get_appropriate_insertion_location(parser, NULL); if (location.target->type == GUMBO_NODE_DOCUMENT) { // The DOM does not allow Document nodes to have Text children, so per the // spec, they are dropped on the floor. destroy_node(parser, text_node); } else { insert_node(parser, text_node, location); } gumbo_string_buffer_clear(parser, &buffer_state->_buffer); buffer_state->_type = GUMBO_NODE_WHITESPACE; assert(buffer_state->_buffer.length == 0); } static void record_end_of_element( GumboToken* current_token, GumboElement* element) { element->end_pos = current_token->position; element->original_end_tag = current_token->type == GUMBO_TOKEN_END_TAG ? current_token->original_text : kGumboEmptyString; } static GumboNode* pop_current_node(GumboParser* parser) { GumboParserState* state = parser->_parser_state; maybe_flush_text_node_buffer(parser); if (state->_open_elements.length > 0) { assert(node_html_tag_is(state->_open_elements.data[0], GUMBO_TAG_HTML)); gumbo_debug("Popping %s node.\n", gumbo_normalized_tagname(get_current_node(parser)->v.element.tag)); } GumboNode* current_node = gumbo_vector_pop(parser, &state->_open_elements); if (!current_node) { assert(state->_open_elements.length == 0); return NULL; } assert(current_node->type == GUMBO_NODE_ELEMENT || current_node->type == GUMBO_NODE_TEMPLATE); bool is_closed_body_or_html_tag = (node_html_tag_is(current_node, GUMBO_TAG_BODY) && state->_closed_body_tag) || (node_html_tag_is(current_node, GUMBO_TAG_HTML) && state->_closed_html_tag); if ((state->_current_token->type != GUMBO_TOKEN_END_TAG || !node_html_tag_is(current_node, state->_current_token->v.end_tag)) && !is_closed_body_or_html_tag) { current_node->parse_flags |= GUMBO_INSERTION_IMPLICIT_END_TAG; } if (!is_closed_body_or_html_tag) { record_end_of_element(state->_current_token, ¤t_node->v.element); } return current_node; } static void append_comment_node( GumboParser* parser, GumboNode* node, const GumboToken* token) { maybe_flush_text_node_buffer(parser); GumboNode* comment = create_node(parser, GUMBO_NODE_COMMENT); comment->type = GUMBO_NODE_COMMENT; comment->parse_flags = GUMBO_INSERTION_NORMAL; comment->v.text.text = token->v.text; comment->v.text.original_text = token->original_text; comment->v.text.start_pos = token->position; append_node(parser, node, comment); } // http://www.whatwg.org/specs/web-apps/current-work/complete/tokenization.html#clear-the-stack-back-to-a-table-row-context static void clear_stack_to_table_row_context(GumboParser* parser) { while (!node_tag_in_set(get_current_node(parser), (gumbo_tagset){TAG(HTML), TAG(TR), TAG(TEMPLATE)})) { pop_current_node(parser); } } // http://www.whatwg.org/specs/web-apps/current-work/complete/tokenization.html#clear-the-stack-back-to-a-table-context static void clear_stack_to_table_context(GumboParser* parser) { while (!node_tag_in_set(get_current_node(parser), (gumbo_tagset){TAG(HTML), TAG(TABLE), TAG(TEMPLATE)})) { pop_current_node(parser); } } // http://www.whatwg.org/specs/web-apps/current-work/complete/tokenization.html#clear-the-stack-back-to-a-table-body-context void clear_stack_to_table_body_context(GumboParser* parser) { while (!node_tag_in_set(get_current_node(parser), (gumbo_tagset){TAG(HTML), TAG(TBODY), TAG(TFOOT), TAG(THEAD), TAG(TEMPLATE)})) { pop_current_node(parser); } } // Creates a parser-inserted element in the HTML namespace and returns it. static GumboNode* create_element(GumboParser* parser, GumboTag tag) { GumboNode* node = create_node(parser, GUMBO_NODE_ELEMENT); GumboElement* element = &node->v.element; gumbo_vector_init(parser, 1, &element->children); gumbo_vector_init(parser, 0, &element->attributes); element->tag = tag; element->tag_namespace = GUMBO_NAMESPACE_HTML; element->original_tag = kGumboEmptyString; element->original_end_tag = kGumboEmptyString; element->start_pos = (parser->_parser_state->_current_token) ? parser->_parser_state->_current_token->position : kGumboEmptySourcePosition; element->end_pos = kGumboEmptySourcePosition; return node; } // Constructs an element from the given start tag token. static GumboNode* create_element_from_token( GumboParser* parser, GumboToken* token, GumboNamespaceEnum tag_namespace) { assert(token->type == GUMBO_TOKEN_START_TAG); GumboTokenStartTag* start_tag = &token->v.start_tag; GumboNodeType type = (tag_namespace == GUMBO_NAMESPACE_HTML && start_tag->tag == GUMBO_TAG_TEMPLATE) ? GUMBO_NODE_TEMPLATE : GUMBO_NODE_ELEMENT; GumboNode* node = create_node(parser, type); GumboElement* element = &node->v.element; gumbo_vector_init(parser, 1, &element->children); element->attributes = start_tag->attributes; element->tag = start_tag->tag; element->tag_namespace = tag_namespace; assert(token->original_text.length >= 2); assert(token->original_text.data[0] == '<'); assert(token->original_text.data[token->original_text.length - 1] == '>'); element->original_tag = token->original_text; element->start_pos = token->position; element->original_end_tag = kGumboEmptyString; element->end_pos = kGumboEmptySourcePosition; // The element takes ownership of the attributes from the token, so any // allocated-memory fields should be nulled out. start_tag->attributes = kGumboEmptyVector; return node; } // http://www.whatwg.org/specs/web-apps/current-work/complete/tokenization.html#insert-an-html-element static void insert_element(GumboParser* parser, GumboNode* node, bool is_reconstructing_formatting_elements) { GumboParserState* state = parser->_parser_state; // NOTE(jdtang): The text node buffer must always be flushed before inserting // a node, otherwise we're handling nodes in a different order than the spec // mandated. However, one clause of the spec (character tokens in the body) // requires that we reconstruct the active formatting elements *before* adding // the character, and reconstructing the active formatting elements may itself // result in the insertion of new elements (which should be pushed onto the // stack of open elements before the buffer is flushed). We solve this (for // the time being, the spec has been rewritten for <template> and the new // version may be simpler here) with a boolean flag to this method. if (!is_reconstructing_formatting_elements) { maybe_flush_text_node_buffer(parser); } InsertionLocation location = get_appropriate_insertion_location(parser, NULL); insert_node(parser, node, location); gumbo_vector_add(parser, (void*) node, &state->_open_elements); } // Convenience method that combines create_element_from_token and // insert_element, inserting the generated element directly into the current // node. Returns the node inserted. static GumboNode* insert_element_from_token( GumboParser* parser, GumboToken* token) { GumboNode* element = create_element_from_token(parser, token, GUMBO_NAMESPACE_HTML); insert_element(parser, element, false); gumbo_debug("Inserting <%s> element (@%x) from token.\n", gumbo_normalized_tagname(element->v.element.tag), element); return element; } // Convenience method that combines create_element and insert_element, inserting // a parser-generated element of a specific tag type. Returns the node // inserted. static GumboNode* insert_element_of_tag_type( GumboParser* parser, GumboTag tag, GumboParseFlags reason) { GumboNode* element = create_element(parser, tag); element->parse_flags |= GUMBO_INSERTION_BY_PARSER | reason; insert_element(parser, element, false); gumbo_debug("Inserting %s element (@%x) from tag type.\n", gumbo_normalized_tagname(tag), element); return element; } // Convenience method for creating foreign namespaced element. Returns the node // inserted. static GumboNode* insert_foreign_element( GumboParser* parser, GumboToken* token, GumboNamespaceEnum tag_namespace) { assert(token->type == GUMBO_TOKEN_START_TAG); GumboNode* element = create_element_from_token(parser, token, tag_namespace); insert_element(parser, element, false); if (token_has_attribute(token, "xmlns") && !attribute_matches_case_sensitive(&token->v.start_tag.attributes, "xmlns", kLegalXmlns[tag_namespace])) { // TODO(jdtang): Since there're multiple possible error codes here, we // eventually need reason codes to differentiate them. parser_add_parse_error(parser, token); } if (token_has_attribute(token, "xmlns:xlink") && !attribute_matches_case_sensitive(&token->v.start_tag.attributes, "xmlns:xlink", "http://www.w3.org/1999/xlink")) { parser_add_parse_error(parser, token); } return element; } static void insert_text_token(GumboParser* parser, GumboToken* token) { assert(token->type == GUMBO_TOKEN_WHITESPACE || token->type == GUMBO_TOKEN_CHARACTER || token->type == GUMBO_TOKEN_NULL || token->type == GUMBO_TOKEN_CDATA); TextNodeBufferState* buffer_state = &parser->_parser_state->_text_node; if (buffer_state->_buffer.length == 0) { // Initialize position fields. buffer_state->_start_original_text = token->original_text.data; buffer_state->_start_position = token->position; } gumbo_string_buffer_append_codepoint( parser, token->v.character, &buffer_state->_buffer); if (token->type == GUMBO_TOKEN_CHARACTER) { buffer_state->_type = GUMBO_NODE_TEXT; } else if (token->type == GUMBO_TOKEN_CDATA) { buffer_state->_type = GUMBO_NODE_CDATA; } gumbo_debug("Inserting text token '%c'.\n", token->v.character); } // http://www.whatwg.org/specs/web-apps/current-work/complete/tokenization.html#generic-rcdata-element-parsing-algorithm static void run_generic_parsing_algorithm( GumboParser* parser, GumboToken* token, GumboTokenizerEnum lexer_state) { insert_element_from_token(parser, token); gumbo_tokenizer_set_state(parser, lexer_state); parser->_parser_state->_original_insertion_mode = parser->_parser_state->_insertion_mode; parser->_parser_state->_insertion_mode = GUMBO_INSERTION_MODE_TEXT; } static void acknowledge_self_closing_tag(GumboParser* parser) { parser->_parser_state->_self_closing_flag_acknowledged = true; } // Returns true if there's an anchor tag in the list of active formatting // elements, and fills in its index if so. static bool find_last_anchor_index(GumboParser* parser, int* anchor_index) { GumboVector* elements = &parser->_parser_state->_active_formatting_elements; for (int i = elements->length; --i >= 0;) { GumboNode* node = elements->data[i]; if (node == &kActiveFormattingScopeMarker) { return false; } if (node_html_tag_is(node, GUMBO_TAG_A)) { *anchor_index = i; return true; } } return false; } // Counts the number of open formatting elements in the list of active // formatting elements (after the last active scope marker) that have a specific // tag. If this is > 0, then earliest_matching_index will be filled in with the // index of the first such element. static int count_formatting_elements_of_tag(GumboParser* parser, const GumboNode* desired_node, int* earliest_matching_index) { const GumboElement* desired_element = &desired_node->v.element; GumboVector* elements = &parser->_parser_state->_active_formatting_elements; int num_identical_elements = 0; for (int i = elements->length; --i >= 0;) { GumboNode* node = elements->data[i]; if (node == &kActiveFormattingScopeMarker) { break; } assert(node->type == GUMBO_NODE_ELEMENT); if (node_qualified_tag_is( node, desired_element->tag_namespace, desired_element->tag) && all_attributes_match( &node->v.element.attributes, &desired_element->attributes)) { num_identical_elements++; *earliest_matching_index = i; } } return num_identical_elements; } // http://www.whatwg.org/specs/web-apps/current-work/complete/parsing.html#reconstruct-the-active-formatting-elements static void add_formatting_element(GumboParser* parser, const GumboNode* node) { assert(node == &kActiveFormattingScopeMarker || node->type == GUMBO_NODE_ELEMENT); GumboVector* elements = &parser->_parser_state->_active_formatting_elements; if (node == &kActiveFormattingScopeMarker) { gumbo_debug("Adding a scope marker.\n"); } else { gumbo_debug("Adding a formatting element.\n"); } // Hunt for identical elements. int earliest_identical_element = elements->length; int num_identical_elements = count_formatting_elements_of_tag( parser, node, &earliest_identical_element); // Noah's Ark clause: if there're at least 3, remove the earliest. if (num_identical_elements >= 3) { gumbo_debug("Noah's ark clause: removing element at %d.\n", earliest_identical_element); gumbo_vector_remove_at(parser, earliest_identical_element, elements); } gumbo_vector_add(parser, (void*) node, elements); } static bool is_open_element(GumboParser* parser, const GumboNode* node) { GumboVector* open_elements = &parser->_parser_state->_open_elements; for (unsigned int i = 0; i < open_elements->length; ++i) { if (open_elements->data[i] == node) { return true; } } return false; } // Clones attributes, tags, etc. of a node, but does not copy the content. The // clone shares no structure with the original node: all owned strings and // values are fresh copies. GumboNode* clone_node( GumboParser* parser, GumboNode* node, GumboParseFlags reason) { assert(node->type == GUMBO_NODE_ELEMENT || node->type == GUMBO_NODE_TEMPLATE); GumboNode* new_node = gumbo_parser_allocate(parser, sizeof(GumboNode)); *new_node = *node; new_node->parent = NULL; new_node->index_within_parent = -1; // Clear the GUMBO_INSERTION_IMPLICIT_END_TAG flag, as the cloned node may // have a separate end tag. new_node->parse_flags &= ~GUMBO_INSERTION_IMPLICIT_END_TAG; new_node->parse_flags |= reason | GUMBO_INSERTION_BY_PARSER; GumboElement* element = &new_node->v.element; gumbo_vector_init(parser, 1, &element->children); const GumboVector* old_attributes = &node->v.element.attributes; gumbo_vector_init(parser, old_attributes->length, &element->attributes); for (unsigned int i = 0; i < old_attributes->length; ++i) { const GumboAttribute* old_attr = old_attributes->data[i]; GumboAttribute* attr = gumbo_parser_allocate(parser, sizeof(GumboAttribute)); *attr = *old_attr; attr->name = gumbo_copy_stringz(parser, old_attr->name); attr->value = gumbo_copy_stringz(parser, old_attr->value); gumbo_vector_add(parser, attr, &element->attributes); } return new_node; } // "Reconstruct active formatting elements" part of the spec. // This implementation is based on the html5lib translation from the mess of // GOTOs in the spec to reasonably structured programming. // http://code.google.com/p/html5lib/source/browse/python/html5lib/treebuilders/_base.py static void reconstruct_active_formatting_elements(GumboParser* parser) { GumboVector* elements = &parser->_parser_state->_active_formatting_elements; // Step 1 if (elements->length == 0) { return; } // Step 2 & 3 unsigned int i = elements->length - 1; GumboNode* element = elements->data[i]; if (element == &kActiveFormattingScopeMarker || is_open_element(parser, element)) { return; } // Step 6 do { if (i == 0) { // Step 4 i = -1; // Incremented to 0 below. break; } // Step 5 element = elements->data[--i]; } while (element != &kActiveFormattingScopeMarker && !is_open_element(parser, element)); ++i; gumbo_debug("Reconstructing elements from %d on %s parent.\n", i, gumbo_normalized_tagname(get_current_node(parser)->v.element.tag)); for (; i < elements->length; ++i) { // Step 7 & 8. assert(elements->length > 0); assert(i < elements->length); element = elements->data[i]; assert(element != &kActiveFormattingScopeMarker); GumboNode* clone = clone_node( parser, element, GUMBO_INSERTION_RECONSTRUCTED_FORMATTING_ELEMENT); // Step 9. InsertionLocation location = get_appropriate_insertion_location(parser, NULL); insert_node(parser, clone, location); gumbo_vector_add( parser, (void*) clone, &parser->_parser_state->_open_elements); // Step 10. elements->data[i] = clone; gumbo_debug("Reconstructed %s element at %d.\n", gumbo_normalized_tagname(clone->v.element.tag), i); } } static void clear_active_formatting_elements(GumboParser* parser) { GumboVector* elements = &parser->_parser_state->_active_formatting_elements; int num_elements_cleared = 0; const GumboNode* node; do { node = gumbo_vector_pop(parser, elements); ++num_elements_cleared; } while (node && node != &kActiveFormattingScopeMarker); gumbo_debug("Cleared %d elements from active formatting list.\n", num_elements_cleared); } // http://www.whatwg.org/specs/web-apps/current-work/complete/tokenization.html#the-initial-insertion-mode static GumboQuirksModeEnum compute_quirks_mode( const GumboTokenDocType* doctype) { if (doctype->force_quirks || strcmp(doctype->name, kDoctypeHtml.data) || is_in_static_list( doctype->public_identifier, kQuirksModePublicIdPrefixes, false) || is_in_static_list( doctype->public_identifier, kQuirksModePublicIdExactMatches, true) || is_in_static_list( doctype->system_identifier, kQuirksModeSystemIdExactMatches, true) || (is_in_static_list(doctype->public_identifier, kLimitedQuirksRequiresSystemIdPublicIdPrefixes, false) && !doctype->has_system_identifier)) { return GUMBO_DOCTYPE_QUIRKS; } else if (is_in_static_list(doctype->public_identifier, kLimitedQuirksPublicIdPrefixes, false) || (is_in_static_list(doctype->public_identifier, kLimitedQuirksRequiresSystemIdPublicIdPrefixes, false) && doctype->has_system_identifier)) { return GUMBO_DOCTYPE_LIMITED_QUIRKS; } return GUMBO_DOCTYPE_NO_QUIRKS; } // The following functions are all defined by the "has an element in __ scope" // sections of the HTML5 spec: // http://www.whatwg.org/specs/web-apps/current-work/multipage/parsing.html#has-an-element-in-the-specific-scope // The basic idea behind them is that they check for an element of the given // qualified name, contained within a scope formed by a set of other qualified // names. For example, "has an element in list scope" looks for an element of // the given qualified name within the nearest enclosing <ol> or <ul>, along // with a bunch of generic element types that serve to "firewall" their content // from the rest of the document. Note that because of the way the spec is // written, // all elements are expected to be in the HTML namespace static bool has_an_element_in_specific_scope(GumboParser* parser, int expected_size, const GumboTag* expected, bool negate, const gumbo_tagset tags) { GumboVector* open_elements = &parser->_parser_state->_open_elements; for (int i = open_elements->length; --i >= 0;) { const GumboNode* node = open_elements->data[i]; if (node->type != GUMBO_NODE_ELEMENT && node->type != GUMBO_NODE_TEMPLATE) continue; GumboTag node_tag = node->v.element.tag; GumboNamespaceEnum node_ns = node->v.element.tag_namespace; for (int j = 0; j < expected_size; ++j) { if (node_tag == expected[j] && node_ns == GUMBO_NAMESPACE_HTML) return true; } bool found = TAGSET_INCLUDES(tags, node_ns, node_tag); if (negate != found) return false; } return false; } // Checks for the presence of an open element of the specified tag type. static bool has_open_element(GumboParser* parser, GumboTag tag) { return has_an_element_in_specific_scope( parser, 1, &tag, false, (gumbo_tagset){TAG(HTML)}); } // http://www.whatwg.org/specs/web-apps/current-work/multipage/parsing.html#has-an-element-in-scope static bool has_an_element_in_scope(GumboParser* parser, GumboTag tag) { return has_an_element_in_specific_scope(parser, 1, &tag, false, (gumbo_tagset){TAG(APPLET), TAG(CAPTION), TAG(HTML), TAG(TABLE), TAG(TD), TAG(TH), TAG(MARQUEE), TAG(OBJECT), TAG(TEMPLATE), TAG_MATHML(MI), TAG_MATHML(MO), TAG_MATHML(MN), TAG_MATHML(MS), TAG_MATHML(MTEXT), TAG_MATHML(ANNOTATION_XML), TAG_SVG(FOREIGNOBJECT), TAG_SVG(DESC), TAG_SVG(TITLE)}); } // Like "has an element in scope", but for the specific case of looking for a // unique target node, not for any node with a given tag name. This duplicates // much of the algorithm from has_an_element_in_specific_scope because the // predicate is different when checking for an exact node, and it's easier & // faster just to duplicate the code for this one case than to try and // parameterize it. static bool has_node_in_scope(GumboParser* parser, const GumboNode* node) { GumboVector* open_elements = &parser->_parser_state->_open_elements; for (int i = open_elements->length; --i >= 0;) { const GumboNode* current = open_elements->data[i]; if (current == node) { return true; } if (current->type != GUMBO_NODE_ELEMENT && current->type != GUMBO_NODE_TEMPLATE) { continue; } if (node_tag_in_set(current, (gumbo_tagset){TAG(APPLET), TAG(CAPTION), TAG(HTML), TAG(TABLE), TAG(TD), TAG(TH), TAG(MARQUEE), TAG(OBJECT), TAG(TEMPLATE), TAG_MATHML(MI), TAG_MATHML(MO), TAG_MATHML(MN), TAG_MATHML(MS), TAG_MATHML(MTEXT), TAG_MATHML(ANNOTATION_XML), TAG_SVG(FOREIGNOBJECT), TAG_SVG(DESC), TAG_SVG(TITLE)})) { return false; } } assert(false); return false; } // Like has_an_element_in_scope, but restricts the expected qualified name to a // range of possible qualified names instead of just a single one. static bool has_an_element_in_scope_with_tagname( GumboParser* parser, int expected_len, const GumboTag expected[]) { return has_an_element_in_specific_scope(parser, expected_len, expected, false, (gumbo_tagset){TAG(APPLET), TAG(CAPTION), TAG(HTML), TAG(TABLE), TAG(TD), TAG(TH), TAG(MARQUEE), TAG(OBJECT), TAG(TEMPLATE), TAG_MATHML(MI), TAG_MATHML(MO), TAG_MATHML(MN), TAG_MATHML(MS), TAG_MATHML(MTEXT), TAG_MATHML(ANNOTATION_XML), TAG_SVG(FOREIGNOBJECT), TAG_SVG(DESC), TAG_SVG(TITLE)}); } // http://www.whatwg.org/specs/web-apps/current-work/multipage/parsing.html#has-an-element-in-list-item-scope static bool has_an_element_in_list_scope(GumboParser* parser, GumboTag tag) { return has_an_element_in_specific_scope(parser, 1, &tag, false, (gumbo_tagset){TAG(APPLET), TAG(CAPTION), TAG(HTML), TAG(TABLE), TAG(TD), TAG(TH), TAG(MARQUEE), TAG(OBJECT), TAG(TEMPLATE), TAG_MATHML(MI), TAG_MATHML(MO), TAG_MATHML(MN), TAG_MATHML(MS), TAG_MATHML(MTEXT), TAG_MATHML(ANNOTATION_XML), TAG_SVG(FOREIGNOBJECT), TAG_SVG(DESC), TAG_SVG(TITLE), TAG(OL), TAG(UL)}); } // http://www.whatwg.org/specs/web-apps/current-work/multipage/parsing.html#has-an-element-in-button-scope static bool has_an_element_in_button_scope(GumboParser* parser, GumboTag tag) { return has_an_element_in_specific_scope(parser, 1, &tag, false, (gumbo_tagset){TAG(APPLET), TAG(CAPTION), TAG(HTML), TAG(TABLE), TAG(TD), TAG(TH), TAG(MARQUEE), TAG(OBJECT), TAG(TEMPLATE), TAG_MATHML(MI), TAG_MATHML(MO), TAG_MATHML(MN), TAG_MATHML(MS), TAG_MATHML(MTEXT), TAG_MATHML(ANNOTATION_XML), TAG_SVG(FOREIGNOBJECT), TAG_SVG(DESC), TAG_SVG(TITLE), TAG(BUTTON)}); } // http://www.whatwg.org/specs/web-apps/current-work/multipage/parsing.html#has-an-element-in-table-scope static bool has_an_element_in_table_scope(GumboParser* parser, GumboTag tag) { return has_an_element_in_specific_scope(parser, 1, &tag, false, (gumbo_tagset){TAG(HTML), TAG(TABLE), TAG(TEMPLATE)}); } // http://www.whatwg.org/specs/web-apps/current-work/multipage/parsing.html#has-an-element-in-select-scope static bool has_an_element_in_select_scope(GumboParser* parser, GumboTag tag) { return has_an_element_in_specific_scope( parser, 1, &tag, true, (gumbo_tagset){TAG(OPTGROUP), TAG(OPTION)}); } // http://www.whatwg.org/specs/web-apps/current-work/complete/tokenization.html#generate-implied-end-tags // "exception" is the "element to exclude from the process" listed in the spec. // Pass GUMBO_TAG_LAST to not exclude any of them. static void generate_implied_end_tags(GumboParser* parser, GumboTag exception) { for (; node_tag_in_set(get_current_node(parser), (gumbo_tagset){TAG(DD), TAG(DT), TAG(LI), TAG(OPTION), TAG(OPTGROUP), TAG(P), TAG(RP), TAG(RB), TAG(RT), TAG(RTC)}) && !node_html_tag_is(get_current_node(parser), exception); pop_current_node(parser)) ; } // This is the "generate all implied end tags thoroughly" clause of the spec. // https://html.spec.whatwg.org/multipage/syntax.html#closing-elements-that-have-implied-end-tags static void generate_all_implied_end_tags_thoroughly(GumboParser* parser) { for ( ; node_tag_in_set(get_current_node(parser), (gumbo_tagset){TAG(CAPTION), TAG(COLGROUP), TAG(DD), TAG(DT), TAG(LI), TAG(OPTION), TAG(OPTGROUP), TAG(P), TAG(RP), TAG(RT), TAG(RTC), TAG(TBODY), TAG(TD), TAG(TFOOT), TAG(TH), TAG(HEAD), TAG(TR)}); pop_current_node(parser)) ; } // This factors out the clauses relating to "act as if an end tag token with tag // name "table" had been seen. Returns true if there's a table element in table // scope which was successfully closed, false if not and the token should be // ignored. Does not add parse errors; callers should handle that. static bool close_table(GumboParser* parser) { if (!has_an_element_in_table_scope(parser, GUMBO_TAG_TABLE)) { return false; } GumboNode* node = pop_current_node(parser); while (!node_html_tag_is(node, GUMBO_TAG_TABLE)) { node = pop_current_node(parser); } reset_insertion_mode_appropriately(parser); return true; } // This factors out the clauses relating to "act as if an end tag token with tag // name `cell_tag` had been seen". static bool close_table_cell( GumboParser* parser, const GumboToken* token, GumboTag cell_tag) { bool result = true; generate_implied_end_tags(parser, GUMBO_TAG_LAST); const GumboNode* node = get_current_node(parser); if (!node_html_tag_is(node, cell_tag)) { parser_add_parse_error(parser, token); result = false; } do { node = pop_current_node(parser); } while (!node_html_tag_is(node, cell_tag)); clear_active_formatting_elements(parser); set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_ROW); return result; } // http://www.whatwg.org/specs/web-apps/current-work/complete/tokenization.html#close-the-cell // This holds the logic to determine whether we should close a <td> or a <th>. static bool close_current_cell(GumboParser* parser, const GumboToken* token) { if (has_an_element_in_table_scope(parser, GUMBO_TAG_TD)) { assert(!has_an_element_in_table_scope(parser, GUMBO_TAG_TH)); return close_table_cell(parser, token, GUMBO_TAG_TD); } else { assert(has_an_element_in_table_scope(parser, GUMBO_TAG_TH)); return close_table_cell(parser, token, GUMBO_TAG_TH); } } // This factors out the "act as if an end tag of tag name 'select' had been // seen" clause of the spec, since it's referenced in several places. It pops // all nodes from the stack until the current <select> has been closed, then // resets the insertion mode appropriately. static void close_current_select(GumboParser* parser) { GumboNode* node = pop_current_node(parser); while (!node_html_tag_is(node, GUMBO_TAG_SELECT)) { node = pop_current_node(parser); } reset_insertion_mode_appropriately(parser); } // The list of nodes in the "special" category: // http://www.whatwg.org/specs/web-apps/current-work/complete/parsing.html#special static bool is_special_node(const GumboNode* node) { assert(node->type == GUMBO_NODE_ELEMENT || node->type == GUMBO_NODE_TEMPLATE); return node_tag_in_set(node, (gumbo_tagset){TAG(ADDRESS), TAG(APPLET), TAG(AREA), TAG(ARTICLE), TAG(ASIDE), TAG(BASE), TAG(BASEFONT), TAG(BGSOUND), TAG(BLOCKQUOTE), TAG(BODY), TAG(BR), TAG(BUTTON), TAG(CAPTION), TAG(CENTER), TAG(COL), TAG(COLGROUP), TAG(MENUITEM), TAG(DD), TAG(DETAILS), TAG(DIR), TAG(DIV), TAG(DL), TAG(DT), TAG(EMBED), TAG(FIELDSET), TAG(FIGCAPTION), TAG(FIGURE), TAG(FOOTER), TAG(FORM), TAG(FRAME), TAG(FRAMESET), TAG(H1), TAG(H2), TAG(H3), TAG(H4), TAG(H5), TAG(H6), TAG(HEAD), TAG(HEADER), TAG(HGROUP), TAG(HR), TAG(HTML), TAG(IFRAME), TAG(IMG), TAG(INPUT), TAG(ISINDEX), TAG(LI), TAG(LINK), TAG(LISTING), TAG(MARQUEE), TAG(MENU), TAG(META), TAG(NAV), TAG(NOEMBED), TAG(NOFRAMES), TAG(NOSCRIPT), TAG(OBJECT), TAG(OL), TAG(P), TAG(PARAM), TAG(PLAINTEXT), TAG(PRE), TAG(SCRIPT), TAG(SECTION), TAG(SELECT), TAG(STYLE), TAG(SUMMARY), TAG(TABLE), TAG(TBODY), TAG(TD), TAG(TEMPLATE), TAG(TEXTAREA), TAG(TFOOT), TAG(TH), TAG(THEAD), TAG(TITLE), TAG(TR), TAG(UL), TAG(WBR), TAG(XMP), TAG_MATHML(MI), TAG_MATHML(MO), TAG_MATHML(MN), TAG_MATHML(MS), TAG_MATHML(MTEXT), TAG_MATHML(ANNOTATION_XML), TAG_SVG(FOREIGNOBJECT), TAG_SVG(DESC)}); } // Implicitly closes currently open elements until it reaches an element with // the // specified qualified name. If the elements closed are in the set handled by // generate_implied_end_tags, this is normal operation and this function returns // true. Otherwise, a parse error is recorded and this function returns false. static bool implicitly_close_tags(GumboParser* parser, GumboToken* token, GumboNamespaceEnum target_ns, GumboTag target) { bool result = true; generate_implied_end_tags(parser, target); if (!node_qualified_tag_is(get_current_node(parser), target_ns, target)) { parser_add_parse_error(parser, token); while ( !node_qualified_tag_is(get_current_node(parser), target_ns, target)) { pop_current_node(parser); } result = false; } assert(node_qualified_tag_is(get_current_node(parser), target_ns, target)); pop_current_node(parser); return result; } // If the stack of open elements has a <p> tag in button scope, this acts as if // a </p> tag was encountered, implicitly closing tags. Returns false if a // parse error occurs. This is a convenience function because this particular // clause appears several times in the spec. static bool maybe_implicitly_close_p_tag( GumboParser* parser, GumboToken* token) { if (has_an_element_in_button_scope(parser, GUMBO_TAG_P)) { return implicitly_close_tags( parser, token, GUMBO_NAMESPACE_HTML, GUMBO_TAG_P); } return true; } // Convenience function to encapsulate the logic for closing <li> or <dd>/<dt> // tags. Pass true to is_li for handling <li> tags, false for <dd> and <dt>. static void maybe_implicitly_close_list_tag( GumboParser* parser, GumboToken* token, bool is_li) { GumboParserState* state = parser->_parser_state; state->_frameset_ok = false; for (int i = state->_open_elements.length; --i >= 0;) { const GumboNode* node = state->_open_elements.data[i]; bool is_list_tag = is_li ? node_html_tag_is(node, GUMBO_TAG_LI) : node_tag_in_set(node, (gumbo_tagset){TAG(DD), TAG(DT)}); if (is_list_tag) { implicitly_close_tags( parser, token, node->v.element.tag_namespace, node->v.element.tag); return; } if (is_special_node(node) && !node_tag_in_set( node, (gumbo_tagset){TAG(ADDRESS), TAG(DIV), TAG(P)})) { return; } } } static void merge_attributes( GumboParser* parser, GumboToken* token, GumboNode* node) { assert(token->type == GUMBO_TOKEN_START_TAG); assert(node->type == GUMBO_NODE_ELEMENT); const GumboVector* token_attr = &token->v.start_tag.attributes; GumboVector* node_attr = &node->v.element.attributes; for (unsigned int i = 0; i < token_attr->length; ++i) { GumboAttribute* attr = token_attr->data[i]; if (!gumbo_get_attribute(node_attr, attr->name)) { // Ownership of the attribute is transferred by this gumbo_vector_add, // so it has to be nulled out of the original token so it doesn't get // double-deleted. gumbo_vector_add(parser, attr, node_attr); token_attr->data[i] = NULL; } } // When attributes are merged, it means the token has been ignored and merged // with another token, so we need to free its memory. The attributes that are // transferred need to be nulled-out in the vector above so that they aren't // double-deleted. gumbo_token_destroy(parser, token); #ifndef NDEBUG // Mark this sentinel so the assertion in the main loop knows it's been // destroyed. token->v.start_tag.attributes = kGumboEmptyVector; #endif } const char* gumbo_normalize_svg_tagname(const GumboStringPiece* tag) { for (size_t i = 0; i < sizeof(kSvgTagReplacements) / sizeof(ReplacementEntry); ++i) { const ReplacementEntry* entry = &kSvgTagReplacements[i]; if (gumbo_string_equals_ignore_case(tag, &entry->from)) { return entry->to.data; } } return NULL; } // http://www.whatwg.org/specs/web-apps/current-work/multipage/tree-construction.html#adjust-foreign-attributes // This destructively modifies any matching attributes on the token and sets the // namespace appropriately. static void adjust_foreign_attributes(GumboParser* parser, GumboToken* token) { assert(token->type == GUMBO_TOKEN_START_TAG); const GumboVector* attributes = &token->v.start_tag.attributes; for (size_t i = 0; i < sizeof(kForeignAttributeReplacements) / sizeof(NamespacedAttributeReplacement); ++i) { const NamespacedAttributeReplacement* entry = &kForeignAttributeReplacements[i]; GumboAttribute* attr = gumbo_get_attribute(attributes, entry->from); if (!attr) { continue; } gumbo_parser_deallocate(parser, (void*) attr->name); attr->attr_namespace = entry->attr_namespace; attr->name = gumbo_copy_stringz(parser, entry->local_name); } } // http://www.whatwg.org/specs/web-apps/current-work/complete/tokenization.html#adjust-svg-attributes // This destructively modifies any matching attributes on the token. static void adjust_svg_attributes(GumboParser* parser, GumboToken* token) { assert(token->type == GUMBO_TOKEN_START_TAG); const GumboVector* attributes = &token->v.start_tag.attributes; for (size_t i = 0; i < sizeof(kSvgAttributeReplacements) / sizeof(ReplacementEntry); ++i) { const ReplacementEntry* entry = &kSvgAttributeReplacements[i]; GumboAttribute* attr = gumbo_get_attribute(attributes, entry->from.data); if (!attr) { continue; } gumbo_parser_deallocate(parser, (void*) attr->name); attr->name = gumbo_copy_stringz(parser, entry->to.data); } } // http://www.whatwg.org/specs/web-apps/current-work/complete/tokenization.html#adjust-mathml-attributes // Note that this may destructively modify the token with the new attribute // value. static void adjust_mathml_attributes(GumboParser* parser, GumboToken* token) { assert(token->type == GUMBO_TOKEN_START_TAG); GumboAttribute* attr = gumbo_get_attribute(&token->v.start_tag.attributes, "definitionurl"); if (!attr) { return; } gumbo_parser_deallocate(parser, (void*) attr->name); attr->name = gumbo_copy_stringz(parser, "definitionURL"); } static bool doctype_matches(const GumboTokenDocType* doctype, const GumboStringPiece* public_id, const GumboStringPiece* system_id, bool allow_missing_system_id) { return !strcmp(doctype->public_identifier, public_id->data) && (allow_missing_system_id || doctype->has_system_identifier) && !strcmp(doctype->system_identifier, system_id->data); } static bool maybe_add_doctype_error( GumboParser* parser, const GumboToken* token) { const GumboTokenDocType* doctype = &token->v.doc_type; bool html_doctype = !strcmp(doctype->name, kDoctypeHtml.data); if ((!html_doctype || doctype->has_public_identifier || (doctype->has_system_identifier && !strcmp( doctype->system_identifier, kSystemIdLegacyCompat.data))) && !(html_doctype && (doctype_matches(doctype, &kPublicIdHtml4_0, &kSystemIdRecHtml4_0, true) || doctype_matches(doctype, &kPublicIdHtml4_01, &kSystemIdHtml4, true) || doctype_matches(doctype, &kPublicIdXhtml1_0, &kSystemIdXhtmlStrict1_1, false) || doctype_matches(doctype, &kPublicIdXhtml1_1, &kSystemIdXhtml1_1, false)))) { parser_add_parse_error(parser, token); return false; } return true; } static void remove_from_parent(GumboParser* parser, GumboNode* node) { if (!node->parent) { // The node may not have a parent if, for example, it is a newly-cloned copy // of an active formatting element. DOM manipulations continue with the // orphaned fragment of the DOM tree until it's appended/foster-parented to // the common ancestor at the end of the adoption agency algorithm. return; } assert(node->parent->type == GUMBO_NODE_ELEMENT); GumboVector* children = &node->parent->v.element.children; int index = gumbo_vector_index_of(children, node); assert(index != -1); gumbo_vector_remove_at(parser, index, children); node->parent = NULL; node->index_within_parent = -1; for (unsigned int i = index; i < children->length; ++i) { GumboNode* child = children->data[i]; child->index_within_parent = i; } } // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-end.html#an-introduction-to-error-handling-and-strange-cases-in-the-parser // Also described in the "in body" handling for end formatting tags. static bool adoption_agency_algorithm( GumboParser* parser, GumboToken* token, GumboTag subject) { GumboParserState* state = parser->_parser_state; gumbo_debug("Entering adoption agency algorithm.\n"); // Step 1. GumboNode* current_node = get_current_node(parser); if (current_node->v.element.tag_namespace == GUMBO_NAMESPACE_HTML && current_node->v.element.tag == subject && gumbo_vector_index_of( &state->_active_formatting_elements, current_node) == -1) { pop_current_node(parser); return false; } // Steps 2-4 & 20: for (unsigned int i = 0; i < 8; ++i) { // Step 5. GumboNode* formatting_node = NULL; int formatting_node_in_open_elements = -1; for (int j = state->_active_formatting_elements.length; --j >= 0;) { GumboNode* current_node = state->_active_formatting_elements.data[j]; if (current_node == &kActiveFormattingScopeMarker) { gumbo_debug("Broke on scope marker; aborting.\n"); // Last scope marker; abort the algorithm. return false; } if (node_html_tag_is(current_node, subject)) { // Found it. formatting_node = current_node; formatting_node_in_open_elements = gumbo_vector_index_of(&state->_open_elements, formatting_node); gumbo_debug("Formatting element of tag %s at %d.\n", gumbo_normalized_tagname(subject), formatting_node_in_open_elements); break; } } if (!formatting_node) { // No matching tag; not a parse error outright, but fall through to the // "any other end tag" clause (which may potentially add a parse error, // but not always). gumbo_debug("No active formatting elements; aborting.\n"); return false; } // Step 6 if (formatting_node_in_open_elements == -1) { gumbo_debug("Formatting node not on stack of open elements.\n"); parser_add_parse_error(parser, token); gumbo_vector_remove( parser, formatting_node, &state->_active_formatting_elements); return false; } // Step 7 if (!has_an_element_in_scope(parser, formatting_node->v.element.tag)) { parser_add_parse_error(parser, token); gumbo_debug("Element not in scope.\n"); return false; } // Step 8 if (formatting_node != get_current_node(parser)) { parser_add_parse_error(parser, token); // But continue onwards. } assert(formatting_node); assert(!node_html_tag_is(formatting_node, GUMBO_TAG_HTML)); assert(!node_html_tag_is(formatting_node, GUMBO_TAG_BODY)); // Step 9 & 10 GumboNode* furthest_block = NULL; for (unsigned int j = formatting_node_in_open_elements; j < state->_open_elements.length; ++j) { assert(j > 0); GumboNode* current = state->_open_elements.data[j]; if (is_special_node(current)) { // Step 9. furthest_block = current; break; } } if (!furthest_block) { // Step 10. while (get_current_node(parser) != formatting_node) { pop_current_node(parser); } // And the formatting element itself. pop_current_node(parser); gumbo_vector_remove( parser, formatting_node, &state->_active_formatting_elements); return false; } assert(!node_html_tag_is(furthest_block, GUMBO_TAG_HTML)); assert(furthest_block); // Step 11. // Elements may be moved and reparented by this algorithm, so // common_ancestor is not necessarily the same as formatting_node->parent. GumboNode* common_ancestor = state->_open_elements.data[gumbo_vector_index_of(&state->_open_elements, formatting_node) - 1]; gumbo_debug("Common ancestor tag = %s, furthest block tag = %s.\n", gumbo_normalized_tagname(common_ancestor->v.element.tag), gumbo_normalized_tagname(furthest_block->v.element.tag)); // Step 12. int bookmark = gumbo_vector_index_of( &state->_active_formatting_elements, formatting_node) + 1; gumbo_debug("Bookmark at %d.\n", bookmark); // Step 13. GumboNode* node = furthest_block; GumboNode* last_node = furthest_block; // Must be stored explicitly, in case node is removed from the stack of open // elements, to handle step 9.4. int saved_node_index = gumbo_vector_index_of(&state->_open_elements, node); assert(saved_node_index > 0); // Step 13.1. for (int j = 0;;) { // Step 13.2. ++j; // Step 13.3. int node_index = gumbo_vector_index_of(&state->_open_elements, node); gumbo_debug( "Current index: %d, last index: %d.\n", node_index, saved_node_index); if (node_index == -1) { node_index = saved_node_index; } saved_node_index = --node_index; assert(node_index > 0); assert((unsigned int) node_index < state->_open_elements.capacity); node = state->_open_elements.data[node_index]; assert(node->parent); if (node == formatting_node) { // Step 13.4. break; } int formatting_index = gumbo_vector_index_of(&state->_active_formatting_elements, node); if (j > 3 && formatting_index != -1) { // Step 13.5. gumbo_debug("Removing formatting element at %d.\n", formatting_index); gumbo_vector_remove_at( parser, formatting_index, &state->_active_formatting_elements); // Removing the element shifts all indices over by one, so we may need // to move the bookmark. if (formatting_index < bookmark) { --bookmark; gumbo_debug("Moving bookmark to %d.\n", bookmark); } continue; } if (formatting_index == -1) { // Step 13.6. gumbo_vector_remove_at(parser, node_index, &state->_open_elements); continue; } // Step 13.7. // "common ancestor as the intended parent" doesn't actually mean insert // it into the common ancestor; that happens below. node = clone_node(parser, node, GUMBO_INSERTION_ADOPTION_AGENCY_CLONED); assert(formatting_index >= 0); state->_active_formatting_elements.data[formatting_index] = node; assert(node_index >= 0); state->_open_elements.data[node_index] = node; // Step 13.8. if (last_node == furthest_block) { bookmark = formatting_index + 1; gumbo_debug("Bookmark moved to %d.\n", bookmark); assert((unsigned int) bookmark <= state->_active_formatting_elements.length); } // Step 13.9. last_node->parse_flags |= GUMBO_INSERTION_ADOPTION_AGENCY_MOVED; remove_from_parent(parser, last_node); append_node(parser, node, last_node); // Step 13.10. last_node = node; } // Step 13.11. // Step 14. gumbo_debug("Removing %s node from parent ", gumbo_normalized_tagname(last_node->v.element.tag)); remove_from_parent(parser, last_node); last_node->parse_flags |= GUMBO_INSERTION_ADOPTION_AGENCY_MOVED; InsertionLocation location = get_appropriate_insertion_location(parser, common_ancestor); gumbo_debug("and inserting it into %s.\n", gumbo_normalized_tagname(location.target->v.element.tag)); insert_node(parser, last_node, location); // Step 15. GumboNode* new_formatting_node = clone_node( parser, formatting_node, GUMBO_INSERTION_ADOPTION_AGENCY_CLONED); formatting_node->parse_flags |= GUMBO_INSERTION_IMPLICIT_END_TAG; // Step 16. Instead of appending nodes one-by-one, we swap the children // vector of furthest_block with the empty children of new_formatting_node, // reducing memory traffic and allocations. We still have to reset their // parent pointers, though. GumboVector temp = new_formatting_node->v.element.children; new_formatting_node->v.element.children = furthest_block->v.element.children; furthest_block->v.element.children = temp; temp = new_formatting_node->v.element.children; for (unsigned int i = 0; i < temp.length; ++i) { GumboNode* child = temp.data[i]; child->parent = new_formatting_node; } // Step 17. append_node(parser, furthest_block, new_formatting_node); // Step 18. // If the formatting node was before the bookmark, it may shift over all // indices after it, so we need to explicitly find the index and possibly // adjust the bookmark. int formatting_node_index = gumbo_vector_index_of( &state->_active_formatting_elements, formatting_node); assert(formatting_node_index != -1); if (formatting_node_index < bookmark) { gumbo_debug( "Formatting node at %d is before bookmark at %d; decrementing.\n", formatting_node_index, bookmark); --bookmark; } gumbo_vector_remove_at( parser, formatting_node_index, &state->_active_formatting_elements); assert(bookmark >= 0); assert((unsigned int) bookmark <= state->_active_formatting_elements.length); gumbo_vector_insert_at(parser, new_formatting_node, bookmark, &state->_active_formatting_elements); // Step 19. gumbo_vector_remove(parser, formatting_node, &state->_open_elements); int insert_at = gumbo_vector_index_of(&state->_open_elements, furthest_block) + 1; assert(insert_at >= 0); assert((unsigned int) insert_at <= state->_open_elements.length); gumbo_vector_insert_at( parser, new_formatting_node, insert_at, &state->_open_elements); } // Step 20. return true; } // This is here to clean up memory when the spec says "Ignore current token." static void ignore_token(GumboParser* parser) { GumboToken* token = parser->_parser_state->_current_token; // Ownership of the token's internal buffers are normally transferred to the // element, but if no element is emitted (as happens in non-verbatim-mode // when a token is ignored), we need to free it here to prevent a memory // leak. gumbo_token_destroy(parser, token); #ifndef NDEBUG if (token->type == GUMBO_TOKEN_START_TAG) { // Mark this sentinel so the assertion in the main loop knows it's been // destroyed. token->v.start_tag.attributes = kGumboEmptyVector; } #endif } // http://www.whatwg.org/specs/web-apps/current-work/complete/the-end.html static void finish_parsing(GumboParser* parser) { gumbo_debug("Finishing parsing"); maybe_flush_text_node_buffer(parser); GumboParserState* state = parser->_parser_state; for (GumboNode* node = pop_current_node(parser); node; node = pop_current_node(parser)) { if ((node_html_tag_is(node, GUMBO_TAG_BODY) && state->_closed_body_tag) || (node_html_tag_is(node, GUMBO_TAG_HTML) && state->_closed_html_tag)) { continue; } node->parse_flags |= GUMBO_INSERTION_IMPLICIT_END_TAG; } while (pop_current_node(parser)) ; // Pop them all. } static bool handle_initial(GumboParser* parser, GumboToken* token) { GumboDocument* document = &get_document_node(parser)->v.document; if (token->type == GUMBO_TOKEN_WHITESPACE) { ignore_token(parser); return true; } else if (token->type == GUMBO_TOKEN_COMMENT) { append_comment_node(parser, get_document_node(parser), token); return true; } else if (token->type == GUMBO_TOKEN_DOCTYPE) { document->has_doctype = true; document->name = token->v.doc_type.name; document->public_identifier = token->v.doc_type.public_identifier; document->system_identifier = token->v.doc_type.system_identifier; document->doc_type_quirks_mode = compute_quirks_mode(&token->v.doc_type); set_insertion_mode(parser, GUMBO_INSERTION_MODE_BEFORE_HTML); return maybe_add_doctype_error(parser, token); } parser_add_parse_error(parser, token); document->doc_type_quirks_mode = GUMBO_DOCTYPE_QUIRKS; set_insertion_mode(parser, GUMBO_INSERTION_MODE_BEFORE_HTML); parser->_parser_state->_reprocess_current_token = true; return true; } // http://www.whatwg.org/specs/web-apps/current-work/complete/tokenization.html#the-before-html-insertion-mode static bool handle_before_html(GumboParser* parser, GumboToken* token) { if (token->type == GUMBO_TOKEN_DOCTYPE) { parser_add_parse_error(parser, token); ignore_token(parser); return false; } else if (token->type == GUMBO_TOKEN_COMMENT) { append_comment_node(parser, get_document_node(parser), token); return true; } else if (token->type == GUMBO_TOKEN_WHITESPACE) { ignore_token(parser); return true; } else if (tag_is(token, kStartTag, GUMBO_TAG_HTML)) { GumboNode* html_node = insert_element_from_token(parser, token); parser->_output->root = html_node; set_insertion_mode(parser, GUMBO_INSERTION_MODE_BEFORE_HEAD); return true; } else if (token->type == GUMBO_TOKEN_END_TAG && !tag_in(token, false, (gumbo_tagset){TAG(HEAD), TAG(BODY), TAG(HTML), TAG(BR)})) { parser_add_parse_error(parser, token); ignore_token(parser); return false; } else { GumboNode* html_node = insert_element_of_tag_type( parser, GUMBO_TAG_HTML, GUMBO_INSERTION_IMPLIED); assert(html_node); parser->_output->root = html_node; set_insertion_mode(parser, GUMBO_INSERTION_MODE_BEFORE_HEAD); parser->_parser_state->_reprocess_current_token = true; return true; } } // http://www.whatwg.org/specs/web-apps/current-work/complete/tokenization.html#the-before-head-insertion-mode static bool handle_before_head(GumboParser* parser, GumboToken* token) { if (token->type == GUMBO_TOKEN_DOCTYPE) { parser_add_parse_error(parser, token); ignore_token(parser); return false; } else if (token->type == GUMBO_TOKEN_COMMENT) { append_comment_node(parser, get_current_node(parser), token); return true; } else if (token->type == GUMBO_TOKEN_WHITESPACE) { ignore_token(parser); return true; } else if (tag_is(token, kStartTag, GUMBO_TAG_HEAD)) { GumboNode* node = insert_element_from_token(parser, token); set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_HEAD); parser->_parser_state->_head_element = node; return true; } else if (token->type == GUMBO_TOKEN_END_TAG && !tag_in(token, false, (gumbo_tagset){TAG(HEAD), TAG(BODY), TAG(HTML), TAG(BR)})) { parser_add_parse_error(parser, token); ignore_token(parser); return false; } else { GumboNode* node = insert_element_of_tag_type( parser, GUMBO_TAG_HEAD, GUMBO_INSERTION_IMPLIED); set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_HEAD); parser->_parser_state->_head_element = node; parser->_parser_state->_reprocess_current_token = true; return true; } } // Forward declarations because of mutual dependencies. static bool handle_token(GumboParser* parser, GumboToken* token); static bool handle_in_body(GumboParser* parser, GumboToken* token); // http://www.whatwg.org/specs/web-apps/current-work/complete/tokenization.html#parsing-main-inhead static bool handle_in_head(GumboParser* parser, GumboToken* token) { if (token->type == GUMBO_TOKEN_WHITESPACE) { insert_text_token(parser, token); return true; } else if (token->type == GUMBO_TOKEN_DOCTYPE) { parser_add_parse_error(parser, token); ignore_token(parser); return false; } else if (token->type == GUMBO_TOKEN_COMMENT) { append_comment_node(parser, get_current_node(parser), token); return true; } else if (tag_is(token, kStartTag, GUMBO_TAG_HTML)) { return handle_in_body(parser, token); } else if (tag_in(token, kStartTag, (gumbo_tagset){TAG(BASE), TAG(BASEFONT), TAG(BGSOUND), TAG(MENUITEM), TAG(LINK)})) { insert_element_from_token(parser, token); pop_current_node(parser); acknowledge_self_closing_tag(parser); return true; } else if (tag_is(token, kStartTag, GUMBO_TAG_META)) { insert_element_from_token(parser, token); pop_current_node(parser); acknowledge_self_closing_tag(parser); // NOTE(jdtang): Gumbo handles only UTF-8, so the encoding clause of the // spec doesn't apply. If clients want to handle meta-tag re-encoding, they // should specifically look for that string in the document and re-encode it // before passing to Gumbo. return true; } else if (tag_is(token, kStartTag, GUMBO_TAG_TITLE)) { run_generic_parsing_algorithm(parser, token, GUMBO_LEX_RCDATA); return true; } else if (tag_in( token, kStartTag, (gumbo_tagset){TAG(NOFRAMES), TAG(STYLE)})) { run_generic_parsing_algorithm(parser, token, GUMBO_LEX_RAWTEXT); return true; } else if (tag_is(token, kStartTag, GUMBO_TAG_NOSCRIPT)) { insert_element_from_token(parser, token); set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_HEAD_NOSCRIPT); return true; } else if (tag_is(token, kStartTag, GUMBO_TAG_SCRIPT)) { run_generic_parsing_algorithm(parser, token, GUMBO_LEX_SCRIPT); return true; } else if (tag_is(token, kEndTag, GUMBO_TAG_HEAD)) { GumboNode* head = pop_current_node(parser); AVOID_UNUSED_VARIABLE_WARNING(head); assert(node_html_tag_is(head, GUMBO_TAG_HEAD)); set_insertion_mode(parser, GUMBO_INSERTION_MODE_AFTER_HEAD); return true; } else if (tag_in(token, kEndTag, (gumbo_tagset){TAG(BODY), TAG(HTML), TAG(BR)})) { pop_current_node(parser); set_insertion_mode(parser, GUMBO_INSERTION_MODE_AFTER_HEAD); parser->_parser_state->_reprocess_current_token = true; return true; } else if (tag_is(token, kStartTag, GUMBO_TAG_TEMPLATE)) { insert_element_from_token(parser, token); add_formatting_element(parser, &kActiveFormattingScopeMarker); parser->_parser_state->_frameset_ok = false; set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_TEMPLATE); push_template_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_TEMPLATE); return true; } else if (tag_is(token, kEndTag, GUMBO_TAG_TEMPLATE)) { if (!has_open_element(parser, GUMBO_TAG_TEMPLATE)) { parser_add_parse_error(parser, token); ignore_token(parser); return false; } generate_all_implied_end_tags_thoroughly(parser); bool success = true; if (!node_html_tag_is(get_current_node(parser), GUMBO_TAG_TEMPLATE)) { parser_add_parse_error(parser, token); success = false; } while (!node_html_tag_is(pop_current_node(parser), GUMBO_TAG_TEMPLATE)) ; clear_active_formatting_elements(parser); pop_template_insertion_mode(parser); reset_insertion_mode_appropriately(parser); return success; } else if (tag_is(token, kStartTag, GUMBO_TAG_HEAD) || (token->type == GUMBO_TOKEN_END_TAG)) { parser_add_parse_error(parser, token); ignore_token(parser); return false; } else { pop_current_node(parser); set_insertion_mode(parser, GUMBO_INSERTION_MODE_AFTER_HEAD); parser->_parser_state->_reprocess_current_token = true; return true; } return true; } // http://www.whatwg.org/specs/web-apps/current-work/complete/tokenization.html#parsing-main-inheadnoscript static bool handle_in_head_noscript(GumboParser* parser, GumboToken* token) { if (token->type == GUMBO_TOKEN_DOCTYPE) { parser_add_parse_error(parser, token); return false; } else if (tag_is(token, kStartTag, GUMBO_TAG_HTML)) { return handle_in_body(parser, token); } else if (tag_is(token, kEndTag, GUMBO_TAG_NOSCRIPT)) { const GumboNode* node = pop_current_node(parser); assert(node_html_tag_is(node, GUMBO_TAG_NOSCRIPT)); AVOID_UNUSED_VARIABLE_WARNING(node); set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_HEAD); return true; } else if (token->type == GUMBO_TOKEN_WHITESPACE || token->type == GUMBO_TOKEN_COMMENT || tag_in(token, kStartTag, (gumbo_tagset){TAG(BASEFONT), TAG(BGSOUND), TAG(LINK), TAG(META), TAG(NOFRAMES), TAG(STYLE)})) { return handle_in_head(parser, token); } else if (tag_in( token, kStartTag, (gumbo_tagset){TAG(HEAD), TAG(NOSCRIPT)}) || (token->type == GUMBO_TOKEN_END_TAG && !tag_is(token, kEndTag, GUMBO_TAG_BR))) { parser_add_parse_error(parser, token); ignore_token(parser); return false; } else { parser_add_parse_error(parser, token); const GumboNode* node = pop_current_node(parser); assert(node_html_tag_is(node, GUMBO_TAG_NOSCRIPT)); AVOID_UNUSED_VARIABLE_WARNING(node); set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_HEAD); parser->_parser_state->_reprocess_current_token = true; return false; } } // http://www.whatwg.org/specs/web-apps/current-work/complete/tokenization.html#the-after-head-insertion-mode static bool handle_after_head(GumboParser* parser, GumboToken* token) { GumboParserState* state = parser->_parser_state; if (token->type == GUMBO_TOKEN_WHITESPACE) { insert_text_token(parser, token); return true; } else if (token->type == GUMBO_TOKEN_DOCTYPE) { parser_add_parse_error(parser, token); ignore_token(parser); return false; } else if (token->type == GUMBO_TOKEN_COMMENT) { append_comment_node(parser, get_current_node(parser), token); return true; } else if (tag_is(token, kStartTag, GUMBO_TAG_HTML)) { return handle_in_body(parser, token); } else if (tag_is(token, kStartTag, GUMBO_TAG_BODY)) { insert_element_from_token(parser, token); state->_frameset_ok = false; set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_BODY); return true; } else if (tag_is(token, kStartTag, GUMBO_TAG_FRAMESET)) { insert_element_from_token(parser, token); set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_FRAMESET); return true; } else if (tag_in(token, kStartTag, (gumbo_tagset){TAG(BASE), TAG(BASEFONT), TAG(BGSOUND), TAG(LINK), TAG(META), TAG(NOFRAMES), TAG(SCRIPT), TAG(STYLE), TAG(TEMPLATE), TAG(TITLE)})) { parser_add_parse_error(parser, token); assert(state->_head_element != NULL); // This must be flushed before we push the head element on, as there may be // pending character tokens that should be attached to the root. maybe_flush_text_node_buffer(parser); gumbo_vector_add(parser, state->_head_element, &state->_open_elements); bool result = handle_in_head(parser, token); gumbo_vector_remove(parser, state->_head_element, &state->_open_elements); return result; } else if (tag_is(token, kEndTag, GUMBO_TAG_TEMPLATE)) { return handle_in_head(parser, token); } else if (tag_is(token, kStartTag, GUMBO_TAG_HEAD) || (token->type == GUMBO_TOKEN_END_TAG && !tag_in(token, kEndTag, (gumbo_tagset){TAG(BODY), TAG(HTML), TAG(BR)}))) { parser_add_parse_error(parser, token); ignore_token(parser); return false; } else { insert_element_of_tag_type(parser, GUMBO_TAG_BODY, GUMBO_INSERTION_IMPLIED); set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_BODY); state->_reprocess_current_token = true; return true; } } static void destroy_node(GumboParser* parser, GumboNode* node) { switch (node->type) { case GUMBO_NODE_DOCUMENT: { GumboDocument* doc = &node->v.document; for (unsigned int i = 0; i < doc->children.length; ++i) { destroy_node(parser, doc->children.data[i]); } gumbo_parser_deallocate(parser, (void*) doc->children.data); gumbo_parser_deallocate(parser, (void*) doc->name); gumbo_parser_deallocate(parser, (void*) doc->public_identifier); gumbo_parser_deallocate(parser, (void*) doc->system_identifier); } break; case GUMBO_NODE_TEMPLATE: case GUMBO_NODE_ELEMENT: for (unsigned int i = 0; i < node->v.element.attributes.length; ++i) { gumbo_destroy_attribute(parser, node->v.element.attributes.data[i]); } gumbo_parser_deallocate(parser, node->v.element.attributes.data); for (unsigned int i = 0; i < node->v.element.children.length; ++i) { destroy_node(parser, node->v.element.children.data[i]); } gumbo_parser_deallocate(parser, node->v.element.children.data); break; case GUMBO_NODE_TEXT: case GUMBO_NODE_CDATA: case GUMBO_NODE_COMMENT: case GUMBO_NODE_WHITESPACE: gumbo_parser_deallocate(parser, (void*) node->v.text.text); break; } gumbo_parser_deallocate(parser, node); } // http://www.whatwg.org/specs/web-apps/current-work/complete/tokenization.html#parsing-main-inbody static bool handle_in_body(GumboParser* parser, GumboToken* token) { GumboParserState* state = parser->_parser_state; assert(state->_open_elements.length > 0); if (token->type == GUMBO_TOKEN_NULL) { parser_add_parse_error(parser, token); ignore_token(parser); return false; } else if (token->type == GUMBO_TOKEN_WHITESPACE) { reconstruct_active_formatting_elements(parser); insert_text_token(parser, token); return true; } else if (token->type == GUMBO_TOKEN_CHARACTER || token->type == GUMBO_TOKEN_CDATA) { reconstruct_active_formatting_elements(parser); insert_text_token(parser, token); set_frameset_not_ok(parser); return true; } else if (token->type == GUMBO_TOKEN_COMMENT) { append_comment_node(parser, get_current_node(parser), token); return true; } else if (token->type == GUMBO_TOKEN_DOCTYPE) { parser_add_parse_error(parser, token); ignore_token(parser); return false; } else if (tag_is(token, kStartTag, GUMBO_TAG_HTML)) { parser_add_parse_error(parser, token); if (has_open_element(parser, GUMBO_TAG_TEMPLATE)) { ignore_token(parser); return false; } assert(parser->_output->root != NULL); assert(parser->_output->root->type == GUMBO_NODE_ELEMENT); merge_attributes(parser, token, parser->_output->root); return false; } else if (tag_in(token, kStartTag, (gumbo_tagset){TAG(BASE), TAG(BASEFONT), TAG(BGSOUND), TAG(MENUITEM), TAG(LINK), TAG(META), TAG(NOFRAMES), TAG(SCRIPT), TAG(STYLE), TAG(TEMPLATE), TAG(TITLE)}) || tag_is(token, kEndTag, GUMBO_TAG_TEMPLATE)) { return handle_in_head(parser, token); } else if (tag_is(token, kStartTag, GUMBO_TAG_BODY)) { parser_add_parse_error(parser, token); if (state->_open_elements.length < 2 || !node_html_tag_is(state->_open_elements.data[1], GUMBO_TAG_BODY) || has_open_element(parser, GUMBO_TAG_TEMPLATE)) { ignore_token(parser); return false; } state->_frameset_ok = false; merge_attributes(parser, token, state->_open_elements.data[1]); return false; } else if (tag_is(token, kStartTag, GUMBO_TAG_FRAMESET)) { parser_add_parse_error(parser, token); if (state->_open_elements.length < 2 || !node_html_tag_is(state->_open_elements.data[1], GUMBO_TAG_BODY) || !state->_frameset_ok) { ignore_token(parser); return false; } // Save the body node for later removal. GumboNode* body_node = state->_open_elements.data[1]; // Pop all nodes except root HTML element. GumboNode* node; do { node = pop_current_node(parser); } while (node != state->_open_elements.data[1]); // Removing & destroying the body node is going to kill any nodes that have // been added to the list of active formatting elements, and so we should // clear it to prevent a use-after-free if the list of active formatting // elements is reconstructed afterwards. This may happen if whitespace // follows the </frameset>. clear_active_formatting_elements(parser); // Remove the body node. We may want to factor this out into a generic // helper, but right now this is the only code that needs to do this. GumboVector* children = &parser->_output->root->v.element.children; for (unsigned int i = 0; i < children->length; ++i) { if (children->data[i] == body_node) { gumbo_vector_remove_at(parser, i, children); break; } } destroy_node(parser, body_node); // Insert the <frameset>, and switch the insertion mode. insert_element_from_token(parser, token); set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_FRAMESET); return true; } else if (token->type == GUMBO_TOKEN_EOF) { for (unsigned int i = 0; i < state->_open_elements.length; ++i) { if (!node_tag_in_set(state->_open_elements.data[i], (gumbo_tagset){TAG(DD), TAG(DT), TAG(LI), TAG(P), TAG(TBODY), TAG(TD), TAG(TFOOT), TAG(TH), TAG(THEAD), TAG(TR), TAG(BODY), TAG(HTML)})) { parser_add_parse_error(parser, token); } } if (get_current_template_insertion_mode(parser) != GUMBO_INSERTION_MODE_INITIAL) { return handle_in_template(parser, token); } return true; } else if (tag_in(token, kEndTag, (gumbo_tagset){TAG(BODY), TAG(HTML)})) { if (!has_an_element_in_scope(parser, GUMBO_TAG_BODY)) { parser_add_parse_error(parser, token); ignore_token(parser); return false; } bool success = true; for (unsigned int i = 0; i < state->_open_elements.length; ++i) { if (!node_tag_in_set(state->_open_elements.data[i], (gumbo_tagset){TAG(DD), TAG(DT), TAG(LI), TAG(OPTGROUP), TAG(OPTION), TAG(P), TAG(RB), TAG(RP), TAG(RT), TAG(RTC), TAG(TBODY), TAG(TD), TAG(TFOOT), TAG(TH), TAG(THEAD), TAG(TR), TAG(BODY), TAG(HTML)})) { parser_add_parse_error(parser, token); success = false; break; } } set_insertion_mode(parser, GUMBO_INSERTION_MODE_AFTER_BODY); if (tag_is(token, kEndTag, GUMBO_TAG_HTML)) { parser->_parser_state->_reprocess_current_token = true; } else { GumboNode* body = state->_open_elements.data[1]; assert(node_html_tag_is(body, GUMBO_TAG_BODY)); record_end_of_element(state->_current_token, &body->v.element); } return success; } else if (tag_in(token, kStartTag, (gumbo_tagset){TAG(ADDRESS), TAG(ARTICLE), TAG(ASIDE), TAG(BLOCKQUOTE), TAG(CENTER), TAG(DETAILS), TAG(DIR), TAG(DIV), TAG(DL), TAG(FIELDSET), TAG(FIGCAPTION), TAG(FIGURE), TAG(FOOTER), TAG(HEADER), TAG(HGROUP), TAG(MENU), TAG(MAIN), TAG(NAV), TAG(OL), TAG(P), TAG(SECTION), TAG(SUMMARY), TAG(UL)})) { bool result = maybe_implicitly_close_p_tag(parser, token); insert_element_from_token(parser, token); return result; } else if (tag_in(token, kStartTag, (gumbo_tagset){TAG(H1), TAG(H2), TAG(H3), TAG(H4), TAG(H5), TAG(H6)})) { bool result = maybe_implicitly_close_p_tag(parser, token); if (node_tag_in_set( get_current_node(parser), (gumbo_tagset){TAG(H1), TAG(H2), TAG(H3), TAG(H4), TAG(H5), TAG(H6)})) { parser_add_parse_error(parser, token); pop_current_node(parser); result = false; } insert_element_from_token(parser, token); return result; } else if (tag_in(token, kStartTag, (gumbo_tagset){TAG(PRE), TAG(LISTING)})) { bool result = maybe_implicitly_close_p_tag(parser, token); insert_element_from_token(parser, token); state->_ignore_next_linefeed = true; state->_frameset_ok = false; return result; } else if (tag_is(token, kStartTag, GUMBO_TAG_FORM)) { if (state->_form_element != NULL && !has_open_element(parser, GUMBO_TAG_TEMPLATE)) { gumbo_debug("Ignoring nested form.\n"); parser_add_parse_error(parser, token); ignore_token(parser); return false; } bool result = maybe_implicitly_close_p_tag(parser, token); GumboNode* form_element = insert_element_from_token(parser, token); if (!has_open_element(parser, GUMBO_TAG_TEMPLATE)) { state->_form_element = form_element; } return result; } else if (tag_is(token, kStartTag, GUMBO_TAG_LI)) { maybe_implicitly_close_list_tag(parser, token, true); bool result = maybe_implicitly_close_p_tag(parser, token); insert_element_from_token(parser, token); return result; } else if (tag_in(token, kStartTag, (gumbo_tagset){TAG(DD), TAG(DT)})) { maybe_implicitly_close_list_tag(parser, token, false); bool result = maybe_implicitly_close_p_tag(parser, token); insert_element_from_token(parser, token); return result; } else if (tag_is(token, kStartTag, GUMBO_TAG_PLAINTEXT)) { bool result = maybe_implicitly_close_p_tag(parser, token); insert_element_from_token(parser, token); gumbo_tokenizer_set_state(parser, GUMBO_LEX_PLAINTEXT); return result; } else if (tag_is(token, kStartTag, GUMBO_TAG_BUTTON)) { if (has_an_element_in_scope(parser, GUMBO_TAG_BUTTON)) { parser_add_parse_error(parser, token); implicitly_close_tags( parser, token, GUMBO_NAMESPACE_HTML, GUMBO_TAG_BUTTON); state->_reprocess_current_token = true; return false; } reconstruct_active_formatting_elements(parser); insert_element_from_token(parser, token); state->_frameset_ok = false; return true; } else if (tag_in(token, kEndTag, (gumbo_tagset){TAG(ADDRESS), TAG(ARTICLE), TAG(ASIDE), TAG(BLOCKQUOTE), TAG(BUTTON), TAG(CENTER), TAG(DETAILS), TAG(DIR), TAG(DIV), TAG(DL), TAG(FIELDSET), TAG(FIGCAPTION), TAG(FIGURE), TAG(FOOTER), TAG(HEADER), TAG(HGROUP), TAG(LISTING), TAG(MAIN), TAG(MENU), TAG(NAV), TAG(OL), TAG(PRE), TAG(SECTION), TAG(SUMMARY), TAG(UL)})) { GumboTag tag = token->v.end_tag; if (!has_an_element_in_scope(parser, tag)) { parser_add_parse_error(parser, token); ignore_token(parser); return false; } implicitly_close_tags( parser, token, GUMBO_NAMESPACE_HTML, token->v.end_tag); return true; } else if (tag_is(token, kEndTag, GUMBO_TAG_FORM)) { if (has_open_element(parser, GUMBO_TAG_TEMPLATE)) { if (!has_an_element_in_scope(parser, GUMBO_TAG_FORM)) { parser_add_parse_error(parser, token); ignore_token(parser); return false; } bool success = true; generate_implied_end_tags(parser, GUMBO_TAG_LAST); if (!node_html_tag_is(get_current_node(parser), GUMBO_TAG_FORM)) { parser_add_parse_error(parser, token); return false; } while (!node_html_tag_is(pop_current_node(parser), GUMBO_TAG_FORM)) ; return success; } else { bool result = true; const GumboNode* node = state->_form_element; assert(!node || node->type == GUMBO_NODE_ELEMENT); state->_form_element = NULL; if (!node || !has_node_in_scope(parser, node)) { gumbo_debug("Closing an unopened form.\n"); parser_add_parse_error(parser, token); ignore_token(parser); return false; } // This differs from implicitly_close_tags because we remove *only* the // <form> element; other nodes are left in scope. generate_implied_end_tags(parser, GUMBO_TAG_LAST); if (get_current_node(parser) != node) { parser_add_parse_error(parser, token); result = false; } GumboVector* open_elements = &state->_open_elements; int index = gumbo_vector_index_of(open_elements, node); assert(index >= 0); gumbo_vector_remove_at(parser, index, open_elements); return result; } } else if (tag_is(token, kEndTag, GUMBO_TAG_P)) { if (!has_an_element_in_button_scope(parser, GUMBO_TAG_P)) { parser_add_parse_error(parser, token); // reconstruct_active_formatting_elements(parser); insert_element_of_tag_type( parser, GUMBO_TAG_P, GUMBO_INSERTION_CONVERTED_FROM_END_TAG); state->_reprocess_current_token = true; return false; } return implicitly_close_tags( parser, token, GUMBO_NAMESPACE_HTML, GUMBO_TAG_P); } else if (tag_is(token, kEndTag, GUMBO_TAG_LI)) { if (!has_an_element_in_list_scope(parser, GUMBO_TAG_LI)) { parser_add_parse_error(parser, token); ignore_token(parser); return false; } return implicitly_close_tags( parser, token, GUMBO_NAMESPACE_HTML, GUMBO_TAG_LI); } else if (tag_in(token, kEndTag, (gumbo_tagset){TAG(DD), TAG(DT)})) { assert(token->type == GUMBO_TOKEN_END_TAG); GumboTag token_tag = token->v.end_tag; if (!has_an_element_in_scope(parser, token_tag)) { parser_add_parse_error(parser, token); ignore_token(parser); return false; } return implicitly_close_tags( parser, token, GUMBO_NAMESPACE_HTML, token_tag); } else if (tag_in(token, kEndTag, (gumbo_tagset){TAG(H1), TAG(H2), TAG(H3), TAG(H4), TAG(H5), TAG(H6)})) { if (!has_an_element_in_scope_with_tagname( parser, 6, (GumboTag[]){GUMBO_TAG_H1, GUMBO_TAG_H2, GUMBO_TAG_H3, GUMBO_TAG_H4, GUMBO_TAG_H5, GUMBO_TAG_H6})) { // No heading open; ignore the token entirely. parser_add_parse_error(parser, token); ignore_token(parser); return false; } else { generate_implied_end_tags(parser, GUMBO_TAG_LAST); const GumboNode* current_node = get_current_node(parser); bool success = node_html_tag_is(current_node, token->v.end_tag); if (!success) { // There're children of the heading currently open; close them below and // record a parse error. // TODO(jdtang): Add a way to distinguish this error case from the one // above. parser_add_parse_error(parser, token); } do { current_node = pop_current_node(parser); } while (!node_tag_in_set( current_node, (gumbo_tagset){TAG(H1), TAG(H2), TAG(H3), TAG(H4), TAG(H5), TAG(H6)})); return success; } } else if (tag_is(token, kStartTag, GUMBO_TAG_A)) { bool success = true; int last_a; int has_matching_a = find_last_anchor_index(parser, &last_a); if (has_matching_a) { assert(has_matching_a == 1); parser_add_parse_error(parser, token); adoption_agency_algorithm(parser, token, GUMBO_TAG_A); // The adoption agency algorithm usually removes all instances of <a> // from the list of active formatting elements, but in case it doesn't, // we're supposed to do this. (The conditions where it might not are // listed in the spec.) if (find_last_anchor_index(parser, &last_a)) { void* last_element = gumbo_vector_remove_at( parser, last_a, &state->_active_formatting_elements); gumbo_vector_remove(parser, last_element, &state->_open_elements); } success = false; } reconstruct_active_formatting_elements(parser); add_formatting_element(parser, insert_element_from_token(parser, token)); return success; } else if (tag_in(token, kStartTag, (gumbo_tagset){TAG(B), TAG(BIG), TAG(CODE), TAG(EM), TAG(FONT), TAG(I), TAG(S), TAG(SMALL), TAG(STRIKE), TAG(STRONG), TAG(TT), TAG(U)})) { reconstruct_active_formatting_elements(parser); add_formatting_element(parser, insert_element_from_token(parser, token)); return true; } else if (tag_is(token, kStartTag, GUMBO_TAG_NOBR)) { bool result = true; reconstruct_active_formatting_elements(parser); if (has_an_element_in_scope(parser, GUMBO_TAG_NOBR)) { result = false; parser_add_parse_error(parser, token); adoption_agency_algorithm(parser, token, GUMBO_TAG_NOBR); reconstruct_active_formatting_elements(parser); } insert_element_from_token(parser, token); add_formatting_element(parser, get_current_node(parser)); return result; } else if (tag_in(token, kEndTag, (gumbo_tagset){TAG(A), TAG(B), TAG(BIG), TAG(CODE), TAG(EM), TAG(FONT), TAG(I), TAG(NOBR), TAG(S), TAG(SMALL), TAG(STRIKE), TAG(STRONG), TAG(TT), TAG(U)})) { return adoption_agency_algorithm(parser, token, token->v.end_tag); } else if (tag_in(token, kStartTag, (gumbo_tagset){TAG(APPLET), TAG(MARQUEE), TAG(OBJECT)})) { reconstruct_active_formatting_elements(parser); insert_element_from_token(parser, token); add_formatting_element(parser, &kActiveFormattingScopeMarker); set_frameset_not_ok(parser); return true; } else if (tag_in(token, kEndTag, (gumbo_tagset){TAG(APPLET), TAG(MARQUEE), TAG(OBJECT)})) { GumboTag token_tag = token->v.end_tag; if (!has_an_element_in_table_scope(parser, token_tag)) { parser_add_parse_error(parser, token); ignore_token(parser); return false; } implicitly_close_tags(parser, token, GUMBO_NAMESPACE_HTML, token_tag); clear_active_formatting_elements(parser); return true; } else if (tag_is(token, kStartTag, GUMBO_TAG_TABLE)) { if (get_document_node(parser)->v.document.doc_type_quirks_mode != GUMBO_DOCTYPE_QUIRKS) { maybe_implicitly_close_p_tag(parser, token); } insert_element_from_token(parser, token); set_frameset_not_ok(parser); set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_TABLE); return true; } else if (tag_in(token, kStartTag, (gumbo_tagset){TAG(AREA), TAG(BR), TAG(EMBED), TAG(IMG), TAG(IMAGE), TAG(KEYGEN), TAG(WBR)})) { bool success = true; if (tag_is(token, kStartTag, GUMBO_TAG_IMAGE)) { success = false; parser_add_parse_error(parser, token); token->v.start_tag.tag = GUMBO_TAG_IMG; } reconstruct_active_formatting_elements(parser); GumboNode* node = insert_element_from_token(parser, token); if (tag_is(token, kStartTag, GUMBO_TAG_IMAGE)) { success = false; parser_add_parse_error(parser, token); node->v.element.tag = GUMBO_TAG_IMG; node->parse_flags |= GUMBO_INSERTION_FROM_IMAGE; } pop_current_node(parser); acknowledge_self_closing_tag(parser); set_frameset_not_ok(parser); return success; } else if (tag_is(token, kStartTag, GUMBO_TAG_INPUT)) { if (!attribute_matches(&token->v.start_tag.attributes, "type", "hidden")) { // Must be before the element is inserted, as that takes ownership of the // token's attribute vector. set_frameset_not_ok(parser); } reconstruct_active_formatting_elements(parser); insert_element_from_token(parser, token); pop_current_node(parser); acknowledge_self_closing_tag(parser); return true; } else if (tag_in(token, kStartTag, (gumbo_tagset){TAG(PARAM), TAG(SOURCE), TAG(TRACK)})) { insert_element_from_token(parser, token); pop_current_node(parser); acknowledge_self_closing_tag(parser); return true; } else if (tag_is(token, kStartTag, GUMBO_TAG_HR)) { bool result = maybe_implicitly_close_p_tag(parser, token); insert_element_from_token(parser, token); pop_current_node(parser); acknowledge_self_closing_tag(parser); set_frameset_not_ok(parser); return result; } else if (tag_is(token, kStartTag, GUMBO_TAG_ISINDEX)) { parser_add_parse_error(parser, token); if (parser->_parser_state->_form_element != NULL && !has_open_element(parser, GUMBO_TAG_TEMPLATE)) { ignore_token(parser); return false; } acknowledge_self_closing_tag(parser); maybe_implicitly_close_p_tag(parser, token); set_frameset_not_ok(parser); GumboVector* token_attrs = &token->v.start_tag.attributes; GumboAttribute* prompt_attr = gumbo_get_attribute(token_attrs, "prompt"); GumboAttribute* action_attr = gumbo_get_attribute(token_attrs, "action"); GumboAttribute* name_attr = gumbo_get_attribute(token_attrs, "name"); GumboNode* form = insert_element_of_tag_type( parser, GUMBO_TAG_FORM, GUMBO_INSERTION_FROM_ISINDEX); if (!has_open_element(parser, GUMBO_TAG_TEMPLATE)) { parser->_parser_state->_form_element = form; } if (action_attr) { gumbo_vector_add(parser, action_attr, &form->v.element.attributes); } insert_element_of_tag_type( parser, GUMBO_TAG_HR, GUMBO_INSERTION_FROM_ISINDEX); pop_current_node(parser); // <hr> insert_element_of_tag_type( parser, GUMBO_TAG_LABEL, GUMBO_INSERTION_FROM_ISINDEX); TextNodeBufferState* text_state = &parser->_parser_state->_text_node; text_state->_start_original_text = token->original_text.data; text_state->_start_position = token->position; text_state->_type = GUMBO_NODE_TEXT; if (prompt_attr) { int prompt_attr_length = strlen(prompt_attr->value); gumbo_string_buffer_destroy(parser, &text_state->_buffer); text_state->_buffer.data = gumbo_copy_stringz(parser, prompt_attr->value); text_state->_buffer.length = prompt_attr_length; text_state->_buffer.capacity = prompt_attr_length + 1; gumbo_destroy_attribute(parser, prompt_attr); } else { GumboStringPiece prompt_text = GUMBO_STRING("This is a searchable index. Enter search keywords: "); gumbo_string_buffer_append_string( parser, &prompt_text, &text_state->_buffer); } GumboNode* input = insert_element_of_tag_type( parser, GUMBO_TAG_INPUT, GUMBO_INSERTION_FROM_ISINDEX); for (unsigned int i = 0; i < token_attrs->length; ++i) { GumboAttribute* attr = token_attrs->data[i]; if (attr != prompt_attr && attr != action_attr && attr != name_attr) { gumbo_vector_add(parser, attr, &input->v.element.attributes); } token_attrs->data[i] = NULL; } // All attributes have been successfully transferred and nulled out at this // point, so the call to ignore_token will free the memory for it without // touching the attributes. ignore_token(parser); // The name attribute, if present, should be destroyed since it's ignored // when copying over. The action attribute should be kept since it's moved // to the form. if (name_attr) { gumbo_destroy_attribute(parser, name_attr); } GumboAttribute* name = gumbo_parser_allocate(parser, sizeof(GumboAttribute)); GumboStringPiece name_str = GUMBO_STRING("name"); GumboStringPiece isindex_str = GUMBO_STRING("isindex"); name->attr_namespace = GUMBO_ATTR_NAMESPACE_NONE; name->name = gumbo_copy_stringz(parser, "name"); name->value = gumbo_copy_stringz(parser, "isindex"); name->original_name = name_str; name->original_value = isindex_str; name->name_start = kGumboEmptySourcePosition; name->name_end = kGumboEmptySourcePosition; name->value_start = kGumboEmptySourcePosition; name->value_end = kGumboEmptySourcePosition; gumbo_vector_add(parser, name, &input->v.element.attributes); pop_current_node(parser); // <input> pop_current_node(parser); // <label> insert_element_of_tag_type( parser, GUMBO_TAG_HR, GUMBO_INSERTION_FROM_ISINDEX); pop_current_node(parser); // <hr> pop_current_node(parser); // <form> if (!has_open_element(parser, GUMBO_TAG_TEMPLATE)) { parser->_parser_state->_form_element = NULL; } return false; } else if (tag_is(token, kStartTag, GUMBO_TAG_TEXTAREA)) { run_generic_parsing_algorithm(parser, token, GUMBO_LEX_RCDATA); parser->_parser_state->_ignore_next_linefeed = true; set_frameset_not_ok(parser); return true; } else if (tag_is(token, kStartTag, GUMBO_TAG_XMP)) { bool result = maybe_implicitly_close_p_tag(parser, token); reconstruct_active_formatting_elements(parser); set_frameset_not_ok(parser); run_generic_parsing_algorithm(parser, token, GUMBO_LEX_RAWTEXT); return result; } else if (tag_is(token, kStartTag, GUMBO_TAG_IFRAME)) { set_frameset_not_ok(parser); run_generic_parsing_algorithm(parser, token, GUMBO_LEX_RAWTEXT); return true; } else if (tag_is(token, kStartTag, GUMBO_TAG_NOEMBED)) { run_generic_parsing_algorithm(parser, token, GUMBO_LEX_RAWTEXT); return true; } else if (tag_is(token, kStartTag, GUMBO_TAG_SELECT)) { reconstruct_active_formatting_elements(parser); insert_element_from_token(parser, token); set_frameset_not_ok(parser); GumboInsertionMode state = parser->_parser_state->_insertion_mode; if (state == GUMBO_INSERTION_MODE_IN_TABLE || state == GUMBO_INSERTION_MODE_IN_CAPTION || state == GUMBO_INSERTION_MODE_IN_TABLE_BODY || state == GUMBO_INSERTION_MODE_IN_ROW || state == GUMBO_INSERTION_MODE_IN_CELL) { set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_SELECT_IN_TABLE); } else { set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_SELECT); } return true; } else if (tag_in(token, kStartTag, (gumbo_tagset){TAG(OPTION), TAG(OPTGROUP)})) { if (node_html_tag_is(get_current_node(parser), GUMBO_TAG_OPTION)) { pop_current_node(parser); } reconstruct_active_formatting_elements(parser); insert_element_from_token(parser, token); return true; } else if (tag_in(token, kStartTag, (gumbo_tagset){TAG(RB), TAG(RP), TAG(RT), TAG(RTC)})) { bool success = true; GumboTag exception = tag_in(token, kStartTag, (gumbo_tagset){TAG(RT), TAG(RP)}) ? GUMBO_TAG_RTC : GUMBO_TAG_LAST; if (has_an_element_in_scope(parser, GUMBO_TAG_RUBY)) { generate_implied_end_tags(parser, exception); } if (!node_html_tag_is(get_current_node(parser), GUMBO_TAG_RUBY) && !(exception == GUMBO_TAG_LAST || node_html_tag_is(get_current_node(parser), GUMBO_TAG_RTC))) { parser_add_parse_error(parser, token); success = false; } insert_element_from_token(parser, token); return success; } else if (tag_is(token, kEndTag, GUMBO_TAG_BR)) { parser_add_parse_error(parser, token); reconstruct_active_formatting_elements(parser); insert_element_of_tag_type( parser, GUMBO_TAG_BR, GUMBO_INSERTION_CONVERTED_FROM_END_TAG); pop_current_node(parser); return false; } else if (tag_is(token, kStartTag, GUMBO_TAG_MATH)) { reconstruct_active_formatting_elements(parser); adjust_mathml_attributes(parser, token); adjust_foreign_attributes(parser, token); insert_foreign_element(parser, token, GUMBO_NAMESPACE_MATHML); if (token->v.start_tag.is_self_closing) { pop_current_node(parser); acknowledge_self_closing_tag(parser); } return true; } else if (tag_is(token, kStartTag, GUMBO_TAG_SVG)) { reconstruct_active_formatting_elements(parser); adjust_svg_attributes(parser, token); adjust_foreign_attributes(parser, token); insert_foreign_element(parser, token, GUMBO_NAMESPACE_SVG); if (token->v.start_tag.is_self_closing) { pop_current_node(parser); acknowledge_self_closing_tag(parser); } return true; } else if (tag_in(token, kStartTag, (gumbo_tagset){TAG(CAPTION), TAG(COL), TAG(COLGROUP), TAG(FRAME), TAG(HEAD), TAG(TBODY), TAG(TD), TAG(TFOOT), TAG(TH), TAG(THEAD), TAG(TR)})) { parser_add_parse_error(parser, token); ignore_token(parser); return false; } else if (token->type == GUMBO_TOKEN_START_TAG) { reconstruct_active_formatting_elements(parser); insert_element_from_token(parser, token); return true; } else { assert(token->type == GUMBO_TOKEN_END_TAG); GumboTag end_tag = token->v.end_tag; assert(state->_open_elements.length > 0); assert(node_html_tag_is(state->_open_elements.data[0], GUMBO_TAG_HTML)); // Walk up the stack of open elements until we find one that either: // a) Matches the tag name we saw // b) Is in the "special" category. // If we see a), implicitly close everything up to and including it. If we // see b), then record a parse error, don't close anything (except the // implied end tags) and ignore the end tag token. for (int i = state->_open_elements.length; --i >= 0;) { const GumboNode* node = state->_open_elements.data[i]; if (node_html_tag_is(node, end_tag)) { generate_implied_end_tags(parser, end_tag); // TODO(jdtang): Do I need to add a parse error here? The condition in // the spec seems like it's the inverse of the loop condition above, and // so would never fire. while (node != pop_current_node(parser)) ; // Pop everything. return true; } else if (is_special_node(node)) { parser_add_parse_error(parser, token); ignore_token(parser); return false; } } // <html> is in the special category, so we should never get here. assert(0); return false; } } // http://www.whatwg.org/specs/web-apps/current-work/complete/tokenization.html#parsing-main-incdata static bool handle_text(GumboParser* parser, GumboToken* token) { if (token->type == GUMBO_TOKEN_CHARACTER || token->type == GUMBO_TOKEN_WHITESPACE) { insert_text_token(parser, token); } else { // We provide only bare-bones script handling that doesn't involve any of // the parser-pause/already-started/script-nesting flags or re-entrant // invocations of the tokenizer. Because the intended usage of this library // is mostly for templating, refactoring, and static-analysis libraries, we // provide the script body as a text-node child of the <script> element. // This behavior doesn't support document.write of partial HTML elements, // but should be adequate for almost all other scripting support. if (token->type == GUMBO_TOKEN_EOF) { parser_add_parse_error(parser, token); parser->_parser_state->_reprocess_current_token = true; } pop_current_node(parser); set_insertion_mode(parser, parser->_parser_state->_original_insertion_mode); } return true; } // http://www.whatwg.org/specs/web-apps/current-work/complete/tokenization.html#parsing-main-intable static bool handle_in_table(GumboParser* parser, GumboToken* token) { GumboParserState* state = parser->_parser_state; if (token->type == GUMBO_TOKEN_CHARACTER || token->type == GUMBO_TOKEN_WHITESPACE) { // The "pending table character tokens" list described in the spec is // nothing more than the TextNodeBufferState. We accumulate text tokens as // normal, except that when we go to flush them in the handle_in_table_text, // we set _foster_parent_insertions if there're non-whitespace characters in // the buffer. assert(state->_text_node._buffer.length == 0); state->_original_insertion_mode = state->_insertion_mode; state->_reprocess_current_token = true; set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_TABLE_TEXT); return true; } else if (token->type == GUMBO_TOKEN_DOCTYPE) { parser_add_parse_error(parser, token); ignore_token(parser); return false; } else if (token->type == GUMBO_TOKEN_COMMENT) { append_comment_node(parser, get_current_node(parser), token); return true; } else if (tag_is(token, kStartTag, GUMBO_TAG_CAPTION)) { clear_stack_to_table_context(parser); add_formatting_element(parser, &kActiveFormattingScopeMarker); insert_element_from_token(parser, token); set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_CAPTION); return true; } else if (tag_is(token, kStartTag, GUMBO_TAG_COLGROUP)) { clear_stack_to_table_context(parser); insert_element_from_token(parser, token); set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_COLUMN_GROUP); return true; } else if (tag_is(token, kStartTag, GUMBO_TAG_COL)) { clear_stack_to_table_context(parser); insert_element_of_tag_type( parser, GUMBO_TAG_COLGROUP, GUMBO_INSERTION_IMPLIED); parser->_parser_state->_reprocess_current_token = true; set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_COLUMN_GROUP); return true; } else if (tag_in(token, kStartTag, (gumbo_tagset){TAG(TBODY), TAG(TFOOT), TAG(THEAD), TAG(TD), TAG(TH), TAG(TR)})) { clear_stack_to_table_context(parser); set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_TABLE_BODY); if (tag_in(token, kStartTag, (gumbo_tagset){TAG(TD), TAG(TH), TAG(TR)})) { insert_element_of_tag_type( parser, GUMBO_TAG_TBODY, GUMBO_INSERTION_IMPLIED); state->_reprocess_current_token = true; } else { insert_element_from_token(parser, token); } return true; } else if (tag_is(token, kStartTag, GUMBO_TAG_TABLE)) { parser_add_parse_error(parser, token); if (close_table(parser)) { parser->_parser_state->_reprocess_current_token = true; } else { ignore_token(parser); } return false; } else if (tag_is(token, kEndTag, GUMBO_TAG_TABLE)) { if (!close_table(parser)) { parser_add_parse_error(parser, token); return false; } return true; } else if (tag_in(token, kEndTag, (gumbo_tagset){TAG(BODY), TAG(CAPTION), TAG(COL), TAG(COLGROUP), TAG(HTML), TAG(TBODY), TAG(TD), TAG(TFOOT), TAG(TH), TAG(THEAD), TAG(TR)})) { parser_add_parse_error(parser, token); ignore_token(parser); return false; } else if (tag_in(token, kStartTag, (gumbo_tagset){TAG(STYLE), TAG(SCRIPT), TAG(TEMPLATE)}) || (tag_is(token, kEndTag, GUMBO_TAG_TEMPLATE))) { return handle_in_head(parser, token); } else if (tag_is(token, kStartTag, GUMBO_TAG_INPUT) && attribute_matches( &token->v.start_tag.attributes, "type", "hidden")) { parser_add_parse_error(parser, token); insert_element_from_token(parser, token); pop_current_node(parser); return false; } else if (tag_is(token, kStartTag, GUMBO_TAG_FORM)) { parser_add_parse_error(parser, token); if (state->_form_element || has_open_element(parser, GUMBO_TAG_TEMPLATE)) { ignore_token(parser); return false; } state->_form_element = insert_element_from_token(parser, token); pop_current_node(parser); return false; } else if (token->type == GUMBO_TOKEN_EOF) { return handle_in_body(parser, token); } else { parser_add_parse_error(parser, token); state->_foster_parent_insertions = true; bool result = handle_in_body(parser, token); state->_foster_parent_insertions = false; return result; } } // http://www.whatwg.org/specs/web-apps/current-work/complete/tokenization.html#parsing-main-intabletext static bool handle_in_table_text(GumboParser* parser, GumboToken* token) { if (token->type == GUMBO_TOKEN_NULL) { parser_add_parse_error(parser, token); ignore_token(parser); return false; } else if (token->type == GUMBO_TOKEN_CHARACTER || token->type == GUMBO_TOKEN_WHITESPACE) { insert_text_token(parser, token); return true; } else { GumboParserState* state = parser->_parser_state; GumboStringBuffer* buffer = &state->_text_node._buffer; // Can't use strspn for this because GumboStringBuffers are not // null-terminated. // Note that TextNodeBuffer may contain UTF-8 characters, but the presence // of any one byte that is not whitespace means we flip the flag, so this // loop is still valid. for (unsigned int i = 0; i < buffer->length; ++i) { if (!isspace((unsigned char) buffer->data[i]) || buffer->data[i] == '\v') { state->_foster_parent_insertions = true; reconstruct_active_formatting_elements(parser); break; } } maybe_flush_text_node_buffer(parser); state->_foster_parent_insertions = false; state->_reprocess_current_token = true; state->_insertion_mode = state->_original_insertion_mode; return true; } } // http://www.whatwg.org/specs/web-apps/current-work/complete/tokenization.html#parsing-main-incaption static bool handle_in_caption(GumboParser* parser, GumboToken* token) { if (tag_is(token, kEndTag, GUMBO_TAG_CAPTION)) { if (!has_an_element_in_table_scope(parser, GUMBO_TAG_CAPTION)) { parser_add_parse_error(parser, token); ignore_token(parser); return false; } else { generate_implied_end_tags(parser, GUMBO_TAG_LAST); bool result = true; if (!node_html_tag_is(get_current_node(parser), GUMBO_TAG_CAPTION)) { parser_add_parse_error(parser, token); } while (!node_html_tag_is(pop_current_node(parser), GUMBO_TAG_CAPTION)) ; clear_active_formatting_elements(parser); set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_TABLE); return result; } } else if (tag_in(token, kStartTag, (gumbo_tagset){TAG(CAPTION), TAG(COL), TAG(COLGROUP), TAG(TBODY), TAG(TD), TAG(TFOOT), TAG(TH), TAG(THEAD), TAG(TR)}) || (tag_is(token, kEndTag, GUMBO_TAG_TABLE))) { if (!has_an_element_in_table_scope(parser, GUMBO_TAG_CAPTION)) { parser_add_parse_error(parser, token); ignore_token(parser); return false; } while (!node_html_tag_is(pop_current_node(parser), GUMBO_TAG_CAPTION)) ; clear_active_formatting_elements(parser); set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_TABLE); parser->_parser_state->_reprocess_current_token = true; return true; } else if (tag_in(token, kEndTag, (gumbo_tagset){TAG(BODY), TAG(COL), TAG(COLGROUP), TAG(HTML), TAG(TBODY), TAG(TD), TAG(TFOOT), TAG(TH), TAG(THEAD), TAG(TR)})) { parser_add_parse_error(parser, token); ignore_token(parser); return false; } else { return handle_in_body(parser, token); } } // http://www.whatwg.org/specs/web-apps/current-work/complete/tokenization.html#parsing-main-incolgroup static bool handle_in_column_group(GumboParser* parser, GumboToken* token) { if (token->type == GUMBO_TOKEN_WHITESPACE) { insert_text_token(parser, token); return true; } else if (token->type == GUMBO_TOKEN_DOCTYPE) { parser_add_parse_error(parser, token); ignore_token(parser); return false; } else if (token->type == GUMBO_TOKEN_COMMENT) { append_comment_node(parser, get_current_node(parser), token); return true; } else if (tag_is(token, kStartTag, GUMBO_TAG_HTML)) { return handle_in_body(parser, token); } else if (tag_is(token, kStartTag, GUMBO_TAG_COL)) { insert_element_from_token(parser, token); pop_current_node(parser); acknowledge_self_closing_tag(parser); return true; } else if (tag_is(token, kEndTag, GUMBO_TAG_COLGROUP)) { if (!node_html_tag_is(get_current_node(parser), GUMBO_TAG_COLGROUP)) { parser_add_parse_error(parser, token); ignore_token(parser); return false; } pop_current_node(parser); set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_TABLE); return false; } else if (tag_is(token, kEndTag, GUMBO_TAG_COL)) { parser_add_parse_error(parser, token); ignore_token(parser); return false; } else if (tag_is(token, kStartTag, GUMBO_TAG_TEMPLATE) || tag_is(token, kEndTag, GUMBO_TAG_TEMPLATE)) { return handle_in_head(parser, token); } else if (token->type == GUMBO_TOKEN_EOF) { return handle_in_body(parser, token); } else { if (!node_html_tag_is(get_current_node(parser), GUMBO_TAG_COLGROUP)) { parser_add_parse_error(parser, token); ignore_token(parser); return false; } pop_current_node(parser); set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_TABLE); parser->_parser_state->_reprocess_current_token = true; return true; } } // http://www.whatwg.org/specs/web-apps/current-work/complete/tokenization.html#parsing-main-intbody static bool handle_in_table_body(GumboParser* parser, GumboToken* token) { if (tag_is(token, kStartTag, GUMBO_TAG_TR)) { clear_stack_to_table_body_context(parser); insert_element_from_token(parser, token); set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_ROW); return true; } else if (tag_in(token, kStartTag, (gumbo_tagset){TAG(TD), TAG(TH)})) { parser_add_parse_error(parser, token); clear_stack_to_table_body_context(parser); insert_element_of_tag_type(parser, GUMBO_TAG_TR, GUMBO_INSERTION_IMPLIED); parser->_parser_state->_reprocess_current_token = true; set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_ROW); return false; } else if (tag_in(token, kEndTag, (gumbo_tagset){TAG(TBODY), TAG(TFOOT), TAG(THEAD)})) { if (!has_an_element_in_table_scope(parser, token->v.end_tag)) { parser_add_parse_error(parser, token); ignore_token(parser); return false; } clear_stack_to_table_body_context(parser); pop_current_node(parser); set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_TABLE); return true; } else if (tag_in(token, kStartTag, (gumbo_tagset){TAG(CAPTION), TAG(COL), TAG(COLGROUP), TAG(TBODY), TAG(TFOOT), TAG(THEAD)}) || tag_is(token, kEndTag, GUMBO_TAG_TABLE)) { if (!(has_an_element_in_table_scope(parser, GUMBO_TAG_TBODY) || has_an_element_in_table_scope(parser, GUMBO_TAG_THEAD) || has_an_element_in_table_scope(parser, GUMBO_TAG_TFOOT))) { parser_add_parse_error(parser, token); ignore_token(parser); return false; } clear_stack_to_table_body_context(parser); pop_current_node(parser); set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_TABLE); parser->_parser_state->_reprocess_current_token = true; return true; } else if (tag_in(token, kEndTag, (gumbo_tagset){TAG(BODY), TAG(CAPTION), TAG(COL), TAG(TR), TAG(COLGROUP), TAG(HTML), TAG(TD), TAG(TH)})) { parser_add_parse_error(parser, token); ignore_token(parser); return false; } else { return handle_in_table(parser, token); } } // http://www.whatwg.org/specs/web-apps/current-work/complete/tokenization.html#parsing-main-intr static bool handle_in_row(GumboParser* parser, GumboToken* token) { if (tag_in(token, kStartTag, (gumbo_tagset){TAG(TH), TAG(TD)})) { clear_stack_to_table_row_context(parser); insert_element_from_token(parser, token); set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_CELL); add_formatting_element(parser, &kActiveFormattingScopeMarker); return true; } else if (tag_is(token, kEndTag, GUMBO_TAG_TR)) { if (!has_an_element_in_table_scope(parser, GUMBO_TAG_TR)) { parser_add_parse_error(parser, token); ignore_token(parser); return false; } else { clear_stack_to_table_row_context(parser); pop_current_node(parser); set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_TABLE_BODY); return true; } } else if (tag_in(token, kStartTag, (gumbo_tagset){TAG(CAPTION), TAG(COL), TAG(COLGROUP), TAG(TBODY), TAG(TFOOT), TAG(THEAD), TAG(TR)}) || tag_is(token, kEndTag, GUMBO_TAG_TABLE)) { if (!has_an_element_in_table_scope(parser, GUMBO_TAG_TR)) { parser_add_parse_error(parser, token); ignore_token(parser); return false; } else { clear_stack_to_table_row_context(parser); pop_current_node(parser); set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_TABLE_BODY); parser->_parser_state->_reprocess_current_token = true; return true; } } else if (tag_in(token, kEndTag, (gumbo_tagset){TAG(TBODY), TAG(TFOOT), TAG(THEAD)})) { if (!has_an_element_in_table_scope(parser, token->v.end_tag) || (!has_an_element_in_table_scope(parser, GUMBO_TAG_TR))) { parser_add_parse_error(parser, token); ignore_token(parser); return false; } else { clear_stack_to_table_row_context(parser); pop_current_node(parser); set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_TABLE_BODY); parser->_parser_state->_reprocess_current_token = true; return true; } } else if (tag_in(token, kEndTag, (gumbo_tagset){TAG(BODY), TAG(CAPTION), TAG(COL), TAG(COLGROUP), TAG(HTML), TAG(TD), TAG(TH)})) { parser_add_parse_error(parser, token); ignore_token(parser); return false; } else { return handle_in_table(parser, token); } } // http://www.whatwg.org/specs/web-apps/current-work/complete/tokenization.html#parsing-main-intd static bool handle_in_cell(GumboParser* parser, GumboToken* token) { if (tag_in(token, kEndTag, (gumbo_tagset){TAG(TD), TAG(TH)})) { GumboTag token_tag = token->v.end_tag; if (!has_an_element_in_table_scope(parser, token_tag)) { parser_add_parse_error(parser, token); ignore_token(parser); return false; } return close_table_cell(parser, token, token_tag); } else if (tag_in(token, kStartTag, (gumbo_tagset){TAG(CAPTION), TAG(COL), TAG(COLGROUP), TAG(TBODY), TAG(TD), TAG(TFOOT), TAG(TH), TAG(THEAD), TAG(TR)})) { gumbo_debug("Handling <td> in cell.\n"); if (!has_an_element_in_table_scope(parser, GUMBO_TAG_TH) && !has_an_element_in_table_scope(parser, GUMBO_TAG_TD)) { gumbo_debug("Bailing out because there's no <td> or <th> in scope.\n"); parser_add_parse_error(parser, token); ignore_token(parser); return false; } parser->_parser_state->_reprocess_current_token = true; return close_current_cell(parser, token); } else if (tag_in(token, kEndTag, (gumbo_tagset){TAG(BODY), TAG(CAPTION), TAG(COL), TAG(COLGROUP), TAG(HTML)})) { parser_add_parse_error(parser, token); ignore_token(parser); return false; } else if (tag_in(token, kEndTag, (gumbo_tagset){TAG(TABLE), TAG(TBODY), TAG(TFOOT), TAG(THEAD), TAG(TR)})) { if (!has_an_element_in_table_scope(parser, token->v.end_tag)) { parser_add_parse_error(parser, token); ignore_token(parser); return false; } parser->_parser_state->_reprocess_current_token = true; return close_current_cell(parser, token); } else { return handle_in_body(parser, token); } } // http://www.whatwg.org/specs/web-apps/current-work/complete/tokenization.html#parsing-main-inselect static bool handle_in_select(GumboParser* parser, GumboToken* token) { if (token->type == GUMBO_TOKEN_NULL) { parser_add_parse_error(parser, token); ignore_token(parser); return false; } else if (token->type == GUMBO_TOKEN_CHARACTER || token->type == GUMBO_TOKEN_WHITESPACE) { insert_text_token(parser, token); return true; } else if (token->type == GUMBO_TOKEN_DOCTYPE) { parser_add_parse_error(parser, token); ignore_token(parser); return false; } else if (token->type == GUMBO_TOKEN_COMMENT) { append_comment_node(parser, get_current_node(parser), token); return true; } else if (tag_is(token, kStartTag, GUMBO_TAG_HTML)) { return handle_in_body(parser, token); } else if (tag_is(token, kStartTag, GUMBO_TAG_OPTION)) { if (node_html_tag_is(get_current_node(parser), GUMBO_TAG_OPTION)) { pop_current_node(parser); } insert_element_from_token(parser, token); return true; } else if (tag_is(token, kStartTag, GUMBO_TAG_OPTGROUP)) { if (node_html_tag_is(get_current_node(parser), GUMBO_TAG_OPTION)) { pop_current_node(parser); } if (node_html_tag_is(get_current_node(parser), GUMBO_TAG_OPTGROUP)) { pop_current_node(parser); } insert_element_from_token(parser, token); return true; } else if (tag_is(token, kEndTag, GUMBO_TAG_OPTGROUP)) { GumboVector* open_elements = &parser->_parser_state->_open_elements; if (node_html_tag_is(get_current_node(parser), GUMBO_TAG_OPTION) && node_html_tag_is(open_elements->data[open_elements->length - 2], GUMBO_TAG_OPTGROUP)) { pop_current_node(parser); } if (node_html_tag_is(get_current_node(parser), GUMBO_TAG_OPTGROUP)) { pop_current_node(parser); return true; } else { parser_add_parse_error(parser, token); ignore_token(parser); return false; } } else if (tag_is(token, kEndTag, GUMBO_TAG_OPTION)) { if (node_html_tag_is(get_current_node(parser), GUMBO_TAG_OPTION)) { pop_current_node(parser); return true; } else { parser_add_parse_error(parser, token); ignore_token(parser); return false; } } else if (tag_is(token, kEndTag, GUMBO_TAG_SELECT)) { if (!has_an_element_in_select_scope(parser, GUMBO_TAG_SELECT)) { parser_add_parse_error(parser, token); ignore_token(parser); return false; } close_current_select(parser); return true; } else if (tag_is(token, kStartTag, GUMBO_TAG_SELECT)) { parser_add_parse_error(parser, token); ignore_token(parser); if (has_an_element_in_select_scope(parser, GUMBO_TAG_SELECT)) { close_current_select(parser); } return false; } else if (tag_in(token, kStartTag, (gumbo_tagset){TAG(INPUT), TAG(KEYGEN), TAG(TEXTAREA)})) { parser_add_parse_error(parser, token); if (!has_an_element_in_select_scope(parser, GUMBO_TAG_SELECT)) { ignore_token(parser); } else { close_current_select(parser); parser->_parser_state->_reprocess_current_token = true; } return false; } else if (tag_in(token, kStartTag, (gumbo_tagset){TAG(SCRIPT), TAG(TEMPLATE)}) || tag_is(token, kEndTag, GUMBO_TAG_TEMPLATE)) { return handle_in_head(parser, token); } else if (token->type == GUMBO_TOKEN_EOF) { return handle_in_body(parser, token); } else { parser_add_parse_error(parser, token); ignore_token(parser); return false; } } // http://www.whatwg.org/specs/web-apps/current-work/complete/tokenization.html#parsing-main-inselectintable static bool handle_in_select_in_table(GumboParser* parser, GumboToken* token) { if (tag_in(token, kStartTag, (gumbo_tagset){TAG(CAPTION), TAG(TABLE), TAG(TBODY), TAG(TFOOT), TAG(THEAD), TAG(TR), TAG(TD), TAG(TH)})) { parser_add_parse_error(parser, token); close_current_select(parser); parser->_parser_state->_reprocess_current_token = true; return false; } else if (tag_in(token, kEndTag, (gumbo_tagset){TAG(CAPTION), TAG(TABLE), TAG(TBODY), TAG(TFOOT), TAG(THEAD), TAG(TR), TAG(TD), TAG(TH)})) { parser_add_parse_error(parser, token); if (!has_an_element_in_table_scope(parser, token->v.end_tag)) { ignore_token(parser); return false; } else { close_current_select(parser); // close_current_select already does the // reset_insertion_mode_appropriately // reset_insertion_mode_appropriately(parser); parser->_parser_state->_reprocess_current_token = true; return false; } } else { return handle_in_select(parser, token); } } // http://www.whatwg.org/specs/web-apps/current-work/multipage/tree-construction.html#parsing-main-intemplate static bool handle_in_template(GumboParser* parser, GumboToken* token) { GumboParserState* state = parser->_parser_state; if (token->type == GUMBO_TOKEN_WHITESPACE || token->type == GUMBO_TOKEN_CHARACTER || token->type == GUMBO_TOKEN_COMMENT || token->type == GUMBO_TOKEN_NULL || token->type == GUMBO_TOKEN_DOCTYPE) { return handle_in_body(parser, token); } else if (tag_in(token, kStartTag, (gumbo_tagset){TAG(BASE), TAG(BASEFONT), TAG(BGSOUND), TAG(LINK), TAG(META), TAG(NOFRAMES), TAG(SCRIPT), TAG(STYLE), TAG(TEMPLATE), TAG(TITLE)}) || tag_is(token, kEndTag, GUMBO_TAG_TEMPLATE)) { return handle_in_head(parser, token); } else if (tag_in( token, kStartTag, (gumbo_tagset){TAG(CAPTION), TAG(COLGROUP), TAG(TBODY), TAG(TFOOT), TAG(THEAD)})) { pop_template_insertion_mode(parser); push_template_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_TABLE); set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_TABLE); state->_reprocess_current_token = true; return true; } else if (tag_is(token, kStartTag, GUMBO_TAG_COL)) { pop_template_insertion_mode(parser); push_template_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_COLUMN_GROUP); set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_COLUMN_GROUP); state->_reprocess_current_token = true; return true; } else if (tag_is(token, kStartTag, GUMBO_TAG_TR)) { pop_template_insertion_mode(parser); push_template_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_TABLE_BODY); set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_TABLE_BODY); state->_reprocess_current_token = true; return true; } else if (tag_in(token, kStartTag, (gumbo_tagset){TAG(TD), TAG(TH)})) { pop_template_insertion_mode(parser); push_template_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_ROW); set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_ROW); state->_reprocess_current_token = true; return true; } else if (token->type == GUMBO_TOKEN_START_TAG) { pop_template_insertion_mode(parser); push_template_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_BODY); set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_BODY); state->_reprocess_current_token = true; return true; } else if (token->type == GUMBO_TOKEN_END_TAG) { parser_add_parse_error(parser, token); ignore_token(parser); return false; } else if (token->type == GUMBO_TOKEN_EOF) { if (!has_open_element(parser, GUMBO_TAG_TEMPLATE)) { // Stop parsing. return true; } parser_add_parse_error(parser, token); while (!node_html_tag_is(pop_current_node(parser), GUMBO_TAG_TEMPLATE)) ; clear_active_formatting_elements(parser); pop_template_insertion_mode(parser); reset_insertion_mode_appropriately(parser); state->_reprocess_current_token = true; return false; } else { assert(0); return false; } } // http://www.whatwg.org/specs/web-apps/current-work/complete/tokenization.html#parsing-main-afterbody static bool handle_after_body(GumboParser* parser, GumboToken* token) { if (token->type == GUMBO_TOKEN_WHITESPACE || tag_is(token, kStartTag, GUMBO_TAG_HTML)) { return handle_in_body(parser, token); } else if (token->type == GUMBO_TOKEN_COMMENT) { GumboNode* html_node = parser->_output->root; assert(html_node != NULL); append_comment_node(parser, html_node, token); return true; } else if (token->type == GUMBO_TOKEN_DOCTYPE) { parser_add_parse_error(parser, token); ignore_token(parser); return false; } else if (tag_is(token, kEndTag, GUMBO_TAG_HTML)) { /* fragment case: ignore the closing HTML token */ if (is_fragment_parser(parser)) { parser_add_parse_error(parser, token); ignore_token(parser); return false; } set_insertion_mode(parser, GUMBO_INSERTION_MODE_AFTER_AFTER_BODY); GumboNode* html = parser->_parser_state->_open_elements.data[0]; assert(node_html_tag_is(html, GUMBO_TAG_HTML)); record_end_of_element( parser->_parser_state->_current_token, &html->v.element); return true; } else if (token->type == GUMBO_TOKEN_EOF) { return true; } else { parser_add_parse_error(parser, token); set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_BODY); parser->_parser_state->_reprocess_current_token = true; return false; } } // http://www.whatwg.org/specs/web-apps/current-work/complete/tokenization.html#parsing-main-inframeset static bool handle_in_frameset(GumboParser* parser, GumboToken* token) { if (token->type == GUMBO_TOKEN_WHITESPACE) { insert_text_token(parser, token); return true; } else if (token->type == GUMBO_TOKEN_COMMENT) { append_comment_node(parser, get_current_node(parser), token); return true; } else if (token->type == GUMBO_TOKEN_DOCTYPE) { parser_add_parse_error(parser, token); ignore_token(parser); return false; } else if (tag_is(token, kStartTag, GUMBO_TAG_HTML)) { return handle_in_body(parser, token); } else if (tag_is(token, kStartTag, GUMBO_TAG_FRAMESET)) { insert_element_from_token(parser, token); return true; } else if (tag_is(token, kEndTag, GUMBO_TAG_FRAMESET)) { if (node_html_tag_is(get_current_node(parser), GUMBO_TAG_HTML)) { parser_add_parse_error(parser, token); ignore_token(parser); return false; } pop_current_node(parser); if (!is_fragment_parser(parser) && !node_html_tag_is(get_current_node(parser), GUMBO_TAG_FRAMESET)) { set_insertion_mode(parser, GUMBO_INSERTION_MODE_AFTER_FRAMESET); } return true; } else if (tag_is(token, kStartTag, GUMBO_TAG_FRAME)) { insert_element_from_token(parser, token); pop_current_node(parser); acknowledge_self_closing_tag(parser); return true; } else if (tag_is(token, kStartTag, GUMBO_TAG_NOFRAMES)) { return handle_in_head(parser, token); } else if (token->type == GUMBO_TOKEN_EOF) { if (!node_html_tag_is(get_current_node(parser), GUMBO_TAG_HTML)) { parser_add_parse_error(parser, token); return false; } return true; } else { parser_add_parse_error(parser, token); ignore_token(parser); return false; } } // http://www.whatwg.org/specs/web-apps/current-work/complete/tokenization.html#parsing-main-afterframeset static bool handle_after_frameset(GumboParser* parser, GumboToken* token) { if (token->type == GUMBO_TOKEN_WHITESPACE) { insert_text_token(parser, token); return true; } else if (token->type == GUMBO_TOKEN_COMMENT) { append_comment_node(parser, get_current_node(parser), token); return true; } else if (token->type == GUMBO_TOKEN_DOCTYPE) { parser_add_parse_error(parser, token); ignore_token(parser); return false; } else if (tag_is(token, kStartTag, GUMBO_TAG_HTML)) { return handle_in_body(parser, token); } else if (tag_is(token, kEndTag, GUMBO_TAG_HTML)) { GumboNode* html = parser->_parser_state->_open_elements.data[0]; assert(node_html_tag_is(html, GUMBO_TAG_HTML)); record_end_of_element( parser->_parser_state->_current_token, &html->v.element); set_insertion_mode(parser, GUMBO_INSERTION_MODE_AFTER_AFTER_FRAMESET); return true; } else if (tag_is(token, kStartTag, GUMBO_TAG_NOFRAMES)) { return handle_in_head(parser, token); } else if (token->type == GUMBO_TOKEN_EOF) { return true; } else { parser_add_parse_error(parser, token); ignore_token(parser); return false; } } // http://www.whatwg.org/specs/web-apps/current-work/complete/tokenization.html#the-after-after-body-insertion-mode static bool handle_after_after_body(GumboParser* parser, GumboToken* token) { if (token->type == GUMBO_TOKEN_COMMENT) { append_comment_node(parser, get_document_node(parser), token); return true; } else if (token->type == GUMBO_TOKEN_DOCTYPE || token->type == GUMBO_TOKEN_WHITESPACE || tag_is(token, kStartTag, GUMBO_TAG_HTML)) { return handle_in_body(parser, token); } else if (token->type == GUMBO_TOKEN_EOF) { return true; } else { parser_add_parse_error(parser, token); set_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_BODY); parser->_parser_state->_reprocess_current_token = true; return false; } } // http://www.whatwg.org/specs/web-apps/current-work/complete/tokenization.html#the-after-after-frameset-insertion-mode static bool handle_after_after_frameset( GumboParser* parser, GumboToken* token) { if (token->type == GUMBO_TOKEN_COMMENT) { append_comment_node(parser, get_document_node(parser), token); return true; } else if (token->type == GUMBO_TOKEN_DOCTYPE || token->type == GUMBO_TOKEN_WHITESPACE || tag_is(token, kStartTag, GUMBO_TAG_HTML)) { return handle_in_body(parser, token); } else if (token->type == GUMBO_TOKEN_EOF) { return true; } else if (tag_is(token, kStartTag, GUMBO_TAG_NOFRAMES)) { return handle_in_head(parser, token); } else { parser_add_parse_error(parser, token); ignore_token(parser); return false; } } // Function pointers for each insertion mode. Keep in sync with // insertion_mode.h. typedef bool (*TokenHandler)(GumboParser* parser, GumboToken* token); static const TokenHandler kTokenHandlers[] = {handle_initial, handle_before_html, handle_before_head, handle_in_head, handle_in_head_noscript, handle_after_head, handle_in_body, handle_text, handle_in_table, handle_in_table_text, handle_in_caption, handle_in_column_group, handle_in_table_body, handle_in_row, handle_in_cell, handle_in_select, handle_in_select_in_table, handle_in_template, handle_after_body, handle_in_frameset, handle_after_frameset, handle_after_after_body, handle_after_after_frameset}; static bool handle_html_content(GumboParser* parser, GumboToken* token) { return kTokenHandlers[(unsigned int) parser->_parser_state->_insertion_mode]( parser, token); } // http://www.whatwg.org/specs/web-apps/current-work/complete/tokenization.html#parsing-main-inforeign static bool handle_in_foreign_content(GumboParser* parser, GumboToken* token) { gumbo_debug("Handling foreign content"); switch (token->type) { case GUMBO_TOKEN_NULL: parser_add_parse_error(parser, token); token->v.character = kUtf8ReplacementChar; insert_text_token(parser, token); return false; case GUMBO_TOKEN_WHITESPACE: insert_text_token(parser, token); return true; case GUMBO_TOKEN_CDATA: case GUMBO_TOKEN_CHARACTER: insert_text_token(parser, token); set_frameset_not_ok(parser); return true; case GUMBO_TOKEN_COMMENT: append_comment_node(parser, get_current_node(parser), token); return true; case GUMBO_TOKEN_DOCTYPE: parser_add_parse_error(parser, token); ignore_token(parser); return false; default: // Fall through to the if-statements below. break; } // Order matters for these clauses. if (tag_in(token, kStartTag, (gumbo_tagset){TAG(B), TAG(BIG), TAG(BLOCKQUOTE), TAG(BODY), TAG(BR), TAG(CENTER), TAG(CODE), TAG(DD), TAG(DIV), TAG(DL), TAG(DT), TAG(EM), TAG(EMBED), TAG(H1), TAG(H2), TAG(H3), TAG(H4), TAG(H5), TAG(H6), TAG(HEAD), TAG(HR), TAG(I), TAG(IMG), TAG(LI), TAG(LISTING), TAG(MENU), TAG(META), TAG(NOBR), TAG(OL), TAG(P), TAG(PRE), TAG(RUBY), TAG(S), TAG(SMALL), TAG(SPAN), TAG(STRONG), TAG(STRIKE), TAG(SUB), TAG(SUP), TAG(TABLE), TAG(TT), TAG(U), TAG(UL), TAG(VAR)}) || (tag_is(token, kStartTag, GUMBO_TAG_FONT) && (token_has_attribute(token, "color") || token_has_attribute(token, "face") || token_has_attribute(token, "size")))) { /* Parse error */ parser_add_parse_error(parser, token); /* * Fragment case: If the parser was originally created for the HTML * fragment parsing algorithm, then act as described in the "any other * start tag" entry below. */ if (!is_fragment_parser(parser)) { do { pop_current_node(parser); } while (!(is_mathml_integration_point(get_current_node(parser)) || is_html_integration_point(get_current_node(parser)) || get_current_node(parser)->v.element.tag_namespace == GUMBO_NAMESPACE_HTML)); parser->_parser_state->_reprocess_current_token = true; return false; } assert(token->type == GUMBO_TOKEN_START_TAG); } if (token->type == GUMBO_TOKEN_START_TAG) { const GumboNamespaceEnum current_namespace = get_adjusted_current_node(parser)->v.element.tag_namespace; if (current_namespace == GUMBO_NAMESPACE_MATHML) { adjust_mathml_attributes(parser, token); } if (current_namespace == GUMBO_NAMESPACE_SVG) { // Tag adjustment is left to the gumbo_normalize_svg_tagname helper // function. adjust_svg_attributes(parser, token); } adjust_foreign_attributes(parser, token); insert_foreign_element(parser, token, current_namespace); if (token->v.start_tag.is_self_closing) { pop_current_node(parser); acknowledge_self_closing_tag(parser); } return true; // </script> tags are handled like any other end tag, putting the script's // text into a text node child and closing the current node. } else { assert(token->type == GUMBO_TOKEN_END_TAG); GumboNode* node = get_current_node(parser); assert(node != NULL); GumboStringPiece token_tagname = token->original_text; GumboStringPiece node_tagname = node->v.element.original_tag; gumbo_tag_from_original_text(&token_tagname); gumbo_tag_from_original_text(&node_tagname); bool is_success = true; if (!gumbo_string_equals_ignore_case(&node_tagname, &token_tagname)) { parser_add_parse_error(parser, token); is_success = false; } int i = parser->_parser_state->_open_elements.length; for (--i; i > 0;) { // Here we move up the stack until we find an HTML element (in which // case we do nothing) or we find the element that we're about to // close (in which case we pop everything we've seen until that // point.) gumbo_debug("Foreign %.*s node at %d.\n", node_tagname.length, node_tagname.data, i); if (gumbo_string_equals_ignore_case(&node_tagname, &token_tagname)) { gumbo_debug("Matches.\n"); while (pop_current_node(parser) != node) { // Pop all the nodes below the current one. Node is guaranteed to // be an element on the stack of open elements (set below), so // this loop is guaranteed to terminate. } return is_success; } --i; node = parser->_parser_state->_open_elements.data[i]; if (node->v.element.tag_namespace == GUMBO_NAMESPACE_HTML) { // Must break before gumbo_tag_from_original_text to avoid passing // parser-inserted nodes through. break; } node_tagname = node->v.element.original_tag; gumbo_tag_from_original_text(&node_tagname); } assert(node->v.element.tag_namespace == GUMBO_NAMESPACE_HTML); // We can't call handle_token directly because the current node is still in // the SVG namespace, so it would re-enter this and result in infinite // recursion. return handle_html_content(parser, token) && is_success; } } // http://www.whatwg.org/specs/web-apps/current-work/multipage/tree-construction.html#tree-construction static bool handle_token(GumboParser* parser, GumboToken* token) { if (parser->_parser_state->_ignore_next_linefeed && token->type == GUMBO_TOKEN_WHITESPACE && token->v.character == '\n') { parser->_parser_state->_ignore_next_linefeed = false; ignore_token(parser); return true; } // This needs to be reset both here and in the conditional above to catch both // the case where the next token is not whitespace (so we don't ignore // whitespace in the middle of <pre> tags) and where there are multiple // whitespace tokens (so we don't ignore the second one). parser->_parser_state->_ignore_next_linefeed = false; if (tag_is(token, kEndTag, GUMBO_TAG_BODY)) { parser->_parser_state->_closed_body_tag = true; } if (tag_is(token, kEndTag, GUMBO_TAG_HTML)) { parser->_parser_state->_closed_html_tag = true; } const GumboNode* current_node = get_adjusted_current_node(parser); assert(!current_node || current_node->type == GUMBO_NODE_ELEMENT || current_node->type == GUMBO_NODE_TEMPLATE); if (current_node) { gumbo_debug("Current node: <%s>.\n", gumbo_normalized_tagname(current_node->v.element.tag)); } if (!current_node || current_node->v.element.tag_namespace == GUMBO_NAMESPACE_HTML || (is_mathml_integration_point(current_node) && (token->type == GUMBO_TOKEN_CHARACTER || token->type == GUMBO_TOKEN_WHITESPACE || token->type == GUMBO_TOKEN_NULL || (token->type == GUMBO_TOKEN_START_TAG && !tag_in(token, kStartTag, (gumbo_tagset){TAG(MGLYPH), TAG(MALIGNMARK)})))) || (current_node->v.element.tag_namespace == GUMBO_NAMESPACE_MATHML && node_qualified_tag_is( current_node, GUMBO_NAMESPACE_MATHML, GUMBO_TAG_ANNOTATION_XML) && tag_is(token, kStartTag, GUMBO_TAG_SVG)) || (is_html_integration_point(current_node) && (token->type == GUMBO_TOKEN_START_TAG || token->type == GUMBO_TOKEN_CHARACTER || token->type == GUMBO_TOKEN_NULL || token->type == GUMBO_TOKEN_WHITESPACE)) || token->type == GUMBO_TOKEN_EOF) { return handle_html_content(parser, token); } else { return handle_in_foreign_content(parser, token); } } static void fragment_parser_init(GumboParser* parser, GumboTag fragment_ctx, GumboNamespaceEnum fragment_namespace) { GumboNode* root; assert(fragment_ctx != GUMBO_TAG_LAST); // 3 parser->_parser_state->_fragment_ctx = create_element(parser, fragment_ctx); parser->_parser_state->_fragment_ctx->v.element.tag_namespace = fragment_namespace; // 4 if (fragment_namespace == GUMBO_NAMESPACE_HTML) { // Non-HTML namespaces always start in the DATA state. switch (fragment_ctx) { case GUMBO_TAG_TITLE: case GUMBO_TAG_TEXTAREA: gumbo_tokenizer_set_state(parser, GUMBO_LEX_RCDATA); break; case GUMBO_TAG_STYLE: case GUMBO_TAG_XMP: case GUMBO_TAG_IFRAME: case GUMBO_TAG_NOEMBED: case GUMBO_TAG_NOFRAMES: gumbo_tokenizer_set_state(parser, GUMBO_LEX_RAWTEXT); break; case GUMBO_TAG_SCRIPT: gumbo_tokenizer_set_state(parser, GUMBO_LEX_SCRIPT); break; case GUMBO_TAG_NOSCRIPT: /* scripting is disabled in Gumbo, so leave the tokenizer * in the default data state */ break; case GUMBO_TAG_PLAINTEXT: gumbo_tokenizer_set_state(parser, GUMBO_LEX_PLAINTEXT); break; default: /* default data state */ break; } } // 5. 6. 7. root = insert_element_of_tag_type( parser, GUMBO_TAG_HTML, GUMBO_INSERTION_IMPLIED); parser->_output->root = root; // 8. if (fragment_ctx == GUMBO_TAG_TEMPLATE) { push_template_insertion_mode(parser, GUMBO_INSERTION_MODE_IN_TEMPLATE); } // 10. reset_insertion_mode_appropriately(parser); } GumboOutput* gumbo_parse(const char* buffer) { return gumbo_parse_with_options( &kGumboDefaultOptions, buffer, strlen(buffer)); } GumboOutput* gumbo_parse_with_options( const GumboOptions* options, const char* buffer, size_t length) { GumboParser parser; parser._options = options; output_init(&parser); gumbo_tokenizer_state_init(&parser, buffer, length); parser_state_init(&parser); if (options->fragment_context != GUMBO_TAG_LAST) { fragment_parser_init( &parser, options->fragment_context, options->fragment_namespace); } GumboParserState* state = parser._parser_state; gumbo_debug("Parsing %.*s.\n", length, buffer); // Sanity check so that infinite loops die with an assertion failure instead // of hanging the process before we ever get an error. int loop_count = 0; GumboToken token; bool has_error = false; do { if (state->_reprocess_current_token) { state->_reprocess_current_token = false; } else { GumboNode* current_node = get_current_node(&parser); gumbo_tokenizer_set_is_current_node_foreign(&parser, current_node && current_node->v.element.tag_namespace != GUMBO_NAMESPACE_HTML); has_error = !gumbo_lex(&parser, &token) || has_error; } const char* token_type = "text"; switch (token.type) { case GUMBO_TOKEN_DOCTYPE: token_type = "doctype"; break; case GUMBO_TOKEN_START_TAG: token_type = gumbo_normalized_tagname(token.v.start_tag.tag); break; case GUMBO_TOKEN_END_TAG: token_type = gumbo_normalized_tagname(token.v.end_tag); break; case GUMBO_TOKEN_COMMENT: token_type = "comment"; break; default: break; } gumbo_debug("Handling %s token @%d:%d in state %d.\n", (char*) token_type, token.position.line, token.position.column, state->_insertion_mode); state->_current_token = &token; state->_self_closing_flag_acknowledged = !(token.type == GUMBO_TOKEN_START_TAG && token.v.start_tag.is_self_closing); has_error = !handle_token(&parser, &token) || has_error; // Check for memory leaks when ownership is transferred from start tag // tokens to nodes. assert(state->_reprocess_current_token || token.type != GUMBO_TOKEN_START_TAG || token.v.start_tag.attributes.data == NULL); if (!state->_self_closing_flag_acknowledged) { GumboError* error = parser_add_parse_error(&parser, &token); if (error) { error->type = GUMBO_ERR_UNACKNOWLEDGED_SELF_CLOSING_TAG; } } ++loop_count; assert(loop_count < 1000000000); } while ((token.type != GUMBO_TOKEN_EOF || state->_reprocess_current_token) && !(options->stop_on_first_error && has_error)); finish_parsing(&parser); // For API uniformity reasons, if the doctype still has nulls, convert them to // empty strings. GumboDocument* doc_type = &parser._output->document->v.document; if (doc_type->name == NULL) { doc_type->name = gumbo_copy_stringz(&parser, ""); } if (doc_type->public_identifier == NULL) { doc_type->public_identifier = gumbo_copy_stringz(&parser, ""); } if (doc_type->system_identifier == NULL) { doc_type->system_identifier = gumbo_copy_stringz(&parser, ""); } parser_state_destroy(&parser); gumbo_tokenizer_state_destroy(&parser); return parser._output; } void gumbo_destroy_node(GumboOptions* options, GumboNode* node) { // Need a dummy GumboParser because the allocator comes along with the // options object. GumboParser parser; parser._options = options; destroy_node(&parser, node); } void gumbo_destroy_output(const GumboOptions* options, GumboOutput* output) { // Need a dummy GumboParser because the allocator comes along with the // options object. GumboParser parser; parser._options = options; destroy_node(&parser, output->document); for (unsigned int i = 0; i < output->errors.length; ++i) { gumbo_error_destroy(&parser, output->errors.data[i]); } gumbo_vector_destroy(&parser, &output->errors); gumbo_parser_deallocate(&parser, output); } ================================================ FILE: zhuishushenqi/Vendor/OCGumbo/gumbo/parser.h ================================================ // Copyright 2010 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // Author: jdtang@google.com (Jonathan Tang) // // Contains the definition of the top-level GumboParser structure that's // threaded through basically every internal function in the library. #ifndef GUMBO_PARSER_H_ #define GUMBO_PARSER_H_ #ifdef __cplusplus extern "C" { #endif struct GumboInternalParserState; struct GumboInternalOutput; struct GumboInternalOptions; struct GumboInternalTokenizerState; // An overarching struct that's threaded through (nearly) all functions in the // library, OOP-style. This gives each function access to the options and // output, along with any internal state needed for the parse. typedef struct GumboInternalParser { // Settings for this parse run. const struct GumboInternalOptions* _options; // Output for the parse. struct GumboInternalOutput* _output; // The internal tokenizer state, defined as a pointer to avoid a cyclic // dependency on html5tokenizer.h. The main parse routine is responsible for // initializing this on parse start, and destroying it on parse end. // End-users will never see a non-garbage value in this pointer. struct GumboInternalTokenizerState* _tokenizer_state; // The internal parser state. Initialized on parse start and destroyed on // parse end; end-users will never see a non-garbage value in this pointer. struct GumboInternalParserState* _parser_state; } GumboParser; #ifdef __cplusplus } #endif #endif // GUMBO_PARSER_H_ ================================================ FILE: zhuishushenqi/Vendor/OCGumbo/gumbo/string_buffer.c ================================================ // Copyright 2010 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // Author: jdtang@google.com (Jonathan Tang) #include "string_buffer.h" #include <assert.h> #include <stdlib.h> #include <string.h> #include <strings.h> #include "string_piece.h" #include "util.h" struct GumboInternalParser; // Size chosen via statistical analysis of ~60K websites. // 99% of text nodes and 98% of attribute names/values fit in this initial size. static const size_t kDefaultStringBufferSize = 5; static void maybe_resize_string_buffer(struct GumboInternalParser* parser, size_t additional_chars, GumboStringBuffer* buffer) { size_t new_length = buffer->length + additional_chars; size_t new_capacity = buffer->capacity; while (new_capacity < new_length) { new_capacity *= 2; } if (new_capacity != buffer->capacity) { char* new_data = gumbo_parser_allocate(parser, new_capacity); memcpy(new_data, buffer->data, buffer->length); gumbo_parser_deallocate(parser, buffer->data); buffer->data = new_data; buffer->capacity = new_capacity; } } void gumbo_string_buffer_init( struct GumboInternalParser* parser, GumboStringBuffer* output) { output->data = gumbo_parser_allocate(parser, kDefaultStringBufferSize); output->length = 0; output->capacity = kDefaultStringBufferSize; } void gumbo_string_buffer_reserve(struct GumboInternalParser* parser, size_t min_capacity, GumboStringBuffer* output) { maybe_resize_string_buffer(parser, min_capacity - output->length, output); } void gumbo_string_buffer_append_codepoint( struct GumboInternalParser* parser, int c, GumboStringBuffer* output) { // num_bytes is actually the number of continuation bytes, 1 less than the // total number of bytes. This is done to keep the loop below simple and // should probably change if we unroll it. int num_bytes, prefix; if (c <= 0x7f) { num_bytes = 0; prefix = 0; } else if (c <= 0x7ff) { num_bytes = 1; prefix = 0xc0; } else if (c <= 0xffff) { num_bytes = 2; prefix = 0xe0; } else { num_bytes = 3; prefix = 0xf0; } maybe_resize_string_buffer(parser, num_bytes + 1, output); output->data[output->length++] = prefix | (c >> (num_bytes * 6)); for (int i = num_bytes - 1; i >= 0; --i) { output->data[output->length++] = 0x80 | (0x3f & (c >> (i * 6))); } } void gumbo_string_buffer_append_string(struct GumboInternalParser* parser, GumboStringPiece* str, GumboStringBuffer* output) { maybe_resize_string_buffer(parser, str->length, output); memcpy(output->data + output->length, str->data, str->length); output->length += str->length; } char* gumbo_string_buffer_to_string( struct GumboInternalParser* parser, GumboStringBuffer* input) { char* buffer = gumbo_parser_allocate(parser, input->length + 1); memcpy(buffer, input->data, input->length); buffer[input->length] = '\0'; return buffer; } void gumbo_string_buffer_clear( struct GumboInternalParser* parser, GumboStringBuffer* input) { input->length = 0; } void gumbo_string_buffer_destroy( struct GumboInternalParser* parser, GumboStringBuffer* buffer) { gumbo_parser_deallocate(parser, buffer->data); } ================================================ FILE: zhuishushenqi/Vendor/OCGumbo/gumbo/string_buffer.h ================================================ // Copyright 2010 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // Author: jdtang@google.com (Jonathan Tang) // #ifndef GUMBO_STRING_BUFFER_H_ #define GUMBO_STRING_BUFFER_H_ #include <stdbool.h> #include <stddef.h> #include "gumbo.h" #ifdef __cplusplus extern "C" { #endif struct GumboInternalParser; // A struct representing a mutable, growable string. This consists of a // heap-allocated buffer that may grow (by doubling) as necessary. When // converting to a string, this allocates a new buffer that is only as long as // it needs to be. Note that the internal buffer here is *not* nul-terminated, // so be sure not to use ordinary string manipulation functions on it. typedef struct { // A pointer to the beginning of the string. NULL iff length == 0. char* data; // The length of the string fragment, in bytes. May be zero. size_t length; // The capacity of the buffer, in bytes. size_t capacity; } GumboStringBuffer; // Initializes a new GumboStringBuffer. void gumbo_string_buffer_init( struct GumboInternalParser* parser, GumboStringBuffer* output); // Ensures that the buffer contains at least a certain amount of space. Most // useful with snprintf and the other length-delimited string functions, which // may want to write directly into the buffer. void gumbo_string_buffer_reserve(struct GumboInternalParser* parser, size_t min_capacity, GumboStringBuffer* output); // Appends a single Unicode codepoint onto the end of the GumboStringBuffer. // This is essentially a UTF-8 encoder, and may add 1-4 bytes depending on the // value of the codepoint. void gumbo_string_buffer_append_codepoint( struct GumboInternalParser* parser, int c, GumboStringBuffer* output); // Appends a string onto the end of the GumboStringBuffer. void gumbo_string_buffer_append_string(struct GumboInternalParser* parser, GumboStringPiece* str, GumboStringBuffer* output); // Converts this string buffer to const char*, alloctaing a new buffer for it. char* gumbo_string_buffer_to_string( struct GumboInternalParser* parser, GumboStringBuffer* input); // Reinitialize this string buffer. This clears it by setting length=0. It // does not zero out the buffer itself. void gumbo_string_buffer_clear( struct GumboInternalParser* parser, GumboStringBuffer* input); // Deallocates this GumboStringBuffer. void gumbo_string_buffer_destroy( struct GumboInternalParser* parser, GumboStringBuffer* buffer); #ifdef __cplusplus } #endif #endif // GUMBO_STRING_BUFFER_H_ ================================================ FILE: zhuishushenqi/Vendor/OCGumbo/gumbo/string_piece.c ================================================ // Copyright 2010 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // Author: jdtang@google.com (Jonathan Tang) #include "string_piece.h" #include <assert.h> #include <stdlib.h> #include <string.h> #include <strings.h> #include "util.h" struct GumboInternalParser; const GumboStringPiece kGumboEmptyString = {NULL, 0}; bool gumbo_string_equals( const GumboStringPiece* str1, const GumboStringPiece* str2) { return str1->length == str2->length && !memcmp(str1->data, str2->data, str1->length); } bool gumbo_string_equals_ignore_case( const GumboStringPiece* str1, const GumboStringPiece* str2) { return str1->length == str2->length && !strncasecmp(str1->data, str2->data, str1->length); } void gumbo_string_copy(struct GumboInternalParser* parser, GumboStringPiece* dest, const GumboStringPiece* source) { dest->length = source->length; char* buffer = gumbo_parser_allocate(parser, source->length); memcpy(buffer, source->data, source->length); dest->data = buffer; } ================================================ FILE: zhuishushenqi/Vendor/OCGumbo/gumbo/string_piece.h ================================================ // Copyright 2010 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // Author: jdtang@google.com (Jonathan Tang) #ifndef GUMBO_STRING_PIECE_H_ #define GUMBO_STRING_PIECE_H_ #include "gumbo.h" #ifdef __cplusplus extern "C" { #endif struct GumboInternalParser; // Performs a deep-copy of an GumboStringPiece, allocating a fresh buffer in the // destination and copying over the characters from source. Dest should be // empty, with no buffer allocated; otherwise, this leaks it. void gumbo_string_copy(struct GumboInternalParser* parser, GumboStringPiece* dest, const GumboStringPiece* source); #ifdef __cplusplus } #endif #endif // GUMBO_STRING_PIECE_H_ ================================================ FILE: zhuishushenqi/Vendor/OCGumbo/gumbo/tag.c ================================================ // Copyright 2011 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // Author: jdtang@google.com (Jonathan Tang) #include "gumbo.h" #include <assert.h> #include <ctype.h> #include <string.h> const char* kGumboTagNames[] = { #include "tag_strings.h" "", // TAG_UNKNOWN "", // TAG_LAST }; static const unsigned char kGumboTagSizes[] = { #include "tag_sizes.h" 0, // TAG_UNKNOWN 0, // TAG_LAST }; const char* gumbo_normalized_tagname(GumboTag tag) { assert(tag <= GUMBO_TAG_LAST); return kGumboTagNames[tag]; } void gumbo_tag_from_original_text(GumboStringPiece* text) { if (text->data == NULL) { return; } assert(text->length >= 2); assert(text->data[0] == '<'); assert(text->data[text->length - 1] == '>'); if (text->data[1] == '/') { // End tag. assert(text->length >= 3); text->data += 2; // Move past </ text->length -= 3; } else { // Start tag. text->data += 1; // Move past < text->length -= 2; // strnchr is apparently not a standard C library function, so I loop // explicitly looking for whitespace or other illegal tag characters. for (const char* c = text->data; c != text->data + text->length; ++c) { if (isspace(*c) || *c == '/') { text->length = c - text->data; break; } } } } static int case_memcmp(const char* s1, const char* s2, unsigned int n) { while (n--) { unsigned char c1 = tolower(*s1++); unsigned char c2 = tolower(*s2++); if (c1 != c2) return (int) c1 - (int) c2; } return 0; } #include "tag_gperf.h" #define TAG_MAP_SIZE (sizeof(kGumboTagMap) / sizeof(kGumboTagMap[0])) GumboTag gumbo_tagn_enum(const char* tagname, unsigned int length) { if (length) { unsigned int key = tag_hash(tagname, length); if (key < TAG_MAP_SIZE) { GumboTag tag = kGumboTagMap[key]; if (length == kGumboTagSizes[(int) tag] && !case_memcmp(tagname, kGumboTagNames[(int) tag], length)) return tag; } } return GUMBO_TAG_UNKNOWN; } GumboTag gumbo_tag_enum(const char* tagname) { return gumbo_tagn_enum(tagname, strlen(tagname)); } ================================================ FILE: zhuishushenqi/Vendor/OCGumbo/gumbo/tag.in ================================================ html head title base link meta style script noscript template body article section nav aside h1 h2 h3 h4 h5 h6 hgroup header footer address p hr pre blockquote ol ul li dl dt dd figure figcaption main div a em strong small s cite q dfn abbr data time code var samp kbd sub sup i b u mark ruby rt rp bdi bdo span br wbr ins del image img iframe embed object param video audio source track canvas map area math mi mo mn ms mtext mglyph malignmark annotation-xml svg foreignobject desc table caption colgroup col tbody thead tfoot tr td th form fieldset legend label input button select datalist optgroup option textarea keygen output progress meter details summary menu menuitem applet acronym bgsound dir frame frameset noframes isindex listing xmp nextid noembed plaintext rb strike basefont big blink center font marquee multicol nobr spacer tt rtc ================================================ FILE: zhuishushenqi/Vendor/OCGumbo/gumbo/tag_enum.h ================================================ // Generated via `gentags.py src/tag.in`. // Do not edit; edit src/tag.in instead. // clang-format off GUMBO_TAG_HTML, GUMBO_TAG_HEAD, GUMBO_TAG_TITLE, GUMBO_TAG_BASE, GUMBO_TAG_LINK, GUMBO_TAG_META, GUMBO_TAG_STYLE, GUMBO_TAG_SCRIPT, GUMBO_TAG_NOSCRIPT, GUMBO_TAG_TEMPLATE, GUMBO_TAG_BODY, GUMBO_TAG_ARTICLE, GUMBO_TAG_SECTION, GUMBO_TAG_NAV, GUMBO_TAG_ASIDE, GUMBO_TAG_H1, GUMBO_TAG_H2, GUMBO_TAG_H3, GUMBO_TAG_H4, GUMBO_TAG_H5, GUMBO_TAG_H6, GUMBO_TAG_HGROUP, GUMBO_TAG_HEADER, GUMBO_TAG_FOOTER, GUMBO_TAG_ADDRESS, GUMBO_TAG_P, GUMBO_TAG_HR, GUMBO_TAG_PRE, GUMBO_TAG_BLOCKQUOTE, GUMBO_TAG_OL, GUMBO_TAG_UL, GUMBO_TAG_LI, GUMBO_TAG_DL, GUMBO_TAG_DT, GUMBO_TAG_DD, GUMBO_TAG_FIGURE, GUMBO_TAG_FIGCAPTION, GUMBO_TAG_MAIN, GUMBO_TAG_DIV, GUMBO_TAG_A, GUMBO_TAG_EM, GUMBO_TAG_STRONG, GUMBO_TAG_SMALL, GUMBO_TAG_S, GUMBO_TAG_CITE, GUMBO_TAG_Q, GUMBO_TAG_DFN, GUMBO_TAG_ABBR, GUMBO_TAG_DATA, GUMBO_TAG_TIME, GUMBO_TAG_CODE, GUMBO_TAG_VAR, GUMBO_TAG_SAMP, GUMBO_TAG_KBD, GUMBO_TAG_SUB, GUMBO_TAG_SUP, GUMBO_TAG_I, GUMBO_TAG_B, GUMBO_TAG_U, GUMBO_TAG_MARK, GUMBO_TAG_RUBY, GUMBO_TAG_RT, GUMBO_TAG_RP, GUMBO_TAG_BDI, GUMBO_TAG_BDO, GUMBO_TAG_SPAN, GUMBO_TAG_BR, GUMBO_TAG_WBR, GUMBO_TAG_INS, GUMBO_TAG_DEL, GUMBO_TAG_IMAGE, GUMBO_TAG_IMG, GUMBO_TAG_IFRAME, GUMBO_TAG_EMBED, GUMBO_TAG_OBJECT, GUMBO_TAG_PARAM, GUMBO_TAG_VIDEO, GUMBO_TAG_AUDIO, GUMBO_TAG_SOURCE, GUMBO_TAG_TRACK, GUMBO_TAG_CANVAS, GUMBO_TAG_MAP, GUMBO_TAG_AREA, GUMBO_TAG_MATH, GUMBO_TAG_MI, GUMBO_TAG_MO, GUMBO_TAG_MN, GUMBO_TAG_MS, GUMBO_TAG_MTEXT, GUMBO_TAG_MGLYPH, GUMBO_TAG_MALIGNMARK, GUMBO_TAG_ANNOTATION_XML, GUMBO_TAG_SVG, GUMBO_TAG_FOREIGNOBJECT, GUMBO_TAG_DESC, GUMBO_TAG_TABLE, GUMBO_TAG_CAPTION, GUMBO_TAG_COLGROUP, GUMBO_TAG_COL, GUMBO_TAG_TBODY, GUMBO_TAG_THEAD, GUMBO_TAG_TFOOT, GUMBO_TAG_TR, GUMBO_TAG_TD, GUMBO_TAG_TH, GUMBO_TAG_FORM, GUMBO_TAG_FIELDSET, GUMBO_TAG_LEGEND, GUMBO_TAG_LABEL, GUMBO_TAG_INPUT, GUMBO_TAG_BUTTON, GUMBO_TAG_SELECT, GUMBO_TAG_DATALIST, GUMBO_TAG_OPTGROUP, GUMBO_TAG_OPTION, GUMBO_TAG_TEXTAREA, GUMBO_TAG_KEYGEN, GUMBO_TAG_OUTPUT, GUMBO_TAG_PROGRESS, GUMBO_TAG_METER, GUMBO_TAG_DETAILS, GUMBO_TAG_SUMMARY, GUMBO_TAG_MENU, GUMBO_TAG_MENUITEM, GUMBO_TAG_APPLET, GUMBO_TAG_ACRONYM, GUMBO_TAG_BGSOUND, GUMBO_TAG_DIR, GUMBO_TAG_FRAME, GUMBO_TAG_FRAMESET, GUMBO_TAG_NOFRAMES, GUMBO_TAG_ISINDEX, GUMBO_TAG_LISTING, GUMBO_TAG_XMP, GUMBO_TAG_NEXTID, GUMBO_TAG_NOEMBED, GUMBO_TAG_PLAINTEXT, GUMBO_TAG_RB, GUMBO_TAG_STRIKE, GUMBO_TAG_BASEFONT, GUMBO_TAG_BIG, GUMBO_TAG_BLINK, GUMBO_TAG_CENTER, GUMBO_TAG_FONT, GUMBO_TAG_MARQUEE, GUMBO_TAG_MULTICOL, GUMBO_TAG_NOBR, GUMBO_TAG_SPACER, GUMBO_TAG_TT, GUMBO_TAG_RTC, ================================================ FILE: zhuishushenqi/Vendor/OCGumbo/gumbo/tag_gperf.h ================================================ static unsigned int tag_hash( register const char *str, register unsigned int len) { static unsigned short asso_values[] = {296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 6, 4, 3, 1, 1, 0, 1, 0, 0, 296, 296, 296, 296, 296, 296, 296, 22, 73, 151, 4, 13, 59, 65, 2, 69, 0, 134, 9, 16, 52, 55, 28, 101, 0, 1, 6, 63, 126, 104, 93, 124, 296, 296, 296, 296, 296, 296, 296, 22, 73, 151, 4, 13, 59, 65, 2, 69, 0, 134, 9, 16, 52, 55, 28, 101, 0, 1, 6, 63, 126, 104, 93, 124, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296, 296}; register unsigned int hval = len; switch (hval) { default: hval += asso_values[(unsigned char) str[1] + 3]; /*FALLTHROUGH*/ case 1: hval += asso_values[(unsigned char) str[0]]; break; } return hval + asso_values[(unsigned char) str[len - 1]]; } static const unsigned char kGumboTagMap[] = {GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_S, GUMBO_TAG_H6, GUMBO_TAG_H5, GUMBO_TAG_H4, GUMBO_TAG_H3, GUMBO_TAG_SPACER, GUMBO_TAG_H2, GUMBO_TAG_HEADER, GUMBO_TAG_H1, GUMBO_TAG_HEAD, GUMBO_TAG_LAST, GUMBO_TAG_DETAILS, GUMBO_TAG_SELECT, GUMBO_TAG_DIR, GUMBO_TAG_LAST, GUMBO_TAG_DEL, GUMBO_TAG_LAST, GUMBO_TAG_SOURCE, GUMBO_TAG_LEGEND, GUMBO_TAG_DATALIST, GUMBO_TAG_METER, GUMBO_TAG_MGLYPH, GUMBO_TAG_LAST, GUMBO_TAG_MATH, GUMBO_TAG_LABEL, GUMBO_TAG_TABLE, GUMBO_TAG_TEMPLATE, GUMBO_TAG_LAST, GUMBO_TAG_RP, GUMBO_TAG_TIME, GUMBO_TAG_TITLE, GUMBO_TAG_DATA, GUMBO_TAG_APPLET, GUMBO_TAG_HGROUP, GUMBO_TAG_SAMP, GUMBO_TAG_TEXTAREA, GUMBO_TAG_ABBR, GUMBO_TAG_MARQUEE, GUMBO_TAG_LAST, GUMBO_TAG_MENUITEM, GUMBO_TAG_SMALL, GUMBO_TAG_META, GUMBO_TAG_A, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_EMBED, GUMBO_TAG_MAP, GUMBO_TAG_LAST, GUMBO_TAG_PARAM, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_NOBR, GUMBO_TAG_P, GUMBO_TAG_SPAN, GUMBO_TAG_EM, GUMBO_TAG_LAST, GUMBO_TAG_NOFRAMES, GUMBO_TAG_SECTION, GUMBO_TAG_NOEMBED, GUMBO_TAG_NEXTID, GUMBO_TAG_FOOTER, GUMBO_TAG_NOSCRIPT, GUMBO_TAG_HR, GUMBO_TAG_LAST, GUMBO_TAG_FONT, GUMBO_TAG_DL, GUMBO_TAG_TR, GUMBO_TAG_SCRIPT, GUMBO_TAG_MO, GUMBO_TAG_LAST, GUMBO_TAG_DD, GUMBO_TAG_MAIN, GUMBO_TAG_TD, GUMBO_TAG_FOREIGNOBJECT, GUMBO_TAG_FORM, GUMBO_TAG_OBJECT, GUMBO_TAG_LAST, GUMBO_TAG_FIELDSET, GUMBO_TAG_LAST, GUMBO_TAG_BGSOUND, GUMBO_TAG_MENU, GUMBO_TAG_TFOOT, GUMBO_TAG_FIGURE, GUMBO_TAG_RB, GUMBO_TAG_LI, GUMBO_TAG_LISTING, GUMBO_TAG_BASEFONT, GUMBO_TAG_OPTGROUP, GUMBO_TAG_LAST, GUMBO_TAG_BASE, GUMBO_TAG_ADDRESS, GUMBO_TAG_MI, GUMBO_TAG_LAST, GUMBO_TAG_PLAINTEXT, GUMBO_TAG_LAST, GUMBO_TAG_PROGRESS, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_ACRONYM, GUMBO_TAG_ARTICLE, GUMBO_TAG_LAST, GUMBO_TAG_PRE, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_AREA, GUMBO_TAG_RT, GUMBO_TAG_LAST, GUMBO_TAG_OPTION, GUMBO_TAG_IMAGE, GUMBO_TAG_DT, GUMBO_TAG_LAST, GUMBO_TAG_TT, GUMBO_TAG_HTML, GUMBO_TAG_WBR, GUMBO_TAG_OL, GUMBO_TAG_LAST, GUMBO_TAG_STYLE, GUMBO_TAG_STRIKE, GUMBO_TAG_SUP, GUMBO_TAG_MULTICOL, GUMBO_TAG_U, GUMBO_TAG_DFN, GUMBO_TAG_UL, GUMBO_TAG_FIGCAPTION, GUMBO_TAG_MTEXT, GUMBO_TAG_LAST, GUMBO_TAG_VAR, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_FRAMESET, GUMBO_TAG_LAST, GUMBO_TAG_BR, GUMBO_TAG_I, GUMBO_TAG_FRAME, GUMBO_TAG_LAST, GUMBO_TAG_DIV, GUMBO_TAG_LAST, GUMBO_TAG_TH, GUMBO_TAG_MS, GUMBO_TAG_ANNOTATION_XML, GUMBO_TAG_B, GUMBO_TAG_TBODY, GUMBO_TAG_THEAD, GUMBO_TAG_BIG, GUMBO_TAG_BLOCKQUOTE, GUMBO_TAG_XMP, GUMBO_TAG_LAST, GUMBO_TAG_KBD, GUMBO_TAG_LAST, GUMBO_TAG_LINK, GUMBO_TAG_IFRAME, GUMBO_TAG_MARK, GUMBO_TAG_CENTER, GUMBO_TAG_OUTPUT, GUMBO_TAG_DESC, GUMBO_TAG_CANVAS, GUMBO_TAG_COL, GUMBO_TAG_MALIGNMARK, GUMBO_TAG_IMG, GUMBO_TAG_ASIDE, GUMBO_TAG_LAST, GUMBO_TAG_CODE, GUMBO_TAG_LAST, GUMBO_TAG_SUB, GUMBO_TAG_MN, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_INS, GUMBO_TAG_AUDIO, GUMBO_TAG_STRONG, GUMBO_TAG_CITE, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_INPUT, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_NAV, GUMBO_TAG_LAST, GUMBO_TAG_COLGROUP, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_SVG, GUMBO_TAG_KEYGEN, GUMBO_TAG_VIDEO, GUMBO_TAG_BDO, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_BODY, GUMBO_TAG_LAST, GUMBO_TAG_Q, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_TRACK, GUMBO_TAG_LAST, GUMBO_TAG_BDI, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_CAPTION, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_RUBY, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_BUTTON, GUMBO_TAG_SUMMARY, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_RTC, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_BLINK, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_LAST, GUMBO_TAG_ISINDEX}; ================================================ FILE: zhuishushenqi/Vendor/OCGumbo/gumbo/tag_sizes.h ================================================ // Generated via `gentags.py src/tag.in`. // Do not edit; edit src/tag.in instead. // clang-format off 4, 4, 5, 4, 4, 4, 5, 6, 8, 8, 4, 7, 7, 3, 5, 2, 2, 2, 2, 2, 2, 6, 6, 6, 7, 1, 2, 3, 10, 2, 2, 2, 2, 2, 2, 6, 10, 4, 3, 1, 2, 6, 5, 1, 4, 1, 3, 4, 4, 4, 4, 3, 4, 3, 3, 3, 1, 1, 1, 4, 4, 2, 2, 3, 3, 4, 2, 3, 3, 3, 5, 3, 6, 5, 6, 5, 5, 5, 6, 5, 6, 3, 4, 4, 2, 2, 2, 2, 5, 6, 10, 14, 3, 13, 4, 5, 7, 8, 3, 5, 5, 5, 2, 2, 2, 4, 8, 6, 5, 5, 6, 6, 8, 8, 6, 8, 6, 6, 8, 5, 7, 7, 4, 8, 6, 7, 7, 3, 5, 8, 8, 7, 7, 3, 6, 7, 9, 2, 6, 8, 3, 5, 6, 4, 7, 8, 4, 6, 2, 3, ================================================ FILE: zhuishushenqi/Vendor/OCGumbo/gumbo/tag_strings.h ================================================ // Generated via `gentags.py src/tag.in`. // Do not edit; edit src/tag.in instead. // clang-format off "html", "head", "title", "base", "link", "meta", "style", "script", "noscript", "template", "body", "article", "section", "nav", "aside", "h1", "h2", "h3", "h4", "h5", "h6", "hgroup", "header", "footer", "address", "p", "hr", "pre", "blockquote", "ol", "ul", "li", "dl", "dt", "dd", "figure", "figcaption", "main", "div", "a", "em", "strong", "small", "s", "cite", "q", "dfn", "abbr", "data", "time", "code", "var", "samp", "kbd", "sub", "sup", "i", "b", "u", "mark", "ruby", "rt", "rp", "bdi", "bdo", "span", "br", "wbr", "ins", "del", "image", "img", "iframe", "embed", "object", "param", "video", "audio", "source", "track", "canvas", "map", "area", "math", "mi", "mo", "mn", "ms", "mtext", "mglyph", "malignmark", "annotation-xml", "svg", "foreignobject", "desc", "table", "caption", "colgroup", "col", "tbody", "thead", "tfoot", "tr", "td", "th", "form", "fieldset", "legend", "label", "input", "button", "select", "datalist", "optgroup", "option", "textarea", "keygen", "output", "progress", "meter", "details", "summary", "menu", "menuitem", "applet", "acronym", "bgsound", "dir", "frame", "frameset", "noframes", "isindex", "listing", "xmp", "nextid", "noembed", "plaintext", "rb", "strike", "basefont", "big", "blink", "center", "font", "marquee", "multicol", "nobr", "spacer", "tt", "rtc", ================================================ FILE: zhuishushenqi/Vendor/OCGumbo/gumbo/token_type.h ================================================ // Copyright 2011 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // Author: jdtang@google.com (Jonathan Tang) #ifndef GUMBO_TOKEN_TYPE_H_ #define GUMBO_TOKEN_TYPE_H_ #ifdef __cplusplus extern "C" { #endif // An enum representing the type of token. typedef enum { GUMBO_TOKEN_DOCTYPE, GUMBO_TOKEN_START_TAG, GUMBO_TOKEN_END_TAG, GUMBO_TOKEN_COMMENT, GUMBO_TOKEN_WHITESPACE, GUMBO_TOKEN_CHARACTER, GUMBO_TOKEN_CDATA, GUMBO_TOKEN_NULL, GUMBO_TOKEN_EOF } GumboTokenType; #ifdef __cplusplus } // extern C #endif #endif // GUMBO_TOKEN_TYPE_H_ ================================================ FILE: zhuishushenqi/Vendor/OCGumbo/gumbo/tokenizer.c ================================================ // Copyright 2010 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // Author: jdtang@google.com (Jonathan Tang) // // Coding conventions specific to this file: // // 1. Functions that fill in a token should be named emit_*, and should be // followed immediately by a return from the tokenizer (true if no error // occurred, false if an error occurred). Sometimes the emit functions // themselves return a boolean so that they can be combined with the return // statement; in this case, they should match this convention. // 2. Functions that shuffle data from temporaries to final API structures // should be named finish_*, and be called just before the tokenizer exits the // state that accumulates the temporary. // 3. All internal data structures should be kept in an initialized state from // tokenizer creation onwards, ready to accept input. When a buffer's flushed // and reset, it should be deallocated and immediately reinitialized. // 4. Make sure there are appropriate break statements following each state. // 5. Assertions on the state of the temporary and tag buffers are usually a // good idea, and should go at the entry point of each state when added. // 6. Statement order within states goes: // 1. Add parse errors, if appropriate. // 2. Call finish_* functions to build up tag state. // 2. Switch to new state. Set _reconsume flag if appropriate. // 3. Perform any other temporary buffer manipulation. // 4. Emit tokens // 5. Return/break. // This order ensures that we can verify that every emit is followed by a // return, ensures that the correct state is recorded with any parse errors, and // prevents parse error position from being messed up by possible mark/resets in // temporary buffer manipulation. #include "tokenizer.h" #include <assert.h> #include <stdbool.h> #include <string.h> #include "attribute.h" #include "char_ref.h" #include "error.h" #include "gumbo.h" #include "parser.h" #include "string_buffer.h" #include "string_piece.h" #include "token_type.h" #include "tokenizer_states.h" #include "utf8.h" #include "util.h" #include "vector.h" // Compared against _script_data_buffer to determine if we're in double-escaped // script mode. const GumboStringPiece kScriptTag = {"script", 6}; // An enum for the return value of each individual state. typedef enum { RETURN_ERROR, // Return false (error) from the tokenizer. RETURN_SUCCESS, // Return true (success) from the tokenizer. NEXT_CHAR // Proceed to the next character and continue lexing. } StateResult; // This is a struct containing state necessary to build up a tag token, // character by character. typedef struct GumboInternalTagState { // A buffer to accumulate characters for various GumboStringPiece fields. GumboStringBuffer _buffer; // A pointer to the start of the original text corresponding to the contents // of the buffer. const char* _original_text; // The current tag enum, computed once the tag name state has finished so that // the buffer can be re-used for building up attributes. GumboTag _tag; // The starting location of the text in the buffer. GumboSourcePosition _start_pos; // The current list of attributes. This is copied (and ownership of its data // transferred) to the GumboStartTag token upon completion of the tag. New // attributes are added as soon as their attribute name state is complete, and // values are filled in by operating on _attributes.data[attributes.length-1]. GumboVector /* GumboAttribute */ _attributes; // If true, the next attribute value to be finished should be dropped. This // happens if a duplicate attribute name is encountered - we want to consume // the attribute value, but shouldn't overwrite the existing value. bool _drop_next_attr_value; // The state that caused the tokenizer to switch into a character reference in // attribute value state. This is used to set the additional allowed // character, and is switched back to on completion. Initialized as the // tokenizer enters the character reference state. GumboTokenizerEnum _attr_value_state; // The last start tag to have been emitted by the tokenizer. This is // necessary to check for appropriate end tags. GumboTag _last_start_tag; // If true, then this is a start tag. If false, it's an end tag. This is // necessary to generate the appropriate token type at tag-closing time. bool _is_start_tag; // If true, then this tag is "self-closing" and doesn't have an end tag. bool _is_self_closing; } GumboTagState; // This is the main tokenizer state struct, containing all state used by in // tokenizing the input stream. typedef struct GumboInternalTokenizerState { // The current lexer state. Starts in GUMBO_LEX_DATA. GumboTokenizerEnum _state; // A flag indicating whether the current input character needs to reconsumed // in another state, or whether the next input character should be read for // the next iteration of the state loop. This is set when the spec reads // "Reconsume the current input character in..." bool _reconsume_current_input; // A flag indicating whether the current node is a foreign element. This is // set by gumbo_tokenizer_set_is_current_node_foreign and checked in the // markup declaration state. bool _is_current_node_foreign; // A flag indicating whether the tokenizer is in a CDATA section. If so, then // text tokens emitted will be GUMBO_TOKEN_CDATA. bool _is_in_cdata; // Certain states (notably character references) may emit two character tokens // at once, but the contract for lex() fills in only one token at a time. The // extra character is buffered here, and then this is checked on entry to // lex(). If a character is stored here, it's immediately emitted and control // returns from the lexer. kGumboNoChar is used to represent 'no character // stored.' // // Note that characters emitted through this mechanism will have their source // position marked as the character under the mark, i.e. multiple characters // may be emitted with the same position. This is desirable for character // references, but unsuitable for many other cases. Use the _temporary_buffer // mechanism if the buffered characters must have their original positions in // the document. int _buffered_emit_char; // A temporary buffer to accumulate characters, as described by the "temporary // buffer" phrase in the tokenizer spec. We use this in a somewhat unorthodox // way: we record the specific character to go into the buffer, which may // sometimes be a lowercased version of the actual input character. However, // we *also* use utf8iterator_mark() to record the position at tag start. // When we start flushing the temporary buffer, we set _temporary_buffer_emit // to the start of it, and then increment it for each call to the tokenizer. // We also call utf8iterator_reset(), and utf8iterator_next() through the // input stream, so that tokens emitted by emit_char have the correct position // and original text. GumboStringBuffer _temporary_buffer; // The current cursor position we're emitting from within // _temporary_buffer.data. NULL whenever we're not flushing the buffer. const char* _temporary_buffer_emit; // The temporary buffer is also used by the spec to check whether we should // enter the script data double escaped state, but we can't use the same // buffer for both because we have to flush out "<s" as emits while still // maintaining the context that will eventually become "script". This is a // separate buffer that's used in place of the temporary buffer for states // that may enter the script data double escape start state. GumboStringBuffer _script_data_buffer; // Pointer to the beginning of the current token in the original buffer; used // to record the original text. const char* _token_start; // GumboSourcePosition recording the source location of the start of the // current token. GumboSourcePosition _token_start_pos; // Current tag state. GumboTagState _tag_state; // Doctype state. We use the temporary buffer to accumulate characters (it's // not used for anything else in the doctype states), and then freshly // allocate the strings in the doctype token, then copy it over on emit. GumboTokenDocType _doc_type_state; // The UTF8Iterator over the tokenizer input. Utf8Iterator _input; } GumboTokenizerState; // Adds an ERR_UNEXPECTED_CODE_POINT parse error to the parser's error struct. static void tokenizer_add_parse_error( GumboParser* parser, GumboErrorType type) { GumboError* error = gumbo_add_error(parser); if (!error) { return; } GumboTokenizerState* tokenizer = parser->_tokenizer_state; utf8iterator_get_position(&tokenizer->_input, &error->position); error->original_text = utf8iterator_get_char_pointer(&tokenizer->_input); error->type = type; error->v.tokenizer.codepoint = utf8iterator_current(&tokenizer->_input); switch (tokenizer->_state) { case GUMBO_LEX_DATA: error->v.tokenizer.state = GUMBO_ERR_TOKENIZER_DATA; break; case GUMBO_LEX_CHAR_REF_IN_DATA: case GUMBO_LEX_CHAR_REF_IN_RCDATA: case GUMBO_LEX_CHAR_REF_IN_ATTR_VALUE: error->v.tokenizer.state = GUMBO_ERR_TOKENIZER_CHAR_REF; break; case GUMBO_LEX_RCDATA: case GUMBO_LEX_RCDATA_LT: case GUMBO_LEX_RCDATA_END_TAG_OPEN: case GUMBO_LEX_RCDATA_END_TAG_NAME: error->v.tokenizer.state = GUMBO_ERR_TOKENIZER_RCDATA; break; case GUMBO_LEX_RAWTEXT: case GUMBO_LEX_RAWTEXT_LT: case GUMBO_LEX_RAWTEXT_END_TAG_OPEN: case GUMBO_LEX_RAWTEXT_END_TAG_NAME: error->v.tokenizer.state = GUMBO_ERR_TOKENIZER_RAWTEXT; break; case GUMBO_LEX_PLAINTEXT: error->v.tokenizer.state = GUMBO_ERR_TOKENIZER_PLAINTEXT; break; case GUMBO_LEX_SCRIPT: case GUMBO_LEX_SCRIPT_LT: case GUMBO_LEX_SCRIPT_END_TAG_OPEN: case GUMBO_LEX_SCRIPT_END_TAG_NAME: case GUMBO_LEX_SCRIPT_ESCAPED_START: case GUMBO_LEX_SCRIPT_ESCAPED_START_DASH: case GUMBO_LEX_SCRIPT_ESCAPED: case GUMBO_LEX_SCRIPT_ESCAPED_DASH: case GUMBO_LEX_SCRIPT_ESCAPED_DASH_DASH: case GUMBO_LEX_SCRIPT_ESCAPED_LT: case GUMBO_LEX_SCRIPT_ESCAPED_END_TAG_OPEN: case GUMBO_LEX_SCRIPT_ESCAPED_END_TAG_NAME: case GUMBO_LEX_SCRIPT_DOUBLE_ESCAPED_START: case GUMBO_LEX_SCRIPT_DOUBLE_ESCAPED: case GUMBO_LEX_SCRIPT_DOUBLE_ESCAPED_DASH: case GUMBO_LEX_SCRIPT_DOUBLE_ESCAPED_DASH_DASH: case GUMBO_LEX_SCRIPT_DOUBLE_ESCAPED_LT: case GUMBO_LEX_SCRIPT_DOUBLE_ESCAPED_END: error->v.tokenizer.state = GUMBO_ERR_TOKENIZER_SCRIPT; break; case GUMBO_LEX_TAG_OPEN: case GUMBO_LEX_END_TAG_OPEN: case GUMBO_LEX_TAG_NAME: case GUMBO_LEX_BEFORE_ATTR_NAME: error->v.tokenizer.state = GUMBO_ERR_TOKENIZER_TAG; break; case GUMBO_LEX_SELF_CLOSING_START_TAG: error->v.tokenizer.state = GUMBO_ERR_TOKENIZER_SELF_CLOSING_TAG; break; case GUMBO_LEX_ATTR_NAME: case GUMBO_LEX_AFTER_ATTR_NAME: case GUMBO_LEX_BEFORE_ATTR_VALUE: error->v.tokenizer.state = GUMBO_ERR_TOKENIZER_ATTR_NAME; break; case GUMBO_LEX_ATTR_VALUE_DOUBLE_QUOTED: case GUMBO_LEX_ATTR_VALUE_SINGLE_QUOTED: case GUMBO_LEX_ATTR_VALUE_UNQUOTED: case GUMBO_LEX_AFTER_ATTR_VALUE_QUOTED: error->v.tokenizer.state = GUMBO_ERR_TOKENIZER_ATTR_VALUE; break; case GUMBO_LEX_BOGUS_COMMENT: case GUMBO_LEX_COMMENT_START: case GUMBO_LEX_COMMENT_START_DASH: case GUMBO_LEX_COMMENT: case GUMBO_LEX_COMMENT_END_DASH: case GUMBO_LEX_COMMENT_END: case GUMBO_LEX_COMMENT_END_BANG: error->v.tokenizer.state = GUMBO_ERR_TOKENIZER_COMMENT; break; case GUMBO_LEX_MARKUP_DECLARATION: case GUMBO_LEX_DOCTYPE: case GUMBO_LEX_BEFORE_DOCTYPE_NAME: case GUMBO_LEX_DOCTYPE_NAME: case GUMBO_LEX_AFTER_DOCTYPE_NAME: case GUMBO_LEX_AFTER_DOCTYPE_PUBLIC_KEYWORD: case GUMBO_LEX_BEFORE_DOCTYPE_PUBLIC_ID: case GUMBO_LEX_DOCTYPE_PUBLIC_ID_DOUBLE_QUOTED: case GUMBO_LEX_DOCTYPE_PUBLIC_ID_SINGLE_QUOTED: case GUMBO_LEX_AFTER_DOCTYPE_PUBLIC_ID: case GUMBO_LEX_BETWEEN_DOCTYPE_PUBLIC_SYSTEM_ID: case GUMBO_LEX_AFTER_DOCTYPE_SYSTEM_KEYWORD: case GUMBO_LEX_BEFORE_DOCTYPE_SYSTEM_ID: case GUMBO_LEX_DOCTYPE_SYSTEM_ID_DOUBLE_QUOTED: case GUMBO_LEX_DOCTYPE_SYSTEM_ID_SINGLE_QUOTED: case GUMBO_LEX_AFTER_DOCTYPE_SYSTEM_ID: case GUMBO_LEX_BOGUS_DOCTYPE: error->v.tokenizer.state = GUMBO_ERR_TOKENIZER_DOCTYPE; break; case GUMBO_LEX_CDATA: error->v.tokenizer.state = GUMBO_ERR_TOKENIZER_CDATA; break; } } static bool is_alpha(int c) { // We don't use ISO C isupper/islower functions here because they // depend upon the program's locale, while the behavior of the HTML5 spec is // independent of which locale the program is run in. return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'); } static int ensure_lowercase(int c) { return c >= 'A' && c <= 'Z' ? c + 0x20 : c; } static GumboTokenType get_char_token_type(bool is_in_cdata, int c) { if (is_in_cdata && c > 0) { return GUMBO_TOKEN_CDATA; } switch (c) { case '\t': case '\n': case '\r': case '\f': case ' ': return GUMBO_TOKEN_WHITESPACE; case 0: gumbo_debug("Emitted null byte.\n"); return GUMBO_TOKEN_NULL; case -1: return GUMBO_TOKEN_EOF; default: return GUMBO_TOKEN_CHARACTER; } } // Starts recording characters in the temporary buffer. // Because this needs to reset the utf8iterator_mark to the beginning of the // text that will eventually be emitted, it needs to be called a couple of // states before the spec says "Set the temporary buffer to the empty string". // In general, this should be called whenever there's a transition to a // "less-than sign state". The initial < and possibly / then need to be // appended to the temporary buffer, their presence needs to be accounted for in // states that compare the temporary buffer against a literal value, and // spec stanzas that say "emit a < and / character token along with a character // token for each character in the temporary buffer" need to be adjusted to // account for the presence of the < and / inside the temporary buffer. static void clear_temporary_buffer(GumboParser* parser) { GumboTokenizerState* tokenizer = parser->_tokenizer_state; assert(!tokenizer->_temporary_buffer_emit); utf8iterator_mark(&tokenizer->_input); gumbo_string_buffer_clear(parser, &tokenizer->_temporary_buffer); // The temporary buffer and script data buffer are the same object in the // spec, so the script data buffer should be cleared as well. gumbo_string_buffer_clear(parser, &tokenizer->_script_data_buffer); } // Appends a codepoint to the temporary buffer. static void append_char_to_temporary_buffer( GumboParser* parser, int codepoint) { gumbo_string_buffer_append_codepoint( parser, codepoint, &parser->_tokenizer_state->_temporary_buffer); } // Checks to see if the temporary buffer equals a certain string. // Make sure this remains side-effect free; it's used in assertions. #ifndef NDEBUG static bool temporary_buffer_equals(GumboParser* parser, const char* text) { GumboStringBuffer* buffer = &parser->_tokenizer_state->_temporary_buffer; // TODO(jdtang): See if the extra strlen is a performance problem, and replace // it with an explicit sizeof(literal) if necessary. I don't think it will // be, as this is only used in a couple of rare states. int text_len = strlen(text); return text_len == buffer->length && memcmp(buffer->data, text, text_len) == 0; } #endif static void doc_type_state_init(GumboParser* parser) { GumboTokenDocType* doc_type_state = &parser->_tokenizer_state->_doc_type_state; // We initialize these to NULL here so that we don't end up leaking memory if // we never see a doctype token. When we do see a doctype token, we reset // them to a freshly-allocated empty string so that we can present a uniform // interface to client code and not make them check for null. Ownership is // transferred to the doctype token when it's emitted. doc_type_state->name = NULL; doc_type_state->public_identifier = NULL; doc_type_state->system_identifier = NULL; doc_type_state->force_quirks = false; doc_type_state->has_public_identifier = false; doc_type_state->has_system_identifier = false; } // Sets the token original_text and position to the current iterator position. // This is necessary because [CDATA[ sections may include text that is ignored // by the tokenizer. static void reset_token_start_point(GumboTokenizerState* tokenizer) { tokenizer->_token_start = utf8iterator_get_char_pointer(&tokenizer->_input); utf8iterator_get_position(&tokenizer->_input, &tokenizer->_token_start_pos); } // Sets the tag buffer original text and start point to the current iterator // position. This is necessary because attribute names & values may have // whitespace preceeding them, and so we can't assume that the actual token // starting point was the end of the last tag buffer usage. static void reset_tag_buffer_start_point(GumboParser* parser) { GumboTokenizerState* tokenizer = parser->_tokenizer_state; GumboTagState* tag_state = &tokenizer->_tag_state; utf8iterator_get_position(&tokenizer->_input, &tag_state->_start_pos); tag_state->_original_text = utf8iterator_get_char_pointer(&tokenizer->_input); } // Moves the temporary buffer contents over to the specified output string, // and clears the temporary buffer. static void finish_temporary_buffer(GumboParser* parser, const char** output) { GumboTokenizerState* tokenizer = parser->_tokenizer_state; *output = gumbo_string_buffer_to_string(parser, &tokenizer->_temporary_buffer); clear_temporary_buffer(parser); } // Advances the iterator past the end of the token, and then fills in the // relevant position fields. It's assumed that after every emit, the tokenizer // will immediately return (letting the tree-construction stage read the filled // in Token). Thus, it's safe to advance the input stream here, since it will // bypass the advance at the bottom of the state machine loop. // // Since this advances the iterator and resets the current input, make sure to // call it after you've recorded any other data you need for the token. static void finish_token(GumboParser* parser, GumboToken* token) { GumboTokenizerState* tokenizer = parser->_tokenizer_state; if (!tokenizer->_reconsume_current_input) { utf8iterator_next(&tokenizer->_input); } token->position = tokenizer->_token_start_pos; token->original_text.data = tokenizer->_token_start; reset_token_start_point(tokenizer); token->original_text.length = tokenizer->_token_start - token->original_text.data; if (token->original_text.length > 0 && token->original_text.data[token->original_text.length - 1] == '\r') { // The UTF8 iterator will ignore carriage returns in the input stream, which // means that the next token may start one past a \r character. The pointer // arithmetic above results in that \r being appended to the original text // of the preceding token, so we have to adjust its length here to chop the // \r off. --token->original_text.length; } } // Records the doctype public ID, assumed to be in the temporary buffer. // Convenience method that also sets has_public_identifier to true. static void finish_doctype_public_id(GumboParser* parser) { GumboTokenDocType* doc_type_state = &parser->_tokenizer_state->_doc_type_state; gumbo_parser_deallocate(parser, (void*) doc_type_state->public_identifier); finish_temporary_buffer(parser, &doc_type_state->public_identifier); doc_type_state->has_public_identifier = true; } // Records the doctype system ID, assumed to be in the temporary buffer. // Convenience method that also sets has_system_identifier to true. static void finish_doctype_system_id(GumboParser* parser) { GumboTokenDocType* doc_type_state = &parser->_tokenizer_state->_doc_type_state; gumbo_parser_deallocate(parser, (void*) doc_type_state->system_identifier); finish_temporary_buffer(parser, &doc_type_state->system_identifier); doc_type_state->has_system_identifier = true; } // Writes a single specified character to the output token. static void emit_char(GumboParser* parser, int c, GumboToken* output) { output->type = get_char_token_type(parser->_tokenizer_state->_is_in_cdata, c); output->v.character = c; finish_token(parser, output); } // Writes a replacement character token and records a parse error. // Always returns RETURN_ERROR, per gumbo_lex return value. static StateResult emit_replacement_char( GumboParser* parser, GumboToken* output) { // In all cases, this is because of a null byte in the input stream. tokenizer_add_parse_error(parser, GUMBO_ERR_UTF8_NULL); emit_char(parser, kUtf8ReplacementChar, output); return RETURN_ERROR; } // Writes an EOF character token. Always returns RETURN_SUCCESS. static StateResult emit_eof(GumboParser* parser, GumboToken* output) { emit_char(parser, -1, output); return RETURN_SUCCESS; } // Writes the current input character out as a character token. // Always returns RETURN_SUCCESS. static bool emit_current_char(GumboParser* parser, GumboToken* output) { emit_char( parser, utf8iterator_current(&parser->_tokenizer_state->_input), output); return RETURN_SUCCESS; } // Writes out a doctype token, copying it from the tokenizer state. static void emit_doctype(GumboParser* parser, GumboToken* output) { output->type = GUMBO_TOKEN_DOCTYPE; output->v.doc_type = parser->_tokenizer_state->_doc_type_state; finish_token(parser, output); doc_type_state_init(parser); } // Debug-only function that explicitly sets the attribute vector data to NULL so // it can be asserted on tag creation, verifying that there are no memory leaks. static void mark_tag_state_as_empty(GumboTagState* tag_state) { #ifndef NDEBUG tag_state->_attributes = kGumboEmptyVector; #endif } // Writes out the current tag as a start or end tag token. // Always returns RETURN_SUCCESS. static StateResult emit_current_tag(GumboParser* parser, GumboToken* output) { GumboTagState* tag_state = &parser->_tokenizer_state->_tag_state; if (tag_state->_is_start_tag) { output->type = GUMBO_TOKEN_START_TAG; output->v.start_tag.tag = tag_state->_tag; output->v.start_tag.attributes = tag_state->_attributes; output->v.start_tag.is_self_closing = tag_state->_is_self_closing; tag_state->_last_start_tag = tag_state->_tag; mark_tag_state_as_empty(tag_state); gumbo_debug( "Emitted start tag %s.\n", gumbo_normalized_tagname(tag_state->_tag)); } else { output->type = GUMBO_TOKEN_END_TAG; output->v.end_tag = tag_state->_tag; // In end tags, ownership of the attributes vector is not transferred to the // token, but it's still initialized as normal, so it must be manually // deallocated. There may also be attributes to destroy, in certain broken // cases like </div</th> (the "th" is an attribute there). for (unsigned int i = 0; i < tag_state->_attributes.length; ++i) { gumbo_destroy_attribute(parser, tag_state->_attributes.data[i]); } gumbo_parser_deallocate(parser, tag_state->_attributes.data); mark_tag_state_as_empty(tag_state); gumbo_debug( "Emitted end tag %s.\n", gumbo_normalized_tagname(tag_state->_tag)); } gumbo_string_buffer_destroy(parser, &tag_state->_buffer); finish_token(parser, output); gumbo_debug("Original text = %.*s.\n", output->original_text.length, output->original_text.data); assert(output->original_text.length >= 2); assert(output->original_text.data[0] == '<'); assert(output->original_text.data[output->original_text.length - 1] == '>'); return RETURN_SUCCESS; } // In some states, we speculatively start a tag, but don't know whether it'll be // emitted as tag token or as a series of character tokens until we finish it. // We need to abandon the tag we'd started & free its memory in that case to // avoid a memory leak. static void abandon_current_tag(GumboParser* parser) { GumboTagState* tag_state = &parser->_tokenizer_state->_tag_state; for (unsigned int i = 0; i < tag_state->_attributes.length; ++i) { gumbo_destroy_attribute(parser, tag_state->_attributes.data[i]); } gumbo_parser_deallocate(parser, tag_state->_attributes.data); mark_tag_state_as_empty(tag_state); gumbo_string_buffer_destroy(parser, &tag_state->_buffer); gumbo_debug("Abandoning current tag.\n"); } // Wraps the consume_char_ref function to handle its output and make the // appropriate TokenizerState modifications. Returns RETURN_ERROR if a parse // error occurred, RETURN_SUCCESS otherwise. static StateResult emit_char_ref(GumboParser* parser, int additional_allowed_char, bool is_in_attribute, GumboToken* output) { GumboTokenizerState* tokenizer = parser->_tokenizer_state; OneOrTwoCodepoints char_ref; bool status = consume_char_ref( parser, &tokenizer->_input, additional_allowed_char, false, &char_ref); if (char_ref.first != kGumboNoChar) { // consume_char_ref ends with the iterator pointing at the next character, // so we need to be sure not advance it again before reading the next token. tokenizer->_reconsume_current_input = true; emit_char(parser, char_ref.first, output); tokenizer->_buffered_emit_char = char_ref.second; } else { emit_char(parser, '&', output); } return status ? RETURN_SUCCESS : RETURN_ERROR; } // Emits a comment token. Comments use the temporary buffer to accumulate their // data, and then it's copied over and released to the 'text' field of the // GumboToken union. Always returns RETURN_SUCCESS. static StateResult emit_comment(GumboParser* parser, GumboToken* output) { output->type = GUMBO_TOKEN_COMMENT; finish_temporary_buffer(parser, &output->v.text); finish_token(parser, output); return RETURN_SUCCESS; } // Checks to see we should be flushing accumulated characters in the temporary // buffer, and fills the output token with the next output character if so. // Returns true if a character has been emitted and the tokenizer should // immediately return, false if we're at the end of the temporary buffer and // should resume normal operation. static bool maybe_emit_from_temporary_buffer( GumboParser* parser, GumboToken* output) { GumboTokenizerState* tokenizer = parser->_tokenizer_state; const char* c = tokenizer->_temporary_buffer_emit; GumboStringBuffer* buffer = &tokenizer->_temporary_buffer; if (!c || c >= buffer->data + buffer->length) { tokenizer->_temporary_buffer_emit = NULL; return false; } assert(*c == utf8iterator_current(&tokenizer->_input)); // emit_char also advances the input stream. We need to do some juggling of // the _reconsume_current_input flag to get the proper behavior when emitting // previous tokens. Basically, _reconsume_current_input should *never* be set // when emitting anything from the temporary buffer, since those characters // have already been advanced past. However, it should be preserved so that // when the *next* character is encountered again, the tokenizer knows not to // advance past it. bool saved_reconsume_state = tokenizer->_reconsume_current_input; tokenizer->_reconsume_current_input = false; emit_char(parser, *c, output); ++tokenizer->_temporary_buffer_emit; tokenizer->_reconsume_current_input = saved_reconsume_state; return true; } // Sets up the tokenizer to begin flushing the temporary buffer. // This resets the input iterator stream to the start of the last tag, sets up // _temporary_buffer_emit, and then (if the temporary buffer is non-empty) emits // the first character in it. It returns true if a character was emitted, false // otherwise. static bool emit_temporary_buffer(GumboParser* parser, GumboToken* output) { GumboTokenizerState* tokenizer = parser->_tokenizer_state; assert(tokenizer->_temporary_buffer.data); utf8iterator_reset(&tokenizer->_input); tokenizer->_temporary_buffer_emit = tokenizer->_temporary_buffer.data; return maybe_emit_from_temporary_buffer(parser, output); } // Appends a codepoint to the current tag buffer. If // reinitilize_position_on_first is set, this also initializes the tag buffer // start point; the only time you would *not* want to pass true for this // parameter is if you want the original_text to include character (like an // opening quote) that doesn't appear in the value. static void append_char_to_tag_buffer( GumboParser* parser, int codepoint, bool reinitilize_position_on_first) { GumboStringBuffer* buffer = &parser->_tokenizer_state->_tag_state._buffer; if (buffer->length == 0 && reinitilize_position_on_first) { reset_tag_buffer_start_point(parser); } gumbo_string_buffer_append_codepoint(parser, codepoint, buffer); } // (Re-)initialize the tag buffer. This also resets the original_text pointer // and _start_pos field to point to the current position. static void initialize_tag_buffer(GumboParser* parser) { GumboTokenizerState* tokenizer = parser->_tokenizer_state; GumboTagState* tag_state = &tokenizer->_tag_state; gumbo_string_buffer_init(parser, &tag_state->_buffer); reset_tag_buffer_start_point(parser); } // Initializes the tag_state to start a new tag, keeping track of the opening // positions and original text. Takes a boolean indicating whether this is a // start or end tag. static void start_new_tag(GumboParser* parser, bool is_start_tag) { GumboTokenizerState* tokenizer = parser->_tokenizer_state; GumboTagState* tag_state = &tokenizer->_tag_state; int c = utf8iterator_current(&tokenizer->_input); assert(is_alpha(c)); c = ensure_lowercase(c); assert(is_alpha(c)); initialize_tag_buffer(parser); gumbo_string_buffer_append_codepoint(parser, c, &tag_state->_buffer); assert(tag_state->_attributes.data == NULL); // Initial size chosen by statistical analysis of a corpus of 60k webpages. // 99.5% of elements have 0 attributes, 93% of the remainder have 1. These // numbers are a bit higher for more modern websites (eg. ~45% = 0, ~40% = 1 // for the HTML5 Spec), but still have basically 99% of nodes with <= 2 attrs. gumbo_vector_init(parser, 1, &tag_state->_attributes); tag_state->_drop_next_attr_value = false; tag_state->_is_start_tag = is_start_tag; tag_state->_is_self_closing = false; gumbo_debug("Starting new tag.\n"); } // Fills in the specified char* with the contents of the tag buffer. static void copy_over_tag_buffer(GumboParser* parser, const char** output) { GumboTokenizerState* tokenizer = parser->_tokenizer_state; GumboTagState* tag_state = &tokenizer->_tag_state; *output = gumbo_string_buffer_to_string(parser, &tag_state->_buffer); } // Fills in: // * The original_text GumboStringPiece with the portion of the original // buffer that corresponds to the tag buffer. // * The start_pos GumboSourcePosition with the start position of the tag // buffer. // * The end_pos GumboSourcePosition with the current source position. static void copy_over_original_tag_text(GumboParser* parser, GumboStringPiece* original_text, GumboSourcePosition* start_pos, GumboSourcePosition* end_pos) { GumboTokenizerState* tokenizer = parser->_tokenizer_state; GumboTagState* tag_state = &tokenizer->_tag_state; original_text->data = tag_state->_original_text; original_text->length = utf8iterator_get_char_pointer(&tokenizer->_input) - tag_state->_original_text; if (original_text->data[original_text->length - 1] == '\r') { // Since \r is skipped by the UTF-8 iterator, it can sometimes end up // appended to the end of original text even when it's really the first part // of the next character. If we detect this situation, shrink the length of // the original text by 1 to remove the carriage return. --original_text->length; } *start_pos = tag_state->_start_pos; utf8iterator_get_position(&tokenizer->_input, end_pos); } // Releases and then re-initializes the tag buffer. static void reinitialize_tag_buffer(GumboParser* parser) { gumbo_parser_deallocate( parser, parser->_tokenizer_state->_tag_state._buffer.data); initialize_tag_buffer(parser); } // Moves some data from the temporary buffer over the the tag-based fields in // TagState. static void finish_tag_name(GumboParser* parser) { GumboTokenizerState* tokenizer = parser->_tokenizer_state; GumboTagState* tag_state = &tokenizer->_tag_state; tag_state->_tag = gumbo_tagn_enum(tag_state->_buffer.data, tag_state->_buffer.length); reinitialize_tag_buffer(parser); } // Adds an ERR_DUPLICATE_ATTR parse error to the parser's error struct. static void add_duplicate_attr_error(GumboParser* parser, const char* attr_name, int original_index, int new_index) { GumboError* error = gumbo_add_error(parser); if (!error) { return; } GumboTagState* tag_state = &parser->_tokenizer_state->_tag_state; error->type = GUMBO_ERR_DUPLICATE_ATTR; error->position = tag_state->_start_pos; error->original_text = tag_state->_original_text; error->v.duplicate_attr.original_index = original_index; error->v.duplicate_attr.new_index = new_index; copy_over_tag_buffer(parser, &error->v.duplicate_attr.name); reinitialize_tag_buffer(parser); } // Creates a new attribute in the current tag, copying the current tag buffer to // the attribute's name. The attribute's value starts out as the empty string // (following the "Boolean attributes" section of the spec) and is only // overwritten on finish_attribute_value(). If the attribute has already been // specified, the new attribute is dropped, a parse error is added, and the // function returns false. Otherwise, this returns true. static bool finish_attribute_name(GumboParser* parser) { GumboTokenizerState* tokenizer = parser->_tokenizer_state; GumboTagState* tag_state = &tokenizer->_tag_state; // May've been set by a previous attribute without a value; reset it here. tag_state->_drop_next_attr_value = false; assert(tag_state->_attributes.data); assert(tag_state->_attributes.capacity); GumboVector* /* GumboAttribute* */ attributes = &tag_state->_attributes; for (unsigned int i = 0; i < attributes->length; ++i) { GumboAttribute* attr = attributes->data[i]; if (strlen(attr->name) == tag_state->_buffer.length && memcmp(attr->name, tag_state->_buffer.data, tag_state->_buffer.length) == 0) { // Identical attribute; bail. add_duplicate_attr_error(parser, attr->name, i, attributes->length); tag_state->_drop_next_attr_value = true; return false; } } GumboAttribute* attr = gumbo_parser_allocate(parser, sizeof(GumboAttribute)); attr->attr_namespace = GUMBO_ATTR_NAMESPACE_NONE; copy_over_tag_buffer(parser, &attr->name); copy_over_original_tag_text( parser, &attr->original_name, &attr->name_start, &attr->name_end); attr->value = gumbo_copy_stringz(parser, ""); copy_over_original_tag_text( parser, &attr->original_value, &attr->name_start, &attr->name_end); gumbo_vector_add(parser, attr, attributes); reinitialize_tag_buffer(parser); return true; } // Finishes an attribute value. This sets the value of the most recently added // attribute to the current contents of the tag buffer. static void finish_attribute_value(GumboParser* parser) { GumboTagState* tag_state = &parser->_tokenizer_state->_tag_state; if (tag_state->_drop_next_attr_value) { // Duplicate attribute name detected in an earlier state, so we have to // ignore the value. tag_state->_drop_next_attr_value = false; reinitialize_tag_buffer(parser); return; } GumboAttribute* attr = tag_state->_attributes.data[tag_state->_attributes.length - 1]; gumbo_parser_deallocate(parser, (void*) attr->value); copy_over_tag_buffer(parser, &attr->value); copy_over_original_tag_text( parser, &attr->original_value, &attr->value_start, &attr->value_end); reinitialize_tag_buffer(parser); } // Returns true if the current end tag matches the last start tag emitted. static bool is_appropriate_end_tag(GumboParser* parser) { GumboTagState* tag_state = &parser->_tokenizer_state->_tag_state; assert(!tag_state->_is_start_tag); return tag_state->_last_start_tag != GUMBO_TAG_LAST && tag_state->_last_start_tag == gumbo_tagn_enum(tag_state->_buffer.data, tag_state->_buffer.length); } void gumbo_tokenizer_state_init( GumboParser* parser, const char* text, size_t text_length) { GumboTokenizerState* tokenizer = gumbo_parser_allocate(parser, sizeof(GumboTokenizerState)); parser->_tokenizer_state = tokenizer; gumbo_tokenizer_set_state(parser, GUMBO_LEX_DATA); tokenizer->_reconsume_current_input = false; tokenizer->_is_current_node_foreign = false; tokenizer->_is_in_cdata = false; tokenizer->_tag_state._last_start_tag = GUMBO_TAG_LAST; tokenizer->_buffered_emit_char = kGumboNoChar; gumbo_string_buffer_init(parser, &tokenizer->_temporary_buffer); tokenizer->_temporary_buffer_emit = NULL; mark_tag_state_as_empty(&tokenizer->_tag_state); gumbo_string_buffer_init(parser, &tokenizer->_script_data_buffer); tokenizer->_token_start = text; utf8iterator_init(parser, text, text_length, &tokenizer->_input); utf8iterator_get_position(&tokenizer->_input, &tokenizer->_token_start_pos); doc_type_state_init(parser); } void gumbo_tokenizer_state_destroy(GumboParser* parser) { GumboTokenizerState* tokenizer = parser->_tokenizer_state; assert(tokenizer->_doc_type_state.name == NULL); assert(tokenizer->_doc_type_state.public_identifier == NULL); assert(tokenizer->_doc_type_state.system_identifier == NULL); gumbo_string_buffer_destroy(parser, &tokenizer->_temporary_buffer); gumbo_string_buffer_destroy(parser, &tokenizer->_script_data_buffer); gumbo_parser_deallocate(parser, tokenizer); } void gumbo_tokenizer_set_state(GumboParser* parser, GumboTokenizerEnum state) { parser->_tokenizer_state->_state = state; } void gumbo_tokenizer_set_is_current_node_foreign( GumboParser* parser, bool is_foreign) { if (is_foreign != parser->_tokenizer_state->_is_current_node_foreign) { gumbo_debug("Toggling is_current_node_foreign to %s.\n", is_foreign ? "true" : "false"); } parser->_tokenizer_state->_is_current_node_foreign = is_foreign; } // http://www.whatwg.org/specs/web-apps/current-work/complete5/tokenization.html#data-state static StateResult handle_data_state(GumboParser* parser, GumboTokenizerState* tokenizer, int c, GumboToken* output) { switch (c) { case '&': gumbo_tokenizer_set_state(parser, GUMBO_LEX_CHAR_REF_IN_DATA); // The char_ref machinery expects to be on the & so it can mark that // and return to it if the text isn't a char ref, so we need to // reconsume it. tokenizer->_reconsume_current_input = true; return NEXT_CHAR; case '<': gumbo_tokenizer_set_state(parser, GUMBO_LEX_TAG_OPEN); clear_temporary_buffer(parser); append_char_to_temporary_buffer(parser, '<'); return NEXT_CHAR; case '\0': tokenizer_add_parse_error(parser, GUMBO_ERR_UTF8_NULL); emit_char(parser, c, output); return RETURN_ERROR; default: return emit_current_char(parser, output); } } // http://www.whatwg.org/specs/web-apps/current-work/complete5/tokenization.html#character-reference-in-data-state static StateResult handle_char_ref_in_data_state(GumboParser* parser, GumboTokenizerState* tokenizer, int c, GumboToken* output) { gumbo_tokenizer_set_state(parser, GUMBO_LEX_DATA); return emit_char_ref(parser, ' ', false, output); } // http://www.whatwg.org/specs/web-apps/current-work/complete5/tokenization.html#rcdata-state static StateResult handle_rcdata_state(GumboParser* parser, GumboTokenizerState* tokenizer, int c, GumboToken* output) { switch (c) { case '&': gumbo_tokenizer_set_state(parser, GUMBO_LEX_CHAR_REF_IN_RCDATA); tokenizer->_reconsume_current_input = true; return NEXT_CHAR; case '<': gumbo_tokenizer_set_state(parser, GUMBO_LEX_RCDATA_LT); clear_temporary_buffer(parser); append_char_to_temporary_buffer(parser, '<'); return NEXT_CHAR; case '\0': return emit_replacement_char(parser, output); case -1: return emit_eof(parser, output); default: return emit_current_char(parser, output); } } // http://www.whatwg.org/specs/web-apps/current-work/complete5/tokenization.html#character-reference-in-rcdata-state static StateResult handle_char_ref_in_rcdata_state(GumboParser* parser, GumboTokenizerState* tokenizer, int c, GumboToken* output) { gumbo_tokenizer_set_state(parser, GUMBO_LEX_RCDATA); return emit_char_ref(parser, ' ', false, output); } // http://www.whatwg.org/specs/web-apps/current-work/complete5/tokenization.html#rawtext-state static StateResult handle_rawtext_state(GumboParser* parser, GumboTokenizerState* tokenizer, int c, GumboToken* output) { switch (c) { case '<': gumbo_tokenizer_set_state(parser, GUMBO_LEX_RAWTEXT_LT); clear_temporary_buffer(parser); append_char_to_temporary_buffer(parser, '<'); return NEXT_CHAR; case '\0': return emit_replacement_char(parser, output); case -1: return emit_eof(parser, output); default: return emit_current_char(parser, output); } } // http://www.whatwg.org/specs/web-apps/current-work/complete5/tokenization.html#script-data-state static StateResult handle_script_state(GumboParser* parser, GumboTokenizerState* tokenizer, int c, GumboToken* output) { switch (c) { case '<': gumbo_tokenizer_set_state(parser, GUMBO_LEX_SCRIPT_LT); clear_temporary_buffer(parser); append_char_to_temporary_buffer(parser, '<'); return NEXT_CHAR; case '\0': return emit_replacement_char(parser, output); case -1: return emit_eof(parser, output); default: return emit_current_char(parser, output); } } // http://www.whatwg.org/specs/web-apps/current-work/complete5/tokenization.html#plaintext-state static StateResult handle_plaintext_state(GumboParser* parser, GumboTokenizerState* tokenizer, int c, GumboToken* output) { switch (c) { case '\0': return emit_replacement_char(parser, output); case -1: return emit_eof(parser, output); default: return emit_current_char(parser, output); } } // http://www.whatwg.org/specs/web-apps/current-work/complete5/tokenization.html#tag-open-state static StateResult handle_tag_open_state(GumboParser* parser, GumboTokenizerState* tokenizer, int c, GumboToken* output) { assert(temporary_buffer_equals(parser, "<")); switch (c) { case '!': gumbo_tokenizer_set_state(parser, GUMBO_LEX_MARKUP_DECLARATION); clear_temporary_buffer(parser); return NEXT_CHAR; case '/': gumbo_tokenizer_set_state(parser, GUMBO_LEX_END_TAG_OPEN); append_char_to_temporary_buffer(parser, '/'); return NEXT_CHAR; case '?': gumbo_tokenizer_set_state(parser, GUMBO_LEX_BOGUS_COMMENT); clear_temporary_buffer(parser); append_char_to_temporary_buffer(parser, '?'); tokenizer_add_parse_error(parser, GUMBO_ERR_TAG_STARTS_WITH_QUESTION); return NEXT_CHAR; default: if (is_alpha(c)) { gumbo_tokenizer_set_state(parser, GUMBO_LEX_TAG_NAME); start_new_tag(parser, true); return NEXT_CHAR; } else { tokenizer_add_parse_error(parser, GUMBO_ERR_TAG_INVALID); gumbo_tokenizer_set_state(parser, GUMBO_LEX_DATA); emit_temporary_buffer(parser, output); return RETURN_ERROR; } } } // http://www.whatwg.org/specs/web-apps/current-work/complete5/tokenization.html#end-tag-open-state static StateResult handle_end_tag_open_state(GumboParser* parser, GumboTokenizerState* tokenizer, int c, GumboToken* output) { assert(temporary_buffer_equals(parser, "</")); switch (c) { case '>': tokenizer_add_parse_error(parser, GUMBO_ERR_CLOSE_TAG_EMPTY); gumbo_tokenizer_set_state(parser, GUMBO_LEX_DATA); return NEXT_CHAR; case -1: tokenizer_add_parse_error(parser, GUMBO_ERR_CLOSE_TAG_EOF); gumbo_tokenizer_set_state(parser, GUMBO_LEX_DATA); return emit_temporary_buffer(parser, output); default: if (is_alpha(c)) { gumbo_tokenizer_set_state(parser, GUMBO_LEX_TAG_NAME); start_new_tag(parser, false); } else { tokenizer_add_parse_error(parser, GUMBO_ERR_CLOSE_TAG_INVALID); gumbo_tokenizer_set_state(parser, GUMBO_LEX_BOGUS_COMMENT); clear_temporary_buffer(parser); append_char_to_temporary_buffer(parser, c); } return NEXT_CHAR; } } // http://www.whatwg.org/specs/web-apps/current-work/complete5/tokenization.html#tag-name-state static StateResult handle_tag_name_state(GumboParser* parser, GumboTokenizerState* tokenizer, int c, GumboToken* output) { switch (c) { case '\t': case '\n': case '\f': case ' ': finish_tag_name(parser); gumbo_tokenizer_set_state(parser, GUMBO_LEX_BEFORE_ATTR_NAME); return NEXT_CHAR; case '/': finish_tag_name(parser); gumbo_tokenizer_set_state(parser, GUMBO_LEX_SELF_CLOSING_START_TAG); return NEXT_CHAR; case '>': finish_tag_name(parser); gumbo_tokenizer_set_state(parser, GUMBO_LEX_DATA); return emit_current_tag(parser, output); case '\0': tokenizer_add_parse_error(parser, GUMBO_ERR_UTF8_NULL); append_char_to_tag_buffer(parser, kUtf8ReplacementChar, true); return NEXT_CHAR; case -1: tokenizer_add_parse_error(parser, GUMBO_ERR_TAG_EOF); abandon_current_tag(parser); gumbo_tokenizer_set_state(parser, GUMBO_LEX_DATA); return NEXT_CHAR; default: append_char_to_tag_buffer(parser, ensure_lowercase(c), true); return NEXT_CHAR; } } // http://www.whatwg.org/specs/web-apps/current-work/complete.html#rcdata-less-than-sign-state static StateResult handle_rcdata_lt_state(GumboParser* parser, GumboTokenizerState* tokenizer, int c, GumboToken* output) { assert(temporary_buffer_equals(parser, "<")); if (c == '/') { gumbo_tokenizer_set_state(parser, GUMBO_LEX_RCDATA_END_TAG_OPEN); append_char_to_temporary_buffer(parser, '/'); return NEXT_CHAR; } else { gumbo_tokenizer_set_state(parser, GUMBO_LEX_RCDATA); tokenizer->_reconsume_current_input = true; return emit_temporary_buffer(parser, output); } } // http://www.whatwg.org/specs/web-apps/current-work/complete.html#rcdata-end-tag-open-state static StateResult handle_rcdata_end_tag_open_state(GumboParser* parser, GumboTokenizerState* tokenizer, int c, GumboToken* output) { assert(temporary_buffer_equals(parser, "</")); if (is_alpha(c)) { gumbo_tokenizer_set_state(parser, GUMBO_LEX_RCDATA_END_TAG_NAME); start_new_tag(parser, false); append_char_to_temporary_buffer(parser, c); return NEXT_CHAR; } else { gumbo_tokenizer_set_state(parser, GUMBO_LEX_RCDATA); return emit_temporary_buffer(parser, output); } return true; } // http://www.whatwg.org/specs/web-apps/current-work/complete.html#rcdata-end-tag-name-state static StateResult handle_rcdata_end_tag_name_state(GumboParser* parser, GumboTokenizerState* tokenizer, int c, GumboToken* output) { assert(tokenizer->_temporary_buffer.length >= 2); if (is_alpha(c)) { append_char_to_tag_buffer(parser, ensure_lowercase(c), true); append_char_to_temporary_buffer(parser, c); return NEXT_CHAR; } else if (is_appropriate_end_tag(parser)) { switch (c) { case '\t': case '\n': case '\f': case ' ': finish_tag_name(parser); gumbo_tokenizer_set_state(parser, GUMBO_LEX_BEFORE_ATTR_NAME); return NEXT_CHAR; case '/': finish_tag_name(parser); gumbo_tokenizer_set_state(parser, GUMBO_LEX_SELF_CLOSING_START_TAG); return NEXT_CHAR; case '>': finish_tag_name(parser); gumbo_tokenizer_set_state(parser, GUMBO_LEX_DATA); return emit_current_tag(parser, output); } } gumbo_tokenizer_set_state(parser, GUMBO_LEX_RCDATA); abandon_current_tag(parser); return emit_temporary_buffer(parser, output); } // http://www.whatwg.org/specs/web-apps/current-work/complete5/tokenization.html#rawtext-less-than-sign-state static StateResult handle_rawtext_lt_state(GumboParser* parser, GumboTokenizerState* tokenizer, int c, GumboToken* output) { assert(temporary_buffer_equals(parser, "<")); if (c == '/') { gumbo_tokenizer_set_state(parser, GUMBO_LEX_RAWTEXT_END_TAG_OPEN); append_char_to_temporary_buffer(parser, '/'); return NEXT_CHAR; } else { gumbo_tokenizer_set_state(parser, GUMBO_LEX_RAWTEXT); tokenizer->_reconsume_current_input = true; return emit_temporary_buffer(parser, output); } } // http://www.whatwg.org/specs/web-apps/current-work/complete5/tokenization.html#rawtext-end-tag-open-state static StateResult handle_rawtext_end_tag_open_state(GumboParser* parser, GumboTokenizerState* tokenizer, int c, GumboToken* output) { assert(temporary_buffer_equals(parser, "</")); if (is_alpha(c)) { gumbo_tokenizer_set_state(parser, GUMBO_LEX_RAWTEXT_END_TAG_NAME); start_new_tag(parser, false); append_char_to_temporary_buffer(parser, c); return NEXT_CHAR; } else { gumbo_tokenizer_set_state(parser, GUMBO_LEX_RAWTEXT); return emit_temporary_buffer(parser, output); } } // http://www.whatwg.org/specs/web-apps/current-work/complete5/tokenization.html#rawtext-end-tag-name-state static StateResult handle_rawtext_end_tag_name_state(GumboParser* parser, GumboTokenizerState* tokenizer, int c, GumboToken* output) { assert(tokenizer->_temporary_buffer.length >= 2); gumbo_debug("Last end tag: %*s\n", (int) tokenizer->_tag_state._buffer.length, tokenizer->_tag_state._buffer.data); if (is_alpha(c)) { append_char_to_tag_buffer(parser, ensure_lowercase(c), true); append_char_to_temporary_buffer(parser, c); return NEXT_CHAR; } else if (is_appropriate_end_tag(parser)) { gumbo_debug("Is an appropriate end tag.\n"); switch (c) { case '\t': case '\n': case '\f': case ' ': finish_tag_name(parser); gumbo_tokenizer_set_state(parser, GUMBO_LEX_BEFORE_ATTR_NAME); return NEXT_CHAR; case '/': finish_tag_name(parser); gumbo_tokenizer_set_state(parser, GUMBO_LEX_SELF_CLOSING_START_TAG); return NEXT_CHAR; case '>': finish_tag_name(parser); gumbo_tokenizer_set_state(parser, GUMBO_LEX_DATA); return emit_current_tag(parser, output); } } gumbo_tokenizer_set_state(parser, GUMBO_LEX_RAWTEXT); abandon_current_tag(parser); return emit_temporary_buffer(parser, output); } // http://www.whatwg.org/specs/web-apps/current-work/complete5/tokenization.html#script-data-less-than-sign-state static StateResult handle_script_lt_state(GumboParser* parser, GumboTokenizerState* tokenizer, int c, GumboToken* output) { assert(temporary_buffer_equals(parser, "<")); if (c == '/') { gumbo_tokenizer_set_state(parser, GUMBO_LEX_SCRIPT_END_TAG_OPEN); append_char_to_temporary_buffer(parser, '/'); return NEXT_CHAR; } else if (c == '!') { gumbo_tokenizer_set_state(parser, GUMBO_LEX_SCRIPT_ESCAPED_START); append_char_to_temporary_buffer(parser, '!'); return emit_temporary_buffer(parser, output); } else { gumbo_tokenizer_set_state(parser, GUMBO_LEX_SCRIPT); tokenizer->_reconsume_current_input = true; return emit_temporary_buffer(parser, output); } } // http://www.whatwg.org/specs/web-apps/current-work/complete5/tokenization.html#script-data-end-tag-open-state static StateResult handle_script_end_tag_open_state(GumboParser* parser, GumboTokenizerState* tokenizer, int c, GumboToken* output) { assert(temporary_buffer_equals(parser, "</")); if (is_alpha(c)) { gumbo_tokenizer_set_state(parser, GUMBO_LEX_SCRIPT_END_TAG_NAME); start_new_tag(parser, false); append_char_to_temporary_buffer(parser, c); return NEXT_CHAR; } else { gumbo_tokenizer_set_state(parser, GUMBO_LEX_SCRIPT); return emit_temporary_buffer(parser, output); } } // http://www.whatwg.org/specs/web-apps/current-work/complete5/tokenization.html#script-data-end-tag-name-state static StateResult handle_script_end_tag_name_state(GumboParser* parser, GumboTokenizerState* tokenizer, int c, GumboToken* output) { assert(tokenizer->_temporary_buffer.length >= 2); if (is_alpha(c)) { append_char_to_tag_buffer(parser, ensure_lowercase(c), true); append_char_to_temporary_buffer(parser, c); return NEXT_CHAR; } else if (is_appropriate_end_tag(parser)) { switch (c) { case '\t': case '\n': case '\f': case ' ': finish_tag_name(parser); gumbo_tokenizer_set_state(parser, GUMBO_LEX_BEFORE_ATTR_NAME); return NEXT_CHAR; case '/': finish_tag_name(parser); gumbo_tokenizer_set_state(parser, GUMBO_LEX_SELF_CLOSING_START_TAG); return NEXT_CHAR; case '>': finish_tag_name(parser); gumbo_tokenizer_set_state(parser, GUMBO_LEX_DATA); return emit_current_tag(parser, output); } } gumbo_tokenizer_set_state(parser, GUMBO_LEX_SCRIPT); abandon_current_tag(parser); return emit_temporary_buffer(parser, output); } // http://www.whatwg.org/specs/web-apps/current-work/complete5/tokenization.html#script-data-escape-start-state static StateResult handle_script_escaped_start_state(GumboParser* parser, GumboTokenizerState* tokenizer, int c, GumboToken* output) { if (c == '-') { gumbo_tokenizer_set_state(parser, GUMBO_LEX_SCRIPT_ESCAPED_START_DASH); return emit_current_char(parser, output); } else { gumbo_tokenizer_set_state(parser, GUMBO_LEX_SCRIPT); tokenizer->_reconsume_current_input = true; return NEXT_CHAR; } } // http://www.whatwg.org/specs/web-apps/current-work/complete5/tokenization.html#script-data-escape-start-dash-state static StateResult handle_script_escaped_start_dash_state(GumboParser* parser, GumboTokenizerState* tokenizer, int c, GumboToken* output) { if (c == '-') { gumbo_tokenizer_set_state(parser, GUMBO_LEX_SCRIPT_ESCAPED_DASH_DASH); return emit_current_char(parser, output); } else { gumbo_tokenizer_set_state(parser, GUMBO_LEX_SCRIPT); tokenizer->_reconsume_current_input = true; return NEXT_CHAR; } } // http://www.whatwg.org/specs/web-apps/current-work/complete5/tokenization.html#script-data-escaped-state static StateResult handle_script_escaped_state(GumboParser* parser, GumboTokenizerState* tokenizer, int c, GumboToken* output) { switch (c) { case '-': gumbo_tokenizer_set_state(parser, GUMBO_LEX_SCRIPT_ESCAPED_DASH); return emit_current_char(parser, output); case '<': gumbo_tokenizer_set_state(parser, GUMBO_LEX_SCRIPT_ESCAPED_LT); clear_temporary_buffer(parser); append_char_to_temporary_buffer(parser, c); return NEXT_CHAR; case '\0': return emit_replacement_char(parser, output); case -1: tokenizer_add_parse_error(parser, GUMBO_ERR_SCRIPT_EOF); return emit_eof(parser, output); default: return emit_current_char(parser, output); } } // http://www.whatwg.org/specs/web-apps/current-work/complete5/tokenization.html#script-data-escaped-dash-state static StateResult handle_script_escaped_dash_state(GumboParser* parser, GumboTokenizerState* tokenizer, int c, GumboToken* output) { switch (c) { case '-': gumbo_tokenizer_set_state(parser, GUMBO_LEX_SCRIPT_ESCAPED_DASH_DASH); return emit_current_char(parser, output); case '<': gumbo_tokenizer_set_state(parser, GUMBO_LEX_SCRIPT_ESCAPED_LT); clear_temporary_buffer(parser); append_char_to_temporary_buffer(parser, c); return NEXT_CHAR; case '\0': gumbo_tokenizer_set_state(parser, GUMBO_LEX_SCRIPT_ESCAPED); return emit_replacement_char(parser, output); case -1: tokenizer_add_parse_error(parser, GUMBO_ERR_SCRIPT_EOF); gumbo_tokenizer_set_state(parser, GUMBO_LEX_DATA); return NEXT_CHAR; default: gumbo_tokenizer_set_state(parser, GUMBO_LEX_SCRIPT_ESCAPED); return emit_current_char(parser, output); } } // http://www.whatwg.org/specs/web-apps/current-work/complete5/tokenization.html#script-data-escaped-dash-dash-state static StateResult handle_script_escaped_dash_dash_state(GumboParser* parser, GumboTokenizerState* tokenizer, int c, GumboToken* output) { switch (c) { case '-': return emit_current_char(parser, output); case '<': gumbo_tokenizer_set_state(parser, GUMBO_LEX_SCRIPT_ESCAPED_LT); clear_temporary_buffer(parser); append_char_to_temporary_buffer(parser, c); return NEXT_CHAR; case '>': gumbo_tokenizer_set_state(parser, GUMBO_LEX_SCRIPT); return emit_current_char(parser, output); case '\0': gumbo_tokenizer_set_state(parser, GUMBO_LEX_SCRIPT_ESCAPED); return emit_replacement_char(parser, output); case -1: tokenizer_add_parse_error(parser, GUMBO_ERR_SCRIPT_EOF); gumbo_tokenizer_set_state(parser, GUMBO_LEX_DATA); return NEXT_CHAR; default: gumbo_tokenizer_set_state(parser, GUMBO_LEX_SCRIPT_ESCAPED); return emit_current_char(parser, output); } } // http://www.whatwg.org/specs/web-apps/current-work/complete5/tokenization.html#script-data-escaped-less-than-sign-state static StateResult handle_script_escaped_lt_state(GumboParser* parser, GumboTokenizerState* tokenizer, int c, GumboToken* output) { assert(temporary_buffer_equals(parser, "<")); assert(!tokenizer->_script_data_buffer.length); if (c == '/') { gumbo_tokenizer_set_state(parser, GUMBO_LEX_SCRIPT_ESCAPED_END_TAG_OPEN); append_char_to_temporary_buffer(parser, c); return NEXT_CHAR; } else if (is_alpha(c)) { gumbo_tokenizer_set_state(parser, GUMBO_LEX_SCRIPT_DOUBLE_ESCAPED_START); append_char_to_temporary_buffer(parser, c); gumbo_string_buffer_append_codepoint( parser, ensure_lowercase(c), &tokenizer->_script_data_buffer); return emit_temporary_buffer(parser, output); } else { gumbo_tokenizer_set_state(parser, GUMBO_LEX_SCRIPT_ESCAPED); return emit_temporary_buffer(parser, output); } } // http://www.whatwg.org/specs/web-apps/current-work/complete5/tokenization.html#script-data-escaped-end-tag-open-state static StateResult handle_script_escaped_end_tag_open_state(GumboParser* parser, GumboTokenizerState* tokenizer, int c, GumboToken* output) { assert(temporary_buffer_equals(parser, "</")); if (is_alpha(c)) { gumbo_tokenizer_set_state(parser, GUMBO_LEX_SCRIPT_ESCAPED_END_TAG_NAME); start_new_tag(parser, false); append_char_to_temporary_buffer(parser, c); return NEXT_CHAR; } else { gumbo_tokenizer_set_state(parser, GUMBO_LEX_SCRIPT_ESCAPED); return emit_temporary_buffer(parser, output); } } // http://www.whatwg.org/specs/web-apps/current-work/complete5/tokenization.html#script-data-escaped-end-tag-name-state static StateResult handle_script_escaped_end_tag_name_state(GumboParser* parser, GumboTokenizerState* tokenizer, int c, GumboToken* output) { assert(tokenizer->_temporary_buffer.length >= 2); if (is_alpha(c)) { append_char_to_tag_buffer(parser, ensure_lowercase(c), true); append_char_to_temporary_buffer(parser, c); return NEXT_CHAR; } else if (is_appropriate_end_tag(parser)) { switch (c) { case '\t': case '\n': case '\f': case ' ': finish_tag_name(parser); gumbo_tokenizer_set_state(parser, GUMBO_LEX_BEFORE_ATTR_NAME); return NEXT_CHAR; case '/': finish_tag_name(parser); gumbo_tokenizer_set_state(parser, GUMBO_LEX_SELF_CLOSING_START_TAG); return NEXT_CHAR; case '>': finish_tag_name(parser); gumbo_tokenizer_set_state(parser, GUMBO_LEX_DATA); return emit_current_tag(parser, output); } } gumbo_tokenizer_set_state(parser, GUMBO_LEX_SCRIPT_ESCAPED); abandon_current_tag(parser); return emit_temporary_buffer(parser, output); } // http://www.whatwg.org/specs/web-apps/current-work/complete5/tokenization.html#script-data-double-escape-start-state static StateResult handle_script_double_escaped_start_state(GumboParser* parser, GumboTokenizerState* tokenizer, int c, GumboToken* output) { switch (c) { case '\t': case '\n': case '\f': case ' ': case '/': case '>': gumbo_tokenizer_set_state( parser, gumbo_string_equals(&kScriptTag, (GumboStringPiece*) &tokenizer->_script_data_buffer) ? GUMBO_LEX_SCRIPT_DOUBLE_ESCAPED : GUMBO_LEX_SCRIPT_ESCAPED); return emit_current_char(parser, output); default: if (is_alpha(c)) { gumbo_string_buffer_append_codepoint( parser, ensure_lowercase(c), &tokenizer->_script_data_buffer); return emit_current_char(parser, output); } else { gumbo_tokenizer_set_state(parser, GUMBO_LEX_SCRIPT_ESCAPED); tokenizer->_reconsume_current_input = true; return NEXT_CHAR; } } } // http://www.whatwg.org/specs/web-apps/current-work/complete5/tokenization.html#script-data-double-escaped-state static StateResult handle_script_double_escaped_state(GumboParser* parser, GumboTokenizerState* tokenizer, int c, GumboToken* output) { switch (c) { case '-': gumbo_tokenizer_set_state(parser, GUMBO_LEX_SCRIPT_DOUBLE_ESCAPED_DASH); return emit_current_char(parser, output); case '<': gumbo_tokenizer_set_state(parser, GUMBO_LEX_SCRIPT_DOUBLE_ESCAPED_LT); return emit_current_char(parser, output); case '\0': return emit_replacement_char(parser, output); case -1: tokenizer_add_parse_error(parser, GUMBO_ERR_SCRIPT_EOF); gumbo_tokenizer_set_state(parser, GUMBO_LEX_DATA); return NEXT_CHAR; default: return emit_current_char(parser, output); } } // http://www.whatwg.org/specs/web-apps/current-work/complete5/tokenization.html#script-data-double-escaped-dash-state static StateResult handle_script_double_escaped_dash_state(GumboParser* parser, GumboTokenizerState* tokenizer, int c, GumboToken* output) { switch (c) { case '-': gumbo_tokenizer_set_state( parser, GUMBO_LEX_SCRIPT_DOUBLE_ESCAPED_DASH_DASH); return emit_current_char(parser, output); case '<': gumbo_tokenizer_set_state(parser, GUMBO_LEX_SCRIPT_DOUBLE_ESCAPED_LT); return emit_current_char(parser, output); case '\0': gumbo_tokenizer_set_state(parser, GUMBO_LEX_SCRIPT_DOUBLE_ESCAPED); return emit_replacement_char(parser, output); case -1: tokenizer_add_parse_error(parser, GUMBO_ERR_SCRIPT_EOF); gumbo_tokenizer_set_state(parser, GUMBO_LEX_DATA); return NEXT_CHAR; default: gumbo_tokenizer_set_state(parser, GUMBO_LEX_SCRIPT_DOUBLE_ESCAPED); return emit_current_char(parser, output); } } // http://www.whatwg.org/specs/web-apps/current-work/complete5/tokenization.html#script-data-double-escaped-dash-dash-state static StateResult handle_script_double_escaped_dash_dash_state( GumboParser* parser, GumboTokenizerState* tokenizer, int c, GumboToken* output) { switch (c) { case '-': return emit_current_char(parser, output); case '<': gumbo_tokenizer_set_state(parser, GUMBO_LEX_SCRIPT_DOUBLE_ESCAPED_LT); return emit_current_char(parser, output); case '>': gumbo_tokenizer_set_state(parser, GUMBO_LEX_SCRIPT); return emit_current_char(parser, output); case '\0': gumbo_tokenizer_set_state(parser, GUMBO_LEX_SCRIPT_DOUBLE_ESCAPED); return emit_replacement_char(parser, output); case -1: tokenizer_add_parse_error(parser, GUMBO_ERR_SCRIPT_EOF); gumbo_tokenizer_set_state(parser, GUMBO_LEX_DATA); return NEXT_CHAR; default: gumbo_tokenizer_set_state(parser, GUMBO_LEX_SCRIPT_DOUBLE_ESCAPED); return emit_current_char(parser, output); } } // http://www.whatwg.org/specs/web-apps/current-work/complete5/tokenization.html#script-data-double-escaped-less-than-sign-state static StateResult handle_script_double_escaped_lt_state(GumboParser* parser, GumboTokenizerState* tokenizer, int c, GumboToken* output) { if (c == '/') { gumbo_tokenizer_set_state(parser, GUMBO_LEX_SCRIPT_DOUBLE_ESCAPED_END); gumbo_string_buffer_clear(parser, &tokenizer->_script_data_buffer); return emit_current_char(parser, output); } else { gumbo_tokenizer_set_state(parser, GUMBO_LEX_SCRIPT_DOUBLE_ESCAPED); tokenizer->_reconsume_current_input = true; return NEXT_CHAR; } } // http://www.whatwg.org/specs/web-apps/current-work/complete5/tokenization.html#script-data-double-escape-end-state static StateResult handle_script_double_escaped_end_state(GumboParser* parser, GumboTokenizerState* tokenizer, int c, GumboToken* output) { switch (c) { case '\t': case '\n': case '\f': case ' ': case '/': case '>': gumbo_tokenizer_set_state( parser, gumbo_string_equals(&kScriptTag, (GumboStringPiece*) &tokenizer->_script_data_buffer) ? GUMBO_LEX_SCRIPT_ESCAPED : GUMBO_LEX_SCRIPT_DOUBLE_ESCAPED); return emit_current_char(parser, output); default: if (is_alpha(c)) { gumbo_string_buffer_append_codepoint( parser, ensure_lowercase(c), &tokenizer->_script_data_buffer); return emit_current_char(parser, output); } else { gumbo_tokenizer_set_state(parser, GUMBO_LEX_SCRIPT_DOUBLE_ESCAPED); tokenizer->_reconsume_current_input = true; return NEXT_CHAR; } } } // http://www.whatwg.org/specs/web-apps/current-work/complete5/tokenization.html#before-attribute-name-state static StateResult handle_before_attr_name_state(GumboParser* parser, GumboTokenizerState* tokenizer, int c, GumboToken* output) { switch (c) { case '\t': case '\n': case '\f': case ' ': return NEXT_CHAR; case '/': gumbo_tokenizer_set_state(parser, GUMBO_LEX_SELF_CLOSING_START_TAG); return NEXT_CHAR; case '>': gumbo_tokenizer_set_state(parser, GUMBO_LEX_DATA); return emit_current_tag(parser, output); case '\0': tokenizer_add_parse_error(parser, GUMBO_ERR_UTF8_NULL); gumbo_tokenizer_set_state(parser, GUMBO_LEX_ATTR_NAME); append_char_to_temporary_buffer(parser, 0xfffd); return NEXT_CHAR; case -1: tokenizer_add_parse_error(parser, GUMBO_ERR_ATTR_NAME_EOF); gumbo_tokenizer_set_state(parser, GUMBO_LEX_DATA); abandon_current_tag(parser); return NEXT_CHAR; case '"': case '\'': case '<': case '=': tokenizer_add_parse_error(parser, GUMBO_ERR_ATTR_NAME_INVALID); // Fall through. default: gumbo_tokenizer_set_state(parser, GUMBO_LEX_ATTR_NAME); append_char_to_tag_buffer(parser, ensure_lowercase(c), true); return NEXT_CHAR; } } // http://www.whatwg.org/specs/web-apps/current-work/complete5/tokenization.html#attribute-name-state static StateResult handle_attr_name_state(GumboParser* parser, GumboTokenizerState* tokenizer, int c, GumboToken* output) { switch (c) { case '\t': case '\n': case '\f': case ' ': finish_attribute_name(parser); gumbo_tokenizer_set_state(parser, GUMBO_LEX_AFTER_ATTR_NAME); return NEXT_CHAR; case '/': finish_attribute_name(parser); gumbo_tokenizer_set_state(parser, GUMBO_LEX_SELF_CLOSING_START_TAG); return NEXT_CHAR; case '=': finish_attribute_name(parser); gumbo_tokenizer_set_state(parser, GUMBO_LEX_BEFORE_ATTR_VALUE); return NEXT_CHAR; case '>': finish_attribute_name(parser); gumbo_tokenizer_set_state(parser, GUMBO_LEX_DATA); return emit_current_tag(parser, output); case '\0': tokenizer_add_parse_error(parser, GUMBO_ERR_UTF8_NULL); append_char_to_tag_buffer(parser, kUtf8ReplacementChar, true); return NEXT_CHAR; case -1: gumbo_tokenizer_set_state(parser, GUMBO_LEX_DATA); abandon_current_tag(parser); tokenizer_add_parse_error(parser, GUMBO_ERR_ATTR_NAME_EOF); return NEXT_CHAR; case '"': case '\'': case '<': tokenizer_add_parse_error(parser, GUMBO_ERR_ATTR_NAME_INVALID); // Fall through. default: append_char_to_tag_buffer(parser, ensure_lowercase(c), true); return NEXT_CHAR; } } // http://www.whatwg.org/specs/web-apps/current-work/complete5/tokenization.html#after-attribute-name-state static StateResult handle_after_attr_name_state(GumboParser* parser, GumboTokenizerState* tokenizer, int c, GumboToken* output) { switch (c) { case '\t': case '\n': case '\f': case ' ': return NEXT_CHAR; case '/': gumbo_tokenizer_set_state(parser, GUMBO_LEX_SELF_CLOSING_START_TAG); return NEXT_CHAR; case '=': gumbo_tokenizer_set_state(parser, GUMBO_LEX_BEFORE_ATTR_VALUE); return NEXT_CHAR; case '>': gumbo_tokenizer_set_state(parser, GUMBO_LEX_DATA); return emit_current_tag(parser, output); case '\0': tokenizer_add_parse_error(parser, GUMBO_ERR_UTF8_NULL); gumbo_tokenizer_set_state(parser, GUMBO_LEX_ATTR_NAME); append_char_to_temporary_buffer(parser, 0xfffd); return NEXT_CHAR; case -1: tokenizer_add_parse_error(parser, GUMBO_ERR_ATTR_NAME_EOF); gumbo_tokenizer_set_state(parser, GUMBO_LEX_DATA); abandon_current_tag(parser); return NEXT_CHAR; case '"': case '\'': case '<': tokenizer_add_parse_error(parser, GUMBO_ERR_ATTR_NAME_INVALID); // Fall through. default: gumbo_tokenizer_set_state(parser, GUMBO_LEX_ATTR_NAME); append_char_to_tag_buffer(parser, ensure_lowercase(c), true); return NEXT_CHAR; } } // http://www.whatwg.org/specs/web-apps/current-work/complete5/tokenization.html#before-attribute-value-state static StateResult handle_before_attr_value_state(GumboParser* parser, GumboTokenizerState* tokenizer, int c, GumboToken* output) { switch (c) { case '\t': case '\n': case '\f': case ' ': return NEXT_CHAR; case '"': gumbo_tokenizer_set_state(parser, GUMBO_LEX_ATTR_VALUE_DOUBLE_QUOTED); reset_tag_buffer_start_point(parser); return NEXT_CHAR; case '&': gumbo_tokenizer_set_state(parser, GUMBO_LEX_ATTR_VALUE_UNQUOTED); tokenizer->_reconsume_current_input = true; return NEXT_CHAR; case '\'': gumbo_tokenizer_set_state(parser, GUMBO_LEX_ATTR_VALUE_SINGLE_QUOTED); reset_tag_buffer_start_point(parser); return NEXT_CHAR; case '\0': tokenizer_add_parse_error(parser, GUMBO_ERR_UTF8_NULL); gumbo_tokenizer_set_state(parser, GUMBO_LEX_ATTR_VALUE_UNQUOTED); append_char_to_tag_buffer(parser, kUtf8ReplacementChar, true); return NEXT_CHAR; case -1: tokenizer_add_parse_error(parser, GUMBO_ERR_ATTR_UNQUOTED_EOF); gumbo_tokenizer_set_state(parser, GUMBO_LEX_DATA); abandon_current_tag(parser); tokenizer->_reconsume_current_input = true; return NEXT_CHAR; case '>': tokenizer_add_parse_error(parser, GUMBO_ERR_ATTR_UNQUOTED_RIGHT_BRACKET); gumbo_tokenizer_set_state(parser, GUMBO_LEX_DATA); emit_current_tag(parser, output); return RETURN_ERROR; case '<': case '=': case '`': tokenizer_add_parse_error(parser, GUMBO_ERR_ATTR_UNQUOTED_EQUALS); // Fall through. default: gumbo_tokenizer_set_state(parser, GUMBO_LEX_ATTR_VALUE_UNQUOTED); append_char_to_tag_buffer(parser, c, true); return NEXT_CHAR; } } // http://www.whatwg.org/specs/web-apps/current-work/complete5/tokenization.html#attribute-value-double-quoted-state static StateResult handle_attr_value_double_quoted_state(GumboParser* parser, GumboTokenizerState* tokenizer, int c, GumboToken* output) { switch (c) { case '"': gumbo_tokenizer_set_state(parser, GUMBO_LEX_AFTER_ATTR_VALUE_QUOTED); return NEXT_CHAR; case '&': tokenizer->_tag_state._attr_value_state = tokenizer->_state; gumbo_tokenizer_set_state(parser, GUMBO_LEX_CHAR_REF_IN_ATTR_VALUE); tokenizer->_reconsume_current_input = true; return NEXT_CHAR; case '\0': tokenizer_add_parse_error(parser, GUMBO_ERR_UTF8_NULL); append_char_to_tag_buffer(parser, kUtf8ReplacementChar, false); return NEXT_CHAR; case -1: tokenizer_add_parse_error(parser, GUMBO_ERR_ATTR_DOUBLE_QUOTE_EOF); gumbo_tokenizer_set_state(parser, GUMBO_LEX_DATA); abandon_current_tag(parser); tokenizer->_reconsume_current_input = true; return NEXT_CHAR; default: append_char_to_tag_buffer(parser, c, false); return NEXT_CHAR; } } // http://www.whatwg.org/specs/web-apps/current-work/complete5/tokenization.html#attribute-value-single-quoted-state static StateResult handle_attr_value_single_quoted_state(GumboParser* parser, GumboTokenizerState* tokenizer, int c, GumboToken* output) { switch (c) { case '\'': gumbo_tokenizer_set_state(parser, GUMBO_LEX_AFTER_ATTR_VALUE_QUOTED); return NEXT_CHAR; case '&': tokenizer->_tag_state._attr_value_state = tokenizer->_state; gumbo_tokenizer_set_state(parser, GUMBO_LEX_CHAR_REF_IN_ATTR_VALUE); tokenizer->_reconsume_current_input = true; return NEXT_CHAR; case '\0': tokenizer_add_parse_error(parser, GUMBO_ERR_UTF8_NULL); append_char_to_tag_buffer(parser, kUtf8ReplacementChar, false); return NEXT_CHAR; case -1: tokenizer_add_parse_error(parser, GUMBO_ERR_ATTR_SINGLE_QUOTE_EOF); gumbo_tokenizer_set_state(parser, GUMBO_LEX_DATA); abandon_current_tag(parser); tokenizer->_reconsume_current_input = true; return NEXT_CHAR; default: append_char_to_tag_buffer(parser, c, false); return NEXT_CHAR; } } // http://www.whatwg.org/specs/web-apps/current-work/complete5/tokenization.html#attribute-value-unquoted-state static StateResult handle_attr_value_unquoted_state(GumboParser* parser, GumboTokenizerState* tokenizer, int c, GumboToken* output) { switch (c) { case '\t': case '\n': case '\f': case ' ': gumbo_tokenizer_set_state(parser, GUMBO_LEX_BEFORE_ATTR_NAME); finish_attribute_value(parser); return NEXT_CHAR; case '&': tokenizer->_tag_state._attr_value_state = tokenizer->_state; gumbo_tokenizer_set_state(parser, GUMBO_LEX_CHAR_REF_IN_ATTR_VALUE); tokenizer->_reconsume_current_input = true; return NEXT_CHAR; case '>': gumbo_tokenizer_set_state(parser, GUMBO_LEX_DATA); finish_attribute_value(parser); return emit_current_tag(parser, output); case '\0': tokenizer_add_parse_error(parser, GUMBO_ERR_UTF8_NULL); append_char_to_tag_buffer(parser, kUtf8ReplacementChar, true); return NEXT_CHAR; case -1: tokenizer_add_parse_error(parser, GUMBO_ERR_ATTR_UNQUOTED_EOF); gumbo_tokenizer_set_state(parser, GUMBO_LEX_DATA); tokenizer->_reconsume_current_input = true; abandon_current_tag(parser); return NEXT_CHAR; case '<': case '=': case '"': case '\'': case '`': tokenizer_add_parse_error(parser, GUMBO_ERR_ATTR_UNQUOTED_EQUALS); // Fall through. default: append_char_to_tag_buffer(parser, c, true); return NEXT_CHAR; } } // http://www.whatwg.org/specs/web-apps/current-work/complete5/tokenization.html#character-reference-in-attribute-value-state static StateResult handle_char_ref_in_attr_value_state(GumboParser* parser, GumboTokenizerState* tokenizer, int c, GumboToken* output) { OneOrTwoCodepoints char_ref; int allowed_char; bool is_unquoted = false; switch (tokenizer->_tag_state._attr_value_state) { case GUMBO_LEX_ATTR_VALUE_DOUBLE_QUOTED: allowed_char = '"'; break; case GUMBO_LEX_ATTR_VALUE_SINGLE_QUOTED: allowed_char = '\''; break; case GUMBO_LEX_ATTR_VALUE_UNQUOTED: allowed_char = '>'; is_unquoted = true; break; default: // -Wmaybe-uninitialized is a little overzealous here, and doesn't // get that the assert(0) means this codepath will never happen. allowed_char = ' '; assert(0); } // Ignore the status, since we don't have a convenient way of signalling that // a parser error has occurred when the error occurs in the middle of a // multi-state token. We'd need a flag inside the TokenizerState to do this, // but that's a low priority fix. consume_char_ref(parser, &tokenizer->_input, allowed_char, true, &char_ref); if (char_ref.first != kGumboNoChar) { tokenizer->_reconsume_current_input = true; append_char_to_tag_buffer(parser, char_ref.first, is_unquoted); if (char_ref.second != kGumboNoChar) { append_char_to_tag_buffer(parser, char_ref.second, is_unquoted); } } else { append_char_to_tag_buffer(parser, '&', is_unquoted); } gumbo_tokenizer_set_state(parser, tokenizer->_tag_state._attr_value_state); return NEXT_CHAR; } // http://www.whatwg.org/specs/web-apps/current-work/complete5/tokenization.html#after-attribute-value-quoted-state static StateResult handle_after_attr_value_quoted_state(GumboParser* parser, GumboTokenizerState* tokenizer, int c, GumboToken* output) { finish_attribute_value(parser); switch (c) { case '\t': case '\n': case '\f': case ' ': gumbo_tokenizer_set_state(parser, GUMBO_LEX_BEFORE_ATTR_NAME); return NEXT_CHAR; case '/': gumbo_tokenizer_set_state(parser, GUMBO_LEX_SELF_CLOSING_START_TAG); return NEXT_CHAR; case '>': gumbo_tokenizer_set_state(parser, GUMBO_LEX_DATA); return emit_current_tag(parser, output); case -1: tokenizer_add_parse_error(parser, GUMBO_ERR_ATTR_AFTER_EOF); gumbo_tokenizer_set_state(parser, GUMBO_LEX_DATA); abandon_current_tag(parser); tokenizer->_reconsume_current_input = true; return NEXT_CHAR; default: tokenizer_add_parse_error(parser, GUMBO_ERR_ATTR_AFTER_INVALID); gumbo_tokenizer_set_state(parser, GUMBO_LEX_BEFORE_ATTR_NAME); tokenizer->_reconsume_current_input = true; return NEXT_CHAR; } } // http://www.whatwg.org/specs/web-apps/current-work/complete.html#self-closing-start-tag-state static StateResult handle_self_closing_start_tag_state(GumboParser* parser, GumboTokenizerState* tokenizer, int c, GumboToken* output) { switch (c) { case '>': gumbo_tokenizer_set_state(parser, GUMBO_LEX_DATA); tokenizer->_tag_state._is_self_closing = true; return emit_current_tag(parser, output); case -1: tokenizer_add_parse_error(parser, GUMBO_ERR_SOLIDUS_EOF); gumbo_tokenizer_set_state(parser, GUMBO_LEX_DATA); abandon_current_tag(parser); return NEXT_CHAR; default: tokenizer_add_parse_error(parser, GUMBO_ERR_SOLIDUS_INVALID); gumbo_tokenizer_set_state(parser, GUMBO_LEX_BEFORE_ATTR_NAME); tokenizer->_reconsume_current_input = true; return NEXT_CHAR; } } // http://www.whatwg.org/specs/web-apps/current-work/complete.html#bogus-comment-state static StateResult handle_bogus_comment_state(GumboParser* parser, GumboTokenizerState* tokenizer, int c, GumboToken* output) { while (c != '>' && c != -1) { if (c == '\0') { c = 0xFFFD; } append_char_to_temporary_buffer(parser, c); utf8iterator_next(&tokenizer->_input); c = utf8iterator_current(&tokenizer->_input); } gumbo_tokenizer_set_state(parser, GUMBO_LEX_DATA); return emit_comment(parser, output); } // http://www.whatwg.org/specs/web-apps/current-work/complete.html#markup-declaration-open-state static StateResult handle_markup_declaration_state(GumboParser* parser, GumboTokenizerState* tokenizer, int c, GumboToken* output) { if (utf8iterator_maybe_consume_match( &tokenizer->_input, "--", sizeof("--") - 1, true)) { gumbo_tokenizer_set_state(parser, GUMBO_LEX_COMMENT_START); tokenizer->_reconsume_current_input = true; } else if (utf8iterator_maybe_consume_match( &tokenizer->_input, "DOCTYPE", sizeof("DOCTYPE") - 1, false)) { gumbo_tokenizer_set_state(parser, GUMBO_LEX_DOCTYPE); tokenizer->_reconsume_current_input = true; // If we get here, we know we'll eventually emit a doctype token, so now is // the time to initialize the doctype strings. (Not in doctype_state_init, // since then they'll leak if ownership never gets transferred to the // doctype token. tokenizer->_doc_type_state.name = gumbo_copy_stringz(parser, ""); tokenizer->_doc_type_state.public_identifier = gumbo_copy_stringz(parser, ""); tokenizer->_doc_type_state.system_identifier = gumbo_copy_stringz(parser, ""); } else if (tokenizer->_is_current_node_foreign && utf8iterator_maybe_consume_match( &tokenizer->_input, "[CDATA[", sizeof("[CDATA[") - 1, true)) { gumbo_tokenizer_set_state(parser, GUMBO_LEX_CDATA); tokenizer->_is_in_cdata = true; tokenizer->_reconsume_current_input = true; } else { tokenizer_add_parse_error(parser, GUMBO_ERR_DASHES_OR_DOCTYPE); gumbo_tokenizer_set_state(parser, GUMBO_LEX_BOGUS_COMMENT); tokenizer->_reconsume_current_input = true; clear_temporary_buffer(parser); } return NEXT_CHAR; } // http://www.whatwg.org/specs/web-apps/current-work/complete.html#comment-start-state static StateResult handle_comment_start_state(GumboParser* parser, GumboTokenizerState* tokenizer, int c, GumboToken* output) { switch (c) { case '-': gumbo_tokenizer_set_state(parser, GUMBO_LEX_COMMENT_START_DASH); return NEXT_CHAR; case '\0': tokenizer_add_parse_error(parser, GUMBO_ERR_UTF8_NULL); gumbo_tokenizer_set_state(parser, GUMBO_LEX_COMMENT); append_char_to_temporary_buffer(parser, kUtf8ReplacementChar); return NEXT_CHAR; case '>': tokenizer_add_parse_error(parser, GUMBO_ERR_COMMENT_INVALID); gumbo_tokenizer_set_state(parser, GUMBO_LEX_DATA); emit_comment(parser, output); return RETURN_ERROR; case -1: tokenizer_add_parse_error(parser, GUMBO_ERR_COMMENT_EOF); gumbo_tokenizer_set_state(parser, GUMBO_LEX_DATA); emit_comment(parser, output); return RETURN_ERROR; default: gumbo_tokenizer_set_state(parser, GUMBO_LEX_COMMENT); append_char_to_temporary_buffer(parser, c); return NEXT_CHAR; } } // http://www.whatwg.org/specs/web-apps/current-work/complete.html#comment-start-dash-state static StateResult handle_comment_start_dash_state(GumboParser* parser, GumboTokenizerState* tokenizer, int c, GumboToken* output) { switch (c) { case '-': gumbo_tokenizer_set_state(parser, GUMBO_LEX_COMMENT_END); return NEXT_CHAR; case '\0': tokenizer_add_parse_error(parser, GUMBO_ERR_UTF8_NULL); gumbo_tokenizer_set_state(parser, GUMBO_LEX_COMMENT); append_char_to_temporary_buffer(parser, '-'); append_char_to_temporary_buffer(parser, kUtf8ReplacementChar); return NEXT_CHAR; case '>': tokenizer_add_parse_error(parser, GUMBO_ERR_COMMENT_INVALID); gumbo_tokenizer_set_state(parser, GUMBO_LEX_DATA); emit_comment(parser, output); return RETURN_ERROR; case -1: tokenizer_add_parse_error(parser, GUMBO_ERR_COMMENT_EOF); gumbo_tokenizer_set_state(parser, GUMBO_LEX_DATA); emit_comment(parser, output); return RETURN_ERROR; default: gumbo_tokenizer_set_state(parser, GUMBO_LEX_COMMENT); append_char_to_temporary_buffer(parser, '-'); append_char_to_temporary_buffer(parser, c); return NEXT_CHAR; } } // http://www.whatwg.org/specs/web-apps/current-work/complete.html#comment-state static StateResult handle_comment_state(GumboParser* parser, GumboTokenizerState* tokenizer, int c, GumboToken* output) { switch (c) { case '-': gumbo_tokenizer_set_state(parser, GUMBO_LEX_COMMENT_END_DASH); return NEXT_CHAR; case '\0': tokenizer_add_parse_error(parser, GUMBO_ERR_UTF8_NULL); append_char_to_temporary_buffer(parser, kUtf8ReplacementChar); return NEXT_CHAR; case -1: tokenizer_add_parse_error(parser, GUMBO_ERR_COMMENT_EOF); gumbo_tokenizer_set_state(parser, GUMBO_LEX_DATA); emit_comment(parser, output); return RETURN_ERROR; default: append_char_to_temporary_buffer(parser, c); return NEXT_CHAR; } } // http://www.whatwg.org/specs/web-apps/current-work/complete.html#comment-end-dash-state static StateResult handle_comment_end_dash_state(GumboParser* parser, GumboTokenizerState* tokenizer, int c, GumboToken* output) { switch (c) { case '-': gumbo_tokenizer_set_state(parser, GUMBO_LEX_COMMENT_END); return NEXT_CHAR; case '\0': tokenizer_add_parse_error(parser, GUMBO_ERR_UTF8_NULL); gumbo_tokenizer_set_state(parser, GUMBO_LEX_COMMENT); append_char_to_temporary_buffer(parser, '-'); append_char_to_temporary_buffer(parser, kUtf8ReplacementChar); return NEXT_CHAR; case -1: tokenizer_add_parse_error(parser, GUMBO_ERR_COMMENT_EOF); gumbo_tokenizer_set_state(parser, GUMBO_LEX_DATA); emit_comment(parser, output); return RETURN_ERROR; default: gumbo_tokenizer_set_state(parser, GUMBO_LEX_COMMENT); append_char_to_temporary_buffer(parser, '-'); append_char_to_temporary_buffer(parser, c); return NEXT_CHAR; } } // http://www.whatwg.org/specs/web-apps/current-work/complete.html#comment-end-state static StateResult handle_comment_end_state(GumboParser* parser, GumboTokenizerState* tokenizer, int c, GumboToken* output) { switch (c) { case '>': gumbo_tokenizer_set_state(parser, GUMBO_LEX_DATA); return emit_comment(parser, output); case '\0': tokenizer_add_parse_error(parser, GUMBO_ERR_UTF8_NULL); gumbo_tokenizer_set_state(parser, GUMBO_LEX_COMMENT); append_char_to_temporary_buffer(parser, '-'); append_char_to_temporary_buffer(parser, '-'); append_char_to_temporary_buffer(parser, kUtf8ReplacementChar); return NEXT_CHAR; case '!': tokenizer_add_parse_error( parser, GUMBO_ERR_COMMENT_BANG_AFTER_DOUBLE_DASH); gumbo_tokenizer_set_state(parser, GUMBO_LEX_COMMENT_END_BANG); return NEXT_CHAR; case '-': tokenizer_add_parse_error( parser, GUMBO_ERR_COMMENT_DASH_AFTER_DOUBLE_DASH); append_char_to_temporary_buffer(parser, '-'); return NEXT_CHAR; case -1: tokenizer_add_parse_error(parser, GUMBO_ERR_UTF8_NULL); gumbo_tokenizer_set_state(parser, GUMBO_LEX_DATA); emit_comment(parser, output); return RETURN_ERROR; default: tokenizer_add_parse_error(parser, GUMBO_ERR_COMMENT_INVALID); gumbo_tokenizer_set_state(parser, GUMBO_LEX_COMMENT); append_char_to_temporary_buffer(parser, '-'); append_char_to_temporary_buffer(parser, '-'); append_char_to_temporary_buffer(parser, c); return NEXT_CHAR; } } // http://www.whatwg.org/specs/web-apps/current-work/complete.html#comment-end-bang-state static StateResult handle_comment_end_bang_state(GumboParser* parser, GumboTokenizerState* tokenizer, int c, GumboToken* output) { switch (c) { case '-': gumbo_tokenizer_set_state(parser, GUMBO_LEX_COMMENT_END_DASH); append_char_to_temporary_buffer(parser, '-'); append_char_to_temporary_buffer(parser, '-'); append_char_to_temporary_buffer(parser, '!'); return NEXT_CHAR; case '>': gumbo_tokenizer_set_state(parser, GUMBO_LEX_DATA); return emit_comment(parser, output); case '\0': tokenizer_add_parse_error(parser, GUMBO_ERR_UTF8_NULL); gumbo_tokenizer_set_state(parser, GUMBO_LEX_COMMENT); append_char_to_temporary_buffer(parser, '-'); append_char_to_temporary_buffer(parser, '-'); append_char_to_temporary_buffer(parser, '!'); append_char_to_temporary_buffer(parser, kUtf8ReplacementChar); return NEXT_CHAR; case -1: tokenizer_add_parse_error(parser, GUMBO_ERR_COMMENT_END_BANG_EOF); gumbo_tokenizer_set_state(parser, GUMBO_LEX_DATA); emit_comment(parser, output); return RETURN_ERROR; default: gumbo_tokenizer_set_state(parser, GUMBO_LEX_COMMENT); append_char_to_temporary_buffer(parser, '-'); append_char_to_temporary_buffer(parser, '-'); append_char_to_temporary_buffer(parser, '!'); append_char_to_temporary_buffer(parser, c); return NEXT_CHAR; } } // http://www.whatwg.org/specs/web-apps/current-work/complete5/tokenization.html#doctype-state static StateResult handle_doctype_state(GumboParser* parser, GumboTokenizerState* tokenizer, int c, GumboToken* output) { assert(!tokenizer->_temporary_buffer.length); switch (c) { case '\t': case '\n': case '\f': case ' ': gumbo_tokenizer_set_state(parser, GUMBO_LEX_BEFORE_DOCTYPE_NAME); return NEXT_CHAR; case -1: tokenizer_add_parse_error(parser, GUMBO_ERR_DOCTYPE_EOF); gumbo_tokenizer_set_state(parser, GUMBO_LEX_DATA); tokenizer->_doc_type_state.force_quirks = true; emit_doctype(parser, output); return RETURN_ERROR; default: tokenizer_add_parse_error(parser, GUMBO_ERR_DOCTYPE_SPACE); gumbo_tokenizer_set_state(parser, GUMBO_LEX_BEFORE_DOCTYPE_NAME); tokenizer->_reconsume_current_input = true; tokenizer->_doc_type_state.force_quirks = true; return NEXT_CHAR; } } // http://www.whatwg.org/specs/web-apps/current-work/complete5/tokenization.html#before-doctype-name-state static StateResult handle_before_doctype_name_state(GumboParser* parser, GumboTokenizerState* tokenizer, int c, GumboToken* output) { switch (c) { case '\t': case '\n': case '\f': case ' ': return NEXT_CHAR; case '\0': tokenizer_add_parse_error(parser, GUMBO_ERR_UTF8_NULL); gumbo_tokenizer_set_state(parser, GUMBO_LEX_DOCTYPE_NAME); tokenizer->_doc_type_state.force_quirks = true; append_char_to_temporary_buffer(parser, kUtf8ReplacementChar); return NEXT_CHAR; case '>': tokenizer_add_parse_error(parser, GUMBO_ERR_DOCTYPE_RIGHT_BRACKET); gumbo_tokenizer_set_state(parser, GUMBO_LEX_DATA); tokenizer->_doc_type_state.force_quirks = true; emit_doctype(parser, output); return RETURN_ERROR; case -1: tokenizer_add_parse_error(parser, GUMBO_ERR_DOCTYPE_EOF); gumbo_tokenizer_set_state(parser, GUMBO_LEX_DATA); tokenizer->_doc_type_state.force_quirks = true; emit_doctype(parser, output); return RETURN_ERROR; default: gumbo_tokenizer_set_state(parser, GUMBO_LEX_DOCTYPE_NAME); tokenizer->_doc_type_state.force_quirks = false; append_char_to_temporary_buffer(parser, ensure_lowercase(c)); return NEXT_CHAR; } } // http://www.whatwg.org/specs/web-apps/current-work/complete5/tokenization.html#doctype-name-state static StateResult handle_doctype_name_state(GumboParser* parser, GumboTokenizerState* tokenizer, int c, GumboToken* output) { switch (c) { case '\t': case '\n': case '\f': case ' ': gumbo_tokenizer_set_state(parser, GUMBO_LEX_AFTER_DOCTYPE_NAME); gumbo_parser_deallocate(parser, (void*) tokenizer->_doc_type_state.name); finish_temporary_buffer(parser, &tokenizer->_doc_type_state.name); return NEXT_CHAR; case '>': gumbo_tokenizer_set_state(parser, GUMBO_LEX_DATA); gumbo_parser_deallocate(parser, (void*) tokenizer->_doc_type_state.name); finish_temporary_buffer(parser, &tokenizer->_doc_type_state.name); emit_doctype(parser, output); return RETURN_SUCCESS; case '\0': tokenizer_add_parse_error(parser, GUMBO_ERR_UTF8_NULL); append_char_to_temporary_buffer(parser, kUtf8ReplacementChar); return NEXT_CHAR; case -1: tokenizer_add_parse_error(parser, GUMBO_ERR_DOCTYPE_EOF); gumbo_tokenizer_set_state(parser, GUMBO_LEX_DATA); tokenizer->_doc_type_state.force_quirks = true; gumbo_parser_deallocate(parser, (void*) tokenizer->_doc_type_state.name); finish_temporary_buffer(parser, &tokenizer->_doc_type_state.name); emit_doctype(parser, output); return RETURN_ERROR; default: gumbo_tokenizer_set_state(parser, GUMBO_LEX_DOCTYPE_NAME); tokenizer->_doc_type_state.force_quirks = false; append_char_to_temporary_buffer(parser, ensure_lowercase(c)); return NEXT_CHAR; } } // http://www.whatwg.org/specs/web-apps/current-work/complete.html#after-doctype-name-state static StateResult handle_after_doctype_name_state(GumboParser* parser, GumboTokenizerState* tokenizer, int c, GumboToken* output) { switch (c) { case '\t': case '\n': case '\f': case ' ': return NEXT_CHAR; case '>': gumbo_tokenizer_set_state(parser, GUMBO_LEX_DATA); emit_doctype(parser, output); return RETURN_SUCCESS; case -1: tokenizer_add_parse_error(parser, GUMBO_ERR_DOCTYPE_EOF); gumbo_tokenizer_set_state(parser, GUMBO_LEX_DATA); tokenizer->_doc_type_state.force_quirks = true; emit_doctype(parser, output); return RETURN_ERROR; default: if (utf8iterator_maybe_consume_match( &tokenizer->_input, "PUBLIC", sizeof("PUBLIC") - 1, false)) { gumbo_tokenizer_set_state( parser, GUMBO_LEX_AFTER_DOCTYPE_PUBLIC_KEYWORD); tokenizer->_reconsume_current_input = true; } else if (utf8iterator_maybe_consume_match(&tokenizer->_input, "SYSTEM", sizeof("SYSTEM") - 1, false)) { gumbo_tokenizer_set_state( parser, GUMBO_LEX_AFTER_DOCTYPE_SYSTEM_KEYWORD); tokenizer->_reconsume_current_input = true; } else { tokenizer_add_parse_error( parser, GUMBO_ERR_DOCTYPE_SPACE_OR_RIGHT_BRACKET); gumbo_tokenizer_set_state(parser, GUMBO_LEX_BOGUS_DOCTYPE); tokenizer->_doc_type_state.force_quirks = true; } return NEXT_CHAR; } } // http://www.whatwg.org/specs/web-apps/current-work/complete.html#after-doctype-public-keyword-state static StateResult handle_after_doctype_public_keyword_state( GumboParser* parser, GumboTokenizerState* tokenizer, int c, GumboToken* output) { switch (c) { case '\t': case '\n': case '\f': case ' ': gumbo_tokenizer_set_state(parser, GUMBO_LEX_BEFORE_DOCTYPE_PUBLIC_ID); return NEXT_CHAR; case '"': tokenizer_add_parse_error(parser, GUMBO_ERR_DOCTYPE_INVALID); assert(temporary_buffer_equals(parser, "")); gumbo_tokenizer_set_state( parser, GUMBO_LEX_DOCTYPE_PUBLIC_ID_DOUBLE_QUOTED); return NEXT_CHAR; case '\'': tokenizer_add_parse_error(parser, GUMBO_ERR_DOCTYPE_INVALID); assert(temporary_buffer_equals(parser, "")); gumbo_tokenizer_set_state( parser, GUMBO_LEX_DOCTYPE_PUBLIC_ID_SINGLE_QUOTED); return NEXT_CHAR; case '>': tokenizer_add_parse_error(parser, GUMBO_ERR_DOCTYPE_RIGHT_BRACKET); gumbo_tokenizer_set_state(parser, GUMBO_LEX_DATA); tokenizer->_doc_type_state.force_quirks = true; emit_doctype(parser, output); return RETURN_ERROR; case -1: tokenizer_add_parse_error(parser, GUMBO_ERR_DOCTYPE_EOF); gumbo_tokenizer_set_state(parser, GUMBO_LEX_DATA); tokenizer->_doc_type_state.force_quirks = true; emit_doctype(parser, output); return RETURN_ERROR; default: tokenizer_add_parse_error(parser, GUMBO_ERR_DOCTYPE_INVALID); gumbo_tokenizer_set_state(parser, GUMBO_LEX_BOGUS_DOCTYPE); tokenizer->_doc_type_state.force_quirks = true; emit_doctype(parser, output); return RETURN_ERROR; } } // http://www.whatwg.org/specs/web-apps/current-work/complete.html#before-doctype-public-identifier-state static StateResult handle_before_doctype_public_id_state(GumboParser* parser, GumboTokenizerState* tokenizer, int c, GumboToken* output) { switch (c) { case '\t': case '\n': case '\f': case ' ': return NEXT_CHAR; case '"': assert(temporary_buffer_equals(parser, "")); gumbo_tokenizer_set_state( parser, GUMBO_LEX_DOCTYPE_PUBLIC_ID_DOUBLE_QUOTED); return NEXT_CHAR; case '\'': assert(temporary_buffer_equals(parser, "")); gumbo_tokenizer_set_state( parser, GUMBO_LEX_DOCTYPE_PUBLIC_ID_SINGLE_QUOTED); return NEXT_CHAR; case '>': tokenizer_add_parse_error(parser, GUMBO_ERR_DOCTYPE_END); gumbo_tokenizer_set_state(parser, GUMBO_LEX_DATA); tokenizer->_doc_type_state.force_quirks = true; emit_doctype(parser, output); return RETURN_ERROR; case -1: tokenizer_add_parse_error(parser, GUMBO_ERR_DOCTYPE_EOF); gumbo_tokenizer_set_state(parser, GUMBO_LEX_DATA); tokenizer->_doc_type_state.force_quirks = true; emit_doctype(parser, output); return RETURN_ERROR; default: tokenizer_add_parse_error(parser, GUMBO_ERR_DOCTYPE_INVALID); gumbo_tokenizer_set_state(parser, GUMBO_LEX_BOGUS_DOCTYPE); tokenizer->_doc_type_state.force_quirks = true; emit_doctype(parser, output); return RETURN_ERROR; } } // http://www.whatwg.org/specs/web-apps/current-work/complete.html#doctype-public-identifier-(double-quoted)-state static StateResult handle_doctype_public_id_double_quoted_state( GumboParser* parser, GumboTokenizerState* tokenizer, int c, GumboToken* output) { switch (c) { case '"': gumbo_tokenizer_set_state(parser, GUMBO_LEX_AFTER_DOCTYPE_PUBLIC_ID); finish_doctype_public_id(parser); return NEXT_CHAR; case '\0': tokenizer_add_parse_error(parser, GUMBO_ERR_UTF8_NULL); append_char_to_temporary_buffer(parser, kUtf8ReplacementChar); return NEXT_CHAR; case '>': tokenizer_add_parse_error(parser, GUMBO_ERR_DOCTYPE_END); gumbo_tokenizer_set_state(parser, GUMBO_LEX_DATA); tokenizer->_doc_type_state.force_quirks = true; finish_doctype_public_id(parser); emit_doctype(parser, output); return RETURN_ERROR; case -1: tokenizer_add_parse_error(parser, GUMBO_ERR_DOCTYPE_EOF); gumbo_tokenizer_set_state(parser, GUMBO_LEX_DATA); tokenizer->_doc_type_state.force_quirks = true; finish_doctype_public_id(parser); emit_doctype(parser, output); return RETURN_ERROR; default: append_char_to_temporary_buffer(parser, c); return NEXT_CHAR; } } // http://www.whatwg.org/specs/web-apps/current-work/complete.html#doctype-public-identifier-(single-quoted)-state static StateResult handle_doctype_public_id_single_quoted_state( GumboParser* parser, GumboTokenizerState* tokenizer, int c, GumboToken* output) { switch (c) { case '\'': gumbo_tokenizer_set_state(parser, GUMBO_LEX_AFTER_DOCTYPE_PUBLIC_ID); finish_doctype_public_id(parser); return NEXT_CHAR; case '\0': tokenizer_add_parse_error(parser, GUMBO_ERR_UTF8_NULL); append_char_to_temporary_buffer(parser, kUtf8ReplacementChar); return NEXT_CHAR; case '>': tokenizer_add_parse_error(parser, GUMBO_ERR_DOCTYPE_END); gumbo_tokenizer_set_state(parser, GUMBO_LEX_DATA); tokenizer->_doc_type_state.force_quirks = true; finish_doctype_public_id(parser); emit_doctype(parser, output); return RETURN_ERROR; case -1: tokenizer_add_parse_error(parser, GUMBO_ERR_DOCTYPE_EOF); gumbo_tokenizer_set_state(parser, GUMBO_LEX_DATA); tokenizer->_doc_type_state.force_quirks = true; finish_doctype_public_id(parser); emit_doctype(parser, output); return RETURN_ERROR; default: append_char_to_temporary_buffer(parser, c); return NEXT_CHAR; } } // http://www.whatwg.org/specs/web-apps/current-work/complete.html#after-doctype-public-identifier-state static StateResult handle_after_doctype_public_id_state(GumboParser* parser, GumboTokenizerState* tokenizer, int c, GumboToken* output) { switch (c) { case '\t': case '\n': case '\f': case ' ': gumbo_tokenizer_set_state( parser, GUMBO_LEX_BETWEEN_DOCTYPE_PUBLIC_SYSTEM_ID); return NEXT_CHAR; case '>': gumbo_tokenizer_set_state(parser, GUMBO_LEX_DATA); emit_doctype(parser, output); return RETURN_SUCCESS; case '"': tokenizer_add_parse_error(parser, GUMBO_ERR_DOCTYPE_INVALID); assert(temporary_buffer_equals(parser, "")); gumbo_tokenizer_set_state( parser, GUMBO_LEX_DOCTYPE_SYSTEM_ID_DOUBLE_QUOTED); return NEXT_CHAR; case '\'': tokenizer_add_parse_error(parser, GUMBO_ERR_DOCTYPE_INVALID); assert(temporary_buffer_equals(parser, "")); gumbo_tokenizer_set_state( parser, GUMBO_LEX_DOCTYPE_SYSTEM_ID_SINGLE_QUOTED); return NEXT_CHAR; case -1: tokenizer_add_parse_error(parser, GUMBO_ERR_DOCTYPE_EOF); gumbo_tokenizer_set_state(parser, GUMBO_LEX_DATA); tokenizer->_reconsume_current_input = true; tokenizer->_doc_type_state.force_quirks = true; emit_doctype(parser, output); return RETURN_ERROR; default: tokenizer_add_parse_error(parser, GUMBO_ERR_DOCTYPE_INVALID); gumbo_tokenizer_set_state(parser, GUMBO_LEX_BOGUS_DOCTYPE); tokenizer->_doc_type_state.force_quirks = true; return NEXT_CHAR; } } // http://www.whatwg.org/specs/web-apps/current-work/complete.html#between-doctype-public-and-system-identifiers-state static StateResult handle_between_doctype_public_system_id_state( GumboParser* parser, GumboTokenizerState* tokenizer, int c, GumboToken* output) { switch (c) { case '\t': case '\n': case '\f': case ' ': return NEXT_CHAR; case '>': gumbo_tokenizer_set_state(parser, GUMBO_LEX_DATA); emit_doctype(parser, output); return RETURN_SUCCESS; case '"': assert(temporary_buffer_equals(parser, "")); gumbo_tokenizer_set_state( parser, GUMBO_LEX_DOCTYPE_SYSTEM_ID_DOUBLE_QUOTED); return NEXT_CHAR; case '\'': assert(temporary_buffer_equals(parser, "")); gumbo_tokenizer_set_state( parser, GUMBO_LEX_DOCTYPE_SYSTEM_ID_SINGLE_QUOTED); return NEXT_CHAR; case -1: tokenizer_add_parse_error(parser, GUMBO_ERR_DOCTYPE_EOF); gumbo_tokenizer_set_state(parser, GUMBO_LEX_DATA); tokenizer->_doc_type_state.force_quirks = true; emit_doctype(parser, output); return RETURN_ERROR; default: tokenizer_add_parse_error(parser, GUMBO_ERR_DOCTYPE_INVALID); gumbo_tokenizer_set_state(parser, GUMBO_LEX_BOGUS_DOCTYPE); tokenizer->_doc_type_state.force_quirks = true; emit_doctype(parser, output); return RETURN_ERROR; } } // http://www.whatwg.org/specs/web-apps/current-work/complete.html#after-doctype-system-keyword-state static StateResult handle_after_doctype_system_keyword_state( GumboParser* parser, GumboTokenizerState* tokenizer, int c, GumboToken* output) { switch (c) { case '\t': case '\n': case '\f': case ' ': gumbo_tokenizer_set_state(parser, GUMBO_LEX_BEFORE_DOCTYPE_SYSTEM_ID); return NEXT_CHAR; case '"': tokenizer_add_parse_error(parser, GUMBO_ERR_DOCTYPE_INVALID); assert(temporary_buffer_equals(parser, "")); gumbo_tokenizer_set_state( parser, GUMBO_LEX_DOCTYPE_SYSTEM_ID_DOUBLE_QUOTED); return NEXT_CHAR; case '\'': tokenizer_add_parse_error(parser, GUMBO_ERR_DOCTYPE_INVALID); assert(temporary_buffer_equals(parser, "")); gumbo_tokenizer_set_state( parser, GUMBO_LEX_DOCTYPE_SYSTEM_ID_SINGLE_QUOTED); return NEXT_CHAR; case '>': tokenizer_add_parse_error(parser, GUMBO_ERR_DOCTYPE_END); gumbo_tokenizer_set_state(parser, GUMBO_LEX_DATA); tokenizer->_doc_type_state.force_quirks = true; emit_doctype(parser, output); return RETURN_ERROR; case -1: tokenizer_add_parse_error(parser, GUMBO_ERR_DOCTYPE_EOF); gumbo_tokenizer_set_state(parser, GUMBO_LEX_DATA); tokenizer->_doc_type_state.force_quirks = true; emit_doctype(parser, output); return RETURN_ERROR; default: tokenizer_add_parse_error(parser, GUMBO_ERR_DOCTYPE_INVALID); gumbo_tokenizer_set_state(parser, GUMBO_LEX_BOGUS_DOCTYPE); tokenizer->_doc_type_state.force_quirks = true; return NEXT_CHAR; } } // http://www.whatwg.org/specs/web-apps/current-work/complete.html#before-doctype-system-identifier-state static StateResult handle_before_doctype_system_id_state(GumboParser* parser, GumboTokenizerState* tokenizer, int c, GumboToken* output) { switch (c) { case '\t': case '\n': case '\f': case ' ': return NEXT_CHAR; case '"': assert(temporary_buffer_equals(parser, "")); gumbo_tokenizer_set_state( parser, GUMBO_LEX_DOCTYPE_SYSTEM_ID_DOUBLE_QUOTED); return NEXT_CHAR; case '\'': assert(temporary_buffer_equals(parser, "")); gumbo_tokenizer_set_state( parser, GUMBO_LEX_DOCTYPE_SYSTEM_ID_SINGLE_QUOTED); return NEXT_CHAR; case '>': tokenizer_add_parse_error(parser, GUMBO_ERR_DOCTYPE_END); gumbo_tokenizer_set_state(parser, GUMBO_LEX_DATA); tokenizer->_doc_type_state.force_quirks = true; emit_doctype(parser, output); return RETURN_ERROR; case -1: tokenizer_add_parse_error(parser, GUMBO_ERR_DOCTYPE_EOF); gumbo_tokenizer_set_state(parser, GUMBO_LEX_DATA); tokenizer->_doc_type_state.force_quirks = true; emit_doctype(parser, output); return RETURN_ERROR; default: tokenizer_add_parse_error(parser, GUMBO_ERR_DOCTYPE_INVALID); gumbo_tokenizer_set_state(parser, GUMBO_LEX_BOGUS_DOCTYPE); tokenizer->_doc_type_state.force_quirks = true; return NEXT_CHAR; } } // http://www.whatwg.org/specs/web-apps/current-work/complete.html#doctype-system-identifier-(double-quoted)-state static StateResult handle_doctype_system_id_double_quoted_state( GumboParser* parser, GumboTokenizerState* tokenizer, int c, GumboToken* output) { switch (c) { case '"': gumbo_tokenizer_set_state(parser, GUMBO_LEX_AFTER_DOCTYPE_SYSTEM_ID); finish_doctype_system_id(parser); return NEXT_CHAR; case '\0': tokenizer_add_parse_error(parser, GUMBO_ERR_UTF8_NULL); append_char_to_temporary_buffer(parser, kUtf8ReplacementChar); return NEXT_CHAR; case '>': tokenizer_add_parse_error(parser, GUMBO_ERR_DOCTYPE_END); gumbo_tokenizer_set_state(parser, GUMBO_LEX_DATA); tokenizer->_doc_type_state.force_quirks = true; finish_doctype_system_id(parser); emit_doctype(parser, output); return RETURN_ERROR; case -1: tokenizer_add_parse_error(parser, GUMBO_ERR_DOCTYPE_EOF); gumbo_tokenizer_set_state(parser, GUMBO_LEX_DATA); tokenizer->_doc_type_state.force_quirks = true; finish_doctype_system_id(parser); emit_doctype(parser, output); return RETURN_ERROR; default: append_char_to_temporary_buffer(parser, c); return NEXT_CHAR; } } // http://www.whatwg.org/specs/web-apps/current-work/complete.html#doctype-system-identifier-(single-quoted)-state static StateResult handle_doctype_system_id_single_quoted_state( GumboParser* parser, GumboTokenizerState* tokenizer, int c, GumboToken* output) { switch (c) { case '\'': gumbo_tokenizer_set_state(parser, GUMBO_LEX_AFTER_DOCTYPE_SYSTEM_ID); finish_doctype_system_id(parser); return NEXT_CHAR; case '\0': tokenizer_add_parse_error(parser, GUMBO_ERR_UTF8_NULL); append_char_to_temporary_buffer(parser, kUtf8ReplacementChar); return NEXT_CHAR; case '>': tokenizer_add_parse_error(parser, GUMBO_ERR_DOCTYPE_END); gumbo_tokenizer_set_state(parser, GUMBO_LEX_DATA); tokenizer->_doc_type_state.force_quirks = true; finish_doctype_system_id(parser); emit_doctype(parser, output); return RETURN_ERROR; case -1: tokenizer_add_parse_error(parser, GUMBO_ERR_DOCTYPE_INVALID); gumbo_tokenizer_set_state(parser, GUMBO_LEX_DATA); tokenizer->_doc_type_state.force_quirks = true; finish_doctype_system_id(parser); emit_doctype(parser, output); return RETURN_ERROR; default: append_char_to_temporary_buffer(parser, c); return NEXT_CHAR; } } // http://www.whatwg.org/specs/web-apps/current-work/complete.html#after-doctype-system-identifier-state static StateResult handle_after_doctype_system_id_state(GumboParser* parser, GumboTokenizerState* tokenizer, int c, GumboToken* output) { switch (c) { case '\t': case '\n': case '\f': case ' ': return NEXT_CHAR; case '>': gumbo_tokenizer_set_state(parser, GUMBO_LEX_DATA); emit_doctype(parser, output); return RETURN_SUCCESS; case -1: tokenizer_add_parse_error(parser, GUMBO_ERR_DOCTYPE_EOF); gumbo_tokenizer_set_state(parser, GUMBO_LEX_DATA); tokenizer->_doc_type_state.force_quirks = true; emit_doctype(parser, output); return RETURN_ERROR; default: tokenizer_add_parse_error(parser, GUMBO_ERR_DOCTYPE_INVALID); gumbo_tokenizer_set_state(parser, GUMBO_LEX_BOGUS_DOCTYPE); return NEXT_CHAR; } } // http://www.whatwg.org/specs/web-apps/current-work/complete.html#bogus-doctype-state static StateResult handle_bogus_doctype_state(GumboParser* parser, GumboTokenizerState* tokenizer, int c, GumboToken* output) { if (c == '>' || c == -1) { gumbo_tokenizer_set_state(parser, GUMBO_LEX_DATA); emit_doctype(parser, output); return RETURN_ERROR; } return NEXT_CHAR; } // http://www.whatwg.org/specs/web-apps/current-work/complete.html#cdata-section-state static StateResult handle_cdata_state(GumboParser* parser, GumboTokenizerState* tokenizer, int c, GumboToken* output) { if (c == -1 || utf8iterator_maybe_consume_match( &tokenizer->_input, "]]>", sizeof("]]>") - 1, true)) { tokenizer->_reconsume_current_input = true; reset_token_start_point(tokenizer); gumbo_tokenizer_set_state(parser, GUMBO_LEX_DATA); tokenizer->_is_in_cdata = false; return NEXT_CHAR; } else { return emit_current_char(parser, output); } } typedef StateResult (*GumboLexerStateFunction)( GumboParser*, GumboTokenizerState*, int, GumboToken*); static GumboLexerStateFunction dispatch_table[] = {handle_data_state, handle_char_ref_in_data_state, handle_rcdata_state, handle_char_ref_in_rcdata_state, handle_rawtext_state, handle_script_state, handle_plaintext_state, handle_tag_open_state, handle_end_tag_open_state, handle_tag_name_state, handle_rcdata_lt_state, handle_rcdata_end_tag_open_state, handle_rcdata_end_tag_name_state, handle_rawtext_lt_state, handle_rawtext_end_tag_open_state, handle_rawtext_end_tag_name_state, handle_script_lt_state, handle_script_end_tag_open_state, handle_script_end_tag_name_state, handle_script_escaped_start_state, handle_script_escaped_start_dash_state, handle_script_escaped_state, handle_script_escaped_dash_state, handle_script_escaped_dash_dash_state, handle_script_escaped_lt_state, handle_script_escaped_end_tag_open_state, handle_script_escaped_end_tag_name_state, handle_script_double_escaped_start_state, handle_script_double_escaped_state, handle_script_double_escaped_dash_state, handle_script_double_escaped_dash_dash_state, handle_script_double_escaped_lt_state, handle_script_double_escaped_end_state, handle_before_attr_name_state, handle_attr_name_state, handle_after_attr_name_state, handle_before_attr_value_state, handle_attr_value_double_quoted_state, handle_attr_value_single_quoted_state, handle_attr_value_unquoted_state, handle_char_ref_in_attr_value_state, handle_after_attr_value_quoted_state, handle_self_closing_start_tag_state, handle_bogus_comment_state, handle_markup_declaration_state, handle_comment_start_state, handle_comment_start_dash_state, handle_comment_state, handle_comment_end_dash_state, handle_comment_end_state, handle_comment_end_bang_state, handle_doctype_state, handle_before_doctype_name_state, handle_doctype_name_state, handle_after_doctype_name_state, handle_after_doctype_public_keyword_state, handle_before_doctype_public_id_state, handle_doctype_public_id_double_quoted_state, handle_doctype_public_id_single_quoted_state, handle_after_doctype_public_id_state, handle_between_doctype_public_system_id_state, handle_after_doctype_system_keyword_state, handle_before_doctype_system_id_state, handle_doctype_system_id_double_quoted_state, handle_doctype_system_id_single_quoted_state, handle_after_doctype_system_id_state, handle_bogus_doctype_state, handle_cdata_state}; bool gumbo_lex(GumboParser* parser, GumboToken* output) { // Because of the spec requirements that... // // 1. Tokens be handled immediately by the parser upon emission. // 2. Some states (eg. CDATA, or various error conditions) require the // emission of multiple tokens in the same states. // 3. The tokenizer often has to reconsume the same character in a different // state. // // ...all state must be held in the GumboTokenizer struct instead of in local // variables in this function. That allows us to return from this method with // a token, and then immediately jump back to the same state with the same // input if we need to return a different token. The various emit_* functions // are responsible for changing state (eg. flushing the chardata buffer, // reading the next input character) to avoid an infinite loop. GumboTokenizerState* tokenizer = parser->_tokenizer_state; if (tokenizer->_buffered_emit_char != kGumboNoChar) { tokenizer->_reconsume_current_input = true; emit_char(parser, tokenizer->_buffered_emit_char, output); // And now that we've avoided advancing the input, make sure we set // _reconsume_current_input back to false to make sure the *next* character // isn't consumed twice. tokenizer->_reconsume_current_input = false; tokenizer->_buffered_emit_char = kGumboNoChar; return true; } if (maybe_emit_from_temporary_buffer(parser, output)) { return true; } while (1) { assert(!tokenizer->_temporary_buffer_emit); assert(tokenizer->_buffered_emit_char == kGumboNoChar); int c = utf8iterator_current(&tokenizer->_input); gumbo_debug( "Lexing character '%c' (%d) in state %d.\n", c, c, tokenizer->_state); StateResult result = dispatch_table[tokenizer->_state](parser, tokenizer, c, output); // We need to clear reconsume_current_input before returning to prevent // certain infinite loop states. bool should_advance = !tokenizer->_reconsume_current_input; tokenizer->_reconsume_current_input = false; if (result == RETURN_SUCCESS) { return true; } else if (result == RETURN_ERROR) { return false; } if (should_advance) { utf8iterator_next(&tokenizer->_input); } } } void gumbo_token_destroy(GumboParser* parser, GumboToken* token) { if (!token) return; switch (token->type) { case GUMBO_TOKEN_DOCTYPE: gumbo_parser_deallocate(parser, (void*) token->v.doc_type.name); gumbo_parser_deallocate( parser, (void*) token->v.doc_type.public_identifier); gumbo_parser_deallocate( parser, (void*) token->v.doc_type.system_identifier); return; case GUMBO_TOKEN_START_TAG: for (unsigned int i = 0; i < token->v.start_tag.attributes.length; ++i) { GumboAttribute* attr = token->v.start_tag.attributes.data[i]; if (attr) { // May have been nulled out if this token was merged with another. gumbo_destroy_attribute(parser, attr); } } gumbo_parser_deallocate( parser, (void*) token->v.start_tag.attributes.data); return; case GUMBO_TOKEN_COMMENT: gumbo_parser_deallocate(parser, (void*) token->v.text); return; default: return; } } ================================================ FILE: zhuishushenqi/Vendor/OCGumbo/gumbo/tokenizer.h ================================================ // Copyright 2010 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // Author: jdtang@google.com (Jonathan Tang) // // This contains an implementation of a tokenizer for HTML5. It consumes a // buffer of UTF-8 characters, and then emits a stream of tokens. #ifndef GUMBO_TOKENIZER_H_ #define GUMBO_TOKENIZER_H_ #include <stdbool.h> #include <stddef.h> #include "gumbo.h" #include "token_type.h" #include "tokenizer_states.h" #ifdef __cplusplus extern "C" { #endif struct GumboInternalParser; // Struct containing all information pertaining to doctype tokens. typedef struct GumboInternalTokenDocType { const char* name; const char* public_identifier; const char* system_identifier; bool force_quirks; // There's no way to tell a 0-length public or system ID apart from the // absence of a public or system ID, but they're handled different by the // spec, so we need bool flags for them. bool has_public_identifier; bool has_system_identifier; } GumboTokenDocType; // Struct containing all information pertaining to start tag tokens. typedef struct GumboInternalTokenStartTag { GumboTag tag; GumboVector /* GumboAttribute */ attributes; bool is_self_closing; } GumboTokenStartTag; // A data structure representing a single token in the input stream. This // contains an enum for the type, the source position, a GumboStringPiece // pointing to the original text, and then a union for any parsed data. typedef struct GumboInternalToken { GumboTokenType type; GumboSourcePosition position; GumboStringPiece original_text; union { GumboTokenDocType doc_type; GumboTokenStartTag start_tag; GumboTag end_tag; const char* text; // For comments. int character; // For character, whitespace, null, and EOF tokens. } v; } GumboToken; // Initializes the tokenizer state within the GumboParser object, setting up a // parse of the specified text. void gumbo_tokenizer_state_init( struct GumboInternalParser* parser, const char* text, size_t text_length); // Destroys the tokenizer state within the GumboParser object, freeing any // dynamically-allocated structures within it. void gumbo_tokenizer_state_destroy(struct GumboInternalParser* parser); // Sets the tokenizer state to the specified value. This is needed by some // parser states, which alter the state of the tokenizer in response to tags // seen. void gumbo_tokenizer_set_state( struct GumboInternalParser* parser, GumboTokenizerEnum state); // Flags whether the current node is a foreign content element. This is // necessary for the markup declaration open state, where the tokenizer must be // aware of the state of the parser to properly tokenize bad comment tags. // http://www.whatwg.org/specs/web-apps/current-work/multipage/tokenization.html#markup-declaration-open-state void gumbo_tokenizer_set_is_current_node_foreign( struct GumboInternalParser* parser, bool is_foreign); // Lexes a single token from the specified buffer, filling the output with the // parsed GumboToken data structure. Returns true for a successful // tokenization, false if a parse error occurs. // // Example: // struct GumboInternalParser parser; // GumboToken output; // gumbo_tokenizer_state_init(&parser, text, strlen(text)); // while (gumbo_lex(&parser, &output)) { // ...do stuff with output. // gumbo_token_destroy(&parser, &token); // } // gumbo_tokenizer_state_destroy(&parser); bool gumbo_lex(struct GumboInternalParser* parser, GumboToken* output); // Frees the internally-allocated pointers within an GumboToken. Note that this // doesn't free the token itself, since oftentimes it will be allocated on the // stack. A simple call to free() (or GumboParser->deallocator, if // appropriate) can handle that. // // Note that if you are handing over ownership of the internal strings to some // other data structure - for example, a parse tree - these do not need to be // freed. void gumbo_token_destroy(struct GumboInternalParser* parser, GumboToken* token); #ifdef __cplusplus } #endif #endif // GUMBO_TOKENIZER_H_ ================================================ FILE: zhuishushenqi/Vendor/OCGumbo/gumbo/tokenizer_states.h ================================================ // Copyright 2011 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // Author: jdtang@google.com (Jonathan Tang) // // This contains the list of states used in the tokenizer. Although at first // glance it seems like these could be kept internal to the tokenizer, several // of the actions in the parser require that it reach into the tokenizer and // reset the tokenizer state. For that to work, it needs to have the // definitions of individual states available. // // This may also be useful for providing more detailed error messages for parse // errors, as we can match up states and inputs in a table without having to // clutter the tokenizer code with lots of precise error messages. #ifndef GUMBO_TOKENIZER_STATES_H_ #define GUMBO_TOKENIZER_STATES_H_ // The ordering of this enum is also used to build the dispatch table for the // tokenizer state machine, so if it is changed, be sure to update that too. typedef enum { GUMBO_LEX_DATA, GUMBO_LEX_CHAR_REF_IN_DATA, GUMBO_LEX_RCDATA, GUMBO_LEX_CHAR_REF_IN_RCDATA, GUMBO_LEX_RAWTEXT, GUMBO_LEX_SCRIPT, GUMBO_LEX_PLAINTEXT, GUMBO_LEX_TAG_OPEN, GUMBO_LEX_END_TAG_OPEN, GUMBO_LEX_TAG_NAME, GUMBO_LEX_RCDATA_LT, GUMBO_LEX_RCDATA_END_TAG_OPEN, GUMBO_LEX_RCDATA_END_TAG_NAME, GUMBO_LEX_RAWTEXT_LT, GUMBO_LEX_RAWTEXT_END_TAG_OPEN, GUMBO_LEX_RAWTEXT_END_TAG_NAME, GUMBO_LEX_SCRIPT_LT, GUMBO_LEX_SCRIPT_END_TAG_OPEN, GUMBO_LEX_SCRIPT_END_TAG_NAME, GUMBO_LEX_SCRIPT_ESCAPED_START, GUMBO_LEX_SCRIPT_ESCAPED_START_DASH, GUMBO_LEX_SCRIPT_ESCAPED, GUMBO_LEX_SCRIPT_ESCAPED_DASH, GUMBO_LEX_SCRIPT_ESCAPED_DASH_DASH, GUMBO_LEX_SCRIPT_ESCAPED_LT, GUMBO_LEX_SCRIPT_ESCAPED_END_TAG_OPEN, GUMBO_LEX_SCRIPT_ESCAPED_END_TAG_NAME, GUMBO_LEX_SCRIPT_DOUBLE_ESCAPED_START, GUMBO_LEX_SCRIPT_DOUBLE_ESCAPED, GUMBO_LEX_SCRIPT_DOUBLE_ESCAPED_DASH, GUMBO_LEX_SCRIPT_DOUBLE_ESCAPED_DASH_DASH, GUMBO_LEX_SCRIPT_DOUBLE_ESCAPED_LT, GUMBO_LEX_SCRIPT_DOUBLE_ESCAPED_END, GUMBO_LEX_BEFORE_ATTR_NAME, GUMBO_LEX_ATTR_NAME, GUMBO_LEX_AFTER_ATTR_NAME, GUMBO_LEX_BEFORE_ATTR_VALUE, GUMBO_LEX_ATTR_VALUE_DOUBLE_QUOTED, GUMBO_LEX_ATTR_VALUE_SINGLE_QUOTED, GUMBO_LEX_ATTR_VALUE_UNQUOTED, GUMBO_LEX_CHAR_REF_IN_ATTR_VALUE, GUMBO_LEX_AFTER_ATTR_VALUE_QUOTED, GUMBO_LEX_SELF_CLOSING_START_TAG, GUMBO_LEX_BOGUS_COMMENT, GUMBO_LEX_MARKUP_DECLARATION, GUMBO_LEX_COMMENT_START, GUMBO_LEX_COMMENT_START_DASH, GUMBO_LEX_COMMENT, GUMBO_LEX_COMMENT_END_DASH, GUMBO_LEX_COMMENT_END, GUMBO_LEX_COMMENT_END_BANG, GUMBO_LEX_DOCTYPE, GUMBO_LEX_BEFORE_DOCTYPE_NAME, GUMBO_LEX_DOCTYPE_NAME, GUMBO_LEX_AFTER_DOCTYPE_NAME, GUMBO_LEX_AFTER_DOCTYPE_PUBLIC_KEYWORD, GUMBO_LEX_BEFORE_DOCTYPE_PUBLIC_ID, GUMBO_LEX_DOCTYPE_PUBLIC_ID_DOUBLE_QUOTED, GUMBO_LEX_DOCTYPE_PUBLIC_ID_SINGLE_QUOTED, GUMBO_LEX_AFTER_DOCTYPE_PUBLIC_ID, GUMBO_LEX_BETWEEN_DOCTYPE_PUBLIC_SYSTEM_ID, GUMBO_LEX_AFTER_DOCTYPE_SYSTEM_KEYWORD, GUMBO_LEX_BEFORE_DOCTYPE_SYSTEM_ID, GUMBO_LEX_DOCTYPE_SYSTEM_ID_DOUBLE_QUOTED, GUMBO_LEX_DOCTYPE_SYSTEM_ID_SINGLE_QUOTED, GUMBO_LEX_AFTER_DOCTYPE_SYSTEM_ID, GUMBO_LEX_BOGUS_DOCTYPE, GUMBO_LEX_CDATA } GumboTokenizerEnum; #endif // GUMBO_TOKENIZER_STATES_H_ ================================================ FILE: zhuishushenqi/Vendor/OCGumbo/gumbo/utf8.c ================================================ // Copyright 2010 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // Author: jdtang@google.com (Jonathan Tang) #include "utf8.h" #include <assert.h> #include <stdint.h> #include <string.h> #include <strings.h> // For strncasecmp. #include "error.h" #include "gumbo.h" #include "parser.h" #include "util.h" #include "vector.h" const int kUtf8ReplacementChar = 0xFFFD; // Reference material: // Wikipedia: http://en.wikipedia.org/wiki/UTF-8#Description // RFC 3629: http://tools.ietf.org/html/rfc3629 // HTML5 Unicode handling: // http://www.whatwg.org/specs/web-apps/current-work/multipage/syntax.html#preprocessing-the-input-stream // // This implementation is based on a DFA-based decoder by Bjoern Hoehrmann // <bjoern@hoehrmann.de>. We wrap the inner table-based decoder routine in our // own handling for newlines, tabs, invalid continuation bytes, and other // conditions that the HTML5 spec fully specifies but normal UTF8 decoders do // not handle. // See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details. Full text of // the license agreement and code follows. // Copyright (c) 2008-2009 Bjoern Hoehrmann <bjoern@hoehrmann.de> // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies // of the Software, and to permit persons to whom the Software is furnished to // do // so, subject to the following conditions: // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. #define UTF8_ACCEPT 0 #define UTF8_REJECT 12 static const uint8_t utf8d[] = { // The first part of the table maps bytes to character classes that // to reduce the size of the transition table and create bitmasks. 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 10, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 3, 3, 11, 6, 6, 6, 5, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, // The second part is a transition table that maps a combination // of a state of the automaton and a character class to a state. 0, 12, 24, 36, 60, 96, 84, 12, 12, 12, 48, 72, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 0, 12, 12, 12, 12, 12, 0, 12, 0, 12, 12, 12, 24, 12, 12, 12, 12, 12, 24, 12, 24, 12, 12, 12, 12, 12, 12, 12, 12, 12, 24, 12, 12, 12, 12, 12, 24, 12, 12, 12, 12, 12, 12, 12, 24, 12, 12, 12, 12, 12, 12, 12, 12, 12, 36, 12, 36, 12, 12, 12, 36, 12, 12, 12, 12, 12, 36, 12, 36, 12, 12, 12, 36, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, }; uint32_t static inline decode(uint32_t* state, uint32_t* codep, uint32_t byte) { uint32_t type = utf8d[byte]; *codep = (*state != UTF8_ACCEPT) ? (byte & 0x3fu) | (*codep << 6) : (0xff >> type) & (byte); *state = utf8d[256 + *state + type]; return *state; } // END COPIED CODE. // Adds a decoding error to the parser's error list, based on the current state // of the Utf8Iterator. static void add_error(Utf8Iterator* iter, GumboErrorType type) { GumboParser* parser = iter->_parser; GumboError* error = gumbo_add_error(parser); if (!error) { return; } error->type = type; error->position = iter->_pos; error->original_text = iter->_start; // At the point the error is recorded, the code point hasn't been computed // yet (and can't be, because it's invalid), so we need to build up the raw // hex value from the bytes under the cursor. uint64_t code_point = 0; for (int i = 0; i < iter->_width; ++i) { code_point = (code_point << 8) | (unsigned char) iter->_start[i]; } error->v.codepoint = code_point; } // Reads the next UTF-8 character in the iter. // This assumes that iter->_start points to the beginning of the character. // When this method returns, iter->_width and iter->_current will be set // appropriately, as well as any error flags. static void read_char(Utf8Iterator* iter) { if (iter->_start >= iter->_end) { // No input left to consume; emit an EOF and set width = 0. iter->_current = -1; iter->_width = 0; return; } uint32_t code_point = 0; uint32_t state = UTF8_ACCEPT; for (const char* c = iter->_start; c < iter->_end; ++c) { decode(&state, &code_point, (uint32_t)(unsigned char) (*c)); if (state == UTF8_ACCEPT) { iter->_width = c - iter->_start + 1; // This is the special handling for carriage returns that is mandated by // the HTML5 spec. Since we're looking for particular 7-bit literal // characters, we operate in terms of chars and only need a check for iter // overrun, instead of having to read in a full next code point. // http://www.whatwg.org/specs/web-apps/current-work/multipage/parsing.html#preprocessing-the-input-stream if (code_point == '\r') { assert(iter->_width == 1); const char* next = c + 1; if (next < iter->_end && *next == '\n') { // Advance the iter, as if the carriage return didn't exist. ++iter->_start; // Preserve the true offset, since other tools that look at it may be // unaware of HTML5's rules for converting \r into \n. ++iter->_pos.offset; } code_point = '\n'; } if (utf8_is_invalid_code_point(code_point)) { add_error(iter, GUMBO_ERR_UTF8_INVALID); code_point = kUtf8ReplacementChar; } iter->_current = code_point; return; } else if (state == UTF8_REJECT) { // We don't want to consume the invalid continuation byte of a multi-byte // run, but we do want to skip past an invalid first byte. iter->_width = c - iter->_start + (c == iter->_start); iter->_current = kUtf8ReplacementChar; add_error(iter, GUMBO_ERR_UTF8_INVALID); return; } } // If we got here without exiting early, then we've reached the end of the // iterator. Add an error for truncated input, set the width to consume the // rest of the iterator, and emit a replacement character. The next time we // enter this method, it will detect that there's no input to consume and // output an EOF. iter->_current = kUtf8ReplacementChar; iter->_width = iter->_end - iter->_start; add_error(iter, GUMBO_ERR_UTF8_TRUNCATED); } static void update_position(Utf8Iterator* iter) { iter->_pos.offset += iter->_width; if (iter->_current == '\n') { ++iter->_pos.line; iter->_pos.column = 1; } else if (iter->_current == '\t') { int tab_stop = iter->_parser->_options->tab_stop; iter->_pos.column = ((iter->_pos.column / tab_stop) + 1) * tab_stop; } else if (iter->_current != -1) { ++iter->_pos.column; } } // Returns true if this Unicode code point is in the list of characters // forbidden by the HTML5 spec, such as undefined control chars. bool utf8_is_invalid_code_point(int c) { return (c >= 0x1 && c <= 0x8) || c == 0xB || (c >= 0xE && c <= 0x1F) || (c >= 0x7F && c <= 0x9F) || (c >= 0xFDD0 && c <= 0xFDEF) || ((c & 0xFFFF) == 0xFFFE) || ((c & 0xFFFF) == 0xFFFF); } void utf8iterator_init(GumboParser* parser, const char* source, size_t source_length, Utf8Iterator* iter) { iter->_start = source; iter->_end = source + source_length; iter->_pos.line = 1; iter->_pos.column = 1; iter->_pos.offset = 0; iter->_parser = parser; read_char(iter); } void utf8iterator_next(Utf8Iterator* iter) { // We update positions based on the *last* character read, so that the first // character following a newline is at column 1 in the next line. update_position(iter); iter->_start += iter->_width; read_char(iter); } int utf8iterator_current(const Utf8Iterator* iter) { return iter->_current; } void utf8iterator_get_position( const Utf8Iterator* iter, GumboSourcePosition* output) { *output = iter->_pos; } const char* utf8iterator_get_char_pointer(const Utf8Iterator* iter) { return iter->_start; } const char* utf8iterator_get_end_pointer(const Utf8Iterator* iter) { return iter->_end; } bool utf8iterator_maybe_consume_match(Utf8Iterator* iter, const char* prefix, size_t length, bool case_sensitive) { bool matched = (iter->_start + length <= iter->_end) && (case_sensitive ? !strncmp(iter->_start, prefix, length) : !strncasecmp(iter->_start, prefix, length)); if (matched) { for (unsigned int i = 0; i < length; ++i) { utf8iterator_next(iter); } return true; } else { return false; } } void utf8iterator_mark(Utf8Iterator* iter) { iter->_mark = iter->_start; iter->_mark_pos = iter->_pos; } // Returns the current input stream position to the mark. void utf8iterator_reset(Utf8Iterator* iter) { iter->_start = iter->_mark; iter->_pos = iter->_mark_pos; read_char(iter); } // Sets the position and original text fields of an error to the value at the // mark. void utf8iterator_fill_error_at_mark(Utf8Iterator* iter, GumboError* error) { error->position = iter->_mark_pos; error->original_text = iter->_mark; } ================================================ FILE: zhuishushenqi/Vendor/OCGumbo/gumbo/utf8.h ================================================ // Copyright 2010 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // Author: jdtang@google.com (Jonathan Tang) // // This contains an implementation of a UTF8 iterator and decoder suitable for // an HTML5 parser. This does a bit more than straight UTF-8 decoding. The // HTML5 spec specifies that: // 1. Decoding errors are parse errors. // 2. Certain other codepoints (eg. control characters) are parse errors. // 3. Carriage returns and CR/LF groups are converted to line feeds. // http://www.whatwg.org/specs/web-apps/current-work/multipage/infrastructure.html#decoded-as-utf-8,-with-error-handling // // Also, we want to keep track of source positions for error handling. As a // result, we fold all that functionality into this decoder, and can't use an // off-the-shelf library. // // This header is internal-only, which is why we prefix functions with only // utf8_ or utf8_iterator_ instead of gumbo_utf8_. #ifndef GUMBO_UTF8_H_ #define GUMBO_UTF8_H_ #include <stdbool.h> #include <stddef.h> #include "gumbo.h" #ifdef __cplusplus extern "C" { #endif struct GumboInternalError; struct GumboInternalParser; // Unicode replacement char. extern const int kUtf8ReplacementChar; typedef struct GumboInternalUtf8Iterator { // Points at the start of the code point most recently read into 'current'. const char* _start; // Points at the mark. The mark is initially set to the beginning of the // input. const char* _mark; // Points past the end of the iter, like a past-the-end iterator in the STL. const char* _end; // The code point under the cursor. int _current; // The width in bytes of the current code point. int _width; // The SourcePosition for the current location. GumboSourcePosition _pos; // The SourcePosition for the mark. GumboSourcePosition _mark_pos; // Pointer back to the GumboParser instance, for configuration options and // error recording. struct GumboInternalParser* _parser; } Utf8Iterator; // Returns true if this Unicode code point is in the list of characters // forbidden by the HTML5 spec, such as NUL bytes and undefined control chars. bool utf8_is_invalid_code_point(int c); // Initializes a new Utf8Iterator from the given byte buffer. The source does // not have to be NUL-terminated, but the length must be passed in explicitly. void utf8iterator_init(struct GumboInternalParser* parser, const char* source, size_t source_length, Utf8Iterator* iter); // Advances the current position by one code point. void utf8iterator_next(Utf8Iterator* iter); // Returns the current code point as an integer. int utf8iterator_current(const Utf8Iterator* iter); // Retrieves and fills the output parameter with the current source position. void utf8iterator_get_position( const Utf8Iterator* iter, GumboSourcePosition* output); // Retrieves a character pointer to the start of the current character. const char* utf8iterator_get_char_pointer(const Utf8Iterator* iter); // Retrieves a character pointer to 1 past the end of the buffer. This is // necessary for certain state machines and string comparisons that would like // to look directly for ASCII text in the buffer without going through the // decoder. const char* utf8iterator_get_end_pointer(const Utf8Iterator* iter); // If the upcoming text in the buffer matches the specified prefix (which has // length 'length'), consume it and return true. Otherwise, return false with // no other effects. If the length of the string would overflow the buffer, // this returns false. Note that prefix should not contain null bytes because // of the use of strncmp/strncasecmp internally. All existing use-cases adhere // to this. bool utf8iterator_maybe_consume_match( Utf8Iterator* iter, const char* prefix, size_t length, bool case_sensitive); // "Marks" a particular location of interest in the input stream, so that it can // later be reset() to. There's also the ability to record an error at the // point that was marked, as oftentimes that's more useful than the last // character before the error was detected. void utf8iterator_mark(Utf8Iterator* iter); // Returns the current input stream position to the mark. void utf8iterator_reset(Utf8Iterator* iter); // Sets the position and original text fields of an error to the value at the // mark. void utf8iterator_fill_error_at_mark( Utf8Iterator* iter, struct GumboInternalError* error); #ifdef __cplusplus } #endif #endif // GUMBO_UTF8_H_ ================================================ FILE: zhuishushenqi/Vendor/OCGumbo/gumbo/util.c ================================================ // Copyright 2010 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // Author: jdtang@google.com (Jonathan Tang) #include "util.h" #include <assert.h> #include <stdlib.h> #include <string.h> #include <strings.h> #include <stdarg.h> #include <stdio.h> #include "gumbo.h" #include "parser.h" // TODO(jdtang): This should be elsewhere, but there's no .c file for // SourcePositions and yet the constant needs some linkage, so this is as good // as any. const GumboSourcePosition kGumboEmptySourcePosition = {0, 0, 0}; void* gumbo_parser_allocate(GumboParser* parser, size_t num_bytes) { return parser->_options->allocator(parser->_options->userdata, num_bytes); } void gumbo_parser_deallocate(GumboParser* parser, void* ptr) { parser->_options->deallocator(parser->_options->userdata, ptr); } char* gumbo_copy_stringz(GumboParser* parser, const char* str) { char* buffer = gumbo_parser_allocate(parser, strlen(str) + 1); strcpy(buffer, str); return buffer; } // Debug function to trace operation of the parser. Pass --copts=-DGUMBO_DEBUG // to use. void gumbo_debug(const char* format, ...) { #ifdef GUMBO_DEBUG va_list args; va_start(args, format); vprintf(format, args); va_end(args); fflush(stdout); #endif } ================================================ FILE: zhuishushenqi/Vendor/OCGumbo/gumbo/util.h ================================================ // Copyright 2010 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // Author: jdtang@google.com (Jonathan Tang) // // This contains some utility functions that didn't fit into any of the other // headers. #ifndef GUMBO_UTIL_H_ #define GUMBO_UTIL_H_ #ifdef _MSC_VER #define _CRT_SECURE_NO_WARNINGS #endif #include <stdbool.h> #include <stddef.h> #ifdef __cplusplus extern "C" { #endif // Forward declaration since it's passed into some of the functions in this // header. struct GumboInternalParser; // Utility function for allocating & copying a null-terminated string into a // freshly-allocated buffer. This is necessary for proper memory management; we // have the convention that all const char* in parse tree structures are // freshly-allocated, so if we didn't copy, we'd try to delete a literal string // when the parse tree is destroyed. char* gumbo_copy_stringz(struct GumboInternalParser* parser, const char* str); // Allocate a chunk of memory, using the allocator specified in the Parser's // config options. void* gumbo_parser_allocate( struct GumboInternalParser* parser, size_t num_bytes); // Deallocate a chunk of memory, using the deallocator specified in the Parser's // config options. void gumbo_parser_deallocate(struct GumboInternalParser* parser, void* ptr); // Debug wrapper for printf, to make it easier to turn off debugging info when // required. void gumbo_debug(const char* format, ...); #ifdef __cplusplus } #endif #endif // GUMBO_UTIL_H_ ================================================ FILE: zhuishushenqi/Vendor/OCGumbo/gumbo/vector.c ================================================ // Copyright 2010 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // Author: jdtang@google.com (Jonathan Tang) #include "vector.h" #include <assert.h> #include <stdlib.h> #include <string.h> #include <strings.h> #include "util.h" struct GumboInternalParser; const GumboVector kGumboEmptyVector = {NULL, 0, 0}; void gumbo_vector_init(struct GumboInternalParser* parser, size_t initial_capacity, GumboVector* vector) { vector->length = 0; vector->capacity = initial_capacity; if (initial_capacity > 0) { vector->data = gumbo_parser_allocate(parser, sizeof(void*) * initial_capacity); } else { vector->data = NULL; } } void gumbo_vector_destroy( struct GumboInternalParser* parser, GumboVector* vector) { if (vector->capacity > 0) { gumbo_parser_deallocate(parser, vector->data); } } static void enlarge_vector_if_full( struct GumboInternalParser* parser, GumboVector* vector) { if (vector->length >= vector->capacity) { if (vector->capacity) { size_t old_num_bytes = sizeof(void*) * vector->capacity; vector->capacity *= 2; size_t num_bytes = sizeof(void*) * vector->capacity; void** temp = gumbo_parser_allocate(parser, num_bytes); memcpy(temp, vector->data, old_num_bytes); gumbo_parser_deallocate(parser, vector->data); vector->data = temp; } else { // 0-capacity vector; no previous array to deallocate. vector->capacity = 2; vector->data = gumbo_parser_allocate(parser, sizeof(void*) * vector->capacity); } } } void gumbo_vector_add( struct GumboInternalParser* parser, void* element, GumboVector* vector) { enlarge_vector_if_full(parser, vector); assert(vector->data); assert(vector->length < vector->capacity); vector->data[vector->length++] = element; } void* gumbo_vector_pop( struct GumboInternalParser* parser, GumboVector* vector) { if (vector->length == 0) { return NULL; } return vector->data[--vector->length]; } int gumbo_vector_index_of(GumboVector* vector, const void* element) { for (unsigned int i = 0; i < vector->length; ++i) { if (vector->data[i] == element) { return i; } } return -1; } void gumbo_vector_insert_at(struct GumboInternalParser* parser, void* element, unsigned int index, GumboVector* vector) { assert(index >= 0); assert(index <= vector->length); enlarge_vector_if_full(parser, vector); ++vector->length; memmove(&vector->data[index + 1], &vector->data[index], sizeof(void*) * (vector->length - index - 1)); vector->data[index] = element; } void gumbo_vector_remove( struct GumboInternalParser* parser, void* node, GumboVector* vector) { int index = gumbo_vector_index_of(vector, node); if (index == -1) { return; } gumbo_vector_remove_at(parser, index, vector); } void* gumbo_vector_remove_at(struct GumboInternalParser* parser, unsigned int index, GumboVector* vector) { assert(index >= 0); assert(index < vector->length); void* result = vector->data[index]; memmove(&vector->data[index], &vector->data[index + 1], sizeof(void*) * (vector->length - index - 1)); --vector->length; return result; } ================================================ FILE: zhuishushenqi/Vendor/OCGumbo/gumbo/vector.h ================================================ // Copyright 2010 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // Author: jdtang@google.com (Jonathan Tang) #ifndef GUMBO_VECTOR_H_ #define GUMBO_VECTOR_H_ #include "gumbo.h" #ifdef __cplusplus extern "C" { #endif // Forward declaration since it's passed into some of the functions in this // header. struct GumboInternalParser; // Initializes a new GumboVector with the specified initial capacity. void gumbo_vector_init(struct GumboInternalParser* parser, size_t initial_capacity, GumboVector* vector); // Frees the memory used by an GumboVector. Does not free the contained // pointers. void gumbo_vector_destroy( struct GumboInternalParser* parser, GumboVector* vector); // Adds a new element to an GumboVector. void gumbo_vector_add( struct GumboInternalParser* parser, void* element, GumboVector* vector); // Removes and returns the element most recently added to the GumboVector. // Ownership is transferred to caller. Capacity is unchanged. If the vector is // empty, NULL is returned. void* gumbo_vector_pop(struct GumboInternalParser* parser, GumboVector* vector); // Inserts an element at a specific index. This is potentially O(N) time, but // is necessary for some of the spec's behavior. void gumbo_vector_insert_at(struct GumboInternalParser* parser, void* element, unsigned int index, GumboVector* vector); // Removes an element from the vector, or does nothing if the element is not in // the vector. void gumbo_vector_remove( struct GumboInternalParser* parser, void* element, GumboVector* vector); // Removes and returns an element at a specific index. Note that this is // potentially O(N) time and should be used sparingly. void* gumbo_vector_remove_at(struct GumboInternalParser* parser, unsigned int index, GumboVector* vector); #ifdef __cplusplus } #endif #endif // GUMBO_VECTOR_H_ ================================================ FILE: zhuishushenqi/Vendor/PullToRefresh/QSPullToRefresh/QSPullToRefresh/DefaultRefreshView.swift ================================================ // // DefaultRefreshView.swift // PullToRefreshDemo // // Created by Serhii Butenko on 26/7/16. // Copyright © 2016 Yalantis. All rights reserved. // import UIKit class DefaultRefreshView: UIView { fileprivate(set) lazy var activityIndicator: UIActivityIndicatorView! = { let activityIndicator = UIActivityIndicatorView(activityIndicatorStyle: .gray) self.addSubview(activityIndicator) return activityIndicator }() fileprivate lazy var tipLabel:UILabel = { let label = UILabel() label.frame = CGRect(x: 0, y: self.bounds.height/2 - 21/2, width: self.bounds.width, height: 21) label.textColor = UIColor.darkGray label.font = UIFont.systemFont(ofSize: 13) label.textAlignment = .center return label }() init(frame: CGRect,tip:String) { super.init(frame: frame) tipLabel.text = tip self.addSubview(tipLabel) } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func layoutSubviews() { centerActivityIndicator() setupFrame(in: superview) super.layoutSubviews() } override func willMove(toSuperview newSuperview: UIView?) { super.willMove(toSuperview: newSuperview) centerActivityIndicator() setupFrame(in: superview) } } private extension DefaultRefreshView { func setupFrame(in newSuperview: UIView?) { guard let superview = newSuperview else { return } frame = CGRect(x: frame.origin.x, y: frame.origin.y, width: superview.frame.width, height: frame.height) } func centerActivityIndicator() { let width = widthOfString(tipLabel.text ?? "", font: UIFont.systemFont(ofSize: 13), height: 21) let qsCenter = CGPoint(x: self.center.x - width, y: self.center.y) tipLabel.frame = CGRect(x: 0, y: self.bounds.height/2 - 21/2, width: self.bounds.width, height: 21) activityIndicator.center = convert(qsCenter, from: superview) } func widthOfString(_ str:String, font:UIFont,height:CGFloat) ->CGFloat { let dict = [NSFontAttributeName:font] let sttt:NSString = str as NSString let rect:CGRect = sttt.boundingRect(with: CGSize(width: CGFloat(MAXFLOAT), height: CGFloat(height)), options: NSStringDrawingOptions.usesLineFragmentOrigin, attributes: dict, context: nil) return rect.size.width } } ================================================ FILE: zhuishushenqi/Vendor/PullToRefresh/QSPullToRefresh/QSPullToRefresh/DefaultViewAnimator.swift ================================================ // // DefaultViewAnimator.swift // PullToRefreshDemo // // Created by Serhii Butenko on 26/7/16. // Copyright © 2016 Yalantis. All rights reserved. // import Foundation class DefaultViewAnimator: RefreshViewAnimator { fileprivate let refreshView: DefaultRefreshView init(refreshView: DefaultRefreshView) { self.refreshView = refreshView } func animate(_ state: State) { switch state { case .initial: refreshView.activityIndicator.stopAnimating() case .releasing(let progress): refreshView.activityIndicator.isHidden = false var transform = CGAffineTransform.identity transform = transform.scaledBy(x: progress, y: progress) transform = transform.rotated(by: CGFloat(M_PI) * progress * 2) refreshView.activityIndicator.transform = transform case .loading: refreshView.activityIndicator.startAnimating() default: break } } } ================================================ FILE: zhuishushenqi/Vendor/PullToRefresh/QSPullToRefresh/QSPullToRefresh/Info.plist ================================================ <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>CFBundleDevelopmentRegion</key> <string>en</string> <key>CFBundleExecutable</key> <string>$(EXECUTABLE_NAME)</string> <key>CFBundleIdentifier</key> <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string> <key>CFBundleInfoDictionaryVersion</key> <string>6.0</string> <key>CFBundleName</key> <string>$(PRODUCT_NAME)</string> <key>CFBundlePackageType</key> <string>FMWK</string> <key>CFBundleShortVersionString</key> <string>1.0</string> <key>CFBundleVersion</key> <string>$(CURRENT_PROJECT_VERSION)</string> <key>NSPrincipalClass</key> <string></string> </dict> </plist> ================================================ FILE: zhuishushenqi/Vendor/PullToRefresh/QSPullToRefresh/QSPullToRefresh/PullToRefresh.swift ================================================ // // Created by Anastasiya Gorban on 4/14/15. // Copyright (c) 2015 Yalantis. All rights reserved. // // Licensed under the MIT license: http://opensource.org/licenses/MIT // Latest version can be found at https://github.com/Yalantis/PullToRefresh // import UIKit public enum Position { case top, bottom } open class PullToRefresh: NSObject { open var position: Position = .top open var animationDuration: TimeInterval = 1 open var hideDelay: TimeInterval = 0 open var springDamping: CGFloat = 0.4 open var initialSpringVelocity: CGFloat = 0.8 open var animationOptions: UIViewAnimationOptions = [.curveLinear] let refreshView: UIView var action: (() -> ())? fileprivate var isObserving = false fileprivate let animator: RefreshViewAnimator // MARK: - ScrollView & Observing fileprivate var scrollViewDefaultInsets: UIEdgeInsets = .zero weak var scrollView: UIScrollView? { willSet { removeScrollViewObserving() } didSet { if let scrollView = scrollView { scrollViewDefaultInsets = scrollView.contentInset addScrollViewObserving() } } } // MARK: - State open fileprivate(set) var state: State = .initial { didSet { animator.animate(state) switch state { case .loading: if oldValue != .loading { animateLoadingState() } case .finished: if isCurrentlyVisible() { animateFinishedState() } else { scrollView?.contentInset = self.scrollViewDefaultInsets state = .initial } default: break } } } // MARK: - Initialization public init(refreshView: UIView, animator: RefreshViewAnimator, height: CGFloat, position: Position) { self.refreshView = refreshView self.animator = animator self.position = position } public convenience init(height: CGFloat = 40, position: Position = .top,tip:String) { let refreshView = DefaultRefreshView(frame:CGRect(x: 0, y: 0, width: 0, height: 0) , tip: tip) refreshView.translatesAutoresizingMaskIntoConstraints = false refreshView.autoresizingMask = [.flexibleWidth] refreshView.frame.size.height = height self.init(refreshView: refreshView, animator: DefaultViewAnimator(refreshView: refreshView), height: height, position: position) } deinit { removeScrollViewObserving() } // MARK: KVO fileprivate var KVOContext = "PullToRefreshKVOContext" fileprivate let contentOffsetKeyPath = "contentOffset" fileprivate let contentInsetKeyPath = "contentInset" fileprivate let contentSizeKeyPath = "contentSize" fileprivate var previousScrollViewOffset: CGPoint = CGPoint.zero override open func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { if (context == &KVOContext && keyPath == contentOffsetKeyPath && object as? UIScrollView == scrollView) { var offset: CGFloat switch position { case .top: offset = previousScrollViewOffset.y + scrollViewDefaultInsets.top case .bottom: if scrollView!.contentSize.height > scrollView!.bounds.height { offset = scrollView!.contentSize.height - previousScrollViewOffset.y - scrollView!.bounds.height } else { offset = scrollView!.contentSize.height - previousScrollViewOffset.y } } let refreshViewHeight = refreshView.frame.size.height switch offset { case 0 where (state != .loading): state = .initial case -refreshViewHeight...0 where (state != .loading && state != .finished): state = .releasing(progress: -offset / refreshViewHeight) case -1000...(-refreshViewHeight): if state == .releasing(progress: 1) && scrollView?.isDragging == false { state = .loading } else if state != .loading && state != .finished { state = .releasing(progress: 1) } default: break } } else if (context == &KVOContext && keyPath == contentSizeKeyPath && object as? UIScrollView == scrollView) { if case .bottom = position { refreshView.frame = CGRect(x: 0, y: scrollView!.contentSize.height, width: scrollView!.bounds.width, height: refreshView.bounds.height) } } else if (context == &KVOContext && keyPath == contentInsetKeyPath && object as? UIScrollView == scrollView) { if self.state == .initial { scrollViewDefaultInsets = scrollView!.contentInset } } else { super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context) } previousScrollViewOffset.y = scrollView?.contentOffset.y ?? 0 } fileprivate func addScrollViewObserving() { guard let scrollView = scrollView, !isObserving else { return } scrollView.addObserver(self, forKeyPath: contentOffsetKeyPath, options: .initial, context: &KVOContext) scrollView.addObserver(self, forKeyPath: contentSizeKeyPath, options: .initial, context: &KVOContext) scrollView.addObserver(self, forKeyPath: contentInsetKeyPath, options: .new, context: &KVOContext) isObserving = true } fileprivate func removeScrollViewObserving() { guard let scrollView = scrollView, isObserving else { return } scrollView.removeObserver(self, forKeyPath: contentOffsetKeyPath, context: &KVOContext) scrollView.removeObserver(self, forKeyPath: contentSizeKeyPath, context: &KVOContext) scrollView.removeObserver(self, forKeyPath: contentInsetKeyPath, context: &KVOContext) isObserving = false } } // MARK: - Start/End Refreshin extension PullToRefresh { func startRefreshing() { if self.state != .initial { return } var offsetY: CGFloat switch position { case .top: offsetY = -refreshView.frame.height - scrollViewDefaultInsets.top case .bottom: offsetY = scrollView!.contentSize.height + refreshView.frame.height + scrollViewDefaultInsets.bottom - scrollView!.bounds.height } scrollView?.setContentOffset(CGPoint(x: 0, y: offsetY), animated: true) let delayTime = DispatchTime.now() + Double(Int64(0.27 * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC) DispatchQueue.main.asyncAfter(deadline: delayTime) { [weak self] in self?.state = .loading } } func endRefreshing() { if state == .loading { state = .finished } } } // MARK: - Animate scroll view private extension PullToRefresh { func animateLoadingState() { guard let scrollView = scrollView else { return } scrollView.contentOffset = previousScrollViewOffset scrollView.bounces = false UIView.animate( withDuration: 0.3, animations: { switch self.position { case .top: let insets = self.refreshView.frame.height + self.scrollViewDefaultInsets.top scrollView.contentInset.top = insets scrollView.contentOffset = CGPoint(x: scrollView.contentOffset.x, y: -insets) case .bottom: let insets = self.refreshView.frame.height + self.scrollViewDefaultInsets.bottom scrollView.contentInset.bottom = insets } }, completion: { _ in scrollView.bounces = true } ) action?() } func animateFinishedState() { removeScrollViewObserving() UIView.animate( withDuration: animationDuration, delay: hideDelay, usingSpringWithDamping: springDamping, initialSpringVelocity: initialSpringVelocity, options: animationOptions, animations: { self.scrollView?.contentInset = self.scrollViewDefaultInsets if case .top = self.position { self.scrollView?.contentOffset.y = -self.scrollViewDefaultInsets.top } }, completion: { _ in self.addScrollViewObserving() self.state = .initial } ) } } // MARK: - Helpers private extension PullToRefresh { func isCurrentlyVisible() -> Bool { guard let scrollView = scrollView else { return false } return scrollView.contentOffset.y <= -scrollViewDefaultInsets.top } } ================================================ FILE: zhuishushenqi/Vendor/PullToRefresh/QSPullToRefresh/QSPullToRefresh/QSPullToRefresh.h ================================================ // // QSPullToRefresh.h // QSPullToRefresh // // Created by Nory Cao on 2017/3/17. // Copyright © 2017年 QS. All rights reserved. // #import <UIKit/UIKit.h> //! Project version number for QSPullToRefresh. FOUNDATION_EXPORT double QSPullToRefreshVersionNumber; //! Project version string for QSPullToRefresh. FOUNDATION_EXPORT const unsigned char QSPullToRefreshVersionString[]; // In this header, you should import all the public headers of your framework using statements like #import <QSPullToRefresh/PublicHeader.h> ================================================ FILE: zhuishushenqi/Vendor/PullToRefresh/QSPullToRefresh/QSPullToRefresh/RefreshViewAnimator.swift ================================================ // // RefreshViewAnimator.swift // PullToRefreshDemo // // Created by Serhii Butenko on 26/7/16. // Copyright © 2016 Yalantis. All rights reserved. // import Foundation public protocol RefreshViewAnimator { func animate(_ state: State) } ================================================ FILE: zhuishushenqi/Vendor/PullToRefresh/QSPullToRefresh/QSPullToRefresh/State.swift ================================================ // // State.swift // PullToRefreshDemo // // Created by Serhii Butenko on 26/7/16. // Copyright © 2016 Yalantis. All rights reserved. // import Foundation public enum State: Equatable, CustomStringConvertible { case initial case releasing(progress: CGFloat) case loading case finished public var description: String { switch self { case .initial: return "Inital" case .releasing(let progress): return "Releasing:\(progress)" case .loading: return "Loading" case .finished: return "Finished" } } } public func ==(a: State, b: State) -> Bool { switch (a, b) { case (.initial, .initial): return true case (.loading, .loading): return true case (.finished, .finished): return true case (.releasing, .releasing): return true default: return false } } public typealias PullToRefreshState = State ================================================ FILE: zhuishushenqi/Vendor/PullToRefresh/QSPullToRefresh/QSPullToRefresh/UIScrollView+PullToRefresh.swift ================================================ // // Created by Anastasiya Gorban on 4/14/15. // Copyright (c) 2015 Yalantis. All rights reserved. // // Licensed under the MIT license: http://opensource.org/licenses/MIT // Latest version can be found at https://github.com/Yalantis/PullToRefresh // import Foundation import UIKit import ObjectiveC private var topPullToRefreshKey: UInt8 = 0 private var bottomPullToRefreshKey: UInt8 = 0 public extension UIScrollView { fileprivate(set) var topPullToRefresh: PullToRefresh? { get { return objc_getAssociatedObject(self, &topPullToRefreshKey) as? PullToRefresh } set { objc_setAssociatedObject(self, &topPullToRefreshKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } } fileprivate(set) var bottomPullToRefresh: PullToRefresh? { get { return objc_getAssociatedObject(self, &bottomPullToRefreshKey) as? PullToRefresh } set { objc_setAssociatedObject(self, &bottomPullToRefreshKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } } public func addPullToRefresh(_ pullToRefresh: PullToRefresh, action: @escaping () -> ()) { pullToRefresh.scrollView = self pullToRefresh.action = action var originY: CGFloat let view = pullToRefresh.refreshView switch pullToRefresh.position { case .top: removePullToRefresh(at: .top) topPullToRefresh = pullToRefresh originY = -view.frame.size.height case .bottom: removePullToRefresh(at: .bottom) bottomPullToRefresh = pullToRefresh originY = contentSize.height } view.frame = CGRect(x: 0, y: originY, width: frame.width, height: view.frame.height) addSubview(view) sendSubview(toBack: view) } func removePullToRefresh(at position: Position) { switch position { case .top: topPullToRefresh?.refreshView.removeFromSuperview() topPullToRefresh = nil case .bottom: bottomPullToRefresh?.refreshView.removeFromSuperview() bottomPullToRefresh = nil } } func removeAllPullToRefresh() { removePullToRefresh(at: .top) removePullToRefresh(at: .bottom) } func startRefreshing(at position: Position) { switch position { case .top: topPullToRefresh?.startRefreshing() case .bottom: bottomPullToRefresh?.startRefreshing() } } func endRefreshing(at position: Position) { switch position { case .top: topPullToRefresh?.endRefreshing() case .bottom: bottomPullToRefresh?.endRefreshing() } } func endAllRefreshing() { endRefreshing(at: .top) endRefreshing(at: .bottom) } } ================================================ FILE: zhuishushenqi/Vendor/PullToRefresh/QSPullToRefresh/QSPullToRefresh.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 46; objects = { /* Begin PBXBuildFile section */ B2C64AAF1E7BC6AF00AA29F6 /* QSPullToRefresh.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B2C64AA51E7BC6AF00AA29F6 /* QSPullToRefresh.framework */; }; B2C64AB41E7BC6AF00AA29F6 /* QSPullToRefreshTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C64AB31E7BC6AF00AA29F6 /* QSPullToRefreshTests.swift */; }; B2C64AB61E7BC6AF00AA29F6 /* QSPullToRefresh.h in Headers */ = {isa = PBXBuildFile; fileRef = B2C64AA81E7BC6AF00AA29F6 /* QSPullToRefresh.h */; settings = {ATTRIBUTES = (Public, ); }; }; B2C64AC51E7BC6BE00AA29F6 /* DefaultRefreshView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C64ABF1E7BC6BE00AA29F6 /* DefaultRefreshView.swift */; }; B2C64AC61E7BC6BE00AA29F6 /* DefaultViewAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C64AC01E7BC6BE00AA29F6 /* DefaultViewAnimator.swift */; }; B2C64AC71E7BC6BE00AA29F6 /* PullToRefresh.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C64AC11E7BC6BE00AA29F6 /* PullToRefresh.swift */; }; B2C64AC81E7BC6BE00AA29F6 /* RefreshViewAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C64AC21E7BC6BE00AA29F6 /* RefreshViewAnimator.swift */; }; B2C64AC91E7BC6BE00AA29F6 /* State.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C64AC31E7BC6BE00AA29F6 /* State.swift */; }; B2C64ACA1E7BC6BE00AA29F6 /* UIScrollView+PullToRefresh.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C64AC41E7BC6BE00AA29F6 /* UIScrollView+PullToRefresh.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ B2C64AB01E7BC6AF00AA29F6 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = B2C64A9C1E7BC6AF00AA29F6 /* Project object */; proxyType = 1; remoteGlobalIDString = B2C64AA41E7BC6AF00AA29F6; remoteInfo = QSPullToRefresh; }; /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ B2C64AA51E7BC6AF00AA29F6 /* QSPullToRefresh.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = QSPullToRefresh.framework; sourceTree = BUILT_PRODUCTS_DIR; }; B2C64AA81E7BC6AF00AA29F6 /* QSPullToRefresh.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = QSPullToRefresh.h; sourceTree = "<group>"; }; B2C64AA91E7BC6AF00AA29F6 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; B2C64AAE1E7BC6AF00AA29F6 /* QSPullToRefreshTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = QSPullToRefreshTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; B2C64AB31E7BC6AF00AA29F6 /* QSPullToRefreshTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QSPullToRefreshTests.swift; sourceTree = "<group>"; }; B2C64AB51E7BC6AF00AA29F6 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; B2C64ABF1E7BC6BE00AA29F6 /* DefaultRefreshView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DefaultRefreshView.swift; sourceTree = "<group>"; }; B2C64AC01E7BC6BE00AA29F6 /* DefaultViewAnimator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DefaultViewAnimator.swift; sourceTree = "<group>"; }; B2C64AC11E7BC6BE00AA29F6 /* PullToRefresh.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PullToRefresh.swift; sourceTree = "<group>"; }; B2C64AC21E7BC6BE00AA29F6 /* RefreshViewAnimator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RefreshViewAnimator.swift; sourceTree = "<group>"; }; B2C64AC31E7BC6BE00AA29F6 /* State.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = State.swift; sourceTree = "<group>"; }; B2C64AC41E7BC6BE00AA29F6 /* UIScrollView+PullToRefresh.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIScrollView+PullToRefresh.swift"; sourceTree = "<group>"; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ B2C64AA11E7BC6AF00AA29F6 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; B2C64AAB1E7BC6AF00AA29F6 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( B2C64AAF1E7BC6AF00AA29F6 /* QSPullToRefresh.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ B2C64A9B1E7BC6AF00AA29F6 = { isa = PBXGroup; children = ( B2C64AA71E7BC6AF00AA29F6 /* QSPullToRefresh */, B2C64AB21E7BC6AF00AA29F6 /* QSPullToRefreshTests */, B2C64AA61E7BC6AF00AA29F6 /* Products */, ); sourceTree = "<group>"; }; B2C64AA61E7BC6AF00AA29F6 /* Products */ = { isa = PBXGroup; children = ( B2C64AA51E7BC6AF00AA29F6 /* QSPullToRefresh.framework */, B2C64AAE1E7BC6AF00AA29F6 /* QSPullToRefreshTests.xctest */, ); name = Products; sourceTree = "<group>"; }; B2C64AA71E7BC6AF00AA29F6 /* QSPullToRefresh */ = { isa = PBXGroup; children = ( B2C64ABF1E7BC6BE00AA29F6 /* DefaultRefreshView.swift */, B2C64AC01E7BC6BE00AA29F6 /* DefaultViewAnimator.swift */, B2C64AC11E7BC6BE00AA29F6 /* PullToRefresh.swift */, B2C64AC21E7BC6BE00AA29F6 /* RefreshViewAnimator.swift */, B2C64AC31E7BC6BE00AA29F6 /* State.swift */, B2C64AC41E7BC6BE00AA29F6 /* UIScrollView+PullToRefresh.swift */, B2C64AA81E7BC6AF00AA29F6 /* QSPullToRefresh.h */, B2C64AA91E7BC6AF00AA29F6 /* Info.plist */, ); path = QSPullToRefresh; sourceTree = "<group>"; }; B2C64AB21E7BC6AF00AA29F6 /* QSPullToRefreshTests */ = { isa = PBXGroup; children = ( B2C64AB31E7BC6AF00AA29F6 /* QSPullToRefreshTests.swift */, B2C64AB51E7BC6AF00AA29F6 /* Info.plist */, ); path = QSPullToRefreshTests; sourceTree = "<group>"; }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ B2C64AA21E7BC6AF00AA29F6 /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( B2C64AB61E7BC6AF00AA29F6 /* QSPullToRefresh.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ B2C64AA41E7BC6AF00AA29F6 /* QSPullToRefresh */ = { isa = PBXNativeTarget; buildConfigurationList = B2C64AB91E7BC6AF00AA29F6 /* Build configuration list for PBXNativeTarget "QSPullToRefresh" */; buildPhases = ( B2C64AA01E7BC6AF00AA29F6 /* Sources */, B2C64AA11E7BC6AF00AA29F6 /* Frameworks */, B2C64AA21E7BC6AF00AA29F6 /* Headers */, B2C64AA31E7BC6AF00AA29F6 /* Resources */, ); buildRules = ( ); dependencies = ( ); name = QSPullToRefresh; productName = QSPullToRefresh; productReference = B2C64AA51E7BC6AF00AA29F6 /* QSPullToRefresh.framework */; productType = "com.apple.product-type.framework"; }; B2C64AAD1E7BC6AF00AA29F6 /* QSPullToRefreshTests */ = { isa = PBXNativeTarget; buildConfigurationList = B2C64ABC1E7BC6AF00AA29F6 /* Build configuration list for PBXNativeTarget "QSPullToRefreshTests" */; buildPhases = ( B2C64AAA1E7BC6AF00AA29F6 /* Sources */, B2C64AAB1E7BC6AF00AA29F6 /* Frameworks */, B2C64AAC1E7BC6AF00AA29F6 /* Resources */, ); buildRules = ( ); dependencies = ( B2C64AB11E7BC6AF00AA29F6 /* PBXTargetDependency */, ); name = QSPullToRefreshTests; productName = QSPullToRefreshTests; productReference = B2C64AAE1E7BC6AF00AA29F6 /* QSPullToRefreshTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ B2C64A9C1E7BC6AF00AA29F6 /* Project object */ = { isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0810; LastUpgradeCheck = 0810; ORGANIZATIONNAME = masterY; TargetAttributes = { B2C64AA41E7BC6AF00AA29F6 = { CreatedOnToolsVersion = 8.1; DevelopmentTeam = 8K684WSCKB; LastSwiftMigration = 0810; ProvisioningStyle = Automatic; }; B2C64AAD1E7BC6AF00AA29F6 = { CreatedOnToolsVersion = 8.1; DevelopmentTeam = 8K684WSCKB; ProvisioningStyle = Automatic; }; }; }; buildConfigurationList = B2C64A9F1E7BC6AF00AA29F6 /* Build configuration list for PBXProject "QSPullToRefresh" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( en, ); mainGroup = B2C64A9B1E7BC6AF00AA29F6; productRefGroup = B2C64AA61E7BC6AF00AA29F6 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( B2C64AA41E7BC6AF00AA29F6 /* QSPullToRefresh */, B2C64AAD1E7BC6AF00AA29F6 /* QSPullToRefreshTests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ B2C64AA31E7BC6AF00AA29F6 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; B2C64AAC1E7BC6AF00AA29F6 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ B2C64AA01E7BC6AF00AA29F6 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( B2C64ACA1E7BC6BE00AA29F6 /* UIScrollView+PullToRefresh.swift in Sources */, B2C64AC51E7BC6BE00AA29F6 /* DefaultRefreshView.swift in Sources */, B2C64AC81E7BC6BE00AA29F6 /* RefreshViewAnimator.swift in Sources */, B2C64AC71E7BC6BE00AA29F6 /* PullToRefresh.swift in Sources */, B2C64AC91E7BC6BE00AA29F6 /* State.swift in Sources */, B2C64AC61E7BC6BE00AA29F6 /* DefaultViewAnimator.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; B2C64AAA1E7BC6AF00AA29F6 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( B2C64AB41E7BC6AF00AA29F6 /* QSPullToRefreshTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ B2C64AB11E7BC6AF00AA29F6 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = B2C64AA41E7BC6AF00AA29F6 /* QSPullToRefresh */; targetProxy = B2C64AB01E7BC6AF00AA29F6 /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ B2C64AB71E7BC6AF00AA29F6 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_SUSPICIOUS_MOVES = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 10.1; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Debug; }; B2C64AB81E7BC6AF00AA29F6 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_SUSPICIOUS_MOVES = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 10.1; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Release; }; B2C64ABA1E7BC6AF00AA29F6 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 8K684WSCKB; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = QSPullToRefresh/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = QS.QSPullToRefresh; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 3.0; }; name = Debug; }; B2C64ABB1E7BC6AF00AA29F6 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 8K684WSCKB; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = QSPullToRefresh/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = QS.QSPullToRefresh; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; SWIFT_VERSION = 3.0; }; name = Release; }; B2C64ABD1E7BC6AF00AA29F6 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; DEVELOPMENT_TEAM = 8K684WSCKB; INFOPLIST_FILE = QSPullToRefreshTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = QS.QSPullToRefreshTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 3.0; }; name = Debug; }; B2C64ABE1E7BC6AF00AA29F6 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; DEVELOPMENT_TEAM = 8K684WSCKB; INFOPLIST_FILE = QSPullToRefreshTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = QS.QSPullToRefreshTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 3.0; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ B2C64A9F1E7BC6AF00AA29F6 /* Build configuration list for PBXProject "QSPullToRefresh" */ = { isa = XCConfigurationList; buildConfigurations = ( B2C64AB71E7BC6AF00AA29F6 /* Debug */, B2C64AB81E7BC6AF00AA29F6 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; B2C64AB91E7BC6AF00AA29F6 /* Build configuration list for PBXNativeTarget "QSPullToRefresh" */ = { isa = XCConfigurationList; buildConfigurations = ( B2C64ABA1E7BC6AF00AA29F6 /* Debug */, B2C64ABB1E7BC6AF00AA29F6 /* Release */, ); defaultConfigurationIsVisible = 0; }; B2C64ABC1E7BC6AF00AA29F6 /* Build configuration list for PBXNativeTarget "QSPullToRefreshTests" */ = { isa = XCConfigurationList; buildConfigurations = ( B2C64ABD1E7BC6AF00AA29F6 /* Debug */, B2C64ABE1E7BC6AF00AA29F6 /* Release */, ); defaultConfigurationIsVisible = 0; }; /* End XCConfigurationList section */ }; rootObject = B2C64A9C1E7BC6AF00AA29F6 /* Project object */; } ================================================ FILE: zhuishushenqi/Vendor/PullToRefresh/QSPullToRefresh/QSPullToRefresh.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ <?xml version="1.0" encoding="UTF-8"?> <Workspace version = "1.0"> <FileRef location = "self:QSPullToRefresh.xcodeproj"> </FileRef> </Workspace> ================================================ FILE: zhuishushenqi/Vendor/PullToRefresh/QSPullToRefresh/QSPullToRefreshTests/Info.plist ================================================ <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>CFBundleDevelopmentRegion</key> <string>en</string> <key>CFBundleExecutable</key> <string>$(EXECUTABLE_NAME)</string> <key>CFBundleIdentifier</key> <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string> <key>CFBundleInfoDictionaryVersion</key> <string>6.0</string> <key>CFBundleName</key> <string>$(PRODUCT_NAME)</string> <key>CFBundlePackageType</key> <string>BNDL</string> <key>CFBundleShortVersionString</key> <string>1.0</string> <key>CFBundleVersion</key> <string>1</string> </dict> </plist> ================================================ FILE: zhuishushenqi/Vendor/PullToRefresh/QSPullToRefresh/QSPullToRefreshTests/QSPullToRefreshTests.swift ================================================ // // QSPullToRefreshTests.swift // QSPullToRefreshTests // // Created by Nory Cao on 2017/3/17. // Copyright © 2017年 QS. All rights reserved. // import XCTest @testable import QSPullToRefresh class QSPullToRefreshTests: XCTestCase { override func setUp() { super.setUp() // Put setup code here. This method is called before the invocation of each test method in the class. } override func tearDown() { // Put teardown code here. This method is called after the invocation of each test method in the class. super.tearDown() } func testExample() { // This is an example of a functional test case. // Use XCTAssert and related functions to verify your tests produce the correct results. } func testPerformanceExample() { // This is an example of a performance test case. self.measure { // Put the code you want to measure the time of here. } } } ================================================ FILE: zhuishushenqi/Vendor/ThirdLoginSDK/TencentOpenAPI.framework/Headers/QQApiInterface.h ================================================ /// /// \file QQApiInterface.h /// \brief QQApi接口简化封装 /// /// Created by Tencent on 12-5-15. /// Copyright (c) 2012年 Tencent. All rights reserved. /// #import <Foundation/Foundation.h> #import "QQApiInterfaceObject.h" /** \brief 处理来至QQ的请求及响应的回调协议 */ @protocol QQApiInterfaceDelegate <NSObject> /** 处理来至QQ的请求 */ - (void)onReq:(QQBaseReq *)req; /** 处理来至QQ的响应 */ - (void)onResp:(QQBaseResp *)resp; /** 处理QQ在线状态的回调 */ - (void)isOnlineResponse:(NSDictionary *)response; @end /** \brief 对QQApi的简单封装类 */ @interface QQApiInterface : NSObject /** 处理由手Q唤起的跳转请求 \param url 待处理的url跳转请求 \param delegate 第三方应用用于处理来至QQ请求及响应的委托对象 \return 跳转请求处理结果,YES表示成功处理,NO表示不支持的请求协议或处理失败 */ + (BOOL)handleOpenURL:(NSURL *)url delegate:(id<QQApiInterfaceDelegate>)delegate; /** 向手Q发起分享请求 \param req 分享内容的请求 \return 请求发送结果码 */ + (QQApiSendResultCode)sendReq:(QQBaseReq *)req; /** 向手Q QZone结合版发起分享请求 \note H5分享只支持单张网络图片的传递 \param req 分享内容的请求 \return 请求发送结果码 */ + (QQApiSendResultCode)SendReqToQZone:(QQBaseReq *)req; /** 检测是否已安装QQ \return 如果QQ已安装则返回YES,否则返回NO \note SDK目前已经支持QQ、TIM授权登录及分享功能, 会按照QQ>TIM的顺序进行调用。 只要用户安装了QQ、TIM中任意一个应用,都可为第三方应用进行授权登录、分享功能。 第三方应用在接入SDK时不需要判断是否安装QQ、TIM。若有判断安装QQ、TIM的逻辑建议移除。 */ + (BOOL)isQQInstalled; /** 检测是否已安装TIM \return 如果TIM已安装则返回YES,否则返回NO \note SDK目前已经支持QQ、TIM授权登录及分享功能, 会按照QQ>TIM的顺序进行调用。 只要用户安装了QQ、TIM中任意一个应用,都可为第三方应用进行授权登录、分享功能。 第三方应用在接入SDK时不需要判断是否安装QQ、TIM。若有判断安装QQ、TIM的逻辑建议移除。 */ + (BOOL)isTIMInstalled; /** 检测QQ是否支持API调用 \return 如果当前安装QQ版本支持API调用则返回YES,否则返回NO */ + (BOOL)isQQSupportApi; /** 检测TIM是否支持API调用 \return 如果当前安装TIM版本支持API调用则返回YES,否则返回NO */ + (BOOL)isTIMSupportApi; /** 检测是否支持分享 \return 如果当前已安装QQ且QQ版本支持API调用 或者 当前已安装TIM且TIM版本支持API调用则返回YES,否则返回NO */ + (BOOL)isSupportShareToQQ; /** 检测是否支持分享到QQ结合版QZone \return 如果当前已安装QQ且QQ版本支持API调用则返回YES,否则返回NO */ + (BOOL)isSupportPushToQZone; /** 启动QQ \return 成功返回YES,否则返回NO */ + (BOOL)openQQ; /** 启动TIM \return 成功返回YES,否则返回NO */ + (BOOL)openTIM; /** 获取QQ下载地址 如果App通过<code>QQApiInterface#isQQInstalled</code>和<code>QQApiInterface#isQQSupportApi</code>检测发现QQ没安装或当前版本QQ不支持API调用,可引导用户通过打开此链接下载最新版QQ。 \return iPhoneQQ下载地址 */ + (NSString *)getQQInstallUrl; /** 获取TIM下载地址 如果App通过<code>QQApiInterface#isTIMInstalled</code>和<code>QQApiInterface#isTIMSupportApi</code>检测发现TIM没安装或当前版本TIM不支持API调用,可引导用户通过打开此链接下载最新版TIM。 \return iPhoneTIM下载地址 */ + (NSString *)getTIMInstallUrl; @end ================================================ FILE: zhuishushenqi/Vendor/ThirdLoginSDK/TencentOpenAPI.framework/Headers/QQApiInterfaceObject.h ================================================ /// /// \file QQApiInterfaceObject.h /// \brief QQApiInterface所依赖的请求及应答消息对象封装帮助类 /// /// Created by Tencent on 12-5-15. /// Copyright (c) 2012年 Tencent. All rights reserved. /// #ifndef QQApiInterface_QQAPIOBJECT_h #define QQApiInterface_QQAPIOBJECT_h #import <Foundation/Foundation.h> typedef enum { EQQAPISENDSUCESS = 0, EQQAPIQQNOTINSTALLED = 1, //QQ未安装 EQQAPIQQNOTSUPPORTAPI = 2, // QQ api不支持 EQQAPIMESSAGETYPEINVALID = 3, EQQAPIMESSAGECONTENTNULL = 4, EQQAPIMESSAGECONTENTINVALID = 5, EQQAPIAPPNOTREGISTED = 6, EQQAPIAPPSHAREASYNC = 7, EQQAPIQQNOTSUPPORTAPI_WITH_ERRORSHOW = 8, //QQ api不支持 && SDK显示error提示(已废弃) EQQAPIMESSAGEARKCONTENTNULL = 9, //ark内容为空 EQQAPISENDFAILD = -1, //发送失败 EQQAPISHAREDESTUNKNOWN = -2, //未指定分享到QQ或TIM EQQAPITIMSENDFAILD = -3, //发送失败 EQQAPITIMNOTINSTALLED = 11, //TIM未安装 EQQAPITIMNOTSUPPORTAPI = 12, // TIM api不支持 EQQAPIQZONENOTSUPPORTTEXT = 10000, //qzone分享不支持text类型分享 EQQAPIQZONENOTSUPPORTIMAGE = 10001, //qzone分享不支持image类型分享 EQQAPIVERSIONNEEDUPDATE = 10002, //当前QQ版本太低,需要更新至新版本才可以支持 ETIMAPIVERSIONNEEDUPDATE = 10004, //当前TIM版本太低,需要更新至新版本才可以支持 } QQApiSendResultCode; #pragma mark - QQApiObject(分享对象类型) // QQApiObject control flags enum { kQQAPICtrlFlagQZoneShareOnStart = 0x01, kQQAPICtrlFlagQZoneShareForbid = 0x02, kQQAPICtrlFlagQQShare = 0x04, kQQAPICtrlFlagQQShareFavorites = 0x08, //收藏 kQQAPICtrlFlagQQShareDataline = 0x10, //数据线 kQQAPICtrlFlagQQShareEnableArk = 0x20, //支持ARK }; // 分享到QQ或TIM typedef enum ShareDestType { ShareDestTypeQQ = 0, ShareDestTypeTIM, }ShareDestType; // QQApiObject /** \brief 所有在QQ及插件间发送的数据对象的根类。 */ __attribute__((visibility("default"))) @interface QQApiObject : NSObject @property(nonatomic,retain) NSString* title; ///< 标题,最长128个字符 @property(nonatomic,retain) NSString* description; ///<简要描述,最长512个字符 @property (nonatomic, assign) uint64_t cflag; /* * 分享到QQ/TIM * SDK根据是否安装对应客户端进行判断,判断顺序:QQ > TIM * 默认分享到QQ,如果QQ未安装检测TIM是否安装 */ @property (nonatomic, assign) ShareDestType shareDestType; @end // ArkObject /** \brief 支持Ark的根类。 */ __attribute__((visibility("default"))) @interface ArkObject : NSObject @property(nonatomic,retain) NSString* arkData; ///< 显示Ark所需的数据,json串,长度暂不限制 @property(nonatomic,assign) QQApiObject* qqApiObject; ///<原有老版本的QQApiObject - (id)initWithData:(NSString *)arkData qqApiObject:(QQApiObject*)qqApiObject; + (id)objectWithData:(NSString *)arkData qqApiObject:(QQApiObject*)qqApiObject; @end // QQApiResultObject /** \brief 用于请求回应的数据类型。 <h3>可能错误码及描述如下:</h3> <TABLE> <TR><TD>error</TD><TD>errorDescription</TD><TD>注释</TD></TR> <TR><TD>0</TD><TD>nil</TD><TD>成功</TD></TR> <TR><TD>-1</TD><TD>param error</TD><TD>参数错误</TD></TR> <TR><TD>-2</TD><TD>group code is invalid</TD><TD>该群不在自己的群列表里面</TD></TR> <TR><TD>-3</TD><TD>upload photo failed</TD><TD>上传图片失败</TD></TR> <TR><TD>-4</TD><TD>user give up the current operation</TD><TD>用户放弃当前操作</TD></TR> <TR><TD>-5</TD><TD>client internal error</TD><TD>客户端内部处理错误</TD></TR> </TABLE> */ __attribute__((visibility("default"))) @interface QQApiResultObject : QQApiObject @property(nonatomic,retain) NSString* error; ///<错误 @property(nonatomic,retain) NSString* errorDescription; ///<错误描述 @property(nonatomic,retain) NSString* extendInfo; ///<扩展信息 @end // QQApiTextObject /** \brief 文本对象 */ @interface QQApiTextObject : QQApiObject @property(nonatomic,retain)NSString* text; ///<文本内容,必填,最长1536个字符 -(id)initWithText:(NSString*)text; ///<初始化方法 +(id)objectWithText:(NSString*)text;///<工厂方法,获取一个QQApiTextObject对象. @end // QQApiURLObject typedef enum QQApiURLTargetType{ QQApiURLTargetTypeNotSpecified = 0x00, QQApiURLTargetTypeAudio = 0x01, QQApiURLTargetTypeVideo = 0x02, QQApiURLTargetTypeNews = 0x03 }QQApiURLTargetType; /** @brief URL对象类型。 包括URL地址,URL地址所指向的目标类型及预览图像。 */ __attribute__((visibility("default"))) @interface QQApiURLObject : QQApiObject /** URL地址所指向的目标类型. @note 参见QQApi.h 中的 QQApiURLTargetType 定义. */ @property(nonatomic)QQApiURLTargetType targetContentType; @property(nonatomic,retain)NSURL* url; ///<URL地址,必填,最长512个字符 @property(nonatomic,retain)NSData* previewImageData;///<预览图像数据,最大1M字节 @property(nonatomic, retain) NSURL *previewImageURL; ///<预览图像URL **预览图像数据与预览图像URL可二选一 /** 初始化方法 */ -(id)initWithURL:(NSURL*)url title:(NSString*)title description:(NSString*)description previewImageData:(NSData*)data targetContentType:(QQApiURLTargetType)targetContentType; -(id)initWithURL:(NSURL*)url title:(NSString*)title description:(NSString*)description previewImageURL:(NSURL*)previewURL targetContentType:(QQApiURLTargetType)targetContentType; /** 工厂方法,获取一个QQApiURLObject对象 */ +(id)objectWithURL:(NSURL*)url title:(NSString*)title description:(NSString*)description previewImageData:(NSData*)data targetContentType:(QQApiURLTargetType)targetContentType; +(id)objectWithURL:(NSURL*)url title:(NSString*)title description:(NSString*)description previewImageURL:(NSURL*)previewURL targetContentType:(QQApiURLTargetType)targetContentType; @end // QQApiExtendObject /** @brief 扩展数据类型 */ @interface QQApiExtendObject : QQApiObject @property(nonatomic,retain) NSData* data;///<具体数据内容,必填,最大5M字节 @property(nonatomic,retain) NSData* previewImageData;///<预览图像,最大1M字节 @property(nonatomic,retain) NSArray* imageDataArray;///图片数组(多图暂只支持分享到手机QQ收藏功能) /** 初始化方法 @param data 数据内容 @param previewImageData 用于预览的图片 @param title 标题 @param description 此对象,分享的描述 */ - (id)initWithData:(NSData*)data previewImageData:(NSData*)previewImageData title:(NSString*)title description:(NSString*)description; /** 初始化方法 @param data 数据内容 @param title 标题 @param description 此对象,分享的描述 @param imageDataArray 发送的多张图片队列 */ - (id)initWithData:(NSData *)data previewImageData:(NSData*)previewImageData title:(NSString *)title description:(NSString *)description imageDataArray:(NSArray *)imageDataArray; /** helper方法获取一个autorelease的<code>QQApiExtendObject</code>对象 @param data 数据内容 @param previewImageData 用于预览的图片 @param title 标题 @param description 此对象,分享的描述 @return 一个自动释放的<code>QQApiExtendObject</code>实例 */ + (id)objectWithData:(NSData*)data previewImageData:(NSData*)previewImageData title:(NSString*)title description:(NSString*)description; /** helper方法获取一个autorelease的<code>QQApiExtendObject</code>对象 @param data 数据内容 @param previewImageData 用于预览的图片 @param title 标题 @param description 此对象,分享的描述 @param imageDataArray 发送的多张图片队列 @return 一个自动释放的<code>QQApiExtendObject</code>实例 */ + (id)objectWithData:(NSData*)data previewImageData:(NSData*)previewImageData title:(NSString*)title description:(NSString*)description imageDataArray:(NSArray*)imageDataArray; @end // QQApiImageObject /** @brief 图片对象 用于分享图片内容的对象,是一个指定为图片类型的<code>QQApiExtendObject</code> */ @interface QQApiImageObject : QQApiExtendObject @end // QQApiImageArrayForQZoneObject /** @brief 图片对象 用于分享图片到空间,走写说说路径,是一个指定为图片类型的,当图片数组为空时,默认走文本写说说<code>QQApiObject</code> */ @interface QQApiImageArrayForQZoneObject : QQApiObject @property(nonatomic,retain) NSArray* imageDataArray;///图片数组 @property(nonatomic,retain) NSDictionary* extMap; // 扩展字段 /** 初始化方法 @param imageDataArray 图片数组 @param title 写说说的内容,可以为空 @param extMap 扩展字段 */ - (id)initWithImageArrayData:(NSArray*)imageDataArray title:(NSString*)title extMap:(NSDictionary *)extMap; /** helper方法获取一个autorelease的<code>QQApiExtendObject</code>对象 @param title 写说说的内容,可以为空 @param imageDataArray 发送的多张图片队列 @param extMap 扩展字段 @return 一个自动释放的<code>QQApiExtendObject</code>实例 */ + (id)objectWithimageDataArray:(NSArray*)imageDataArray title:(NSString*)title extMap:(NSDictionary *)extMap; @end // QQApiVideoForQZoneObject /** @brief 视频对象 用于分享视频到空间,走写说说路径<code>QQApiObject</code> assetURL可传ALAsset的ALAssetPropertyAssetURL,或者PHAsset的localIdentifier @param extMap 扩展字段 */ @interface QQApiVideoForQZoneObject : QQApiObject @property(nonatomic, retain) NSString *assetURL; @property(nonatomic,retain) NSDictionary* extMap; // 扩展字段 - (id)initWithAssetURL:(NSString*)assetURL title:(NSString*)title extMap:(NSDictionary *)extMap; + (id)objectWithAssetURL:(NSString*)assetURL title:(NSString*)title extMap:(NSDictionary *)extMap; @end // QQApiWebImageObject /** @brief 图片对象 用于分享网络图片内容的对象,是一个指定网络图片url的: 该类型只在2.9.0的h5分享中才支持, 原有的手q分享是不支持该类型的。 */ @interface QQApiWebImageObject : QQApiObject @property(nonatomic, retain) NSURL *previewImageURL; ///<预览图像URL /** 初始化方法 @param previewImageURL 用于预览的图片 @param title 标题 @param description 此对象,分享的描述 */ - (id)initWithPreviewImageURL:(NSURL*)previewImageURL title:(NSString*)title description:(NSString*)description; /** helper方法获取一个autorelease的<code>QQApiWebImageObject</code>对象 @param previewImageURL 用于预览的图片 @param title 标题 @param description 此对象,分享的描述 */ + (id)objectWithPreviewImageURL:(NSURL*)previewImageURL title:(NSString*)title description:(NSString*)description; @end //QQApiFileObject /** @brief 本地文件对象(暂只支持分享到手机QQ数据线功能) 用于分享文件内容的对象,是一个指定为文件类型的<code>QQApiExtendObject</code> */ @interface QQApiFileObject : QQApiExtendObject { NSString* _fileName; } @property(nonatomic, retain)NSString* fileName; @end // QQApiAudioObject /** @brief 音频URL对象 用于分享目标内容为音频的URL的对象 */ @interface QQApiAudioObject : QQApiURLObject @property (nonatomic, retain) NSURL *flashURL; ///<音频URL地址,最长512个字符 /** 获取一个autorelease的<code>QQApiAudioObject</code> @param url 音频内容的目标URL @param title 分享内容的标题 @param description 分享内容的描述 @param data 分享内容的预览图像 @note 如果url为空,调用<code>QQApi#sendMessage:</code>时将返回FALSE */ +(id)objectWithURL:(NSURL*)url title:(NSString*)title description:(NSString*)description previewImageData:(NSData*)data; /** 获取一个autorelease的<code>QQApiAudioObject</code> @param url 音频内容的目标URL @param title 分享内容的标题 @param description 分享内容的描述 @param previewURL 分享内容的预览图像URL @note 如果url为空,调用<code>QQApi#sendMessage:</code>时将返回FALSE */ +(id)objectWithURL:(NSURL*)url title:(NSString*)title description:(NSString*)description previewImageURL:(NSURL*)previewURL; @end // QQApiVideoObject /** @brief 视频URL对象 用于分享目标内容为视频的URL的对象 QQApiVideoObject类型的分享,目前在Android和PC QQ上接收消息时,展现有待完善,待手机QQ版本以后更新支持 目前如果要分享视频,推荐使用 QQApiNewsObject 类型 */ @interface QQApiVideoObject : QQApiURLObject @property (nonatomic, retain) NSURL *flashURL; ///<视频URL地址,最长512个字符 /** 获取一个autorelease的<code>QQApiVideoObject</code> @param url 视频内容的目标URL @param title 分享内容的标题 @param description 分享内容的描述 @param data 分享内容的预览图像 @note 如果url为空,调用<code>QQApi#sendMessage:</code>时将返回FALSE */ +(id)objectWithURL:(NSURL*)url title:(NSString*)title description:(NSString*)description previewImageData:(NSData*)data; /** 获取一个autorelease的<code>QQApiVideoObject</code> @param url 视频内容的目标URL @param title 分享内容的标题 @param description 分享内容的描述 @param previewURL 分享内容的预览图像URL @note 如果url为空,调用<code>QQApi#sendMessage:</code>时将返回FALSE */ +(id)objectWithURL:(NSURL*)url title:(NSString*)title description:(NSString*)description previewImageURL:(NSURL*)previewURL; @end // QQApiNewsObject /** @brief 新闻URL对象 用于分享目标内容为新闻的URL的对象 */ @interface QQApiNewsObject : QQApiURLObject /** 获取一个autorelease的<code>QQApiNewsObject</code> @param url 视频内容的目标URL @param title 分享内容的标题 @param description 分享内容的描述 @param data 分享内容的预览图像 @note 如果url为空,调用<code>QQApi#sendMessage:</code>时将返回FALSE */ +(id)objectWithURL:(NSURL*)url title:(NSString*)title description:(NSString*)description previewImageData:(NSData*)data; /** 获取一个autorelease的<code>QQApiNewsObject</code> @param url 视频内容的目标URL @param title 分享内容的标题 @param description 分享内容的描述 @param previewURL 分享内容的预览图像URL @note 如果url为空,调用<code>QQApi#sendMessage:</code>时将返回FALSE */ +(id)objectWithURL:(NSURL*)url title:(NSString*)title description:(NSString*)description previewImageURL:(NSURL*)previewURL; @end // QQApiCommonContentObject; /** @brief 通用模板类型对象 用于分享一个固定显示模板的图文混排对象 @note 图片列表和文本列表不能同时为空 */ @interface QQApiCommonContentObject : QQApiObject /** 预定义的界面布局类型 */ @property(nonatomic,assign) unsigned int layoutType; @property(nonatomic,assign) NSData* previewImageData;///<预览图 @property(nonatomic,retain) NSArray* textArray;///<文本列表 @property(nonatomic,retain) NSArray* pictureDataArray;///<图片列表 +(id)objectWithLayoutType:(int)layoutType textArray:(NSArray*)textArray pictureArray:(NSArray*)pictureArray previewImageData:(NSData*)data; /** 将一个NSDictionary对象转化为QQApiCommomContentObject,如果无法转换,则返回空 */ +(id)objectWithDictionary:(NSDictionary*)dic; -(NSDictionary*)toDictionary; @end //////////////////////////////////////////////////////////////////////////////////////////////////////////// // Ad item object definition //////////////////////////////////////////////////////////////////////////////////////////////////////////// /** @brief 广告数据对象 */ @interface QQApiAdItem : NSObject @property(nonatomic,retain) NSString* title; ///<名称 @property(nonatomic,retain) NSString* description;///<描述 @property(nonatomic,retain) NSData* imageData;///<广告图片 @property(nonatomic,retain) NSURL* target;///<广告目标链接 @end #pragma mark - QQApiObject(关系链对象) // QQApiAddFriendObject /** \brief 添加好友 */ @interface QQApiAddFriendObject : QQApiObject @property (nonatomic,retain)NSString* openID; @property (nonatomic,retain)NSString* subID; @property (nonatomic,retain)NSString* remark; -(id)initWithOpenID:(NSString*)openID; ///<初始化方法 +(id)objecWithOpenID:(NSString*)openID; ///<工厂方法,获取一个QQApiAddFriendObject对象. @end // QQApiGameConsortiumBindingGroupObject /** \brief 游戏公会绑定群 */ @interface QQApiGameConsortiumBindingGroupObject : QQApiObject @property (nonatomic,retain)NSString* signature; @property (nonatomic,retain)NSString* unionid; @property (nonatomic,retain)NSString* zoneID; @property (nonatomic,retain)NSString* appDisplayName; -(id)initWithGameConsortium:(NSString*)signature unionid:(NSString*)unionid zoneID:(NSString*)zoneID appDisplayName:(NSString*)appDisplayName; ///<初始化方法 +(id)objectWithGameConsortium:(NSString*)signature unionid:(NSString*)unionid zoneID:(NSString*)zoneID appDisplayName:(NSString*)appDisplayName; ///<工厂方法,获取一个QQApiAddFriendObject对象. @end // QQApiGameConsortiumBindingGroupObject /** \brief 加入群 */ @interface QQApiJoinGroupObject : QQApiObject @property (nonatomic,retain)NSString* groupUin; @property (nonatomic,retain)NSString* groupKey; - (id)initWithGroupInfo:(NSString*)groupUin key:(NSString*)groupKey; ///<初始化方法 + (id)objectWithGroupInfo:(NSString*)groupUin key:(NSString*)groupKey; ///<同时提供群号和群KEY 工厂方法,获取一个QQApiAddFriendObject对象. + (id)objectWithGroupKey:(NSString*)groupKey; ///<只需要群的KEY 工厂方法,获取一个QQApiAddFriendObject对象. @end #pragma mark - QQApi请求消息类型 /** QQApi请求消息类型 */ enum QQApiInterfaceReqType { EGETMESSAGEFROMQQREQTYPE = 0, ///< 手Q -> 第三方应用,请求第三方应用向手Q发送消息 ESENDMESSAGETOQQREQTYPE = 1, ///< 第三方应用 -> 手Q,第三方应用向手Q分享消息 ESHOWMESSAGEFROMQQREQTYPE = 2, ///< 手Q -> 第三方应用,请求第三方应用展现消息中的数据 ESENDMESSAGEARKTOQQREQTYPE = 3 ///< 第三方应用 -> 手Q,第三方应用向手Q分享Ark消息 }; /** QQApi应答消息类型 */ enum QQApiInterfaceRespType { ESHOWMESSAGEFROMQQRESPTYPE = 0, ///< 第三方应用 -> 手Q,第三方应用应答消息展现结果 EGETMESSAGEFROMQQRESPTYPE = 1, ///< 第三方应用 -> 手Q,第三方应用回应发往手Q的消息 ESENDMESSAGETOQQRESPTYPE = 2 ///< 手Q -> 第三方应用,手Q应答处理分享消息的结果 }; /** QQApi请求消息基类 */ @interface QQBaseReq : NSObject /** 请求消息类型,参见\ref QQApiInterfaceReqType */ @property (nonatomic, assign) int type; @end /** QQApi应答消息基类 */ @interface QQBaseResp : NSObject /** 请求处理结果 */ @property (nonatomic, copy) NSString* result; /** 具体错误描述信息 */ @property (nonatomic, copy) NSString* errorDescription; /** 应答消息类型,参见\ref QQApiInterfaceRespType */ @property (nonatomic, assign) int type; /** 扩展信息 */ @property (nonatomic, assign) NSString* extendInfo; @end /** GetMessageFromQQReq请求帮助类 */ @interface GetMessageFromQQReq : QQBaseReq /** 创建一个GetMessageFromQQReq请求实例 */ + (GetMessageFromQQReq *)req; @end @interface SendMessageToQQReq : QQBaseReq /** 创建一个SendMessageToQQReq请求实例 \param message 具体分享消息实例 \return 新创建的SendMessageToQQReq请求实例 */ + (SendMessageToQQReq *)reqWithContent:(QQApiObject *)message; /** 创建一个支持Ark的SendMessageToQQReq请求实例 \param message 具体分享消息实例 \return 新创建的SendMessageToQQReq请求实例 */ + (SendMessageToQQReq *)reqWithArkContent:(ArkObject *)message; /** 具体分享消息 */ @property (nonatomic, retain) QQApiObject *message; /** 支持Ark的具体分享消息 */ @property (nonatomic, retain) ArkObject *arkMessage; @end /** SendMessageToQQResp应答帮助类 */ @interface SendMessageToQQResp : QQBaseResp /** 创建一个SendMessageToQQResp应答实例 \param result 请求处理结果 \param errDesp 具体错误描述信息 \param extendInfo 扩展信息 \return 新创建的SendMessageToQQResp应答实例 */ + (SendMessageToQQResp *)respWithResult:(NSString *)result errorDescription:(NSString *)errDesp extendInfo:(NSString*)extendInfo; @end /** ShowMessageFromQQReq请求帮助类 */ @interface ShowMessageFromQQReq : QQBaseReq /** 创建一个ShowMessageFromQQReq请求实例 \param message 具体待展现消息实例 \return 新创建的ShowMessageFromQQReq请求实例 */ + (ShowMessageFromQQReq *)reqWithContent:(QQApiObject *)message; /** 具体待展现消息 */ @property (nonatomic, retain) QQApiObject *message; @end #endif ================================================ FILE: zhuishushenqi/Vendor/ThirdLoginSDK/TencentOpenAPI.framework/Headers/TencentOAuth.h ================================================ /// /// \file TencentOAuth.h /// \brief QQ互联开放平台授权登录及相关开放接口实现类 /// /// Created by Tencent on 12-12-21. /// Copyright (c) 2012年 Tencent. All rights reserved. /// #import <UIKit/UIKit.h> #import "sdkdef.h" @protocol TencentSessionDelegate; @protocol TencentLoginDelegate; @protocol TencentApiInterfaceDelegate; @protocol TencentWebViewDelegate; @class TencentApiReq; @class TencentApiResp; typedef enum { kTencentNotAuthorizeState, kTencentSSOAuthorizeState, kTencentWebviewAuthorzieState, } TencentAuthorizeState; typedef enum { kAuthModeClientSideToken, kAuthModeServerSideCode, } TencentAuthMode; #pragma mark - TencentOAuth(授权登录及相关开放接口调用) /** * \brief TencentOpenAPI授权登录及相关开放接口调用 * * TencentOAuth实现授权登录逻辑以及相关开放接口的请求调用 */ @interface TencentOAuth : NSObject { NSMutableDictionary* _apiRequests; NSString* _accessToken; NSDate* _expirationDate; id<TencentSessionDelegate> _sessionDelegate; NSString* _localAppId; NSString* _openId; NSString* _redirectURI; NSArray* _permissions; } /** Access Token凭证,用于后续访问各开放接口 */ @property(nonatomic, copy) NSString* accessToken; /** Access Token的失效期 */ @property(nonatomic, copy) NSDate* expirationDate; /** 已实现的开放接口的回调委托对象 */ @property(nonatomic, assign) id<TencentSessionDelegate> sessionDelegate; /** 第三方应用在开发过程中设置的URLSchema,用于浏览器登录后后跳到第三方应用 */ @property(nonatomic, copy) NSString* localAppId; /** 用户授权登录后对该用户的唯一标识 */ @property(nonatomic, copy) NSString* openId; /** 用户登录成功过后的跳转页面地址 */ @property(nonatomic, copy) NSString* redirectURI; /** 第三方应用在互联开放平台申请的appID */ @property(nonatomic, retain) NSString* appId; /** 主要是互娱的游戏设置uin */ @property(nonatomic, retain) NSString* uin; /** 主要是互娱的游戏设置鉴定票据 */ @property(nonatomic, retain) NSString* skey; /** 登陆透传的数据 */ @property(nonatomic, copy) NSDictionary* passData; /** 授权方式(Client Side Token或者Server Side Code) */ @property(nonatomic, assign) TencentAuthMode authMode; /** union id */ @property(nonatomic, retain) NSString* unionid; /** 第三方在授权登录/分享 时选择 QQ,还是TIM 。在授权前一定要指定其中一个类型*/ @property(nonatomic, assign) TencentAuthShareType authShareType; /** * 获取上次登录得到的token * **/ - (NSString *)getCachedToken; /** * 获取上次登录得到的openid * **/ - (NSString *)getCachedOpenID; /** * 获取上次登录的token过期日期 * **/ - (NSDate *)getCachedExpirationDate; /** * 上次登录的token是否过期 * **/ - (BOOL)isCachedTokenValid; /** * 删除上次登录登录的token信息 * **/ - (BOOL)deleteCachedToken; /** * 用来获得当前sdk的版本号 * \return 返回sdk版本号 **/ + (NSString*)sdkVersion; /** * 用来获得当前sdk的小版本号 * \return 返回sdk小版本号 **/ + (NSString*)sdkSubVersion; /** * 用来获得当前sdk的是否精简版 * \return 返回YES表示精简版 **/ + (BOOL)isLiteSDK; /** * 主要是用来帮助判断是否有登陆被发起,但是还没有过返回结果 * \return * kTencentNotAuthorizeState:无授权 * kTencentSSOAuthorizeState:有人发起了sso授权但无返回 * kTencentWebviewAuthorzieState:有人发起了webview授权还未返回 **/ + (TencentAuthorizeState *)authorizeState; /** * 用来获得当前手机qq的版本号 * \return 返回手机qq版本号 **/ + (QQVersion)iphoneQQVersion; /** * 用来获得当前手机TIM的版本号 * \return 返回手机qq版本号 **/ + (QQVersion)iphoneTIMVersion; /** * 初始化TencentOAuth对象 * \param appId 第三方应用在互联开放平台申请的唯一标识 * \param delegate 第三方应用用于接收请求返回结果的委托对象 * \return 初始化后的授权登录对象 */ - (id)initWithAppId:(NSString *)appId andDelegate:(id<TencentSessionDelegate>)delegate; /** * 判断用户手机上是否安装手机QQ * \return YES:安装 NO:没安装 * * \note SDK目前已经支持QQ、TIM授权登录及分享功能, 会按照QQ>TIM的顺序进行调用。 * 只要用户安装了QQ、TIM中任意一个应用,都可为第三方应用进行授权登录、分享功能。 * 第三方应用在接入SDK时不需要判断是否安装QQ、TIM。若有判断安装QQ、TIM的逻辑建议移除。 */ + (BOOL)iphoneQQInstalled; /** * 判断用户手机上是否安装手机TIM * \return YES:安装 NO:没安装 * * \note SDK目前已经支持QQ、TIM授权登录及分享功能, 会按照QQ>TIM的顺序进行调用。 * 只要用户安装了QQ、TIM中任意一个应用,都可为第三方应用进行授权登录、分享功能。 * 第三方应用在接入SDK时不需要判断是否安装QQ、TIM。若有判断安装QQ、TIM的逻辑建议移除。 */ + (BOOL)iphoneTIMInstalled; /** * 判断用户手机上的手机QQ是否支持SSO登录 * \return YES:支持 NO:不支持 */ + (BOOL)iphoneQQSupportSSOLogin; /** * 判断用户手机上的手机TIM是否支持SSO登录 * \return YES:支持 NO:不支持 */ + (BOOL)iphoneTIMSupportSSOLogin; /** * 登录授权 * * \param permissions 授权信息列 */ - (BOOL)authorize:(NSArray *)permissions; /** * 登录授权 * \param permissions 授权信息列表 * \param bInSafari 是否使用safari进行登录.<b>IOS SDK 1.3版本开始此参数废除</b> */ - (BOOL)authorize:(NSArray *)permissions inSafari:(BOOL)bInSafari; /** * 登录授权 * \param permissions 授权信息列表 * \param localAppId 应用APPID * \param bInSafari 是否使用safari进行登录.<b>IOS SDK 1.3版本开始此参数废除</b> */ - (BOOL)authorize:(NSArray *)permissions localAppId:(NSString *)localAppId inSafari:(BOOL)bInSafari; /** * 登录授权<web为二维码扫码方式> * * \param permissions 授权信息列 */ - (BOOL)authorizeWithQRlogin:(NSArray *)permissions; /** * 增量授权,因用户没有授予相应接口调用的权限,需要用户确认是否授权 * \param permissions 需增量授权的信息列表 * \return 增量授权调用是否成功 */ - (BOOL)incrAuthWithPermissions:(NSArray *)permissions; /** * 重新授权,因token废除或失效导致接口调用失败,需用户重新授权 * \param permissions 授权信息列表,同登录授权 * \return 授权调用是否成功 */ - (BOOL)reauthorizeWithPermissions:(NSArray *)permissions; /** * 获取UnindID,可以根据UnindID的比较来确定OpenID是否属于同一个用户 * \return NO未登录,信息不足;YES条件满足,发送请求成功,请等待回调 */ - (BOOL)RequestUnionId; /** * (静态方法)处理应用拉起协议 * \param url 处理被其他应用呼起时的逻辑 * \return 处理结果,YES表示成功,NO表示失败 */ + (BOOL)HandleOpenURL:(NSURL *)url; /** * (静态方法)sdk是否可以处理应用拉起协议 * \param url 处理被其他应用呼起时的逻辑 * \return 处理结果,YES表示可以 NO表示不行 */ + (BOOL)CanHandleOpenURL:(NSURL *)url; /** * (静态方法)获取TencentOAuth调用的上一次错误信息 */ + (NSString *)getLastErrorMsg; /** * 以Server Side Code模式授权登录时,通过此接口获取返回的code值; * 以Client Side Token模式授权登录时,忽略此接口。 */ - (NSString *)getServerSideCode; /** * 退出登录(退出登录后,TecentOAuth失效,需要重新初始化) * \param delegate 第三方应用用于接收请求返回结果的委托对象 */ - (void)logout:(id<TencentSessionDelegate>)delegate; /** * 判断登录态是否有效 * \return 处理结果,YES表示有效,NO表示无效,请用户重新登录授权 */ - (BOOL)isSessionValid; /** * 获取用户个人信息 * \return 处理结果,YES表示API调用成功,NO表示API调用失败,登录态失败,重新登录 */ - (BOOL)getUserInfo; /** * 退出指定API调用 * \param userData 用户调用某条API的时候传入的保留参数 * \return 处理结果,YES表示成功 NO表示失败 */ - (BOOL)cancel:(id)userData; /** * CGI类任务创建接口 * \param apiURL CGI请求的URL地址 * \param method CGI请求方式:"GET","POST" * \param params CGI请求参数字典 * \param callback CGI请求结果的回调接口对象 * \return CGI请求任务实例,用于取消任务,返回nil代表任务创建失败 */ - (TCAPIRequest *)cgiRequestWithURL:(NSURL *)apiURL method:(NSString *)method params:(NSDictionary *)params callback:(id<TCAPIRequestDelegate>)callback; /** * TencentOpenApi发送任务统一接口 * \param request 请求发送的任务 * \param callback 任务发送后的回调地址 */ - (BOOL)sendAPIRequest:(TCAPIRequest *)request callback:(id<TCAPIRequestDelegate>)callback; - (NSString *)getUserOpenID; @end #pragma mark - TencentLoginDelegate(授权登录回调协议) /** * \brief TencentLoginDelegate iOS Open SDK 1.3 API回调协议 * * 第三方应用实现登录的回调协议 */ @protocol TencentLoginDelegate <NSObject> @required /** * 登录成功后的回调 */ - (void)tencentDidLogin; /** * 登录失败后的回调 * \param cancelled 代表用户是否主动退出登录 */ - (void)tencentDidNotLogin:(BOOL)cancelled; /** * 登录时网络有问题的回调 */ - (void)tencentDidNotNetWork; @optional /** * 登录时权限信息的获得 */ - (NSArray *)getAuthorizedPermissions:(NSArray *)permissions withExtraParams:(NSDictionary *)extraParams; /** * unionID获得 */ - (void)didGetUnionID; @end #pragma mark - TencentSessionDelegate(开放接口回调协议) /** * \brief TencentSessionDelegate iOS Open SDK 1.3 API回调协议 * * 第三方应用需要实现每条需要调用的API的回调协议 */ @protocol TencentSessionDelegate<NSObject, TencentLoginDelegate, TencentWebViewDelegate> @optional /** * 退出登录的回调 */ - (void)tencentDidLogout; /** * 因用户未授予相应权限而需要执行增量授权。在用户调用某个api接口时,如果服务器返回操作未被授权,则触发该回调协议接口,由第三方决定是否跳转到增量授权页面,让用户重新授权。 * \param tencentOAuth 登录授权对象。 * \param permissions 需增量授权的权限列表。 * \return 是否仍然回调返回原始的api请求结果。 * \note 不实现该协议接口则默认为不开启增量授权流程。若需要增量授权请调用\ref TencentOAuth#incrAuthWithPermissions: \n注意:增量授权时用户可能会修改登录的帐号 */ - (BOOL)tencentNeedPerformIncrAuth:(TencentOAuth *)tencentOAuth withPermissions:(NSArray *)permissions; /** * [该逻辑未实现]因token失效而需要执行重新登录授权。在用户调用某个api接口时,如果服务器返回token失效,则触发该回调协议接口,由第三方决定是否跳转到登录授权页面,让用户重新授权。 * \param tencentOAuth 登录授权对象。 * \return 是否仍然回调返回原始的api请求结果。 * \note 不实现该协议接口则默认为不开启重新登录授权流程。若需要重新登录授权请调用\ref TencentOAuth#reauthorizeWithPermissions: \n注意:重新登录授权时用户可能会修改登录的帐号 */ - (BOOL)tencentNeedPerformReAuth:(TencentOAuth *)tencentOAuth; /** * 用户通过增量授权流程重新授权登录,token及有效期限等信息已被更新。 * \param tencentOAuth token及有效期限等信息更新后的授权实例对象 * \note 第三方应用需更新已保存的token及有效期限等信息。 */ - (void)tencentDidUpdate:(TencentOAuth *)tencentOAuth; /** * 用户增量授权过程中因取消或网络问题导致授权失败 * \param reason 授权失败原因,具体失败原因参见sdkdef.h文件中\ref UpdateFailType */ - (void)tencentFailedUpdate:(UpdateFailType)reason; /** * 获取用户个人信息回调 * \param response API返回结果,具体定义参见sdkdef.h文件中\ref APIResponse * \remarks 正确返回示例: \snippet example/getUserInfoResponse.exp success * 错误返回示例: \snippet example/getUserInfoResponse.exp fail */ - (void)getUserInfoResponse:(APIResponse*) response; /** * 社交API统一回调接口 * \param response API返回结果,具体定义参见sdkdef.h文件中\ref APIResponse * \param message 响应的消息,目前支持‘SendStory’,‘AppInvitation’,‘AppChallenge’,‘AppGiftRequest’ */ - (void)responseDidReceived:(APIResponse*)response forMessage:(NSString *)message; /** * post请求的上传进度 * \param tencentOAuth 返回回调的tencentOAuth对象 * \param bytesWritten 本次回调上传的数据字节数 * \param totalBytesWritten 总共已经上传的字节数 * \param totalBytesExpectedToWrite 总共需要上传的字节数 * \param userData 用户自定义数据 */ - (void)tencentOAuth:(TencentOAuth *)tencentOAuth didSendBodyData:(NSInteger)bytesWritten totalBytesWritten:(NSInteger)totalBytesWritten totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite userData:(id)userData; /** * 通知第三方界面需要被关闭 * \param tencentOAuth 返回回调的tencentOAuth对象 * \param viewController 需要关闭的viewController */ - (void)tencentOAuth:(TencentOAuth *)tencentOAuth doCloseViewController:(UIViewController *)viewController; @end #pragma mark - TencentWebViewDelegate(H5登录webview旋转方向回调) /** * \brief TencentWebViewDelegate: H5登录webview旋转方向回调协议 * * 第三方应用可以根据自己APP的旋转方向限制,通过此协议设置 */ @protocol TencentWebViewDelegate <NSObject> @optional - (BOOL) tencentWebViewShouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation; - (NSUInteger) tencentWebViewSupportedInterfaceOrientationsWithWebkit; - (BOOL) tencentWebViewShouldAutorotateWithWebkit; @end ================================================ FILE: zhuishushenqi/Vendor/ThirdLoginSDK/TencentOpenAPI.framework/Headers/sdkdef.h ================================================ /// /// \file sdkdef.h /// \brief SDK中相关常量定义 /// /// Created by Tencent on 12-12-25. /// Copyright (c) 2012年 Tencent. All rights reserved. /// #import <Foundation/Foundation.h> #import <UIKit/UIKit.h> /** * \brief 设置sdk的log等级 */ typedef enum { TCOLogLevel_Disabled = -1, // 关闭所有log TCOLogLevel_Error = 0, TCOLogLevel_Warning, TCOLogLevel_Info, TCOLogLevel_Debug, } TCOLogLevel; /** * \brief 手机qq的当前版本 */ typedef enum QQVersion { kQQUninstall, kQQVersion3_0, kQQVersion4_0, //支持sso登陆 kQQVersion4_2_1, //ios7兼容 kQQVersion4_5, //4.5版本,wpa会话 kQQVersion4_6, //4.6版本,sso登陆信令通道切换 kQQVersion4_7, //4.7版本 不确定新支持了什么样的属性 } QQVersion; /** * \breif TIM的当前版本 */ typedef enum TIMVersion { kTIMUinstall, kTIMVersion1_1, }TIMVersion; /** * \breif 授权/分享 方式 */ typedef enum TencentAuthShareType { AuthShareType_QQ, AuthShareType_TIM, }TencentAuthShareType; /** * \brief APIResponse.retCode可能的枚举常量 */ typedef enum { URLREQUEST_SUCCEED = 0, /**< 网络请求成功发送至服务器,并且服务器返回数据格式正确 * \note 这里包括所请求业务操作失败的情况,例如没有授权等原因导致 */ URLREQUEST_FAILED = 1, /**< 网络异常,或服务器返回的数据格式不正确导致无法解析 */ } REPONSE_RESULT; /** * \brief 增量授权失败原因 * * \note 增量授权失败不影响原token的有效性(原token已失效的情况除外) */ typedef enum { kUpdateFailUnknown = 1, ///< 未知原因 kUpdateFailUserCancel, ///< 用户取消 kUpdateFailNetwork, ///< 网络问题 } UpdateFailType; /** * \brief 封装服务器返回的结果 * * APIResponse用于封装所有请求的返回结果,包括错误码、错误信息、原始返回数据以及返回数据的json格式字典 */ @interface APIResponse : NSObject<NSCoding> { int _detailRetCode; int _retCode; int _seq; NSString *_errorMsg; NSDictionary *_jsonResponse; NSString *_message; id _userData; } /** * 新增的详细错误码\n * detailRetCode主要用于区分不同的错误情况,参见\ref OpenSDKError */ @property (nonatomic, assign) int detailRetCode; /** * 网络请求是否成功送达服务器,以及服务器返回的数据格式是否正确\n * retCode具体取值可参考\ref REPONSE_RESULT */ @property (nonatomic, assign) int retCode; /** * 网络请求对应的递增序列号,方便内部管理 */ @property (nonatomic, assign) int seq; /** * 错误提示语 */ @property (nonatomic, retain) NSString *errorMsg; /** * 服务器返回数据的json格式字典\n * 字典内具体参数的命名和含义请参考\ref api_spec */ @property (nonatomic, retain) NSDictionary *jsonResponse; /** * 服务器返回的原始数据字符串 */ @property (nonatomic, retain) NSString *message; /** * 用户保留数据 */ @property (nonatomic, retain) id userData; @end /** * 用户自定义的保留字段 */ FOUNDATION_EXTERN NSString * const PARAM_USER_DATA; /** * \name 应用邀请参数字段定义 */ ///@{ /** 应用邀请展示图片url的key */ FOUNDATION_EXTERN NSString * const PARAM_APP_ICON; /** 应用邀请描述文本的key */ FOUNDATION_EXTERN NSString * const PARAM_APP_DESC; /** 应用邀请好友列表的key */ FOUNDATION_EXTERN NSString * const PARAM_APP_INVITED_OPENIDS; ///@} /** * \name sendStory新分享参数字段定义 */ ///@{ /** 预填入接受人列表的key */ FOUNDATION_EXTERN NSString * const PARAM_SENDSTORY_RECEIVER; /** 分享feeds标题的key */ FOUNDATION_EXTERN NSString * const PARAM_SENDSTORY_TITLE; /** 分享feeds评论内容的key */ FOUNDATION_EXTERN NSString * const PARAM_SENDSTORY_COMMENT; /** 分享feeds摘要的key */ FOUNDATION_EXTERN NSString * const PARAM_SENDSTORY_SUMMARY; /** 分享feeds展示图片url的key */ FOUNDATION_EXTERN NSString * const PARAM_SENDSTORY_IMAGE; /** 分享feeds跳转链接url的key */ FOUNDATION_EXTERN NSString * const PARAM_SENDSTORY_URL; /** 分享feeds点击操作默认行为的key */ FOUNDATION_EXTERN NSString * const PARAM_SENDSTORY_ACT; ///@} /** * \name 设置头像参数字段定义 */ ///@{ /** 头像图片数据的key */ FOUNDATION_EXTERN NSString * const PARAM_SETUSERHEAD_PIC; /** 头像图片文件名的key */ FOUNDATION_EXTERN NSString * const PARAM_SETUSERHEAD_FILENAME; ///@} /** * \name 服务器返回数据的参数字段定义 */ ///@{ /** 服务器返回码的key */ FOUNDATION_EXTERN NSString * const PARAM_RETCODE; /** 服务器返回错误信息的key */ FOUNDATION_EXTERN NSString * const PARAM_MESSAGE; /** 服务器返回额外数据的key */ FOUNDATION_EXTERN NSString * const PARAM_DATA; ///@} /** * \name 错误信息相关常量定义 */ ///@{ /** 详细错误信息字典中额外信息的key */ FOUNDATION_EXTERN NSString * const TCOpenSDKErrorKeyExtraInfo; /** 详细错误信息字典中返回码的key */ FOUNDATION_EXTERN NSString * const TCOpenSDKErrorKeyRetCode; /** 详细错误信息字典中错误语句的key */ FOUNDATION_EXTERN NSString * const TCOpenSDKErrorKeyMsg; /** 不支持的接口 */ FOUNDATION_EXTERN NSString * const TCOpenSDKErrorMsgUnsupportedAPI; /** 操作成功 */ FOUNDATION_EXTERN NSString * const TCOpenSDKErrorMsgSuccess; /** 未知错误 */ FOUNDATION_EXTERN NSString * const TCOpenSDKErrorMsgUnknown; /** 用户取消 */ FOUNDATION_EXTERN NSString * const TCOpenSDKErrorMsgUserCancel; /** 请重新登录 */ FOUNDATION_EXTERN NSString * const TCOpenSDKErrorMsgReLogin; /** 应用没有操作权限 */ FOUNDATION_EXTERN NSString * const TCOpenSDKErrorMsgOperationDeny; /** 网络异常或没有网络 */ FOUNDATION_EXTERN NSString * const TCOpenSDKErrorMsgNetwork; /** URL格式或协议错误 */ FOUNDATION_EXTERN NSString * const TCOpenSDKErrorMsgURL; /** 解析数据出错 */ FOUNDATION_EXTERN NSString * const TCOpenSDKErrorMsgDataParse; /** 传入参数有误 */ FOUNDATION_EXTERN NSString * const TCOpenSDKErrorMsgParam; /** 连接超时 */ FOUNDATION_EXTERN NSString * const TCOpenSDKErrorMsgTimeout; /** 安全问题 */ FOUNDATION_EXTERN NSString * const TCOpenSDKErrorMsgSecurity; /** 文件读写错误 */ FOUNDATION_EXTERN NSString * const TCOpenSDKErrorMsgIO; /** 服务器端错误 */ FOUNDATION_EXTERN NSString * const TCOpenSDKErrorMsgServer; /** 页面错误 */ FOUNDATION_EXTERN NSString * const TCOpenSDKErrorMsgWebPage; /** 设置头像图片过大 */ FOUNDATION_EXTERN NSString * const TCOpenSDKErrorMsgUserHeadPicLarge; ///@} /** * \brief SDK新增详细错误常量 */ typedef enum { kOpenSDKInvalid = -1, ///< 无效的错误码 kOpenSDKErrorUnsupportedAPI = -2, ///< 不支持的接口 /** * \name CommonErrorCode * 公共错误码 */ ///@{ kOpenSDKErrorSuccess = 0, ///< 成功 kOpenSDKErrorUnknown, ///< 未知错误 kOpenSDKErrorUserCancel, ///< 用户取消 kOpenSDKErrorReLogin, ///< token无效或用户未授权相应权限需要重新登录 kOpenSDKErrorOperationDeny, ///< 第三方应用没有该api操作的权限 ///@} /** * \name NetworkRelatedErrorCode * 网络相关错误码 */ ///@{ kOpenSDKErrorNetwork, ///< 网络错误,网络不通或连接不到服务器 kOpenSDKErrorURL, ///< URL格式或协议错误 kOpenSDKErrorDataParse, ///< 数据解析错误,服务器返回的数据解析出错 kOpenSDKErrorParam, ///< 传入参数错误 kOpenSDKErrorConnTimeout, ///< http连接超时 kOpenSDKErrorSecurity, ///< 安全问题 kOpenSDKErrorIO, ///< 下载和文件IO错误 kOpenSDKErrorServer, ///< 服务器端错误 ///@} /** * \name WebViewRelatedError * webview特有错误 */ ///@{ kOpenSDKErrorWebPage, ///< 页面错误 ///@} /** * \name SetUserHeadRelatedErrorCode * 设置头像自定义错误码段 */ ///@{ kOpenSDKErrorUserHeadPicLarge = 0x010000, ///< 图片过大 设置头像自定义错误码 ///@} } OpenSDKError; /** * \name SDK版本(v1.3)支持的授权列表常量 */ ///@{ /** 发表一条说说到QQ空间(<b>需要申请权限</b>) */ FOUNDATION_EXTERN NSString *const kOPEN_PERMISSION_ADD_TOPIC; /** 发表一篇日志到QQ空间(<b>需要申请权限</b>) */ FOUNDATION_EXTERN NSString *const kOPEN_PERMISSION_ADD_ONE_BLOG; /** 创建一个QQ空间相册(<b>需要申请权限</b>) */ FOUNDATION_EXTERN NSString *const kOPEN_PERMISSION_ADD_ALBUM; /** 上传一张照片到QQ空间相册(<b>需要申请权限</b>) */ FOUNDATION_EXTERN NSString *const kOPEN_PERMISSION_UPLOAD_PIC; /** 获取用户QQ空间相册列表(<b>需要申请权限</b>) */ FOUNDATION_EXTERN NSString *const kOPEN_PERMISSION_LIST_ALBUM; /** 同步分享到QQ空间、腾讯微博 */ FOUNDATION_EXTERN NSString *const kOPEN_PERMISSION_ADD_SHARE; /** 验证是否认证空间粉丝 */ FOUNDATION_EXTERN NSString *const kOPEN_PERMISSION_CHECK_PAGE_FANS; /** 获取登录用户自己的详细信息 */ FOUNDATION_EXTERN NSString *const kOPEN_PERMISSION_GET_INFO; /** 获取其他用户的详细信息 */ FOUNDATION_EXTERN NSString *const kOPEN_PERMISSION_GET_OTHER_INFO; /** 获取会员用户基本信息 */ FOUNDATION_EXTERN NSString *const kOPEN_PERMISSION_GET_VIP_INFO; /** 获取会员用户详细信息 */ FOUNDATION_EXTERN NSString *const kOPEN_PERMISSION_GET_VIP_RICH_INFO; /** 获取用户信息 */ FOUNDATION_EXTERN NSString *const kOPEN_PERMISSION_GET_USER_INFO; /** 移动端获取用户信息 */ FOUNDATION_EXTERN NSString *const kOPEN_PERMISSION_GET_SIMPLE_USER_INFO; ///@} /** * \name CGI接口相关参数类型定义 */ /** 可选的字符串类型参数 */ typedef NSString *TCOptionalStr; /** 可选的不定类型参数 */ typedef id TCRequiredId; ///@} /** * \brief CGI请求的参数字典封装辅助基类 * * 将相应属性的值以key-value的形式保存到参数字典中 */ @interface TCAPIRequest : NSMutableDictionary /** CGI请求的URL地址 */ @property (nonatomic, readonly) NSURL *apiURL; /** CGI请求方式:"GET","POST" */ @property (nonatomic, readonly) NSString *method; /** * API参数中的保留字段,可以塞入任意字典支持的类型,再调用完成后会带回给调用方 */ @property (nonatomic, retain) TCRequiredId paramUserData; /** * APIResponse,API的返回结果 */ @property (nonatomic, readonly) APIResponse *response; /** 取消相应的CGI请求任务 */ - (void)cancel; @end @protocol TCAPIRequestDelegate <NSObject> @optional - (void)cgiRequest:(TCAPIRequest *)request didResponse:(APIResponse *)response; @end ================================================ FILE: zhuishushenqi/Vendor/XimalayaSDK_iOS_5.5.1/include/Authorization/XMLYAuthorize.h ================================================ // // XMLYAuthorize.h // AuthorizeDemo // // Created by jude on 16/10/18. // Copyright © 2016年 ximalaya. All rights reserved. // #import <Foundation/Foundation.h> #import "XMSingleTone.h" //响应消息 typedef NS_ENUM(NSInteger, XmlyResponseType) { AuthorizeSuccess = 0, //授权成功 RefreshTokenSuccess, //刷新accesstoken成功 RequestQRCodeSuccess, // 请求二维码信息成功 QRCodeLoginSuccess, // 二维码登录成功 ErrorAuthorizeFail, //授权失败 ErrorRefreshTokenFail, //刷新accesstoken失败 ErrorQRCodeLoginFail, // 二维码登录失败 ErrorRequestQRCodeFail // 请求二维码信息失败 }; typedef enum { kQRCodeSizeMiddle = 0, //!< M表示238*238,默认为M kQRCodeSizeSmall, //!< S表示180*180 kQRCodeSizeLarge, //!< L表示418*418 } QRCodeSize; //授权回调返回数据 @interface XMLYAuthorizeModel : NSObject //授权后的access_token @property (nonatomic, strong) NSString *access_token; //用来刷新access_token @property (nonatomic, strong) NSString *refresh_token; //access_token的生命周期,单位是秒数 @property (nonatomic, assign) NSTimeInterval expires_in; //喜马拉雅用户id @property (nonatomic, assign) long uid; //授权范围 @property (nonatomic, strong) NSString *scope; //设备号 @property (nonatomic, strong) NSString *device_id; @end //授权回调代理 @protocol XMLYAuthorizeDelegate <NSObject> /* * 成功回调 * * @param responseType 响应消息类型 * @param authorizeModel 响应数据 */ - (void)onAuthorizeSuccess:(XmlyResponseType)responseType responseData:(XMLYAuthorizeModel *)authorizeModel; /* * 失败回调 * * @param errorType 失败消息类型 * @param info 错误描述 */ - (void)onAuthorizeFail:(XmlyResponseType)errorType errorInfo:(NSDictionary*)info; /* * 请求生成二维码成功回调 * * @param responseType 响应消息类型 * @param authorizeModel 响应数据,为二进制流图片数据 */ - (void)onRequestQRCodeSuccess:(XmlyResponseType)responseType responseData:(NSData *)imageData; /* * 请求生成二维码失败回调 * * @param errorType 失败消息类型 * @param info 错误描述 */ - (void)onRequestQRCodeFail:(XmlyResponseType)errorType errorInfo:(NSDictionary*)info; @end @interface XMLYAuthorize : NSObject @property (nonatomic,assign) BOOL usingSynRequest; //!< 使用同步的方式进行请求 send request synchronously,目前仅对refreshToken和requestExchangeTokenWithThirdUid两个方法有效 @property (nonatomic, weak) id<XMLYAuthorizeDelegate> callbackMetheds; //!< 代理,一般不建议直接设置该属性,如果设置则要确保其代理一直存在否则将收不到授权相关的一些回调 /* * SDK单例对象 */ + (XMLYAuthorize *)shareInstance; /* * 初始化 * * @param appKey 分配的appkey * @param appSecret 分配的appsecret * @param appRedirectUri 应用定义的回调地址 * @param appPackageId 应用定义的包名 * @param appName 应用名字 * @param delegate 回调 */ - (void)initWithAppkey:(NSString *)appKey appSecret:(NSString *)appSecret appRedirectUri:(NSString *)appRedirectUri appPackageId:(NSString *)appPackageId appName:(NSString *)appName delegate:(id<XMLYAuthorizeDelegate>)delegate; /* * 授权接口 * @param isOpen 是否跳转喜马拉雅 */ - (void)authorize:(BOOL)isOpen; /* * 刷新授权信息接口 */ - (void)refreshToken; /* * 注册并授权接口 * @param isOpen 是否跳转喜马拉雅 */ - (void)registerAndAuthorize:(BOOL)isOpen; /* * url跳转 * * @param url 跳转的url */ - (void)handleURL:(NSURL *)url; /* * 获取AuthorizeModel */ - (XMLYAuthorizeModel *)loadAuthorizeModel; - (void)saveAuthorizeModel:(XMLYAuthorizeModel *)model; - (BOOL)isLoggedIn; - (long)currentUid; /* * 第三方使用third_uid和third_token换取授权后的access_token */ - (void)requestExchangeTokenWithThirdUid:(NSString *)tUid thirdToken:(NSString *)tToken; /* * 设置state参数(optional/非必需)。state表示客户端的当前状态,可以指定任意值,认证服务器会原封不动地返回这个值。开发者可以用这个参数验证请求有效性,也可以记录用户请求授权页前的位置。这个参数可用于防止跨站请求伪造(CSRF)攻击 */ - (void)settingAuthorizeState:(NSString *)state; /** * 请求服务器生成并返回二维码信息用于喜马拉雅APP扫描验证登录 * @param qrCodeSize 用于选择返回二维码的size大小 */ -(void)requestGernerateQRCodeForLoginWithSize:(QRCodeSize)qrCodeSize; /** * 查询登录状态及授权信息接口 * 1. 登录成功,进入AuthorizeSuccess回调 * 2. 若失败,则进入AuthorizeFail回调 * - 若用户未登录,则继续轮询本接口查询用户登录状态,建议控制在2-3秒轮询一次 * - 二维码图片过期,则需要重新调用接口获取新的有效二维码图片 */ -(void)checkQRCodeLoginStatus; @end ================================================ FILE: zhuishushenqi/Vendor/XimalayaSDK_iOS_5.5.1/include/Downloader/XMSDKDownloadManager.h ================================================ // // XMSDKDownloadManager.h // XMOpenPlatform // // Created by chesterchen on 9/5/16. // // #import <Foundation/Foundation.h> #import "XMSDK.h" #import "XMSingleTone.h" @class XMTrackDownloadStatus; @protocol SDKDownloadMgrDelegate <NSObject> #pragma mark 代理回调 @optional /** * 下载失败时被调用 */ - (void)XMTrackDownloadDidFailed:(XMTrack *)track; /** * 下载完成时被调用 */ - (void)XMTrackDownloadDidFinished:(XMTrack *)track; /** * 下载开始时被调用 */ - (void)XMTrackDownloadDidBegan:(XMTrack *)track; /** * 下载取消时被调用 */ - (void)XMTrackDownloadDidCanceled:(XMTrack *)track; /** * 下载暂停时被调用 */ - (void)XMTrackDownloadDidPaused:(XMTrack *)track; /** * 下载进度更新时被调用 */ - (void)XMTrack:(XMTrack *)track updateDownloadedPercent:(double)downloadedPercent; /** * 下载状态更新为ready时被调用 */ - (void)XMTrackDownloadStatusUpdated:(XMTrack *)track; /** * 从数据库载入数据完成时被调用 */ - (void)XMTrackDownloadDidLoadFromDB; @end @interface XMSDKDownloadManager : NSObject @property (nonatomic, assign) id <SDKDownloadMgrDelegate> sdkDownloadMgrDelegate; //初始化方法 DECLARE_SINGLETON_METHOD(XMSDKDownloadManager, sharedSDKDownloadManager) #pragma mark 全局配置接口 /** * 设置下载音频文件的保存目录 * 默认路径:~/Documents/iDoc/Download/... * 请确保自定义设置的saveDir有效,否则仍会保存到默认路径 * @param saveDir NSString */ - (void)settingDownloadAudioSaveDir:(NSString *)saveDir; /** * 获取下载音频文件的保存目录 * * @return saveDir */ - (NSString *)getDownloadAudioSaveDir; /** * 获取下载文件已占用的磁盘空间,单位字节 * * @return bytes */ - (unsigned long long)getDownloadOccupationInBytes; #pragma mark 下载接口 /** * 下载单条音频 * * @param track * @param ifImmediate 是否立即下载,若否,只注册不下载 * * @return 返回值含义 (0:已加入下载队列;1:已下载完成;2:URL无效;4:正在下载;5:数据库错误;9:此音频不允许被下载;10:已注册但不立即下载;21:此音频正在下载中;22:此音频已下载) */ - (int)downloadSingleTrack:(XMTrack *)track immediately:(BOOL)ifImmediate; /** * 批量下载音频 * * @param tracks 单次批量下载数量不超过50 * @param ifImmediate 是否立即下载,若否,只注册不下载 * */ - (void)downloadTracks:(NSMutableArray *)tracks immediately:(BOOL)ifImmediate; #pragma mark 暂停下载接口 /** * 暂停单条音频下载 * */ - (void)pauseDownloadSingleTrack:(XMTrack *)track; /** * 批量暂停音频下载 * */ - (void)pauseDownloadTracks:(NSMutableArray *)tracks; /** * 暂停某专辑中全部音频下载 * */ - (void)pauseDownloadTracksInAlbum:(NSInteger)albumId; /** * 暂停全部音频下载 * */ - (void)pauseAllDownloads; #pragma mark 恢复下载接口 /** * 恢复被暂停的单条音频下载任务 * 注:此接口会立即开始下载 immediately * */ - (void)resumeDownloadSingleTrack:(XMTrack *)track; /** * 批量恢复被暂停的音频下载任务 * 注:此接口会立即开始下载 immediately * */ - (void)resumeDownloadTracks:(NSMutableArray *)tracks; /** * 恢复被暂停的某专辑中的全部音频下载 * 注:此接口会立即开始下载 immediately * */ - (void)resumeDownloadTracksInAlbum:(NSInteger)albumId; /** * 恢复被暂停的全部音频下载任务 * 注:此接口会立即开始下载 immediately * */ - (void)resumeAllDownloads; #pragma mark 取消下载接口 //取消下载:只对准备下载和正在下载状态的音频有效 /** * 取消单条音频下载,已下载部分也会被删除 * */ - (void)cancelDownloadSingleTrack:(XMTrack *)track; /** * 批量取消音频下载,已下载部分也会被删除 * */ - (void)cancelDownloadTracks:(NSMutableArray *)tracks; /** * 取消某专辑中全部音频下载,已下载部分也会被删除 * */ - (void)cancelDownloadTracksInAlbum:(NSInteger)albumId; /** * 取消全部音频下载,已下载部分也会被删除 * */ - (void)cancelAllDownloads; #pragma mark 清除接口 /** * 清除单个音频,不管是已下载,正在下载还是准备下载,都会被删除 * */ - (void)clearDownloadAudio:(XMTrack *)track; /** * 批量清除音频,不管是已下载,正在下载还是准备下载,都会被删除 * */ - (void)clearDownloadTracks:(NSMutableArray *)tracks; /** * 指定清除某专辑中的音频,不管是已下载,正在下载还是准备下载,都会被删除 * */ - (void)clearDownloadAlbum:(NSInteger)albumId; /** * 清除全部音频,不管是已下载,正在下载还是准备下载,都会被删除 * */ - (void)clearAllDownloads; #pragma mark 获取下载状态/音频/专辑信息接口 /* 获取状态的接口请在XMTrackDownloadDidLoadFromDB回调触发后再调用,否则可能会出现获取不到状态的情况 */ /** * 获取单个音频下载状态 * @param trackId * */ - (XMTrackDownloadStatus *)getSingleTrackDownloadStatus:(NSInteger)trackId; /** * 批量获取音频下载状态 * @param trackId的数组 * @return 字典 key:trackId NSNumber, value:XMTrackDownloadStatus */ - (NSMutableDictionary *)getBatchTrackDownloadStatus:(NSMutableArray *)trackIds; /** * 获取某专辑中全部音频的下载状态 * @param albumId * @return 字典 key:trackId NSNumber, value:XMTrackDownloadStatus */ - (NSMutableDictionary *)getTrackInAlbumDownloadStatus:(NSInteger)albumId; /** * 获取全部音频下载状态 * @return 字典 key:trackId NSNumber, value:XMTrackDownloadStatus */ - (NSMutableDictionary *)getAllDownloadStatus; /** * 获取全部下载中的音频 * @return XMCacheTrack array */ - (NSArray *)getDownloadingSounds; /** * 获取已下载音频 * @return XMCacheTrack array */ - (NSMutableArray *)getDownloadedTracks; /** * 获取已下载的专辑 * @return XMSubordinatedAlbum array */ - (NSMutableArray *)getDownloadedAlbums; /** * 获取某专辑中已下载的音频 * @return XMCacheTrack array */ - (NSMutableArray *)getDownloadedTrackInAlbum:(NSInteger)albumId; @end ================================================ FILE: zhuishushenqi/Vendor/XimalayaSDK_iOS_5.5.1/include/FMDB/FMDatabase.h ================================================ #import <Foundation/Foundation.h> #import "sqlite3.h" #import "FMResultSet.h" #import "FMDatabasePool.h" #if ! __has_feature(objc_arc) #define FMDBAutorelease(__v) ([__v autorelease]); #define FMDBReturnAutoreleased FMDBAutorelease #define FMDBRetain(__v) ([__v retain]); #define FMDBReturnRetained FMDBRetain #define FMDBRelease(__v) ([__v release]); #define FMDBDispatchQueueRelease(__v) (dispatch_release(__v)); #else // -fobjc-arc #define FMDBAutorelease(__v) #define FMDBReturnAutoreleased(__v) (__v) #define FMDBRetain(__v) #define FMDBReturnRetained(__v) (__v) #define FMDBRelease(__v) #if TARGET_OS_IPHONE // Compiling for iOS #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 60000 // iOS 6.0 or later #define FMDBDispatchQueueRelease(__v) #else // iOS 5.X or earlier #define FMDBDispatchQueueRelease(__v) (dispatch_release(__v)); #endif #else // Compiling for Mac OS X #if MAC_OS_X_VERSION_MIN_REQUIRED >= 1080 // Mac OS X 10.8 or later #define FMDBDispatchQueueRelease(__v) #else // Mac OS X 10.7 or earlier #define FMDBDispatchQueueRelease(__v) (dispatch_release(__v)); #endif #endif #endif @interface FMDatabase : NSObject { sqlite3* _db; NSString* _databasePath; BOOL _logsErrors; BOOL _crashOnErrors; BOOL _traceExecution; BOOL _checkedOut; BOOL _shouldCacheStatements; BOOL _isExecutingStatement; BOOL _inTransaction; int _busyRetryTimeout; NSMutableDictionary *_cachedStatements; NSMutableSet *_openResultSets; NSMutableSet *_openFunctions; NSDateFormatter *_dateFormat; } @property (atomic, assign) BOOL traceExecution; @property (atomic, assign) BOOL checkedOut; @property (atomic, assign) int busyRetryTimeout; @property (atomic, assign) BOOL crashOnErrors; @property (atomic, assign) BOOL logsErrors; @property (atomic, retain) NSMutableDictionary *cachedStatements; + (id)databaseWithPath:(NSString*)inPath; - (id)initWithPath:(NSString*)inPath; - (BOOL)open; #if SQLITE_VERSION_NUMBER >= 3005000 - (BOOL)openWithFlags:(int)flags; #endif - (BOOL)close; - (BOOL)goodConnection; - (void)clearCachedStatements; - (void)closeOpenResultSets; - (BOOL)hasOpenResultSets; // encryption methods. You need to have purchased the sqlite encryption extensions for these to work. - (BOOL)setKey:(NSString*)key; - (BOOL)rekey:(NSString*)key; - (BOOL)setKeyWithData:(NSData *)keyData; - (BOOL)rekeyWithData:(NSData *)keyData; - (NSString *)databasePath; - (NSString*)lastErrorMessage; - (int)lastErrorCode; - (BOOL)hadError; - (NSError*)lastError; - (sqlite_int64)lastInsertRowId; - (sqlite3*)sqliteHandle; - (BOOL)update:(NSString*)sql withErrorAndBindings:(NSError**)outErr, ...; - (BOOL)executeUpdate:(NSString*)sql, ...; - (BOOL)executeUpdateWithFormat:(NSString *)format, ...; - (BOOL)executeUpdate:(NSString*)sql withArgumentsInArray:(NSArray *)arguments; - (BOOL)executeUpdate:(NSString*)sql withParameterDictionary:(NSDictionary *)arguments; - (FMResultSet *)executeQuery:(NSString*)sql, ...; - (FMResultSet *)executeQueryWithFormat:(NSString*)format, ...; - (FMResultSet *)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray *)arguments; - (FMResultSet *)executeQuery:(NSString *)sql withParameterDictionary:(NSDictionary *)arguments; - (BOOL)rollback; - (BOOL)commit; - (BOOL)beginTransaction; - (BOOL)beginDeferredTransaction; - (BOOL)inTransaction; - (BOOL)shouldCacheStatements; - (void)setShouldCacheStatements:(BOOL)value; #if SQLITE_VERSION_NUMBER >= 3007000 - (BOOL)startSavePointWithName:(NSString*)name error:(NSError**)outErr; - (BOOL)releaseSavePointWithName:(NSString*)name error:(NSError**)outErr; - (BOOL)rollbackToSavePointWithName:(NSString*)name error:(NSError**)outErr; - (NSError*)inSavePoint:(void (^)(BOOL *rollback))block; #endif + (BOOL)isSQLiteThreadSafe; + (NSString*)sqliteLibVersion; - (int)changes; - (void)makeFunctionNamed:(NSString*)name maximumArguments:(int)count withBlock:(void (^)(sqlite3_context *context, int argc, sqlite3_value **argv))block; /** Generate an NSDateFormat that won't be broken by timezone or locale changes. Use this method to generate values to set the dateFormat property. @param dateFormat A valid NSDateFormatter format string. Example: myDB.dateFormat = [FMDatabase storeableDateFormat:@"yyyy-MM-dd HH:mm:ss"]; Note that NSDateFormatter is not thread-safe, so the formatter generated by this method should be assigned to only one FMDB instance and should not be used for other purposes. */ + (NSDateFormatter *)storeableDateFormat:(NSString *)format; /** Test whether the database has a date formatter assigned. */ - (BOOL)hasDateFormatter; /** Set to a date formatter to use string dates with sqlite instead of the default UNIX timestamps. Set to nil to use UNIX timestamps. Defaults to nil. Should be set using a formatter generated using FMDatabase::storeableDateFormat. Note there is no direct getter for the NSDateFormatter, and you should not use the formatter you pass to FMDB for other purposes, as NSDateFormatter is not thread-safe. */ - (void)setDateFormat:(NSDateFormatter *)format; /** Convert the supplied NSString to NSDate, using the current database formatter. Returns nil if no formatter is set. */ - (NSDate *)dateFromString:(NSString *)s; /** Convert the supplied NSDate to NSString, using the current database formatter. Returns nil if no formatter is set. */ - (NSString *)stringFromDate:(NSDate *)date; @end @interface FMStatement : NSObject { sqlite3_stmt *_statement; NSString *_query; long _useCount; } @property (atomic, assign) long useCount; @property (atomic, retain) NSString *query; @property (atomic, assign) sqlite3_stmt *statement; - (void)close; - (void)reset; @end ================================================ FILE: zhuishushenqi/Vendor/XimalayaSDK_iOS_5.5.1/include/FMDB/FMDatabase.m ================================================ #import "FMDatabase.h" #import "unistd.h" #import <objc/runtime.h> @interface FMDatabase () - (FMResultSet *)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray*)arrayArgs orDictionary:(NSDictionary *)dictionaryArgs orVAList:(va_list)args; - (BOOL)executeUpdate:(NSString*)sql error:(NSError**)outErr withArgumentsInArray:(NSArray*)arrayArgs orDictionary:(NSDictionary *)dictionaryArgs orVAList:(va_list)args; @end @implementation FMDatabase @synthesize cachedStatements=_cachedStatements; @synthesize logsErrors=_logsErrors; @synthesize crashOnErrors=_crashOnErrors; @synthesize busyRetryTimeout=_busyRetryTimeout; @synthesize checkedOut=_checkedOut; @synthesize traceExecution=_traceExecution; + (id)databaseWithPath:(NSString*)aPath { return FMDBReturnAutoreleased([[self alloc] initWithPath:aPath]); } + (NSString*)sqliteLibVersion { return [NSString stringWithFormat:@"%s", sqlite3_libversion()]; } + (BOOL)isSQLiteThreadSafe { // make sure to read the sqlite headers on this guy! return sqlite3_threadsafe() != 0; } - (id)initWithPath:(NSString*)aPath { assert(sqlite3_threadsafe()); // whoa there big boy- gotta make sure sqlite it happy with what we're going to do. self = [super init]; if (self) { _databasePath = [aPath copy]; _openResultSets = [[NSMutableSet alloc] init]; _db = 0x00; _logsErrors = 0x00; _crashOnErrors = 0x00; _busyRetryTimeout = 0x00; } return self; } - (void)finalize { [self close]; [super finalize]; } - (void)dealloc { [self close]; FMDBRelease(_openResultSets); FMDBRelease(_cachedStatements); FMDBRelease(_dateFormat); FMDBRelease(_databasePath); FMDBRelease(_openFunctions); #if ! __has_feature(objc_arc) [super dealloc]; #endif } - (NSString *)databasePath { return _databasePath; } - (sqlite3*)sqliteHandle { return _db; } - (const char*)sqlitePath { if (!_databasePath) { return ":memory:"; } if ([_databasePath length] == 0) { return ""; // this creates a temporary database (it's an sqlite thing). } return [_databasePath fileSystemRepresentation]; } - (BOOL)open { if (_db) { return YES; } int err = sqlite3_open([self sqlitePath], &_db ); if(err != SQLITE_OK) { NSLog(@"error opening!: %d", err); return NO; } return YES; } #if SQLITE_VERSION_NUMBER >= 3005000 - (BOOL)openWithFlags:(int)flags { int err = sqlite3_open_v2([self sqlitePath], &_db, flags, NULL /* Name of VFS module to use */); if(err != SQLITE_OK) { NSLog(@"error opening!: %d", err); return NO; } return YES; } #endif - (BOOL)close { [self clearCachedStatements]; [self closeOpenResultSets]; if (!_db) { return YES; } int rc; BOOL retry; int numberOfRetries = 0; BOOL triedFinalizingOpenStatements = NO; do { retry = NO; rc = sqlite3_close(_db); if (SQLITE_BUSY == rc || SQLITE_LOCKED == rc) { retry = YES; usleep(20); if (_busyRetryTimeout && (numberOfRetries++ > _busyRetryTimeout)) { NSLog(@"%s:%d", __FUNCTION__, __LINE__); NSLog(@"Database busy, unable to close"); return NO; } if (!triedFinalizingOpenStatements) { triedFinalizingOpenStatements = YES; sqlite3_stmt *pStmt; while ((pStmt = sqlite3_next_stmt(_db, 0x00)) !=0) { NSLog(@"Closing leaked statement"); sqlite3_finalize(pStmt); } } } else if (SQLITE_OK != rc) { NSLog(@"error closing!: %d", rc); } } while (retry); _db = nil; return YES; } - (void)clearCachedStatements { for (FMStatement *cachedStmt in [_cachedStatements objectEnumerator]) { [cachedStmt close]; } [_cachedStatements removeAllObjects]; } - (BOOL)hasOpenResultSets { return [_openResultSets count] > 0; } - (void)closeOpenResultSets { //Copy the set so we don't get mutation errors NSSet *openSetCopy = FMDBReturnAutoreleased([_openResultSets copy]); for (NSValue *rsInWrappedInATastyValueMeal in openSetCopy) { FMResultSet *rs = (FMResultSet *)[rsInWrappedInATastyValueMeal pointerValue]; [rs setParentDB:nil]; [rs close]; [_openResultSets removeObject:rsInWrappedInATastyValueMeal]; } } - (void)resultSetDidClose:(FMResultSet *)resultSet { NSValue *setValue = [NSValue valueWithNonretainedObject:resultSet]; [_openResultSets removeObject:setValue]; } - (FMStatement*)cachedStatementForQuery:(NSString*)query { return [_cachedStatements objectForKey:query]; } - (void)setCachedStatement:(FMStatement*)statement forQuery:(NSString*)query { if (nil == query) { return; } query = [query copy]; // in case we got handed in a mutable string... [statement setQuery:query]; [_cachedStatements setObject:statement forKey:query]; FMDBRelease(query); } - (BOOL)rekey:(NSString*)key { NSData *keyData = [NSData dataWithBytes:(void *)[key UTF8String] length:(NSUInteger)strlen([key UTF8String])]; return [self rekeyWithData:keyData]; } - (BOOL)rekeyWithData:(NSData *)keyData { #ifdef SQLITE_HAS_CODEC if (!keyData) { return NO; } int rc = sqlite3_rekey(_db, [keyData bytes], (int)[keyData length]); if (rc != SQLITE_OK) { NSLog(@"error on rekey: %d", rc); NSLog(@"%@", [self lastErrorMessage]); } return (rc == SQLITE_OK); #else return NO; #endif } - (BOOL)setKey:(NSString*)key { NSData *keyData = [NSData dataWithBytes:[key UTF8String] length:(NSUInteger)strlen([key UTF8String])]; return [self setKeyWithData:keyData]; } - (BOOL)setKeyWithData:(NSData *)keyData { #ifdef SQLITE_HAS_CODEC if (!keyData) { return NO; } int rc = sqlite3_key(_db, [keyData bytes], (int)[keyData length]); return (rc == SQLITE_OK); #else return NO; #endif } + (NSDateFormatter *)storeableDateFormat:(NSString *)format { NSDateFormatter *result = FMDBReturnAutoreleased([[NSDateFormatter alloc] init]); result.dateFormat = format; result.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0]; result.locale = FMDBReturnAutoreleased([[NSLocale alloc] initWithLocaleIdentifier:@"en_US"]); return result; } - (BOOL)hasDateFormatter { return _dateFormat != nil; } - (void)setDateFormat:(NSDateFormatter *)format { FMDBAutorelease(_dateFormat); _dateFormat = FMDBReturnRetained(format); } - (NSDate *)dateFromString:(NSString *)s { return [_dateFormat dateFromString:s]; } - (NSString *)stringFromDate:(NSDate *)date { return [_dateFormat stringFromDate:date]; } - (BOOL)goodConnection { if (!_db) { return NO; } FMResultSet *rs = [self executeQuery:@"select name from sqlite_master where type='table'"]; if (rs) { [rs close]; return YES; } return NO; } - (void)warnInUse { NSLog(@"The FMDatabase %@ is currently in use.", self); #ifndef NS_BLOCK_ASSERTIONS if (_crashOnErrors) { NSAssert1(false, @"The FMDatabase %@ is currently in use.", self); abort(); } #endif } - (BOOL)databaseExists { if (!_db) { NSLog(@"The FMDatabase %@ is not open.", self); #ifndef NS_BLOCK_ASSERTIONS if (_crashOnErrors) { NSAssert1(false, @"The FMDatabase %@ is not open.", self); abort(); } #endif return NO; } return YES; } - (NSString*)lastErrorMessage { return [NSString stringWithUTF8String:sqlite3_errmsg(_db)]; } - (BOOL)hadError { int lastErrCode = [self lastErrorCode]; return (lastErrCode > SQLITE_OK && lastErrCode < SQLITE_ROW); } - (int)lastErrorCode { return sqlite3_errcode(_db); } - (NSError*)errorWithMessage:(NSString*)message { NSDictionary* errorMessage = [NSDictionary dictionaryWithObject:message forKey:NSLocalizedDescriptionKey]; return [NSError errorWithDomain:@"FMDatabase" code:sqlite3_errcode(_db) userInfo:errorMessage]; } - (NSError*)lastError { return [self errorWithMessage:[self lastErrorMessage]]; } - (sqlite_int64)lastInsertRowId { if (_isExecutingStatement) { [self warnInUse]; return NO; } _isExecutingStatement = YES; sqlite_int64 ret = sqlite3_last_insert_rowid(_db); _isExecutingStatement = NO; return ret; } - (int)changes { if (_isExecutingStatement) { [self warnInUse]; return 0; } _isExecutingStatement = YES; int ret = sqlite3_changes(_db); _isExecutingStatement = NO; return ret; } - (void)bindObject:(id)obj toColumn:(int)idx inStatement:(sqlite3_stmt*)pStmt { if ((!obj) || ((NSNull *)obj == [NSNull null])) { sqlite3_bind_null(pStmt, idx); } // FIXME - someday check the return codes on these binds. else if ([obj isKindOfClass:[NSData class]]) { const void *bytes = [obj bytes]; if (!bytes) { // it's an empty NSData object, aka [NSData data]. // Don't pass a NULL pointer, or sqlite will bind a SQL null instead of a blob. bytes = ""; } sqlite3_bind_blob(pStmt, idx, bytes, (int)[obj length], SQLITE_STATIC); } else if ([obj isKindOfClass:[NSDate class]]) { if (self.hasDateFormatter) sqlite3_bind_text(pStmt, idx, [[self stringFromDate:obj] UTF8String], -1, SQLITE_STATIC); else sqlite3_bind_double(pStmt, idx, [obj timeIntervalSince1970]); } else if ([obj isKindOfClass:[NSNumber class]]) { if (strcmp([obj objCType], @encode(BOOL)) == 0) { sqlite3_bind_int(pStmt, idx, ([obj boolValue] ? 1 : 0)); } else if (strcmp([obj objCType], @encode(int)) == 0) { sqlite3_bind_int64(pStmt, idx, [obj longValue]); } else if (strcmp([obj objCType], @encode(long)) == 0) { sqlite3_bind_int64(pStmt, idx, [obj longValue]); } else if (strcmp([obj objCType], @encode(long long)) == 0) { sqlite3_bind_int64(pStmt, idx, [obj longLongValue]); } else if (strcmp([obj objCType], @encode(unsigned long long)) == 0) { sqlite3_bind_int64(pStmt, idx, (long long)[obj unsignedLongLongValue]); } else if (strcmp([obj objCType], @encode(float)) == 0) { sqlite3_bind_double(pStmt, idx, [obj floatValue]); } else if (strcmp([obj objCType], @encode(double)) == 0) { sqlite3_bind_double(pStmt, idx, [obj doubleValue]); } else { sqlite3_bind_text(pStmt, idx, [[obj description] UTF8String], -1, SQLITE_STATIC); } } else { sqlite3_bind_text(pStmt, idx, [[obj description] UTF8String], -1, SQLITE_STATIC); } } - (void)extractSQL:(NSString *)sql argumentsList:(va_list)args intoString:(NSMutableString *)cleanedSQL arguments:(NSMutableArray *)arguments { NSUInteger length = [sql length]; unichar last = '\0'; for (NSUInteger i = 0; i < length; ++i) { id arg = nil; unichar current = [sql characterAtIndex:i]; unichar add = current; if (last == '%') { switch (current) { case '@': arg = va_arg(args, id); break; case 'c': // warning: second argument to 'va_arg' is of promotable type 'char'; this va_arg has undefined behavior because arguments will be promoted to 'int' arg = [NSString stringWithFormat:@"%c", va_arg(args, int)]; break; case 's': arg = [NSString stringWithUTF8String:va_arg(args, char*)]; break; case 'd': case 'D': case 'i': arg = [NSNumber numberWithInt:va_arg(args, int)]; break; case 'u': case 'U': arg = [NSNumber numberWithUnsignedInt:va_arg(args, unsigned int)]; break; case 'h': i++; if (i < length && [sql characterAtIndex:i] == 'i') { // warning: second argument to 'va_arg' is of promotable type 'short'; this va_arg has undefined behavior because arguments will be promoted to 'int' arg = [NSNumber numberWithShort:(short)(va_arg(args, int))]; } else if (i < length && [sql characterAtIndex:i] == 'u') { // warning: second argument to 'va_arg' is of promotable type 'unsigned short'; this va_arg has undefined behavior because arguments will be promoted to 'int' arg = [NSNumber numberWithUnsignedShort:(unsigned short)(va_arg(args, uint))]; } else { i--; } break; case 'q': i++; if (i < length && [sql characterAtIndex:i] == 'i') { arg = [NSNumber numberWithLongLong:va_arg(args, long long)]; } else if (i < length && [sql characterAtIndex:i] == 'u') { arg = [NSNumber numberWithUnsignedLongLong:va_arg(args, unsigned long long)]; } else { i--; } break; case 'f': arg = [NSNumber numberWithDouble:va_arg(args, double)]; break; case 'g': // warning: second argument to 'va_arg' is of promotable type 'float'; this va_arg has undefined behavior because arguments will be promoted to 'double' arg = [NSNumber numberWithFloat:(float)(va_arg(args, double))]; break; case 'l': i++; if (i < length) { unichar next = [sql characterAtIndex:i]; if (next == 'l') { i++; if (i < length && [sql characterAtIndex:i] == 'd') { //%lld arg = [NSNumber numberWithLongLong:va_arg(args, long long)]; } else if (i < length && [sql characterAtIndex:i] == 'u') { //%llu arg = [NSNumber numberWithUnsignedLongLong:va_arg(args, unsigned long long)]; } else { i--; } } else if (next == 'd') { //%ld arg = [NSNumber numberWithLong:va_arg(args, long)]; } else if (next == 'u') { //%lu arg = [NSNumber numberWithUnsignedLong:va_arg(args, unsigned long)]; } else { i--; } } else { i--; } break; default: // something else that we can't interpret. just pass it on through like normal break; } } else if (current == '%') { // percent sign; skip this character add = '\0'; } if (arg != nil) { [cleanedSQL appendString:@"?"]; [arguments addObject:arg]; } else if (add == (unichar)'@' && last == (unichar) '%') { [cleanedSQL appendFormat:@"NULL"]; } else if (add != '\0') { [cleanedSQL appendFormat:@"%C", add]; } last = current; } } - (FMResultSet *)executeQuery:(NSString *)sql withParameterDictionary:(NSDictionary *)arguments { return [self executeQuery:sql withArgumentsInArray:nil orDictionary:arguments orVAList:nil]; } - (FMResultSet *)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray*)arrayArgs orDictionary:(NSDictionary *)dictionaryArgs orVAList:(va_list)args { if (![self databaseExists]) { return 0x00; } if (_isExecutingStatement) { [self warnInUse]; return 0x00; } _isExecutingStatement = YES; int rc = 0x00; sqlite3_stmt *pStmt = 0x00; FMStatement *statement = 0x00; FMResultSet *rs = 0x00; if (_traceExecution && sql) { NSLog(@"%@ executeQuery: %@", self, sql); } if (_shouldCacheStatements) { statement = [self cachedStatementForQuery:sql]; pStmt = statement ? [statement statement] : 0x00; [statement reset]; } int numberOfRetries = 0; BOOL retry = NO; if (!pStmt) { do { retry = NO; rc = sqlite3_prepare_v2(_db, [sql UTF8String], -1, &pStmt, 0); if (SQLITE_BUSY == rc || SQLITE_LOCKED == rc) { retry = YES; usleep(20); if (_busyRetryTimeout && (numberOfRetries++ > _busyRetryTimeout)) { NSLog(@"%s:%d Database busy (%@)", __FUNCTION__, __LINE__, [self databasePath]); NSLog(@"Database busy"); sqlite3_finalize(pStmt); _isExecutingStatement = NO; return nil; } } else if (SQLITE_OK != rc) { if (_logsErrors) { NSLog(@"DB Error: %d \"%@\"", [self lastErrorCode], [self lastErrorMessage]); NSLog(@"DB Query: %@", sql); NSLog(@"DB Path: %@", _databasePath); #ifndef NS_BLOCK_ASSERTIONS if (_crashOnErrors) { abort(); NSAssert2(false, @"DB Error: %d \"%@\"", [self lastErrorCode], [self lastErrorMessage]); } #endif } sqlite3_finalize(pStmt); _isExecutingStatement = NO; return nil; } } while (retry); } id obj; int idx = 0; int queryCount = sqlite3_bind_parameter_count(pStmt); // pointed out by Dominic Yu (thanks!) // If dictionaryArgs is passed in, that means we are using sqlite's named parameter support if (dictionaryArgs) { for (NSString *dictionaryKey in [dictionaryArgs allKeys]) { // Prefix the key with a colon. NSString *parameterName = [[NSString alloc] initWithFormat:@":%@", dictionaryKey]; // Get the index for the parameter name. int namedIdx = sqlite3_bind_parameter_index(pStmt, [parameterName UTF8String]); FMDBRelease(parameterName); if (namedIdx > 0) { // Standard binding from here. [self bindObject:[dictionaryArgs objectForKey:dictionaryKey] toColumn:namedIdx inStatement:pStmt]; // increment the binding count, so our check below works out idx++; } else { NSLog(@"Could not find index for %@", dictionaryKey); } } } else { while (idx < queryCount) { if (arrayArgs) { obj = [arrayArgs objectAtIndex:(NSUInteger)idx]; } else { obj = va_arg(args, id); } if (_traceExecution) { if ([obj isKindOfClass:[NSData class]]) { NSLog(@"data: %ld bytes", (unsigned long)[(NSData*)obj length]); } else { NSLog(@"obj: %@", obj); } } idx++; [self bindObject:obj toColumn:idx inStatement:pStmt]; } } if (idx != queryCount) { NSLog(@"Error: the bind count is not correct for the # of variables (executeQuery)"); sqlite3_finalize(pStmt); _isExecutingStatement = NO; return nil; } FMDBRetain(statement); // to balance the release below if (!statement) { statement = [[FMStatement alloc] init]; [statement setStatement:pStmt]; if (_shouldCacheStatements) { [self setCachedStatement:statement forQuery:sql]; } } // the statement gets closed in rs's dealloc or [rs close]; rs = [FMResultSet resultSetWithStatement:statement usingParentDatabase:self]; [rs setQuery:sql]; NSValue *openResultSet = [NSValue valueWithNonretainedObject:rs]; [_openResultSets addObject:openResultSet]; [statement setUseCount:[statement useCount] + 1]; FMDBRelease(statement); _isExecutingStatement = NO; return rs; } - (FMResultSet *)executeQuery:(NSString*)sql, ... { va_list args; va_start(args, sql); id result = [self executeQuery:sql withArgumentsInArray:nil orDictionary:nil orVAList:args]; va_end(args); return result; } - (FMResultSet *)executeQueryWithFormat:(NSString*)format, ... { va_list args; va_start(args, format); NSMutableString *sql = [NSMutableString stringWithCapacity:[format length]]; NSMutableArray *arguments = [NSMutableArray array]; [self extractSQL:format argumentsList:args intoString:sql arguments:arguments]; va_end(args); return [self executeQuery:sql withArgumentsInArray:arguments]; } - (FMResultSet *)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray *)arguments { return [self executeQuery:sql withArgumentsInArray:arguments orDictionary:nil orVAList:nil]; } - (BOOL)executeUpdate:(NSString*)sql error:(NSError**)outErr withArgumentsInArray:(NSArray*)arrayArgs orDictionary:(NSDictionary *)dictionaryArgs orVAList:(va_list)args { if (![self databaseExists]) { return NO; } if (_isExecutingStatement) { [self warnInUse]; return NO; } _isExecutingStatement = YES; int rc = 0x00; sqlite3_stmt *pStmt = 0x00; FMStatement *cachedStmt = 0x00; if (_traceExecution && sql) { NSLog(@"%@ executeUpdate: %@", self, sql); } if (_shouldCacheStatements) { cachedStmt = [self cachedStatementForQuery:sql]; pStmt = cachedStmt ? [cachedStmt statement] : 0x00; [cachedStmt reset]; } int numberOfRetries = 0; BOOL retry = NO; if (!pStmt) { do { retry = NO; rc = sqlite3_prepare_v2(_db, [sql UTF8String], -1, &pStmt, 0); if (SQLITE_BUSY == rc || SQLITE_LOCKED == rc) { retry = YES; usleep(20); if (_busyRetryTimeout && (numberOfRetries++ > _busyRetryTimeout)) { NSLog(@"%s:%d Database busy (%@)", __FUNCTION__, __LINE__, [self databasePath]); NSLog(@"Database busy"); sqlite3_finalize(pStmt); _isExecutingStatement = NO; return NO; } } else if (SQLITE_OK != rc) { if (_logsErrors) { NSLog(@"DB Error: %d \"%@\"", [self lastErrorCode], [self lastErrorMessage]); NSLog(@"DB Query: %@", sql); NSLog(@"DB Path: %@", _databasePath); #ifndef NS_BLOCK_ASSERTIONS if (_crashOnErrors) { abort(); NSAssert2(false, @"DB Error: %d \"%@\"", [self lastErrorCode], [self lastErrorMessage]); } #endif } sqlite3_finalize(pStmt); if (outErr) { *outErr = [self errorWithMessage:[NSString stringWithUTF8String:sqlite3_errmsg(_db)]]; } _isExecutingStatement = NO; return NO; } } while (retry); } id obj; int idx = 0; int queryCount = sqlite3_bind_parameter_count(pStmt); // If dictionaryArgs is passed in, that means we are using sqlite's named parameter support if (dictionaryArgs) { for (NSString *dictionaryKey in [dictionaryArgs allKeys]) { // Prefix the key with a colon. NSString *parameterName = [[NSString alloc] initWithFormat:@":%@", dictionaryKey]; // Get the index for the parameter name. int namedIdx = sqlite3_bind_parameter_index(pStmt, [parameterName UTF8String]); FMDBRelease(parameterName); if (namedIdx > 0) { // Standard binding from here. [self bindObject:[dictionaryArgs objectForKey:dictionaryKey] toColumn:namedIdx inStatement:pStmt]; // increment the binding count, so our check below works out idx++; } else { NSLog(@"Could not find index for %@", dictionaryKey); } } } else { while (idx < queryCount) { if (arrayArgs) { obj = [arrayArgs objectAtIndex:(NSUInteger)idx]; } else { obj = va_arg(args, id); } if (_traceExecution) { if ([obj isKindOfClass:[NSData class]]) { NSLog(@"data: %ld bytes", (unsigned long)[(NSData*)obj length]); } else { NSLog(@"obj: %@", obj); } } idx++; [self bindObject:obj toColumn:idx inStatement:pStmt]; } } if (idx != queryCount) { NSLog(@"Error: the bind count (%d) is not correct for the # of variables in the query (%d) (%@) (executeUpdate)", idx, queryCount, sql); sqlite3_finalize(pStmt); _isExecutingStatement = NO; return NO; } /* Call sqlite3_step() to run the virtual machine. Since the SQL being ** executed is not a SELECT statement, we assume no data will be returned. */ numberOfRetries = 0; do { rc = sqlite3_step(pStmt); retry = NO; if (SQLITE_BUSY == rc || SQLITE_LOCKED == rc) { // this will happen if the db is locked, like if we are doing an update or insert. // in that case, retry the step... and maybe wait just 10 milliseconds. retry = YES; if (SQLITE_LOCKED == rc) { rc = sqlite3_reset(pStmt); if (rc != SQLITE_LOCKED) { NSLog(@"Unexpected result from sqlite3_reset (%d) eu", rc); } } usleep(20); if (_busyRetryTimeout && (numberOfRetries++ > _busyRetryTimeout)) { NSLog(@"%s:%d Database busy (%@)", __FUNCTION__, __LINE__, [self databasePath]); NSLog(@"Database busy"); retry = NO; } } else if (SQLITE_DONE == rc) { // all is well, let's return. } else if (SQLITE_ERROR == rc) { NSLog(@"Error calling sqlite3_step (%d: %s) SQLITE_ERROR", rc, sqlite3_errmsg(_db)); NSLog(@"DB Query: %@", sql); } else if (SQLITE_MISUSE == rc) { // uh oh. NSLog(@"Error calling sqlite3_step (%d: %s) SQLITE_MISUSE", rc, sqlite3_errmsg(_db)); NSLog(@"DB Query: %@", sql); } else { // wtf? NSLog(@"Unknown error calling sqlite3_step (%d: %s) eu", rc, sqlite3_errmsg(_db)); NSLog(@"DB Query: %@", sql); } } while (retry); if (rc == SQLITE_ROW) { NSAssert1(NO, @"A executeUpdate is being called with a query string '%@'", sql); } if (_shouldCacheStatements && !cachedStmt) { cachedStmt = [[FMStatement alloc] init]; [cachedStmt setStatement:pStmt]; [self setCachedStatement:cachedStmt forQuery:sql]; FMDBRelease(cachedStmt); } int closeErrorCode; if (cachedStmt) { [cachedStmt setUseCount:[cachedStmt useCount] + 1]; closeErrorCode = sqlite3_reset(pStmt); } else { /* Finalize the virtual machine. This releases all memory and other ** resources allocated by the sqlite3_prepare() call above. */ closeErrorCode = sqlite3_finalize(pStmt); } if (closeErrorCode != SQLITE_OK) { NSLog(@"Unknown error finalizing or resetting statement (%d: %s)", closeErrorCode, sqlite3_errmsg(_db)); NSLog(@"DB Query: %@", sql); } _isExecutingStatement = NO; return (rc == SQLITE_DONE || rc == SQLITE_OK); } - (BOOL)executeUpdate:(NSString*)sql, ... { va_list args; va_start(args, sql); BOOL result = [self executeUpdate:sql error:nil withArgumentsInArray:nil orDictionary:nil orVAList:args]; va_end(args); return result; } - (BOOL)executeUpdate:(NSString*)sql withArgumentsInArray:(NSArray *)arguments { return [self executeUpdate:sql error:nil withArgumentsInArray:arguments orDictionary:nil orVAList:nil]; } - (BOOL)executeUpdate:(NSString*)sql withParameterDictionary:(NSDictionary *)arguments { return [self executeUpdate:sql error:nil withArgumentsInArray:nil orDictionary:arguments orVAList:nil]; } - (BOOL)executeUpdateWithFormat:(NSString*)format, ... { va_list args; va_start(args, format); NSMutableString *sql = [NSMutableString stringWithCapacity:[format length]]; NSMutableArray *arguments = [NSMutableArray array]; [self extractSQL:format argumentsList:args intoString:sql arguments:arguments]; va_end(args); return [self executeUpdate:sql withArgumentsInArray:arguments]; } - (BOOL)update:(NSString*)sql withErrorAndBindings:(NSError**)outErr, ... { va_list args; va_start(args, outErr); BOOL result = [self executeUpdate:sql error:outErr withArgumentsInArray:nil orDictionary:nil orVAList:args]; va_end(args); return result; } - (BOOL)rollback { BOOL b = [self executeUpdate:@"rollback transaction"]; if (b) { _inTransaction = NO; } return b; } - (BOOL)commit { BOOL b = [self executeUpdate:@"commit transaction"]; if (b) { _inTransaction = NO; } return b; } - (BOOL)beginDeferredTransaction { BOOL b = [self executeUpdate:@"begin deferred transaction"]; if (b) { _inTransaction = YES; } return b; } - (BOOL)beginTransaction { BOOL b = [self executeUpdate:@"begin exclusive transaction"]; if (b) { _inTransaction = YES; } return b; } - (BOOL)inTransaction { return _inTransaction; } #if SQLITE_VERSION_NUMBER >= 3007000 - (BOOL)startSavePointWithName:(NSString*)name error:(NSError**)outErr { // FIXME: make sure the savepoint name doesn't have a ' in it. NSParameterAssert(name); if (![self executeUpdate:[NSString stringWithFormat:@"savepoint '%@';", name]]) { if (outErr) { *outErr = [self lastError]; } return NO; } return YES; } - (BOOL)releaseSavePointWithName:(NSString*)name error:(NSError**)outErr { NSParameterAssert(name); BOOL worked = [self executeUpdate:[NSString stringWithFormat:@"release savepoint '%@';", name]]; if (!worked && outErr) { *outErr = [self lastError]; } return worked; } - (BOOL)rollbackToSavePointWithName:(NSString*)name error:(NSError**)outErr { NSParameterAssert(name); BOOL worked = [self executeUpdate:[NSString stringWithFormat:@"rollback transaction to savepoint '%@';", name]]; if (!worked && *outErr) { *outErr = [self lastError]; } return worked; } - (NSError*)inSavePoint:(void (^)(BOOL *rollback))block { static unsigned long savePointIdx = 0; NSString *name = [NSString stringWithFormat:@"dbSavePoint%ld", savePointIdx++]; BOOL shouldRollback = NO; NSError *err = 0x00; if (![self startSavePointWithName:name error:&err]) { return err; } block(&shouldRollback); if (shouldRollback) { [self rollbackToSavePointWithName:name error:&err]; } else { [self releaseSavePointWithName:name error:&err]; } return err; } #endif - (BOOL)shouldCacheStatements { return _shouldCacheStatements; } - (void)setShouldCacheStatements:(BOOL)value { _shouldCacheStatements = value; if (_shouldCacheStatements && !_cachedStatements) { [self setCachedStatements:[NSMutableDictionary dictionary]]; } if (!_shouldCacheStatements) { [self setCachedStatements:nil]; } } void FMDBBlockSQLiteCallBackFunction(sqlite3_context *context, int argc, sqlite3_value **argv); void FMDBBlockSQLiteCallBackFunction(sqlite3_context *context, int argc, sqlite3_value **argv) { #if ! __has_feature(objc_arc) void (^block)(sqlite3_context *context, int argc, sqlite3_value **argv) = (id)sqlite3_user_data(context); #else void (^block)(sqlite3_context *context, int argc, sqlite3_value **argv) = (__bridge id)sqlite3_user_data(context); #endif block(context, argc, argv); } - (void)makeFunctionNamed:(NSString*)name maximumArguments:(int)count withBlock:(void (^)(sqlite3_context *context, int argc, sqlite3_value **argv))block { if (!_openFunctions) { _openFunctions = [NSMutableSet new]; } id b = FMDBReturnAutoreleased([block copy]); [_openFunctions addObject:b]; /* I tried adding custom functions to release the block when the connection is destroyed- but they seemed to never be called, so we use _openFunctions to store the values instead. */ #if ! __has_feature(objc_arc) sqlite3_create_function([self sqliteHandle], [name UTF8String], count, SQLITE_UTF8, (void*)b, &FMDBBlockSQLiteCallBackFunction, 0x00, 0x00); #else sqlite3_create_function([self sqliteHandle], [name UTF8String], count, SQLITE_UTF8, (__bridge void*)b, &FMDBBlockSQLiteCallBackFunction, 0x00, 0x00); #endif } @end @implementation FMStatement @synthesize statement=_statement; @synthesize query=_query; @synthesize useCount=_useCount; - (void)finalize { [self close]; [super finalize]; } - (void)dealloc { [self close]; FMDBRelease(_query); #if ! __has_feature(objc_arc) [super dealloc]; #endif } - (void)close { if (_statement) { sqlite3_finalize(_statement); _statement = 0x00; } } - (void)reset { if (_statement) { sqlite3_reset(_statement); } } - (NSString*)description { return [NSString stringWithFormat:@"%@ %ld hit(s) for query %@", [super description], _useCount, _query]; } @end ================================================ FILE: zhuishushenqi/Vendor/XimalayaSDK_iOS_5.5.1/include/FMDB/FMDatabaseAdditions.h ================================================ // // FMDatabaseAdditions.h // fmkit // // Created by August Mueller on 10/30/05. // Copyright 2005 Flying Meat Inc.. All rights reserved. // #import <Foundation/Foundation.h> @interface FMDatabase (FMDatabaseAdditions) - (int)intForQuery:(NSString*)objs, ...; - (long)longForQuery:(NSString*)objs, ...; - (BOOL)boolForQuery:(NSString*)objs, ...; - (double)doubleForQuery:(NSString*)objs, ...; - (NSString*)stringForQuery:(NSString*)objs, ...; - (NSData*)dataForQuery:(NSString*)objs, ...; - (NSDate*)dateForQuery:(NSString*)objs, ...; // Notice that there's no dataNoCopyForQuery:. // That would be a bad idea, because we close out the result set, and then what // happens to the data that we just didn't copy? Who knows, not I. - (BOOL)tableExists:(NSString*)tableName; - (FMResultSet*)getSchema; - (FMResultSet*)getTableSchema:(NSString*)tableName; - (BOOL)columnExists:(NSString*)columnName inTableWithName:(NSString*)tableName; - (BOOL)validateSQL:(NSString*)sql error:(NSError**)error; // deprecated - use columnExists:inTableWithName: instead. - (BOOL)columnExists:(NSString*)tableName columnName:(NSString*)columnName __attribute__ ((deprecated)); @end ================================================ FILE: zhuishushenqi/Vendor/XimalayaSDK_iOS_5.5.1/include/FMDB/FMDatabaseAdditions.m ================================================ // // FMDatabaseAdditions.m // fmkit // // Created by August Mueller on 10/30/05. // Copyright 2005 Flying Meat Inc.. All rights reserved. // #import "FMDatabase.h" #import "FMDatabaseAdditions.h" @interface FMDatabase (PrivateStuff) - (FMResultSet *)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray*)arrayArgs orDictionary:(NSDictionary *)dictionaryArgs orVAList:(va_list)args; @end @implementation FMDatabase (FMDatabaseAdditions) #define RETURN_RESULT_FOR_QUERY_WITH_SELECTOR(type, sel) \ va_list args; \ va_start(args, query); \ FMResultSet *resultSet = [self executeQuery:query withArgumentsInArray:0x00 orDictionary:0x00 orVAList:args]; \ va_end(args); \ if (![resultSet next]) { return (type)0; } \ type ret = [resultSet sel:0]; \ [resultSet close]; \ [resultSet setParentDB:nil]; \ return ret; - (NSString*)stringForQuery:(NSString*)query, ... { RETURN_RESULT_FOR_QUERY_WITH_SELECTOR(NSString *, stringForColumnIndex); } - (int)intForQuery:(NSString*)query, ... { RETURN_RESULT_FOR_QUERY_WITH_SELECTOR(int, intForColumnIndex); } - (long)longForQuery:(NSString*)query, ... { RETURN_RESULT_FOR_QUERY_WITH_SELECTOR(long, longForColumnIndex); } - (BOOL)boolForQuery:(NSString*)query, ... { RETURN_RESULT_FOR_QUERY_WITH_SELECTOR(BOOL, boolForColumnIndex); } - (double)doubleForQuery:(NSString*)query, ... { RETURN_RESULT_FOR_QUERY_WITH_SELECTOR(double, doubleForColumnIndex); } - (NSData*)dataForQuery:(NSString*)query, ... { RETURN_RESULT_FOR_QUERY_WITH_SELECTOR(NSData *, dataForColumnIndex); } - (NSDate*)dateForQuery:(NSString*)query, ... { RETURN_RESULT_FOR_QUERY_WITH_SELECTOR(NSDate *, dateForColumnIndex); } - (BOOL)tableExists:(NSString*)tableName { tableName = [tableName lowercaseString]; FMResultSet *rs = [self executeQuery:@"select [sql] from sqlite_master where [type] = 'table' and lower(name) = ?", tableName]; //if at least one next exists, table exists BOOL returnBool = [rs next]; //close and free object [rs close]; return returnBool; } /* get table with list of tables: result colums: type[STRING], name[STRING],tbl_name[STRING],rootpage[INTEGER],sql[STRING] check if table exist in database (patch from OZLB) */ - (FMResultSet*)getSchema { //result colums: type[STRING], name[STRING],tbl_name[STRING],rootpage[INTEGER],sql[STRING] FMResultSet *rs = [self executeQuery:@"SELECT type, name, tbl_name, rootpage, sql FROM (SELECT * FROM sqlite_master UNION ALL SELECT * FROM sqlite_temp_master) WHERE type != 'meta' AND name NOT LIKE 'sqlite_%' ORDER BY tbl_name, type DESC, name"]; return rs; } /* get table schema: result colums: cid[INTEGER], name,type [STRING], notnull[INTEGER], dflt_value[],pk[INTEGER] */ - (FMResultSet*)getTableSchema:(NSString*)tableName { //result colums: cid[INTEGER], name,type [STRING], notnull[INTEGER], dflt_value[],pk[INTEGER] FMResultSet *rs = [self executeQuery:[NSString stringWithFormat: @"PRAGMA table_info('%@')", tableName]]; return rs; } - (BOOL)columnExists:(NSString*)columnName inTableWithName:(NSString*)tableName { BOOL returnBool = NO; tableName = [tableName lowercaseString]; columnName = [columnName lowercaseString]; FMResultSet *rs = [self getTableSchema:tableName]; //check if column is present in table schema while ([rs next]) { if ([[[rs stringForColumn:@"name"] lowercaseString] isEqualToString:columnName]) { returnBool = YES; break; } } //If this is not done FMDatabase instance stays out of pool [rs close]; return returnBool; } #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-implementations" - (BOOL)columnExists:(NSString*)tableName columnName:(NSString*)columnName __attribute__ ((deprecated)) { return [self columnExists:columnName inTableWithName:tableName]; } #pragma clang diagnostic pop - (BOOL)validateSQL:(NSString*)sql error:(NSError**)error { sqlite3_stmt *pStmt = NULL; BOOL validationSucceeded = YES; BOOL keepTrying = YES; int numberOfRetries = 0; while (keepTrying == YES) { keepTrying = NO; int rc = sqlite3_prepare_v2(_db, [sql UTF8String], -1, &pStmt, 0); if (rc == SQLITE_BUSY || rc == SQLITE_LOCKED) { keepTrying = YES; usleep(20); if (_busyRetryTimeout && (numberOfRetries++ > _busyRetryTimeout)) { NSLog(@"%s:%d Database busy (%@)", __FUNCTION__, __LINE__, [self databasePath]); NSLog(@"Database busy"); } } else if (rc != SQLITE_OK) { validationSucceeded = NO; if (error) { *error = [NSError errorWithDomain:NSCocoaErrorDomain code:[self lastErrorCode] userInfo:[NSDictionary dictionaryWithObject:[self lastErrorMessage] forKey:NSLocalizedDescriptionKey]]; } } } sqlite3_finalize(pStmt); return validationSucceeded; } @end ================================================ FILE: zhuishushenqi/Vendor/XimalayaSDK_iOS_5.5.1/include/FMDB/FMDatabasePool.h ================================================ // // FMDatabasePool.h // fmdb // // Created by August Mueller on 6/22/11. // Copyright 2011 Flying Meat Inc. All rights reserved. // #import <Foundation/Foundation.h> #import "sqlite3.h" /* ***README OR SUFFER*** Before using FMDatabasePool, please consider using FMDatabaseQueue instead. If you really really really know what you're doing and FMDatabasePool is what you really really need (ie, you're using a read only database), OK you can use it. But just be careful not to deadlock! For an example on deadlocking, search for: ONLY_USE_THE_POOL_IF_YOU_ARE_DOING_READS_OTHERWISE_YOULL_DEADLOCK_USE_FMDATABASEQUEUE_INSTEAD in the main.m file. */ @class FMDatabase; @interface FMDatabasePool : NSObject { NSString *_path; dispatch_queue_t _lockQueue; NSMutableArray *_databaseInPool; NSMutableArray *_databaseOutPool; __unsafe_unretained id _delegate; NSUInteger _maximumNumberOfDatabasesToCreate; } @property (atomic, retain) NSString *path; @property (atomic, assign) id delegate; @property (atomic, assign) NSUInteger maximumNumberOfDatabasesToCreate; + (id)databasePoolWithPath:(NSString*)aPath; - (id)initWithPath:(NSString*)aPath; - (NSUInteger)countOfCheckedInDatabases; - (NSUInteger)countOfCheckedOutDatabases; - (NSUInteger)countOfOpenDatabases; - (void)releaseAllDatabases; - (void)inDatabase:(void (^)(FMDatabase *db))block; - (void)inTransaction:(void (^)(FMDatabase *db, BOOL *rollback))block; - (void)inDeferredTransaction:(void (^)(FMDatabase *db, BOOL *rollback))block; #if SQLITE_VERSION_NUMBER >= 3007000 // NOTE: you can not nest these, since calling it will pull another database out of the pool and you'll get a deadlock. // If you need to nest, use FMDatabase's startSavePointWithName:error: instead. - (NSError*)inSavePoint:(void (^)(FMDatabase *db, BOOL *rollback))block; #endif @end @interface NSObject (FMDatabasePoolDelegate) - (BOOL)databasePool:(FMDatabasePool*)pool shouldAddDatabaseToPool:(FMDatabase*)database; @end ================================================ FILE: zhuishushenqi/Vendor/XimalayaSDK_iOS_5.5.1/include/FMDB/FMDatabasePool.m ================================================ // // FMDatabasePool.m // fmdb // // Created by August Mueller on 6/22/11. // Copyright 2011 Flying Meat Inc. All rights reserved. // #import "FMDatabasePool.h" #import "FMDatabase.h" @interface FMDatabasePool() - (void)pushDatabaseBackInPool:(FMDatabase*)db; - (FMDatabase*)db; @end @implementation FMDatabasePool @synthesize path=_path; @synthesize delegate=_delegate; @synthesize maximumNumberOfDatabasesToCreate=_maximumNumberOfDatabasesToCreate; + (id)databasePoolWithPath:(NSString*)aPath { return FMDBReturnAutoreleased([[self alloc] initWithPath:aPath]); } - (id)initWithPath:(NSString*)aPath { self = [super init]; if (self != nil) { _path = [aPath copy]; _lockQueue = dispatch_queue_create([[NSString stringWithFormat:@"fmdb.%@", self] UTF8String], NULL); _databaseInPool = FMDBReturnRetained([NSMutableArray array]); _databaseOutPool = FMDBReturnRetained([NSMutableArray array]); } return self; } - (void)dealloc { _delegate = 0x00; FMDBRelease(_path); FMDBRelease(_databaseInPool); FMDBRelease(_databaseOutPool); if (_lockQueue) { FMDBDispatchQueueRelease(_lockQueue); _lockQueue = 0x00; } #if ! __has_feature(objc_arc) [super dealloc]; #endif } - (void)executeLocked:(void (^)(void))aBlock { dispatch_sync(_lockQueue, aBlock); } - (void)pushDatabaseBackInPool:(FMDatabase*)db { if (!db) { // db can be null if we set an upper bound on the # of databases to create. return; } [self executeLocked:^() { if ([_databaseInPool containsObject:db]) { [[NSException exceptionWithName:@"Database already in pool" reason:@"The FMDatabase being put back into the pool is already present in the pool" userInfo:nil] raise]; } [_databaseInPool addObject:db]; [_databaseOutPool removeObject:db]; }]; } - (FMDatabase*)db { __block FMDatabase *db; [self executeLocked:^() { db = [_databaseInPool lastObject]; if (db) { [_databaseOutPool addObject:db]; [_databaseInPool removeLastObject]; } else { if (_maximumNumberOfDatabasesToCreate) { NSUInteger currentCount = [_databaseOutPool count] + [_databaseInPool count]; if (currentCount >= _maximumNumberOfDatabasesToCreate) { NSLog(@"Maximum number of databases (%ld) has already been reached!", (long)currentCount); return; } } db = [FMDatabase databaseWithPath:_path]; } //This ensures that the db is opened before returning if ([db open]) { if ([_delegate respondsToSelector:@selector(databasePool:shouldAddDatabaseToPool:)] && ![_delegate databasePool:self shouldAddDatabaseToPool:db]) { [db close]; db = 0x00; } else { //It should not get added in the pool twice if lastObject was found if (![_databaseOutPool containsObject:db]) { [_databaseOutPool addObject:db]; } } } else { NSLog(@"Could not open up the database at path %@", _path); db = 0x00; } }]; return db; } - (NSUInteger)countOfCheckedInDatabases { __block NSUInteger count; [self executeLocked:^() { count = [_databaseInPool count]; }]; return count; } - (NSUInteger)countOfCheckedOutDatabases { __block NSUInteger count; [self executeLocked:^() { count = [_databaseOutPool count]; }]; return count; } - (NSUInteger)countOfOpenDatabases { __block NSUInteger count; [self executeLocked:^() { count = [_databaseOutPool count] + [_databaseInPool count]; }]; return count; } - (void)releaseAllDatabases { [self executeLocked:^() { [_databaseOutPool removeAllObjects]; [_databaseInPool removeAllObjects]; }]; } - (void)inDatabase:(void (^)(FMDatabase *db))block { FMDatabase *db = [self db]; block(db); [self pushDatabaseBackInPool:db]; } - (void)beginTransaction:(BOOL)useDeferred withBlock:(void (^)(FMDatabase *db, BOOL *rollback))block { BOOL shouldRollback = NO; FMDatabase *db = [self db]; if (useDeferred) { [db beginDeferredTransaction]; } else { [db beginTransaction]; } block(db, &shouldRollback); if (shouldRollback) { [db rollback]; } else { [db commit]; } [self pushDatabaseBackInPool:db]; } - (void)inDeferredTransaction:(void (^)(FMDatabase *db, BOOL *rollback))block { [self beginTransaction:YES withBlock:block]; } - (void)inTransaction:(void (^)(FMDatabase *db, BOOL *rollback))block { [self beginTransaction:NO withBlock:block]; } #if SQLITE_VERSION_NUMBER >= 3007000 - (NSError*)inSavePoint:(void (^)(FMDatabase *db, BOOL *rollback))block { static unsigned long savePointIdx = 0; NSString *name = [NSString stringWithFormat:@"savePoint%ld", savePointIdx++]; BOOL shouldRollback = NO; FMDatabase *db = [self db]; NSError *err = 0x00; if (![db startSavePointWithName:name error:&err]) { [self pushDatabaseBackInPool:db]; return err; } block(db, &shouldRollback); if (shouldRollback) { [db rollbackToSavePointWithName:name error:&err]; } else { [db releaseSavePointWithName:name error:&err]; } [self pushDatabaseBackInPool:db]; return err; } #endif @end ================================================ FILE: zhuishushenqi/Vendor/XimalayaSDK_iOS_5.5.1/include/FMDB/FMDatabaseQueue.h ================================================ // // FMDatabaseQueue.h // fmdb // // Created by August Mueller on 6/22/11. // Copyright 2011 Flying Meat Inc. All rights reserved. // #import <Foundation/Foundation.h> #import "sqlite3.h" @class FMDatabase; @interface FMDatabaseQueue : NSObject { NSString *_path; dispatch_queue_t _queue; FMDatabase *_db; } @property (atomic, retain) NSString *path; + (id)databaseQueueWithPath:(NSString*)aPath; - (id)initWithPath:(NSString*)aPath; - (void)close; - (void)inDatabase:(void (^)(FMDatabase *db))block; - (void)inTransaction:(void (^)(FMDatabase *db, BOOL *rollback))block; - (void)inDeferredTransaction:(void (^)(FMDatabase *db, BOOL *rollback))block; #if SQLITE_VERSION_NUMBER >= 3007000 // NOTE: you can not nest these, since calling it will pull another database out of the pool and you'll get a deadlock. // If you need to nest, use FMDatabase's startSavePointWithName:error: instead. - (NSError*)inSavePoint:(void (^)(FMDatabase *db, BOOL *rollback))block; #endif @end ================================================ FILE: zhuishushenqi/Vendor/XimalayaSDK_iOS_5.5.1/include/FMDB/FMDatabaseQueue.m ================================================ // // FMDatabaseQueue.m // fmdb // // Created by August Mueller on 6/22/11. // Copyright 2011 Flying Meat Inc. All rights reserved. // #import "FMDatabaseQueue.h" #import "FMDatabase.h" /* Note: we call [self retain]; before using dispatch_sync, just incase FMDatabaseQueue is released on another thread and we're in the middle of doing something in dispatch_sync */ @implementation FMDatabaseQueue @synthesize path = _path; + (id)databaseQueueWithPath:(NSString*)aPath { FMDatabaseQueue *q = [[self alloc] initWithPath:aPath]; FMDBAutorelease(q); return q; } - (id)initWithPath:(NSString*)aPath { self = [super init]; if (self != nil) { _db = [FMDatabase databaseWithPath:aPath]; FMDBRetain(_db); if (![_db open]) { NSLog(@"Could not create database queue for path %@", aPath); FMDBRelease(self); return 0x00; } _path = FMDBReturnRetained(aPath); _queue = dispatch_queue_create([[NSString stringWithFormat:@"fmdb.%@", self] UTF8String], NULL); } return self; } - (void)dealloc { FMDBRelease(_db); FMDBRelease(_path); if (_queue) { FMDBDispatchQueueRelease(_queue); _queue = 0x00; } #if ! __has_feature(objc_arc) [super dealloc]; #endif } - (void)close { FMDBRetain(self); dispatch_sync(_queue, ^() { [_db close]; FMDBRelease(_db); _db = 0x00; }); FMDBRelease(self); } - (FMDatabase*)database { if (!_db) { _db = FMDBReturnRetained([FMDatabase databaseWithPath:_path]); if (![_db open]) { NSLog(@"FMDatabaseQueue could not reopen database for path %@", _path); FMDBRelease(_db); _db = 0x00; return 0x00; } } return _db; } - (void)inDatabase:(void (^)(FMDatabase *db))block { FMDBRetain(self); dispatch_sync(_queue, ^() { FMDatabase *db = [self database]; block(db); if ([db hasOpenResultSets]) { NSLog(@"Warning: there is at least one open result set around after performing [FMDatabaseQueue inDatabase:]"); } }); FMDBRelease(self); } - (void)beginTransaction:(BOOL)useDeferred withBlock:(void (^)(FMDatabase *db, BOOL *rollback))block { FMDBRetain(self); dispatch_sync(_queue, ^() { BOOL shouldRollback = NO; if (useDeferred) { [[self database] beginDeferredTransaction]; } else { [[self database] beginTransaction]; } block([self database], &shouldRollback); if (shouldRollback) { [[self database] rollback]; } else { [[self database] commit]; } }); FMDBRelease(self); } - (void)inDeferredTransaction:(void (^)(FMDatabase *db, BOOL *rollback))block { [self beginTransaction:YES withBlock:block]; } - (void)inTransaction:(void (^)(FMDatabase *db, BOOL *rollback))block { [self beginTransaction:NO withBlock:block]; } #if SQLITE_VERSION_NUMBER >= 3007000 - (NSError*)inSavePoint:(void (^)(FMDatabase *db, BOOL *rollback))block { static unsigned long savePointIdx = 0; __block NSError *err = 0x00; FMDBRetain(self); dispatch_sync(_queue, ^() { NSString *name = [NSString stringWithFormat:@"savePoint%ld", savePointIdx++]; BOOL shouldRollback = NO; if ([[self database] startSavePointWithName:name error:&err]) { block([self database], &shouldRollback); if (shouldRollback) { [[self database] rollbackToSavePointWithName:name error:&err]; } else { [[self database] releaseSavePointWithName:name error:&err]; } } }); FMDBRelease(self); return err; } #endif @end ================================================ FILE: zhuishushenqi/Vendor/XimalayaSDK_iOS_5.5.1/include/FMDB/FMResultSet.h ================================================ #import <Foundation/Foundation.h> #import "sqlite3.h" #ifndef __has_feature // Optional. #define __has_feature(x) 0 // Compatibility with non-clang compilers. #endif #ifndef NS_RETURNS_NOT_RETAINED #if __has_feature(attribute_ns_returns_not_retained) #define NS_RETURNS_NOT_RETAINED __attribute__((ns_returns_not_retained)) #else #define NS_RETURNS_NOT_RETAINED #endif #endif @class FMDatabase; @class FMStatement; @interface FMResultSet : NSObject { FMDatabase *_parentDB; FMStatement *_statement; NSString *_query; NSMutableDictionary *_columnNameToIndexMap; } @property (atomic, retain) NSString *query; @property (readonly) NSMutableDictionary *columnNameToIndexMap; @property (atomic, retain) FMStatement *statement; + (id)resultSetWithStatement:(FMStatement *)statement usingParentDatabase:(FMDatabase*)aDB; - (void)close; - (void)setParentDB:(FMDatabase *)newDb; - (BOOL)next; - (BOOL)hasAnotherRow; - (int)columnCount; - (int)columnIndexForName:(NSString*)columnName; - (NSString*)columnNameForIndex:(int)columnIdx; - (int)intForColumn:(NSString*)columnName; - (int)intForColumnIndex:(int)columnIdx; - (long)longForColumn:(NSString*)columnName; - (long)longForColumnIndex:(int)columnIdx; - (long long int)longLongIntForColumn:(NSString*)columnName; - (long long int)longLongIntForColumnIndex:(int)columnIdx; - (unsigned long long int)unsignedLongLongIntForColumn:(NSString*)columnName; - (unsigned long long int)unsignedLongLongIntForColumnIndex:(int)columnIdx; - (BOOL)boolForColumn:(NSString*)columnName; - (BOOL)boolForColumnIndex:(int)columnIdx; - (double)doubleForColumn:(NSString*)columnName; - (double)doubleForColumnIndex:(int)columnIdx; - (NSString*)stringForColumn:(NSString*)columnName; - (NSString*)stringForColumnIndex:(int)columnIdx; - (NSDate*)dateForColumn:(NSString*)columnName; - (NSDate*)dateForColumnIndex:(int)columnIdx; - (NSData*)dataForColumn:(NSString*)columnName; - (NSData*)dataForColumnIndex:(int)columnIdx; - (const unsigned char *)UTF8StringForColumnIndex:(int)columnIdx; - (const unsigned char *)UTF8StringForColumnName:(NSString*)columnName; // returns one of NSNumber, NSString, NSData, or NSNull - (id)objectForColumnName:(NSString*)columnName; - (id)objectForColumnIndex:(int)columnIdx; - (id)objectForKeyedSubscript:(NSString *)columnName; - (id)objectAtIndexedSubscript:(int)columnIdx; /* If you are going to use this data after you iterate over the next row, or after you close the result set, make sure to make a copy of the data first (or just use dataForColumn:/dataForColumnIndex:) If you don't, you're going to be in a world of hurt when you try and use the data. */ - (NSData*)dataNoCopyForColumn:(NSString*)columnName NS_RETURNS_NOT_RETAINED; - (NSData*)dataNoCopyForColumnIndex:(int)columnIdx NS_RETURNS_NOT_RETAINED; - (BOOL)columnIndexIsNull:(int)columnIdx; - (BOOL)columnIsNull:(NSString*)columnName; /* Returns a dictionary of the row results mapped to case sensitive keys of the column names. */ - (NSDictionary*)resultDictionary; /* Please use resultDictionary instead. Also, beware that resultDictionary is case sensitive! */ - (NSDictionary*)resultDict __attribute__ ((deprecated)); - (void)kvcMagic:(id)object; @end ================================================ FILE: zhuishushenqi/Vendor/XimalayaSDK_iOS_5.5.1/include/FMDB/FMResultSet.m ================================================ #import "FMResultSet.h" #import "FMDatabase.h" #import "unistd.h" @interface FMDatabase () - (void)resultSetDidClose:(FMResultSet *)resultSet; @end @implementation FMResultSet @synthesize query=_query; @synthesize statement=_statement; + (id)resultSetWithStatement:(FMStatement *)statement usingParentDatabase:(FMDatabase*)aDB { FMResultSet *rs = [[FMResultSet alloc] init]; [rs setStatement:statement]; [rs setParentDB:aDB]; return FMDBReturnAutoreleased(rs); } - (void)finalize { [self close]; [super finalize]; } - (void)dealloc { [self close]; FMDBRelease(_query); _query = nil; FMDBRelease(_columnNameToIndexMap); _columnNameToIndexMap = nil; #if ! __has_feature(objc_arc) [super dealloc]; #endif } - (void)close { [_statement reset]; FMDBRelease(_statement); _statement = nil; // we don't need this anymore... (i think) //[_parentDB setInUse:NO]; [_parentDB resultSetDidClose:self]; _parentDB = nil; } - (int)columnCount { return sqlite3_column_count([_statement statement]); } - (NSMutableDictionary *)columnNameToIndexMap { if (!_columnNameToIndexMap) { int columnCount = sqlite3_column_count([_statement statement]); _columnNameToIndexMap = [[NSMutableDictionary alloc] initWithCapacity:(NSUInteger)columnCount]; int columnIdx = 0; for (columnIdx = 0; columnIdx < columnCount; columnIdx++) { [_columnNameToIndexMap setObject:[NSNumber numberWithInt:columnIdx] forKey:[[NSString stringWithUTF8String:sqlite3_column_name([_statement statement], columnIdx)] lowercaseString]]; } } return _columnNameToIndexMap; } - (void)kvcMagic:(id)object { int columnCount = sqlite3_column_count([_statement statement]); int columnIdx = 0; for (columnIdx = 0; columnIdx < columnCount; columnIdx++) { const char *c = (const char *)sqlite3_column_text([_statement statement], columnIdx); // check for a null row if (c) { NSString *s = [NSString stringWithUTF8String:c]; [object setValue:s forKey:[NSString stringWithUTF8String:sqlite3_column_name([_statement statement], columnIdx)]]; } } } #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-implementations" - (NSDictionary*)resultDict { NSUInteger num_cols = (NSUInteger)sqlite3_data_count([_statement statement]); if (num_cols > 0) { NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithCapacity:num_cols]; NSEnumerator *columnNames = [[self columnNameToIndexMap] keyEnumerator]; NSString *columnName = nil; while ((columnName = [columnNames nextObject])) { id objectValue = [self objectForColumnName:columnName]; [dict setObject:objectValue forKey:columnName]; } return FMDBReturnAutoreleased([dict copy]); } else { NSLog(@"Warning: There seem to be no columns in this set."); } return nil; } #pragma clang diagnostic pop - (NSDictionary*)resultDictionary { NSUInteger num_cols = (NSUInteger)sqlite3_data_count([_statement statement]); if (num_cols > 0) { NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithCapacity:num_cols]; int columnCount = sqlite3_column_count([_statement statement]); int columnIdx = 0; for (columnIdx = 0; columnIdx < columnCount; columnIdx++) { NSString *columnName = [NSString stringWithUTF8String:sqlite3_column_name([_statement statement], columnIdx)]; id objectValue = [self objectForColumnIndex:columnIdx]; [dict setObject:objectValue forKey:columnName]; } return dict; } else { NSLog(@"Warning: There seem to be no columns in this set."); } return nil; } - (BOOL)next { int rc; BOOL retry; int numberOfRetries = 0; do { retry = NO; rc = sqlite3_step([_statement statement]); if (SQLITE_BUSY == rc || SQLITE_LOCKED == rc) { // this will happen if the db is locked, like if we are doing an update or insert. // in that case, retry the step... and maybe wait just 10 milliseconds. retry = YES; if (SQLITE_LOCKED == rc) { rc = sqlite3_reset([_statement statement]); if (rc != SQLITE_LOCKED) { NSLog(@"Unexpected result from sqlite3_reset (%d) rs", rc); } } usleep(20); if ([_parentDB busyRetryTimeout] && (numberOfRetries++ > [_parentDB busyRetryTimeout])) { NSLog(@"%s:%d Database busy (%@)", __FUNCTION__, __LINE__, [_parentDB databasePath]); NSLog(@"Database busy"); break; } } else if (SQLITE_DONE == rc || SQLITE_ROW == rc) { // all is well, let's return. } else if (SQLITE_ERROR == rc) { NSLog(@"Error calling sqlite3_step (%d: %s) rs", rc, sqlite3_errmsg([_parentDB sqliteHandle])); break; } else if (SQLITE_MISUSE == rc) { // uh oh. NSLog(@"Error calling sqlite3_step (%d: %s) rs", rc, sqlite3_errmsg([_parentDB sqliteHandle])); break; } else { // wtf? NSLog(@"Unknown error calling sqlite3_step (%d: %s) rs", rc, sqlite3_errmsg([_parentDB sqliteHandle])); break; } } while (retry); if (rc != SQLITE_ROW) { [self close]; } return (rc == SQLITE_ROW); } - (BOOL)hasAnotherRow { return sqlite3_errcode([_parentDB sqliteHandle]) == SQLITE_ROW; } - (int)columnIndexForName:(NSString*)columnName { columnName = [columnName lowercaseString]; NSNumber *n = [[self columnNameToIndexMap] objectForKey:columnName]; if (n) { return [n intValue]; } NSLog(@"Warning: I could not find the column named '%@'.", columnName); return -1; } - (int)intForColumn:(NSString*)columnName { return [self intForColumnIndex:[self columnIndexForName:columnName]]; } - (int)intForColumnIndex:(int)columnIdx { return sqlite3_column_int([_statement statement], columnIdx); } - (long)longForColumn:(NSString*)columnName { return [self longForColumnIndex:[self columnIndexForName:columnName]]; } - (long)longForColumnIndex:(int)columnIdx { return (long)sqlite3_column_int64([_statement statement], columnIdx); } - (long long int)longLongIntForColumn:(NSString*)columnName { return [self longLongIntForColumnIndex:[self columnIndexForName:columnName]]; } - (long long int)longLongIntForColumnIndex:(int)columnIdx { return sqlite3_column_int64([_statement statement], columnIdx); } - (unsigned long long int)unsignedLongLongIntForColumn:(NSString*)columnName { return [self unsignedLongLongIntForColumnIndex:[self columnIndexForName:columnName]]; } - (unsigned long long int)unsignedLongLongIntForColumnIndex:(int)columnIdx { return (unsigned long long int)[self longLongIntForColumnIndex:columnIdx]; } - (BOOL)boolForColumn:(NSString*)columnName { return [self boolForColumnIndex:[self columnIndexForName:columnName]]; } - (BOOL)boolForColumnIndex:(int)columnIdx { return ([self intForColumnIndex:columnIdx] != 0); } - (double)doubleForColumn:(NSString*)columnName { return [self doubleForColumnIndex:[self columnIndexForName:columnName]]; } - (double)doubleForColumnIndex:(int)columnIdx { return sqlite3_column_double([_statement statement], columnIdx); } - (NSString*)stringForColumnIndex:(int)columnIdx { if (sqlite3_column_type([_statement statement], columnIdx) == SQLITE_NULL || (columnIdx < 0)) { return nil; } const char *c = (const char *)sqlite3_column_text([_statement statement], columnIdx); if (!c) { // null row. return nil; } return [NSString stringWithUTF8String:c]; } - (NSString*)stringForColumn:(NSString*)columnName { return [self stringForColumnIndex:[self columnIndexForName:columnName]]; } - (NSDate*)dateForColumn:(NSString*)columnName { return [self dateForColumnIndex:[self columnIndexForName:columnName]]; } - (NSDate*)dateForColumnIndex:(int)columnIdx { if (sqlite3_column_type([_statement statement], columnIdx) == SQLITE_NULL || (columnIdx < 0)) { return nil; } return [_parentDB hasDateFormatter] ? [_parentDB dateFromString:[self stringForColumnIndex:columnIdx]] : [NSDate dateWithTimeIntervalSince1970:[self doubleForColumnIndex:columnIdx]]; } - (NSData*)dataForColumn:(NSString*)columnName { return [self dataForColumnIndex:[self columnIndexForName:columnName]]; } - (NSData*)dataForColumnIndex:(int)columnIdx { if (sqlite3_column_type([_statement statement], columnIdx) == SQLITE_NULL || (columnIdx < 0)) { return nil; } int dataSize = sqlite3_column_bytes([_statement statement], columnIdx); NSMutableData *data = [NSMutableData dataWithLength:(NSUInteger)dataSize]; memcpy([data mutableBytes], sqlite3_column_blob([_statement statement], columnIdx), dataSize); return data; } - (NSData*)dataNoCopyForColumn:(NSString*)columnName { return [self dataNoCopyForColumnIndex:[self columnIndexForName:columnName]]; } - (NSData*)dataNoCopyForColumnIndex:(int)columnIdx { if (sqlite3_column_type([_statement statement], columnIdx) == SQLITE_NULL || (columnIdx < 0)) { return nil; } int dataSize = sqlite3_column_bytes([_statement statement], columnIdx); NSData *data = [NSData dataWithBytesNoCopy:(void *)sqlite3_column_blob([_statement statement], columnIdx) length:(NSUInteger)dataSize freeWhenDone:NO]; return data; } - (BOOL)columnIndexIsNull:(int)columnIdx { return sqlite3_column_type([_statement statement], columnIdx) == SQLITE_NULL; } - (BOOL)columnIsNull:(NSString*)columnName { return [self columnIndexIsNull:[self columnIndexForName:columnName]]; } - (const unsigned char *)UTF8StringForColumnIndex:(int)columnIdx { if (sqlite3_column_type([_statement statement], columnIdx) == SQLITE_NULL || (columnIdx < 0)) { return nil; } return sqlite3_column_text([_statement statement], columnIdx); } - (const unsigned char *)UTF8StringForColumnName:(NSString*)columnName { return [self UTF8StringForColumnIndex:[self columnIndexForName:columnName]]; } - (id)objectForColumnIndex:(int)columnIdx { int columnType = sqlite3_column_type([_statement statement], columnIdx); id returnValue = nil; if (columnType == SQLITE_INTEGER) { returnValue = [NSNumber numberWithLongLong:[self longLongIntForColumnIndex:columnIdx]]; } else if (columnType == SQLITE_FLOAT) { returnValue = [NSNumber numberWithDouble:[self doubleForColumnIndex:columnIdx]]; } else if (columnType == SQLITE_BLOB) { returnValue = [self dataForColumnIndex:columnIdx]; } else { //default to a string for everything else returnValue = [self stringForColumnIndex:columnIdx]; } if (returnValue == nil) { returnValue = [NSNull null]; } return returnValue; } - (id)objectForColumnName:(NSString*)columnName { return [self objectForColumnIndex:[self columnIndexForName:columnName]]; } // returns autoreleased NSString containing the name of the column in the result set - (NSString*)columnNameForIndex:(int)columnIdx { return [NSString stringWithUTF8String: sqlite3_column_name([_statement statement], columnIdx)]; } - (void)setParentDB:(FMDatabase *)newDb { _parentDB = newDb; } - (id)objectAtIndexedSubscript:(int)columnIdx { return [self objectForColumnIndex:columnIdx]; } - (id)objectForKeyedSubscript:(NSString *)columnName { return [self objectForColumnName:columnName]; } @end ================================================ FILE: zhuishushenqi/Vendor/XimalayaSDK_iOS_5.5.1/include/MBProgressHUD/MBProgressHUD.h ================================================ // // MBProgressHUD.h // Version 1.1.0 // Created by Matej Bukovinski on 2.4.09. // // This code is distributed under the terms and conditions of the MIT license. // Copyright © 2009-2016 Matej Bukovinski // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #import <Foundation/Foundation.h> #import <UIKit/UIKit.h> #import <CoreGraphics/CoreGraphics.h> @class MBBackgroundView; @protocol MBProgressHUDDelegate; extern CGFloat const MBProgressMaxOffset; typedef NS_ENUM(NSInteger, MBProgressHUDMode) { /// UIActivityIndicatorView. MBProgressHUDModeIndeterminate, /// A round, pie-chart like, progress view. MBProgressHUDModeDeterminate, /// Horizontal progress bar. MBProgressHUDModeDeterminateHorizontalBar, /// Ring-shaped progress view. MBProgressHUDModeAnnularDeterminate, /// Shows a custom view. MBProgressHUDModeCustomView, /// Shows only labels. MBProgressHUDModeText }; typedef NS_ENUM(NSInteger, MBProgressHUDAnimation) { /// Opacity animation MBProgressHUDAnimationFade, /// Opacity + scale animation (zoom in when appearing zoom out when disappearing) MBProgressHUDAnimationZoom, /// Opacity + scale animation (zoom out style) MBProgressHUDAnimationZoomOut, /// Opacity + scale animation (zoom in style) MBProgressHUDAnimationZoomIn }; typedef NS_ENUM(NSInteger, MBProgressHUDBackgroundStyle) { /// Solid color background MBProgressHUDBackgroundStyleSolidColor, /// UIVisualEffectView or UIToolbar.layer background view MBProgressHUDBackgroundStyleBlur }; typedef void (^MBProgressHUDCompletionBlock)(void); NS_ASSUME_NONNULL_BEGIN /** * Displays a simple HUD window containing a progress indicator and two optional labels for short messages. * * This is a simple drop-in class for displaying a progress HUD view similar to Apple's private UIProgressHUD class. * The MBProgressHUD window spans over the entire space given to it by the initWithFrame: constructor and catches all * user input on this region, thereby preventing the user operations on components below the view. * * @note To still allow touches to pass through the HUD, you can set hud.userInteractionEnabled = NO. * @attention MBProgressHUD is a UI class and should therefore only be accessed on the main thread. */ @interface MBProgressHUD : UIView /** * Creates a new HUD, adds it to provided view and shows it. The counterpart to this method is hideHUDForView:animated:. * * @note This method sets removeFromSuperViewOnHide. The HUD will automatically be removed from the view hierarchy when hidden. * * @param view The view that the HUD will be added to * @param animated If set to YES the HUD will appear using the current animationType. If set to NO the HUD will not use * animations while appearing. * @return A reference to the created HUD. * * @see hideHUDForView:animated: * @see animationType */ + (instancetype)showHUDAddedTo:(UIView *)view animated:(BOOL)animated; /// @name Showing and hiding /** * Finds the top-most HUD subview that hasn't finished and hides it. The counterpart to this method is showHUDAddedTo:animated:. * * @note This method sets removeFromSuperViewOnHide. The HUD will automatically be removed from the view hierarchy when hidden. * * @param view The view that is going to be searched for a HUD subview. * @param animated If set to YES the HUD will disappear using the current animationType. If set to NO the HUD will not use * animations while disappearing. * @return YES if a HUD was found and removed, NO otherwise. * * @see showHUDAddedTo:animated: * @see animationType */ + (BOOL)hideHUDForView:(UIView *)view animated:(BOOL)animated; /** * Finds the top-most HUD subview that hasn't finished and returns it. * * @param view The view that is going to be searched. * @return A reference to the last HUD subview discovered. */ + (nullable MBProgressHUD *)HUDForView:(UIView *)view; /** * A convenience constructor that initializes the HUD with the view's bounds. Calls the designated constructor with * view.bounds as the parameter. * * @param view The view instance that will provide the bounds for the HUD. Should be the same instance as * the HUD's superview (i.e., the view that the HUD will be added to). */ - (instancetype)initWithView:(UIView *)view; /** * Displays the HUD. * * @note You need to make sure that the main thread completes its run loop soon after this method call so that * the user interface can be updated. Call this method when your task is already set up to be executed in a new thread * (e.g., when using something like NSOperation or making an asynchronous call like NSURLRequest). * * @param animated If set to YES the HUD will appear using the current animationType. If set to NO the HUD will not use * animations while appearing. * * @see animationType */ - (void)showAnimated:(BOOL)animated; /** * Hides the HUD. This still calls the hudWasHidden: delegate. This is the counterpart of the show: method. Use it to * hide the HUD when your task completes. * * @param animated If set to YES the HUD will disappear using the current animationType. If set to NO the HUD will not use * animations while disappearing. * * @see animationType */ - (void)hideAnimated:(BOOL)animated; /** * Hides the HUD after a delay. This still calls the hudWasHidden: delegate. This is the counterpart of the show: method. Use it to * hide the HUD when your task completes. * * @param animated If set to YES the HUD will disappear using the current animationType. If set to NO the HUD will not use * animations while disappearing. * @param delay Delay in seconds until the HUD is hidden. * * @see animationType */ - (void)hideAnimated:(BOOL)animated afterDelay:(NSTimeInterval)delay; /** * The HUD delegate object. Receives HUD state notifications. */ @property (weak, nonatomic) id<MBProgressHUDDelegate> delegate; /** * Called after the HUD is hiden. */ @property (copy, nullable) MBProgressHUDCompletionBlock completionBlock; /* * Grace period is the time (in seconds) that the invoked method may be run without * showing the HUD. If the task finishes before the grace time runs out, the HUD will * not be shown at all. * This may be used to prevent HUD display for very short tasks. * Defaults to 0 (no grace time). * @note The graceTime needs to be set before the hud is shown. You thus can't use `showHUDAddedTo:animated:`, * but instead need to alloc / init the HUD, configure the grace time and than show it manually. */ @property (assign, nonatomic) NSTimeInterval graceTime; /** * The minimum time (in seconds) that the HUD is shown. * This avoids the problem of the HUD being shown and than instantly hidden. * Defaults to 0 (no minimum show time). */ @property (assign, nonatomic) NSTimeInterval minShowTime; /** * Removes the HUD from its parent view when hidden. * Defaults to NO. */ @property (assign, nonatomic) BOOL removeFromSuperViewOnHide; /// @name Appearance /** * MBProgressHUD operation mode. The default is MBProgressHUDModeIndeterminate. */ @property (assign, nonatomic) MBProgressHUDMode mode; /** * A color that gets forwarded to all labels and supported indicators. Also sets the tintColor * for custom views on iOS 7+. Set to nil to manage color individually. * Defaults to semi-translucent black on iOS 7 and later and white on earlier iOS versions. */ @property (strong, nonatomic, nullable) UIColor *contentColor UI_APPEARANCE_SELECTOR; /** * The animation type that should be used when the HUD is shown and hidden. */ @property (assign, nonatomic) MBProgressHUDAnimation animationType UI_APPEARANCE_SELECTOR; /** * The bezel offset relative to the center of the view. You can use MBProgressMaxOffset * and -MBProgressMaxOffset to move the HUD all the way to the screen edge in each direction. * E.g., CGPointMake(0.f, MBProgressMaxOffset) would position the HUD centered on the bottom edge. */ @property (assign, nonatomic) CGPoint offset UI_APPEARANCE_SELECTOR; /** * The amount of space between the HUD edge and the HUD elements (labels, indicators or custom views). * This also represents the minimum bezel distance to the edge of the HUD view. * Defaults to 20.f */ @property (assign, nonatomic) CGFloat margin UI_APPEARANCE_SELECTOR; /** * The minimum size of the HUD bezel. Defaults to CGSizeZero (no minimum size). */ @property (assign, nonatomic) CGSize minSize UI_APPEARANCE_SELECTOR; /** * Force the HUD dimensions to be equal if possible. */ @property (assign, nonatomic, getter = isSquare) BOOL square UI_APPEARANCE_SELECTOR; /** * When enabled, the bezel center gets slightly affected by the device accelerometer data. * Has no effect on iOS < 7.0. Defaults to YES. */ @property (assign, nonatomic, getter=areDefaultMotionEffectsEnabled) BOOL defaultMotionEffectsEnabled UI_APPEARANCE_SELECTOR; /// @name Progress /** * The progress of the progress indicator, from 0.0 to 1.0. Defaults to 0.0. */ @property (assign, nonatomic) float progress; /// @name ProgressObject /** * The NSProgress object feeding the progress information to the progress indicator. */ @property (strong, nonatomic, nullable) NSProgress *progressObject; /// @name Views /** * The view containing the labels and indicator (or customView). */ @property (strong, nonatomic, readonly) MBBackgroundView *bezelView; /** * View covering the entire HUD area, placed behind bezelView. */ @property (strong, nonatomic, readonly) MBBackgroundView *backgroundView; /** * The UIView (e.g., a UIImageView) to be shown when the HUD is in MBProgressHUDModeCustomView. * The view should implement intrinsicContentSize for proper sizing. For best results use approximately 37 by 37 pixels. */ @property (strong, nonatomic, nullable) UIView *customView; /** * A label that holds an optional short message to be displayed below the activity indicator. The HUD is automatically resized to fit * the entire text. */ @property (strong, nonatomic, readonly) UILabel *label; /** * A label that holds an optional details message displayed below the labelText message. The details text can span multiple lines. */ @property (strong, nonatomic, readonly) UILabel *detailsLabel; /** * A button that is placed below the labels. Visible only if a target / action is added. */ @property (strong, nonatomic, readonly) UIButton *button; @end @protocol MBProgressHUDDelegate <NSObject> @optional /** * Called after the HUD was fully hidden from the screen. */ - (void)hudWasHidden:(MBProgressHUD *)hud; @end /** * A progress view for showing definite progress by filling up a circle (pie chart). */ @interface MBRoundProgressView : UIView /** * Progress (0.0 to 1.0) */ @property (nonatomic, assign) float progress; /** * Indicator progress color. * Defaults to white [UIColor whiteColor]. */ @property (nonatomic, strong) UIColor *progressTintColor; /** * Indicator background (non-progress) color. * Only applicable on iOS versions older than iOS 7. * Defaults to translucent white (alpha 0.1). */ @property (nonatomic, strong) UIColor *backgroundTintColor; /* * Display mode - NO = round or YES = annular. Defaults to round. */ @property (nonatomic, assign, getter = isAnnular) BOOL annular; @end /** * A flat bar progress view. */ @interface MBBarProgressView : UIView /** * Progress (0.0 to 1.0) */ @property (nonatomic, assign) float progress; /** * Bar border line color. * Defaults to white [UIColor whiteColor]. */ @property (nonatomic, strong) UIColor *lineColor; /** * Bar background color. * Defaults to clear [UIColor clearColor]; */ @property (nonatomic, strong) UIColor *progressRemainingColor; /** * Bar progress color. * Defaults to white [UIColor whiteColor]. */ @property (nonatomic, strong) UIColor *progressColor; @end @interface MBBackgroundView : UIView /** * The background style. * Defaults to MBProgressHUDBackgroundStyleBlur. */ @property (nonatomic) MBProgressHUDBackgroundStyle style; /** * The blur effect style, when using MBProgressHUDBackgroundStyleBlur. * Defaults to UIBlurEffectStyleLight. */ @property (nonatomic) UIBlurEffectStyle blurEffectStyle; /** * The background color or the blur tint color. */ @property (nonatomic, strong) UIColor *color; @end NS_ASSUME_NONNULL_END ================================================ FILE: zhuishushenqi/Vendor/XimalayaSDK_iOS_5.5.1/include/MBProgressHUD/MBProgressHUD.m ================================================ // // MBProgressHUD.m // Version 1.1.0 // Created by Matej Bukovinski on 2.4.09. // #import "MBProgressHUD.h" #import <tgmath.h> #define MBMainThreadAssert() NSAssert([NSThread isMainThread], @"MBProgressHUD needs to be accessed on the main thread."); CGFloat const MBProgressMaxOffset = 1000000.f; static const CGFloat MBDefaultPadding = 4.f; static const CGFloat MBDefaultLabelFontSize = 16.f; static const CGFloat MBDefaultDetailsLabelFontSize = 12.f; @interface MBProgressHUD () @property (nonatomic, assign) BOOL useAnimation; @property (nonatomic, assign, getter=hasFinished) BOOL finished; @property (nonatomic, strong) UIView *indicator; @property (nonatomic, strong) NSDate *showStarted; @property (nonatomic, strong) NSArray *paddingConstraints; @property (nonatomic, strong) NSArray *bezelConstraints; @property (nonatomic, strong) UIView *topSpacer; @property (nonatomic, strong) UIView *bottomSpacer; @property (nonatomic, weak) NSTimer *graceTimer; @property (nonatomic, weak) NSTimer *minShowTimer; @property (nonatomic, weak) NSTimer *hideDelayTimer; @property (nonatomic, weak) CADisplayLink *progressObjectDisplayLink; @end @interface MBProgressHUDRoundedButton : UIButton @end @implementation MBProgressHUD #pragma mark - Class methods + (instancetype)showHUDAddedTo:(UIView *)view animated:(BOOL)animated { MBProgressHUD *hud = [[self alloc] initWithView:view]; hud.removeFromSuperViewOnHide = YES; [view addSubview:hud]; [hud showAnimated:animated]; return hud; } + (BOOL)hideHUDForView:(UIView *)view animated:(BOOL)animated { MBProgressHUD *hud = [self HUDForView:view]; if (hud != nil) { hud.removeFromSuperViewOnHide = YES; [hud hideAnimated:animated]; return YES; } return NO; } + (MBProgressHUD *)HUDForView:(UIView *)view { NSEnumerator *subviewsEnum = [view.subviews reverseObjectEnumerator]; for (UIView *subview in subviewsEnum) { if ([subview isKindOfClass:self]) { MBProgressHUD *hud = (MBProgressHUD *)subview; if (hud.hasFinished == NO) { return hud; } } } return nil; } #pragma mark - Lifecycle - (void)commonInit { // Set default values for properties _animationType = MBProgressHUDAnimationFade; _mode = MBProgressHUDModeIndeterminate; _margin = 20.0f; _defaultMotionEffectsEnabled = YES; _contentColor = [UIColor colorWithWhite:0.f alpha:0.7f]; // Transparent background self.opaque = NO; self.backgroundColor = [UIColor clearColor]; // Make it invisible for now self.alpha = 0.0f; self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; self.layer.allowsGroupOpacity = NO; [self setupViews]; [self updateIndicators]; [self registerForNotifications]; } - (instancetype)initWithFrame:(CGRect)frame { if ((self = [super initWithFrame:frame])) { [self commonInit]; } return self; } - (instancetype)initWithCoder:(NSCoder *)aDecoder { if ((self = [super initWithCoder:aDecoder])) { [self commonInit]; } return self; } - (id)initWithView:(UIView *)view { NSAssert(view, @"View must not be nil."); return [self initWithFrame:view.bounds]; } - (void)dealloc { [self unregisterFromNotifications]; } #pragma mark - Show & hide - (void)showAnimated:(BOOL)animated { MBMainThreadAssert(); [self.minShowTimer invalidate]; self.useAnimation = animated; self.finished = NO; // If the grace time is set, postpone the HUD display if (self.graceTime > 0.0) { NSTimer *timer = [NSTimer timerWithTimeInterval:self.graceTime target:self selector:@selector(handleGraceTimer:) userInfo:nil repeats:NO]; [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes]; self.graceTimer = timer; } // ... otherwise show the HUD immediately else { [self showUsingAnimation:self.useAnimation]; } } - (void)hideAnimated:(BOOL)animated { MBMainThreadAssert(); [self.graceTimer invalidate]; self.useAnimation = animated; self.finished = YES; // If the minShow time is set, calculate how long the HUD was shown, // and postpone the hiding operation if necessary if (self.minShowTime > 0.0 && self.showStarted) { NSTimeInterval interv = [[NSDate date] timeIntervalSinceDate:self.showStarted]; if (interv < self.minShowTime) { NSTimer *timer = [NSTimer timerWithTimeInterval:(self.minShowTime - interv) target:self selector:@selector(handleMinShowTimer:) userInfo:nil repeats:NO]; [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes]; self.minShowTimer = timer; return; } } // ... otherwise hide the HUD immediately [self hideUsingAnimation:self.useAnimation]; } - (void)hideAnimated:(BOOL)animated afterDelay:(NSTimeInterval)delay { // Cancel any scheduled hideAnimated:afterDelay: calls [self.hideDelayTimer invalidate]; NSTimer *timer = [NSTimer timerWithTimeInterval:delay target:self selector:@selector(handleHideTimer:) userInfo:@(animated) repeats:NO]; [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes]; self.hideDelayTimer = timer; } #pragma mark - Timer callbacks - (void)handleGraceTimer:(NSTimer *)theTimer { // Show the HUD only if the task is still running if (!self.hasFinished) { [self showUsingAnimation:self.useAnimation]; } } - (void)handleMinShowTimer:(NSTimer *)theTimer { [self hideUsingAnimation:self.useAnimation]; } - (void)handleHideTimer:(NSTimer *)timer { [self hideAnimated:[timer.userInfo boolValue]]; } #pragma mark - View Hierrarchy - (void)didMoveToSuperview { [self updateForCurrentOrientationAnimated:NO]; } #pragma mark - Internal show & hide operations - (void)showUsingAnimation:(BOOL)animated { // Cancel any previous animations [self.bezelView.layer removeAllAnimations]; [self.backgroundView.layer removeAllAnimations]; // Cancel any scheduled hideAnimated:afterDelay: calls [self.hideDelayTimer invalidate]; self.showStarted = [NSDate date]; self.alpha = 1.f; // Needed in case we hide and re-show with the same NSProgress object attached. [self setNSProgressDisplayLinkEnabled:YES]; if (animated) { [self animateIn:YES withType:self.animationType completion:NULL]; } else { self.bezelView.alpha = 1.f; self.backgroundView.alpha = 1.f; } } - (void)hideUsingAnimation:(BOOL)animated { // Cancel any scheduled hideAnimated:afterDelay: calls. // This needs to happen here instead of in done, // to avoid races if another hideAnimated:afterDelay: // call comes in while the HUD is animating out. [self.hideDelayTimer invalidate]; if (animated && self.showStarted) { self.showStarted = nil; [self animateIn:NO withType:self.animationType completion:^(BOOL finished) { [self done]; }]; } else { self.showStarted = nil; self.bezelView.alpha = 0.f; self.backgroundView.alpha = 1.f; [self done]; } } - (void)animateIn:(BOOL)animatingIn withType:(MBProgressHUDAnimation)type completion:(void(^)(BOOL finished))completion { // Automatically determine the correct zoom animation type if (type == MBProgressHUDAnimationZoom) { type = animatingIn ? MBProgressHUDAnimationZoomIn : MBProgressHUDAnimationZoomOut; } CGAffineTransform small = CGAffineTransformMakeScale(0.5f, 0.5f); CGAffineTransform large = CGAffineTransformMakeScale(1.5f, 1.5f); // Set starting state UIView *bezelView = self.bezelView; if (animatingIn && bezelView.alpha == 0.f && type == MBProgressHUDAnimationZoomIn) { bezelView.transform = small; } else if (animatingIn && bezelView.alpha == 0.f && type == MBProgressHUDAnimationZoomOut) { bezelView.transform = large; } // Perform animations dispatch_block_t animations = ^{ if (animatingIn) { bezelView.transform = CGAffineTransformIdentity; } else if (!animatingIn && type == MBProgressHUDAnimationZoomIn) { bezelView.transform = large; } else if (!animatingIn && type == MBProgressHUDAnimationZoomOut) { bezelView.transform = small; } CGFloat alpha = animatingIn ? 1.f : 0.f; bezelView.alpha = alpha; self.backgroundView.alpha = alpha; }; [UIView animateWithDuration:0.3 delay:0. usingSpringWithDamping:1.f initialSpringVelocity:0.f options:UIViewAnimationOptionBeginFromCurrentState animations:animations completion:completion]; } - (void)done { [self setNSProgressDisplayLinkEnabled:NO]; if (self.hasFinished) { self.alpha = 0.0f; if (self.removeFromSuperViewOnHide) { [self removeFromSuperview]; } } MBProgressHUDCompletionBlock completionBlock = self.completionBlock; if (completionBlock) { completionBlock(); } id<MBProgressHUDDelegate> delegate = self.delegate; if ([delegate respondsToSelector:@selector(hudWasHidden:)]) { [delegate performSelector:@selector(hudWasHidden:) withObject:self]; } } #pragma mark - UI - (void)setupViews { UIColor *defaultColor = self.contentColor; MBBackgroundView *backgroundView = [[MBBackgroundView alloc] initWithFrame:self.bounds]; backgroundView.style = MBProgressHUDBackgroundStyleSolidColor; backgroundView.backgroundColor = [UIColor clearColor]; backgroundView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; backgroundView.alpha = 0.f; [self addSubview:backgroundView]; _backgroundView = backgroundView; MBBackgroundView *bezelView = [MBBackgroundView new]; bezelView.translatesAutoresizingMaskIntoConstraints = NO; bezelView.layer.cornerRadius = 5.f; bezelView.alpha = 0.f; [self addSubview:bezelView]; _bezelView = bezelView; [self updateBezelMotionEffects]; UILabel *label = [UILabel new]; label.adjustsFontSizeToFitWidth = NO; label.textAlignment = NSTextAlignmentCenter; label.textColor = defaultColor; label.font = [UIFont boldSystemFontOfSize:MBDefaultLabelFontSize]; label.opaque = NO; label.backgroundColor = [UIColor clearColor]; _label = label; UILabel *detailsLabel = [UILabel new]; detailsLabel.adjustsFontSizeToFitWidth = NO; detailsLabel.textAlignment = NSTextAlignmentCenter; detailsLabel.textColor = defaultColor; detailsLabel.numberOfLines = 0; detailsLabel.font = [UIFont boldSystemFontOfSize:MBDefaultDetailsLabelFontSize]; detailsLabel.opaque = NO; detailsLabel.backgroundColor = [UIColor clearColor]; _detailsLabel = detailsLabel; UIButton *button = [MBProgressHUDRoundedButton buttonWithType:UIButtonTypeCustom]; button.titleLabel.textAlignment = NSTextAlignmentCenter; button.titleLabel.font = [UIFont boldSystemFontOfSize:MBDefaultDetailsLabelFontSize]; [button setTitleColor:defaultColor forState:UIControlStateNormal]; _button = button; for (UIView *view in @[label, detailsLabel, button]) { view.translatesAutoresizingMaskIntoConstraints = NO; [view setContentCompressionResistancePriority:998.f forAxis:UILayoutConstraintAxisHorizontal]; [view setContentCompressionResistancePriority:998.f forAxis:UILayoutConstraintAxisVertical]; [bezelView addSubview:view]; } UIView *topSpacer = [UIView new]; topSpacer.translatesAutoresizingMaskIntoConstraints = NO; topSpacer.hidden = YES; [bezelView addSubview:topSpacer]; _topSpacer = topSpacer; UIView *bottomSpacer = [UIView new]; bottomSpacer.translatesAutoresizingMaskIntoConstraints = NO; bottomSpacer.hidden = YES; [bezelView addSubview:bottomSpacer]; _bottomSpacer = bottomSpacer; } - (void)updateIndicators { UIView *indicator = self.indicator; BOOL isActivityIndicator = [indicator isKindOfClass:[UIActivityIndicatorView class]]; BOOL isRoundIndicator = [indicator isKindOfClass:[MBRoundProgressView class]]; MBProgressHUDMode mode = self.mode; if (mode == MBProgressHUDModeIndeterminate) { if (!isActivityIndicator) { // Update to indeterminate indicator [indicator removeFromSuperview]; indicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge]; [(UIActivityIndicatorView *)indicator startAnimating]; [self.bezelView addSubview:indicator]; } } else if (mode == MBProgressHUDModeDeterminateHorizontalBar) { // Update to bar determinate indicator [indicator removeFromSuperview]; indicator = [[MBBarProgressView alloc] init]; [self.bezelView addSubview:indicator]; } else if (mode == MBProgressHUDModeDeterminate || mode == MBProgressHUDModeAnnularDeterminate) { if (!isRoundIndicator) { // Update to determinante indicator [indicator removeFromSuperview]; indicator = [[MBRoundProgressView alloc] init]; [self.bezelView addSubview:indicator]; } if (mode == MBProgressHUDModeAnnularDeterminate) { [(MBRoundProgressView *)indicator setAnnular:YES]; } } else if (mode == MBProgressHUDModeCustomView && self.customView != indicator) { // Update custom view indicator [indicator removeFromSuperview]; indicator = self.customView; [self.bezelView addSubview:indicator]; } else if (mode == MBProgressHUDModeText) { [indicator removeFromSuperview]; indicator = nil; } indicator.translatesAutoresizingMaskIntoConstraints = NO; self.indicator = indicator; if ([indicator respondsToSelector:@selector(setProgress:)]) { [(id)indicator setValue:@(self.progress) forKey:@"progress"]; } [indicator setContentCompressionResistancePriority:998.f forAxis:UILayoutConstraintAxisHorizontal]; [indicator setContentCompressionResistancePriority:998.f forAxis:UILayoutConstraintAxisVertical]; [self updateViewsForColor:self.contentColor]; [self setNeedsUpdateConstraints]; } - (void)updateViewsForColor:(UIColor *)color { if (!color) return; self.label.textColor = color; self.detailsLabel.textColor = color; [self.button setTitleColor:color forState:UIControlStateNormal]; // UIAppearance settings are prioritized. If they are preset the set color is ignored. UIView *indicator = self.indicator; if ([indicator isKindOfClass:[UIActivityIndicatorView class]]) { UIActivityIndicatorView *appearance = nil; #if __IPHONE_OS_VERSION_MIN_REQUIRED < 90000 appearance = [UIActivityIndicatorView appearanceWhenContainedIn:[MBProgressHUD class], nil]; #else // For iOS 9+ appearance = [UIActivityIndicatorView appearanceWhenContainedInInstancesOfClasses:@[[MBProgressHUD class]]]; #endif if (appearance.color == nil) { ((UIActivityIndicatorView *)indicator).color = color; } } else if ([indicator isKindOfClass:[MBRoundProgressView class]]) { MBRoundProgressView *appearance = nil; #if __IPHONE_OS_VERSION_MIN_REQUIRED < 90000 appearance = [MBRoundProgressView appearanceWhenContainedIn:[MBProgressHUD class], nil]; #else appearance = [MBRoundProgressView appearanceWhenContainedInInstancesOfClasses:@[[MBProgressHUD class]]]; #endif if (appearance.progressTintColor == nil) { ((MBRoundProgressView *)indicator).progressTintColor = color; } if (appearance.backgroundTintColor == nil) { ((MBRoundProgressView *)indicator).backgroundTintColor = [color colorWithAlphaComponent:0.1]; } } else if ([indicator isKindOfClass:[MBBarProgressView class]]) { MBBarProgressView *appearance = nil; #if __IPHONE_OS_VERSION_MIN_REQUIRED < 90000 appearance = [MBBarProgressView appearanceWhenContainedIn:[MBProgressHUD class], nil]; #else appearance = [MBBarProgressView appearanceWhenContainedInInstancesOfClasses:@[[MBProgressHUD class]]]; #endif if (appearance.progressColor == nil) { ((MBBarProgressView *)indicator).progressColor = color; } if (appearance.lineColor == nil) { ((MBBarProgressView *)indicator).lineColor = color; } } else { #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000 || TARGET_OS_TV if ([indicator respondsToSelector:@selector(setTintColor:)]) { [indicator setTintColor:color]; } #endif } } - (void)updateBezelMotionEffects { #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000 || TARGET_OS_TV MBBackgroundView *bezelView = self.bezelView; if (![bezelView respondsToSelector:@selector(addMotionEffect:)]) return; if (self.defaultMotionEffectsEnabled) { CGFloat effectOffset = 10.f; UIInterpolatingMotionEffect *effectX = [[UIInterpolatingMotionEffect alloc] initWithKeyPath:@"center.x" type:UIInterpolatingMotionEffectTypeTiltAlongHorizontalAxis]; effectX.maximumRelativeValue = @(effectOffset); effectX.minimumRelativeValue = @(-effectOffset); UIInterpolatingMotionEffect *effectY = [[UIInterpolatingMotionEffect alloc] initWithKeyPath:@"center.y" type:UIInterpolatingMotionEffectTypeTiltAlongVerticalAxis]; effectY.maximumRelativeValue = @(effectOffset); effectY.minimumRelativeValue = @(-effectOffset); UIMotionEffectGroup *group = [[UIMotionEffectGroup alloc] init]; group.motionEffects = @[effectX, effectY]; [bezelView addMotionEffect:group]; } else { NSArray *effects = [bezelView motionEffects]; for (UIMotionEffect *effect in effects) { [bezelView removeMotionEffect:effect]; } } #endif } #pragma mark - Layout - (void)updateConstraints { UIView *bezel = self.bezelView; UIView *topSpacer = self.topSpacer; UIView *bottomSpacer = self.bottomSpacer; CGFloat margin = self.margin; NSMutableArray *bezelConstraints = [NSMutableArray array]; NSDictionary *metrics = @{@"margin": @(margin)}; NSMutableArray *subviews = [NSMutableArray arrayWithObjects:self.topSpacer, self.label, self.detailsLabel, self.button, self.bottomSpacer, nil]; if (self.indicator) [subviews insertObject:self.indicator atIndex:1]; // Remove existing constraints [self removeConstraints:self.constraints]; [topSpacer removeConstraints:topSpacer.constraints]; [bottomSpacer removeConstraints:bottomSpacer.constraints]; if (self.bezelConstraints) { [bezel removeConstraints:self.bezelConstraints]; self.bezelConstraints = nil; } // Center bezel in container (self), applying the offset if set CGPoint offset = self.offset; NSMutableArray *centeringConstraints = [NSMutableArray array]; [centeringConstraints addObject:[NSLayoutConstraint constraintWithItem:bezel attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterX multiplier:1.f constant:offset.x]]; [centeringConstraints addObject:[NSLayoutConstraint constraintWithItem:bezel attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterY multiplier:1.f constant:offset.y]]; [self applyPriority:998.f toConstraints:centeringConstraints]; [self addConstraints:centeringConstraints]; // Ensure minimum side margin is kept NSMutableArray *sideConstraints = [NSMutableArray array]; [sideConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"|-(>=margin)-[bezel]-(>=margin)-|" options:0 metrics:metrics views:NSDictionaryOfVariableBindings(bezel)]]; [sideConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-(>=margin)-[bezel]-(>=margin)-|" options:0 metrics:metrics views:NSDictionaryOfVariableBindings(bezel)]]; [self applyPriority:999.f toConstraints:sideConstraints]; [self addConstraints:sideConstraints]; // Minimum bezel size, if set CGSize minimumSize = self.minSize; if (!CGSizeEqualToSize(minimumSize, CGSizeZero)) { NSMutableArray *minSizeConstraints = [NSMutableArray array]; [minSizeConstraints addObject:[NSLayoutConstraint constraintWithItem:bezel attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.f constant:minimumSize.width]]; [minSizeConstraints addObject:[NSLayoutConstraint constraintWithItem:bezel attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.f constant:minimumSize.height]]; [self applyPriority:997.f toConstraints:minSizeConstraints]; [bezelConstraints addObjectsFromArray:minSizeConstraints]; } // Square aspect ratio, if set if (self.square) { NSLayoutConstraint *square = [NSLayoutConstraint constraintWithItem:bezel attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:bezel attribute:NSLayoutAttributeWidth multiplier:1.f constant:0]; square.priority = 997.f; [bezelConstraints addObject:square]; } // Top and bottom spacing [topSpacer addConstraint:[NSLayoutConstraint constraintWithItem:topSpacer attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.f constant:margin]]; [bottomSpacer addConstraint:[NSLayoutConstraint constraintWithItem:bottomSpacer attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.f constant:margin]]; // Top and bottom spaces should be equal [bezelConstraints addObject:[NSLayoutConstraint constraintWithItem:topSpacer attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:bottomSpacer attribute:NSLayoutAttributeHeight multiplier:1.f constant:0.f]]; // Layout subviews in bezel NSMutableArray *paddingConstraints = [NSMutableArray new]; [subviews enumerateObjectsUsingBlock:^(UIView *view, NSUInteger idx, BOOL *stop) { // Center in bezel [bezelConstraints addObject:[NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:bezel attribute:NSLayoutAttributeCenterX multiplier:1.f constant:0.f]]; // Ensure the minimum edge margin is kept [bezelConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"|-(>=margin)-[view]-(>=margin)-|" options:0 metrics:metrics views:NSDictionaryOfVariableBindings(view)]]; // Element spacing if (idx == 0) { // First, ensure spacing to bezel edge [bezelConstraints addObject:[NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:bezel attribute:NSLayoutAttributeTop multiplier:1.f constant:0.f]]; } else if (idx == subviews.count - 1) { // Last, ensure spacing to bezel edge [bezelConstraints addObject:[NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:bezel attribute:NSLayoutAttributeBottom multiplier:1.f constant:0.f]]; } if (idx > 0) { // Has previous NSLayoutConstraint *padding = [NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:subviews[idx - 1] attribute:NSLayoutAttributeBottom multiplier:1.f constant:0.f]; [bezelConstraints addObject:padding]; [paddingConstraints addObject:padding]; } }]; [bezel addConstraints:bezelConstraints]; self.bezelConstraints = bezelConstraints; self.paddingConstraints = [paddingConstraints copy]; [self updatePaddingConstraints]; [super updateConstraints]; } - (void)layoutSubviews { // There is no need to update constraints if they are going to // be recreated in [super layoutSubviews] due to needsUpdateConstraints being set. // This also avoids an issue on iOS 8, where updatePaddingConstraints // would trigger a zombie object access. if (!self.needsUpdateConstraints) { [self updatePaddingConstraints]; } [super layoutSubviews]; } - (void)updatePaddingConstraints { // Set padding dynamically, depending on whether the view is visible or not __block BOOL hasVisibleAncestors = NO; [self.paddingConstraints enumerateObjectsUsingBlock:^(NSLayoutConstraint *padding, NSUInteger idx, BOOL *stop) { UIView *firstView = (UIView *)padding.firstItem; UIView *secondView = (UIView *)padding.secondItem; BOOL firstVisible = !firstView.hidden && !CGSizeEqualToSize(firstView.intrinsicContentSize, CGSizeZero); BOOL secondVisible = !secondView.hidden && !CGSizeEqualToSize(secondView.intrinsicContentSize, CGSizeZero); // Set if both views are visible or if there's a visible view on top that doesn't have padding // added relative to the current view yet padding.constant = (firstVisible && (secondVisible || hasVisibleAncestors)) ? MBDefaultPadding : 0.f; hasVisibleAncestors |= secondVisible; }]; } - (void)applyPriority:(UILayoutPriority)priority toConstraints:(NSArray *)constraints { for (NSLayoutConstraint *constraint in constraints) { constraint.priority = priority; } } #pragma mark - Properties - (void)setMode:(MBProgressHUDMode)mode { if (mode != _mode) { _mode = mode; [self updateIndicators]; } } - (void)setCustomView:(UIView *)customView { if (customView != _customView) { _customView = customView; if (self.mode == MBProgressHUDModeCustomView) { [self updateIndicators]; } } } - (void)setOffset:(CGPoint)offset { if (!CGPointEqualToPoint(offset, _offset)) { _offset = offset; [self setNeedsUpdateConstraints]; } } - (void)setMargin:(CGFloat)margin { if (margin != _margin) { _margin = margin; [self setNeedsUpdateConstraints]; } } - (void)setMinSize:(CGSize)minSize { if (!CGSizeEqualToSize(minSize, _minSize)) { _minSize = minSize; [self setNeedsUpdateConstraints]; } } - (void)setSquare:(BOOL)square { if (square != _square) { _square = square; [self setNeedsUpdateConstraints]; } } - (void)setProgressObjectDisplayLink:(CADisplayLink *)progressObjectDisplayLink { if (progressObjectDisplayLink != _progressObjectDisplayLink) { [_progressObjectDisplayLink invalidate]; _progressObjectDisplayLink = progressObjectDisplayLink; [_progressObjectDisplayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode]; } } - (void)setProgressObject:(NSProgress *)progressObject { if (progressObject != _progressObject) { _progressObject = progressObject; [self setNSProgressDisplayLinkEnabled:YES]; } } - (void)setProgress:(float)progress { if (progress != _progress) { _progress = progress; UIView *indicator = self.indicator; if ([indicator respondsToSelector:@selector(setProgress:)]) { [(id)indicator setValue:@(self.progress) forKey:@"progress"]; } } } - (void)setContentColor:(UIColor *)contentColor { if (contentColor != _contentColor && ![contentColor isEqual:_contentColor]) { _contentColor = contentColor; [self updateViewsForColor:contentColor]; } } - (void)setDefaultMotionEffectsEnabled:(BOOL)defaultMotionEffectsEnabled { if (defaultMotionEffectsEnabled != _defaultMotionEffectsEnabled) { _defaultMotionEffectsEnabled = defaultMotionEffectsEnabled; [self updateBezelMotionEffects]; } } #pragma mark - NSProgress - (void)setNSProgressDisplayLinkEnabled:(BOOL)enabled { // We're using CADisplayLink, because NSProgress can change very quickly and observing it may starve the main thread, // so we're refreshing the progress only every frame draw if (enabled && self.progressObject) { // Only create if not already active. if (!self.progressObjectDisplayLink) { self.progressObjectDisplayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(updateProgressFromProgressObject)]; } } else { self.progressObjectDisplayLink = nil; } } - (void)updateProgressFromProgressObject { self.progress = self.progressObject.fractionCompleted; } #pragma mark - Notifications - (void)registerForNotifications { #if !TARGET_OS_TV NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; [nc addObserver:self selector:@selector(statusBarOrientationDidChange:) name:UIApplicationDidChangeStatusBarOrientationNotification object:nil]; #endif } - (void)unregisterFromNotifications { #if !TARGET_OS_TV NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; [nc removeObserver:self name:UIApplicationDidChangeStatusBarOrientationNotification object:nil]; #endif } #if !TARGET_OS_TV - (void)statusBarOrientationDidChange:(NSNotification *)notification { UIView *superview = self.superview; if (!superview) { return; } else { [self updateForCurrentOrientationAnimated:YES]; } } #endif - (void)updateForCurrentOrientationAnimated:(BOOL)animated { // Stay in sync with the superview in any case if (self.superview) { self.frame = self.superview.bounds; } // Not needed on iOS 8+, compile out when the deployment target allows, // to avoid sharedApplication problems on extension targets #if __IPHONE_OS_VERSION_MIN_REQUIRED < 80000 // Only needed pre iOS 8 when added to a window BOOL iOS8OrLater = kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_8_0; if (iOS8OrLater || ![self.superview isKindOfClass:[UIWindow class]]) return; // Make extension friendly. Will not get called on extensions (iOS 8+) due to the above check. // This just ensures we don't get a warning about extension-unsafe API. Class UIApplicationClass = NSClassFromString(@"UIApplication"); if (!UIApplicationClass || ![UIApplicationClass respondsToSelector:@selector(sharedApplication)]) return; UIApplication *application = [UIApplication performSelector:@selector(sharedApplication)]; UIInterfaceOrientation orientation = application.statusBarOrientation; CGFloat radians = 0; if (UIInterfaceOrientationIsLandscape(orientation)) { radians = orientation == UIInterfaceOrientationLandscapeLeft ? -(CGFloat)M_PI_2 : (CGFloat)M_PI_2; // Window coordinates differ! self.bounds = CGRectMake(0, 0, self.bounds.size.height, self.bounds.size.width); } else { radians = orientation == UIInterfaceOrientationPortraitUpsideDown ? (CGFloat)M_PI : 0.f; } if (animated) { [UIView animateWithDuration:0.3 animations:^{ self.transform = CGAffineTransformMakeRotation(radians); }]; } else { self.transform = CGAffineTransformMakeRotation(radians); } #endif } @end @implementation MBRoundProgressView #pragma mark - Lifecycle - (id)init { return [self initWithFrame:CGRectMake(0.f, 0.f, 37.f, 37.f)]; } - (id)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { self.backgroundColor = [UIColor clearColor]; self.opaque = NO; _progress = 0.f; _annular = NO; _progressTintColor = [[UIColor alloc] initWithWhite:1.f alpha:1.f]; _backgroundTintColor = [[UIColor alloc] initWithWhite:1.f alpha:.1f]; } return self; } #pragma mark - Layout - (CGSize)intrinsicContentSize { return CGSizeMake(37.f, 37.f); } #pragma mark - Properties - (void)setProgress:(float)progress { if (progress != _progress) { _progress = progress; [self setNeedsDisplay]; } } - (void)setProgressTintColor:(UIColor *)progressTintColor { NSAssert(progressTintColor, @"The color should not be nil."); if (progressTintColor != _progressTintColor && ![progressTintColor isEqual:_progressTintColor]) { _progressTintColor = progressTintColor; [self setNeedsDisplay]; } } - (void)setBackgroundTintColor:(UIColor *)backgroundTintColor { NSAssert(backgroundTintColor, @"The color should not be nil."); if (backgroundTintColor != _backgroundTintColor && ![backgroundTintColor isEqual:_backgroundTintColor]) { _backgroundTintColor = backgroundTintColor; [self setNeedsDisplay]; } } #pragma mark - Drawing - (void)drawRect:(CGRect)rect { CGContextRef context = UIGraphicsGetCurrentContext(); if (_annular) { // Draw background CGFloat lineWidth = 2.f; UIBezierPath *processBackgroundPath = [UIBezierPath bezierPath]; processBackgroundPath.lineWidth = lineWidth; processBackgroundPath.lineCapStyle = kCGLineCapButt; CGPoint center = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds)); CGFloat radius = (self.bounds.size.width - lineWidth)/2; CGFloat startAngle = - ((float)M_PI / 2); // 90 degrees CGFloat endAngle = (2 * (float)M_PI) + startAngle; [processBackgroundPath addArcWithCenter:center radius:radius startAngle:startAngle endAngle:endAngle clockwise:YES]; [_backgroundTintColor set]; [processBackgroundPath stroke]; // Draw progress UIBezierPath *processPath = [UIBezierPath bezierPath]; processPath.lineCapStyle = kCGLineCapSquare; processPath.lineWidth = lineWidth; endAngle = (self.progress * 2 * (float)M_PI) + startAngle; [processPath addArcWithCenter:center radius:radius startAngle:startAngle endAngle:endAngle clockwise:YES]; [_progressTintColor set]; [processPath stroke]; } else { // Draw background CGFloat lineWidth = 2.f; CGRect allRect = self.bounds; CGRect circleRect = CGRectInset(allRect, lineWidth/2.f, lineWidth/2.f); CGPoint center = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds)); [_progressTintColor setStroke]; [_backgroundTintColor setFill]; CGContextSetLineWidth(context, lineWidth); CGContextStrokeEllipseInRect(context, circleRect); // 90 degrees CGFloat startAngle = - ((float)M_PI / 2.f); // Draw progress UIBezierPath *processPath = [UIBezierPath bezierPath]; processPath.lineCapStyle = kCGLineCapButt; processPath.lineWidth = lineWidth * 2.f; CGFloat radius = (CGRectGetWidth(self.bounds) / 2.f) - (processPath.lineWidth / 2.f); CGFloat endAngle = (self.progress * 2.f * (float)M_PI) + startAngle; [processPath addArcWithCenter:center radius:radius startAngle:startAngle endAngle:endAngle clockwise:YES]; // Ensure that we don't get color overlapping when _progressTintColor alpha < 1.f. CGContextSetBlendMode(context, kCGBlendModeCopy); [_progressTintColor set]; [processPath stroke]; } } @end @implementation MBBarProgressView #pragma mark - Lifecycle - (id)init { return [self initWithFrame:CGRectMake(.0f, .0f, 120.0f, 20.0f)]; } - (id)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { _progress = 0.f; _lineColor = [UIColor whiteColor]; _progressColor = [UIColor whiteColor]; _progressRemainingColor = [UIColor clearColor]; self.backgroundColor = [UIColor clearColor]; self.opaque = NO; } return self; } #pragma mark - Layout - (CGSize)intrinsicContentSize { return CGSizeMake(120.f, 10.f); } #pragma mark - Properties - (void)setProgress:(float)progress { if (progress != _progress) { _progress = progress; [self setNeedsDisplay]; } } - (void)setProgressColor:(UIColor *)progressColor { NSAssert(progressColor, @"The color should not be nil."); if (progressColor != _progressColor && ![progressColor isEqual:_progressColor]) { _progressColor = progressColor; [self setNeedsDisplay]; } } - (void)setProgressRemainingColor:(UIColor *)progressRemainingColor { NSAssert(progressRemainingColor, @"The color should not be nil."); if (progressRemainingColor != _progressRemainingColor && ![progressRemainingColor isEqual:_progressRemainingColor]) { _progressRemainingColor = progressRemainingColor; [self setNeedsDisplay]; } } #pragma mark - Drawing - (void)drawRect:(CGRect)rect { CGContextRef context = UIGraphicsGetCurrentContext(); CGContextSetLineWidth(context, 2); CGContextSetStrokeColorWithColor(context,[_lineColor CGColor]); CGContextSetFillColorWithColor(context, [_progressRemainingColor CGColor]); // Draw background and Border CGFloat radius = (rect.size.height / 2) - 2; CGContextMoveToPoint(context, 2, rect.size.height/2); CGContextAddArcToPoint(context, 2, 2, radius + 2, 2, radius); CGContextAddArcToPoint(context, rect.size.width - 2, 2, rect.size.width - 2, rect.size.height / 2, radius); CGContextAddArcToPoint(context, rect.size.width - 2, rect.size.height - 2, rect.size.width - radius - 2, rect.size.height - 2, radius); CGContextAddArcToPoint(context, 2, rect.size.height - 2, 2, rect.size.height/2, radius); CGContextDrawPath(context, kCGPathFillStroke); CGContextSetFillColorWithColor(context, [_progressColor CGColor]); radius = radius - 2; CGFloat amount = self.progress * rect.size.width; // Progress in the middle area if (amount >= radius + 4 && amount <= (rect.size.width - radius - 4)) { CGContextMoveToPoint(context, 4, rect.size.height/2); CGContextAddArcToPoint(context, 4, 4, radius + 4, 4, radius); CGContextAddLineToPoint(context, amount, 4); CGContextAddLineToPoint(context, amount, radius + 4); CGContextMoveToPoint(context, 4, rect.size.height/2); CGContextAddArcToPoint(context, 4, rect.size.height - 4, radius + 4, rect.size.height - 4, radius); CGContextAddLineToPoint(context, amount, rect.size.height - 4); CGContextAddLineToPoint(context, amount, radius + 4); CGContextFillPath(context); } // Progress in the right arc else if (amount > radius + 4) { CGFloat x = amount - (rect.size.width - radius - 4); CGContextMoveToPoint(context, 4, rect.size.height/2); CGContextAddArcToPoint(context, 4, 4, radius + 4, 4, radius); CGContextAddLineToPoint(context, rect.size.width - radius - 4, 4); CGFloat angle = -acos(x/radius); if (isnan(angle)) angle = 0; CGContextAddArc(context, rect.size.width - radius - 4, rect.size.height/2, radius, M_PI, angle, 0); CGContextAddLineToPoint(context, amount, rect.size.height/2); CGContextMoveToPoint(context, 4, rect.size.height/2); CGContextAddArcToPoint(context, 4, rect.size.height - 4, radius + 4, rect.size.height - 4, radius); CGContextAddLineToPoint(context, rect.size.width - radius - 4, rect.size.height - 4); angle = acos(x/radius); if (isnan(angle)) angle = 0; CGContextAddArc(context, rect.size.width - radius - 4, rect.size.height/2, radius, -M_PI, angle, 1); CGContextAddLineToPoint(context, amount, rect.size.height/2); CGContextFillPath(context); } // Progress is in the left arc else if (amount < radius + 4 && amount > 0) { CGContextMoveToPoint(context, 4, rect.size.height/2); CGContextAddArcToPoint(context, 4, 4, radius + 4, 4, radius); CGContextAddLineToPoint(context, radius + 4, rect.size.height/2); CGContextMoveToPoint(context, 4, rect.size.height/2); CGContextAddArcToPoint(context, 4, rect.size.height - 4, radius + 4, rect.size.height - 4, radius); CGContextAddLineToPoint(context, radius + 4, rect.size.height/2); CGContextFillPath(context); } } @end @interface MBBackgroundView () @property UIVisualEffectView *effectView; @end @implementation MBBackgroundView #pragma mark - Lifecycle - (instancetype)initWithFrame:(CGRect)frame { if ((self = [super initWithFrame:frame])) { _style = MBProgressHUDBackgroundStyleBlur; _blurEffectStyle = UIBlurEffectStyleLight; _color = [UIColor colorWithWhite:0.8f alpha:0.6f]; self.clipsToBounds = YES; [self updateForBackgroundStyle]; } return self; } #pragma mark - Layout - (CGSize)intrinsicContentSize { // Smallest size possible. Content pushes against this. return CGSizeZero; } #pragma mark - Appearance - (void)setStyle:(MBProgressHUDBackgroundStyle)style { if (_style != style) { _style = style; [self updateForBackgroundStyle]; } } - (void)setColor:(UIColor *)color { NSAssert(color, @"The color should not be nil."); if (color != _color && ![color isEqual:_color]) { _color = color; [self updateViewsForColor:color]; } } #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000 || TARGET_OS_TV - (void)setBlurEffectStyle:(UIBlurEffectStyle)blurEffectStyle { if (_blurEffectStyle == blurEffectStyle) { return; } _blurEffectStyle = blurEffectStyle; [self updateForBackgroundStyle]; } #endif /////////////////////////////////////////////////////////////////////////////////////////// #pragma mark - Views - (void)updateForBackgroundStyle { [self.effectView removeFromSuperview]; self.effectView = nil; MBProgressHUDBackgroundStyle style = self.style; if (style == MBProgressHUDBackgroundStyleBlur) { UIBlurEffect *effect = [UIBlurEffect effectWithStyle:self.blurEffectStyle]; UIVisualEffectView *effectView = [[UIVisualEffectView alloc] initWithEffect:effect]; [self insertSubview:effectView atIndex:0]; effectView.frame = self.bounds; effectView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth; self.backgroundColor = self.color; self.layer.allowsGroupOpacity = NO; self.effectView = effectView; } else { self.backgroundColor = self.color; } } - (void)updateViewsForColor:(UIColor *)color { if (self.style == MBProgressHUDBackgroundStyleBlur) { self.backgroundColor = self.color; } else { self.backgroundColor = self.color; } } @end @implementation MBProgressHUDRoundedButton #pragma mark - Lifecycle - (instancetype)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { CALayer *layer = self.layer; layer.borderWidth = 1.f; } return self; } #pragma mark - Layout - (void)layoutSubviews { [super layoutSubviews]; // Fully rounded corners CGFloat height = CGRectGetHeight(self.bounds); self.layer.cornerRadius = ceil(height / 2.f); } - (CGSize)intrinsicContentSize { // Only show if we have associated control events if (self.allControlEvents == 0) return CGSizeZero; CGSize size = [super intrinsicContentSize]; // Add some side padding size.width += 20.f; return size; } #pragma mark - Color - (void)setTitleColor:(UIColor *)color forState:(UIControlState)state { [super setTitleColor:color forState:state]; // Update related colors [self setHighlighted:self.highlighted]; self.layer.borderColor = color.CGColor; } - (void)setHighlighted:(BOOL)highlighted { [super setHighlighted:highlighted]; UIColor *baseColor = [self titleColorForState:UIControlStateSelected]; self.backgroundColor = highlighted ? [baseColor colorWithAlphaComponent:0.1f] : [UIColor clearColor]; } @end ================================================ FILE: zhuishushenqi/Vendor/XimalayaSDK_iOS_5.5.1/include/Model/XMAlbum.h ================================================ // // XMAlbum.h // // Create by 王瑞 on 28/8/2015 // Copyright © 2015. All rights reserved. // // Model file Generated using JSONExport: https://github.com/Ahmed-Ali/JSONExport #import <UIKit/UIKit.h> #import "XMAnnouncer.h" #import "XMLastUptrack.h" @interface XMAlbum : NSObject @property (nonatomic, strong) NSString * albumIntro; @property (nonatomic, strong) NSString * albumTags; @property (nonatomic, strong) NSString * albumTitle; @property (nonatomic, strong) XMAnnouncer * announcer; @property (nonatomic, strong) NSString * coverUrlLarge; @property (nonatomic, strong) NSString * coverUrlMiddle; @property (nonatomic, strong) NSString * coverUrlSmall; @property (nonatomic, assign) NSInteger favoriteCount; @property (nonatomic, assign) NSInteger albumId; @property (nonatomic, assign) NSInteger includeTrackCount; @property (nonatomic, assign) NSInteger isFinished; @property (nonatomic, strong) NSString * kind; @property (nonatomic, strong) XMLastUptrack * lastUptrack; @property (nonatomic, assign) NSInteger playCount; @property (nonatomic, assign) NSInteger subscribeCount; @property (nonatomic, assign) double createdAt; @property (nonatomic, assign) double updatedAt; //可否下载,YES-可下载,NO-不可下载 @property (nonatomic, assign) BOOL canDownload; -(instancetype)initWithDictionary:(NSDictionary *)dictionary; -(NSDictionary *)toDictionary; //--------------------------------付费相关---------------------------------------------------- /** * 是否是付费专辑,-1 - 无此属性;0 - 免费专辑;1 - 付费专辑 */ @property (nonatomic, assign) NSInteger isPaid; /** * 专辑内声音排序是否自然序,自然序是指先上传的声音在前面,晚上传的声音在后面 */ @property (nonatomic, assign) BOOL tracksNaturalOrdered; /** * 是否支持试听 */ @property (nonatomic, assign) BOOL hasSample; /** * 预计更新多少集 */ @property (nonatomic, assign) NSInteger estimatedTrackCount; /** * 专辑内包含的整条免费听声音总数 */ @property (nonatomic, assign) NSInteger freeTrackCount; /** * 支持的购买类型,1-只支持分集购买,2-只支持整张专辑购买,3-同时支持分集购买和整张专辑购买 */ @property (nonatomic, assign) NSInteger composedPriceType; /** * 专辑富文本简介 */ @property (nonatomic, strong) NSString *albumRichIntro; /** * 主讲人介绍 */ @property (nonatomic, strong) NSString *speakerIntro; /** * 专辑内整条免费听声音ID列表,英文逗号分隔 */ @property (nonatomic, strong) NSString *freeTrackIds; /** * 营销简介 */ @property (nonatomic, strong) NSString *saleIntro; /** * 对应喜马拉雅APP上的“你将获得”,主要卖点,是由UGC主播提供的富文本 */ @property (nonatomic, strong) NSString *expectedRevenue; /** * 购买须知,富文本 */ @property (nonatomic, strong) NSString *buyNotes; /** * 主讲人自定义标题 */ @property (nonatomic, strong) NSString *speakerTitle; /** * 主讲人自定义标题下的内容,富文本 */ @property (nonatomic, strong) NSString *speakerContent; /** * 付费专辑详情页焦点图,无则返回空字符串”” */ @property (nonatomic, strong) NSString *detailBannerUrl; /** * 支持的详细价格模型列表 */ //@property (nonatomic, strong) XMPriceTypeDetail *priceTypeDetail; @property (nonatomic, strong) NSString *priceTypeDetail; /** * 专辑评分 */ @property (nonatomic, assign) NSInteger albumScore; /** * 目标人群或适合谁听 */ @property (nonatomic, strong) NSString *targetCloud; @end ================================================ FILE: zhuishushenqi/Vendor/XimalayaSDK_iOS_5.5.1/include/Model/XMAlbumColumn.h ================================================ // // XMAlbumColumn.h // // Create by nali on 23/2/2017 // Copyright © 2017. All rights reserved. // // Model file Generated using JSONExport: https://github.com/Ahmed-Ali/JSONExport #import <UIKit/UIKit.h> #import "XMAlbumColumnItem.h" @interface XMAlbumColumn : NSObject @property (nonatomic, strong) NSArray * albumColumnItems; @property (nonatomic, assign) NSInteger channelPlayCount; @property (nonatomic, assign) NSInteger columnContentCount; @property (nonatomic, strong) NSString * columnIntro; @property (nonatomic, strong) NSString * columnTitle; @property (nonatomic, assign) NSInteger createdAt; @property (nonatomic, assign) NSInteger currentPage; @property (nonatomic, assign) NSInteger idField; @property (nonatomic, strong) NSString * kind; @property (nonatomic, assign) NSInteger totalCount; @property (nonatomic, assign) NSInteger totalPage; @property (nonatomic, assign) NSInteger updatedAt; -(instancetype)initWithDictionary:(NSDictionary *)dictionary; -(NSDictionary *)toDictionary; @end ================================================ FILE: zhuishushenqi/Vendor/XimalayaSDK_iOS_5.5.1/include/Model/XMAlbumColumnItem.h ================================================ // // XMAlbumColumnItem.h // // Create by nali on 23/2/2017 // Copyright © 2017. All rights reserved. // // Model file Generated using JSONExport: https://github.com/Ahmed-Ali/JSONExport #import <UIKit/UIKit.h> #import "XMDimension.h" @interface XMAlbumColumnItem : NSObject @property (nonatomic, assign) NSInteger categoryId; @property (nonatomic, assign) NSInteger channelPlayCount; @property (nonatomic, assign) NSInteger contentType; @property (nonatomic, strong) NSString * coverUrlLarge; @property (nonatomic, strong) NSString * coverUrlMiddle; @property (nonatomic, strong) NSString * coverUrlOriginal; @property (nonatomic, strong) NSString * coverUrlSmall; @property (nonatomic, assign) NSInteger createdAt; @property (nonatomic, strong) NSArray * dimensions; @property (nonatomic, assign) NSInteger idField; @property (nonatomic, assign) NSInteger includeTrackCount; @property (nonatomic, strong) NSString * intro; @property (nonatomic, assign) NSInteger isFinished; @property (nonatomic, strong) NSString * kind; @property (nonatomic, assign) NSInteger orderNum; @property (nonatomic, assign) NSInteger playCount; @property (nonatomic, assign) NSInteger publishAt; @property (nonatomic, strong) NSString * title; @property (nonatomic, assign) NSInteger updatedAt; @property (nonatomic, assign) BOOL isPaid; -(instancetype)initWithDictionary:(NSDictionary *)dictionary; -(NSDictionary *)toDictionary; @end ================================================ FILE: zhuishushenqi/Vendor/XimalayaSDK_iOS_5.5.1/include/Model/XMAlbumGuessLike.h ================================================ // // XMAlbumGuessLike.h // // Create by 王瑞 on 6/11/2015 // Copyright © 2015. All rights reserved. // #import <UIKit/UIKit.h> @interface XMAlbumGuessLike : NSObject @property (nonatomic, assign) NSInteger basedRelativeAlbumId; @property (nonatomic, assign) NSInteger id; @property (nonatomic, strong) NSObject * isFinished; @property (nonatomic, strong) NSString * kind; @property (nonatomic, strong) NSString * recommendSrc; @property (nonatomic, strong) NSString * recommendTrace; //可否下载,YES-可下载,NO-不可下载 @property (nonatomic, assign) BOOL canDownload; -(instancetype)initWithDictionary:(NSDictionary *)dictionary; -(NSDictionary *)toDictionary; @end ================================================ FILE: zhuishushenqi/Vendor/XimalayaSDK_iOS_5.5.1/include/Model/XMAnnouncer.h ================================================ // // XMAnnouncer.h // // Create by 王瑞 on 28/8/2015 // Copyright © 2015. All rights reserved. // // Model file Generated using JSONExport: https://github.com/Ahmed-Ali/JSONExport #import <UIKit/UIKit.h> @interface XMAnnouncer : NSObject @property (nonatomic, strong) NSString * avatarUrl; @property (nonatomic, assign) NSInteger id; @property (nonatomic, assign) BOOL isVerified; @property (nonatomic, strong) NSString * kind; @property (nonatomic, strong) NSString * nickname; -(instancetype)initWithDictionary:(NSDictionary *)dictionary; -(NSDictionary *)toDictionary; @end ================================================ FILE: zhuishushenqi/Vendor/XimalayaSDK_iOS_5.5.1/include/Model/XMAnnouncerCategory.h ================================================ // // XMAnnouncerCategory.h // // Create by 瑞 王 on 18/12/2015 // Copyright © 2015. All rights reserved. // // Model file Generated using JSONExport: https://github.com/Ahmed-Ali/JSONExport #import <UIKit/UIKit.h> /** * 主播分类 */ @interface XMAnnouncerCategory : NSObject @property (nonatomic, assign) NSInteger id; @property (nonatomic, strong) NSString * kind; @property (nonatomic, assign) NSInteger orderNum; @property (nonatomic, strong) NSString * vcategoryName; -(instancetype)initWithDictionary:(NSDictionary *)dictionary; -(NSDictionary *)toDictionary; @end ================================================ FILE: zhuishushenqi/Vendor/XimalayaSDK_iOS_5.5.1/include/Model/XMAttribute.h ================================================ // // XMAttribute.h // // Create by nali on 31/8/2016 // Copyright © 2016. All rights reserved. // // Model file Generated using JSONExport: https://github.com/Ahmed-Ali/JSONExport #import <UIKit/UIKit.h> #import "XMMetadata.h" @class XMMetadata; @interface XMAttribute : NSObject @property (nonatomic, assign) NSInteger attrKey; @property (nonatomic, strong) NSString * attrValue; @property (nonatomic, strong) NSString * displayName; @property (nonatomic, strong) XMMetadata * childMetadata; -(instancetype)initWithDictionary:(NSDictionary *)dictionary; -(NSDictionary *)toDictionary; @end ================================================ FILE: zhuishushenqi/Vendor/XimalayaSDK_iOS_5.5.1/include/Model/XMBanner.h ================================================ // // XMBanner.h // // Create by 王瑞 on 2/9/2015 // Copyright © 2015. All rights reserved. // #import <UIKit/UIKit.h> @interface XMBanner : NSObject @property (nonatomic, assign) NSInteger bannerContentType; @property (nonatomic, strong) NSString * bannerRedirectUrl; @property (nonatomic, strong) NSString * bannerShortTitle; @property (nonatomic, strong) NSString * bannerTitle; @property (nonatomic, strong) NSString * bannerUrl; @property (nonatomic, assign) BOOL canShare; @property (nonatomic, assign) NSInteger bannerId; @property (nonatomic, assign) BOOL isExternalUrl; @property (nonatomic, strong) NSString * kind; @property (nonatomic, assign) NSInteger bannerUid; @property (nonatomic, assign) NSInteger trackId; @property (nonatomic, assign) NSInteger columnId; @property (nonatomic, strong) NSString *columnContentType; @property (nonatomic, assign) NSInteger albumId; @property (nonatomic, strong) NSString *thirdPartyUrl; -(instancetype)initWithDictionary:(NSDictionary *)dictionary; @end ================================================ FILE: zhuishushenqi/Vendor/XimalayaSDK_iOS_5.5.1/include/Model/XMCacheTrack.h ================================================ // // XMCacheTrack.h // XMOpenPlatform // // Created by chesterchen on 8/30/16. // // #import "XMTrack.h" @interface XMCacheTrack : XMTrack @property (nonatomic, assign) NSTimeInterval cacheTime; // 下载信息 - (void)updateDownloadInfoFromDict:(NSDictionary *)dict; - (void)copyPropertiesFrom:(XMCacheTrack *)sound; @end ================================================ FILE: zhuishushenqi/Vendor/XimalayaSDK_iOS_5.5.1/include/Model/XMCategory.h ================================================ // // XMCategory.h // // Create by 王瑞 on 24/8/2015 // Copyright © 2015. All rights reserved. // // #import <UIKit/UIKit.h> @interface XMCategory : NSObject @property (nonatomic, strong) NSString * categoryName; @property (nonatomic, strong) NSString * coverUrlLarge; @property (nonatomic, strong) NSString * coverUrlMiddle; @property (nonatomic, strong) NSString * coverUrlSmall; @property (nonatomic, assign) NSInteger categoryId; @property (nonatomic, strong) NSString * kind; -(instancetype)initWithDictionary:(NSDictionary *)dictionary; -(NSDictionary *)toDictionary; @end ================================================ FILE: zhuishushenqi/Vendor/XimalayaSDK_iOS_5.5.1/include/Model/XMCategoryHumanRecommend.h ================================================ // // XMCategoryHumanRecommend.h // // Create by 王瑞 on 7/9/2015 // Copyright © 2015. All rights reserved. // // Model file Generated using JSONExport: https://github.com/Ahmed-Ali/JSONExport #import <UIKit/UIKit.h> @interface XMCategoryHumanRecommend : NSObject @property (nonatomic, strong) NSString * categoryName; @property (nonatomic, strong) NSString * coverUrlLarge; @property (nonatomic, strong) NSString * coverUrlMiddle; @property (nonatomic, strong) NSString * coverUrlSmall; @property (nonatomic, strong) NSString * humanRecommendCategoryName; @property (nonatomic, assign) NSInteger categoryId; @property (nonatomic, strong) NSString * kind; @property (nonatomic, assign) NSInteger orderNum; -(instancetype)initWithDictionary:(NSDictionary *)dictionary; -(NSDictionary *)toDictionary; @end ================================================ FILE: zhuishushenqi/Vendor/XimalayaSDK_iOS_5.5.1/include/Model/XMColdbootDetail.h ================================================ // // XMColdbootDetail.h // // Create by 瑞 王 on 8/12/2015 // Copyright © 2015. All rights reserved. // // Model file Generated using JSONExport: https://github.com/Ahmed-Ali/JSONExport #import <UIKit/UIKit.h> @interface XMColdbootDetail : NSObject @property (nonatomic, strong) NSString * coldbootGenre; @property (nonatomic, strong) NSString * coldbootSubGenre; @property (nonatomic, strong) NSArray * coldbootTags; @property (nonatomic, strong) NSString * deviceId; @property (nonatomic, assign) NSInteger deviceType; @property (nonatomic, strong) NSString * kind; -(instancetype)initWithDictionary:(NSDictionary *)dictionary; -(NSDictionary *)toDictionary; @end ================================================ FILE: zhuishushenqi/Vendor/XimalayaSDK_iOS_5.5.1/include/Model/XMColdbootTag.h ================================================ // // XMColdbootTag.h // // Create by 瑞 王 on 8/12/2015 // Copyright © 2015. All rights reserved. // // Model file Generated using JSONExport: https://github.com/Ahmed-Ali/JSONExport #import <UIKit/UIKit.h> @interface XMColdbootTag : NSObject @property (nonatomic, strong) NSString * coldbootGenre; @property (nonatomic, strong) NSString * coldbootSubGenre; @property (nonatomic, strong) NSArray * coldbootTags; @property (nonatomic, strong) NSString * kind; -(instancetype)initWithDictionary:(NSDictionary *)dictionary; -(NSDictionary *)toDictionary; @end ================================================ FILE: zhuishushenqi/Vendor/XimalayaSDK_iOS_5.5.1/include/Model/XMColumn.h ================================================ // // XMColumn.h // // Create by 王瑞 on 26/8/2015 // Copyright © 2015. All rights reserved. // #import <UIKit/UIKit.h> @interface XMColumn : NSObject @property (nonatomic, assign) NSInteger columnContentType; @property (nonatomic, strong) NSString * columnFootNote; @property (nonatomic, strong) NSString * columnSubTitle; @property (nonatomic, strong) NSString * columnTitle; @property (nonatomic, strong) NSString * coverUrlLarge; @property (nonatomic, strong) NSString * coverUrlSmall; @property (nonatomic, assign) NSInteger id; @property (nonatomic, assign) BOOL isHot; @property (nonatomic, strong) NSString * kind; @property (nonatomic, assign) NSInteger releasedAt; -(instancetype)initWithDictionary:(NSDictionary *)dictionary; -(NSDictionary *)toDictionary; @end ================================================ FILE: zhuishushenqi/Vendor/XimalayaSDK_iOS_5.5.1/include/Model/XMColumnDetail.h ================================================ // // XMColumnDetail.h // // Create by 王瑞 on 26/8/2015 // Copyright © 2015. All rights reserved. // #import <UIKit/UIKit.h> #import "XMColumnEditor.h" @interface XMColumnDetail : NSObject @property (nonatomic, assign) NSInteger columnContentType; @property (nonatomic, strong) XMColumnEditor * columnEditor; @property (nonatomic, strong) NSString * columnIntro; @property (nonatomic, strong) NSArray * columnItems; @property (nonatomic, strong) NSString * coverUrlLarge; @property (nonatomic, assign) NSInteger id; @property (nonatomic, assign) BOOL isHot; @property (nonatomic, strong) NSString * kind; @property (nonatomic, strong) NSString * logoSmall; -(instancetype)initWithDictionary:(NSDictionary *)dictionary; -(NSDictionary *)toDictionary; @end ================================================ FILE: zhuishushenqi/Vendor/XimalayaSDK_iOS_5.5.1/include/Model/XMColumnEditor.h ================================================ // // XMColumnEditor.h // // Create by 王瑞 on 26/8/2015 // Copyright © 2015. All rights reserved. // // #import <UIKit/UIKit.h> @interface XMColumnEditor : NSObject @property (nonatomic, strong) NSString * avatarUrl; @property (nonatomic, assign) NSInteger id; @property (nonatomic, strong) NSObject * isVerified; @property (nonatomic, strong) NSString * kind; @property (nonatomic, strong) NSString * nickname; @property (nonatomic, strong) NSString * personalSignature; -(instancetype)initWithDictionary:(NSDictionary *)dictionary; -(NSDictionary *)toDictionary; @end ================================================ FILE: zhuishushenqi/Vendor/XimalayaSDK_iOS_5.5.1/include/Model/XMColumnList.h ================================================ // // XMColumnList.h // // Create by 王瑞 on 26/8/2015 // Copyright © 2015. All rights reserved. // // #import <UIKit/UIKit.h> #import "XMColumn.h" @interface XMColumnList : NSObject @property (nonatomic, strong) NSArray * columns; @property (nonatomic, assign) NSInteger currentPage; @property (nonatomic, assign) NSInteger totalCount; @property (nonatomic, assign) NSInteger totalPage; -(instancetype)initWithDictionary:(NSDictionary *)dictionary; -(NSDictionary *)toDictionary; @end ================================================ FILE: zhuishushenqi/Vendor/XimalayaSDK_iOS_5.5.1/include/Model/XMDimension.h ================================================ // // XMDimension.h // // Create by nali on 23/2/2017 // Copyright © 2017. All rights reserved. // // Model file Generated using JSONExport: https://github.com/Ahmed-Ali/JSONExport #import <UIKit/UIKit.h> @interface XMDimension : NSObject @property (nonatomic, assign) NSInteger dimId; @property (nonatomic, strong) NSString * dimName; @property (nonatomic, strong) NSString * dimValue; -(instancetype)initWithDictionary:(NSDictionary *)dictionary; -(NSDictionary *)toDictionary; @end ================================================ FILE: zhuishushenqi/Vendor/XimalayaSDK_iOS_5.5.1/include/Model/XMErrorModel.h ================================================ // // XMErrorModel.h // XMOpenPlatform // // Created by chesterchen on 08/05/2017. // // #import <Foundation/Foundation.h> @interface XMErrorModel : NSObject /** 错误编号 */ @property (nonatomic, assign) NSInteger error_no; /** 错误代码 */ @property (nonatomic, strong) NSString *error_code; /** 错误信息描述 */ @property (nonatomic, strong) NSString *error_desc; -(instancetype)initWithDictionary:(NSDictionary *)dictionary; + (instancetype)errorWithDic:(NSDictionary *)dictionary; @end ================================================ FILE: zhuishushenqi/Vendor/XimalayaSDK_iOS_5.5.1/include/Model/XMHotTrack.h ================================================ // // XMHotTrack.h // // Create by 王瑞 on 24/8/2015 // Copyright © 2015. All rights reserved. // // #import <UIKit/UIKit.h> #import "XMTrack.h" @interface XMHotTrack : NSObject @property (nonatomic, assign) NSInteger categoryId; @property (nonatomic, assign) NSInteger currentPage; @property (nonatomic, strong) NSString * tagName; @property (nonatomic, assign) NSInteger totalCount; @property (nonatomic, assign) NSInteger totalPage; @property (nonatomic, strong) NSArray * tracks; //可否下载,YES-可下载,NO-不可下载 @property (nonatomic, assign) BOOL canDownload; -(instancetype)initWithDictionary:(NSDictionary *)dictionary; -(NSDictionary *)toDictionary; @end ================================================ FILE: zhuishushenqi/Vendor/XimalayaSDK_iOS_5.5.1/include/Model/XMHotword.h ================================================ // // XMHotword.h // // Create by 王瑞 on 25/8/2015 // Copyright © 2015. All rights reserved. // // #import <UIKit/UIKit.h> @interface XMHotword : NSObject @property (nonatomic, assign) NSInteger count; @property (nonatomic, assign) NSInteger degree; @property (nonatomic, strong) NSString * searchWord; -(instancetype)initWithDictionary:(NSDictionary *)dictionary; -(NSDictionary *)toDictionary; @end ================================================ FILE: zhuishushenqi/Vendor/XimalayaSDK_iOS_5.5.1/include/Model/XMIndexRankItem.h ================================================ // // XMIndexRankItem.h // // Create by 王瑞 on 25/8/2015 // Copyright © 2015. All rights reserved. // // #import <UIKit/UIKit.h> @interface XMIndexRankItem : NSObject @property (nonatomic, strong) NSString * contentType; @property (nonatomic, assign) NSInteger id; @property (nonatomic, strong) NSString * title; -(instancetype)initWithDictionary:(NSDictionary *)dictionary; -(NSDictionary *)toDictionary; @end ================================================ FILE: zhuishushenqi/Vendor/XimalayaSDK_iOS_5.5.1/include/Model/XMLastUptrack.h ================================================ // // XMLastUptrack.h // // Create by 王瑞 on 28/8/2015 // Copyright © 2015. All rights reserved. // // Model file Generated using JSONExport: https://github.com/Ahmed-Ali/JSONExport #import <UIKit/UIKit.h> @interface XMLastUptrack : NSObject @property (nonatomic, assign) CGFloat duration; @property (nonatomic, assign) NSInteger trackId; @property (nonatomic, strong) NSString * trackTitle; @property (nonatomic, assign) double createdAt; @property (nonatomic, assign) double updatedAt; //可否下载,YES-可下载,NO-不可下载 @property (nonatomic, assign) BOOL canDownload; -(instancetype)initWithDictionary:(NSDictionary *)dictionary; -(NSDictionary *)toDictionary; @end ================================================ FILE: zhuishushenqi/Vendor/XimalayaSDK_iOS_5.5.1/include/Model/XMLiveAnnouncer.h ================================================ // // XMLiveAnnouncer.h // // Create by 王瑞 on 24/8/2015 // Copyright © 2015. All rights reserved. // // #import <UIKit/UIKit.h> @interface XMLiveAnnouncer : NSObject @property (nonatomic, strong) NSString * avatarUrl; @property (nonatomic, assign) NSInteger id; @property (nonatomic, strong) NSString * kind; @property (nonatomic, strong) NSString * nickname; @property (nonatomic, assign) double createdAt; @property (nonatomic, assign) double updatedAt; -(instancetype)initWithDictionary:(NSDictionary *)dictionary; -(NSDictionary *)toDictionary; @end ================================================ FILE: zhuishushenqi/Vendor/XimalayaSDK_iOS_5.5.1/include/Model/XMLiveCity.h ================================================ // // XMLiveCity.h // // Create by 王瑞 on 21/9/2015 // Copyright © 2015. All rights reserved. // // Model file Generated using JSONExport: https://github.com/Ahmed-Ali/JSONExport #import <UIKit/UIKit.h> @interface XMLiveCity : NSObject @property (nonatomic, assign) NSInteger cityCode; @property (nonatomic, strong) NSString * cityName; @property (nonatomic, assign) NSInteger id; @property (nonatomic, strong) NSString * kind; -(instancetype)initWithDictionary:(NSDictionary *)dictionary; -(NSDictionary *)toDictionary; @end ================================================ FILE: zhuishushenqi/Vendor/XimalayaSDK_iOS_5.5.1/include/Model/XMMetadata.h ================================================ // // XMMetadata.h // // Create by nali on 31/8/2016 // Copyright © 2016. All rights reserved. // // Model file Generated using JSONExport: https://github.com/Ahmed-Ali/JSONExport #import <UIKit/UIKit.h> #import "XMAttribute.h" @interface XMMetadata : NSObject @property (nonatomic, strong) NSArray * attributes; @property (nonatomic, strong) NSString * displayName; @property (nonatomic, strong) NSString * kind; -(instancetype)initWithDictionary:(NSDictionary *)dictionary; -(NSDictionary *)toDictionary; @end ================================================ FILE: zhuishushenqi/Vendor/XimalayaSDK_iOS_5.5.1/include/Model/XMProvince.h ================================================ // // XMProvince.h // // Create by 王瑞 on 25/8/2015 // Copyright © 2015. All rights reserved. // // #import <UIKit/UIKit.h> @interface XMProvince : NSObject @property (nonatomic, assign) double createdAt; @property (nonatomic, assign) NSInteger id; @property (nonatomic, strong) NSString * kind; @property (nonatomic, assign) NSInteger provinceCode; @property (nonatomic, strong) NSString * provinceName; -(instancetype)initWithDictionary:(NSDictionary *)dictionary; -(NSDictionary *)toDictionary; @end ================================================ FILE: zhuishushenqi/Vendor/XimalayaSDK_iOS_5.5.1/include/Model/XMRadio.h ================================================ // // XMRadio.h // // Create by 王瑞 on 24/8/2015 // Copyright © 2015. All rights reserved. // // #import <UIKit/UIKit.h> @interface XMRadio : NSObject @property (nonatomic, strong) NSString * coverUrlLarge; @property (nonatomic, strong) NSString * coverUrlSmall; @property (nonatomic, assign) NSInteger radioId; @property (nonatomic, strong) NSString * kind; @property (nonatomic, strong) NSString * programName; @property (nonatomic, strong) NSString * radioDesc; @property (nonatomic, strong) NSString * radioName; @property (nonatomic, assign) NSInteger radioPlayCount; @property (nonatomic, strong) NSString * rate24AacUrl; @property (nonatomic, strong) NSString * rate24TsUrl; @property (nonatomic, strong) NSString * rate64AacUrl; @property (nonatomic, strong) NSString * rate64TsUrl; @property (nonatomic, assign) NSInteger scheduleId; @property (nonatomic, strong) NSArray * supportBitrates; @property (nonatomic, assign) double updatedAt; -(instancetype)initWithDictionary:(NSDictionary *)dictionary; -(NSDictionary *)toDictionary; @end ================================================ FILE: zhuishushenqi/Vendor/XimalayaSDK_iOS_5.5.1/include/Model/XMRadioSchedule.h ================================================ // // XMRadioSchedule.h // // Create by 王瑞 on 24/8/2015 // Copyright © 2015. All rights reserved. // // #import <UIKit/UIKit.h> #import "XMRelatedProgram.h" @interface XMRadioSchedule : NSObject @property (nonatomic, strong) NSString * endTime; @property (nonatomic, assign) NSInteger radioScheduleId; @property (nonatomic, assign) NSInteger radioId; @property (nonatomic, strong) NSString * kind; @property (nonatomic, strong) NSString * listenBackUrl; @property (nonatomic, assign) NSInteger playType; @property (nonatomic, strong) XMRelatedProgram * relatedProgram; @property (nonatomic, strong) NSString * startTime; @property (nonatomic, assign) double updatedAt; @property (nonatomic,assign) NSInteger totalPlayedTime; @property (nonatomic,strong) NSDate *startDate; @property (nonatomic,strong) NSDate *endDate; @property (nonatomic,assign) NSTimeInterval duration; -(instancetype)initWithDictionary:(NSDictionary *)dictionary; -(NSDictionary *)toDictionary; @end ================================================ FILE: zhuishushenqi/Vendor/XimalayaSDK_iOS_5.5.1/include/Model/XMRankSectionList.h ================================================ // // XMRankSectionList.h // // Create by 王瑞 on 25/8/2015 // Copyright © 2015. All rights reserved. // // #import <UIKit/UIKit.h> #import "XMIndexRankItem.h" @interface XMRankSectionList : NSObject @property (nonatomic, assign) NSInteger categoryId; @property (nonatomic, strong) NSString * coverUrl; @property (nonatomic, strong) NSArray * indexRankItems; @property (nonatomic, strong) NSString * kind; @property (nonatomic, strong) NSString * rankContentType; @property (nonatomic, assign) NSInteger rankFirstItemId; @property (nonatomic, strong) NSString * rankFirstItemTitle; @property (nonatomic, assign) NSInteger rankItemNum; @property (nonatomic, strong) NSString * rankKey; @property (nonatomic, assign) NSInteger rankOrderNum; @property (nonatomic, assign) NSInteger rankPeriod; @property (nonatomic, strong) NSObject * rankPeriodType; @property (nonatomic, strong) NSString * rankSubTitle; @property (nonatomic, strong) NSString * rankTitle; @property (nonatomic, assign) NSInteger rankType; -(instancetype)initWithDictionary:(NSDictionary *)dictionary; -(NSDictionary *)toDictionary; @end ================================================ FILE: zhuishushenqi/Vendor/XimalayaSDK_iOS_5.5.1/include/Model/XMRelatedProgram.h ================================================ // // XMRelatedProgram.h // // Create by 王瑞 on 24/8/2015 // Copyright © 2015. All rights reserved. // // #import <UIKit/UIKit.h> #import "XMLiveAnnouncer.h" @interface XMRelatedProgram : NSObject @property (nonatomic, strong) NSString * backPicUrl; @property (nonatomic, assign) NSInteger radioProgramId; @property (nonatomic, strong) NSString * kind; @property (nonatomic, strong) NSArray * liveAnnouncers; @property (nonatomic, strong) NSString * programName; @property (nonatomic, strong) NSString * rate24AacUrl; @property (nonatomic, strong) NSString * rate24TsUrl; @property (nonatomic, strong) NSString * rate64AacUrl; @property (nonatomic, strong) NSString * rate64TsUrl; @property (nonatomic, strong) NSArray * supportBitrates; @property (nonatomic, assign) double updatedAt; -(instancetype)initWithDictionary:(NSDictionary *)dictionary; -(NSDictionary *)toDictionary; @end ================================================ FILE: zhuishushenqi/Vendor/XimalayaSDK_iOS_5.5.1/include/Model/XMSubordinatedAlbum.h ================================================ // // XMSubordinatedAlbum.h // // Create by 王瑞 on 24/8/2015 // Copyright © 2015. All rights reserved. // // #import <UIKit/UIKit.h> @interface XMSubordinatedAlbum : NSObject @property (nonatomic, strong) NSString * albumTitle; @property (nonatomic, strong) NSString * coverUrlLarge; @property (nonatomic, strong) NSString * coverUrlMiddle; @property (nonatomic, strong) NSString * coverUrlSmall; @property (nonatomic, assign) NSInteger albumId; -(instancetype)initWithDictionary:(NSDictionary *)dictionary; -(NSDictionary *)toDictionary; @end ================================================ FILE: zhuishushenqi/Vendor/XimalayaSDK_iOS_5.5.1/include/Model/XMTag.h ================================================ // // XMTag.h // // Create by 王瑞 on 24/8/2015 // Copyright © 2015. All rights reserved. // // #import <UIKit/UIKit.h> @interface XMTag : NSObject @property (nonatomic, strong) NSString * coverUrlLarge; @property (nonatomic, strong) NSString * coverUrlMiddle; @property (nonatomic, strong) NSString * coverUrlSmall; @property (nonatomic, strong) NSString * kind; @property (nonatomic, strong) NSString * tagName; -(instancetype)initWithDictionary:(NSDictionary *)dictionary; -(NSDictionary *)toDictionary; @end ================================================ FILE: zhuishushenqi/Vendor/XimalayaSDK_iOS_5.5.1/include/Model/XMTrack.h ================================================ // // XMTrack.h // // Create by 王瑞 on 24/8/2015 // Copyright © 2015. All rights reserved. // // #import <UIKit/UIKit.h> #import "XMAnnouncer.h" #import "XMSubordinatedAlbum.h" // 下载状态 typedef NS_ENUM(NSInteger, XMCacheTrackStatus) { XMCacheTrackStatusReady = 20, //!<准备下载 XMCacheTrackStatusDownloading, //!<下载中 XMCacheTrackStatusDownloaded, //!<已下载 XMCacheTrackStatusPausedBySystem, //!<非人为暂停 XMCacheTrackStatusPausedByUser, //!<人为暂停 XMCacheTrackStatusCancelled, //!<已取消下载 XMCacheTrackStatusFailed, //!<下载失败 XMCacheTrackStatusHeld //!<只注册,不立即下载 }; @interface XMTrack : NSObject<NSCoding> @property (nonatomic, strong) XMAnnouncer * announcer; @property (nonatomic, assign) NSInteger commentCount; @property (nonatomic, strong) NSString * coverUrlLarge; @property (nonatomic, strong) NSString * coverUrlMiddle; @property (nonatomic, strong) NSString * coverUrlSmall; @property (nonatomic, assign) NSInteger downloadCount; @property (nonatomic, assign) NSInteger downloadSize; @property (nonatomic, strong) NSString * downloadUrl; @property (nonatomic, assign) NSInteger duration; @property (nonatomic, assign) NSInteger favoriteCount; @property (nonatomic, assign) NSInteger trackId; @property (nonatomic, strong) NSString * kind; @property (nonatomic, assign) NSInteger orderNum; @property (nonatomic, assign) NSInteger playCount; @property (nonatomic, assign) NSInteger playSize24M4a; @property (nonatomic, assign) NSInteger playSize32; @property (nonatomic, assign) NSInteger playSize64; @property (nonatomic, assign) NSInteger playSize64M4a; @property (nonatomic, strong) NSString * playUrl24M4a; //!< 24码率M4A格式播放地址 @property (nonatomic, strong) NSString * playUrl32; //!< 32码率MP3格式播放地址 @property (nonatomic, strong) NSString * playUrl64; //!< 64码率MP3格式播放地址 @property (nonatomic, strong) NSString * playUrl64M4a; //!< 64码率M4A格式播放地址 @property (nonatomic, strong) NSString * playUrlAmr; //!< 声音AMR格式地址 @property (nonatomic, assign) NSInteger playSizeAmr; //!< 声音AMR格式大小 @property (nonatomic, assign) NSInteger source; @property (nonatomic, strong) XMSubordinatedAlbum * subordinatedAlbum; @property (nonatomic, strong) NSString * trackIntro; @property (nonatomic, strong) NSString * trackTags; @property (nonatomic, strong) NSString * trackTitle; @property (nonatomic, assign) double updatedAt; @property (nonatomic, assign) double createdAt; //可否下载,YES-可下载,NO-不可下载 @property (nonatomic, assign) BOOL canDownload; @property (nonatomic, assign) BOOL playing; @property (nonatomic, assign) BOOL pause; @property (nonatomic, strong) NSDate *startPlayTime; @property (nonatomic, assign) NSInteger listenedTime; @property (nonatomic, assign) NSInteger listenedPosition; @property (nonatomic, assign) XMCacheTrackStatus status; @property (nonatomic, copy ) NSString *filePath; @property (nonatomic, assign) unsigned long downloadedBytes; @property (nonatomic, assign) unsigned long totalBytes; -(instancetype)initWithDictionary:(NSDictionary *)dictionary; -(NSDictionary *)toDictionary; - (void)copyPropertiesFrom:(XMTrack *)sound; //--------------------------------付费相关---------------------------------------------------- /** * 是否付费,固定值true */ @property (nonatomic, assign) BOOL isPaid; /** * 声音是否整条免费听 */ @property (nonatomic, assign) BOOL isFree; /** * 是否加V。如果这条声音是转采的,则announcer是上传原始专辑的主播 */ @property (nonatomic, assign) BOOL isVerified; /** * 是否片花,片花声音一定是整条免费听声音 */ @property (nonatomic, assign) BOOL isTrailer; /** * 是否支持试听 */ @property (nonatomic, assign) BOOL hasSample; /** * 是否已购买 */ @property (nonatomic, assign) BOOL isBought; /** * 试听时长,如果不支持试听则这个试听时长为0 */ @property (nonatomic, assign) NSInteger sampleDuration; /** * 判断是否为付费音频 */ - (BOOL)isPayTrack; @end ================================================ FILE: zhuishushenqi/Vendor/XimalayaSDK_iOS_5.5.1/include/Model/XMTrackColumn.h ================================================ // // XMTrackColumn.h // // Create by nali on 2/3/2017 // Copyright © 2017. All rights reserved. // // Model file Generated using JSONExport: https://github.com/Ahmed-Ali/JSONExport #import <UIKit/UIKit.h> #import "XMTrackColumnItem.h" @interface XMTrackColumn : NSObject @property (nonatomic, assign) NSInteger channelPlayCount; @property (nonatomic, strong) NSString * columnIntro; @property (nonatomic, strong) NSString * columnTitle; @property (nonatomic, assign) NSInteger createdAt; @property (nonatomic, assign) NSInteger currentPage; @property (nonatomic, assign) NSInteger idField; @property (nonatomic, strong) NSString * kind; @property (nonatomic, assign) NSInteger totalCount; @property (nonatomic, assign) NSInteger totalPage; @property (nonatomic, strong) NSArray * trackColumnItems; @property (nonatomic, assign) NSInteger updatedAt; -(instancetype)initWithDictionary:(NSDictionary *)dictionary; -(NSDictionary *)toDictionary; @end ================================================ FILE: zhuishushenqi/Vendor/XimalayaSDK_iOS_5.5.1/include/Model/XMTrackColumnItem.h ================================================ // // XMTrackColumnItem.h // // Create by nali on 2/3/2017 // Copyright © 2017. All rights reserved. // // Model file Generated using JSONExport: https://github.com/Ahmed-Ali/JSONExport #import <UIKit/UIKit.h> #import "XMDimension.h" @interface XMTrackColumnItem : NSObject @property (nonatomic, assign) BOOL canDownload; @property (nonatomic, assign) NSInteger categoryId; @property (nonatomic, assign) NSInteger channelPlayCount; @property (nonatomic, assign) NSInteger contentType; @property (nonatomic, strong) NSString * coverUrlLarge; @property (nonatomic, strong) NSString * coverUrlMiddle; @property (nonatomic, strong) NSString * coverUrlOriginal; @property (nonatomic, strong) NSString * coverUrlSmall; @property (nonatomic, assign) NSInteger createdAt; @property (nonatomic, strong) NSArray * dimensions; @property (nonatomic, assign) NSInteger downloadSize; @property (nonatomic, strong) NSString * downloadUrl; @property (nonatomic, assign) NSInteger duration; @property (nonatomic, assign) NSInteger idField; @property (nonatomic, strong) NSString * intro; @property (nonatomic, assign) NSInteger isFinished; @property (nonatomic, strong) NSString * kind; @property (nonatomic, assign) NSInteger playCount; @property (nonatomic, strong) NSString * playSize24M4a; @property (nonatomic, strong) NSString * playSize32; @property (nonatomic, strong) NSString * playSize64; @property (nonatomic, strong) NSString * playSize64M4a; @property (nonatomic, strong) NSString * playUrl24M4a; @property (nonatomic, strong) NSString * playUrl32; @property (nonatomic, strong) NSString * playUrl64; @property (nonatomic, strong) NSString * playUrl64M4a; @property (nonatomic, assign) NSInteger publishAt; @property (nonatomic, strong) NSString * shortExtInfo; @property (nonatomic, strong) NSArray * subordinatedAlbum; @property (nonatomic, strong) NSString * title; @property (nonatomic, assign) NSInteger updatedAt; -(instancetype)initWithDictionary:(NSDictionary *)dictionary; -(NSDictionary *)toDictionary; @end ================================================ FILE: zhuishushenqi/Vendor/XimalayaSDK_iOS_5.5.1/include/Model/XMTrackDownloadStatus.h ================================================ // // XMTrackDownloadStatus.h // XMOpenPlatform // // Created by chesterchen on 9/27/16. // // #import <Foundation/Foundation.h> #import "XMTrack.h" @interface XMTrackDownloadStatus : NSObject @property (nonatomic, assign) XMCacheTrackStatus state; @property (nonatomic, assign) unsigned long downloadedBytes; @property (nonatomic, assign) unsigned long totalBytes; @property (nonatomic, assign) NSTimeInterval timeStamp; @end ================================================ FILE: zhuishushenqi/Vendor/XimalayaSDK_iOS_5.5.1/include/Player/XMADAudioPlayer.h ================================================ // // XMADAudioPlayer.h // ting // // Created by kuben on 15/5/6. // // #import <Foundation/Foundation.h> #import "XMSingleTone.h" typedef void (^XMDoneBlock)(void); @class XMADAudioItem; /* * @brief audio player start notification for NSNotificationCenter */ extern NSString* AdAudioPlayedDidStartNotification ; /* * @brief */ extern NSString* AdAudioPlayedDidPauseNotification ; extern NSString* AdAudioPlayedDidPauseStartNotification; /* * @brief audio player end notification for NSNotificationCenter */ extern NSString* AdAudioPlayedDidEndNotification ; /** * @brief audio player data ready */ extern NSString* AdAudioDataDidGetNotification ; @interface XMADAudioPlayer : NSObject DECLARE_SINGLETON_METHOD(XMADAudioPlayer,sharedPlayer); /* * @brief audio player Done to do some thing * */ @property (nonatomic, copy)XMDoneBlock playDoneBlock; - (XMADAudioItem*)currentPlayAdItem; - (void)playSound:(NSURL*)sounURL withSoundId:(long)soundId; - (void)playAdSound:(XMADAudioItem*)adItem; - (void)pause; - (BOOL)isRunningSoundLogo; - (BOOL)isAdAudioPlaying:(long)soundId; - (void)setRunningSoundLogoStatus:(BOOL)status; - (BOOL)isPlaying; /** * remove ad cache */ - (void)checkAndRemoveAudioCache; @end ================================================ FILE: zhuishushenqi/Vendor/XimalayaSDK_iOS_5.5.1/include/Player/XMSDKPlayer.h ================================================ // // XMSDKPlayer.h // libXMOpenPlatform // // Created by nali on 15/7/13. // Copyright (c) 2015年 ximalaya. All rights reserved. // #import <Foundation/Foundation.h> #import "XMSDK.h" #import <CoreGraphics/CGBase.h> #import <AVFoundation/AVFoundation.h> typedef NS_ENUM(NSInteger, XMSDKPlayMode) { XMSDKPlayModeTrack = 0, XMSDKPlayModeLive }; typedef NS_ENUM(NSInteger, XMSDKTrackPlayMode) { XMTrackPlayerModeList = 0, // 列表 (默认) XMTrackModeSingle, // 单曲循环 XMTrackModeRandom, // 随机顺序 XMTrackModeCycle, // 循环顺序 XMTrackPlayerModeEnd // mark for rounded }; // PlayerState typedef NS_ENUM(NSInteger, XMSDKPlayerState) { XMSDKPlayerStatePlaying = 0, //正在播放 XMSDKPlayerStatePaused, //暂停 XMSDKPlayerStateStop //停止或其他状态 }; // livePlayerState typedef NS_ENUM(NSInteger, XMSDKLivePlayerState) { XMSDKLivePlayerStatePlaying = 0, //正在播放 XMSDKLivePlayerStatePaused, //暂停 XMSDKLivePlayerStateStop //停止或其他状态 }; //------------------------------------------------------------------------------------ #pragma mark - XMTrackPlayerDelegate @protocol XMTrackPlayerDelegate <NSObject> @optional #pragma mark - process notification //播放时被调用,频率为1s,告知当前播放进度和播放时间 - (void)XMTrackPlayNotifyProcess:(CGFloat)percent currentSecond:(NSUInteger)currentSecond; //播放时被调用,告知当前播放器的缓冲进度 - (void)XMTrackPlayNotifyCacheProcess:(CGFloat)percent; #pragma mark - player state change //播放列表结束时被调用 - (void)XMTrackPlayerDidPlaylistEnd; //将要播放时被调用 - (void)XMTrackPlayerWillPlaying; //已经播放时被调用 - (void)XMTrackPlayerDidPlaying; //暂停时调用 - (void)XMTrackPlayerDidPaused; //停止时调用 - (void)XMTrackPlayerDidStopped; //结束播放时调用 - (void)XMTrackPlayerDidEnd; //切换声音时调用 - (void)XMTrackPlayerDidChangeToTrack:(XMTrack *)track; //播放失败时调用 - (void)XMTrackPlayerDidFailedToPlayTrack:(XMTrack *)track withError:(NSError *)error; //播放失败时是否继续播放下一首 - (BOOL)XMTrackPlayerShouldContinueNextTrackWhenFailed:(XMTrack *)track; //成功替换播放列表时调用 - (void)XMTrackPlayerDidReplacePlayList:(NSArray *)list; //播放数据请求失败时调用,data.description是错误信息 - (void)XMTrackPlayerDidErrorWithType:(NSString *)type withData:(NSDictionary*)data; //- (void)XMTrackPlayerDidErrorWithType:(NSInteger)type withData:(NSDictionary*)data; //没有网络情况下播放器因缓冲已播完而停止播放时触发 - (void)XMTrackPlayerDidPausePlayForBadNetwork; @end //------------------------------------------------------------------------------------ #pragma mark - XMLivePlayerDelegate @protocol XMLivePlayerDelegate <NSObject> @optional #pragma mark - live radio - (void)XMLiveRadioPlayerDidFailWithError:(NSError *)error; - (void)XMLiveRadioPlayerNotifyCacheProgress:(CGFloat)percent; - (void)XMLiveRadioPlayerNotifyPlayProgress:(CGFloat)percent currentTime:(NSInteger)currentTime; - (void)XMLiveRadioPlayerDidEnd; - (void)XMLiveRadioPlayerDidPaused; - (void)XMLiveRadioPlayerDidPlaying; - (void)XMLiveRadioPlayerDidStart; - (void)XMLiveRadioPlayerDidStopped; - (void)XMLivePlayerWillPlaying; - (void)XMLivePlayerDidDataBufferStart; - (void)XMLivePlayerDidDataBufferEnd; @end //------------------------------------------------------------------------------------ #pragma mark - XMSDKPlayer @interface XMSDKPlayer : NSObject + (XMSDKPlayer *)sharedPlayer; @property (nonatomic,assign) id<XMTrackPlayerDelegate> trackPlayDelegate; @property (nonatomic,assign) id<XMLivePlayerDelegate> livePlayDelegate; // 默认使用低码率的url进行播放,如果需要使用高码率,请将usingHighQualityUrl设置为YES; @property (nonatomic,assign) BOOL usingHighQualityUrl; // 每个声音都默认从上次中断处开始播放,如需要从头开始播放,请将usingResumeFromStart设置为YES; @property (nonatomic,assign) BOOL usingResumeFromStart; //如需要从指定时间开始播放,请将usingStartFromAppointedTime设置为YES,并设置指定时间appointedTime; @property (nonatomic,assign) BOOL usingStartFromAppointedTime; @property (nonatomic,assign) float appointedTime; //音量属性,用于显示音量 @property (nonatomic,assign) float sdkPlayerVolume; //播放状态 @property (nonatomic,assign,readonly) XMSDKPlayerState playerState; //track播放状态 @property (nonatomic,assign,readonly) XMSDKLivePlayerState livePlayerState; //live播放状态 //播放速率(范围:0.5~2.0) @property (nonatomic, assign) CGFloat playRate; /* The play rate for the sound. 1.0 is normal, 0.5 is half speed, 2.0 is double speed. */ #pragma mark 配置相关方法 /** * 设置播放器播放模式:专辑播放、电台播放 */ - (void)setPlayMode:(XMSDKPlayMode)playMode; /** * 设置播放器的音量,volume范围:0~1 */ - (void)setVolume:(float)volume; /** * 获得播放器缓存的大小,第三方可以自己统计缓存大小 */ - (unsigned long long)getTotalCacheSize; /** * 重置播放速率,即恢复正常速率playRate=1.0 */ - (void)resetPlaySpeed; #pragma mark 播放相关方法 /** * 播放声音列表 */ - (void)playWithTrack:(XMTrack *)track playlist:(NSArray *)playlist; /** * 通过url进行播放 */ - (void)playWithDecryptedUrl:(NSURL *)dUrl; /** * 接着播上一次正在播放的专辑 */ - (void)continuePlayFromAlbum:(NSInteger)albumID track:(NSInteger)trackID; /** * 暂停当前播放 */ - (void)pauseTrackPlay; /** * 恢复当前播放 */ - (void)resumeTrackPlay; /** * 停止当前播放 */ - (void)stopTrackPlay; /** * 更新当前播放列表 */ - (void)replacePlayList:(NSArray *)playlist; /** * 是否有下一首 */ + (BOOL)hasNextTrack; /** * 是否有上一首 */ + (BOOL)hasPrevTrack; /** * 播放下一首 */ - (BOOL)playNextTrack; /** * 播放上一首 */ - (BOOL)playPrevTrack; /** * 设置播放器自动播放下一首 */ - (void)setAutoNexTrack:(BOOL)status; /** * 返回当前播放列表 */ - (NSArray *)playList; /** * 返回下一首 */ - (XMTrack *)nextTrack; /** * 返回上一首 */ - (XMTrack *)prevTrack; /** * 设置播放器从特定的时间播放 */ - (void)seekToTime:(CGFloat)time; /** * 清空缓存 */ - (void)clearCacheSafely; /** * 设置当前播放器的下一首选择模式:列表播放、单曲循环、随机、列表循环 */ - (void)setTrackPlayMode:(XMSDKTrackPlayMode)trackPlayMode; /** * 获取当前播放器的下一首选择模式:列表播放、单曲循环、随机、列表循环 */ - (XMSDKTrackPlayMode)getTrackPlayMode; /** * 返回当前播放的声音 */ - (XMTrack*)currentTrack; #pragma mark 电台相关方法 /** * 开始播放直播电台 */ - (void)startLivePlayWithRadio:(XMRadio*)radio; /** * 停止当前电台播放 */ - (void)pauseLivePlay; /** * 恢复当前电台播放 */ - (void)resumeLivePlay; /** * 停止当前电台播放 */ - (void)stopLivePlay; /** * 播放直播电台当前时间之前的节目 */ - (void)startHistoryLivePlayWithRadio:(XMRadio*)radio withProgram:(XMRadioSchedule*)program; /** * 播放直播电台当前时间之前的节目列表 */ - (void)startHistoryLivePlayWithRadio:(XMRadio*)radio withProgram:(XMRadioSchedule*)program inProgramList:(NSArray*)list; /** * 设置播放器到指定的时间播放 */ - (BOOL)seekHistoryLivePlay:(double)durtion; /** * 播放下一个录播电台节目 */ - (void)playNextProgram; /** * 播放上一个录播电台节目 */ - (void)playPreProgram; /** * 清理缓存(即上文同名方法) */ //- (void)clearCacheSafely; /** * 返回当前正在播放的电台 */ - (XMRadio*)currentPlayingRadio; /** * 返回当前正在播放的节目 */ - (XMRadioSchedule*)currentPlayingProgram; @end ================================================ FILE: zhuishushenqi/Vendor/XimalayaSDK_iOS_5.5.1/include/Player/XMSDKPlayerDataCollector.h ================================================ // // XMSDKPlayerDataCollector.h // XMOpenPlatform // // Created by nali on 16/1/29. // // #import <Foundation/Foundation.h> @interface XMTrackPlayDataCollectItem : NSObject @property (nonatomic, assign) NSInteger trackID; //声音ID @property (nonatomic, assign) double duration; //播放时长,单位秒。即播放一个音频过程中,真正处于播放中状态的总时间。 @property (nonatomic, assign) double playedSecs; //播放第几秒或最后播放到的位置,是相对于这个音频开始位置的一个值。如果没有拖动播放条、快进、快退、暂停、单曲循环等操作,played_secs的值一般和duration一致。 @property (nonatomic, assign) long startedAt; //播放开始时刻,Unix毫秒数时间戳 @property (nonatomic, assign) NSInteger playType; //0:联网播放,1:断网播放 @end @interface XMRadioPlayDataCollectItem : NSObject @property (nonatomic, assign) NSInteger radioID; //电台ID @property (nonatomic, assign) NSInteger programScheduleID; //节目排期ID @property (nonatomic, assign) NSInteger programID; //节目ID @property (nonatomic, assign) double duration; //播放时长,单位秒。即播放一个音频过程中,真正处于播放中状态的总时间。 @property (nonatomic, assign) double playedSecs; //播放第几秒或最后播放到的位置,是相对于这个音频开始位置的一个值。如果没有拖动播放条、快进、快退、暂停、单曲循环等操作,played_secs的值一般和duration一致。 @property (nonatomic, assign) long startedAt; //播放开始时刻,Unix毫秒数时间戳 @end @interface XMSDKPlayerDataCollector : NSObject + (XMSDKPlayerDataCollector *)sharedInstance; /** * 向喜马拉雅发送播放track的统计数据(单个) */ - (void)sendTrackPlayDataWithItem:(XMTrackPlayDataCollectItem*)item; /** * 向喜马拉雅发送播放track的统计数据(批量) */ - (void)sendTrackPlayDataWithItems:(NSArray*)items; /** * 向喜马拉雅发送播放radio的统计数据(单个) */ - (void)sendRadioPlayDataWithItem:(XMRadioPlayDataCollectItem*)item; /** * 向喜马拉雅发送播放radio的统计数据(批量) */ - (void)sendRadioPlayDataWithItems:(NSArray*)items; @end ================================================ FILE: zhuishushenqi/Vendor/XimalayaSDK_iOS_5.5.1/include/Request/XMReqMgr.h ================================================ // // XMReqMgr.h // XMOpenPlatform // // Created by nali on 15/8/24. // // #import <Foundation/Foundation.h> #import "XMSDKInfo.h" #import "XMErrorModel.h" typedef void (^XMRequestHandler)(id result,XMErrorModel *error); //------------------------------------------------------------------------------------ #pragma mark - XMReqDelegate @protocol XMReqDelegate <NSObject> /* 初始化请求成功 */ -(void)didXMInitReqOK:(BOOL)result; /* 初始化请求失败 */ -(void)didXMInitReqFail:(XMErrorModel *)respModel; @optional /* accessToken过期 */ /* 若接入付费接口,必须实现此代理方法,并在此方法中刷新accessToken。此处的accessToken指的是XMLYAuthorize中登录成功后获取的授权登录token。 */ - (void)didXMAuthAccessTokenExpired; /* 付费内容播放失败/下载失败/下单失败 */ - (void)didXMOpenPayFailWithError:(XMErrorModel *)error; /* 付费内容支付成功 */ - (void)didXMPurchaseSucceed; /* 付费内容支付失败 */ - (void)didXMPurchaseFailWithError:(XMErrorModel *)error; /* 购买支付页面已显示 */ - (void)didXMPurchaseViewAppear; /* 用户退出购买支付页面 */ - (void)didXMPurchaseViewBackByUser; @end //------------------------------------------------------------------------------------ #pragma mark - XMReqMgr @interface XMReqMgr : NSObject @property (nonatomic,strong) NSString *proxyHost; //http 代理 host @property (nonatomic,assign) NSInteger proxyPort; //http 代理 port @property (nonatomic,strong) NSString *proxyUsername; //http 代理 认证用户 @property (nonatomic,strong) NSString *proxyPassword; //http 代理 认证密码 @property (nonatomic,assign) BOOL usingSynPost; @property (nonatomic,strong) NSString *appkey; @property (nonatomic,strong) NSString *appSecret; //设为YES即仍旧使用HTTP请求api.ximalaya.com(默认使用HTTPS) @property (nonatomic,assign) BOOL usingHttpWhenRequestApiDomain; + (XMReqMgr *)sharedInstance; + (NSString *)version; @property (nonatomic,weak) id<XMReqDelegate> delegate; /** * 生成或更新动态口令(如没有则生成、有则更新) * * @param appKey 必填,开放平台应用唯一Key * * @param appSecret 必填,APP的私钥,用于生产sig签名 */ - (void)registerXMReqInfoWithKey:(NSString *)appKey appSecret:(NSString *)appSecret; /** * 请求喜马拉雅的内容 * * @param reqType 必填,请求类型 * * @param params 必填,请求参数字典 * * @param reqHandler 必填,请求完成后的回调block */ - (void)requestXMData:(XMReqType)reqType params:(NSDictionary*)params withCompletionHander:(XMRequestHandler)reqHandler; - (void)postDataToXMSvr:(NSInteger)reqType params:(NSDictionary*)params withCompletionHander:(XMRequestHandler)reqHandler; /** * 请在 AppDelegate的 - (void)applicationWillTerminate:(UIApplication *)application 中调用 * */ - (void)closeXMReqMgr; //--------------------------------付费相关---------------------------------------------------- #pragma mark - 付费相关 /** * 将授权成功获得的token同步到XMReqMgr */ - (void)updateAuthTokenToXMReqMgr:(NSString *)authToken; /** * 请求付费相关接口 * * @param reqType 必填,请求类型 * @param params 必填,请求参数字典 * @param reqHandler 必填,请求完成后的回调block * * @param accessToken 必填,此处的accessToken指的是XMLYAuthorize中登录成功后获取的授权登录token */ - (void)requestXMAuthData:(XMReqType)reqType withAuthToken:(NSString *)accessToken params:(NSDictionary*)params withCompletionHander:(XMRequestHandler)reqHandler; /** * 下单接口 * * @param params 必填,请求参数字典 * @param reqHandler 必填,请求完成后的回调block * */ - (void)postOrderWithParams:(NSDictionary *)params withCompletionHander:(XMRequestHandler)reqHandler; /** * 取消订单接口 * * @param params 必填,请求参数字典 * @param reqHandler 必填,请求完成后的回调block * */ - (void)cancelOrderWithParams:(NSDictionary *)params withCompletionHandler:(XMRequestHandler)reqHandler; /** * 取消所有现存订单 */ - (void)cancelAllPendingOrders; /** * 现存订单数组 */ - (NSArray *)getAllPendingOrdersArray; /** * 删除所有现存订单,为防止某些情况下订单已失效仍存在于沙盒中,一般情况下不需使用 */ - (void)deleteAllPendingOrdersArray; @end ================================================ FILE: zhuishushenqi/Vendor/XimalayaSDK_iOS_5.5.1/include/Utility/XMSDK.h ================================================ // // XMSDK.h // XMOpenPlatform // // Created by nali on 15/8/24. // // #ifndef XMOpenPlatform_XMSDK_h #define XMOpenPlatform_XMSDK_h #import "XMReqMgr.h" #import "XMCategory.h" #import "XMAlbum.h" #import "XMTag.h" #import "XMTrack.h" #import "XMAnnouncer.h" #import "XMRadio.h" #import "XMRadioSchedule.h" #import "XMRelatedProgram.h" #import "XMHotword.h" #import "XMProvince.h" #import "XMColumnList.h" #import "XMSDKPlayer.h" #import "XMBanner.h" #import "XMCategoryHumanRecommend.h" #import "XMLiveCity.h" #endif ================================================ FILE: zhuishushenqi/Vendor/XimalayaSDK_iOS_5.5.1/include/Utility/XMSDKInfo.h ================================================ // // XMSDKInfo.h // libXMOpenPlatform // // Created by colton.cai on 15/4/30. // Copyright (c) 2015年 ximalaya. All rights reserved. // #ifndef libXMOpenPlatform_XMSDKInfo_h #define libXMOpenPlatform_XMSDKInfo_h /** * 喜马拉雅请求类型 */ typedef NS_ENUM(NSInteger, XMReqType){ //点播接口 /** 喜马拉雅内容分类 */ XMReqType_CategoriesList = 0, /** 获取专辑或声音的标签 */ XMReqType_TagsList, //根据分类和标签获取某个分类某个标签下的热门专辑列表/最新专辑列表/最多播放专辑列表 XMReqType_AlbumsList, /** 根据专辑ID获取专辑下的声音列表,即专辑浏览 */ XMReqType_AlbumsBrowse, /** 批量获取专辑列表 */ XMReqType_AlbumsBatch, /** 根据专辑ID列表获取批量专辑更新提醒信息列表 */ XMReqType_AlbumsUpdateBatch, /** 根据分类和标签获取某个分类下某个标签的热门声音列表 */ XMReqType_TracksHot, /** 批量获取声音 */ XMReqType_TracksBatch, /** 根据上一次所听声音的id,获取从那条声音开始往前一页声音。 */ XMReqType_TrackGetLastPlay, /** 获取某个分类下的元数据列表 */ XMReqType_MetadataList, /** 获取某个分类的元数据属性键值组合下包含的热门专辑列表/最新专辑列表/最多播放专辑列表 */ XMReqType_MetadataAlbums, //直播接口 /** 获取直播省份列表 */ XMReqType_LiveProvince, /** 获取直播电台列表 */ XMReqType_LiveRadio, /** 获取直播节目列表 */ XMReqType_LiveSchedule, /** 获取当前直播的节目 */ XMReqType_LiveProgram, XMReqType_LiveCity, XMReqType_LiveRadioOfCity, XMReqType_LiveRadioByID, /** 获取直播电台分类 */ XMReqType_LiveRadioCategories, /** 根据分类获取直播电台数据 */ XMReqType_LiveGetRadiosByCategory, //搜索接口 /** 搜索获取专辑列表 */ XMReqType_SearchAlbums, /** 搜索获取声音列表 */ XMReqType_SearchTracks, /** 获取最新热搜词 */ XMReqType_SearchHotWords, /** 获取某个关键词的联想词 */ XMReqType_SearchSuggestWords, /** 搜索获取电台列表 */ XMReqType_SearchRadios, /** 获取指定数量直播,声音,专辑的内容 */ XMReqType_SearchAll, /** 搜索获取主播列表 */ XMReqType_SearchAnnouncers, //推荐接口 /** 获取某个专辑的相关推荐。 */ XMReqType_AlbumsRelative, //获取某个声音列表的相关专辑 XMReqType_TracksRelativeAlbum, /** 获取下载听模块的推荐下载专辑 */ XMReqType_AlbumsRecommendDownload, //猜你喜欢 XMReqType_AlbumsGuessLike, //获取运营人员在发现页配置的分类维度专辑推荐模块的列表 XMReqType_DiscoveryRecommendAlbums, //获取运营人员为某个分类配置的标签维度专辑推荐模块列表 XMReqType_CategoryRecommendAlbums, //获取儿童版猜你喜欢专辑列表 XMReqType_StoryMachineGuessLikeAlbums, //榜单接口 /** 根据榜单类型获取榜单首页的榜单列表 */ XMReqType_RankList, /** 根据rank_key获取某个榜单下的专辑列表 */ XMReqType_RankAlbum, /** 根据rank_key获取某个榜单下的声音列表 */ XMReqType_RankTrack, /** 获取直播电台排行榜 */ XMReqType_RankRadio, //听单接口 /** 获取精品听单列表 */ XMReqType_ColumnList, /** 获取某个听单详情,每个听单包含听单简介信息和专辑或声音的列表 */ XMReqType_ColumnDetail, //焦点图接口 /** 获取榜单的焦点图列表 */ XMReqType_RankBanner, /** 获取发现页推荐的焦点图列表 */ XMReqType_DiscoveryBanner, /** 获取分类推荐的焦点图列表 */ XMReqType_CategoryBanner, //冷启动接口 /** 获取冷启动一级分类 */ XMReqType_ColdbootGenRes, /** 获取冷启动二级分类 */ XMReqType_ColdbootSubGenRes, /** 获取冷启动一级分类 */ XMReqType_ColdbootTag, /** 获取冷启动二级分类 */ XMReqType_ColdbootSubmitTag, /** 获取冷启动一级分类 */ XMReqType_ColdbootDetail, //主播接口 //获取喜马拉雅主播分类 XMReqType_AnnouncerCategory, //获取某个分类下的主播列表 XMReqType_AnnouncerList, //批量获取主播列表 XMReqType_AnnouncersBatch, //获取某个主播下的专辑列表 XMReqType_AlbumsByAnnouncer, //获取某个主播下的声音列表 XMReqType_TracksByAnnouncer, //定制化接口 /** 获取为合作方定制化接口 */ XMReqType_CustomizedCategory, /** 获取为合作方定制化的声音列表 */ XMReqType_CustomizedTrack, /** 获取合作方自定制的专辑类听单 */ XMReqType_CustomizedAlbumColumns, /** 获取合作方自定制的专辑类听单 */ XMReqType_CustomizedTrackColumns, /** 获取自定义专辑听单内容详情 */ XMReqType_CustomizedAlbumColumnDetail, /** 获取自定义声音听单内容详情 */ XMReqType_CustomizedTrackColumnDetail, /** 搜索自定义声音听单下的声音列表 */ XMReqType_CustomizedSearchTracks, /** 获取自定义声音详情 */ XMReqType_CustomizedTrackDetail, /** 搜索所有专辑或声音听单 */ XMReqType_CustomizedSearchAlbumsOrTrackColumns, //配置接口 XMReqType_appConfig, //付费接口 /** 获取所有付费专辑 */ XMReqType_AllPaidAlbums, /** 获取付费精品分类下的标签列表 */ XMReqType_Tags, /** 获取热门付费专辑列表 */ XMReqType_PaidAlbumsByTag, /** 分页获取付费专辑下的声音列表 */ XMReqType_BrowsePaidAlbumTracks, /** 批量根据付费专辑ID获取付费专辑详情 */ XMReqType_BatchGetPaidAlbums, /** 根据声音ID获取付费声音详情 */ XMReqType_BatchGetPaidTracks, /** 获取所有付费内容排行榜基础信息 */ XMReqType_PaidContentRanks, /** 根据rank_key分页获取某个付费专辑排行榜下的付费专辑列表 */ XMReqType_RankAlbums, /** 获取用户购买过的所有付费专辑 */ XMReqType_GetBoughtAlbums, /** 根据用户ID和一批付费专辑ID获取用户对这批付费专辑的购买状态 */ XMReqType_AlbumBoughtStatus, /** 根据用户ID和一批付费声音ID获取用户对这批付费声音的购买状态 */ XMReqType_TrackBoughtStatus, /** 根据关键词搜索付费专辑 */ XMReqType_SearchPaidAlbums, /** 根据关键词搜索付费声音 */ XMReqType_SearchPaidTracks, /** 实时获取专辑的价格信息 */ XMReqType_GetPriceInfo, /** 根据订单号获取支付url */ XMReqType_GetPayUrl, // 用户隐私数据API接口 /** 获取当前登陆的喜马拉雅用户的基本信息 */ XMReqType_UserInfo, /** 获取当前登陆的喜马拉雅用户的画像数据*/ XMReqType_Persona, //用户播放历史接口 /** 根据用户ID获取用户播放云历史记录 */ XMReqType_PlayHistoryGetByUid, /** 用户上传播放云历史记录 */ XMReqType_PlayHistoryUpload, /** 用户批量上传播放云历史记录 */ XMReqType_PlayHistoryBatchUpload, /** 用户批量删除播放云历史记录 */ XMReqType_PlayHistoryBatchDelete, //用户订阅接口 /** 获取喜马拉雅用户的动态更新的订阅专辑列表 */ XMReqType_SubscribeGetAlbumsByUid, /** 用户新增或取消已订阅专辑 */ XMReqType_SubscribeAddOrDelete, /** 用户批量新增已订阅专辑 */ XMReqType_SubscribeBatchAdd, }; #endif ================================================ FILE: zhuishushenqi/Vendor/XimalayaSDK_iOS_5.5.1/include/Utility/XMSingleTone.h ================================================ // // XMSingleTone.h // ting // // Created by cszhan on 14-5-28. // // // declare (.h) #define DECLARE_SINGLETON_METHOD(className, singletonName) \ + (className *)singletonName; // define (.m) #define DEFINE_SINGLETON_METHOD(className, singletonName) \ static className *singletonName = nil; \ + (className *)singletonName \ {\ @synchronized(self) {\ if (singletonName == nil) {\ singletonName = [[self alloc] init];\ }\ }\ return singletonName;\ }\ \ + (id)allocWithZone:(NSZone *)zone \ {\ @synchronized(self) {\ if (singletonName == nil) { \ singletonName = [super allocWithZone:zone];\ return singletonName;\ }\ }\ return nil;\ }\ \ + (id)copyWithZone:(NSZone *)zone \ {\ return self;\ }\ ================================================ FILE: zhuishushenqi/Vendor/XimalayaSDK_iOS_5.5.1/libXMOpenPlatform.a ================================================ [File too large to display: 45.3 MB] ================================================ FILE: zhuishushenqi/Vendor/YTK/YTKKeyValueStore.h ================================================ // // YTKKeyValueStore.h // Ape // // Created by TangQiao on 12-11-6. // Copyright (c) 2012年 TangQiao. All rights reserved. // #import <Foundation/Foundation.h> @interface YTKKeyValueItem : NSObject @property (strong, nonatomic) NSString *itemId; @property (strong, nonatomic) id itemObject; @property (strong, nonatomic) NSDate *createdTime; @end @interface YTKKeyValueStore : NSObject - (id)initDBWithName:(NSString *)dbName; - (id)initWithDBWithPath:(NSString *)dbPath; - (void)createTableWithName:(NSString *)tableName; - (BOOL)isTableExists:(NSString *)tableName; - (void)clearTable:(NSString *)tableName; - (void)dropTable:(NSString *)tableName; - (void)close; ///************************ Put&Get methods ***************************************** - (void)putObject:(id)object withId:(NSString *)objectId intoTable:(NSString *)tableName; - (id)getObjectById:(NSString *)objectId fromTable:(NSString *)tableName; - (YTKKeyValueItem *)getYTKKeyValueItemById:(NSString *)objectId fromTable:(NSString *)tableName; - (void)putString:(NSString *)string withId:(NSString *)stringId intoTable:(NSString *)tableName; - (NSString *)getStringById:(NSString *)stringId fromTable:(NSString *)tableName; - (void)putNumber:(NSNumber *)number withId:(NSString *)numberId intoTable:(NSString *)tableName; - (NSNumber *)getNumberById:(NSString *)numberId fromTable:(NSString *)tableName; - (NSArray *)getAllItemsFromTable:(NSString *)tableName; - (NSUInteger)getCountFromTable:(NSString *)tableName; - (void)deleteObjectById:(NSString *)objectId fromTable:(NSString *)tableName; - (void)deleteObjectsByIdArray:(NSArray *)objectIdArray fromTable:(NSString *)tableName; - (void)deleteObjectsByIdPrefix:(NSString *)objectIdPrefix fromTable:(NSString *)tableName; @end ================================================ FILE: zhuishushenqi/Vendor/YTK/YTKKeyValueStore.m ================================================ // // YTKKeyValueStore.m // Ape // // Created by TangQiao on 12-11-6. // Copyright (c) 2012年 TangQiao. All rights reserved. // #import "YTKKeyValueStore.h" #import "FMDatabase.h" #import "FMDatabaseAdditions.h" #import "FMDatabaseQueue.h" #ifdef DEBUG #define debugLog(...) NSLog(__VA_ARGS__) #define debugMethod() NSLog(@"%s", __func__) #define debugError() NSLog(@"Error at %s Line:%d", __func__, __LINE__) #else #define debugLog(...) #define debugMethod() #define debugError() #endif #define PATH_OF_DOCUMENT [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0] @implementation YTKKeyValueItem - (NSString *)description { return [NSString stringWithFormat:@"id=%@, value=%@, timeStamp=%@", _itemId, _itemObject, _createdTime]; } @end @interface YTKKeyValueStore() @property (strong, nonatomic) FMDatabaseQueue * dbQueue; @end @implementation YTKKeyValueStore static NSString *const DEFAULT_DB_NAME = @"database.sqlite"; static NSString *const CREATE_TABLE_SQL = @"CREATE TABLE IF NOT EXISTS %@ ( \ id TEXT NOT NULL, \ json TEXT NOT NULL, \ createdTime TEXT NOT NULL, \ PRIMARY KEY(id)) \ "; static NSString *const UPDATE_ITEM_SQL = @"REPLACE INTO %@ (id, json, createdTime) values (?, ?, ?)"; static NSString *const QUERY_ITEM_SQL = @"SELECT json, createdTime from %@ where id = ? Limit 1"; static NSString *const SELECT_ALL_SQL = @"SELECT * from %@"; static NSString *const COUNT_ALL_SQL = @"SELECT count(*) as num from %@"; static NSString *const CLEAR_ALL_SQL = @"DELETE from %@"; static NSString *const DELETE_ITEM_SQL = @"DELETE from %@ where id = ?"; static NSString *const DELETE_ITEMS_SQL = @"DELETE from %@ where id in ( %@ )"; static NSString *const DELETE_ITEMS_WITH_PREFIX_SQL = @"DELETE from %@ where id like ? "; static NSString *const DROP_TABLE_SQL = @" DROP TABLE '%@' "; + (BOOL)checkTableName:(NSString *)tableName { if (tableName == nil || tableName.length == 0 || [tableName rangeOfString:@" "].location != NSNotFound) { debugLog(@"ERROR, table name: %@ format error.", tableName); return NO; } return YES; } - (id)init { return [self initDBWithName:DEFAULT_DB_NAME]; } - (id)initDBWithName:(NSString *)dbName { self = [super init]; if (self) { NSString * dbPath = [PATH_OF_DOCUMENT stringByAppendingPathComponent:dbName]; debugLog(@"dbPath = %@", dbPath); if (_dbQueue) { [self close]; } _dbQueue = [FMDatabaseQueue databaseQueueWithPath:dbPath]; } return self; } - (id)initWithDBWithPath:(NSString *)dbPath { self = [super init]; if (self) { debugLog(@"dbPath = %@", dbPath); if (_dbQueue) { [self close]; } _dbQueue = [FMDatabaseQueue databaseQueueWithPath:dbPath]; } return self; } - (void)createTableWithName:(NSString *)tableName { if ([YTKKeyValueStore checkTableName:tableName] == NO) { return; } NSString * sql = [NSString stringWithFormat:CREATE_TABLE_SQL, tableName]; __block BOOL result; [_dbQueue inDatabase:^(FMDatabase *db) { result = [db executeUpdate:sql]; }]; if (!result) { debugLog(@"ERROR, failed to create table: %@", tableName); } } - (BOOL)isTableExists:(NSString *)tableName{ if ([YTKKeyValueStore checkTableName:tableName] == NO) { return NO; } __block BOOL result; [_dbQueue inDatabase:^(FMDatabase *db) { result = [db tableExists:tableName]; }]; if (!result) { debugLog(@"ERROR, table: %@ not exists in current DB", tableName); } return result; } - (void)clearTable:(NSString *)tableName { if ([YTKKeyValueStore checkTableName:tableName] == NO) { return; } NSString * sql = [NSString stringWithFormat:CLEAR_ALL_SQL, tableName]; __block BOOL result; [_dbQueue inDatabase:^(FMDatabase *db) { result = [db executeUpdate:sql]; }]; if (!result) { debugLog(@"ERROR, failed to clear table: %@", tableName); } } - (void)dropTable:(NSString *)tableName { if ([YTKKeyValueStore checkTableName:tableName] == NO) { return; } NSString * sql = [NSString stringWithFormat:DROP_TABLE_SQL, tableName]; __block BOOL result; [_dbQueue inDatabase:^(FMDatabase *db) { result = [db executeUpdate:sql]; }]; if (!result) { debugLog(@"ERROR, failed to drop table: %@", tableName); } } - (void)putObject:(id)object withId:(NSString *)objectId intoTable:(NSString *)tableName { if ([YTKKeyValueStore checkTableName:tableName] == NO) { return; } NSError * error; NSData * data = [NSJSONSerialization dataWithJSONObject:object options:0 error:&error]; if (error) { debugLog(@"ERROR, faild to get json data"); return; } NSString * jsonString = [[NSString alloc] initWithData:data encoding:(NSUTF8StringEncoding)]; NSDate * createdTime = [NSDate date]; NSString * sql = [NSString stringWithFormat:UPDATE_ITEM_SQL, tableName]; __block BOOL result; [_dbQueue inDatabase:^(FMDatabase *db) { result = [db executeUpdate:sql, objectId, jsonString, createdTime]; }]; if (!result) { debugLog(@"ERROR, failed to insert/replace into table: %@", tableName); } } - (id)getObjectById:(NSString *)objectId fromTable:(NSString *)tableName { YTKKeyValueItem * item = [self getYTKKeyValueItemById:objectId fromTable:tableName]; if (item) { return item.itemObject; } else { return nil; } } - (YTKKeyValueItem *)getYTKKeyValueItemById:(NSString *)objectId fromTable:(NSString *)tableName { if ([YTKKeyValueStore checkTableName:tableName] == NO) { return nil; } NSString * sql = [NSString stringWithFormat:QUERY_ITEM_SQL, tableName]; __block NSString * json = nil; __block NSDate * createdTime = nil; [_dbQueue inDatabase:^(FMDatabase *db) { FMResultSet * rs = [db executeQuery:sql, objectId]; if ([rs next]) { json = [rs stringForColumn:@"json"]; createdTime = [rs dateForColumn:@"createdTime"]; } [rs close]; }]; if (json) { NSError * error; id result = [NSJSONSerialization JSONObjectWithData:[json dataUsingEncoding:NSUTF8StringEncoding] options:(NSJSONReadingAllowFragments) error:&error]; if (error) { debugLog(@"ERROR, faild to prase to json"); return nil; } YTKKeyValueItem * item = [[YTKKeyValueItem alloc] init]; item.itemId = objectId; item.itemObject = result; item.createdTime = createdTime; return item; } else { return nil; } } - (void)putString:(NSString *)string withId:(NSString *)stringId intoTable:(NSString *)tableName { if (string == nil) { debugLog(@"error, string is nil"); return; } [self putObject:@[string] withId:stringId intoTable:tableName]; } - (NSString *)getStringById:(NSString *)stringId fromTable:(NSString *)tableName { NSArray * array = [self getObjectById:stringId fromTable:tableName]; if (array && [array isKindOfClass:[NSArray class]]) { return array[0]; } return nil; } - (void)putNumber:(NSNumber *)number withId:(NSString *)numberId intoTable:(NSString *)tableName { if (number == nil) { debugLog(@"error, number is nil"); return; } [self putObject:@[number] withId:numberId intoTable:tableName]; } - (NSNumber *)getNumberById:(NSString *)numberId fromTable:(NSString *)tableName { NSArray * array = [self getObjectById:numberId fromTable:tableName]; if (array && [array isKindOfClass:[NSArray class]]) { return array[0]; } return nil; } - (NSArray *)getAllItemsFromTable:(NSString *)tableName { if ([YTKKeyValueStore checkTableName:tableName] == NO) { return nil; } NSString * sql = [NSString stringWithFormat:SELECT_ALL_SQL, tableName]; __block NSMutableArray * result = [NSMutableArray array]; [_dbQueue inDatabase:^(FMDatabase *db) { FMResultSet * rs = [db executeQuery:sql]; while ([rs next]) { YTKKeyValueItem * item = [[YTKKeyValueItem alloc] init]; item.itemId = [rs stringForColumn:@"id"]; item.itemObject = [rs stringForColumn:@"json"]; item.createdTime = [rs dateForColumn:@"createdTime"]; [result addObject:item]; } [rs close]; }]; // parse json string to object NSError * error; for (YTKKeyValueItem * item in result) { error = nil; id object = [NSJSONSerialization JSONObjectWithData:[item.itemObject dataUsingEncoding:NSUTF8StringEncoding] options:(NSJSONReadingAllowFragments) error:&error]; if (error) { debugLog(@"ERROR, faild to prase to json."); } else { item.itemObject = object; } } return result; } - (NSUInteger)getCountFromTable:(NSString *)tableName { if ([YTKKeyValueStore checkTableName:tableName] == NO) { return 0; } NSString * sql = [NSString stringWithFormat:COUNT_ALL_SQL, tableName]; __block NSInteger num = 0; [_dbQueue inDatabase:^(FMDatabase *db) { FMResultSet * rs = [db executeQuery:sql]; if ([rs next]) { num = [rs unsignedLongLongIntForColumn:@"num"]; } [rs close]; }]; return num; } - (void)deleteObjectById:(NSString *)objectId fromTable:(NSString *)tableName { if ([YTKKeyValueStore checkTableName:tableName] == NO) { return; } NSString * sql = [NSString stringWithFormat:DELETE_ITEM_SQL, tableName]; __block BOOL result; [_dbQueue inDatabase:^(FMDatabase *db) { result = [db executeUpdate:sql, objectId]; }]; if (!result) { debugLog(@"ERROR, failed to delete item from table: %@", tableName); } } - (void)deleteObjectsByIdArray:(NSArray *)objectIdArray fromTable:(NSString *)tableName { if ([YTKKeyValueStore checkTableName:tableName] == NO) { return; } NSMutableString *stringBuilder = [NSMutableString string]; for (id objectId in objectIdArray) { NSString *item = [NSString stringWithFormat:@" '%@' ", objectId]; if (stringBuilder.length == 0) { [stringBuilder appendString:item]; } else { [stringBuilder appendString:@","]; [stringBuilder appendString:item]; } } NSString *sql = [NSString stringWithFormat:DELETE_ITEMS_SQL, tableName, stringBuilder]; __block BOOL result; [_dbQueue inDatabase:^(FMDatabase *db) { result = [db executeUpdate:sql]; }]; if (!result) { debugLog(@"ERROR, failed to delete items by ids from table: %@", tableName); } } - (void)deleteObjectsByIdPrefix:(NSString *)objectIdPrefix fromTable:(NSString *)tableName { if ([YTKKeyValueStore checkTableName:tableName] == NO) { return; } NSString *sql = [NSString stringWithFormat:DELETE_ITEMS_WITH_PREFIX_SQL, tableName]; NSString *prefixArgument = [NSString stringWithFormat:@"%@%%", objectIdPrefix]; __block BOOL result; [_dbQueue inDatabase:^(FMDatabase *db) { result = [db executeUpdate:sql, prefixArgument]; }]; if (!result) { debugLog(@"ERROR, failed to delete items by id prefix from table: %@", tableName); } } - (void)close { [_dbQueue close]; _dbQueue = nil; } @end ================================================ FILE: zhuishushenqi/Vendor/uchardet/uchardet.h ================================================ /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (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.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Mozilla Universal charset detector code. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 2001 * the Initial Developer. All Rights Reserved. * * Contributor(s): * BYVoid <byvoid.kcp@gmail.com> * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #ifndef UCHARDET_H___ #define UCHARDET_H___ #ifdef __cplusplus extern "C" { #endif #include <stddef.h> typedef struct uchardet * uchardet_t; /** * Create an encoding detector. * @return a handle of a instance of uchardet */ uchardet_t uchardet_new(void); /** * Delete an encoding detector. * @param ud [in] handle of a instance of uchardet */ void uchardet_delete(uchardet_t ud); /** * Feed data to an encoding detector. * @param ud [in] handle of a instance of uchardet * @param data [in] data * @param len [in] number of byte of data * @return non-zero number on failure. */ int uchardet_handle_data(uchardet_t ud, const char * data, size_t len); /** * Notify an end of data to an encoding detctor. * @param ud [in] handle of a instance of uchardet */ void uchardet_data_end(uchardet_t ud); /** * Reset an encoding detector. * @param ud [in] handle of a instance of uchardet */ void uchardet_reset(uchardet_t ud); /** * Get an iconv-compatible name of the encoding that was detected. * @param ud [in] handle of a instance of uchardet * @return name of charset on success and "" on failure. */ const char * uchardet_get_charset(uchardet_t ud); #ifdef __cplusplus } #endif #endif ================================================ FILE: zhuishushenqi/ViewController.swift ================================================ // // ViewController.swift // zhuishushenqi // // Created by Nory Cao on 16/9/16. // Copyright © 2016年 QS. All rights reserved. // import UIKit class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } } ================================================ FILE: zhuishushenqi/zhuishushenqi-Bridge-Header.h ================================================ // // zhuishushenqi-Bridge-Header.h // zhuishushenqi // // Created by Nory Cao on 16/9/16. // Copyright © 2016年 QS. All rights reserved. // #ifndef zhuishushenqi_Bridge_Header_h #define zhuishushenqi_Bridge_Header_h //#import "XYCBaseModel.h" //#import <CommonCrypto/CommonDigest.h> #import "NSDate+Extension.h" #import "UIFont+ZSExtension.h" #import "M80AttributedLabel.h" #import "NSString+Encode.h" #import "YTKKeyValueStore.h" #import "UIScrollView+StateView.h" #import "CTDisplayText.h" //#import <MJRefresh/MJRefresh.h> #import "HTTPServer.h" #import "ZSHTTPConnection.h" #import "ZSHTTPTool.h" #import "MonitorFileChangeHelp.h" #import "AKPickerView.h" #import "IFlyMSC/IFlyMSC.h" #import "PcmPlayer.h" #import <TencentOpenAPI/TencentOAuth.h> #import <TencentOpenAPI/QQApiInterface.h> #import "WXApi.h" #import "WechatAuthSDK.h" #import "WeiboSDK.h" #import "FBEncryptorAESUtils.h" #import "ZSDBManager.h" #import "XMReqMgr.h" #import "XMSDKPlayer.h" #import "AikanParserModel.h" #import "AikanHtmlParser.h" #import "OCGumbo.h" #import "OCGumbo+Query.h" #import "uchardet.h" #import "ZSAddSourceTextField.h" #endif /* zhuishushenqi_Bridge_Header_h */ ================================================ FILE: zhuishushenqi.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 46; objects = { /* Begin PBXBuildFile section */ 3D05BCB423C592E2001EAB2A /* ZSReaderToolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D05BCB323C592E2001EAB2A /* ZSReaderToolbar.swift */; }; 3D05BCB623C592FE001EAB2A /* ZSReaderTopbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D05BCB523C592FE001EAB2A /* ZSReaderTopbar.swift */; }; 3D05BCB823C59314001EAB2A /* ZSReaderBottomBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D05BCB723C59314001EAB2A /* ZSReaderBottomBar.swift */; }; 3D05BCBA23C5ACE8001EAB2A /* ZSSearchInfoBottomView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D05BCB923C5ACE8001EAB2A /* ZSSearchInfoBottomView.swift */; }; 3D05BCBE23C5D424001EAB2A /* AikanParserModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D05BCBD23C5D424001EAB2A /* AikanParserModel.swift */; }; 3D05BCC023C5EAFE001EAB2A /* ZSShelfManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D05BCBF23C5EAFE001EAB2A /* ZSShelfManager.swift */; }; 3D08F242212C2DC6007B3D19 /* ZSDiscussViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D08F241212C2DC6007B3D19 /* ZSDiscussViewModel.swift */; }; 3D08F244212C2DFC007B3D19 /* ZSDiscussWebService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D08F243212C2DFC007B3D19 /* ZSDiscussWebService.swift */; }; 3D096BF722CF76A70005C9EA /* ZSRefreshFooter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D096BF622CF76A70005C9EA /* ZSRefreshFooter.swift */; }; 3D0D709D23C88B30006902E8 /* HtmlParserModelData_edit.dat in Resources */ = {isa = PBXBuildFile; fileRef = 3D0D709C23C88B30006902E8 /* HtmlParserModelData_edit.dat */; }; 3D1330CB23C6FA9200D81EF4 /* ZSReaderTouchArea.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D1330CA23C6FA9200D81EF4 /* ZSReaderTouchArea.swift */; }; 3D1330CD23C7239000D81EF4 /* ZSReaderCatalogViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D1330CC23C7239000D81EF4 /* ZSReaderCatalogViewController.swift */; }; 3D17A02722D47364001FAC0C /* ZSReaderController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D17A02622D47364001FAC0C /* ZSReaderController.swift */; }; 3D17A02922D47426001FAC0C /* ZSPageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D17A02822D47426001FAC0C /* ZSPageViewController.swift */; }; 3D17A02B22D474A7001FAC0C /* ZSHorizonalViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D17A02A22D474A7001FAC0C /* ZSHorizonalViewController.swift */; }; 3D1E315523D1B30700A2359E /* ZSToast.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D1E315423D1B30700A2359E /* ZSToast.swift */; }; 3D1F67A1217DBA1B000FB968 /* ZSLoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D1F67A0217DBA1B000FB968 /* ZSLoginViewController.swift */; }; 3D1F67A3217DBA94000FB968 /* ZSLoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D1F67A2217DBA94000FB968 /* ZSLoginView.swift */; }; 3D1F67A5217DCE9B000FB968 /* ZSLeftViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D1F67A4217DCE9B000FB968 /* ZSLeftViewCell.swift */; }; 3D1FFDEE23C89A4E0017ECE7 /* ZSShelfTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D1FFDED23C89A4E0017ECE7 /* ZSShelfTableViewCell.swift */; }; 3D1FFDF023C9A68F0017ECE7 /* ZSReaderBottomBigBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D1FFDEF23C9A68F0017ECE7 /* ZSReaderBottomBigBar.swift */; }; 3D1FFDF223C9A9600017ECE7 /* ZSReaderThemeSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D1FFDF123C9A9600017ECE7 /* ZSReaderThemeSelectionView.swift */; }; 3D1FFDF423CC52D80017ECE7 /* ThemeManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D1FFDF323CC52D70017ECE7 /* ThemeManager.swift */; }; 3D1FFDF623CC52E10017ECE7 /* Color.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D1FFDF523CC52E10017ECE7 /* Color.swift */; }; 3D1FFDF823CC586B0017ECE7 /* ReaderNavigationBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D1FFDF723CC586B0017ECE7 /* ReaderNavigationBar.swift */; }; 3D1FFDFA23CC5B880017ECE7 /* ReaderBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D1FFDF923CC5B880017ECE7 /* ReaderBar.swift */; }; 3D1FFEF0249B523300832576 /* ZSSyncStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D1FFEEF249B523300832576 /* ZSSyncStorage.swift */; }; 3D1FFEF2249B562500832576 /* ZSShelfStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D1FFEF1249B562500832576 /* ZSShelfStorage.swift */; }; 3D2006D1216F36BE00C326B4 /* ZSReaderBaseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D2006D0216F36BE00C326B4 /* ZSReaderBaseViewController.swift */; }; 3D2006D3216F443A00C326B4 /* ZSSpeakerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D2006D2216F443A00C326B4 /* ZSSpeakerViewController.swift */; }; 3D2006D5216F478C00C326B4 /* ZSSpeakerCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D2006D4216F478C00C326B4 /* ZSSpeakerCell.swift */; }; 3D2330DE21F5F04700EFE522 /* ZSBookDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D2330DD21F5F04700EFE522 /* ZSBookDownloader.swift */; }; 3D2330E021F6F05A00EFE522 /* ZSAppInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D2330DF21F6F05A00EFE522 /* ZSAppInfo.swift */; }; 3D2330E321F6F39D00EFE522 /* FloatExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D2330E221F6F39D00EFE522 /* FloatExtensions.swift */; }; 3D2330E521F9CC6F00EFE522 /* IntExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D2330E421F9CC6F00EFE522 /* IntExtensions.swift */; }; 3D252CE0219175FD0051B60D /* ZSDatabase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D252CDF219175FC0051B60D /* ZSDatabase.swift */; }; 3D26002724A1B61000458B58 /* libuchardet-ios.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 3D26002524A1B61000458B58 /* libuchardet-ios.a */; }; 3D28402822841281009463A3 /* ZSDetailSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D28402722841281009463A3 /* ZSDetailSection.swift */; }; 3D28402A2284267C009463A3 /* ZSDetailInfoCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D2840292284267C009463A3 /* ZSDetailInfoCell.swift */; }; 3D29C7AB233381A600113A25 /* ZSReaderVCProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D29C7AA233381A600113A25 /* ZSReaderVCProtocol.swift */; }; 3D29C7AD23347E6900113A25 /* ZSReaderBaseViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D29C7AC23347E6900113A25 /* ZSReaderBaseViewModel.swift */; }; 3D2B4D0320EB616D008E3E81 /* ZSReaderViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D2B4D0220EB616D008E3E81 /* ZSReaderViewController.swift */; }; 3D2B4D0520EB6552008E3E81 /* ZSNoneAnimationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D2B4D0420EB6552008E3E81 /* ZSNoneAnimationViewController.swift */; }; 3D2B4D0720EB68AC008E3E81 /* ZSReaderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D2B4D0620EB68AC008E3E81 /* ZSReaderProtocol.swift */; }; 3D31FFAF2216BDAD0011D275 /* ZSReaderViewModel+Bought.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D31FFAE2216BDAD0011D275 /* ZSReaderViewModel+Bought.swift */; }; 3D31FFB12216E1850011D275 /* ZSChapterPayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D31FFB02216E1850011D275 /* ZSChapterPayView.swift */; }; 3D33B4D322BB6C40000C2D8D /* DispatchTime+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D33B4D222BB6C40000C2D8D /* DispatchTime+Extension.swift */; }; 3D3AB5D2214E98AC00E9C246 /* hylst.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 3D3AB5D1214E98AC00E9C246 /* hylst.ttf */; }; 3D3AB5D4214E98B800E9C246 /* Redocn.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 3D3AB5D3214E98B800E9C246 /* Redocn.ttf */; }; 3D3AB5D6214E993E00E9C246 /* hkppt.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 3D3AB5D5214E993E00E9C246 /* hkppt.ttf */; }; 3D3AB5DA214E99CC00E9C246 /* ypt.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 3D3AB5D9214E99CB00E9C246 /* ypt.ttf */; }; 3D3AB5DC214E99FE00E9C246 /* fz-wbt.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 3D3AB5DB214E99FE00E9C246 /* fz-wbt.ttf */; }; 3D3AB5DE214E9A8300E9C246 /* fz-kt.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 3D3AB5DD214E9A8300E9C246 /* fz-kt.ttf */; }; 3D3AB5E0214E9ABD00E9C246 /* ltt.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 3D3AB5DF214E9ABD00E9C246 /* ltt.ttf */; }; 3D3BA0002160C5D700B0C80E /* speaker.json in Resources */ = {isa = PBXBuildFile; fileRef = 3D3B9FFF2160C5D700B0C80E /* speaker.json */; }; 3D3BA0042160C77E00B0C80E /* speakers.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3D3BA0032160C77E00B0C80E /* speakers.plist */; }; 3D448E5822C501C40099B6E8 /* ZSBookShelfViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D448E5622C501C40099B6E8 /* ZSBookShelfViewController.swift */; }; 3D448E5922C501C40099B6E8 /* NavigationBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D448E5722C501C40099B6E8 /* NavigationBar.swift */; }; 3D448E5B22C501CC0099B6E8 /* ZSBookStoreViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D448E5A22C501CC0099B6E8 /* ZSBookStoreViewController.swift */; }; 3D448E5D22C501DA0099B6E8 /* ZSCommunityViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D448E5C22C501DA0099B6E8 /* ZSCommunityViewController.swift */; }; 3D448E5F22C501E10099B6E8 /* ZSDiscoverViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D448E5E22C501E10099B6E8 /* ZSDiscoverViewController.swift */; }; 3D448E6122C501E90099B6E8 /* ZSMineViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D448E6022C501E90099B6E8 /* ZSMineViewController.swift */; }; 3D5280E020C8D2BB00252D67 /* ZSForumViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D5280DF20C8D2BB00252D67 /* ZSForumViewController.swift */; }; 3D55F4D922D47B3800AE0E2B /* ZSVerticalViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D55F4D822D47B3800AE0E2B /* ZSVerticalViewController.swift */; }; 3D55F4DB22D47CAD00AE0E2B /* ZSReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D55F4DA22D47CAD00AE0E2B /* ZSReader.swift */; }; 3D55F4DD22D47CEF00AE0E2B /* ZSNormalViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D55F4DC22D47CEF00AE0E2B /* ZSNormalViewController.swift */; }; 3D5671FC2174CADD0049B2FF /* ZSBookCTLayoutModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D5671FB2174CADD0049B2FF /* ZSBookCTLayoutModel.swift */; }; 3D58635F20CE1C96002AD3CC /* ZSProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D58635E20CE1C96002AD3CC /* ZSProtocol.swift */; }; 3D62EE5922604768002FFA39 /* ZSVoicePlayerCatelogView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D62EE5822604768002FFA39 /* ZSVoicePlayerCatelogView.swift */; }; 3D63E16B235EF9550015B7D3 /* ZSSearchBookViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D63E16A235EF9550015B7D3 /* ZSSearchBookViewController.swift */; }; 3D63E16D235EF9B80015B7D3 /* ZSSearchBookView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D63E16C235EF9B80015B7D3 /* ZSSearchBookView.swift */; }; 3D63E16F235EF9E60015B7D3 /* ZSSearchResultView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D63E16E235EF9E60015B7D3 /* ZSSearchResultView.swift */; }; 3D63E171235EFAE70015B7D3 /* ZSTopSearchBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D63E170235EFAE70015B7D3 /* ZSTopSearchBar.swift */; }; 3D63E173235F021F0015B7D3 /* ZSHeaderSearchCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D63E172235F021F0015B7D3 /* ZSHeaderSearchCell.swift */; }; 3D63E175235F02670015B7D3 /* ZSSearchHotView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D63E174235F02670015B7D3 /* ZSSearchHotView.swift */; }; 3D63E177235F03C40015B7D3 /* ZSSearchBookViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D63E176235F03C40015B7D3 /* ZSSearchBookViewModel.swift */; }; 3D63E179235F14BF0015B7D3 /* ZSSearchHotwords.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D63E178235F14BF0015B7D3 /* ZSSearchHotwords.swift */; }; 3D63E17B235F18F40015B7D3 /* ZSHotWord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D63E17A235F18F40015B7D3 /* ZSHotWord.swift */; }; 3D63E18F23603CD80015B7D3 /* ZSSearchRecommendView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D63E18E23603CD80015B7D3 /* ZSSearchRecommendView.swift */; }; 3D63E191236043250015B7D3 /* ZSHeaderSearch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D63E190236043250015B7D3 /* ZSHeaderSearch.swift */; }; 3D64030623BF5E8500D4B9EB /* ZSSearchHistory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D64030523BF5E8500D4B9EB /* ZSSearchHistory.swift */; }; 3D65504B219577DC0064BA0C /* ZSMultiplePayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D65504A219577DB0064BA0C /* ZSMultiplePayView.swift */; }; 3D65504D21957B410064BA0C /* ZSChapterSelectView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D65504C21957B410064BA0C /* ZSChapterSelectView.swift */; }; 3D6CCABC23794D2C004D46CE /* ZSSetting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D6CCABB23794D2C004D46CE /* ZSSetting.swift */; }; 3D6CCABE237952B3004D46CE /* ZSAddSourceViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D6CCABD237952B3004D46CE /* ZSAddSourceViewController.swift */; }; 3D6CCAC02379550C004D46CE /* ZSSourcesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D6CCABF2379550C004D46CE /* ZSSourcesViewController.swift */; }; 3D6CCAC2237956FB004D46CE /* ZSSourceManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D6CCAC1237956FB004D46CE /* ZSSourceManager.swift */; }; 3D6CCAC423796155004D46CE /* ZSSourceCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D6CCAC323796155004D46CE /* ZSSourceCell.swift */; }; 3D82F9EB22C9DD5D00F035CB /* ZSMineNavigationBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D82F9EA22C9DD5D00F035CB /* ZSMineNavigationBar.swift */; }; 3D82F9ED22C9EFA800F035CB /* ZSDetailButtonCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D82F9EC22C9EFA800F035CB /* ZSDetailButtonCell.swift */; }; 3D82F9EF22CA01A100F035CB /* ZSMineMenuItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D82F9EE22CA01A100F035CB /* ZSMineMenuItem.swift */; }; 3D850DFE21E61C9800F23DDB /* ZSVoucher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D850DFD21E61C9800F23DDB /* ZSVoucher.swift */; }; 3D850E0021E61E1800F23DDB /* ZSVoucherViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D850DFF21E61E1800F23DDB /* ZSVoucherViewController.swift */; }; 3D865B55219B1975001294EB /* ZSChapterSelectModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D865B54219B1975001294EB /* ZSChapterSelectModel.swift */; }; 3D88A7872670EBCB00C87AB5 /* ZSFloatingManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D88A7822670EBCB00C87AB5 /* ZSFloatingManager.swift */; }; 3D88A7882670EBCB00C87AB5 /* ZSMemoryFloatingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D88A7832670EBCB00C87AB5 /* ZSMemoryFloatingView.swift */; }; 3D88A7892670EBCB00C87AB5 /* ZSFloatingWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D88A7842670EBCB00C87AB5 /* ZSFloatingWindow.swift */; }; 3D88A78A2670EBCB00C87AB5 /* ZSFloatingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D88A7852670EBCB00C87AB5 /* ZSFloatingViewController.swift */; }; 3D88A78B2670EBCB00C87AB5 /* ZSFloatingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D88A7862670EBCB00C87AB5 /* ZSFloatingView.swift */; }; 3D9325E120B2BCFB0049CDBF /* ZSRootViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D9325E020B2BCFB0049CDBF /* ZSRootViewController.swift */; }; 3D9325E620B2C44A0049CDBF /* ZSBaseTableViewManger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D9325E520B2C44A0049CDBF /* ZSBaseTableViewManger.swift */; }; 3D9325E820B2C66A0049CDBF /* UITableViewCell+ZSExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D9325E720B2C66A0049CDBF /* UITableViewCell+ZSExtension.swift */; }; 3D9325EC20B2C9120049CDBF /* ZSHomeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D9325EB20B2C9120049CDBF /* ZSHomeViewModel.swift */; }; 3D9325EE20B2E07F0049CDBF /* Value.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D9325ED20B2E07F0049CDBF /* Value.swift */; }; 3D968CF62133917900DF3279 /* ZSCellAdapterProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D968CF52133917900DF3279 /* ZSCellAdapterProtocol.swift */; }; 3D968CF8213392F500DF3279 /* ZSBaseCellAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D968CF7213392F500DF3279 /* ZSBaseCellAdapter.swift */; }; 3D968CFA213396B700DF3279 /* ZSBaseSectionAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D968CF9213396B700DF3279 /* ZSBaseSectionAdapter.swift */; }; 3D968CFC2135391400DF3279 /* ZSBookCTViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D968CFB2135391400DF3279 /* ZSBookCTViewModel.swift */; }; 3D968CFE2135398600DF3279 /* ZSBookCTService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D968CFD2135398600DF3279 /* ZSBookCTService.swift */; }; 3D998937223FDFB900EA33D5 /* ZSSegmentBaseViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D998936223FDFB900EA33D5 /* ZSSegmentBaseViewModel.swift */; }; 3D998939223FEA0400EA33D5 /* ZSCatelogViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D998938223FEA0400EA33D5 /* ZSCatelogViewModel.swift */; }; 3D99893B223FEC5900EA33D5 /* ZSCatelogModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D99893A223FEC5900EA33D5 /* ZSCatelogModel.swift */; }; 3D9D857C20CA439D003FEDFE /* ZSShelfMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D9D857B20CA439D003FEDFE /* ZSShelfMessage.swift */; }; 3DABFC2B22B9183D00ECB5E3 /* ZSTabBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DABFC2A22B9183D00ECB5E3 /* ZSTabBarController.swift */; }; 3DAC238E21E1D66A00E77891 /* ZSWebStoreViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DAC238D21E1D66A00E77891 /* ZSWebStoreViewController.swift */; }; 3DAC239021E1D73300E77891 /* ZSWebViewControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DAC238F21E1D73300E77891 /* ZSWebViewControllerDelegate.swift */; }; 3DAC239221E1DE7400E77891 /* ZSWebItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DAC239121E1DE7400E77891 /* ZSWebItem.swift */; }; 3DAC23CE21E32DA500E77891 /* ZSYJSchemeHandle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DAC23CD21E32DA500E77891 /* ZSYJSchemeHandle.swift */; }; 3DB1FDB12334A8BE00CAC8C0 /* ZSReaderCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DB1FDB02334A8BE00CAC8C0 /* ZSReaderCache.swift */; }; 3DB24B5D2240F5F100DCE8B2 /* ZSCatelogCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DB24B5C2240F5F100DCE8B2 /* ZSCatelogCell.swift */; }; 3DB24B622243CBD200DCE8B2 /* ZSCatelogHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DB24B612243CBD200DCE8B2 /* ZSCatelogHeaderView.swift */; }; 3DBC088F22CB02030004B3F4 /* ZSCommunityNavigationBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DBC088E22CB02030004B3F4 /* ZSCommunityNavigationBar.swift */; }; 3DBC089122CB37C10004B3F4 /* ZSDiscoverNavigationBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DBC089022CB37C00004B3F4 /* ZSDiscoverNavigationBar.swift */; }; 3DBC089322CB54DD0004B3F4 /* ZSCommunityCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DBC089222CB54DD0004B3F4 /* ZSCommunityCell.swift */; }; 3DBC089522CB56C60004B3F4 /* ZSInsertedBookScoreView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DBC089422CB56C60004B3F4 /* ZSInsertedBookScoreView.swift */; }; 3DBC089722CB78DB0004B3F4 /* ZSCommunityViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DBC089622CB78DB0004B3F4 /* ZSCommunityViewModel.swift */; }; 3DBC089F22CB80DB0004B3F4 /* UIButton+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DBC089E22CB80DB0004B3F4 /* UIButton+Extension.swift */; }; 3DC93D55224A6264004FC392 /* ZSVoiceAlbum.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DC93D54224A6264004FC392 /* ZSVoiceAlbum.swift */; }; 3DD08407215698E5008E3B4A /* iflyMSC.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3DD08406215698E5008E3B4A /* iflyMSC.framework */; }; 3DD0840B21569902008E3B4A /* Speaker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DD0840A21569902008E3B4A /* Speaker.swift */; }; 3DD0841E21569A54008E3B4A /* TTSConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DD0841D21569A54008E3B4A /* TTSConfig.swift */; }; 3DD0842021569A65008E3B4A /* TTSResource in Resources */ = {isa = PBXBuildFile; fileRef = 3DD0841F21569A65008E3B4A /* TTSResource */; }; 3DD0842221569A74008E3B4A /* VoiceBook.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DD0842121569A74008E3B4A /* VoiceBook.swift */; }; 3DD0842521569A95008E3B4A /* AKPickerView.m in Sources */ = {isa = PBXBuildFile; fileRef = 3DD0842321569A95008E3B4A /* AKPickerView.m */; }; 3DD0842B215750C8008E3B4A /* Network.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DD0842A215750C6008E3B4A /* Network.swift */; }; 3DD0842D2157604D008E3B4A /* ZSSpeechView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DD0842C2157604D008E3B4A /* ZSSpeechView.swift */; }; 3DD084322157A14C008E3B4A /* PcmPlayer.m in Sources */ = {isa = PBXBuildFile; fileRef = 3DD084312157A14B008E3B4A /* PcmPlayer.m */; }; 3DD084362157C0ED008E3B4A /* libc++.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 3DD084352157C0ED008E3B4A /* libc++.tbd */; }; 3DD084382157C101008E3B4A /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3DD084372157C101008E3B4A /* AudioToolbox.framework */; }; 3DD0843A2157C124008E3B4A /* AddressBook.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3DD084392157C124008E3B4A /* AddressBook.framework */; }; 3DD0843C2157C136008E3B4A /* Contacts.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3DD0843B2157C135008E3B4A /* Contacts.framework */; }; 3DDB6CA82130F4BC00E8698D /* CoreTextUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 3DDB6C952130F4BC00E8698D /* CoreTextUtils.m */; }; 3DDB6CA92130F4BC00E8698D /* CTFrameParser.m in Sources */ = {isa = PBXBuildFile; fileRef = 3DDB6C962130F4BC00E8698D /* CTFrameParser.m */; }; 3DDB6CAA2130F4BC00E8698D /* CoreTextImageData.m in Sources */ = {isa = PBXBuildFile; fileRef = 3DDB6C982130F4BC00E8698D /* CoreTextImageData.m */; }; 3DDB6CAB2130F4BC00E8698D /* CTFrameParserConfig.m in Sources */ = {isa = PBXBuildFile; fileRef = 3DDB6C9E2130F4BC00E8698D /* CTFrameParserConfig.m */; }; 3DDB6CAC2130F4BC00E8698D /* CoreTextData.m in Sources */ = {isa = PBXBuildFile; fileRef = 3DDB6C9F2130F4BC00E8698D /* CoreTextData.m */; }; 3DDB6CAD2130F4BC00E8698D /* CTDisplayView.m in Sources */ = {isa = PBXBuildFile; fileRef = 3DDB6CA12130F4BC00E8698D /* CTDisplayView.m */; }; 3DDB6CAE2130F4BC00E8698D /* UIView+frameAdjust.m in Sources */ = {isa = PBXBuildFile; fileRef = 3DDB6CA32130F4BC00E8698D /* UIView+frameAdjust.m */; }; 3DDB6CAF2130F4BC00E8698D /* MagnifiterView.m in Sources */ = {isa = PBXBuildFile; fileRef = 3DDB6CA52130F4BC00E8698D /* MagnifiterView.m */; }; 3DDB6CB02130F4BC00E8698D /* CoreTextLinkData.m in Sources */ = {isa = PBXBuildFile; fileRef = 3DDB6CA72130F4BC00E8698D /* CoreTextLinkData.m */; }; 3DDB6CB52130F63300E8698D /* RegexKitLite.m in Sources */ = {isa = PBXBuildFile; fileRef = 3DDB6CB42130F63200E8698D /* RegexKitLite.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; 3DDB6CB72130F73000E8698D /* libicucore.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 3DDB6CB62130F73000E8698D /* libicucore.tbd */; }; 3DDEEAF9214A8E3F003D12DB /* ZSFontViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DDEEAF8214A8E3F003D12DB /* ZSFontViewController.swift */; }; 3DDEEAFB214A8EC8003D12DB /* ZSFontService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DDEEAFA214A8EC8003D12DB /* ZSFontService.swift */; }; 3DDEEAFD214A8EE1003D12DB /* ZSFontViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DDEEAFC214A8EE1003D12DB /* ZSFontViewModel.swift */; }; 3DDEEAFF214B5D3A003D12DB /* UIFont+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DDEEAFE214B5D39003D12DB /* UIFont+Extension.swift */; }; 3DDEEB02214B5D59003D12DB /* UIFont+ZSExtension.m in Sources */ = {isa = PBXBuildFile; fileRef = 3DDEEB01214B5D59003D12DB /* UIFont+ZSExtension.m */; }; 3DE393D9219D505600890488 /* ZSBoughtInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DE393D8219D505500890488 /* ZSBoughtInfo.swift */; }; 3DE393DB219D514100890488 /* ZSBookBoughtViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DE393DA219D514100890488 /* ZSBookBoughtViewModel.swift */; }; 3DE393E721A3B7B600890488 /* ZSReadRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DE393E621A3B7B600890488 /* ZSReadRecord.swift */; }; 3DE393E921A3B84600890488 /* BookShelf.json in Resources */ = {isa = PBXBuildFile; fileRef = 3DE393E821A3B84600890488 /* BookShelf.json */; }; 3DE393EB21A3EB2D00890488 /* ZSJSON.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DE393EA21A3EB2D00890488 /* ZSJSON.swift */; }; 3DE3AF7F23CF061A00D74C1F /* ZSPageTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DE3AF7E23CF061A00D74C1F /* ZSPageTableViewCell.swift */; }; 3DE3AF8123D0622C00D74C1F /* ZSShelfOperatingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DE3AF8023D0622C00D74C1F /* ZSShelfOperatingView.swift */; }; 3DE77D2620C8E99500A86DF0 /* ZSBaseTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DE77D2520C8E99500A86DF0 /* ZSBaseTableViewController.swift */; }; 3DE77D2A20C90EA300A86DF0 /* ZSRootWebService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DE77D2920C90EA300A86DF0 /* ZSRootWebService.swift */; }; 3DECC38422452F9E00EEB146 /* ZSFilterThemeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DECC38322452F9E00EEB146 /* ZSFilterThemeViewModel.swift */; }; 3DECC386224533EB00EEB146 /* ZSFilterThemeModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DECC385224533EB00EEB146 /* ZSFilterThemeModel.swift */; }; 3DECC3882245379400EEB146 /* ZSFilterThemeCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DECC3872245379300EEB146 /* ZSFilterThemeCell.swift */; }; 3DECC38A22460B6200EEB146 /* ZSVoicePlayViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DECC38922460B6100EEB146 /* ZSVoicePlayViewController.swift */; }; 3DECC38C22460DD000EEB146 /* ZSSegmenuViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DECC38B22460DD000EEB146 /* ZSSegmenuViewController.swift */; }; 3DECC38E224611BC00EEB146 /* ZSVoicePlayerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DECC38D224611BC00EEB146 /* ZSVoicePlayerViewController.swift */; }; 3DECC390224611EB00EEB146 /* ZSVoicePlayListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DECC38F224611EB00EEB146 /* ZSVoicePlayListViewController.swift */; }; 3DECC39222461E1900EEB146 /* ZSVoicePlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DECC39122461E1900EEB146 /* ZSVoicePlayerView.swift */; }; 3DECC39422461E4900EEB146 /* ZSVoicePlayProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DECC39322461E4900EEB146 /* ZSVoicePlayProgressView.swift */; }; 3DECC3962246298300EEB146 /* ZSVoicePlayViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DECC3952246298300EEB146 /* ZSVoicePlayViewModel.swift */; }; 3DECC3DE22464C4F00EEB146 /* libXMOpenPlatform.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 3DECC39822464C4E00EEB146 /* libXMOpenPlatform.a */; }; 3DECC3E522464C5000EEB146 /* XMResource.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 3DECC3D322464C4F00EEB146 /* XMResource.bundle */; }; 3DECC3E722464F9300EEB146 /* ZSVoiceBookCategoryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DECC3E622464F9300EEB146 /* ZSVoiceBookCategoryViewController.swift */; }; 3DECC3E922467F9B00EEB146 /* ZSVoiceSegmentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DECC3E822467F9A00EEB146 /* ZSVoiceSegmentView.swift */; }; 3DEFD99123BDB12200116ECD /* ZSRefreshTextHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DEFD99023BDB12200116ECD /* ZSRefreshTextHeader.swift */; }; 3DF2D27820EB5773004E73B6 /* ZSChapterInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DF2D27720EB5773004E73B6 /* ZSChapterInfo.swift */; }; 3DF2D27A20EB5C1C004E73B6 /* ZSChapterBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DF2D27920EB5C1C004E73B6 /* ZSChapterBody.swift */; }; 3DF6238D277970270049FD73 /* AppOpenAdManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DF6238C277970260049FD73 /* AppOpenAdManager.swift */; }; 3DF623912779AE100049FD73 /* AdSupport.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3DF623902779AE100049FD73 /* AdSupport.framework */; }; 3DF623952779AE4D0049FD73 /* CoreLocation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3DF623942779AE4D0049FD73 /* CoreLocation.framework */; }; 3DF623972779AE7B0049FD73 /* JavaScriptCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3DF623962779AE7B0049FD73 /* JavaScriptCore.framework */; }; 3DF623992779AE8C0049FD73 /* MapKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3DF623982779AE8C0049FD73 /* MapKit.framework */; }; 3DF6239B2779AEA20049FD73 /* MediaPlayer.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3DF6239A2779AEA20049FD73 /* MediaPlayer.framework */; }; 3DF6239F2779AEE00049FD73 /* libbz2.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 3DF6239E2779AEE00049FD73 /* libbz2.tbd */; }; 3DF623A12779AEF50049FD73 /* libiconv.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 3DF623A02779AEF50049FD73 /* libiconv.tbd */; }; 3DF623A32779AF020049FD73 /* libresolv.9.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 3DF623A22779AF010049FD73 /* libresolv.9.tbd */; }; 3DF623A52779AF180049FD73 /* libc++abi.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 3DF623A42779AF180049FD73 /* libc++abi.tbd */; }; 3DF623A72779B09A0049FD73 /* BUAdManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DF623A62779B09A0049FD73 /* BUAdManager.swift */; }; 3DFBDEDD21F1A7ED00AED519 /* ZSCacheHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DFBDEDC21F1A7EC00AED519 /* ZSCacheHelper.swift */; }; 3DFCF6EF21E6470000D3A8EC /* ZSVoucherView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DFCF6EE21E6470000D3A8EC /* ZSVoucherView.swift */; }; 3DFCF6F421E9984100D3A8EC /* ZSSwipeCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DFCF6F321E9984000D3A8EC /* ZSSwipeCell.swift */; }; 3DFE8E812595D4280044DCEB /* ZSAddSourceTextField.m in Sources */ = {isa = PBXBuildFile; fileRef = 3DFE8E802595D4280044DCEB /* ZSAddSourceTextField.m */; }; 567D4C5122D3A2E149B8AD65 /* Pods_zhuishushenqi.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E6DD667D5A369B09442DE895 /* Pods_zhuishushenqi.framework */; }; AD065949258C66D1009009FA /* ZSRegularVerifyViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD065948258C66D1009009FA /* ZSRegularVerifyViewController.swift */; }; ADCB1BEE278732E300289C83 /* ZSCommunityHotCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = ADCB1BED278732E300289C83 /* ZSCommunityHotCell.swift */; }; B200D1FF1EEDB1FE003A42E3 /* QSIntroduceReadCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B200D1FD1EEDB1FE003A42E3 /* QSIntroduceReadCell.swift */; }; B200D2001EEDB1FE003A42E3 /* QSIntroduceReadCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = B200D1FE1EEDB1FE003A42E3 /* QSIntroduceReadCell.xib */; }; B200D2221EEE44AB003A42E3 /* QSLaunchRecView.xib in Resources */ = {isa = PBXBuildFile; fileRef = B200D2211EEE44AB003A42E3 /* QSLaunchRecView.xib */; }; B203BB591E9E0F4400F9C052 /* QSSearchViewController+SearchBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = B203BB581E9E0F4400F9C052 /* QSSearchViewController+SearchBar.swift */; }; B203BB651E9E119400F9C052 /* QSSearchViewController+Transition.swift in Sources */ = {isa = PBXBuildFile; fileRef = B203BB641E9E119400F9C052 /* QSSearchViewController+Transition.swift */; }; B203BB681E9E142C00F9C052 /* QSHistoryCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B203BB661E9E142C00F9C052 /* QSHistoryCell.swift */; }; B203BB691E9E142C00F9C052 /* QSHistoryCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = B203BB671E9E142C00F9C052 /* QSHistoryCell.xib */; }; B209975123DBFA2D00E37FD3 /* ZSBookLocalShelfViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B209975023DBFA2D00E37FD3 /* ZSBookLocalShelfViewController.swift */; }; B20D74081EED7D250034516F /* QSIntroduceCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B20D74061EED7D250034516F /* QSIntroduceCell.swift */; }; B20D74091EED7D250034516F /* QSIntroduceCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = B20D74071EED7D250034516F /* QSIntroduceCell.xib */; }; B20D74161EED83690034516F /* QSLastIntroduceCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B20D74141EED83690034516F /* QSLastIntroduceCell.swift */; }; B20D74171EED83690034516F /* QSLastIntroduceCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = B20D74151EED83690034516F /* QSLastIntroduceCell.xib */; }; B21C732B2365774B00E9D148 /* ZSSearchResultCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B21C732A2365774B00E9D148 /* ZSSearchResultCell.swift */; }; B21C735123657ACF00E9D148 /* string_buffer.c in Sources */ = {isa = PBXBuildFile; fileRef = B21C732E23657ACE00E9D148 /* string_buffer.c */; }; B21C735223657ACF00E9D148 /* util.c in Sources */ = {isa = PBXBuildFile; fileRef = B21C733123657ACE00E9D148 /* util.c */; }; B21C735323657ACF00E9D148 /* char_ref.rl in Resources */ = {isa = PBXBuildFile; fileRef = B21C733223657ACE00E9D148 /* char_ref.rl */; }; B21C735423657ACF00E9D148 /* vector.c in Sources */ = {isa = PBXBuildFile; fileRef = B21C733523657ACE00E9D148 /* vector.c */; }; B21C735523657ACF00E9D148 /* attribute.c in Sources */ = {isa = PBXBuildFile; fileRef = B21C733623657ACE00E9D148 /* attribute.c */; }; B21C735623657ACF00E9D148 /* char_ref.c in Sources */ = {isa = PBXBuildFile; fileRef = B21C733723657ACE00E9D148 /* char_ref.c */; }; B21C735723657ACF00E9D148 /* utf8.c in Sources */ = {isa = PBXBuildFile; fileRef = B21C733923657ACE00E9D148 /* utf8.c */; }; B21C735823657ACF00E9D148 /* error.c in Sources */ = {isa = PBXBuildFile; fileRef = B21C733F23657ACE00E9D148 /* error.c */; }; B21C735923657ACF00E9D148 /* tag.in in Resources */ = {isa = PBXBuildFile; fileRef = B21C734023657ACE00E9D148 /* tag.in */; }; B21C735A23657ACF00E9D148 /* tag.c in Sources */ = {isa = PBXBuildFile; fileRef = B21C734223657ACE00E9D148 /* tag.c */; }; B21C735B23657ACF00E9D148 /* parser.c in Sources */ = {isa = PBXBuildFile; fileRef = B21C734623657ACE00E9D148 /* parser.c */; }; B21C735C23657ACF00E9D148 /* tokenizer.c in Sources */ = {isa = PBXBuildFile; fileRef = B21C734A23657ACE00E9D148 /* tokenizer.c */; }; B21C735D23657ACF00E9D148 /* string_piece.c in Sources */ = {isa = PBXBuildFile; fileRef = B21C734B23657ACE00E9D148 /* string_piece.c */; }; B21C735E23657ACF00E9D148 /* OCGumbo+Query.m in Sources */ = {isa = PBXBuildFile; fileRef = B21C734F23657ACE00E9D148 /* OCGumbo+Query.m */; }; B21C735F23657ACF00E9D148 /* OCGumbo.m in Sources */ = {isa = PBXBuildFile; fileRef = B21C735023657ACE00E9D148 /* OCGumbo.m */; }; B21C736223657B1200E9D148 /* AikanParserModel.m in Sources */ = {isa = PBXBuildFile; fileRef = B21C736023657B1100E9D148 /* AikanParserModel.m */; }; B21C736523657B3F00E9D148 /* AikanHtmlParser.m in Sources */ = {isa = PBXBuildFile; fileRef = B21C736423657B3F00E9D148 /* AikanHtmlParser.m */; }; B21C736723657E1400E9D148 /* HtmlParserModelData.dat in Resources */ = {isa = PBXBuildFile; fileRef = B21C736623657E1400E9D148 /* HtmlParserModelData.dat */; }; B21C736923672D6400E9D148 /* ZSSearchInfoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B21C736823672D6400E9D148 /* ZSSearchInfoViewController.swift */; }; B21C736B2367304800E9D148 /* ZSBookInfoHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B21C736A2367304800E9D148 /* ZSBookInfoHeaderView.swift */; }; B22182B91DA40540002458D2 /* CoreText.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B22182B81DA40540002458D2 /* CoreText.framework */; }; B22182BB1DA40548002458D2 /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B22182BA1DA40548002458D2 /* CoreFoundation.framework */; }; B22182BD1DA40550002458D2 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B22182BC1DA40550002458D2 /* CoreGraphics.framework */; }; B22182BF1DA40558002458D2 /* CoreImage.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B22182BE1DA40558002458D2 /* CoreImage.framework */; }; B22182C11DA4055E002458D2 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B22182C01DA4055E002458D2 /* QuartzCore.framework */; }; B22182C31DA40566002458D2 /* ImageIO.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B22182C21DA40566002458D2 /* ImageIO.framework */; }; B22182C51DA40570002458D2 /* AssetsLibrary.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B22182C41DA40570002458D2 /* AssetsLibrary.framework */; }; B22182C71DA4057A002458D2 /* Accelerate.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B22182C61DA4057A002458D2 /* Accelerate.framework */; }; B22182C91DA40582002458D2 /* MobileCoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B22182C81DA40582002458D2 /* MobileCoreServices.framework */; }; B22182CB1DA40588002458D2 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B22182CA1DA40588002458D2 /* SystemConfiguration.framework */; }; B22182CD1DA4058E002458D2 /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = B22182CC1DA4058E002458D2 /* libsqlite3.tbd */; }; B22182CF1DA40594002458D2 /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = B22182CE1DA40594002458D2 /* libz.tbd */; }; B22204A21EE9284F008E1902 /* QSSplashScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = B22204A11EE9284F008E1902 /* QSSplashScreen.swift */; }; B222A2C31EEE6EE9007CD57F /* QSLaunchRecView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B222A2C21EEE6EE9007CD57F /* QSLaunchRecView.swift */; }; B222C08A23C1CAA800D66E3C /* ZSSearchInfoTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B222C08923C1CAA800D66E3C /* ZSSearchInfoTableViewCell.swift */; }; B222C08C23C379E000D66E3C /* ZSReadHistory.swift in Sources */ = {isa = PBXBuildFile; fileRef = B222C08B23C379E000D66E3C /* ZSReadHistory.swift */; }; B22AC43A1E9E6626008625E6 /* QSSearchResultTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = B22AC4391E9E6626008625E6 /* QSSearchResultTable.swift */; }; B22AC44B1E9F0AE7008625E6 /* QSSearchAutoCompleteTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = B22AC44A1E9F0AE7008625E6 /* QSSearchAutoCompleteTable.swift */; }; B22AC44D1E9F1C62008625E6 /* ZSBaseSegmentItemViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B22AC44C1E9F1C62008625E6 /* ZSBaseSegmentItemViewController.swift */; }; B22AC44F1E9F1C7D008625E6 /* ZSCatelogItemViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B22AC44E1E9F1C7D008625E6 /* ZSCatelogItemViewController.swift */; }; B22AC4511E9F1C8C008625E6 /* ZSRankViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B22AC4501E9F1C8C008625E6 /* ZSRankViewModel.swift */; }; B22AC4531E9F1CA8008625E6 /* ZSCatelogDetailViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B22AC4521E9F1CA8008625E6 /* ZSCatelogDetailViewModel.swift */; }; B22AC4551E9F1E8F008625E6 /* QSRankViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B22AC4541E9F1E8F008625E6 /* QSRankViewController.swift */; }; B22AC4571E9F5520008625E6 /* QSLoadingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B22AC4561E9F5520008625E6 /* QSLoadingView.swift */; }; B22AC4591E9F6767008625E6 /* ZSCatelogParameterModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B22AC4581E9F6767008625E6 /* ZSCatelogParameterModel.swift */; }; B22AC45B1E9F6774008625E6 /* ZSRankDetailWebService.swift in Sources */ = {isa = PBXBuildFile; fileRef = B22AC45A1E9F6774008625E6 /* ZSRankDetailWebService.swift */; }; B22AC45D1E9F678D008625E6 /* ZSRankDetailViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B22AC45C1E9F678D008625E6 /* ZSRankDetailViewModel.swift */; }; B22AC4611E9F695D008625E6 /* ZSRankViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B22AC4601E9F695D008625E6 /* ZSRankViewController.swift */; }; B23505C02783F8EC00D444C7 /* SplashViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B23505BF2783F8EC00D444C7 /* SplashViewController.swift */; }; B2414D9C1EA602ED005A5758 /* UIViewController+Alert.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2414D9B1EA602ED005A5758 /* UIViewController+Alert.swift */; }; B24B3F4D20E534390098ACE5 /* ZSSearchViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B24B3F4B20E534390098ACE5 /* ZSSearchViewCell.swift */; }; B24B3F4E20E534390098ACE5 /* ZSSearchViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = B24B3F4C20E534390098ACE5 /* ZSSearchViewCell.xib */; }; B24B3F5020E8885A0098ACE5 /* ZSSearchHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B24B3F4F20E8885A0098ACE5 /* ZSSearchHeaderView.swift */; }; B24B3F5220E888920098ACE5 /* ZSHistoryHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B24B3F5120E888920098ACE5 /* ZSHistoryHeaderView.swift */; }; B24B3F5420E888D60098ACE5 /* ZSSearchResultViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B24B3F5320E888D60098ACE5 /* ZSSearchResultViewController.swift */; }; B24B3F5620ECA04B0098ACE5 /* Array+ZSExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B24B3F5520ECA04B0098ACE5 /* Array+ZSExtension.swift */; }; B24BED4E2301692800CF8C4D /* ZSForumToolBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = B24BED4D2301692700CF8C4D /* ZSForumToolBar.swift */; }; B24BED5023017CB500CF8C4D /* ZSForumPageFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B24BED4F23017CB500CF8C4D /* ZSForumPageFooterView.swift */; }; B24BED522301865400CF8C4D /* ZSForumComment.swift in Sources */ = {isa = PBXBuildFile; fileRef = B24BED512301865400CF8C4D /* ZSForumComment.swift */; }; B251228D1DB3291A0040CAD3 /* CFNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B251228C1DB3291A0040CAD3 /* CFNetwork.framework */; }; B251F89C2030350E004AAF95 /* Dictionary+QSExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B251F89B2030350E004AAF95 /* Dictionary+QSExtension.swift */; }; B252D487210EF3960091858E /* HTTPDataResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = B252D45D210EF3960091858E /* HTTPDataResponse.m */; }; B252D488210EF3960091858E /* HTTPDynamicFileResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = B252D45F210EF3960091858E /* HTTPDynamicFileResponse.m */; }; B252D489210EF3960091858E /* HTTPFileResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = B252D460210EF3960091858E /* HTTPFileResponse.m */; }; B252D48A210EF3960091858E /* HTTPAsyncFileResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = B252D461210EF3960091858E /* HTTPAsyncFileResponse.m */; }; B252D48B210EF3960091858E /* HTTPRedirectResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = B252D462210EF3960091858E /* HTTPRedirectResponse.m */; }; B252D48C210EF3960091858E /* HTTPMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = B252D467210EF3960091858E /* HTTPMessage.m */; }; B252D48D210EF3960091858E /* HTTPConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = B252D468210EF3960091858E /* HTTPConnection.m */; }; B252D48E210EF3960091858E /* WebSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = B252D469210EF3960091858E /* WebSocket.m */; }; B252D48F210EF3960091858E /* MultipartMessageHeaderField.m in Sources */ = {isa = PBXBuildFile; fileRef = B252D46E210EF3960091858E /* MultipartMessageHeaderField.m */; }; B252D490210EF3960091858E /* MultipartFormDataParser.m in Sources */ = {isa = PBXBuildFile; fileRef = B252D46F210EF3960091858E /* MultipartFormDataParser.m */; }; B252D491210EF3960091858E /* MultipartMessageHeader.m in Sources */ = {isa = PBXBuildFile; fileRef = B252D470210EF3960091858E /* MultipartMessageHeader.m */; }; B252D492210EF3960091858E /* HTTPAuthenticationRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = B252D472210EF3960091858E /* HTTPAuthenticationRequest.m */; }; B252D493210EF3960091858E /* DDNumber.m in Sources */ = {isa = PBXBuildFile; fileRef = B252D474210EF3960091858E /* DDNumber.m */; }; B252D494210EF3960091858E /* DDData.m in Sources */ = {isa = PBXBuildFile; fileRef = B252D475210EF3960091858E /* DDData.m */; }; B252D495210EF3960091858E /* DDRange.m in Sources */ = {isa = PBXBuildFile; fileRef = B252D478210EF3960091858E /* DDRange.m */; }; B252D496210EF3960091858E /* HTTPServer.m in Sources */ = {isa = PBXBuildFile; fileRef = B252D47A210EF3960091858E /* HTTPServer.m */; }; B252D497210EF3960091858E /* DAVConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = B252D47D210EF3960091858E /* DAVConnection.m */; }; B252D498210EF3960091858E /* DELETEResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = B252D47F210EF3960091858E /* DELETEResponse.m */; }; B252D499210EF3960091858E /* DAVResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = B252D482210EF3960091858E /* DAVResponse.m */; }; B252D49A210EF3960091858E /* PUTResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = B252D483210EF3960091858E /* PUTResponse.m */; }; B252D49B210EF3960091858E /* README.markdown in Resources */ = {isa = PBXBuildFile; fileRef = B252D485210EF3960091858E /* README.markdown */; }; B252D49C210EF3960091858E /* LICENSE.txt in Resources */ = {isa = PBXBuildFile; fileRef = B252D486210EF3960091858E /* LICENSE.txt */; }; B252D49E210EF4190091858E /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B252D49D210EF4190091858E /* Security.framework */; }; B252D4A0210EF4480091858E /* libxml2.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = B252D49F210EF4470091858E /* libxml2.tbd */; }; B252D4A2210EF4600091858E /* libxml2.2.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = B252D4A1210EF4600091858E /* libxml2.2.tbd */; }; B252D4A5210EF7070091858E /* ZSHTTPConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = B252D4A4210EF7070091858E /* ZSHTTPConnection.m */; }; B252D4AA210EF8770091858E /* ZSImportBookViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B252D4A9210EF8770091858E /* ZSImportBookViewController.swift */; }; B252D4AD210EFA660091858E /* ZSHTTPTool.m in Sources */ = {isa = PBXBuildFile; fileRef = B252D4AC210EFA660091858E /* ZSHTTPTool.m */; }; B252D4B1210EFF260091858E /* index.html in Resources */ = {isa = PBXBuildFile; fileRef = B252D4AF210EFF260091858E /* index.html */; }; B252D4B2210EFF260091858E /* upload.html in Resources */ = {isa = PBXBuildFile; fileRef = B252D4B0210EFF260091858E /* upload.html */; }; B252D4C2210F0B990091858E /* s.css in Resources */ = {isa = PBXBuildFile; fileRef = B252D4BE210F0B980091858E /* s.css */; }; B252D4C3210F0B990091858E /* bg.png in Resources */ = {isa = PBXBuildFile; fileRef = B252D4BF210F0B980091858E /* bg.png */; }; B252D4C4210F0B990091858E /* titlebg.png in Resources */ = {isa = PBXBuildFile; fileRef = B252D4C0210F0B990091858E /* titlebg.png */; }; B252D4C5210F0B990091858E /* theadbg.png in Resources */ = {isa = PBXBuildFile; fileRef = B252D4C1210F0B990091858E /* theadbg.png */; }; B252D4C7210F163C0091858E /* txt.jpeg in Resources */ = {isa = PBXBuildFile; fileRef = B252D4C6210F163C0091858E /* txt.jpeg */; }; B252D4DC210F583B0091858E /* MonitorFileChangeHelp.m in Sources */ = {isa = PBXBuildFile; fileRef = B252D4DB210F583B0091858E /* MonitorFileChangeHelp.m */; }; B252D4DE211006670091858E /* Alamofire+ZSExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B252D4DD211006670091858E /* Alamofire+ZSExtension.swift */; }; B252D4E0211033F20091858E /* ZSShelfWebService.swift in Sources */ = {isa = PBXBuildFile; fileRef = B252D4DF211033F20091858E /* ZSShelfWebService.swift */; }; B252D4E2211035060091858E /* ZSShelfViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B252D4E1211035060091858E /* ZSShelfViewModel.swift */; }; B252D4E4211037380091858E /* ZSShelfViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B252D4E3211037380091858E /* ZSShelfViewController.swift */; }; B253EE3A2784328500F6C6D9 /* UIStoryboardExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B253EE392784328500F6C6D9 /* UIStoryboardExtension.swift */; }; B253EE3D2784358900F6C6D9 /* EncrtptorText in Resources */ = {isa = PBXBuildFile; fileRef = B253EE3C2784358900F6C6D9 /* EncrtptorText */; }; B253EE3F2784436300F6C6D9 /* UserDefaultsExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B253EE3E2784436300F6C6D9 /* UserDefaultsExtension.swift */; }; B253EE422786E95C00F6C6D9 /* ZSNetwork.swift in Sources */ = {isa = PBXBuildFile; fileRef = B253EE412786E95B00F6C6D9 /* ZSNetwork.swift */; }; B255707E1F3A075600C35A34 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B255707C1F3A075600C35A34 /* AppDelegate.swift */; }; B255707F1F3A075600C35A34 /* Config.swift in Sources */ = {isa = PBXBuildFile; fileRef = B255707D1F3A075600C35A34 /* Config.swift */; }; B25570811F3A07BB00C35A34 /* AppStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = B25570801F3A07BB00C35A34 /* AppStyle.swift */; }; B25570871F3A963300C35A34 /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = B25570861F3A963300C35A34 /* Theme.swift */; }; B25570891F3A96E500C35A34 /* BookManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B25570881F3A96E500C35A34 /* BookManager.swift */; }; B2580E1B1E9B236000455AFE /* QSSearchInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2580E151E9B236000455AFE /* QSSearchInteractor.swift */; }; B2580E1C1E9B236000455AFE /* QSSearchPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2580E161E9B236000455AFE /* QSSearchPresenter.swift */; }; B2580E1D1E9B236000455AFE /* QSSearchProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2580E171E9B236000455AFE /* QSSearchProtocols.swift */; }; B2580E1E1E9B236000455AFE /* QSSearchRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2580E181E9B236000455AFE /* QSSearchRouter.swift */; }; B2580E1F1E9B236000455AFE /* QSSearchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2580E191E9B236000455AFE /* QSSearchViewController.swift */; }; B2580E231E9B284900455AFE /* QSHotwords.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2580E221E9B284900455AFE /* QSHotwords.swift */; }; B2580E251E9B6AF500455AFE /* QSSearchHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2580E241E9B6AF500455AFE /* QSSearchHeaderView.swift */; }; B2583CB21EA78280004178F3 /* ZSFilterThemeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2583CAD1EA78280004178F3 /* ZSFilterThemeViewController.swift */; }; B2583CB41EA78280004178F3 /* TopicDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2583CB11EA78280004178F3 /* TopicDetailViewController.swift */; }; B2583CBA1EA782C2004178F3 /* QSThemeTopicInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2583CB51EA782C2004178F3 /* QSThemeTopicInteractor.swift */; }; B2583CBB1EA782C2004178F3 /* QSThemeTopicPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2583CB61EA782C2004178F3 /* QSThemeTopicPresenter.swift */; }; B2583CBC1EA782C2004178F3 /* QSThemeTopicProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2583CB71EA782C2004178F3 /* QSThemeTopicProtocols.swift */; }; B2583CBD1EA782C2004178F3 /* QSThemeTopicRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2583CB81EA782C2004178F3 /* QSThemeTopicRouter.swift */; }; B2583CBE1EA782C2004178F3 /* QSThemeTopicViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2583CB91EA782C2004178F3 /* QSThemeTopicViewController.swift */; }; B258BB241EA4B201006E5802 /* UIView+ScreenShot.swift in Sources */ = {isa = PBXBuildFile; fileRef = B258BB231EA4B201006E5802 /* UIView+ScreenShot.swift */; }; B258F318249BB8E500E17943 /* ApplicationExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B258F317249BB8E400E17943 /* ApplicationExtension.swift */; }; B25CEFEE22F9268A002ABE30 /* MarkupParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = B25CEFEA22F92688002ABE30 /* MarkupParser.swift */; }; B25CEFEF22F9268A002ABE30 /* ZSDisplayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B25CEFEB22F92689002ABE30 /* ZSDisplayView.swift */; }; B25CEFF022F9268A002ABE30 /* CTSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = B25CEFEC22F92689002ABE30 /* CTSettings.swift */; }; B25CEFF122F9268A002ABE30 /* ZSTouchAnchorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B25CEFED22F9268A002ABE30 /* ZSTouchAnchorView.swift */; }; B25CEFF422F92AE4002ABE30 /* ZSForumPageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B25CEFF322F92AE4002ABE30 /* ZSForumPageViewController.swift */; }; B25CEFF622F92C5E002ABE30 /* ZSForumViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B25CEFF522F92C5E002ABE30 /* ZSForumViewModel.swift */; }; B25CEFF822F97937002ABE30 /* ZSForumPageHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B25CEFF722F97937002ABE30 /* ZSForumPageHeaderView.swift */; }; B25CEFFA22FA7BB2002ABE30 /* ZSPostReview.swift in Sources */ = {isa = PBXBuildFile; fileRef = B25CEFF922FA7BB2002ABE30 /* ZSPostReview.swift */; }; B25CF00822FAA732002ABE30 /* ZSPostReviewAuthor.swift in Sources */ = {isa = PBXBuildFile; fileRef = B25CF00522FAA731002ABE30 /* ZSPostReviewAuthor.swift */; }; B25CF00922FAA732002ABE30 /* ZSPostReviewHelpful.swift in Sources */ = {isa = PBXBuildFile; fileRef = B25CF00622FAA732002ABE30 /* ZSPostReviewHelpful.swift */; }; B25CF00A22FAA732002ABE30 /* ZSPostReviewBook.swift in Sources */ = {isa = PBXBuildFile; fileRef = B25CF00722FAA732002ABE30 /* ZSPostReviewBook.swift */; }; B25D1AD9230293BB00B1F786 /* ZSForumPageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B25D1AD8230293BB00B1F786 /* ZSForumPageCell.swift */; }; B25EB92A21933A6D000D3657 /* ZSWriteReview.swift in Sources */ = {isa = PBXBuildFile; fileRef = B25EB92921933A6D000D3657 /* ZSWriteReview.swift */; }; B25EB92C2194986C000D3657 /* ZSEncryptorAESUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = B25EB92B2194986C000D3657 /* ZSEncryptorAESUtils.swift */; }; B25EB92F2194999E000D3657 /* FBEncryptorAESUtils.mm in Sources */ = {isa = PBXBuildFile; fileRef = B25EB92E2194999E000D3657 /* FBEncryptorAESUtils.mm */; }; B25EB93221949AF1000D3657 /* FBEncryptorAES.m in Sources */ = {isa = PBXBuildFile; fileRef = B25EB93121949AF1000D3657 /* FBEncryptorAES.m */; }; B26286CA1EEA46DD005A1A8A /* ZSIntroducePage.swift in Sources */ = {isa = PBXBuildFile; fileRef = B26286C91EEA46DD005A1A8A /* ZSIntroducePage.swift */; }; B263ACDF1EE679830096CE80 /* HomeListViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B263ACDD1EE679830096CE80 /* HomeListViewCell.swift */; }; B263E2E9211EBFEC001819FB /* SQLite+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B263E2E8211EBFEC001819FB /* SQLite+Extension.swift */; }; B2662EBC21A7DAC5008AD5C6 /* ZSDBPropertyModel.m in Sources */ = {isa = PBXBuildFile; fileRef = B2662EBB21A7DAC5008AD5C6 /* ZSDBPropertyModel.m */; }; B266C66A21A6FCC3006B5A15 /* ZSDBManager.m in Sources */ = {isa = PBXBuildFile; fileRef = B266C66921A6FCC3006B5A15 /* ZSDBManager.m */; }; B2683CD922C8F65300FE9CF0 /* ZSWebJumpHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2683CD822C8F65300FE9CF0 /* ZSWebJumpHandler.swift */; }; B2683CF122C8FC4C00FE9CF0 /* ZSWebUserHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2683CF022C8FC4C00FE9CF0 /* ZSWebUserHandler.swift */; }; B2683CF322C8FCE900FE9CF0 /* ZSWebToolHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2683CF222C8FCE900FE9CF0 /* ZSWebToolHandler.swift */; }; B2683CF522C8FD6500FE9CF0 /* ZSWebContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2683CF422C8FD6500FE9CF0 /* ZSWebContext.swift */; }; B2683CF722C8FE4E00FE9CF0 /* ZSWebBIHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2683CF622C8FE4E00FE9CF0 /* ZSWebBIHandler.swift */; }; B2683CF922C8FE8A00FE9CF0 /* ZSWebSpeakHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2683CF822C8FE8A00FE9CF0 /* ZSWebSpeakHandler.swift */; }; B2683CFB22C9111600FE9CF0 /* ZSConfigUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2683CFA22C9111600FE9CF0 /* ZSConfigUtil.swift */; }; B2683CFD22D0811000FE9CF0 /* ZSUserDynamicViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2683CFC22D0811000FE9CF0 /* ZSUserDynamicViewController.swift */; }; B2683CFF22D083B200FE9CF0 /* ZSNoNetworkView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2683CFE22D083B200FE9CF0 /* ZSNoNetworkView.swift */; }; B2683D0122D089FE00FE9CF0 /* ZSDynamicHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2683D0022D089FE00FE9CF0 /* ZSDynamicHeaderView.swift */; }; B2683D0322D0940200FE9CF0 /* ZSDynamicViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2683D0222D0940200FE9CF0 /* ZSDynamicViewModel.swift */; }; B2683D0522D0977C00FE9CF0 /* ZSFollowings.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2683D0422D0977C00FE9CF0 /* ZSFollowings.swift */; }; B2683D0722D17FCD00FE9CF0 /* ZSNotificationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2683D0622D17FCD00FE9CF0 /* ZSNotificationViewController.swift */; }; B2683D0922D1830A00FE9CF0 /* ZSNotificationCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2683D0822D1830A00FE9CF0 /* ZSNotificationCell.swift */; }; B2683D0B22D188A600FE9CF0 /* ZSNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2683D0A22D188A600FE9CF0 /* ZSNotification.swift */; }; B2683D0D22D1911900FE9CF0 /* ZSNotificationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2683D0C22D1911900FE9CF0 /* ZSNotificationViewModel.swift */; }; B2683D0F22D1AE7900FE9CF0 /* ZSDiscoverHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2683D0E22D1AE7900FE9CF0 /* ZSDiscoverHeaderView.swift */; }; B2683D1122D1B1C900FE9CF0 /* ZSDiscoverItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2683D1022D1B1C900FE9CF0 /* ZSDiscoverItem.swift */; }; B2683D1322D1C57800FE9CF0 /* ZSMineHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2683D1222D1C57800FE9CF0 /* ZSMineHeaderView.swift */; }; B2683D1522D1CDA900FE9CF0 /* ZSMineViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2683D1422D1CDA900FE9CF0 /* ZSMineViewModel.swift */; }; B2683D1722D1F08300FE9CF0 /* ZSSearchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2683D1622D1F08300FE9CF0 /* ZSSearchViewController.swift */; }; B278AA661F3954FF000A0D55 /* QSAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = B278AA651F3954FF000A0D55 /* QSAPI.swift */; }; B278AA8C1F395F30000A0D55 /* LookBookViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B278AA8A1F395F30000A0D55 /* LookBookViewController.swift */; }; B278AA8D1F395F30000A0D55 /* ReadHistoryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B278AA8B1F395F30000A0D55 /* ReadHistoryViewController.swift */; }; B278AA931F395FA4000A0D55 /* QSHelpViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B278AA901F395FA4000A0D55 /* QSHelpViewCell.swift */; }; B278AA941F395FA4000A0D55 /* QSHelpViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = B278AA911F395FA4000A0D55 /* QSHelpViewCell.xib */; }; B278AA951F395FA4000A0D55 /* QSSegmentDropView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B278AA921F395FA4000A0D55 /* QSSegmentDropView.swift */; }; B278AA981F396073000A0D55 /* ReadHistoryCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B278AA961F396073000A0D55 /* ReadHistoryCell.swift */; }; B278AA991F396074000A0D55 /* ReadHistoryCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = B278AA971F396073000A0D55 /* ReadHistoryCell.xib */; }; B278AAD71F396773000A0D55 /* YTKKeyValueStore.m in Sources */ = {isa = PBXBuildFile; fileRef = B278AAC11F396773000A0D55 /* YTKKeyValueStore.m */; }; B278AB051F398926000A0D55 /* M80AttributedLabel.m in Sources */ = {isa = PBXBuildFile; fileRef = B278AAFD1F398926000A0D55 /* M80AttributedLabel.m */; }; B278AB061F398926000A0D55 /* M80AttributedLabelAttachment.m in Sources */ = {isa = PBXBuildFile; fileRef = B278AAFF1F398926000A0D55 /* M80AttributedLabelAttachment.m */; }; B278AB071F398926000A0D55 /* M80AttributedLabelURL.m in Sources */ = {isa = PBXBuildFile; fileRef = B278AB021F398926000A0D55 /* M80AttributedLabelURL.m */; }; B278AB081F398926000A0D55 /* NSMutableAttributedString+M80.m in Sources */ = {isa = PBXBuildFile; fileRef = B278AB041F398926000A0D55 /* NSMutableAttributedString+M80.m */; }; B27B752121E5061100EFC2D7 /* ZSUserAccountViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B27B752021E5061100EFC2D7 /* ZSUserAccountViewController.swift */; }; B27D7E861F4BE04300866341 /* UICollectionView+QSExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B27D7E851F4BE04300866341 /* UICollectionView+QSExtension.swift */; }; B27D7EA11F4D593D00866341 /* UIScrollView+StateView.m in Sources */ = {isa = PBXBuildFile; fileRef = B27D7EA01F4D593D00866341 /* UIScrollView+StateView.m */; }; B2840F3422C85B75009C9116 /* ZSBookShelfViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2840F3322C85B75009C9116 /* ZSBookShelfViewModel.swift */; }; B286F01A202981DD0069565B /* NSObject+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B286F019202981DD0069565B /* NSObject+Extension.swift */; }; B289DD7C23E5C51A004ED039 /* ZSReaderStyleSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B289DD7B23E5C51A004ED039 /* ZSReaderStyleSelectionView.swift */; }; B28FFC861EAFAED600C27FF9 /* NSString+Encode.m in Sources */ = {isa = PBXBuildFile; fileRef = B28FFC851EAFAED600C27FF9 /* NSString+Encode.m */; }; B28FFC921EB03EA300C27FF9 /* QSHomeDeleteBtn.swift in Sources */ = {isa = PBXBuildFile; fileRef = B28FFC911EB03EA300C27FF9 /* QSHomeDeleteBtn.swift */; }; B29A6D032029C5B400AC4C73 /* NotificationCenter+QSExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B29A6D022029C5B400AC4C73 /* NotificationCenter+QSExtension.swift */; }; B29B4F9D1E821E61008852E3 /* UIImage+QSData.swift in Sources */ = {isa = PBXBuildFile; fileRef = B29B4F9C1E821E61008852E3 /* UIImage+QSData.swift */; }; B29B4F9F1E822992008852E3 /* QSHotModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B29B4F9E1E822992008852E3 /* QSHotModel.swift */; }; B29B4FA21E8257BC008852E3 /* DynamicCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B29B4FA01E8257BC008852E3 /* DynamicCell.swift */; }; B29B4FA31E8257BC008852E3 /* DynamicCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = B29B4FA11E8257BC008852E3 /* DynamicCell.xib */; }; B29CFAE42179A0DF0006F294 /* TencentOpenAPI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B29CFAE32179A0DF0006F294 /* TencentOpenAPI.framework */; }; B29CFAE62179B6E50006F294 /* ZSLoginService.swift in Sources */ = {isa = PBXBuildFile; fileRef = B29CFAE52179B6E50006F294 /* ZSLoginService.swift */; }; B29CFAE82179B7270006F294 /* ZSQQUser.swift in Sources */ = {isa = PBXBuildFile; fileRef = B29CFAE72179B7270006F294 /* ZSQQUser.swift */; }; B29CFB6B217A3D8F0006F294 /* ZSLogin.swift in Sources */ = {isa = PBXBuildFile; fileRef = B29CFB6A217A3D8F0006F294 /* ZSLogin.swift */; }; B29CFB6D217AD6D30006F294 /* ZSMyViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B29CFB6C217AD6D30006F294 /* ZSMyViewController.swift */; }; B29CFB6F217ADA690006F294 /* ZSMyCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B29CFB6E217ADA690006F294 /* ZSMyCell.swift */; }; B29CFB71217AE84B0006F294 /* ZSMyHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B29CFB70217AE84B0006F294 /* ZSMyHeaderView.swift */; }; B29CFB73217B1CC00006F294 /* ZSMyService.swift in Sources */ = {isa = PBXBuildFile; fileRef = B29CFB72217B1CC00006F294 /* ZSMyService.swift */; }; B29CFB75217B1DAB0006F294 /* ZSAccount.swift in Sources */ = {isa = PBXBuildFile; fileRef = B29CFB74217B1DAB0006F294 /* ZSAccount.swift */; }; B29CFB77217B1FEC0006F294 /* ZSCoin.swift in Sources */ = {isa = PBXBuildFile; fileRef = B29CFB76217B1FEC0006F294 /* ZSCoin.swift */; }; B29CFB79217B21C60006F294 /* ZSMyViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B29CFB78217B21C60006F294 /* ZSMyViewModel.swift */; }; B29CFB7B217B2F3A0006F294 /* ZSUserDetail.swift in Sources */ = {isa = PBXBuildFile; fileRef = B29CFB7A217B2F3A0006F294 /* ZSUserDetail.swift */; }; B29CFB7D217B35EA0006F294 /* ZSUserBind.swift in Sources */ = {isa = PBXBuildFile; fileRef = B29CFB7C217B35E90006F294 /* ZSUserBind.swift */; }; B29CFB7F217B37DC0006F294 /* ZSUserInfoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B29CFB7E217B37DC0006F294 /* ZSUserInfoViewController.swift */; }; B29CFB81217B395D0006F294 /* ZSUserBindCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B29CFB80217B395D0006F294 /* ZSUserBindCell.swift */; }; B29E275F230A87E800BA6E33 /* ZSForumTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B29E275E230A87E800BA6E33 /* ZSForumTextView.swift */; }; B2A02F7C20F337C20034DC64 /* ZSHorizonalMoveViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2A02F7B20F337C20034DC64 /* ZSHorizonalMoveViewController.swift */; }; B2A02F7E20F33A1A0034DC64 /* ZSHorizonalMoveCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2A02F7D20F33A1A0034DC64 /* ZSHorizonalMoveCell.swift */; }; B2A02F8020F33F710034DC64 /* ZSReaderManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2A02F7F20F33F710034DC64 /* ZSReaderManager.swift */; }; B2A39C482110447300D6308E /* ZSLocalShelfViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2A39C472110447300D6308E /* ZSLocalShelfViewController.swift */; }; B2A39C4A2110453100D6308E /* ZSLocalShelfViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2A39C492110453100D6308E /* ZSLocalShelfViewModel.swift */; }; B2A7CD81217C5E5E0067D25B /* ZSUserBookshelf.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2A7CD80217C5E5E0067D25B /* ZSUserBookshelf.swift */; }; B2A7CD83217F33230067D25B /* Photos.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B2A7CD82217F33230067D25B /* Photos.framework */; }; B2A7CD85217F36B20067D25B /* ZSThirdLoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2A7CD84217F36B20067D25B /* ZSThirdLoginView.swift */; }; B2A7CD87218016060067D25B /* ZSLoginVerifyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2A7CD86218016060067D25B /* ZSLoginVerifyView.swift */; }; B2A7CD89218017380067D25B /* ZSMobileLogin.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2A7CD88218017380067D25B /* ZSMobileLogin.swift */; }; B2A7CD8B2180D6E90067D25B /* ZSModifyNicknameViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2A7CD8A2180D6E90067D25B /* ZSModifyNicknameViewController.swift */; }; B2A7CDEB2184526D0067D25B /* ZSBookReviewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2A7CDEA2184526D0067D25B /* ZSBookReviewViewController.swift */; }; B2A7CDED218453900067D25B /* ZSReviewDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2A7CDEC218453900067D25B /* ZSReviewDetailView.swift */; }; B2A7CDEF218453DC0067D25B /* ZSBestReviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2A7CDEE218453DC0067D25B /* ZSBestReviewView.swift */; }; B2A7CDF1218469860067D25B /* ZSFeelingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2A7CDF0218469860067D25B /* ZSFeelingView.swift */; }; B2ACA01E212179F60032305E /* ZSSegmentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2ACA01D212179F60032305E /* ZSSegmentViewController.swift */; }; B2ACA02221217DFB0032305E /* ZSRankService.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2ACA02121217DFB0032305E /* ZSRankService.swift */; }; B2ACA05F212416AE0032305E /* ZSBaseNavigationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2ACA05E212416AE0032305E /* ZSBaseNavigationViewController.swift */; }; B2ACA061212444870032305E /* ZSSettingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2ACA060212444870032305E /* ZSSettingViewController.swift */; }; B2ACA0632124467F0032305E /* ZSSettingViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2ACA0622124467F0032305E /* ZSSettingViewModel.swift */; }; B2ACA06521244B3D0032305E /* ZSSetting.plist in Resources */ = {isa = PBXBuildFile; fileRef = B2ACA06421244B3D0032305E /* ZSSetting.plist */; }; B2ACA06A212915240032305E /* ZSReviewsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2ACA069212915240032305E /* ZSReviewsCell.swift */; }; B2ACA06C212915AC0032305E /* ZSReviewsCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = B2ACA06B212915AC0032305E /* ZSReviewsCell.xib */; }; B2ACA06E212944AC0032305E /* ZSWebViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2ACA06D212944AC0032305E /* ZSWebViewController.swift */; }; B2ACA7772787085200B8B275 /* ZSCommunityHot.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2ACA7762787085200B8B275 /* ZSCommunityHot.swift */; }; B2AF57F02675FB850080A065 /* qs_bookshelf.png in Resources */ = {isa = PBXBuildFile; fileRef = B2AF57E92675FB850080A065 /* qs_bookshelf.png */; }; B2AF57F12675FB850080A065 /* style.bundle in Resources */ = {isa = PBXBuildFile; fileRef = B2AF57EA2675FB850080A065 /* style.bundle */; }; B2AF57F22675FB850080A065 /* regular_verify.png in Resources */ = {isa = PBXBuildFile; fileRef = B2AF57EB2675FB850080A065 /* regular_verify.png */; }; B2AF57F32675FB850080A065 /* qs_readerMain.png in Resources */ = {isa = PBXBuildFile; fileRef = B2AF57EC2675FB850080A065 /* qs_readerMain.png */; }; B2AF57F42675FB850080A065 /* qs_changeSource.png in Resources */ = {isa = PBXBuildFile; fileRef = B2AF57ED2675FB850080A065 /* qs_changeSource.png */; }; B2AF57F52675FB850080A065 /* zhuishushenqi.png in Resources */ = {isa = PBXBuildFile; fileRef = B2AF57EE2675FB850080A065 /* zhuishushenqi.png */; }; B2AF57F62675FB850080A065 /* qs_reader.png in Resources */ = {isa = PBXBuildFile; fileRef = B2AF57EF2675FB850080A065 /* qs_reader.png */; }; B2B02E2B1EA8505200A6880A /* QSTopicDetailInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2B02E251EA8505200A6880A /* QSTopicDetailInteractor.swift */; }; B2B02E2C1EA8505200A6880A /* QSTopicDetailPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2B02E261EA8505200A6880A /* QSTopicDetailPresenter.swift */; }; B2B02E2D1EA8505200A6880A /* QSTopicDetailProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2B02E271EA8505200A6880A /* QSTopicDetailProtocols.swift */; }; B2B02E2E1EA8505200A6880A /* QSTopicDetailRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2B02E281EA8505200A6880A /* QSTopicDetailRouter.swift */; }; B2B02E2F1EA8505200A6880A /* QSTopicDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2B02E291EA8505200A6880A /* QSTopicDetailViewController.swift */; }; B2B02E3D1EA891E700A6880A /* ZSDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2B02E3C1EA891E700A6880A /* ZSDetailViewController.swift */; }; B2B02E481EA8920F00A6880A /* ZSCategoryDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2B02E421EA8920F00A6880A /* ZSCategoryDetailViewController.swift */; }; B2B02E4C1EA9A9CA00A6880A /* ZSCatelogViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2B02E4B1EA9A9CA00A6880A /* ZSCatelogViewController.swift */; }; B2B02E531EA9A9F000A6880A /* ZSThemeTopicViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2B02E4D1EA9A9F000A6880A /* ZSThemeTopicViewModel.swift */; }; B2B02E541EA9A9F000A6880A /* ZSVoiceCategoryHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2B02E4E1EA9A9F000A6880A /* ZSVoiceCategoryHeaderView.swift */; }; B2B02E551EA9A9F000A6880A /* ZSVoiceCategoryCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2B02E4F1EA9A9F000A6880A /* ZSVoiceCategoryCell.swift */; }; B2B02E561EA9A9F000A6880A /* ZSVoiceBookSegmentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2B02E501EA9A9F000A6880A /* ZSVoiceBookSegmentViewController.swift */; }; B2B02E571EA9A9F000A6880A /* ZSVoicePlayerCatelogHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2B02E511EA9A9F000A6880A /* ZSVoicePlayerCatelogHeaderView.swift */; }; B2B4BBDF1E7EA4AB000CC201 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B2B4B9F81E7EA4A8000CC201 /* Assets.xcassets */; }; B2B4BBE01E7EA4AB000CC201 /* BaseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2B4B9FB1E7EA4A8000CC201 /* BaseViewController.swift */; }; B2B4BBE11E7EA4AB000CC201 /* SideViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2B4B9FC1E7EA4A8000CC201 /* SideViewController.swift */; }; B2B4BBE21E7EA4AB000CC201 /* SwiftyJSON.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2B4B9FE1E7EA4A8000CC201 /* SwiftyJSON.swift */; }; B2B4BBE31E7EA4AB000CC201 /* XYCBaseModel.m in Sources */ = {isa = PBXBuildFile; fileRef = B2B4BA001E7EA4A8000CC201 /* XYCBaseModel.m */; }; B2B4BBE41E7EA4AB000CC201 /* CommunityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2B4BA021E7EA4A8000CC201 /* CommunityView.swift */; }; B2B4BBE51E7EA4AB000CC201 /* DarkStarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2B4BA031E7EA4A8000CC201 /* DarkStarView.swift */; }; B2B4BBE61E7EA4AB000CC201 /* DarkView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2B4BA041E7EA4A8000CC201 /* DarkView.swift */; }; B2B4BBE91E7EA4AB000CC201 /* EmptyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2B4BA071E7EA4A8000CC201 /* EmptyView.swift */; }; B2B4BBEA1E7EA4AB000CC201 /* EmptyView.xib in Resources */ = {isa = PBXBuildFile; fileRef = B2B4BA081E7EA4A8000CC201 /* EmptyView.xib */; }; B2B4BBEB1E7EA4AB000CC201 /* LightStarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2B4BA091E7EA4A8000CC201 /* LightStarView.swift */; }; B2B4BBEC1E7EA4AB000CC201 /* LightView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2B4BA0A1E7EA4A8000CC201 /* LightView.swift */; }; B2B4BBED1E7EA4AB000CC201 /* RateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2B4BA0B1E7EA4A8000CC201 /* RateView.swift */; }; B2B4BBEE1E7EA4AB000CC201 /* V2FPSLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2B4BA0C1E7EA4A8000CC201 /* V2FPSLabel.swift */; }; B2B4BBEF1E7EA4AB000CC201 /* XYCActionSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2B4BA0D1E7EA4A8000CC201 /* XYCActionSheet.swift */; }; B2B4BBF01E7EA4AB000CC201 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B2B4BA0E1E7EA4A8000CC201 /* LaunchScreen.storyboard */; }; B2B4BBF11E7EA4AB000CC201 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B2B4BA101E7EA4A8000CC201 /* Main.storyboard */; }; B2B4BBF31E7EA4AB000CC201 /* Date+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2B4BA141E7EA4A8000CC201 /* Date+Extension.swift */; }; B2B4BBF41E7EA4AB000CC201 /* DateIntervalFormatter+formatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2B4BA151E7EA4A8000CC201 /* DateIntervalFormatter+formatter.swift */; }; B2B4BBF61E7EA4AB000CC201 /* NSDate+Extension.m in Sources */ = {isa = PBXBuildFile; fileRef = B2B4BA191E7EA4A8000CC201 /* NSDate+Extension.m */; }; B2B4BBF71E7EA4AB000CC201 /* String+QSExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2B4BA1A1E7EA4A8000CC201 /* String+QSExtension.swift */; }; B2B4BBF81E7EA4AB000CC201 /* UIImageView+zhuishu.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2B4BA1B1E7EA4A8000CC201 /* UIImageView+zhuishu.swift */; }; B2B4BBF91E7EA4AB000CC201 /* UILabel+zhuishu.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2B4BA1C1E7EA4A8000CC201 /* UILabel+zhuishu.swift */; }; B2B4BBFA1E7EA4AB000CC201 /* UINavigationItem+BackItem.m in Sources */ = {isa = PBXBuildFile; fileRef = B2B4BA1E1E7EA4A8000CC201 /* UINavigationItem+BackItem.m */; }; B2B4BBFB1E7EA4AB000CC201 /* UITableView+QSGeneric.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2B4BA1F1E7EA4A8000CC201 /* UITableView+QSGeneric.swift */; }; B2B4BBFC1E7EA4AB000CC201 /* UITableView+swizzling.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2B4BA201E7EA4A8000CC201 /* UITableView+swizzling.swift */; }; B2B4BC0E1E7EA4AB000CC201 /* QSRankModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2B4BA3A1E7EA4A8000CC201 /* QSRankModel.swift */; }; B2B4BC0F1E7EA4AB000CC201 /* RankingViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2B4BA3C1E7EA4A8000CC201 /* RankingViewCell.swift */; }; B2B4BC101E7EA4AB000CC201 /* TopDetailCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2B4BA3D1E7EA4A8000CC201 /* TopDetailCell.swift */; }; B2B4BC111E7EA4AB000CC201 /* TopDetailCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = B2B4BA3E1E7EA4A8000CC201 /* TopDetailCell.xib */; }; B2B4BC121E7EA4AB000CC201 /* SearchDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2B4BA411E7EA4A8000CC201 /* SearchDetailViewController.swift */; }; B2B4BC141E7EA4AB000CC201 /* SearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2B4BA451E7EA4A8000CC201 /* SearchView.swift */; }; B2B4BC181E7EA4AB000CC201 /* ThemeTopicModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2B4BA4C1E7EA4A8000CC201 /* ThemeTopicModel.swift */; }; B2B4BC191E7EA4AB000CC201 /* TopicDetailHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2B4BA4D1E7EA4A8000CC201 /* TopicDetailHeader.swift */; }; B2B4BC1A1E7EA4AB000CC201 /* TopicDetailModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2B4BA4E1E7EA4A8000CC201 /* TopicDetailModel.swift */; }; B2B4BC1C1E7EA4AB000CC201 /* ThemeTopicCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2B4BA511E7EA4A8000CC201 /* ThemeTopicCell.swift */; }; B2B4BC1D1E7EA4AB000CC201 /* ThemeTopicCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = B2B4BA521E7EA4A8000CC201 /* ThemeTopicCell.xib */; }; B2B4BC1E1E7EA4AB000CC201 /* TopicDetailCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2B4BA531E7EA4A8000CC201 /* TopicDetailCell.swift */; }; B2B4BC1F1E7EA4AB000CC201 /* TopicDetailCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = B2B4BA541E7EA4A8000CC201 /* TopicDetailCell.xib */; }; B2B4BC201E7EA4AB000CC201 /* TopicDetailHeaderCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2B4BA551E7EA4A8000CC201 /* TopicDetailHeaderCell.swift */; }; B2B4BC211E7EA4AB000CC201 /* TopicDetailHeaderCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = B2B4BA561E7EA4A8000CC201 /* TopicDetailHeaderCell.xib */; }; B2B4BC221E7EA4AB000CC201 /* DynamicViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2B4BA591E7EA4A8000CC201 /* DynamicViewController.swift */; }; B2B4BC231E7EA4AB000CC201 /* LeftViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2B4BA5A1E7EA4A8000CC201 /* LeftViewController.swift */; }; B2B4BC241E7EA4AB000CC201 /* RightViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2B4BA5B1E7EA4A8000CC201 /* RightViewController.swift */; }; B2B4BC251E7EA4AB000CC201 /* RootViewController+FetchData.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2B4BA5C1E7EA4A8000CC201 /* RootViewController+FetchData.swift */; }; B2B4BC261E7EA4AB000CC201 /* RootViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2B4BA5D1E7EA4A8000CC201 /* RootViewController.swift */; }; B2B4BC271E7EA4AB000CC201 /* BarButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2B4BA601E7EA4A8000CC201 /* BarButton.swift */; }; B2B4BC281E7EA4AB000CC201 /* RightTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2B4BA611E7EA4A8000CC201 /* RightTableViewCell.swift */; }; B2B4BC291E7EA4AB000CC201 /* RootNavigationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2B4BA621E7EA4A8000CC201 /* RootNavigationView.swift */; }; B2B4BC2A1E7EA4AB000CC201 /* SegMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2B4BA631E7EA4A8000CC201 /* SegMenu.swift */; }; B2B4BC2B1E7EA4AB000CC201 /* SwipableCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2B4BA641E7EA4A8000CC201 /* SwipableCell.swift */; }; B2B4BD091E7EA4AC000CC201 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2B4BBDC1E7EA4AB000CC201 /* ViewController.swift */; }; B2B4BD771E7ECEE0000CC201 /* RootViewController+Subviews.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2B4BD761E7ECEE0000CC201 /* RootViewController+Subviews.swift */; }; B2B4BD7A1E7EE2B1000CC201 /* QSNetworkManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2B4BD791E7EE2B1000CC201 /* QSNetworkManager.swift */; }; B2B8A21722C70C07005527C8 /* mj_refresh_loading@2x.gif in Resources */ = {isa = PBXBuildFile; fileRef = B2B8A21622C70C07005527C8 /* mj_refresh_loading@2x.gif */; }; B2B8A21922C71641005527C8 /* mjRefreshHeadTitle.plist in Resources */ = {isa = PBXBuildFile; fileRef = B2B8A21822C71641005527C8 /* mjRefreshHeadTitle.plist */; }; B2B8A21B22C71A23005527C8 /* ZSBookShelfHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2B8A21A22C71A23005527C8 /* ZSBookShelfHeaderView.swift */; }; B2BB5BAF1D8BDF8E00379217 /* zhuishushenqiTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2BB5BAE1D8BDF8E00379217 /* zhuishushenqiTests.swift */; }; B2BB5BBA1D8BDF8E00379217 /* zhuishushenqiUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2BB5BB91D8BDF8E00379217 /* zhuishushenqiUITests.swift */; }; B2BCE0C42306530A00E52903 /* ZSForumPageTitleHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2BCE0C32306530A00E52903 /* ZSForumPageTitleHeaderView.swift */; }; B2C0DF1C20D4CB350057DC84 /* QSTextRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C0DEAD20D4CB340057DC84 /* QSTextRouter.swift */; }; B2C0DF1D20D4CB350057DC84 /* QSMoreSettingController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C0DEAE20D4CB340057DC84 /* QSMoreSettingController.swift */; }; B2C0DF1E20D4CB350057DC84 /* QSReaderViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C0DEAF20D4CB340057DC84 /* QSReaderViewController.swift */; }; B2C0DF1F20D4CB350057DC84 /* QSReaderBackgroundViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C0DEB020D4CB340057DC84 /* QSReaderBackgroundViewController.swift */; }; B2C0DF2020D4CB350057DC84 /* QSTextReaderController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C0DEB120D4CB340057DC84 /* QSTextReaderController.swift */; }; B2C0DF2120D4CB350057DC84 /* ZSReaderViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C0DEB220D4CB340057DC84 /* ZSReaderViewModel.swift */; }; B2C0DF2220D4CB350057DC84 /* QSTextProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C0DEB320D4CB340057DC84 /* QSTextProtocols.swift */; }; B2C0DF2320D4CB350057DC84 /* ZSReaderWebService.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C0DEB420D4CB340057DC84 /* ZSReaderWebService.swift */; }; B2C0DF2420D4CB350057DC84 /* TXTReaderViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C0DEB720D4CB340057DC84 /* TXTReaderViewController.swift */; }; B2C0DF2520D4CB350057DC84 /* PageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C0DEB920D4CB340057DC84 /* PageViewController.swift */; }; B2C0DF2620D4CB350057DC84 /* QSInterestedViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C0DEBC20D4CB340057DC84 /* QSInterestedViewController.swift */; }; B2C0DF2720D4CB350057DC84 /* UpdateInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C0DEBE20D4CB340057DC84 /* UpdateInfo.swift */; }; B2C0DF2820D4CB350057DC84 /* QSReaderParse.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C0DEBF20D4CB340057DC84 /* QSReaderParse.swift */; }; B2C0DF2920D4CB350057DC84 /* BookCommentDetail.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C0DEC020D4CB340057DC84 /* BookCommentDetail.swift */; }; B2C0DF2A20D4CB350057DC84 /* BookComment.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C0DEC120D4CB340057DC84 /* BookComment.swift */; }; B2C0DF2B20D4CB350057DC84 /* QSHotComment.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C0DEC220D4CB340057DC84 /* QSHotComment.swift */; }; B2C0DF2C20D4CB350057DC84 /* BookShelfInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C0DEC320D4CB340057DC84 /* BookShelfInfo.swift */; }; B2C0DF2D20D4CB350057DC84 /* QSRecord.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C0DEC420D4CB340057DC84 /* QSRecord.swift */; }; B2C0DF2E20D4CB350057DC84 /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C0DEC520D4CB340057DC84 /* User.swift */; }; B2C0DF2F20D4CB350057DC84 /* QSReaderSetting.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C0DEC620D4CB340057DC84 /* QSReaderSetting.swift */; }; B2C0DF3020D4CB350057DC84 /* QSBookList.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C0DEC720D4CB340057DC84 /* QSBookList.swift */; }; B2C0DF3120D4CB350057DC84 /* Book.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C0DEC820D4CB340057DC84 /* Book.swift */; }; B2C0DF3220D4CB350057DC84 /* QSBook.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C0DEC920D4CB340057DC84 /* QSBook.swift */; }; B2C0DF3320D4CB350057DC84 /* QSReaderViewFlowLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C0DECA20D4CB340057DC84 /* QSReaderViewFlowLayout.swift */; }; B2C0DF3420D4CB350057DC84 /* Chapters.json in Resources */ = {isa = PBXBuildFile; fileRef = B2C0DECB20D4CB340057DC84 /* Chapters.json */; }; B2C0DF3520D4CB350057DC84 /* QSChapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C0DECC20D4CB340057DC84 /* QSChapter.swift */; }; B2C0DF3620D4CB350057DC84 /* ResourceModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C0DECD20D4CB340057DC84 /* ResourceModel.swift */; }; B2C0DF3720D4CB350057DC84 /* PageInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C0DECE20D4CB340057DC84 /* PageInfo.swift */; }; B2C0DF3820D4CB350057DC84 /* BookDetail.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C0DECF20D4CB340057DC84 /* BookDetail.swift */; }; B2C0DF3920D4CB350057DC84 /* BookShelf.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C0DED020D4CB340057DC84 /* BookShelf.swift */; }; B2C0DF3A20D4CB350057DC84 /* QSRecomment.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C0DED120D4CB340057DC84 /* QSRecomment.swift */; }; B2C0DF3B20D4CB350057DC84 /* ChapterInfo.json in Resources */ = {isa = PBXBuildFile; fileRef = B2C0DED220D4CB340057DC84 /* ChapterInfo.json */; }; B2C0DF3C20D4CB350057DC84 /* QSPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C0DED320D4CB340057DC84 /* QSPage.swift */; }; B2C0DF3D20D4CB350057DC84 /* ChapterInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C0DED420D4CB340057DC84 /* ChapterInfo.swift */; }; B2C0DF3E20D4CB350057DC84 /* QSBookDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C0DED520D4CB340057DC84 /* QSBookDetailViewController.swift */; }; B2C0DF3F20D4CB350057DC84 /* QSBookDetailProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C0DED820D4CB340057DC84 /* QSBookDetailProtocols.swift */; }; B2C0DF4020D4CB350057DC84 /* QSBookDetailInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C0DEDA20D4CB340057DC84 /* QSBookDetailInteractor.swift */; }; B2C0DF4120D4CB350057DC84 /* ToolBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C0DEDC20D4CB340057DC84 /* ToolBar.swift */; }; B2C0DF4220D4CB350057DC84 /* QSBookListViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = B2C0DEDD20D4CB340057DC84 /* QSBookListViewCell.xib */; }; B2C0DF4320D4CB350057DC84 /* .DS_Store~HEAD in Resources */ = {isa = PBXBuildFile; fileRef = B2C0DEDE20D4CB340057DC84 /* .DS_Store~HEAD */; }; B2C0DF4420D4CB350057DC84 /* QSBatteryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C0DEDF20D4CB340057DC84 /* QSBatteryView.swift */; }; B2C0DF4520D4CB350057DC84 /* ChangeSourceCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = B2C0DEE020D4CB340057DC84 /* ChangeSourceCell.xib */; }; B2C0DF4620D4CB350057DC84 /* UserfulCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = B2C0DEE120D4CB340057DC84 /* UserfulCell.xib */; }; B2C0DF4720D4CB350057DC84 /* ProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C0DEE220D4CB340057DC84 /* ProgressView.swift */; }; B2C0DF4820D4CB350057DC84 /* BookCommentViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C0DEE320D4CB340057DC84 /* BookCommentViewCell.swift */; }; B2C0DF4920D4CB350057DC84 /* QSBookDetailRateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C0DEE420D4CB340057DC84 /* QSBookDetailRateView.swift */; }; B2C0DF4A20D4CB350057DC84 /* QSRecommendCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C0DEE520D4CB340057DC84 /* QSRecommendCell.swift */; }; B2C0DF4B20D4CB350057DC84 /* BookCommentViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = B2C0DEE620D4CB340057DC84 /* BookCommentViewCell.xib */; }; B2C0DF4C20D4CB350057DC84 /* BookCommentCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = B2C0DEE720D4CB340057DC84 /* BookCommentCell.xib */; }; B2C0DF4D20D4CB350057DC84 /* UserfulCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C0DEE820D4CB340057DC84 /* UserfulCell.swift */; }; B2C0DF4E20D4CB350057DC84 /* QSDiscussCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C0DEE920D4CB340057DC84 /* QSDiscussCell.swift */; }; B2C0DF5120D4CB350057DC84 /* ChangeSourceCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C0DEEC20D4CB340057DC84 /* ChangeSourceCell.swift */; }; B2C0DF5220D4CB350057DC84 /* CategoryButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C0DEED20D4CB340057DC84 /* CategoryButton.swift */; }; B2C0DF5320D4CB350057DC84 /* QSRecommendCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = B2C0DEEE20D4CB340057DC84 /* QSRecommendCell.xib */; }; B2C0DF5420D4CB350057DC84 /* QSBookDetailTagsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C0DEEF20D4CB340057DC84 /* QSBookDetailTagsView.swift */; }; B2C0DF5520D4CB350057DC84 /* QSDiscussCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = B2C0DEF020D4CB340057DC84 /* QSDiscussCell.xib */; }; B2C0DF5620D4CB350057DC84 /* HotCommentCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C0DEF120D4CB340057DC84 /* HotCommentCell.swift */; }; B2C0DF5720D4CB350057DC84 /* CategoryTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C0DEF220D4CB340057DC84 /* CategoryTableViewCell.swift */; }; B2C0DF5820D4CB350057DC84 /* HotCommentCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = B2C0DEF320D4CB340057DC84 /* HotCommentCell.xib */; }; B2C0DF5920D4CB350057DC84 /* BookDetailHeader.xib in Resources */ = {isa = PBXBuildFile; fileRef = B2C0DEF420D4CB340057DC84 /* BookDetailHeader.xib */; }; B2C0DF5A20D4CB350057DC84 /* BookCommentCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C0DEF520D4CB340057DC84 /* BookCommentCell.swift */; }; B2C0DF5B20D4CB350057DC84 /* BookDetailHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C0DEF620D4CB340057DC84 /* BookDetailHeader.swift */; }; B2C0DF5C20D4CB350057DC84 /* CategoryTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = B2C0DEF720D4CB340057DC84 /* CategoryTableViewCell.xib */; }; B2C0DF5D20D4CB350057DC84 /* QSBookDetailContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C0DEF820D4CB340057DC84 /* QSBookDetailContentView.swift */; }; B2C0DF5E20D4CB350057DC84 /* QSBookListViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C0DEF920D4CB340057DC84 /* QSBookListViewCell.swift */; }; B2C0DF5F20D4CB350057DC84 /* PageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C0DEFA20D4CB340057DC84 /* PageView.swift */; }; B2C0DF6020D4CB350057DC84 /* QSBookDetailRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C0DEFB20D4CB340057DC84 /* QSBookDetailRouter.swift */; }; B2C0DF6120D4CB350057DC84 /* BookDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C0DEFC20D4CB340057DC84 /* BookDetailViewController.swift */; }; B2C0DF6220D4CB350057DC84 /* QSBookDetailPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C0DEFD20D4CB340057DC84 /* QSBookDetailPresenter.swift */; }; B2C0DF6320D4CB350057DC84 /* ChangeSourceViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C0DF0020D4CB340057DC84 /* ChangeSourceViewController.swift */; }; B2C0DF6420D4CB350057DC84 /* QSCategoryProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C0DF0120D4CB340057DC84 /* QSCategoryProtocols.swift */; }; B2C0DF6520D4CB350057DC84 /* CategoryController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C0DF0220D4CB340057DC84 /* CategoryController.swift */; }; B2C0DF6620D4CB350057DC84 /* QSCategoryPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C0DF0320D4CB340057DC84 /* QSCategoryPresenter.swift */; }; B2C0DF6720D4CB350057DC84 /* QSCategoryRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C0DF0420D4CB340057DC84 /* QSCategoryRouter.swift */; }; B2C0DF6820D4CB350057DC84 /* QSCategoryReaderViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C0DF0620D4CB340057DC84 /* QSCategoryReaderViewController.swift */; }; B2C0DF6920D4CB350057DC84 /* CategoryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C0DF0820D4CB340057DC84 /* CategoryViewController.swift */; }; B2C0DF6A20D4CB350057DC84 /* QSCategoryInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C0DF0A20D4CB340057DC84 /* QSCategoryInteractor.swift */; }; B2C0DF6B20D4CB350057DC84 /* QSCommunityRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C0DF0C20D4CB340057DC84 /* QSCommunityRouter.swift */; }; B2C0DF6C20D4CB350057DC84 /* QSCommunityViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C0DF0E20D4CB340057DC84 /* QSCommunityViewController.swift */; }; B2C0DF6D20D4CB350057DC84 /* QSCommunityProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C0DF1220D4CB340057DC84 /* QSCommunityProtocols.swift */; }; B2C0DF6E20D4CB350057DC84 /* QSCommunityInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C0DF1320D4CB340057DC84 /* QSCommunityInteractor.swift */; }; B2C0DF6F20D4CB350057DC84 /* QSCommunityPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C0DF1420D4CB340057DC84 /* QSCommunityPresenter.swift */; }; B2C0DF7720D4CB6D0057DC84 /* QSBookCommentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C0DF7020D4CB6C0057DC84 /* QSBookCommentViewController.swift */; }; B2C0DF7820D4CB6D0057DC84 /* QSBookCommentPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C0DF7120D4CB6C0057DC84 /* QSBookCommentPresenter.swift */; }; B2C0DF7920D4CB6D0057DC84 /* BookCommentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C0DF7220D4CB6C0057DC84 /* BookCommentViewController.swift */; }; B2C0DF7A20D4CB6D0057DC84 /* TXTReader.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B2C0DF7320D4CB6C0057DC84 /* TXTReader.storyboard */; }; B2C0DF7B20D4CB6D0057DC84 /* QSBookCommentInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C0DF7420D4CB6C0057DC84 /* QSBookCommentInteractor.swift */; }; B2C0DF7C20D4CB6D0057DC84 /* QSBookCommentRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C0DF7520D4CB6C0057DC84 /* QSBookCommentRouter.swift */; }; B2C0DF7D20D4CB6D0057DC84 /* QSBookCommentProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C0DF7620D4CB6D0057DC84 /* QSBookCommentProtocols.swift */; }; B2C0DF7F20D4CC480057DC84 /* ZSBookCommentService.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C0DF7E20D4CC480057DC84 /* ZSBookCommentService.swift */; }; B2C0DF8120D4CC5A0057DC84 /* ZSBaseService.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C0DF8020D4CC5A0057DC84 /* ZSBaseService.swift */; }; B2C0DF8520D7C1D30057DC84 /* ZSHottwitterViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C0DF8420D7C1D30057DC84 /* ZSHottwitterViewController.swift */; }; B2C63A3223C1A44B0083C987 /* ZSReaderDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C63A3123C1A44B0083C987 /* ZSReaderDownloader.swift */; }; B2C63A3423C1A57A0083C987 /* ZSBookMemoryCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C63A3323C1A57A0083C987 /* ZSBookMemoryCache.swift */; }; B2C63A3623C1A6C80083C987 /* ZSBookChapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C63A3523C1A6C80083C987 /* ZSBookChapter.swift */; }; B2C63A3823C1ABA60083C987 /* ZSBookDiskCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C63A3723C1ABA60083C987 /* ZSBookDiskCache.swift */; }; B2C63A3B23C1ADC50083C987 /* ZSBookCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C63A3A23C1ADC50083C987 /* ZSBookCache.swift */; }; B2CA80F021797EA2007A3EE7 /* ZSThirdLogin.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2CA80EF21797EA2007A3EE7 /* ZSThirdLogin.swift */; }; B2CA942B1EB331DC00D60BF2 /* UIColor+Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2CA942A1EB331DC00D60BF2 /* UIColor+Theme.swift */; }; B2E072781F3AD99500C63347 /* Reader.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2E072771F3AD99500C63347 /* Reader.swift */; }; B2EE7CF41F3BFC7B00BE997C /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = B2EE7CF61F3BFC7B00BE997C /* Localizable.strings */; }; B2F778BC1EAF31DB004B4362 /* UITableView+FINAutomaticHeightCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2F778BB1EAF31DB004B4362 /* UITableView+FINAutomaticHeightCell.swift */; }; B2FB91FD1EB1EEDF000C990A /* Reachability.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2FB91FC1EB1EEDF000C990A /* Reachability.swift */; }; B2FB92111EB1EEDF000C990A /* CoreTelephony.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B2FB92101EB1EEDF000C990A /* CoreTelephony.framework */; }; B2FCAA8B1E9C82CE0064837C /* QSSearchItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2FCAA8A1E9C82CE0064837C /* QSSearchItem.swift */; }; B2FCAA8D1E9C85600064837C /* QSHistoryHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2FCAA8C1E9C85600064837C /* QSHistoryHeaderView.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ B2BB5BAB1D8BDF8E00379217 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = B2BB5B8E1D8BDF8E00379217 /* Project object */; proxyType = 1; remoteGlobalIDString = B2BB5B951D8BDF8E00379217; remoteInfo = zhuishushenqi; }; B2BB5BB61D8BDF8E00379217 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = B2BB5B8E1D8BDF8E00379217 /* Project object */; proxyType = 1; remoteGlobalIDString = B2BB5B951D8BDF8E00379217; remoteInfo = zhuishushenqi; }; /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ 3D05BCB323C592E2001EAB2A /* ZSReaderToolbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSReaderToolbar.swift; sourceTree = "<group>"; }; 3D05BCB523C592FE001EAB2A /* ZSReaderTopbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSReaderTopbar.swift; sourceTree = "<group>"; }; 3D05BCB723C59314001EAB2A /* ZSReaderBottomBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSReaderBottomBar.swift; sourceTree = "<group>"; }; 3D05BCB923C5ACE8001EAB2A /* ZSSearchInfoBottomView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSSearchInfoBottomView.swift; sourceTree = "<group>"; }; 3D05BCBD23C5D424001EAB2A /* AikanParserModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AikanParserModel.swift; sourceTree = "<group>"; }; 3D05BCBF23C5EAFE001EAB2A /* ZSShelfManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSShelfManager.swift; sourceTree = "<group>"; }; 3D08F241212C2DC6007B3D19 /* ZSDiscussViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSDiscussViewModel.swift; sourceTree = "<group>"; }; 3D08F243212C2DFC007B3D19 /* ZSDiscussWebService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSDiscussWebService.swift; sourceTree = "<group>"; }; 3D096BF622CF76A70005C9EA /* ZSRefreshFooter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSRefreshFooter.swift; sourceTree = "<group>"; }; 3D0D709C23C88B30006902E8 /* HtmlParserModelData_edit.dat */ = {isa = PBXFileReference; lastKnownFileType = file.bplist; path = HtmlParserModelData_edit.dat; sourceTree = "<group>"; }; 3D1330CA23C6FA9200D81EF4 /* ZSReaderTouchArea.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSReaderTouchArea.swift; sourceTree = "<group>"; }; 3D1330CC23C7239000D81EF4 /* ZSReaderCatalogViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSReaderCatalogViewController.swift; sourceTree = "<group>"; }; 3D17A02622D47364001FAC0C /* ZSReaderController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSReaderController.swift; sourceTree = "<group>"; }; 3D17A02822D47426001FAC0C /* ZSPageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSPageViewController.swift; sourceTree = "<group>"; }; 3D17A02A22D474A7001FAC0C /* ZSHorizonalViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSHorizonalViewController.swift; sourceTree = "<group>"; }; 3D1E315423D1B30700A2359E /* ZSToast.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSToast.swift; sourceTree = "<group>"; }; 3D1F67A0217DBA1B000FB968 /* ZSLoginViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSLoginViewController.swift; sourceTree = "<group>"; }; 3D1F67A2217DBA94000FB968 /* ZSLoginView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSLoginView.swift; sourceTree = "<group>"; }; 3D1F67A4217DCE9B000FB968 /* ZSLeftViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSLeftViewCell.swift; sourceTree = "<group>"; }; 3D1FFDED23C89A4E0017ECE7 /* ZSShelfTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSShelfTableViewCell.swift; sourceTree = "<group>"; }; 3D1FFDEF23C9A68F0017ECE7 /* ZSReaderBottomBigBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSReaderBottomBigBar.swift; sourceTree = "<group>"; }; 3D1FFDF123C9A9600017ECE7 /* ZSReaderThemeSelectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSReaderThemeSelectionView.swift; sourceTree = "<group>"; }; 3D1FFDF323CC52D70017ECE7 /* ThemeManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThemeManager.swift; sourceTree = "<group>"; }; 3D1FFDF523CC52E10017ECE7 /* Color.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Color.swift; sourceTree = "<group>"; }; 3D1FFDF723CC586B0017ECE7 /* ReaderNavigationBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReaderNavigationBar.swift; sourceTree = "<group>"; }; 3D1FFDF923CC5B880017ECE7 /* ReaderBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReaderBar.swift; sourceTree = "<group>"; }; 3D1FFEEF249B523300832576 /* ZSSyncStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSSyncStorage.swift; sourceTree = "<group>"; }; 3D1FFEF1249B562500832576 /* ZSShelfStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSShelfStorage.swift; sourceTree = "<group>"; }; 3D2006D0216F36BE00C326B4 /* ZSReaderBaseViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSReaderBaseViewController.swift; sourceTree = "<group>"; }; 3D2006D2216F443A00C326B4 /* ZSSpeakerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSSpeakerViewController.swift; sourceTree = "<group>"; }; 3D2006D4216F478C00C326B4 /* ZSSpeakerCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSSpeakerCell.swift; sourceTree = "<group>"; }; 3D2330DD21F5F04700EFE522 /* ZSBookDownloader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSBookDownloader.swift; sourceTree = "<group>"; }; 3D2330DF21F6F05A00EFE522 /* ZSAppInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSAppInfo.swift; sourceTree = "<group>"; }; 3D2330E221F6F39D00EFE522 /* FloatExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatExtensions.swift; sourceTree = "<group>"; }; 3D2330E421F9CC6F00EFE522 /* IntExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntExtensions.swift; sourceTree = "<group>"; }; 3D252CDF219175FC0051B60D /* ZSDatabase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSDatabase.swift; sourceTree = "<group>"; }; 3D26002524A1B61000458B58 /* libuchardet-ios.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libuchardet-ios.a"; sourceTree = "<group>"; }; 3D26002624A1B61000458B58 /* uchardet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = uchardet.h; sourceTree = "<group>"; }; 3D28402722841281009463A3 /* ZSDetailSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSDetailSection.swift; sourceTree = "<group>"; }; 3D2840292284267C009463A3 /* ZSDetailInfoCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSDetailInfoCell.swift; sourceTree = "<group>"; }; 3D29C7AA233381A600113A25 /* ZSReaderVCProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSReaderVCProtocol.swift; sourceTree = "<group>"; }; 3D29C7AC23347E6900113A25 /* ZSReaderBaseViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSReaderBaseViewModel.swift; sourceTree = "<group>"; }; 3D2B4D0220EB616D008E3E81 /* ZSReaderViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSReaderViewController.swift; sourceTree = "<group>"; }; 3D2B4D0420EB6552008E3E81 /* ZSNoneAnimationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSNoneAnimationViewController.swift; sourceTree = "<group>"; }; 3D2B4D0620EB68AC008E3E81 /* ZSReaderProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSReaderProtocol.swift; sourceTree = "<group>"; }; 3D31FFAE2216BDAD0011D275 /* ZSReaderViewModel+Bought.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ZSReaderViewModel+Bought.swift"; sourceTree = "<group>"; }; 3D31FFB02216E1850011D275 /* ZSChapterPayView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSChapterPayView.swift; sourceTree = "<group>"; }; 3D33B4D222BB6C40000C2D8D /* DispatchTime+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DispatchTime+Extension.swift"; sourceTree = "<group>"; }; 3D3AB5D1214E98AC00E9C246 /* hylst.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = hylst.ttf; sourceTree = "<group>"; }; 3D3AB5D3214E98B800E9C246 /* Redocn.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = Redocn.ttf; sourceTree = "<group>"; }; 3D3AB5D5214E993E00E9C246 /* hkppt.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = hkppt.ttf; sourceTree = "<group>"; }; 3D3AB5D9214E99CB00E9C246 /* ypt.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = ypt.ttf; sourceTree = "<group>"; }; 3D3AB5DB214E99FE00E9C246 /* fz-wbt.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "fz-wbt.ttf"; sourceTree = "<group>"; }; 3D3AB5DD214E9A8300E9C246 /* fz-kt.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "fz-kt.ttf"; sourceTree = "<group>"; }; 3D3AB5DF214E9ABD00E9C246 /* ltt.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = ltt.ttf; sourceTree = "<group>"; }; 3D3B9FFF2160C5D700B0C80E /* speaker.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = speaker.json; sourceTree = "<group>"; }; 3D3BA0032160C77E00B0C80E /* speakers.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = speakers.plist; sourceTree = "<group>"; }; 3D448E5622C501C40099B6E8 /* ZSBookShelfViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ZSBookShelfViewController.swift; sourceTree = "<group>"; }; 3D448E5722C501C40099B6E8 /* NavigationBar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationBar.swift; sourceTree = "<group>"; }; 3D448E5A22C501CC0099B6E8 /* ZSBookStoreViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ZSBookStoreViewController.swift; sourceTree = "<group>"; }; 3D448E5C22C501DA0099B6E8 /* ZSCommunityViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ZSCommunityViewController.swift; sourceTree = "<group>"; }; 3D448E5E22C501E10099B6E8 /* ZSDiscoverViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ZSDiscoverViewController.swift; sourceTree = "<group>"; }; 3D448E6022C501E90099B6E8 /* ZSMineViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ZSMineViewController.swift; sourceTree = "<group>"; }; 3D5280DF20C8D2BB00252D67 /* ZSForumViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSForumViewController.swift; sourceTree = "<group>"; }; 3D55F4D822D47B3800AE0E2B /* ZSVerticalViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSVerticalViewController.swift; sourceTree = "<group>"; }; 3D55F4DA22D47CAD00AE0E2B /* ZSReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSReader.swift; sourceTree = "<group>"; }; 3D55F4DC22D47CEF00AE0E2B /* ZSNormalViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSNormalViewController.swift; sourceTree = "<group>"; }; 3D5671FB2174CADD0049B2FF /* ZSBookCTLayoutModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSBookCTLayoutModel.swift; sourceTree = "<group>"; }; 3D58635E20CE1C96002AD3CC /* ZSProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSProtocol.swift; sourceTree = "<group>"; }; 3D62EE5822604768002FFA39 /* ZSVoicePlayerCatelogView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSVoicePlayerCatelogView.swift; sourceTree = "<group>"; }; 3D63E16A235EF9550015B7D3 /* ZSSearchBookViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSSearchBookViewController.swift; sourceTree = "<group>"; }; 3D63E16C235EF9B80015B7D3 /* ZSSearchBookView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSSearchBookView.swift; sourceTree = "<group>"; }; 3D63E16E235EF9E60015B7D3 /* ZSSearchResultView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSSearchResultView.swift; sourceTree = "<group>"; }; 3D63E170235EFAE70015B7D3 /* ZSTopSearchBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSTopSearchBar.swift; sourceTree = "<group>"; }; 3D63E172235F021F0015B7D3 /* ZSHeaderSearchCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSHeaderSearchCell.swift; sourceTree = "<group>"; }; 3D63E174235F02670015B7D3 /* ZSSearchHotView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSSearchHotView.swift; sourceTree = "<group>"; }; 3D63E176235F03C40015B7D3 /* ZSSearchBookViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSSearchBookViewModel.swift; sourceTree = "<group>"; }; 3D63E178235F14BF0015B7D3 /* ZSSearchHotwords.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSSearchHotwords.swift; sourceTree = "<group>"; }; 3D63E17A235F18F40015B7D3 /* ZSHotWord.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSHotWord.swift; sourceTree = "<group>"; }; 3D63E18E23603CD80015B7D3 /* ZSSearchRecommendView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSSearchRecommendView.swift; sourceTree = "<group>"; }; 3D63E190236043250015B7D3 /* ZSHeaderSearch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSHeaderSearch.swift; sourceTree = "<group>"; }; 3D64030523BF5E8500D4B9EB /* ZSSearchHistory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSSearchHistory.swift; sourceTree = "<group>"; }; 3D65504A219577DB0064BA0C /* ZSMultiplePayView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ZSMultiplePayView.swift; sourceTree = "<group>"; }; 3D65504C21957B410064BA0C /* ZSChapterSelectView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSChapterSelectView.swift; sourceTree = "<group>"; }; 3D6CCABB23794D2C004D46CE /* ZSSetting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSSetting.swift; sourceTree = "<group>"; }; 3D6CCABD237952B3004D46CE /* ZSAddSourceViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSAddSourceViewController.swift; sourceTree = "<group>"; }; 3D6CCABF2379550C004D46CE /* ZSSourcesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSSourcesViewController.swift; sourceTree = "<group>"; }; 3D6CCAC1237956FB004D46CE /* ZSSourceManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSSourceManager.swift; sourceTree = "<group>"; }; 3D6CCAC323796155004D46CE /* ZSSourceCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSSourceCell.swift; sourceTree = "<group>"; }; 3D82F9EA22C9DD5D00F035CB /* ZSMineNavigationBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSMineNavigationBar.swift; sourceTree = "<group>"; }; 3D82F9EC22C9EFA800F035CB /* ZSDetailButtonCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSDetailButtonCell.swift; sourceTree = "<group>"; }; 3D82F9EE22CA01A100F035CB /* ZSMineMenuItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSMineMenuItem.swift; sourceTree = "<group>"; }; 3D850DFD21E61C9800F23DDB /* ZSVoucher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSVoucher.swift; sourceTree = "<group>"; }; 3D850DFF21E61E1800F23DDB /* ZSVoucherViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSVoucherViewController.swift; sourceTree = "<group>"; }; 3D865B54219B1975001294EB /* ZSChapterSelectModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSChapterSelectModel.swift; sourceTree = "<group>"; }; 3D88A7822670EBCB00C87AB5 /* ZSFloatingManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ZSFloatingManager.swift; sourceTree = "<group>"; }; 3D88A7832670EBCB00C87AB5 /* ZSMemoryFloatingView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ZSMemoryFloatingView.swift; sourceTree = "<group>"; }; 3D88A7842670EBCB00C87AB5 /* ZSFloatingWindow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ZSFloatingWindow.swift; sourceTree = "<group>"; }; 3D88A7852670EBCB00C87AB5 /* ZSFloatingViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ZSFloatingViewController.swift; sourceTree = "<group>"; }; 3D88A7862670EBCB00C87AB5 /* ZSFloatingView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ZSFloatingView.swift; sourceTree = "<group>"; }; 3D9325E020B2BCFB0049CDBF /* ZSRootViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSRootViewController.swift; sourceTree = "<group>"; }; 3D9325E520B2C44A0049CDBF /* ZSBaseTableViewManger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSBaseTableViewManger.swift; sourceTree = "<group>"; }; 3D9325E720B2C66A0049CDBF /* UITableViewCell+ZSExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UITableViewCell+ZSExtension.swift"; sourceTree = "<group>"; }; 3D9325EB20B2C9120049CDBF /* ZSHomeViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSHomeViewModel.swift; sourceTree = "<group>"; }; 3D9325ED20B2E07F0049CDBF /* Value.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Value.swift; sourceTree = "<group>"; }; 3D968CF52133917900DF3279 /* ZSCellAdapterProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSCellAdapterProtocol.swift; sourceTree = "<group>"; }; 3D968CF7213392F500DF3279 /* ZSBaseCellAdapter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSBaseCellAdapter.swift; sourceTree = "<group>"; }; 3D968CF9213396B700DF3279 /* ZSBaseSectionAdapter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSBaseSectionAdapter.swift; sourceTree = "<group>"; }; 3D968CFB2135391400DF3279 /* ZSBookCTViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSBookCTViewModel.swift; sourceTree = "<group>"; }; 3D968CFD2135398600DF3279 /* ZSBookCTService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSBookCTService.swift; sourceTree = "<group>"; }; 3D998936223FDFB900EA33D5 /* ZSSegmentBaseViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSSegmentBaseViewModel.swift; sourceTree = "<group>"; }; 3D998938223FEA0400EA33D5 /* ZSCatelogViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSCatelogViewModel.swift; sourceTree = "<group>"; }; 3D99893A223FEC5900EA33D5 /* ZSCatelogModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSCatelogModel.swift; sourceTree = "<group>"; }; 3D9D857B20CA439D003FEDFE /* ZSShelfMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSShelfMessage.swift; sourceTree = "<group>"; }; 3DABFC2A22B9183D00ECB5E3 /* ZSTabBarController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSTabBarController.swift; sourceTree = "<group>"; }; 3DAC238D21E1D66A00E77891 /* ZSWebStoreViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSWebStoreViewController.swift; sourceTree = "<group>"; }; 3DAC238F21E1D73300E77891 /* ZSWebViewControllerDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSWebViewControllerDelegate.swift; sourceTree = "<group>"; }; 3DAC239121E1DE7400E77891 /* ZSWebItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSWebItem.swift; sourceTree = "<group>"; }; 3DAC23CD21E32DA500E77891 /* ZSYJSchemeHandle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSYJSchemeHandle.swift; sourceTree = "<group>"; }; 3DB1FDB02334A8BE00CAC8C0 /* ZSReaderCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSReaderCache.swift; sourceTree = "<group>"; }; 3DB24B5C2240F5F100DCE8B2 /* ZSCatelogCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSCatelogCell.swift; sourceTree = "<group>"; }; 3DB24B612243CBD200DCE8B2 /* ZSCatelogHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSCatelogHeaderView.swift; sourceTree = "<group>"; }; 3DBC088E22CB02030004B3F4 /* ZSCommunityNavigationBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSCommunityNavigationBar.swift; sourceTree = "<group>"; }; 3DBC089022CB37C00004B3F4 /* ZSDiscoverNavigationBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSDiscoverNavigationBar.swift; sourceTree = "<group>"; }; 3DBC089222CB54DD0004B3F4 /* ZSCommunityCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSCommunityCell.swift; sourceTree = "<group>"; }; 3DBC089422CB56C60004B3F4 /* ZSInsertedBookScoreView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSInsertedBookScoreView.swift; sourceTree = "<group>"; }; 3DBC089622CB78DB0004B3F4 /* ZSCommunityViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSCommunityViewModel.swift; sourceTree = "<group>"; }; 3DBC089822CB79830004B3F4 /* ZSAPI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ZSAPI.framework; path = Carthage/Build/iOS/ZSAPI.framework; sourceTree = "<group>"; }; 3DBC089E22CB80DB0004B3F4 /* UIButton+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIButton+Extension.swift"; sourceTree = "<group>"; }; 3DC93D54224A6264004FC392 /* ZSVoiceAlbum.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSVoiceAlbum.swift; sourceTree = "<group>"; }; 3DD08406215698E5008E3B4A /* iflyMSC.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = iflyMSC.framework; sourceTree = "<group>"; }; 3DD0840A21569902008E3B4A /* Speaker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Speaker.swift; sourceTree = "<group>"; }; 3DD0841D21569A54008E3B4A /* TTSConfig.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TTSConfig.swift; sourceTree = "<group>"; }; 3DD0841F21569A65008E3B4A /* TTSResource */ = {isa = PBXFileReference; lastKnownFileType = folder; path = TTSResource; sourceTree = "<group>"; }; 3DD0842121569A74008E3B4A /* VoiceBook.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VoiceBook.swift; sourceTree = "<group>"; }; 3DD0842321569A95008E3B4A /* AKPickerView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AKPickerView.m; sourceTree = "<group>"; }; 3DD0842421569A95008E3B4A /* AKPickerView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AKPickerView.h; sourceTree = "<group>"; }; 3DD0842A215750C6008E3B4A /* Network.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Network.swift; sourceTree = "<group>"; }; 3DD0842C2157604D008E3B4A /* ZSSpeechView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSSpeechView.swift; sourceTree = "<group>"; }; 3DD0842F2157A14B008E3B4A /* PcmPlayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PcmPlayer.h; sourceTree = "<group>"; }; 3DD084302157A14B008E3B4A /* PcmPlayerDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PcmPlayerDelegate.h; sourceTree = "<group>"; }; 3DD084312157A14B008E3B4A /* PcmPlayer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PcmPlayer.m; sourceTree = "<group>"; }; 3DD084332157C0BA008E3B4A /* libstdc++.6.0.9.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = "libstdc++.6.0.9.tbd"; path = "usr/lib/libstdc++.6.0.9.tbd"; sourceTree = SDKROOT; }; 3DD084352157C0ED008E3B4A /* libc++.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = "libc++.tbd"; path = "usr/lib/libc++.tbd"; sourceTree = SDKROOT; }; 3DD084372157C101008E3B4A /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = System/Library/Frameworks/AudioToolbox.framework; sourceTree = SDKROOT; }; 3DD084392157C124008E3B4A /* AddressBook.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AddressBook.framework; path = System/Library/Frameworks/AddressBook.framework; sourceTree = SDKROOT; }; 3DD0843B2157C135008E3B4A /* Contacts.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Contacts.framework; path = System/Library/Frameworks/Contacts.framework; sourceTree = SDKROOT; }; 3DDB6C952130F4BC00E8698D /* CoreTextUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CoreTextUtils.m; sourceTree = "<group>"; }; 3DDB6C962130F4BC00E8698D /* CTFrameParser.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CTFrameParser.m; sourceTree = "<group>"; }; 3DDB6C972130F4BC00E8698D /* CTFrameParserConfig.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CTFrameParserConfig.h; sourceTree = "<group>"; }; 3DDB6C982130F4BC00E8698D /* CoreTextImageData.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CoreTextImageData.m; sourceTree = "<group>"; }; 3DDB6C992130F4BC00E8698D /* CoreTextData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CoreTextData.h; sourceTree = "<group>"; }; 3DDB6C9A2130F4BC00E8698D /* CoreTextLinkData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CoreTextLinkData.h; sourceTree = "<group>"; }; 3DDB6C9B2130F4BC00E8698D /* CTFrameParser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CTFrameParser.h; sourceTree = "<group>"; }; 3DDB6C9C2130F4BC00E8698D /* CoreTextUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CoreTextUtils.h; sourceTree = "<group>"; }; 3DDB6C9D2130F4BC00E8698D /* CoreTextImageData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CoreTextImageData.h; sourceTree = "<group>"; }; 3DDB6C9E2130F4BC00E8698D /* CTFrameParserConfig.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CTFrameParserConfig.m; sourceTree = "<group>"; }; 3DDB6C9F2130F4BC00E8698D /* CoreTextData.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CoreTextData.m; sourceTree = "<group>"; }; 3DDB6CA12130F4BC00E8698D /* CTDisplayView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CTDisplayView.m; sourceTree = "<group>"; }; 3DDB6CA22130F4BC00E8698D /* MagnifiterView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MagnifiterView.h; sourceTree = "<group>"; }; 3DDB6CA32130F4BC00E8698D /* UIView+frameAdjust.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIView+frameAdjust.m"; sourceTree = "<group>"; }; 3DDB6CA42130F4BC00E8698D /* CTDisplayView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CTDisplayView.h; sourceTree = "<group>"; }; 3DDB6CA52130F4BC00E8698D /* MagnifiterView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MagnifiterView.m; sourceTree = "<group>"; }; 3DDB6CA62130F4BC00E8698D /* UIView+frameAdjust.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIView+frameAdjust.h"; sourceTree = "<group>"; }; 3DDB6CA72130F4BC00E8698D /* CoreTextLinkData.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CoreTextLinkData.m; sourceTree = "<group>"; }; 3DDB6CB12130F56200E8698D /* CTDisplayText.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CTDisplayText.h; sourceTree = "<group>"; }; 3DDB6CB32130F63200E8698D /* RegexKitLite.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RegexKitLite.h; sourceTree = "<group>"; }; 3DDB6CB42130F63200E8698D /* RegexKitLite.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RegexKitLite.m; sourceTree = "<group>"; }; 3DDB6CB62130F73000E8698D /* libicucore.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libicucore.tbd; path = usr/lib/libicucore.tbd; sourceTree = SDKROOT; }; 3DDEEAF8214A8E3F003D12DB /* ZSFontViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSFontViewController.swift; sourceTree = "<group>"; }; 3DDEEAFA214A8EC8003D12DB /* ZSFontService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSFontService.swift; sourceTree = "<group>"; }; 3DDEEAFC214A8EE1003D12DB /* ZSFontViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSFontViewModel.swift; sourceTree = "<group>"; }; 3DDEEAFE214B5D39003D12DB /* UIFont+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIFont+Extension.swift"; sourceTree = "<group>"; }; 3DDEEB00214B5D59003D12DB /* UIFont+ZSExtension.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIFont+ZSExtension.h"; sourceTree = "<group>"; }; 3DDEEB01214B5D59003D12DB /* UIFont+ZSExtension.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIFont+ZSExtension.m"; sourceTree = "<group>"; }; 3DE393D8219D505500890488 /* ZSBoughtInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSBoughtInfo.swift; sourceTree = "<group>"; }; 3DE393DA219D514100890488 /* ZSBookBoughtViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSBookBoughtViewModel.swift; sourceTree = "<group>"; }; 3DE393E621A3B7B600890488 /* ZSReadRecord.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSReadRecord.swift; sourceTree = "<group>"; }; 3DE393E821A3B84600890488 /* BookShelf.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = BookShelf.json; sourceTree = "<group>"; }; 3DE393EA21A3EB2D00890488 /* ZSJSON.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSJSON.swift; sourceTree = "<group>"; }; 3DE3AF7E23CF061A00D74C1F /* ZSPageTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSPageTableViewCell.swift; sourceTree = "<group>"; }; 3DE3AF8023D0622C00D74C1F /* ZSShelfOperatingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSShelfOperatingView.swift; sourceTree = "<group>"; }; 3DE77D2520C8E99500A86DF0 /* ZSBaseTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSBaseTableViewController.swift; sourceTree = "<group>"; }; 3DE77D2920C90EA300A86DF0 /* ZSRootWebService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSRootWebService.swift; sourceTree = "<group>"; }; 3DECC38322452F9E00EEB146 /* ZSFilterThemeViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSFilterThemeViewModel.swift; sourceTree = "<group>"; }; 3DECC385224533EB00EEB146 /* ZSFilterThemeModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSFilterThemeModel.swift; sourceTree = "<group>"; }; 3DECC3872245379300EEB146 /* ZSFilterThemeCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSFilterThemeCell.swift; sourceTree = "<group>"; }; 3DECC38922460B6100EEB146 /* ZSVoicePlayViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSVoicePlayViewController.swift; sourceTree = "<group>"; }; 3DECC38B22460DD000EEB146 /* ZSSegmenuViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSSegmenuViewController.swift; sourceTree = "<group>"; }; 3DECC38D224611BC00EEB146 /* ZSVoicePlayerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSVoicePlayerViewController.swift; sourceTree = "<group>"; }; 3DECC38F224611EB00EEB146 /* ZSVoicePlayListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSVoicePlayListViewController.swift; sourceTree = "<group>"; }; 3DECC39122461E1900EEB146 /* ZSVoicePlayerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSVoicePlayerView.swift; sourceTree = "<group>"; }; 3DECC39322461E4900EEB146 /* ZSVoicePlayProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSVoicePlayProgressView.swift; sourceTree = "<group>"; }; 3DECC3952246298300EEB146 /* ZSVoicePlayViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSVoicePlayViewModel.swift; sourceTree = "<group>"; }; 3DECC39822464C4E00EEB146 /* libXMOpenPlatform.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libXMOpenPlatform.a; sourceTree = "<group>"; }; 3DECC39B22464C4E00EEB146 /* XMSDKDownloadManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XMSDKDownloadManager.h; sourceTree = "<group>"; }; 3DECC39D22464C4E00EEB146 /* XMAttribute.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XMAttribute.h; sourceTree = "<group>"; }; 3DECC39E22464C4E00EEB146 /* XMAlbum.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XMAlbum.h; sourceTree = "<group>"; }; 3DECC39F22464C4E00EEB146 /* XMCategory.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XMCategory.h; sourceTree = "<group>"; }; 3DECC3A022464C4E00EEB146 /* XMColdbootTag.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XMColdbootTag.h; sourceTree = "<group>"; }; 3DECC3A122464C4E00EEB146 /* XMHotTrack.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XMHotTrack.h; sourceTree = "<group>"; }; 3DECC3A222464C4E00EEB146 /* XMDimension.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XMDimension.h; sourceTree = "<group>"; }; 3DECC3A322464C4E00EEB146 /* XMColumn.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XMColumn.h; sourceTree = "<group>"; }; 3DECC3A422464C4E00EEB146 /* XMAlbumColumn.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XMAlbumColumn.h; sourceTree = "<group>"; }; 3DECC3A522464C4E00EEB146 /* XMTrack.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XMTrack.h; sourceTree = "<group>"; }; 3DECC3A622464C4E00EEB146 /* XMRadio.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XMRadio.h; sourceTree = "<group>"; }; 3DECC3A722464C4E00EEB146 /* XMTag.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XMTag.h; sourceTree = "<group>"; }; 3DECC3A822464C4E00EEB146 /* XMIndexRankItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XMIndexRankItem.h; sourceTree = "<group>"; }; 3DECC3A922464C4E00EEB146 /* XMColdbootDetail.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XMColdbootDetail.h; sourceTree = "<group>"; }; 3DECC3AA22464C4E00EEB146 /* XMRadioSchedule.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XMRadioSchedule.h; sourceTree = "<group>"; }; 3DECC3AB22464C4E00EEB146 /* XMCategoryHumanRecommend.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XMCategoryHumanRecommend.h; sourceTree = "<group>"; }; 3DECC3AC22464C4E00EEB146 /* XMSubordinatedAlbum.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XMSubordinatedAlbum.h; sourceTree = "<group>"; }; 3DECC3AD22464C4E00EEB146 /* XMTrackColumnItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XMTrackColumnItem.h; sourceTree = "<group>"; }; 3DECC3AE22464C4E00EEB146 /* XMLiveAnnouncer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XMLiveAnnouncer.h; sourceTree = "<group>"; }; 3DECC3AF22464C4E00EEB146 /* XMAnnouncer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XMAnnouncer.h; sourceTree = "<group>"; }; 3DECC3B022464C4E00EEB146 /* XMTrackDownloadStatus.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XMTrackDownloadStatus.h; sourceTree = "<group>"; }; 3DECC3B122464C4E00EEB146 /* XMErrorModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XMErrorModel.h; sourceTree = "<group>"; }; 3DECC3B222464C4E00EEB146 /* XMLastUptrack.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XMLastUptrack.h; sourceTree = "<group>"; }; 3DECC3B322464C4E00EEB146 /* XMAlbumGuessLike.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XMAlbumGuessLike.h; sourceTree = "<group>"; }; 3DECC3B422464C4E00EEB146 /* XMCacheTrack.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XMCacheTrack.h; sourceTree = "<group>"; }; 3DECC3B522464C4E00EEB146 /* XMBanner.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XMBanner.h; sourceTree = "<group>"; }; 3DECC3B622464C4E00EEB146 /* XMLiveCity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XMLiveCity.h; sourceTree = "<group>"; }; 3DECC3B722464C4E00EEB146 /* XMMetadata.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XMMetadata.h; sourceTree = "<group>"; }; 3DECC3B822464C4E00EEB146 /* XMColumnList.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XMColumnList.h; sourceTree = "<group>"; }; 3DECC3B922464C4E00EEB146 /* XMColumnDetail.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XMColumnDetail.h; sourceTree = "<group>"; }; 3DECC3BA22464C4E00EEB146 /* XMAlbumColumnItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XMAlbumColumnItem.h; sourceTree = "<group>"; }; 3DECC3BB22464C4E00EEB146 /* XMProvince.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XMProvince.h; sourceTree = "<group>"; }; 3DECC3BC22464C4E00EEB146 /* XMTrackColumn.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XMTrackColumn.h; sourceTree = "<group>"; }; 3DECC3BD22464C4E00EEB146 /* XMHotword.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XMHotword.h; sourceTree = "<group>"; }; 3DECC3BE22464C4E00EEB146 /* XMRankSectionList.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XMRankSectionList.h; sourceTree = "<group>"; }; 3DECC3BF22464C4E00EEB146 /* XMColumnEditor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XMColumnEditor.h; sourceTree = "<group>"; }; 3DECC3C022464C4E00EEB146 /* XMRelatedProgram.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XMRelatedProgram.h; sourceTree = "<group>"; }; 3DECC3C122464C4E00EEB146 /* XMAnnouncerCategory.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XMAnnouncerCategory.h; sourceTree = "<group>"; }; 3DECC3C622464C4E00EEB146 /* XMReqMgr.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XMReqMgr.h; sourceTree = "<group>"; }; 3DECC3D322464C4F00EEB146 /* XMResource.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = XMResource.bundle; sourceTree = "<group>"; }; 3DECC3D522464C4F00EEB146 /* XMLYAuthorize.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XMLYAuthorize.h; sourceTree = "<group>"; }; 3DECC3D722464C4F00EEB146 /* XMSDKPlayerDataCollector.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XMSDKPlayerDataCollector.h; sourceTree = "<group>"; }; 3DECC3D822464C4F00EEB146 /* XMADAudioPlayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XMADAudioPlayer.h; sourceTree = "<group>"; }; 3DECC3D922464C4F00EEB146 /* XMSDKPlayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XMSDKPlayer.h; sourceTree = "<group>"; }; 3DECC3DB22464C4F00EEB146 /* XMSingleTone.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XMSingleTone.h; sourceTree = "<group>"; }; 3DECC3DC22464C4F00EEB146 /* XMSDKInfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XMSDKInfo.h; sourceTree = "<group>"; }; 3DECC3DD22464C4F00EEB146 /* XMSDK.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XMSDK.h; sourceTree = "<group>"; }; 3DECC3E622464F9300EEB146 /* ZSVoiceBookCategoryViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSVoiceBookCategoryViewController.swift; sourceTree = "<group>"; }; 3DECC3E822467F9A00EEB146 /* ZSVoiceSegmentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSVoiceSegmentView.swift; sourceTree = "<group>"; }; 3DEFD99023BDB12200116ECD /* ZSRefreshTextHeader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ZSRefreshTextHeader.swift; sourceTree = "<group>"; }; 3DF2D27720EB5773004E73B6 /* ZSChapterInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSChapterInfo.swift; sourceTree = "<group>"; }; 3DF2D27920EB5C1C004E73B6 /* ZSChapterBody.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSChapterBody.swift; sourceTree = "<group>"; }; 3DF6238C277970260049FD73 /* AppOpenAdManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppOpenAdManager.swift; sourceTree = "<group>"; }; 3DF623902779AE100049FD73 /* AdSupport.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AdSupport.framework; path = System/Library/Frameworks/AdSupport.framework; sourceTree = SDKROOT; }; 3DF623922779AE230049FD73 /* AppTrackingTransparency.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppTrackingTransparency.framework; path = System/Library/Frameworks/AppTrackingTransparency.framework; sourceTree = SDKROOT; }; 3DF623942779AE4D0049FD73 /* CoreLocation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreLocation.framework; path = System/Library/Frameworks/CoreLocation.framework; sourceTree = SDKROOT; }; 3DF623962779AE7B0049FD73 /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; 3DF623982779AE8C0049FD73 /* MapKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MapKit.framework; path = System/Library/Frameworks/MapKit.framework; sourceTree = SDKROOT; }; 3DF6239A2779AEA20049FD73 /* MediaPlayer.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MediaPlayer.framework; path = System/Library/Frameworks/MediaPlayer.framework; sourceTree = SDKROOT; }; 3DF6239E2779AEE00049FD73 /* libbz2.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libbz2.tbd; path = usr/lib/libbz2.tbd; sourceTree = SDKROOT; }; 3DF623A02779AEF50049FD73 /* libiconv.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libiconv.tbd; path = usr/lib/libiconv.tbd; sourceTree = SDKROOT; }; 3DF623A22779AF010049FD73 /* libresolv.9.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libresolv.9.tbd; path = usr/lib/libresolv.9.tbd; sourceTree = SDKROOT; }; 3DF623A42779AF180049FD73 /* libc++abi.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = "libc++abi.tbd"; path = "usr/lib/libc++abi.tbd"; sourceTree = SDKROOT; }; 3DF623A62779B09A0049FD73 /* BUAdManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BUAdManager.swift; sourceTree = "<group>"; }; 3DFBDEDC21F1A7EC00AED519 /* ZSCacheHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSCacheHelper.swift; sourceTree = "<group>"; }; 3DFCF6EE21E6470000D3A8EC /* ZSVoucherView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSVoucherView.swift; sourceTree = "<group>"; }; 3DFCF6F321E9984000D3A8EC /* ZSSwipeCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSSwipeCell.swift; sourceTree = "<group>"; }; 3DFE8E7F2595D4270044DCEB /* ZSAddSourceTextField.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ZSAddSourceTextField.h; sourceTree = "<group>"; }; 3DFE8E802595D4280044DCEB /* ZSAddSourceTextField.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ZSAddSourceTextField.m; sourceTree = "<group>"; }; AD065948258C66D1009009FA /* ZSRegularVerifyViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSRegularVerifyViewController.swift; sourceTree = "<group>"; }; ADCB1BED278732E300289C83 /* ZSCommunityHotCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSCommunityHotCell.swift; sourceTree = "<group>"; }; B08901F972525F29A370AA86 /* Pods-zhuishushenqi.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-zhuishushenqi.debug.xcconfig"; path = "Pods/Target Support Files/Pods-zhuishushenqi/Pods-zhuishushenqi.debug.xcconfig"; sourceTree = "<group>"; }; B200D1FD1EEDB1FE003A42E3 /* QSIntroduceReadCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QSIntroduceReadCell.swift; sourceTree = "<group>"; }; B200D1FE1EEDB1FE003A42E3 /* QSIntroduceReadCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = QSIntroduceReadCell.xib; sourceTree = "<group>"; }; B200D2211EEE44AB003A42E3 /* QSLaunchRecView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = QSLaunchRecView.xib; sourceTree = "<group>"; }; B203BB581E9E0F4400F9C052 /* QSSearchViewController+SearchBar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "QSSearchViewController+SearchBar.swift"; sourceTree = "<group>"; }; B203BB641E9E119400F9C052 /* QSSearchViewController+Transition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "QSSearchViewController+Transition.swift"; sourceTree = "<group>"; }; B203BB661E9E142C00F9C052 /* QSHistoryCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QSHistoryCell.swift; sourceTree = "<group>"; }; B203BB671E9E142C00F9C052 /* QSHistoryCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = QSHistoryCell.xib; sourceTree = "<group>"; }; B209975023DBFA2D00E37FD3 /* ZSBookLocalShelfViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSBookLocalShelfViewController.swift; sourceTree = "<group>"; }; B20D74061EED7D250034516F /* QSIntroduceCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QSIntroduceCell.swift; sourceTree = "<group>"; }; B20D74071EED7D250034516F /* QSIntroduceCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = QSIntroduceCell.xib; sourceTree = "<group>"; }; B20D74141EED83690034516F /* QSLastIntroduceCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QSLastIntroduceCell.swift; sourceTree = "<group>"; }; B20D74151EED83690034516F /* QSLastIntroduceCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = QSLastIntroduceCell.xib; sourceTree = "<group>"; }; B21C732A2365774B00E9D148 /* ZSSearchResultCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSSearchResultCell.swift; sourceTree = "<group>"; }; B21C732E23657ACE00E9D148 /* string_buffer.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = string_buffer.c; sourceTree = "<group>"; }; B21C732F23657ACE00E9D148 /* error.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = error.h; sourceTree = "<group>"; }; B21C733023657ACE00E9D148 /* tag_enum.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = tag_enum.h; sourceTree = "<group>"; }; B21C733123657ACE00E9D148 /* util.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = util.c; sourceTree = "<group>"; }; B21C733223657ACE00E9D148 /* char_ref.rl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = char_ref.rl; sourceTree = "<group>"; }; B21C733323657ACE00E9D148 /* insertion_mode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = insertion_mode.h; sourceTree = "<group>"; }; B21C733423657ACE00E9D148 /* parser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = parser.h; sourceTree = "<group>"; }; B21C733523657ACE00E9D148 /* vector.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = vector.c; sourceTree = "<group>"; }; B21C733623657ACE00E9D148 /* attribute.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = attribute.c; sourceTree = "<group>"; }; B21C733723657ACE00E9D148 /* char_ref.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = char_ref.c; sourceTree = "<group>"; }; B21C733823657ACE00E9D148 /* tokenizer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = tokenizer.h; sourceTree = "<group>"; }; B21C733923657ACE00E9D148 /* utf8.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = utf8.c; sourceTree = "<group>"; }; B21C733A23657ACE00E9D148 /* string_piece.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = string_piece.h; sourceTree = "<group>"; }; B21C733B23657ACE00E9D148 /* tokenizer_states.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = tokenizer_states.h; sourceTree = "<group>"; }; B21C733C23657ACE00E9D148 /* token_type.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = token_type.h; sourceTree = "<group>"; }; B21C733D23657ACE00E9D148 /* tag_gperf.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = tag_gperf.h; sourceTree = "<group>"; }; B21C733E23657ACE00E9D148 /* string_buffer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = string_buffer.h; sourceTree = "<group>"; }; B21C733F23657ACE00E9D148 /* error.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = error.c; sourceTree = "<group>"; }; B21C734023657ACE00E9D148 /* tag.in */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = tag.in; sourceTree = "<group>"; }; B21C734123657ACE00E9D148 /* util.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = util.h; sourceTree = "<group>"; }; B21C734223657ACE00E9D148 /* tag.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = tag.c; sourceTree = "<group>"; }; B21C734323657ACE00E9D148 /* char_ref.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = char_ref.h; sourceTree = "<group>"; }; B21C734423657ACE00E9D148 /* vector.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = vector.h; sourceTree = "<group>"; }; B21C734523657ACE00E9D148 /* attribute.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = attribute.h; sourceTree = "<group>"; }; B21C734623657ACE00E9D148 /* parser.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = parser.c; sourceTree = "<group>"; }; B21C734723657ACE00E9D148 /* tag_sizes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = tag_sizes.h; sourceTree = "<group>"; }; B21C734823657ACE00E9D148 /* tag_strings.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = tag_strings.h; sourceTree = "<group>"; }; B21C734923657ACE00E9D148 /* utf8.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = utf8.h; sourceTree = "<group>"; }; B21C734A23657ACE00E9D148 /* tokenizer.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = tokenizer.c; sourceTree = "<group>"; }; B21C734B23657ACE00E9D148 /* string_piece.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = string_piece.c; sourceTree = "<group>"; }; B21C734C23657ACE00E9D148 /* gumbo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = gumbo.h; sourceTree = "<group>"; }; B21C734D23657ACE00E9D148 /* OCGumbo+Query.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "OCGumbo+Query.h"; sourceTree = "<group>"; }; B21C734E23657ACE00E9D148 /* OCGumbo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OCGumbo.h; sourceTree = "<group>"; }; B21C734F23657ACE00E9D148 /* OCGumbo+Query.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "OCGumbo+Query.m"; sourceTree = "<group>"; }; B21C735023657ACE00E9D148 /* OCGumbo.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCGumbo.m; sourceTree = "<group>"; }; B21C736023657B1100E9D148 /* AikanParserModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AikanParserModel.m; sourceTree = "<group>"; }; B21C736123657B1200E9D148 /* AikanParserModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AikanParserModel.h; sourceTree = "<group>"; }; B21C736323657B3F00E9D148 /* AikanHtmlParser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AikanHtmlParser.h; sourceTree = "<group>"; }; B21C736423657B3F00E9D148 /* AikanHtmlParser.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AikanHtmlParser.m; sourceTree = "<group>"; }; B21C736623657E1400E9D148 /* HtmlParserModelData.dat */ = {isa = PBXFileReference; lastKnownFileType = file.bplist; path = HtmlParserModelData.dat; sourceTree = "<group>"; }; B21C736823672D6400E9D148 /* ZSSearchInfoViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSSearchInfoViewController.swift; sourceTree = "<group>"; }; B21C736A2367304800E9D148 /* ZSBookInfoHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSBookInfoHeaderView.swift; sourceTree = "<group>"; }; B22182B81DA40540002458D2 /* CoreText.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreText.framework; path = System/Library/Frameworks/CoreText.framework; sourceTree = SDKROOT; }; B22182BA1DA40548002458D2 /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = System/Library/Frameworks/CoreFoundation.framework; sourceTree = SDKROOT; }; B22182BC1DA40550002458D2 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; B22182BE1DA40558002458D2 /* CoreImage.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreImage.framework; path = System/Library/Frameworks/CoreImage.framework; sourceTree = SDKROOT; }; B22182C01DA4055E002458D2 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; B22182C21DA40566002458D2 /* ImageIO.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ImageIO.framework; path = System/Library/Frameworks/ImageIO.framework; sourceTree = SDKROOT; }; B22182C41DA40570002458D2 /* AssetsLibrary.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AssetsLibrary.framework; path = System/Library/Frameworks/AssetsLibrary.framework; sourceTree = SDKROOT; }; B22182C61DA4057A002458D2 /* Accelerate.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Accelerate.framework; path = System/Library/Frameworks/Accelerate.framework; sourceTree = SDKROOT; }; B22182C81DA40582002458D2 /* MobileCoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MobileCoreServices.framework; path = System/Library/Frameworks/MobileCoreServices.framework; sourceTree = SDKROOT; }; B22182CA1DA40588002458D2 /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; }; B22182CC1DA4058E002458D2 /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = usr/lib/libsqlite3.tbd; sourceTree = SDKROOT; }; B22182CE1DA40594002458D2 /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; }; B22204A11EE9284F008E1902 /* QSSplashScreen.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QSSplashScreen.swift; sourceTree = "<group>"; }; B222A2C21EEE6EE9007CD57F /* QSLaunchRecView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QSLaunchRecView.swift; sourceTree = "<group>"; }; B222C08923C1CAA800D66E3C /* ZSSearchInfoTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSSearchInfoTableViewCell.swift; sourceTree = "<group>"; }; B222C08B23C379E000D66E3C /* ZSReadHistory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSReadHistory.swift; sourceTree = "<group>"; }; B22AC4391E9E6626008625E6 /* QSSearchResultTable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QSSearchResultTable.swift; sourceTree = "<group>"; }; B22AC44A1E9F0AE7008625E6 /* QSSearchAutoCompleteTable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QSSearchAutoCompleteTable.swift; sourceTree = "<group>"; }; B22AC44C1E9F1C62008625E6 /* ZSBaseSegmentItemViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ZSBaseSegmentItemViewController.swift; sourceTree = "<group>"; }; B22AC44E1E9F1C7D008625E6 /* ZSCatelogItemViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ZSCatelogItemViewController.swift; sourceTree = "<group>"; }; B22AC4501E9F1C8C008625E6 /* ZSRankViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ZSRankViewModel.swift; sourceTree = "<group>"; }; B22AC4521E9F1CA8008625E6 /* ZSCatelogDetailViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ZSCatelogDetailViewModel.swift; sourceTree = "<group>"; }; B22AC4541E9F1E8F008625E6 /* QSRankViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QSRankViewController.swift; sourceTree = "<group>"; }; B22AC4561E9F5520008625E6 /* QSLoadingView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QSLoadingView.swift; sourceTree = "<group>"; }; B22AC4581E9F6767008625E6 /* ZSCatelogParameterModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ZSCatelogParameterModel.swift; sourceTree = "<group>"; }; B22AC45A1E9F6774008625E6 /* ZSRankDetailWebService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ZSRankDetailWebService.swift; sourceTree = "<group>"; }; B22AC45C1E9F678D008625E6 /* ZSRankDetailViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ZSRankDetailViewModel.swift; sourceTree = "<group>"; }; B22AC4601E9F695D008625E6 /* ZSRankViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ZSRankViewController.swift; sourceTree = "<group>"; }; B23505BF2783F8EC00D444C7 /* SplashViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SplashViewController.swift; sourceTree = "<group>"; }; B2414D9B1EA602ED005A5758 /* UIViewController+Alert.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIViewController+Alert.swift"; sourceTree = "<group>"; }; B24B3F4B20E534390098ACE5 /* ZSSearchViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSSearchViewCell.swift; sourceTree = "<group>"; }; B24B3F4C20E534390098ACE5 /* ZSSearchViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ZSSearchViewCell.xib; sourceTree = "<group>"; }; B24B3F4F20E8885A0098ACE5 /* ZSSearchHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSSearchHeaderView.swift; sourceTree = "<group>"; }; B24B3F5120E888920098ACE5 /* ZSHistoryHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSHistoryHeaderView.swift; sourceTree = "<group>"; }; B24B3F5320E888D60098ACE5 /* ZSSearchResultViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSSearchResultViewController.swift; sourceTree = "<group>"; }; B24B3F5520ECA04B0098ACE5 /* Array+ZSExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Array+ZSExtension.swift"; sourceTree = "<group>"; }; B24BED4D2301692700CF8C4D /* ZSForumToolBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSForumToolBar.swift; sourceTree = "<group>"; }; B24BED4F23017CB500CF8C4D /* ZSForumPageFooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSForumPageFooterView.swift; sourceTree = "<group>"; }; B24BED512301865400CF8C4D /* ZSForumComment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSForumComment.swift; sourceTree = "<group>"; }; B251228C1DB3291A0040CAD3 /* CFNetwork.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CFNetwork.framework; path = System/Library/Frameworks/CFNetwork.framework; sourceTree = SDKROOT; }; B251F89B2030350E004AAF95 /* Dictionary+QSExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Dictionary+QSExtension.swift"; sourceTree = "<group>"; }; B252D456210EF3960091858E /* HTTPConnection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTTPConnection.h; sourceTree = "<group>"; }; B252D457210EF3960091858E /* HTTPLogging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTTPLogging.h; sourceTree = "<group>"; }; B252D458210EF3960091858E /* HTTPMessage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTTPMessage.h; sourceTree = "<group>"; }; B252D459210EF3960091858E /* WebSocket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WebSocket.h; sourceTree = "<group>"; }; B252D45A210EF3960091858E /* HTTPAuthenticationRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTTPAuthenticationRequest.h; sourceTree = "<group>"; }; B252D45C210EF3960091858E /* HTTPAsyncFileResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTTPAsyncFileResponse.h; sourceTree = "<group>"; }; B252D45D210EF3960091858E /* HTTPDataResponse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTTPDataResponse.m; sourceTree = "<group>"; }; B252D45E210EF3960091858E /* HTTPRedirectResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTTPRedirectResponse.h; sourceTree = "<group>"; }; B252D45F210EF3960091858E /* HTTPDynamicFileResponse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTTPDynamicFileResponse.m; sourceTree = "<group>"; }; B252D460210EF3960091858E /* HTTPFileResponse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTTPFileResponse.m; sourceTree = "<group>"; }; B252D461210EF3960091858E /* HTTPAsyncFileResponse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTTPAsyncFileResponse.m; sourceTree = "<group>"; }; B252D462210EF3960091858E /* HTTPRedirectResponse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTTPRedirectResponse.m; sourceTree = "<group>"; }; B252D463210EF3960091858E /* HTTPDataResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTTPDataResponse.h; sourceTree = "<group>"; }; B252D464210EF3960091858E /* HTTPFileResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTTPFileResponse.h; sourceTree = "<group>"; }; B252D465210EF3960091858E /* HTTPDynamicFileResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTTPDynamicFileResponse.h; sourceTree = "<group>"; }; B252D466210EF3960091858E /* HTTPServer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTTPServer.h; sourceTree = "<group>"; }; B252D467210EF3960091858E /* HTTPMessage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTTPMessage.m; sourceTree = "<group>"; }; B252D468210EF3960091858E /* HTTPConnection.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTTPConnection.m; sourceTree = "<group>"; }; B252D469210EF3960091858E /* WebSocket.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WebSocket.m; sourceTree = "<group>"; }; B252D46A210EF3960091858E /* HTTPResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTTPResponse.h; sourceTree = "<group>"; }; B252D46C210EF3960091858E /* MultipartFormDataParser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MultipartFormDataParser.h; sourceTree = "<group>"; }; B252D46D210EF3960091858E /* MultipartMessageHeader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MultipartMessageHeader.h; sourceTree = "<group>"; }; B252D46E210EF3960091858E /* MultipartMessageHeaderField.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MultipartMessageHeaderField.m; sourceTree = "<group>"; }; B252D46F210EF3960091858E /* MultipartFormDataParser.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MultipartFormDataParser.m; sourceTree = "<group>"; }; B252D470210EF3960091858E /* MultipartMessageHeader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MultipartMessageHeader.m; sourceTree = "<group>"; }; B252D471210EF3960091858E /* MultipartMessageHeaderField.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MultipartMessageHeaderField.h; sourceTree = "<group>"; }; B252D472210EF3960091858E /* HTTPAuthenticationRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTTPAuthenticationRequest.m; sourceTree = "<group>"; }; B252D474210EF3960091858E /* DDNumber.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DDNumber.m; sourceTree = "<group>"; }; B252D475210EF3960091858E /* DDData.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DDData.m; sourceTree = "<group>"; }; B252D476210EF3960091858E /* DDRange.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DDRange.h; sourceTree = "<group>"; }; B252D477210EF3960091858E /* DDNumber.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DDNumber.h; sourceTree = "<group>"; }; B252D478210EF3960091858E /* DDRange.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DDRange.m; sourceTree = "<group>"; }; B252D479210EF3960091858E /* DDData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DDData.h; sourceTree = "<group>"; }; B252D47A210EF3960091858E /* HTTPServer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTTPServer.m; sourceTree = "<group>"; }; B252D47D210EF3960091858E /* DAVConnection.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DAVConnection.m; sourceTree = "<group>"; }; B252D47E210EF3960091858E /* DAVResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DAVResponse.h; sourceTree = "<group>"; }; B252D47F210EF3960091858E /* DELETEResponse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DELETEResponse.m; sourceTree = "<group>"; }; B252D480210EF3960091858E /* PUTResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PUTResponse.h; sourceTree = "<group>"; }; B252D481210EF3960091858E /* DAVConnection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DAVConnection.h; sourceTree = "<group>"; }; B252D482210EF3960091858E /* DAVResponse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DAVResponse.m; sourceTree = "<group>"; }; B252D483210EF3960091858E /* PUTResponse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PUTResponse.m; sourceTree = "<group>"; }; B252D484210EF3960091858E /* DELETEResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DELETEResponse.h; sourceTree = "<group>"; }; B252D485210EF3960091858E /* README.markdown */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.markdown; sourceTree = "<group>"; }; B252D486210EF3960091858E /* LICENSE.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = LICENSE.txt; sourceTree = "<group>"; }; B252D49D210EF4190091858E /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; B252D49F210EF4470091858E /* libxml2.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libxml2.tbd; path = usr/lib/libxml2.tbd; sourceTree = SDKROOT; }; B252D4A1210EF4600091858E /* libxml2.2.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libxml2.2.tbd; path = usr/lib/libxml2.2.tbd; sourceTree = SDKROOT; }; B252D4A3210EF7070091858E /* ZSHTTPConnection.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ZSHTTPConnection.h; sourceTree = "<group>"; }; B252D4A4210EF7070091858E /* ZSHTTPConnection.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ZSHTTPConnection.m; sourceTree = "<group>"; }; B252D4A9210EF8770091858E /* ZSImportBookViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSImportBookViewController.swift; sourceTree = "<group>"; }; B252D4AB210EFA660091858E /* ZSHTTPTool.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ZSHTTPTool.h; sourceTree = "<group>"; }; B252D4AC210EFA660091858E /* ZSHTTPTool.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ZSHTTPTool.m; sourceTree = "<group>"; }; B252D4AF210EFF260091858E /* index.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = index.html; sourceTree = "<group>"; }; B252D4B0210EFF260091858E /* upload.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = upload.html; sourceTree = "<group>"; }; B252D4BE210F0B980091858E /* s.css */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.css; path = s.css; sourceTree = "<group>"; }; B252D4BF210F0B980091858E /* bg.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = bg.png; sourceTree = "<group>"; }; B252D4C0210F0B990091858E /* titlebg.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = titlebg.png; sourceTree = "<group>"; }; B252D4C1210F0B990091858E /* theadbg.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = theadbg.png; sourceTree = "<group>"; }; B252D4C6210F163C0091858E /* txt.jpeg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = txt.jpeg; sourceTree = "<group>"; }; B252D4DA210F583B0091858E /* MonitorFileChangeHelp.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MonitorFileChangeHelp.h; sourceTree = "<group>"; }; B252D4DB210F583B0091858E /* MonitorFileChangeHelp.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MonitorFileChangeHelp.m; sourceTree = "<group>"; }; B252D4DD211006670091858E /* Alamofire+ZSExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Alamofire+ZSExtension.swift"; sourceTree = "<group>"; }; B252D4DF211033F20091858E /* ZSShelfWebService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSShelfWebService.swift; sourceTree = "<group>"; }; B252D4E1211035060091858E /* ZSShelfViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSShelfViewModel.swift; sourceTree = "<group>"; }; B252D4E3211037380091858E /* ZSShelfViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSShelfViewController.swift; sourceTree = "<group>"; }; B253EE392784328500F6C6D9 /* UIStoryboardExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIStoryboardExtension.swift; sourceTree = "<group>"; }; B253EE3C2784358900F6C6D9 /* EncrtptorText */ = {isa = PBXFileReference; lastKnownFileType = text; path = EncrtptorText; sourceTree = "<group>"; }; B253EE3E2784436300F6C6D9 /* UserDefaultsExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDefaultsExtension.swift; sourceTree = "<group>"; }; B253EE412786E95B00F6C6D9 /* ZSNetwork.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSNetwork.swift; sourceTree = "<group>"; }; B255707C1F3A075600C35A34 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; }; B255707D1F3A075600C35A34 /* Config.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Config.swift; sourceTree = "<group>"; }; B25570801F3A07BB00C35A34 /* AppStyle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppStyle.swift; sourceTree = "<group>"; }; B25570861F3A963300C35A34 /* Theme.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Theme.swift; sourceTree = "<group>"; }; B25570881F3A96E500C35A34 /* BookManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BookManager.swift; sourceTree = "<group>"; }; B2580E151E9B236000455AFE /* QSSearchInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QSSearchInteractor.swift; sourceTree = "<group>"; }; B2580E161E9B236000455AFE /* QSSearchPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QSSearchPresenter.swift; sourceTree = "<group>"; }; B2580E171E9B236000455AFE /* QSSearchProtocols.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QSSearchProtocols.swift; sourceTree = "<group>"; }; B2580E181E9B236000455AFE /* QSSearchRouter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QSSearchRouter.swift; sourceTree = "<group>"; }; B2580E191E9B236000455AFE /* QSSearchViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QSSearchViewController.swift; sourceTree = "<group>"; }; B2580E221E9B284900455AFE /* QSHotwords.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QSHotwords.swift; sourceTree = "<group>"; }; B2580E241E9B6AF500455AFE /* QSSearchHeaderView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QSSearchHeaderView.swift; sourceTree = "<group>"; }; B2583CAD1EA78280004178F3 /* ZSFilterThemeViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ZSFilterThemeViewController.swift; sourceTree = "<group>"; }; B2583CB11EA78280004178F3 /* TopicDetailViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TopicDetailViewController.swift; sourceTree = "<group>"; }; B2583CB51EA782C2004178F3 /* QSThemeTopicInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QSThemeTopicInteractor.swift; sourceTree = "<group>"; }; B2583CB61EA782C2004178F3 /* QSThemeTopicPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QSThemeTopicPresenter.swift; sourceTree = "<group>"; }; B2583CB71EA782C2004178F3 /* QSThemeTopicProtocols.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QSThemeTopicProtocols.swift; sourceTree = "<group>"; }; B2583CB81EA782C2004178F3 /* QSThemeTopicRouter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QSThemeTopicRouter.swift; sourceTree = "<group>"; }; B2583CB91EA782C2004178F3 /* QSThemeTopicViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QSThemeTopicViewController.swift; sourceTree = "<group>"; }; B258BB231EA4B201006E5802 /* UIView+ScreenShot.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIView+ScreenShot.swift"; sourceTree = "<group>"; }; B258F317249BB8E400E17943 /* ApplicationExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationExtension.swift; sourceTree = "<group>"; }; B258F31924A0E67800E17943 /* copy.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = copy.txt; sourceTree = "<group>"; }; B25CEFEA22F92688002ABE30 /* MarkupParser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MarkupParser.swift; sourceTree = "<group>"; }; B25CEFEB22F92689002ABE30 /* ZSDisplayView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ZSDisplayView.swift; sourceTree = "<group>"; }; B25CEFEC22F92689002ABE30 /* CTSettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CTSettings.swift; sourceTree = "<group>"; }; B25CEFED22F9268A002ABE30 /* ZSTouchAnchorView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ZSTouchAnchorView.swift; sourceTree = "<group>"; }; B25CEFF322F92AE4002ABE30 /* ZSForumPageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSForumPageViewController.swift; sourceTree = "<group>"; }; B25CEFF522F92C5E002ABE30 /* ZSForumViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSForumViewModel.swift; sourceTree = "<group>"; }; B25CEFF722F97937002ABE30 /* ZSForumPageHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSForumPageHeaderView.swift; sourceTree = "<group>"; }; B25CEFF922FA7BB2002ABE30 /* ZSPostReview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSPostReview.swift; sourceTree = "<group>"; }; B25CF00522FAA731002ABE30 /* ZSPostReviewAuthor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ZSPostReviewAuthor.swift; sourceTree = "<group>"; }; B25CF00622FAA732002ABE30 /* ZSPostReviewHelpful.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ZSPostReviewHelpful.swift; sourceTree = "<group>"; }; B25CF00722FAA732002ABE30 /* ZSPostReviewBook.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ZSPostReviewBook.swift; sourceTree = "<group>"; }; B25D1AD8230293BB00B1F786 /* ZSForumPageCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSForumPageCell.swift; sourceTree = "<group>"; }; B25EB92921933A6D000D3657 /* ZSWriteReview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSWriteReview.swift; sourceTree = "<group>"; }; B25EB92B2194986C000D3657 /* ZSEncryptorAESUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSEncryptorAESUtils.swift; sourceTree = "<group>"; }; B25EB92D2194999E000D3657 /* FBEncryptorAESUtils.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FBEncryptorAESUtils.h; sourceTree = "<group>"; }; B25EB92E2194999E000D3657 /* FBEncryptorAESUtils.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = FBEncryptorAESUtils.mm; sourceTree = "<group>"; }; B25EB93021949AF1000D3657 /* FBEncryptorAES.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FBEncryptorAES.h; sourceTree = "<group>"; }; B25EB93121949AF1000D3657 /* FBEncryptorAES.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FBEncryptorAES.m; sourceTree = "<group>"; }; B26286C91EEA46DD005A1A8A /* ZSIntroducePage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ZSIntroducePage.swift; sourceTree = "<group>"; }; B263ACDD1EE679830096CE80 /* HomeListViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeListViewCell.swift; sourceTree = "<group>"; }; B263E2E8211EBFEC001819FB /* SQLite+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SQLite+Extension.swift"; sourceTree = "<group>"; }; B2662EBA21A7DAC5008AD5C6 /* ZSDBPropertyModel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ZSDBPropertyModel.h; sourceTree = "<group>"; }; B2662EBB21A7DAC5008AD5C6 /* ZSDBPropertyModel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ZSDBPropertyModel.m; sourceTree = "<group>"; }; B266C66821A6FCC3006B5A15 /* ZSDBManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ZSDBManager.h; sourceTree = "<group>"; }; B266C66921A6FCC3006B5A15 /* ZSDBManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ZSDBManager.m; sourceTree = "<group>"; }; B2683CD822C8F65300FE9CF0 /* ZSWebJumpHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSWebJumpHandler.swift; sourceTree = "<group>"; }; B2683CF022C8FC4C00FE9CF0 /* ZSWebUserHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSWebUserHandler.swift; sourceTree = "<group>"; }; B2683CF222C8FCE900FE9CF0 /* ZSWebToolHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSWebToolHandler.swift; sourceTree = "<group>"; }; B2683CF422C8FD6500FE9CF0 /* ZSWebContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSWebContext.swift; sourceTree = "<group>"; }; B2683CF622C8FE4E00FE9CF0 /* ZSWebBIHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSWebBIHandler.swift; sourceTree = "<group>"; }; B2683CF822C8FE8A00FE9CF0 /* ZSWebSpeakHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSWebSpeakHandler.swift; sourceTree = "<group>"; }; B2683CFA22C9111600FE9CF0 /* ZSConfigUtil.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSConfigUtil.swift; sourceTree = "<group>"; }; B2683CFC22D0811000FE9CF0 /* ZSUserDynamicViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSUserDynamicViewController.swift; sourceTree = "<group>"; }; B2683CFE22D083B200FE9CF0 /* ZSNoNetworkView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSNoNetworkView.swift; sourceTree = "<group>"; }; B2683D0022D089FE00FE9CF0 /* ZSDynamicHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSDynamicHeaderView.swift; sourceTree = "<group>"; }; B2683D0222D0940200FE9CF0 /* ZSDynamicViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSDynamicViewModel.swift; sourceTree = "<group>"; }; B2683D0422D0977C00FE9CF0 /* ZSFollowings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSFollowings.swift; sourceTree = "<group>"; }; B2683D0622D17FCD00FE9CF0 /* ZSNotificationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSNotificationViewController.swift; sourceTree = "<group>"; }; B2683D0822D1830A00FE9CF0 /* ZSNotificationCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSNotificationCell.swift; sourceTree = "<group>"; }; B2683D0A22D188A600FE9CF0 /* ZSNotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSNotification.swift; sourceTree = "<group>"; }; B2683D0C22D1911900FE9CF0 /* ZSNotificationViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSNotificationViewModel.swift; sourceTree = "<group>"; }; B2683D0E22D1AE7900FE9CF0 /* ZSDiscoverHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSDiscoverHeaderView.swift; sourceTree = "<group>"; }; B2683D1022D1B1C900FE9CF0 /* ZSDiscoverItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSDiscoverItem.swift; sourceTree = "<group>"; }; B2683D1222D1C57800FE9CF0 /* ZSMineHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSMineHeaderView.swift; sourceTree = "<group>"; }; B2683D1422D1CDA900FE9CF0 /* ZSMineViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSMineViewModel.swift; sourceTree = "<group>"; }; B2683D1622D1F08300FE9CF0 /* ZSSearchViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSSearchViewController.swift; sourceTree = "<group>"; }; B278AA651F3954FF000A0D55 /* QSAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QSAPI.swift; sourceTree = "<group>"; }; B278AA8A1F395F30000A0D55 /* LookBookViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LookBookViewController.swift; sourceTree = "<group>"; }; B278AA8B1F395F30000A0D55 /* ReadHistoryViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReadHistoryViewController.swift; sourceTree = "<group>"; }; B278AA901F395FA4000A0D55 /* QSHelpViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QSHelpViewCell.swift; sourceTree = "<group>"; }; B278AA911F395FA4000A0D55 /* QSHelpViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = QSHelpViewCell.xib; sourceTree = "<group>"; }; B278AA921F395FA4000A0D55 /* QSSegmentDropView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QSSegmentDropView.swift; sourceTree = "<group>"; }; B278AA961F396073000A0D55 /* ReadHistoryCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReadHistoryCell.swift; sourceTree = "<group>"; }; B278AA971F396073000A0D55 /* ReadHistoryCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ReadHistoryCell.xib; sourceTree = "<group>"; }; B278AAC01F396773000A0D55 /* YTKKeyValueStore.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = YTKKeyValueStore.h; sourceTree = "<group>"; }; B278AAC11F396773000A0D55 /* YTKKeyValueStore.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = YTKKeyValueStore.m; sourceTree = "<group>"; }; B278AAFC1F398926000A0D55 /* M80AttributedLabel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = M80AttributedLabel.h; sourceTree = "<group>"; }; B278AAFD1F398926000A0D55 /* M80AttributedLabel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = M80AttributedLabel.m; sourceTree = "<group>"; }; B278AAFE1F398926000A0D55 /* M80AttributedLabelAttachment.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = M80AttributedLabelAttachment.h; sourceTree = "<group>"; }; B278AAFF1F398926000A0D55 /* M80AttributedLabelAttachment.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = M80AttributedLabelAttachment.m; sourceTree = "<group>"; }; B278AB001F398926000A0D55 /* M80AttributedLabelDefines.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = M80AttributedLabelDefines.h; sourceTree = "<group>"; }; B278AB011F398926000A0D55 /* M80AttributedLabelURL.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = M80AttributedLabelURL.h; sourceTree = "<group>"; }; B278AB021F398926000A0D55 /* M80AttributedLabelURL.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = M80AttributedLabelURL.m; sourceTree = "<group>"; }; B278AB031F398926000A0D55 /* NSMutableAttributedString+M80.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSMutableAttributedString+M80.h"; sourceTree = "<group>"; }; B278AB041F398926000A0D55 /* NSMutableAttributedString+M80.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSMutableAttributedString+M80.m"; sourceTree = "<group>"; }; B27B752021E5061100EFC2D7 /* ZSUserAccountViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSUserAccountViewController.swift; sourceTree = "<group>"; }; B27D7E851F4BE04300866341 /* UICollectionView+QSExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UICollectionView+QSExtension.swift"; sourceTree = "<group>"; }; B27D7E9F1F4D593D00866341 /* UIScrollView+StateView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIScrollView+StateView.h"; sourceTree = "<group>"; }; B27D7EA01F4D593D00866341 /* UIScrollView+StateView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIScrollView+StateView.m"; sourceTree = "<group>"; }; B2840F3322C85B75009C9116 /* ZSBookShelfViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSBookShelfViewModel.swift; sourceTree = "<group>"; }; B286F019202981DD0069565B /* NSObject+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSObject+Extension.swift"; sourceTree = "<group>"; }; B289DD7B23E5C51A004ED039 /* ZSReaderStyleSelectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSReaderStyleSelectionView.swift; sourceTree = "<group>"; }; B28FFC841EAFAED600C27FF9 /* NSString+Encode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+Encode.h"; sourceTree = "<group>"; }; B28FFC851EAFAED600C27FF9 /* NSString+Encode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+Encode.m"; sourceTree = "<group>"; }; B28FFC911EB03EA300C27FF9 /* QSHomeDeleteBtn.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QSHomeDeleteBtn.swift; sourceTree = "<group>"; }; B29A6D022029C5B400AC4C73 /* NotificationCenter+QSExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NotificationCenter+QSExtension.swift"; sourceTree = "<group>"; }; B29B4F9C1E821E61008852E3 /* UIImage+QSData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIImage+QSData.swift"; sourceTree = "<group>"; }; B29B4F9E1E822992008852E3 /* QSHotModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QSHotModel.swift; sourceTree = "<group>"; }; B29B4FA01E8257BC008852E3 /* DynamicCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DynamicCell.swift; sourceTree = "<group>"; }; B29B4FA11E8257BC008852E3 /* DynamicCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = DynamicCell.xib; sourceTree = "<group>"; }; B29CFAE32179A0DF0006F294 /* TencentOpenAPI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = TencentOpenAPI.framework; sourceTree = "<group>"; }; B29CFAE52179B6E50006F294 /* ZSLoginService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSLoginService.swift; sourceTree = "<group>"; }; B29CFAE72179B7270006F294 /* ZSQQUser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSQQUser.swift; sourceTree = "<group>"; }; B29CFB6A217A3D8F0006F294 /* ZSLogin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSLogin.swift; sourceTree = "<group>"; }; B29CFB6C217AD6D30006F294 /* ZSMyViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSMyViewController.swift; sourceTree = "<group>"; }; B29CFB6E217ADA690006F294 /* ZSMyCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSMyCell.swift; sourceTree = "<group>"; }; B29CFB70217AE84B0006F294 /* ZSMyHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSMyHeaderView.swift; sourceTree = "<group>"; }; B29CFB72217B1CC00006F294 /* ZSMyService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSMyService.swift; sourceTree = "<group>"; }; B29CFB74217B1DAB0006F294 /* ZSAccount.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSAccount.swift; sourceTree = "<group>"; }; B29CFB76217B1FEC0006F294 /* ZSCoin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSCoin.swift; sourceTree = "<group>"; }; B29CFB78217B21C60006F294 /* ZSMyViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSMyViewModel.swift; sourceTree = "<group>"; }; B29CFB7A217B2F3A0006F294 /* ZSUserDetail.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSUserDetail.swift; sourceTree = "<group>"; }; B29CFB7C217B35E90006F294 /* ZSUserBind.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSUserBind.swift; sourceTree = "<group>"; }; B29CFB7E217B37DC0006F294 /* ZSUserInfoViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSUserInfoViewController.swift; sourceTree = "<group>"; }; B29CFB80217B395D0006F294 /* ZSUserBindCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSUserBindCell.swift; sourceTree = "<group>"; }; B29E275E230A87E800BA6E33 /* ZSForumTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSForumTextView.swift; sourceTree = "<group>"; }; B2A02F7B20F337C20034DC64 /* ZSHorizonalMoveViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSHorizonalMoveViewController.swift; sourceTree = "<group>"; }; B2A02F7D20F33A1A0034DC64 /* ZSHorizonalMoveCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSHorizonalMoveCell.swift; sourceTree = "<group>"; }; B2A02F7F20F33F710034DC64 /* ZSReaderManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSReaderManager.swift; sourceTree = "<group>"; }; B2A39C472110447300D6308E /* ZSLocalShelfViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSLocalShelfViewController.swift; sourceTree = "<group>"; }; B2A39C492110453100D6308E /* ZSLocalShelfViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSLocalShelfViewModel.swift; sourceTree = "<group>"; }; B2A7CD80217C5E5E0067D25B /* ZSUserBookshelf.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSUserBookshelf.swift; sourceTree = "<group>"; }; B2A7CD82217F33230067D25B /* Photos.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Photos.framework; path = System/Library/Frameworks/Photos.framework; sourceTree = SDKROOT; }; B2A7CD84217F36B20067D25B /* ZSThirdLoginView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSThirdLoginView.swift; sourceTree = "<group>"; }; B2A7CD86218016060067D25B /* ZSLoginVerifyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSLoginVerifyView.swift; sourceTree = "<group>"; }; B2A7CD88218017380067D25B /* ZSMobileLogin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSMobileLogin.swift; sourceTree = "<group>"; }; B2A7CD8A2180D6E90067D25B /* ZSModifyNicknameViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSModifyNicknameViewController.swift; sourceTree = "<group>"; }; B2A7CDEA2184526D0067D25B /* ZSBookReviewViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSBookReviewViewController.swift; sourceTree = "<group>"; }; B2A7CDEC218453900067D25B /* ZSReviewDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSReviewDetailView.swift; sourceTree = "<group>"; }; B2A7CDEE218453DC0067D25B /* ZSBestReviewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSBestReviewView.swift; sourceTree = "<group>"; }; B2A7CDF0218469860067D25B /* ZSFeelingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSFeelingView.swift; sourceTree = "<group>"; }; B2ACA01D212179F60032305E /* ZSSegmentViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSSegmentViewController.swift; sourceTree = "<group>"; }; B2ACA02121217DFB0032305E /* ZSRankService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSRankService.swift; sourceTree = "<group>"; }; B2ACA05E212416AE0032305E /* ZSBaseNavigationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSBaseNavigationViewController.swift; sourceTree = "<group>"; }; B2ACA060212444870032305E /* ZSSettingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSSettingViewController.swift; sourceTree = "<group>"; }; B2ACA0622124467F0032305E /* ZSSettingViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSSettingViewModel.swift; sourceTree = "<group>"; }; B2ACA06421244B3D0032305E /* ZSSetting.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = ZSSetting.plist; sourceTree = "<group>"; }; B2ACA069212915240032305E /* ZSReviewsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSReviewsCell.swift; sourceTree = "<group>"; }; B2ACA06B212915AC0032305E /* ZSReviewsCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ZSReviewsCell.xib; sourceTree = "<group>"; }; B2ACA06D212944AC0032305E /* ZSWebViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSWebViewController.swift; sourceTree = "<group>"; }; B2ACA7762787085200B8B275 /* ZSCommunityHot.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSCommunityHot.swift; sourceTree = "<group>"; }; B2AF57E92675FB850080A065 /* qs_bookshelf.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = qs_bookshelf.png; sourceTree = "<group>"; }; B2AF57EA2675FB850080A065 /* style.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = style.bundle; sourceTree = "<group>"; }; B2AF57EB2675FB850080A065 /* regular_verify.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = regular_verify.png; sourceTree = "<group>"; }; B2AF57EC2675FB850080A065 /* qs_readerMain.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = qs_readerMain.png; sourceTree = "<group>"; }; B2AF57ED2675FB850080A065 /* qs_changeSource.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = qs_changeSource.png; sourceTree = "<group>"; }; B2AF57EE2675FB850080A065 /* zhuishushenqi.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = zhuishushenqi.png; sourceTree = "<group>"; }; B2AF57EF2675FB850080A065 /* qs_reader.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = qs_reader.png; sourceTree = "<group>"; }; B2B02E251EA8505200A6880A /* QSTopicDetailInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QSTopicDetailInteractor.swift; sourceTree = "<group>"; }; B2B02E261EA8505200A6880A /* QSTopicDetailPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QSTopicDetailPresenter.swift; sourceTree = "<group>"; }; B2B02E271EA8505200A6880A /* QSTopicDetailProtocols.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QSTopicDetailProtocols.swift; sourceTree = "<group>"; }; B2B02E281EA8505200A6880A /* QSTopicDetailRouter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QSTopicDetailRouter.swift; sourceTree = "<group>"; }; B2B02E291EA8505200A6880A /* QSTopicDetailViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QSTopicDetailViewController.swift; sourceTree = "<group>"; }; B2B02E3C1EA891E700A6880A /* ZSDetailViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ZSDetailViewController.swift; sourceTree = "<group>"; }; B2B02E421EA8920F00A6880A /* ZSCategoryDetailViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ZSCategoryDetailViewController.swift; sourceTree = "<group>"; }; B2B02E4B1EA9A9CA00A6880A /* ZSCatelogViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ZSCatelogViewController.swift; sourceTree = "<group>"; }; B2B02E4D1EA9A9F000A6880A /* ZSThemeTopicViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ZSThemeTopicViewModel.swift; sourceTree = "<group>"; }; B2B02E4E1EA9A9F000A6880A /* ZSVoiceCategoryHeaderView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ZSVoiceCategoryHeaderView.swift; sourceTree = "<group>"; }; B2B02E4F1EA9A9F000A6880A /* ZSVoiceCategoryCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ZSVoiceCategoryCell.swift; sourceTree = "<group>"; }; B2B02E501EA9A9F000A6880A /* ZSVoiceBookSegmentViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ZSVoiceBookSegmentViewController.swift; sourceTree = "<group>"; }; B2B02E511EA9A9F000A6880A /* ZSVoicePlayerCatelogHeaderView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ZSVoicePlayerCatelogHeaderView.swift; sourceTree = "<group>"; }; B2B4B9F81E7EA4A8000CC201 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; }; B2B4B9FB1E7EA4A8000CC201 /* BaseViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseViewController.swift; sourceTree = "<group>"; }; B2B4B9FC1E7EA4A8000CC201 /* SideViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SideViewController.swift; sourceTree = "<group>"; }; B2B4B9FE1E7EA4A8000CC201 /* SwiftyJSON.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftyJSON.swift; sourceTree = "<group>"; }; B2B4B9FF1E7EA4A8000CC201 /* XYCBaseModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XYCBaseModel.h; sourceTree = "<group>"; }; B2B4BA001E7EA4A8000CC201 /* XYCBaseModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = XYCBaseModel.m; sourceTree = "<group>"; }; B2B4BA021E7EA4A8000CC201 /* CommunityView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = CommunityView.swift; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; B2B4BA031E7EA4A8000CC201 /* DarkStarView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DarkStarView.swift; sourceTree = "<group>"; }; B2B4BA041E7EA4A8000CC201 /* DarkView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DarkView.swift; sourceTree = "<group>"; }; B2B4BA071E7EA4A8000CC201 /* EmptyView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmptyView.swift; sourceTree = "<group>"; }; B2B4BA081E7EA4A8000CC201 /* EmptyView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = EmptyView.xib; sourceTree = "<group>"; }; B2B4BA091E7EA4A8000CC201 /* LightStarView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LightStarView.swift; sourceTree = "<group>"; }; B2B4BA0A1E7EA4A8000CC201 /* LightView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LightView.swift; sourceTree = "<group>"; }; B2B4BA0B1E7EA4A8000CC201 /* RateView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RateView.swift; sourceTree = "<group>"; }; B2B4BA0C1E7EA4A8000CC201 /* V2FPSLabel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = V2FPSLabel.swift; sourceTree = "<group>"; }; B2B4BA0D1E7EA4A8000CC201 /* XYCActionSheet.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XYCActionSheet.swift; sourceTree = "<group>"; }; B2B4BA0F1E7EA4A8000CC201 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; }; B2B4BA111E7EA4A8000CC201 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; }; B2B4BA141E7EA4A8000CC201 /* Date+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Date+Extension.swift"; sourceTree = "<group>"; }; B2B4BA151E7EA4A8000CC201 /* DateIntervalFormatter+formatter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "DateIntervalFormatter+formatter.swift"; sourceTree = "<group>"; }; B2B4BA181E7EA4A8000CC201 /* NSDate+Extension.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSDate+Extension.h"; sourceTree = "<group>"; }; B2B4BA191E7EA4A8000CC201 /* NSDate+Extension.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSDate+Extension.m"; sourceTree = "<group>"; }; B2B4BA1A1E7EA4A8000CC201 /* String+QSExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = "String+QSExtension.swift"; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; B2B4BA1B1E7EA4A8000CC201 /* UIImageView+zhuishu.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIImageView+zhuishu.swift"; sourceTree = "<group>"; }; B2B4BA1C1E7EA4A8000CC201 /* UILabel+zhuishu.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UILabel+zhuishu.swift"; sourceTree = "<group>"; }; B2B4BA1D1E7EA4A8000CC201 /* UINavigationItem+BackItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UINavigationItem+BackItem.h"; sourceTree = "<group>"; }; B2B4BA1E1E7EA4A8000CC201 /* UINavigationItem+BackItem.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UINavigationItem+BackItem.m"; sourceTree = "<group>"; }; B2B4BA1F1E7EA4A8000CC201 /* UITableView+QSGeneric.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UITableView+QSGeneric.swift"; sourceTree = "<group>"; }; B2B4BA201E7EA4A8000CC201 /* UITableView+swizzling.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UITableView+swizzling.swift"; sourceTree = "<group>"; }; B2B4BA211E7EA4A8000CC201 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; B2B4BA3A1E7EA4A8000CC201 /* QSRankModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QSRankModel.swift; sourceTree = "<group>"; }; B2B4BA3C1E7EA4A8000CC201 /* RankingViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = RankingViewCell.swift; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; B2B4BA3D1E7EA4A8000CC201 /* TopDetailCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TopDetailCell.swift; sourceTree = "<group>"; }; B2B4BA3E1E7EA4A8000CC201 /* TopDetailCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = TopDetailCell.xib; sourceTree = "<group>"; }; B2B4BA411E7EA4A8000CC201 /* SearchDetailViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SearchDetailViewController.swift; sourceTree = "<group>"; }; B2B4BA451E7EA4A8000CC201 /* SearchView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SearchView.swift; sourceTree = "<group>"; }; B2B4BA4C1E7EA4A8000CC201 /* ThemeTopicModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThemeTopicModel.swift; sourceTree = "<group>"; }; B2B4BA4D1E7EA4A8000CC201 /* TopicDetailHeader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TopicDetailHeader.swift; sourceTree = "<group>"; }; B2B4BA4E1E7EA4A8000CC201 /* TopicDetailModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TopicDetailModel.swift; sourceTree = "<group>"; }; B2B4BA511E7EA4A8000CC201 /* ThemeTopicCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThemeTopicCell.swift; sourceTree = "<group>"; }; B2B4BA521E7EA4A8000CC201 /* ThemeTopicCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ThemeTopicCell.xib; sourceTree = "<group>"; }; B2B4BA531E7EA4A8000CC201 /* TopicDetailCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TopicDetailCell.swift; sourceTree = "<group>"; }; B2B4BA541E7EA4A8000CC201 /* TopicDetailCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = TopicDetailCell.xib; sourceTree = "<group>"; }; B2B4BA551E7EA4A8000CC201 /* TopicDetailHeaderCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TopicDetailHeaderCell.swift; sourceTree = "<group>"; }; B2B4BA561E7EA4A8000CC201 /* TopicDetailHeaderCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = TopicDetailHeaderCell.xib; sourceTree = "<group>"; }; B2B4BA591E7EA4A8000CC201 /* DynamicViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = DynamicViewController.swift; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; B2B4BA5A1E7EA4A8000CC201 /* LeftViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LeftViewController.swift; sourceTree = "<group>"; }; B2B4BA5B1E7EA4A8000CC201 /* RightViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RightViewController.swift; sourceTree = "<group>"; }; B2B4BA5C1E7EA4A8000CC201 /* RootViewController+FetchData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "RootViewController+FetchData.swift"; sourceTree = "<group>"; }; B2B4BA5D1E7EA4A8000CC201 /* RootViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RootViewController.swift; sourceTree = "<group>"; }; B2B4BA601E7EA4A8000CC201 /* BarButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BarButton.swift; sourceTree = "<group>"; }; B2B4BA611E7EA4A8000CC201 /* RightTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RightTableViewCell.swift; sourceTree = "<group>"; }; B2B4BA621E7EA4A8000CC201 /* RootNavigationView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RootNavigationView.swift; sourceTree = "<group>"; }; B2B4BA631E7EA4A8000CC201 /* SegMenu.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SegMenu.swift; sourceTree = "<group>"; }; B2B4BA641E7EA4A8000CC201 /* SwipableCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwipableCell.swift; sourceTree = "<group>"; }; B2B4BBDC1E7EA4AB000CC201 /* ViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = "<group>"; }; B2B4BBDD1E7EA4AB000CC201 /* zhuishushenqi-Bridge-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "zhuishushenqi-Bridge-Header.h"; sourceTree = "<group>"; }; B2B4BD761E7ECEE0000CC201 /* RootViewController+Subviews.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "RootViewController+Subviews.swift"; sourceTree = "<group>"; }; B2B4BD791E7EE2B1000CC201 /* QSNetworkManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QSNetworkManager.swift; sourceTree = "<group>"; }; B2B8A21622C70C07005527C8 /* mj_refresh_loading@2x.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; path = "mj_refresh_loading@2x.gif"; sourceTree = "<group>"; }; B2B8A21822C71641005527C8 /* mjRefreshHeadTitle.plist */ = {isa = PBXFileReference; lastKnownFileType = file.bplist; path = mjRefreshHeadTitle.plist; sourceTree = "<group>"; }; B2B8A21A22C71A23005527C8 /* ZSBookShelfHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSBookShelfHeaderView.swift; sourceTree = "<group>"; }; B2BB5B961D8BDF8E00379217 /* zhuishushenqi.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = zhuishushenqi.app; sourceTree = BUILT_PRODUCTS_DIR; }; B2BB5BAA1D8BDF8E00379217 /* zhuishushenqiTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = zhuishushenqiTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; B2BB5BAE1D8BDF8E00379217 /* zhuishushenqiTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = zhuishushenqiTests.swift; sourceTree = "<group>"; }; B2BB5BB01D8BDF8E00379217 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; B2BB5BB51D8BDF8E00379217 /* zhuishushenqiUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = zhuishushenqiUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; B2BB5BB91D8BDF8E00379217 /* zhuishushenqiUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = zhuishushenqiUITests.swift; sourceTree = "<group>"; }; B2BB5BBB1D8BDF8E00379217 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; B2BCE0C32306530A00E52903 /* ZSForumPageTitleHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSForumPageTitleHeaderView.swift; sourceTree = "<group>"; }; B2C0DEAD20D4CB340057DC84 /* QSTextRouter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QSTextRouter.swift; sourceTree = "<group>"; }; B2C0DEAE20D4CB340057DC84 /* QSMoreSettingController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QSMoreSettingController.swift; sourceTree = "<group>"; }; B2C0DEAF20D4CB340057DC84 /* QSReaderViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QSReaderViewController.swift; sourceTree = "<group>"; }; B2C0DEB020D4CB340057DC84 /* QSReaderBackgroundViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QSReaderBackgroundViewController.swift; sourceTree = "<group>"; }; B2C0DEB120D4CB340057DC84 /* QSTextReaderController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QSTextReaderController.swift; sourceTree = "<group>"; }; B2C0DEB220D4CB340057DC84 /* ZSReaderViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ZSReaderViewModel.swift; sourceTree = "<group>"; }; B2C0DEB320D4CB340057DC84 /* QSTextProtocols.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QSTextProtocols.swift; sourceTree = "<group>"; }; B2C0DEB420D4CB340057DC84 /* ZSReaderWebService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ZSReaderWebService.swift; sourceTree = "<group>"; }; B2C0DEB720D4CB340057DC84 /* TXTReaderViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TXTReaderViewController.swift; sourceTree = "<group>"; }; B2C0DEB920D4CB340057DC84 /* PageViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PageViewController.swift; sourceTree = "<group>"; }; B2C0DEBC20D4CB340057DC84 /* QSInterestedViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QSInterestedViewController.swift; sourceTree = "<group>"; }; B2C0DEBE20D4CB340057DC84 /* UpdateInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UpdateInfo.swift; sourceTree = "<group>"; }; B2C0DEBF20D4CB340057DC84 /* QSReaderParse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QSReaderParse.swift; sourceTree = "<group>"; }; B2C0DEC020D4CB340057DC84 /* BookCommentDetail.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BookCommentDetail.swift; sourceTree = "<group>"; }; B2C0DEC120D4CB340057DC84 /* BookComment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BookComment.swift; sourceTree = "<group>"; }; B2C0DEC220D4CB340057DC84 /* QSHotComment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QSHotComment.swift; sourceTree = "<group>"; }; B2C0DEC320D4CB340057DC84 /* BookShelfInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BookShelfInfo.swift; sourceTree = "<group>"; }; B2C0DEC420D4CB340057DC84 /* QSRecord.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QSRecord.swift; sourceTree = "<group>"; }; B2C0DEC520D4CB340057DC84 /* User.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = User.swift; sourceTree = "<group>"; }; B2C0DEC620D4CB340057DC84 /* QSReaderSetting.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QSReaderSetting.swift; sourceTree = "<group>"; }; B2C0DEC720D4CB340057DC84 /* QSBookList.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QSBookList.swift; sourceTree = "<group>"; }; B2C0DEC820D4CB340057DC84 /* Book.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Book.swift; sourceTree = "<group>"; }; B2C0DEC920D4CB340057DC84 /* QSBook.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QSBook.swift; sourceTree = "<group>"; }; B2C0DECA20D4CB340057DC84 /* QSReaderViewFlowLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QSReaderViewFlowLayout.swift; sourceTree = "<group>"; }; B2C0DECB20D4CB340057DC84 /* Chapters.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = Chapters.json; sourceTree = "<group>"; }; B2C0DECC20D4CB340057DC84 /* QSChapter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QSChapter.swift; sourceTree = "<group>"; }; B2C0DECD20D4CB340057DC84 /* ResourceModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ResourceModel.swift; sourceTree = "<group>"; }; B2C0DECE20D4CB340057DC84 /* PageInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PageInfo.swift; sourceTree = "<group>"; }; B2C0DECF20D4CB340057DC84 /* BookDetail.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BookDetail.swift; sourceTree = "<group>"; }; B2C0DED020D4CB340057DC84 /* BookShelf.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BookShelf.swift; sourceTree = "<group>"; }; B2C0DED120D4CB340057DC84 /* QSRecomment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QSRecomment.swift; sourceTree = "<group>"; }; B2C0DED220D4CB340057DC84 /* ChapterInfo.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = ChapterInfo.json; sourceTree = "<group>"; }; B2C0DED320D4CB340057DC84 /* QSPage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QSPage.swift; sourceTree = "<group>"; }; B2C0DED420D4CB340057DC84 /* ChapterInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChapterInfo.swift; sourceTree = "<group>"; }; B2C0DED520D4CB340057DC84 /* QSBookDetailViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QSBookDetailViewController.swift; sourceTree = "<group>"; }; B2C0DED820D4CB340057DC84 /* QSBookDetailProtocols.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QSBookDetailProtocols.swift; sourceTree = "<group>"; }; B2C0DEDA20D4CB340057DC84 /* QSBookDetailInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QSBookDetailInteractor.swift; sourceTree = "<group>"; }; B2C0DEDC20D4CB340057DC84 /* ToolBar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ToolBar.swift; sourceTree = "<group>"; }; B2C0DEDD20D4CB340057DC84 /* QSBookListViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = QSBookListViewCell.xib; sourceTree = "<group>"; }; B2C0DEDE20D4CB340057DC84 /* .DS_Store~HEAD */ = {isa = PBXFileReference; lastKnownFileType = file; path = ".DS_Store~HEAD"; sourceTree = "<group>"; }; B2C0DEDF20D4CB340057DC84 /* QSBatteryView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QSBatteryView.swift; sourceTree = "<group>"; }; B2C0DEE020D4CB340057DC84 /* ChangeSourceCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ChangeSourceCell.xib; sourceTree = "<group>"; }; B2C0DEE120D4CB340057DC84 /* UserfulCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = UserfulCell.xib; sourceTree = "<group>"; }; B2C0DEE220D4CB340057DC84 /* ProgressView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProgressView.swift; sourceTree = "<group>"; }; B2C0DEE320D4CB340057DC84 /* BookCommentViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BookCommentViewCell.swift; sourceTree = "<group>"; }; B2C0DEE420D4CB340057DC84 /* QSBookDetailRateView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QSBookDetailRateView.swift; sourceTree = "<group>"; }; B2C0DEE520D4CB340057DC84 /* QSRecommendCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QSRecommendCell.swift; sourceTree = "<group>"; }; B2C0DEE620D4CB340057DC84 /* BookCommentViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = BookCommentViewCell.xib; sourceTree = "<group>"; }; B2C0DEE720D4CB340057DC84 /* BookCommentCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = BookCommentCell.xib; sourceTree = "<group>"; }; B2C0DEE820D4CB340057DC84 /* UserfulCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserfulCell.swift; sourceTree = "<group>"; }; B2C0DEE920D4CB340057DC84 /* QSDiscussCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QSDiscussCell.swift; sourceTree = "<group>"; }; B2C0DEEC20D4CB340057DC84 /* ChangeSourceCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChangeSourceCell.swift; sourceTree = "<group>"; }; B2C0DEED20D4CB340057DC84 /* CategoryButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CategoryButton.swift; sourceTree = "<group>"; }; B2C0DEEE20D4CB340057DC84 /* QSRecommendCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = QSRecommendCell.xib; sourceTree = "<group>"; }; B2C0DEEF20D4CB340057DC84 /* QSBookDetailTagsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QSBookDetailTagsView.swift; sourceTree = "<group>"; }; B2C0DEF020D4CB340057DC84 /* QSDiscussCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = QSDiscussCell.xib; sourceTree = "<group>"; }; B2C0DEF120D4CB340057DC84 /* HotCommentCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HotCommentCell.swift; sourceTree = "<group>"; }; B2C0DEF220D4CB340057DC84 /* CategoryTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CategoryTableViewCell.swift; sourceTree = "<group>"; }; B2C0DEF320D4CB340057DC84 /* HotCommentCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = HotCommentCell.xib; sourceTree = "<group>"; }; B2C0DEF420D4CB340057DC84 /* BookDetailHeader.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = BookDetailHeader.xib; sourceTree = "<group>"; }; B2C0DEF520D4CB340057DC84 /* BookCommentCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BookCommentCell.swift; sourceTree = "<group>"; }; B2C0DEF620D4CB340057DC84 /* BookDetailHeader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BookDetailHeader.swift; sourceTree = "<group>"; }; B2C0DEF720D4CB340057DC84 /* CategoryTableViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = CategoryTableViewCell.xib; sourceTree = "<group>"; }; B2C0DEF820D4CB340057DC84 /* QSBookDetailContentView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QSBookDetailContentView.swift; sourceTree = "<group>"; }; B2C0DEF920D4CB340057DC84 /* QSBookListViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QSBookListViewCell.swift; sourceTree = "<group>"; }; B2C0DEFA20D4CB340057DC84 /* PageView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PageView.swift; sourceTree = "<group>"; }; B2C0DEFB20D4CB340057DC84 /* QSBookDetailRouter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QSBookDetailRouter.swift; sourceTree = "<group>"; }; B2C0DEFC20D4CB340057DC84 /* BookDetailViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BookDetailViewController.swift; sourceTree = "<group>"; }; B2C0DEFD20D4CB340057DC84 /* QSBookDetailPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QSBookDetailPresenter.swift; sourceTree = "<group>"; }; B2C0DF0020D4CB340057DC84 /* ChangeSourceViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChangeSourceViewController.swift; sourceTree = "<group>"; }; B2C0DF0120D4CB340057DC84 /* QSCategoryProtocols.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QSCategoryProtocols.swift; sourceTree = "<group>"; }; B2C0DF0220D4CB340057DC84 /* CategoryController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CategoryController.swift; sourceTree = "<group>"; }; B2C0DF0320D4CB340057DC84 /* QSCategoryPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QSCategoryPresenter.swift; sourceTree = "<group>"; }; B2C0DF0420D4CB340057DC84 /* QSCategoryRouter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QSCategoryRouter.swift; sourceTree = "<group>"; }; B2C0DF0620D4CB340057DC84 /* QSCategoryReaderViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QSCategoryReaderViewController.swift; sourceTree = "<group>"; }; B2C0DF0820D4CB340057DC84 /* CategoryViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CategoryViewController.swift; sourceTree = "<group>"; }; B2C0DF0A20D4CB340057DC84 /* QSCategoryInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QSCategoryInteractor.swift; sourceTree = "<group>"; }; B2C0DF0C20D4CB340057DC84 /* QSCommunityRouter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QSCommunityRouter.swift; sourceTree = "<group>"; }; B2C0DF0E20D4CB340057DC84 /* QSCommunityViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QSCommunityViewController.swift; sourceTree = "<group>"; }; B2C0DF1220D4CB340057DC84 /* QSCommunityProtocols.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QSCommunityProtocols.swift; sourceTree = "<group>"; }; B2C0DF1320D4CB340057DC84 /* QSCommunityInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QSCommunityInteractor.swift; sourceTree = "<group>"; }; B2C0DF1420D4CB340057DC84 /* QSCommunityPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QSCommunityPresenter.swift; sourceTree = "<group>"; }; B2C0DF7020D4CB6C0057DC84 /* QSBookCommentViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QSBookCommentViewController.swift; sourceTree = "<group>"; }; B2C0DF7120D4CB6C0057DC84 /* QSBookCommentPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QSBookCommentPresenter.swift; sourceTree = "<group>"; }; B2C0DF7220D4CB6C0057DC84 /* BookCommentViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BookCommentViewController.swift; sourceTree = "<group>"; }; B2C0DF7320D4CB6C0057DC84 /* TXTReader.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = TXTReader.storyboard; sourceTree = "<group>"; }; B2C0DF7420D4CB6C0057DC84 /* QSBookCommentInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QSBookCommentInteractor.swift; sourceTree = "<group>"; }; B2C0DF7520D4CB6C0057DC84 /* QSBookCommentRouter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QSBookCommentRouter.swift; sourceTree = "<group>"; }; B2C0DF7620D4CB6D0057DC84 /* QSBookCommentProtocols.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QSBookCommentProtocols.swift; sourceTree = "<group>"; }; B2C0DF7E20D4CC480057DC84 /* ZSBookCommentService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSBookCommentService.swift; sourceTree = "<group>"; }; B2C0DF8020D4CC5A0057DC84 /* ZSBaseService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSBaseService.swift; sourceTree = "<group>"; }; B2C0DF8420D7C1D30057DC84 /* ZSHottwitterViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSHottwitterViewController.swift; sourceTree = "<group>"; }; B2C63A3123C1A44B0083C987 /* ZSReaderDownloader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSReaderDownloader.swift; sourceTree = "<group>"; }; B2C63A3323C1A57A0083C987 /* ZSBookMemoryCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSBookMemoryCache.swift; sourceTree = "<group>"; }; B2C63A3523C1A6C80083C987 /* ZSBookChapter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSBookChapter.swift; sourceTree = "<group>"; }; B2C63A3723C1ABA60083C987 /* ZSBookDiskCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSBookDiskCache.swift; sourceTree = "<group>"; }; B2C63A3A23C1ADC50083C987 /* ZSBookCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSBookCache.swift; sourceTree = "<group>"; }; B2CA80EF21797EA2007A3EE7 /* ZSThirdLogin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZSThirdLogin.swift; sourceTree = "<group>"; }; B2CA942A1EB331DC00D60BF2 /* UIColor+Theme.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIColor+Theme.swift"; sourceTree = "<group>"; }; B2E072771F3AD99500C63347 /* Reader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Reader.swift; sourceTree = "<group>"; }; B2EE7CF51F3BFC7B00BE997C /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; }; B2EE7CFB1F3C063F00BE997C /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = "<group>"; }; B2EE7CFC1F3C064800BE997C /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/Localizable.strings"; sourceTree = "<group>"; }; B2EE7CFD1F3C0C3400BE997C /* Script.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = Script.sh; sourceTree = "<group>"; }; B2F778BB1EAF31DB004B4362 /* UITableView+FINAutomaticHeightCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UITableView+FINAutomaticHeightCell.swift"; sourceTree = "<group>"; }; B2FB91FC1EB1EEDF000C990A /* Reachability.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Reachability.swift; sourceTree = "<group>"; }; B2FB92101EB1EEDF000C990A /* CoreTelephony.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreTelephony.framework; path = System/Library/Frameworks/CoreTelephony.framework; sourceTree = SDKROOT; }; B2FCAA8A1E9C82CE0064837C /* QSSearchItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QSSearchItem.swift; sourceTree = "<group>"; }; B2FCAA8C1E9C85600064837C /* QSHistoryHeaderView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QSHistoryHeaderView.swift; sourceTree = "<group>"; }; C17F4CC59FFA95D661BDB3BB /* Pods-zhuishushenqi.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-zhuishushenqi.release.xcconfig"; path = "Pods/Target Support Files/Pods-zhuishushenqi/Pods-zhuishushenqi.release.xcconfig"; sourceTree = "<group>"; }; E6DD667D5A369B09442DE895 /* Pods_zhuishushenqi.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_zhuishushenqi.framework; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ B2BB5B931D8BDF8E00379217 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 3DF623A52779AF180049FD73 /* libc++abi.tbd in Frameworks */, 3DF623A32779AF020049FD73 /* libresolv.9.tbd in Frameworks */, 3DF623A12779AEF50049FD73 /* libiconv.tbd in Frameworks */, 3DF6239F2779AEE00049FD73 /* libbz2.tbd in Frameworks */, 3DF6239B2779AEA20049FD73 /* MediaPlayer.framework in Frameworks */, 3DF623992779AE8C0049FD73 /* MapKit.framework in Frameworks */, 3DF623972779AE7B0049FD73 /* JavaScriptCore.framework in Frameworks */, 3DF623952779AE4D0049FD73 /* CoreLocation.framework in Frameworks */, 3DF623912779AE100049FD73 /* AdSupport.framework in Frameworks */, B2A7CD83217F33230067D25B /* Photos.framework in Frameworks */, 3DD0843C2157C136008E3B4A /* Contacts.framework in Frameworks */, B22182C11DA4055E002458D2 /* QuartzCore.framework in Frameworks */, 3DD0843A2157C124008E3B4A /* AddressBook.framework in Frameworks */, B22182CB1DA40588002458D2 /* SystemConfiguration.framework in Frameworks */, 3DD084382157C101008E3B4A /* AudioToolbox.framework in Frameworks */, B2FB92111EB1EEDF000C990A /* CoreTelephony.framework in Frameworks */, 3DD084362157C0ED008E3B4A /* libc++.tbd in Frameworks */, 3DDB6CB72130F73000E8698D /* libicucore.tbd in Frameworks */, 3DECC3DE22464C4F00EEB146 /* libXMOpenPlatform.a in Frameworks */, B252D4A2210EF4600091858E /* libxml2.2.tbd in Frameworks */, B252D4A0210EF4480091858E /* libxml2.tbd in Frameworks */, B29CFAE42179A0DF0006F294 /* TencentOpenAPI.framework in Frameworks */, B252D49E210EF4190091858E /* Security.framework in Frameworks */, B251228D1DB3291A0040CAD3 /* CFNetwork.framework in Frameworks */, B22182CF1DA40594002458D2 /* libz.tbd in Frameworks */, B22182CD1DA4058E002458D2 /* libsqlite3.tbd in Frameworks */, 3DD08407215698E5008E3B4A /* iflyMSC.framework in Frameworks */, B22182C91DA40582002458D2 /* MobileCoreServices.framework in Frameworks */, 3D26002724A1B61000458B58 /* libuchardet-ios.a in Frameworks */, B22182C71DA4057A002458D2 /* Accelerate.framework in Frameworks */, B22182C51DA40570002458D2 /* AssetsLibrary.framework in Frameworks */, B22182C31DA40566002458D2 /* ImageIO.framework in Frameworks */, B22182BF1DA40558002458D2 /* CoreImage.framework in Frameworks */, B22182BD1DA40550002458D2 /* CoreGraphics.framework in Frameworks */, B22182BB1DA40548002458D2 /* CoreFoundation.framework in Frameworks */, B22182B91DA40540002458D2 /* CoreText.framework in Frameworks */, 567D4C5122D3A2E149B8AD65 /* Pods_zhuishushenqi.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; B2BB5BA71D8BDF8E00379217 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; B2BB5BB21D8BDF8E00379217 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 3D17A02522D47331001FAC0C /* Reader */ = { isa = PBXGroup; children = ( 3D17A02622D47364001FAC0C /* ZSReaderController.swift */, 3D17A02822D47426001FAC0C /* ZSPageViewController.swift */, 3D17A02A22D474A7001FAC0C /* ZSHorizonalViewController.swift */, 3D55F4D822D47B3800AE0E2B /* ZSVerticalViewController.swift */, 3D55F4DC22D47CEF00AE0E2B /* ZSNormalViewController.swift */, 3D1330CC23C7239000D81EF4 /* ZSReaderCatalogViewController.swift */, 3DE3AF7E23CF061A00D74C1F /* ZSPageTableViewCell.swift */, 3D05BCB323C592E2001EAB2A /* ZSReaderToolbar.swift */, 3D05BCB523C592FE001EAB2A /* ZSReaderTopbar.swift */, 3D05BCB723C59314001EAB2A /* ZSReaderBottomBar.swift */, 3D1FFDEF23C9A68F0017ECE7 /* ZSReaderBottomBigBar.swift */, 3D1FFDF123C9A9600017ECE7 /* ZSReaderThemeSelectionView.swift */, B289DD7B23E5C51A004ED039 /* ZSReaderStyleSelectionView.swift */, 3D1330CA23C6FA9200D81EF4 /* ZSReaderTouchArea.swift */, 3D29C7AA233381A600113A25 /* ZSReaderVCProtocol.swift */, 3D29C7AC23347E6900113A25 /* ZSReaderBaseViewModel.swift */, 3DB1FDB02334A8BE00CAC8C0 /* ZSReaderCache.swift */, 3D55F4DA22D47CAD00AE0E2B /* ZSReader.swift */, B2C63A3123C1A44B0083C987 /* ZSReaderDownloader.swift */, B2C63A3A23C1ADC50083C987 /* ZSBookCache.swift */, B2C63A3323C1A57A0083C987 /* ZSBookMemoryCache.swift */, B2C63A3723C1ABA60083C987 /* ZSBookDiskCache.swift */, B2C63A3523C1A6C80083C987 /* ZSBookChapter.swift */, B222C08B23C379E000D66E3C /* ZSReadHistory.swift */, 3D1FFDF323CC52D70017ECE7 /* ThemeManager.swift */, 3D1FFDF523CC52E10017ECE7 /* Color.swift */, 3D1FFDF723CC586B0017ECE7 /* ReaderNavigationBar.swift */, 3D1FFDF923CC5B880017ECE7 /* ReaderBar.swift */, ); path = Reader; sourceTree = "<group>"; }; 3D2330E121F6F38100EFE522 /* SwiftStdlib */ = { isa = PBXGroup; children = ( 3D2330E221F6F39D00EFE522 /* FloatExtensions.swift */, 3D2330E421F9CC6F00EFE522 /* IntExtensions.swift */, ); path = SwiftStdlib; sourceTree = "<group>"; }; 3D26002424A1B61000458B58 /* uchardet */ = { isa = PBXGroup; children = ( 3D26002524A1B61000458B58 /* libuchardet-ios.a */, 3D26002624A1B61000458B58 /* uchardet.h */, ); path = uchardet; sourceTree = "<group>"; }; 3D448E5122C501490099B6E8 /* BookShelf */ = { isa = PBXGroup; children = ( B2B8A21622C70C07005527C8 /* mj_refresh_loading@2x.gif */, B2B8A21822C71641005527C8 /* mjRefreshHeadTitle.plist */, 3DEFD99023BDB12200116ECD /* ZSRefreshTextHeader.swift */, B2B8A21A22C71A23005527C8 /* ZSBookShelfHeaderView.swift */, 3DE3AF8023D0622C00D74C1F /* ZSShelfOperatingView.swift */, 3D448E5722C501C40099B6E8 /* NavigationBar.swift */, 3D05BCBF23C5EAFE001EAB2A /* ZSShelfManager.swift */, 3D1FFEEF249B523300832576 /* ZSSyncStorage.swift */, 3D1FFEF1249B562500832576 /* ZSShelfStorage.swift */, B2840F3322C85B75009C9116 /* ZSBookShelfViewModel.swift */, 3D448E5622C501C40099B6E8 /* ZSBookShelfViewController.swift */, B209975023DBFA2D00E37FD3 /* ZSBookLocalShelfViewController.swift */, 3D1FFDED23C89A4E0017ECE7 /* ZSShelfTableViewCell.swift */, 3D1E315423D1B30700A2359E /* ZSToast.swift */, ); path = BookShelf; sourceTree = "<group>"; }; 3D448E5222C501550099B6E8 /* BookStore */ = { isa = PBXGroup; children = ( 3D448E5A22C501CC0099B6E8 /* ZSBookStoreViewController.swift */, ); path = BookStore; sourceTree = "<group>"; }; 3D448E5322C5015D0099B6E8 /* Community */ = { isa = PBXGroup; children = ( 3D448E5C22C501DA0099B6E8 /* ZSCommunityViewController.swift */, B2683CFC22D0811000FE9CF0 /* ZSUserDynamicViewController.swift */, B2683D0622D17FCD00FE9CF0 /* ZSNotificationViewController.swift */, B2683D0022D089FE00FE9CF0 /* ZSDynamicHeaderView.swift */, 3DBC088E22CB02030004B3F4 /* ZSCommunityNavigationBar.swift */, 3DBC089222CB54DD0004B3F4 /* ZSCommunityCell.swift */, ADCB1BED278732E300289C83 /* ZSCommunityHotCell.swift */, 3DBC089422CB56C60004B3F4 /* ZSInsertedBookScoreView.swift */, 3D096BF622CF76A70005C9EA /* ZSRefreshFooter.swift */, B2683CFE22D083B200FE9CF0 /* ZSNoNetworkView.swift */, B2683D0822D1830A00FE9CF0 /* ZSNotificationCell.swift */, 3DBC089622CB78DB0004B3F4 /* ZSCommunityViewModel.swift */, B2683D0222D0940200FE9CF0 /* ZSDynamicViewModel.swift */, B2683D0C22D1911900FE9CF0 /* ZSNotificationViewModel.swift */, B2683D0422D0977C00FE9CF0 /* ZSFollowings.swift */, B2ACA7762787085200B8B275 /* ZSCommunityHot.swift */, B2683D0A22D188A600FE9CF0 /* ZSNotification.swift */, ); path = Community; sourceTree = "<group>"; }; 3D448E5422C501660099B6E8 /* Discover */ = { isa = PBXGroup; children = ( 3D448E5E22C501E10099B6E8 /* ZSDiscoverViewController.swift */, B2683D1622D1F08300FE9CF0 /* ZSSearchViewController.swift */, 3DBC089022CB37C00004B3F4 /* ZSDiscoverNavigationBar.swift */, B2683D0E22D1AE7900FE9CF0 /* ZSDiscoverHeaderView.swift */, B2683D1022D1B1C900FE9CF0 /* ZSDiscoverItem.swift */, ); path = Discover; sourceTree = "<group>"; }; 3D448E5522C5016F0099B6E8 /* Mine */ = { isa = PBXGroup; children = ( 3D448E6022C501E90099B6E8 /* ZSMineViewController.swift */, 3D6CCABF2379550C004D46CE /* ZSSourcesViewController.swift */, 3D6CCABD237952B3004D46CE /* ZSAddSourceViewController.swift */, AD065948258C66D1009009FA /* ZSRegularVerifyViewController.swift */, 3D6CCAC323796155004D46CE /* ZSSourceCell.swift */, 3D82F9EA22C9DD5D00F035CB /* ZSMineNavigationBar.swift */, 3D82F9EC22C9EFA800F035CB /* ZSDetailButtonCell.swift */, B2683D1222D1C57800FE9CF0 /* ZSMineHeaderView.swift */, 3D82F9EE22CA01A100F035CB /* ZSMineMenuItem.swift */, B2683D1422D1CDA900FE9CF0 /* ZSMineViewModel.swift */, 3D6CCABB23794D2C004D46CE /* ZSSetting.swift */, 3DFE8E7F2595D4270044DCEB /* ZSAddSourceTextField.h */, 3DFE8E802595D4280044DCEB /* ZSAddSourceTextField.m */, ); path = Mine; sourceTree = "<group>"; }; 3D5671FA2174CABA0049B2FF /* Model */ = { isa = PBXGroup; children = ( 3D5671FB2174CADD0049B2FF /* ZSBookCTLayoutModel.swift */, ); path = Model; sourceTree = "<group>"; }; 3D63E169235EF90A0015B7D3 /* Search */ = { isa = PBXGroup; children = ( 3D63E16A235EF9550015B7D3 /* ZSSearchBookViewController.swift */, B21C736823672D6400E9D148 /* ZSSearchInfoViewController.swift */, 3D05BCB923C5ACE8001EAB2A /* ZSSearchInfoBottomView.swift */, 3D63E16C235EF9B80015B7D3 /* ZSSearchBookView.swift */, 3D63E16E235EF9E60015B7D3 /* ZSSearchResultView.swift */, 3D63E172235F021F0015B7D3 /* ZSHeaderSearchCell.swift */, B222C08923C1CAA800D66E3C /* ZSSearchInfoTableViewCell.swift */, B21C732A2365774B00E9D148 /* ZSSearchResultCell.swift */, 3D63E174235F02670015B7D3 /* ZSSearchHotView.swift */, 3D63E18E23603CD80015B7D3 /* ZSSearchRecommendView.swift */, 3D63E170235EFAE70015B7D3 /* ZSTopSearchBar.swift */, B21C736A2367304800E9D148 /* ZSBookInfoHeaderView.swift */, 3D63E176235F03C40015B7D3 /* ZSSearchBookViewModel.swift */, 3D6CCAC1237956FB004D46CE /* ZSSourceManager.swift */, 3D63E178235F14BF0015B7D3 /* ZSSearchHotwords.swift */, 3D63E17A235F18F40015B7D3 /* ZSHotWord.swift */, 3D64030523BF5E8500D4B9EB /* ZSSearchHistory.swift */, 3D63E190236043250015B7D3 /* ZSHeaderSearch.swift */, B21C736123657B1200E9D148 /* AikanParserModel.h */, B21C736023657B1100E9D148 /* AikanParserModel.m */, B21C736323657B3F00E9D148 /* AikanHtmlParser.h */, B21C736423657B3F00E9D148 /* AikanHtmlParser.m */, 3D05BCBD23C5D424001EAB2A /* AikanParserModel.swift */, 3D0D709C23C88B30006902E8 /* HtmlParserModelData_edit.dat */, B21C736623657E1400E9D148 /* HtmlParserModelData.dat */, ); path = Search; sourceTree = "<group>"; }; 3D88A7812670EBCB00C87AB5 /* Device */ = { isa = PBXGroup; children = ( 3D88A7822670EBCB00C87AB5 /* ZSFloatingManager.swift */, 3D88A7832670EBCB00C87AB5 /* ZSMemoryFloatingView.swift */, 3D88A7842670EBCB00C87AB5 /* ZSFloatingWindow.swift */, 3D88A7852670EBCB00C87AB5 /* ZSFloatingViewController.swift */, 3D88A7862670EBCB00C87AB5 /* ZSFloatingView.swift */, ); path = Device; sourceTree = "<group>"; }; 3D9325E320B2C4220049CDBF /* ViewManager */ = { isa = PBXGroup; children = ( 3D9325E520B2C44A0049CDBF /* ZSBaseTableViewManger.swift */, 3D968CF52133917900DF3279 /* ZSCellAdapterProtocol.swift */, 3D2330DD21F5F04700EFE522 /* ZSBookDownloader.swift */, 3D2330DF21F6F05A00EFE522 /* ZSAppInfo.swift */, ); path = ViewManager; sourceTree = "<group>"; }; 3D9325E420B2C4220049CDBF /* ViewModel */ = { isa = PBXGroup; children = ( ); path = ViewModel; sourceTree = "<group>"; }; 3D9325EA20B2C8C50049CDBF /* ViewModel */ = { isa = PBXGroup; children = ( 3D9325EB20B2C9120049CDBF /* ZSHomeViewModel.swift */, B252D4E1211035060091858E /* ZSShelfViewModel.swift */, B2A39C492110453100D6308E /* ZSLocalShelfViewModel.swift */, 3D58635E20CE1C96002AD3CC /* ZSProtocol.swift */, B2ACA0622124467F0032305E /* ZSSettingViewModel.swift */, 3D08F241212C2DC6007B3D19 /* ZSDiscussViewModel.swift */, B29CFB78217B21C60006F294 /* ZSMyViewModel.swift */, 3DECC3952246298300EEB146 /* ZSVoicePlayViewModel.swift */, B2ACA06421244B3D0032305E /* ZSSetting.plist */, ); path = ViewModel; sourceTree = "<group>"; }; 3D998935223FD5AA00EA33D5 /* ViewModel */ = { isa = PBXGroup; children = ( B22AC4521E9F1CA8008625E6 /* ZSCatelogDetailViewModel.swift */, 3D998936223FDFB900EA33D5 /* ZSSegmentBaseViewModel.swift */, 3D998938223FEA0400EA33D5 /* ZSCatelogViewModel.swift */, ); path = ViewModel; sourceTree = "<group>"; }; 3DABFC2922B9182000ECB5E3 /* Tabbar */ = { isa = PBXGroup; children = ( 3DABFC2A22B9183D00ECB5E3 /* ZSTabBarController.swift */, ); path = Tabbar; sourceTree = "<group>"; }; 3DD0840321569850008E3B4A /* Speech */ = { isa = PBXGroup; children = ( 3DD08405215698CE008E3B4A /* Model */, 3DD08404215698CE008E3B4A /* View */, ); path = Speech; sourceTree = "<group>"; }; 3DD08404215698CE008E3B4A /* View */ = { isa = PBXGroup; children = ( 3DD0842421569A95008E3B4A /* AKPickerView.h */, 3DD0842321569A95008E3B4A /* AKPickerView.m */, 3DD0842C2157604D008E3B4A /* ZSSpeechView.swift */, ); path = View; sourceTree = "<group>"; }; 3DD08405215698CE008E3B4A /* Model */ = { isa = PBXGroup; children = ( 3D3BA0032160C77E00B0C80E /* speakers.plist */, 3D3B9FFF2160C5D700B0C80E /* speaker.json */, 3DD0842E2157A14B008E3B4A /* pcmPlayerCode */, 3DD0841F21569A65008E3B4A /* TTSResource */, 3DD0841D21569A54008E3B4A /* TTSConfig.swift */, 3DD0840A21569902008E3B4A /* Speaker.swift */, 3DD0842A215750C6008E3B4A /* Network.swift */, 3DD0842121569A74008E3B4A /* VoiceBook.swift */, 3DD08406215698E5008E3B4A /* iflyMSC.framework */, ); path = Model; sourceTree = "<group>"; }; 3DD0842E2157A14B008E3B4A /* pcmPlayerCode */ = { isa = PBXGroup; children = ( 3DD0842F2157A14B008E3B4A /* PcmPlayer.h */, 3DD084302157A14B008E3B4A /* PcmPlayerDelegate.h */, 3DD084312157A14B008E3B4A /* PcmPlayer.m */, ); path = pcmPlayerCode; sourceTree = "<group>"; }; 3DDB6C942130F4BC00E8698D /* Source */ = { isa = PBXGroup; children = ( 3DDB6CB32130F63200E8698D /* RegexKitLite.h */, 3DDB6CB42130F63200E8698D /* RegexKitLite.m */, 3DDB6CB12130F56200E8698D /* CTDisplayText.h */, 3DDB6C952130F4BC00E8698D /* CoreTextUtils.m */, 3DDB6C962130F4BC00E8698D /* CTFrameParser.m */, 3DDB6C972130F4BC00E8698D /* CTFrameParserConfig.h */, 3DDB6C982130F4BC00E8698D /* CoreTextImageData.m */, 3DDB6C992130F4BC00E8698D /* CoreTextData.h */, 3DDB6C9A2130F4BC00E8698D /* CoreTextLinkData.h */, 3DDB6C9B2130F4BC00E8698D /* CTFrameParser.h */, 3DDB6C9C2130F4BC00E8698D /* CoreTextUtils.h */, 3DDB6C9D2130F4BC00E8698D /* CoreTextImageData.h */, 3DDB6C9E2130F4BC00E8698D /* CTFrameParserConfig.m */, 3DDB6C9F2130F4BC00E8698D /* CoreTextData.m */, 3DDB6CA02130F4BC00E8698D /* Views */, 3DDB6CA72130F4BC00E8698D /* CoreTextLinkData.m */, ); path = Source; sourceTree = "<group>"; }; 3DDB6CA02130F4BC00E8698D /* Views */ = { isa = PBXGroup; children = ( 3DDB6CA22130F4BC00E8698D /* MagnifiterView.h */, 3DDB6CA52130F4BC00E8698D /* MagnifiterView.m */, 3DDB6CA42130F4BC00E8698D /* CTDisplayView.h */, 3DDB6CA12130F4BC00E8698D /* CTDisplayView.m */, 3DDB6CA62130F4BC00E8698D /* UIView+frameAdjust.h */, 3DDB6CA32130F4BC00E8698D /* UIView+frameAdjust.m */, ); path = Views; sourceTree = "<group>"; }; 3DE77D2820C90E8900A86DF0 /* Service */ = { isa = PBXGroup; children = ( 3DE77D2920C90EA300A86DF0 /* ZSRootWebService.swift */, B2C0DF8020D4CC5A0057DC84 /* ZSBaseService.swift */, B252D4DF211033F20091858E /* ZSShelfWebService.swift */, 3D08F243212C2DFC007B3D19 /* ZSDiscussWebService.swift */, B29CFAE52179B6E50006F294 /* ZSLoginService.swift */, B29CFB72217B1CC00006F294 /* ZSMyService.swift */, ); path = Service; sourceTree = "<group>"; }; 3DECC38222452A7500EEB146 /* ViewModel */ = { isa = PBXGroup; children = ( B2B02E4D1EA9A9F000A6880A /* ZSThemeTopicViewModel.swift */, 3DECC38322452F9E00EEB146 /* ZSFilterThemeViewModel.swift */, ); path = ViewModel; sourceTree = "<group>"; }; 3DECC39722464C4E00EEB146 /* XimalayaSDK_iOS_5.5.1 */ = { isa = PBXGroup; children = ( 3DECC39822464C4E00EEB146 /* libXMOpenPlatform.a */, 3DECC39922464C4E00EEB146 /* include */, ); path = XimalayaSDK_iOS_5.5.1; sourceTree = "<group>"; }; 3DECC39922464C4E00EEB146 /* include */ = { isa = PBXGroup; children = ( 3DECC39A22464C4E00EEB146 /* Downloader */, 3DECC39C22464C4E00EEB146 /* Model */, 3DECC3C522464C4E00EEB146 /* Request */, 3DECC3D222464C4F00EEB146 /* Resource */, 3DECC3D422464C4F00EEB146 /* Authorization */, 3DECC3D622464C4F00EEB146 /* Player */, 3DECC3DA22464C4F00EEB146 /* Utility */, ); path = include; sourceTree = "<group>"; }; 3DECC39A22464C4E00EEB146 /* Downloader */ = { isa = PBXGroup; children = ( 3DECC39B22464C4E00EEB146 /* XMSDKDownloadManager.h */, ); path = Downloader; sourceTree = "<group>"; }; 3DECC39C22464C4E00EEB146 /* Model */ = { isa = PBXGroup; children = ( 3DECC39D22464C4E00EEB146 /* XMAttribute.h */, 3DECC39E22464C4E00EEB146 /* XMAlbum.h */, 3DECC39F22464C4E00EEB146 /* XMCategory.h */, 3DECC3A022464C4E00EEB146 /* XMColdbootTag.h */, 3DECC3A122464C4E00EEB146 /* XMHotTrack.h */, 3DECC3A222464C4E00EEB146 /* XMDimension.h */, 3DECC3A322464C4E00EEB146 /* XMColumn.h */, 3DECC3A422464C4E00EEB146 /* XMAlbumColumn.h */, 3DECC3A522464C4E00EEB146 /* XMTrack.h */, 3DECC3A622464C4E00EEB146 /* XMRadio.h */, 3DECC3A722464C4E00EEB146 /* XMTag.h */, 3DECC3A822464C4E00EEB146 /* XMIndexRankItem.h */, 3DECC3A922464C4E00EEB146 /* XMColdbootDetail.h */, 3DECC3AA22464C4E00EEB146 /* XMRadioSchedule.h */, 3DECC3AB22464C4E00EEB146 /* XMCategoryHumanRecommend.h */, 3DECC3AC22464C4E00EEB146 /* XMSubordinatedAlbum.h */, 3DECC3AD22464C4E00EEB146 /* XMTrackColumnItem.h */, 3DECC3AE22464C4E00EEB146 /* XMLiveAnnouncer.h */, 3DECC3AF22464C4E00EEB146 /* XMAnnouncer.h */, 3DECC3B022464C4E00EEB146 /* XMTrackDownloadStatus.h */, 3DECC3B122464C4E00EEB146 /* XMErrorModel.h */, 3DECC3B222464C4E00EEB146 /* XMLastUptrack.h */, 3DECC3B322464C4E00EEB146 /* XMAlbumGuessLike.h */, 3DECC3B422464C4E00EEB146 /* XMCacheTrack.h */, 3DECC3B522464C4E00EEB146 /* XMBanner.h */, 3DECC3B622464C4E00EEB146 /* XMLiveCity.h */, 3DECC3B722464C4E00EEB146 /* XMMetadata.h */, 3DECC3B822464C4E00EEB146 /* XMColumnList.h */, 3DECC3B922464C4E00EEB146 /* XMColumnDetail.h */, 3DECC3BA22464C4E00EEB146 /* XMAlbumColumnItem.h */, 3DECC3BB22464C4E00EEB146 /* XMProvince.h */, 3DECC3BC22464C4E00EEB146 /* XMTrackColumn.h */, 3DECC3BD22464C4E00EEB146 /* XMHotword.h */, 3DECC3BE22464C4E00EEB146 /* XMRankSectionList.h */, 3DECC3BF22464C4E00EEB146 /* XMColumnEditor.h */, 3DECC3C022464C4E00EEB146 /* XMRelatedProgram.h */, 3DECC3C122464C4E00EEB146 /* XMAnnouncerCategory.h */, ); path = Model; sourceTree = "<group>"; }; 3DECC3C522464C4E00EEB146 /* Request */ = { isa = PBXGroup; children = ( 3DECC3C622464C4E00EEB146 /* XMReqMgr.h */, ); path = Request; sourceTree = "<group>"; }; 3DECC3D222464C4F00EEB146 /* Resource */ = { isa = PBXGroup; children = ( 3DECC3D322464C4F00EEB146 /* XMResource.bundle */, ); path = Resource; sourceTree = "<group>"; }; 3DECC3D422464C4F00EEB146 /* Authorization */ = { isa = PBXGroup; children = ( 3DECC3D522464C4F00EEB146 /* XMLYAuthorize.h */, ); path = Authorization; sourceTree = "<group>"; }; 3DECC3D622464C4F00EEB146 /* Player */ = { isa = PBXGroup; children = ( 3DECC3D722464C4F00EEB146 /* XMSDKPlayerDataCollector.h */, 3DECC3D822464C4F00EEB146 /* XMADAudioPlayer.h */, 3DECC3D922464C4F00EEB146 /* XMSDKPlayer.h */, ); path = Player; sourceTree = "<group>"; }; 3DECC3DA22464C4F00EEB146 /* Utility */ = { isa = PBXGroup; children = ( 3DECC3DB22464C4F00EEB146 /* XMSingleTone.h */, 3DECC3DC22464C4F00EEB146 /* XMSDKInfo.h */, 3DECC3DD22464C4F00EEB146 /* XMSDK.h */, ); path = Utility; sourceTree = "<group>"; }; 3DF2D27320EB375A004E73B6 /* View */ = { isa = PBXGroup; children = ( 3D31FFB02216E1850011D275 /* ZSChapterPayView.swift */, 3D65504A219577DB0064BA0C /* ZSMultiplePayView.swift */, 3D65504C21957B410064BA0C /* ZSChapterSelectView.swift */, 3D2B4D0220EB616D008E3E81 /* ZSReaderViewController.swift */, 3D2B4D0420EB6552008E3E81 /* ZSNoneAnimationViewController.swift */, B2A02F7B20F337C20034DC64 /* ZSHorizonalMoveViewController.swift */, B2A02F7D20F33A1A0034DC64 /* ZSHorizonalMoveCell.swift */, 3DDEEAF8214A8E3F003D12DB /* ZSFontViewController.swift */, 3D2006D0216F36BE00C326B4 /* ZSReaderBaseViewController.swift */, ); path = View; sourceTree = "<group>"; }; 3DF2D27420EB375A004E73B6 /* Service */ = { isa = PBXGroup; children = ( B2C0DEB420D4CB340057DC84 /* ZSReaderWebService.swift */, B2A02F7F20F33F710034DC64 /* ZSReaderManager.swift */, 3DDEEAFA214A8EC8003D12DB /* ZSFontService.swift */, ); path = Service; sourceTree = "<group>"; }; 3DF2D27520EB375A004E73B6 /* ViewModel */ = { isa = PBXGroup; children = ( B2C0DEB220D4CB340057DC84 /* ZSReaderViewModel.swift */, 3D31FFAE2216BDAD0011D275 /* ZSReaderViewModel+Bought.swift */, 3DE393DA219D514100890488 /* ZSBookBoughtViewModel.swift */, 3DDEEAFC214A8EE1003D12DB /* ZSFontViewModel.swift */, 3D3AB5DF214E9ABD00E9C246 /* ltt.ttf */, 3D3AB5DD214E9A8300E9C246 /* fz-kt.ttf */, 3D3AB5DB214E99FE00E9C246 /* fz-wbt.ttf */, 3D3AB5D9214E99CB00E9C246 /* ypt.ttf */, 3D3AB5D5214E993E00E9C246 /* hkppt.ttf */, 3D3AB5D3214E98B800E9C246 /* Redocn.ttf */, 3D3AB5D1214E98AC00E9C246 /* hylst.ttf */, ); path = ViewModel; sourceTree = "<group>"; }; 3DF2D27620EB375A004E73B6 /* Model */ = { isa = PBXGroup; children = ( 3DF2D27720EB5773004E73B6 /* ZSChapterInfo.swift */, 3DF2D27920EB5C1C004E73B6 /* ZSChapterBody.swift */, 3D2B4D0620EB68AC008E3E81 /* ZSReaderProtocol.swift */, 3D865B54219B1975001294EB /* ZSChapterSelectModel.swift */, 3DE393D8219D505500890488 /* ZSBoughtInfo.swift */, B252D4A3210EF7070091858E /* ZSHTTPConnection.h */, B252D4A4210EF7070091858E /* ZSHTTPConnection.m */, B252D4AB210EFA660091858E /* ZSHTTPTool.h */, B252D4AC210EFA660091858E /* ZSHTTPTool.m */, B252D4DA210F583B0091858E /* MonitorFileChangeHelp.h */, B252D4DB210F583B0091858E /* MonitorFileChangeHelp.m */, B252D4AE210EFF260091858E /* web */, ); path = Model; sourceTree = "<group>"; }; 96FE9B86D6BF6F1F079B71F4 /* Frameworks */ = { isa = PBXGroup; children = ( 3DF623A42779AF180049FD73 /* libc++abi.tbd */, 3DF623A22779AF010049FD73 /* libresolv.9.tbd */, 3DF623A02779AEF50049FD73 /* libiconv.tbd */, 3DF6239E2779AEE00049FD73 /* libbz2.tbd */, 3DF6239A2779AEA20049FD73 /* MediaPlayer.framework */, 3DF623982779AE8C0049FD73 /* MapKit.framework */, 3DF623962779AE7B0049FD73 /* JavaScriptCore.framework */, 3DF623942779AE4D0049FD73 /* CoreLocation.framework */, 3DF623922779AE230049FD73 /* AppTrackingTransparency.framework */, 3DF623902779AE100049FD73 /* AdSupport.framework */, 3DBC089822CB79830004B3F4 /* ZSAPI.framework */, B2A7CD82217F33230067D25B /* Photos.framework */, 3DD0843B2157C135008E3B4A /* Contacts.framework */, 3DD084392157C124008E3B4A /* AddressBook.framework */, 3DD084372157C101008E3B4A /* AudioToolbox.framework */, 3DD084352157C0ED008E3B4A /* libc++.tbd */, 3DD084332157C0BA008E3B4A /* libstdc++.6.0.9.tbd */, 3DDB6CB62130F73000E8698D /* libicucore.tbd */, B252D4A1210EF4600091858E /* libxml2.2.tbd */, B252D49F210EF4470091858E /* libxml2.tbd */, B252D49D210EF4190091858E /* Security.framework */, B2FB92101EB1EEDF000C990A /* CoreTelephony.framework */, B251228C1DB3291A0040CAD3 /* CFNetwork.framework */, B22182CE1DA40594002458D2 /* libz.tbd */, B22182CC1DA4058E002458D2 /* libsqlite3.tbd */, B22182CA1DA40588002458D2 /* SystemConfiguration.framework */, B22182C81DA40582002458D2 /* MobileCoreServices.framework */, B22182C61DA4057A002458D2 /* Accelerate.framework */, B22182C41DA40570002458D2 /* AssetsLibrary.framework */, B22182C21DA40566002458D2 /* ImageIO.framework */, B22182C01DA4055E002458D2 /* QuartzCore.framework */, B22182BE1DA40558002458D2 /* CoreImage.framework */, B22182BC1DA40550002458D2 /* CoreGraphics.framework */, B22182BA1DA40548002458D2 /* CoreFoundation.framework */, B22182B81DA40540002458D2 /* CoreText.framework */, E6DD667D5A369B09442DE895 /* Pods_zhuishushenqi.framework */, ); name = Frameworks; sourceTree = "<group>"; }; B2091C2A22B158F500AD550A /* NewVersion */ = { isa = PBXGroup; children = ( B253EE402786E8A700F6C6D9 /* Network */, B253EE3B2784355A00F6C6D9 /* File */, 3D88A7812670EBCB00C87AB5 /* Device */, 3D63E169235EF90A0015B7D3 /* Search */, B25CEFF222F92917002ABE30 /* Comment */, B25CEFE922F9265F002ABE30 /* CoreText */, 3D17A02522D47331001FAC0C /* Reader */, 3D448E5522C5016F0099B6E8 /* Mine */, 3D448E5422C501660099B6E8 /* Discover */, 3D448E5322C5015D0099B6E8 /* Community */, 3D448E5222C501550099B6E8 /* BookStore */, 3D448E5122C501490099B6E8 /* BookShelf */, 3DABFC2922B9182000ECB5E3 /* Tabbar */, ); path = NewVersion; sourceTree = "<group>"; }; B21C732C23657ACE00E9D148 /* OCGumbo */ = { isa = PBXGroup; children = ( B21C732D23657ACE00E9D148 /* gumbo */, B21C734D23657ACE00E9D148 /* OCGumbo+Query.h */, B21C734E23657ACE00E9D148 /* OCGumbo.h */, B21C734F23657ACE00E9D148 /* OCGumbo+Query.m */, B21C735023657ACE00E9D148 /* OCGumbo.m */, ); path = OCGumbo; sourceTree = "<group>"; }; B21C732D23657ACE00E9D148 /* gumbo */ = { isa = PBXGroup; children = ( B21C732E23657ACE00E9D148 /* string_buffer.c */, B21C732F23657ACE00E9D148 /* error.h */, B21C733023657ACE00E9D148 /* tag_enum.h */, B21C733123657ACE00E9D148 /* util.c */, B21C733223657ACE00E9D148 /* char_ref.rl */, B21C733323657ACE00E9D148 /* insertion_mode.h */, B21C733423657ACE00E9D148 /* parser.h */, B21C733523657ACE00E9D148 /* vector.c */, B21C733623657ACE00E9D148 /* attribute.c */, B21C733723657ACE00E9D148 /* char_ref.c */, B21C733823657ACE00E9D148 /* tokenizer.h */, B21C733923657ACE00E9D148 /* utf8.c */, B21C733A23657ACE00E9D148 /* string_piece.h */, B21C733B23657ACE00E9D148 /* tokenizer_states.h */, B21C733C23657ACE00E9D148 /* token_type.h */, B21C733D23657ACE00E9D148 /* tag_gperf.h */, B21C733E23657ACE00E9D148 /* string_buffer.h */, B21C733F23657ACE00E9D148 /* error.c */, B21C734023657ACE00E9D148 /* tag.in */, B21C734123657ACE00E9D148 /* util.h */, B21C734223657ACE00E9D148 /* tag.c */, B21C734323657ACE00E9D148 /* char_ref.h */, B21C734423657ACE00E9D148 /* vector.h */, B21C734523657ACE00E9D148 /* attribute.h */, B21C734623657ACE00E9D148 /* parser.c */, B21C734723657ACE00E9D148 /* tag_sizes.h */, B21C734823657ACE00E9D148 /* tag_strings.h */, B21C734923657ACE00E9D148 /* utf8.h */, B21C734A23657ACE00E9D148 /* tokenizer.c */, B21C734B23657ACE00E9D148 /* string_piece.c */, B21C734C23657ACE00E9D148 /* gumbo.h */, ); path = gumbo; sourceTree = "<group>"; }; B22204961EE8FC45008E1902 /* Splash */ = { isa = PBXGroup; children = ( B23505BF2783F8EC00D444C7 /* SplashViewController.swift */, B22204A11EE9284F008E1902 /* QSSplashScreen.swift */, ); path = Splash; sourceTree = "<group>"; }; B252D454210EF3960091858E /* CocoaHTTPServer */ = { isa = PBXGroup; children = ( B252D455210EF3960091858E /* Core */, B252D47B210EF3960091858E /* Extensions */, B252D485210EF3960091858E /* README.markdown */, B252D486210EF3960091858E /* LICENSE.txt */, ); path = CocoaHTTPServer; sourceTree = "<group>"; }; B252D455210EF3960091858E /* Core */ = { isa = PBXGroup; children = ( B252D456210EF3960091858E /* HTTPConnection.h */, B252D457210EF3960091858E /* HTTPLogging.h */, B252D458210EF3960091858E /* HTTPMessage.h */, B252D459210EF3960091858E /* WebSocket.h */, B252D45A210EF3960091858E /* HTTPAuthenticationRequest.h */, B252D45B210EF3960091858E /* Responses */, B252D466210EF3960091858E /* HTTPServer.h */, B252D467210EF3960091858E /* HTTPMessage.m */, B252D468210EF3960091858E /* HTTPConnection.m */, B252D469210EF3960091858E /* WebSocket.m */, B252D46A210EF3960091858E /* HTTPResponse.h */, B252D46B210EF3960091858E /* Mime */, B252D472210EF3960091858E /* HTTPAuthenticationRequest.m */, B252D473210EF3960091858E /* Categories */, B252D47A210EF3960091858E /* HTTPServer.m */, ); path = Core; sourceTree = "<group>"; }; B252D45B210EF3960091858E /* Responses */ = { isa = PBXGroup; children = ( B252D45C210EF3960091858E /* HTTPAsyncFileResponse.h */, B252D45D210EF3960091858E /* HTTPDataResponse.m */, B252D45E210EF3960091858E /* HTTPRedirectResponse.h */, B252D45F210EF3960091858E /* HTTPDynamicFileResponse.m */, B252D460210EF3960091858E /* HTTPFileResponse.m */, B252D461210EF3960091858E /* HTTPAsyncFileResponse.m */, B252D462210EF3960091858E /* HTTPRedirectResponse.m */, B252D463210EF3960091858E /* HTTPDataResponse.h */, B252D464210EF3960091858E /* HTTPFileResponse.h */, B252D465210EF3960091858E /* HTTPDynamicFileResponse.h */, ); path = Responses; sourceTree = "<group>"; }; B252D46B210EF3960091858E /* Mime */ = { isa = PBXGroup; children = ( B252D46C210EF3960091858E /* MultipartFormDataParser.h */, B252D46D210EF3960091858E /* MultipartMessageHeader.h */, B252D46E210EF3960091858E /* MultipartMessageHeaderField.m */, B252D46F210EF3960091858E /* MultipartFormDataParser.m */, B252D470210EF3960091858E /* MultipartMessageHeader.m */, B252D471210EF3960091858E /* MultipartMessageHeaderField.h */, ); path = Mime; sourceTree = "<group>"; }; B252D473210EF3960091858E /* Categories */ = { isa = PBXGroup; children = ( B252D474210EF3960091858E /* DDNumber.m */, B252D475210EF3960091858E /* DDData.m */, B252D476210EF3960091858E /* DDRange.h */, B252D477210EF3960091858E /* DDNumber.h */, B252D478210EF3960091858E /* DDRange.m */, B252D479210EF3960091858E /* DDData.h */, ); path = Categories; sourceTree = "<group>"; }; B252D47B210EF3960091858E /* Extensions */ = { isa = PBXGroup; children = ( B252D47C210EF3960091858E /* WebDAV */, ); path = Extensions; sourceTree = "<group>"; }; B252D47C210EF3960091858E /* WebDAV */ = { isa = PBXGroup; children = ( B252D47D210EF3960091858E /* DAVConnection.m */, B252D47E210EF3960091858E /* DAVResponse.h */, B252D47F210EF3960091858E /* DELETEResponse.m */, B252D480210EF3960091858E /* PUTResponse.h */, B252D481210EF3960091858E /* DAVConnection.h */, B252D482210EF3960091858E /* DAVResponse.m */, B252D483210EF3960091858E /* PUTResponse.m */, B252D484210EF3960091858E /* DELETEResponse.h */, ); path = WebDAV; sourceTree = "<group>"; }; B252D4AE210EFF260091858E /* web */ = { isa = PBXGroup; children = ( B252D4C6210F163C0091858E /* txt.jpeg */, B252D4BF210F0B980091858E /* bg.png */, B252D4BE210F0B980091858E /* s.css */, B252D4C1210F0B990091858E /* theadbg.png */, B252D4C0210F0B990091858E /* titlebg.png */, B252D4AF210EFF260091858E /* index.html */, B252D4B0210EFF260091858E /* upload.html */, ); path = web; sourceTree = "<group>"; }; B253EE3B2784355A00F6C6D9 /* File */ = { isa = PBXGroup; children = ( B253EE3C2784358900F6C6D9 /* EncrtptorText */, ); path = File; sourceTree = "<group>"; }; B253EE402786E8A700F6C6D9 /* Network */ = { isa = PBXGroup; children = ( B253EE412786E95B00F6C6D9 /* ZSNetwork.swift */, ); path = Network; sourceTree = "<group>"; }; B255707B1F3A075600C35A34 /* App */ = { isa = PBXGroup; children = ( B255707C1F3A075600C35A34 /* AppDelegate.swift */, 3DF6238C277970260049FD73 /* AppOpenAdManager.swift */, 3DF623A62779B09A0049FD73 /* BUAdManager.swift */, B25570801F3A07BB00C35A34 /* AppStyle.swift */, B255707D1F3A075600C35A34 /* Config.swift */, B25570861F3A963300C35A34 /* Theme.swift */, B25570881F3A96E500C35A34 /* BookManager.swift */, B2E072771F3AD99500C63347 /* Reader.swift */, B2CA80EF21797EA2007A3EE7 /* ZSThirdLogin.swift */, B29CFB6A217A3D8F0006F294 /* ZSLogin.swift */, B2A7CD88218017380067D25B /* ZSMobileLogin.swift */, 3D252CDF219175FC0051B60D /* ZSDatabase.swift */, B25EB92B2194986C000D3657 /* ZSEncryptorAESUtils.swift */, 3DE393EA21A3EB2D00890488 /* ZSJSON.swift */, B25EB92D2194999E000D3657 /* FBEncryptorAESUtils.h */, B25EB92E2194999E000D3657 /* FBEncryptorAESUtils.mm */, B25EB93021949AF1000D3657 /* FBEncryptorAES.h */, B25EB93121949AF1000D3657 /* FBEncryptorAES.m */, B266C66821A6FCC3006B5A15 /* ZSDBManager.h */, B266C66921A6FCC3006B5A15 /* ZSDBManager.m */, B2662EBA21A7DAC5008AD5C6 /* ZSDBPropertyModel.h */, B2662EBB21A7DAC5008AD5C6 /* ZSDBPropertyModel.m */, ); path = App; sourceTree = "<group>"; }; B2580E211E9B279A00455AFE /* Network */ = { isa = PBXGroup; children = ( B2580E221E9B284900455AFE /* QSHotwords.swift */, ); path = Network; sourceTree = "<group>"; }; B2583CAC1EA78280004178F3 /* Filter */ = { isa = PBXGroup; children = ( B2583CAD1EA78280004178F3 /* ZSFilterThemeViewController.swift */, ); path = Filter; sourceTree = "<group>"; }; B2583CAE1EA78280004178F3 /* ThemeTopic */ = { isa = PBXGroup; children = ( B2583CB51EA782C2004178F3 /* QSThemeTopicInteractor.swift */, B2583CB61EA782C2004178F3 /* QSThemeTopicPresenter.swift */, B2583CB71EA782C2004178F3 /* QSThemeTopicProtocols.swift */, B2583CB81EA782C2004178F3 /* QSThemeTopicRouter.swift */, B2583CB91EA782C2004178F3 /* QSThemeTopicViewController.swift */, ); path = ThemeTopic; sourceTree = "<group>"; }; B2583CB01EA78280004178F3 /* TopicDetail */ = { isa = PBXGroup; children = ( B2583CB11EA78280004178F3 /* TopicDetailViewController.swift */, B2B02E251EA8505200A6880A /* QSTopicDetailInteractor.swift */, B2B02E261EA8505200A6880A /* QSTopicDetailPresenter.swift */, B2B02E271EA8505200A6880A /* QSTopicDetailProtocols.swift */, B2B02E281EA8505200A6880A /* QSTopicDetailRouter.swift */, B2B02E291EA8505200A6880A /* QSTopicDetailViewController.swift */, ); path = TopicDetail; sourceTree = "<group>"; }; B25A70E0223E961900D0182D /* ViewModel */ = { isa = PBXGroup; children = ( B22AC4501E9F1C8C008625E6 /* ZSRankViewModel.swift */, B22AC45C1E9F678D008625E6 /* ZSRankDetailViewModel.swift */, ); path = ViewModel; sourceTree = "<group>"; }; B25CEFE922F9265F002ABE30 /* CoreText */ = { isa = PBXGroup; children = ( B25CEFEC22F92689002ABE30 /* CTSettings.swift */, B25CEFEA22F92688002ABE30 /* MarkupParser.swift */, B25CEFEB22F92689002ABE30 /* ZSDisplayView.swift */, B25CEFED22F9268A002ABE30 /* ZSTouchAnchorView.swift */, ); path = CoreText; sourceTree = "<group>"; }; B25CEFF222F92917002ABE30 /* Comment */ = { isa = PBXGroup; children = ( B25CEFF322F92AE4002ABE30 /* ZSForumPageViewController.swift */, B25CEFF722F97937002ABE30 /* ZSForumPageHeaderView.swift */, B2BCE0C32306530A00E52903 /* ZSForumPageTitleHeaderView.swift */, B24BED4F23017CB500CF8C4D /* ZSForumPageFooterView.swift */, B25D1AD8230293BB00B1F786 /* ZSForumPageCell.swift */, B24BED4D2301692700CF8C4D /* ZSForumToolBar.swift */, B29E275E230A87E800BA6E33 /* ZSForumTextView.swift */, B25CEFF522F92C5E002ABE30 /* ZSForumViewModel.swift */, B24BED512301865400CF8C4D /* ZSForumComment.swift */, B25CEFF922FA7BB2002ABE30 /* ZSPostReview.swift */, B25CF00522FAA731002ABE30 /* ZSPostReviewAuthor.swift */, B25CF00722FAA732002ABE30 /* ZSPostReviewBook.swift */, B25CF00622FAA732002ABE30 /* ZSPostReviewHelpful.swift */, ); path = Comment; sourceTree = "<group>"; }; B26286BE1EEA46C7005A1A8A /* IntroducePage */ = { isa = PBXGroup; children = ( B26286C91EEA46DD005A1A8A /* ZSIntroducePage.swift */, B20D74061EED7D250034516F /* QSIntroduceCell.swift */, B20D74071EED7D250034516F /* QSIntroduceCell.xib */, B20D74141EED83690034516F /* QSLastIntroduceCell.swift */, B20D74151EED83690034516F /* QSLastIntroduceCell.xib */, B200D1FD1EEDB1FE003A42E3 /* QSIntroduceReadCell.swift */, B200D1FE1EEDB1FE003A42E3 /* QSIntroduceReadCell.xib */, ); path = IntroducePage; sourceTree = "<group>"; }; B278AA9F1F396773000A0D55 /* Vendor */ = { isa = PBXGroup; children = ( 3D26002424A1B61000458B58 /* uchardet */, B21C732C23657ACE00E9D148 /* OCGumbo */, 3DECC39722464C4E00EEB146 /* XimalayaSDK_iOS_5.5.1 */, B2CA80E821797B9C007A3EE7 /* ThirdLoginSDK */, B252D454210EF3960091858E /* CocoaHTTPServer */, B2D8688520050C2800B9AF01 /* CTDisplayText */, B278AAFB1F398926000A0D55 /* M80AttributedLabel */, B278AABF1F396773000A0D55 /* YTK */, ); name = Vendor; path = zhuishushenqi/Vendor; sourceTree = SOURCE_ROOT; }; B278AABF1F396773000A0D55 /* YTK */ = { isa = PBXGroup; children = ( B278AAC01F396773000A0D55 /* YTKKeyValueStore.h */, B278AAC11F396773000A0D55 /* YTKKeyValueStore.m */, ); path = YTK; sourceTree = "<group>"; }; B278AAFB1F398926000A0D55 /* M80AttributedLabel */ = { isa = PBXGroup; children = ( B278AAFC1F398926000A0D55 /* M80AttributedLabel.h */, B278AAFD1F398926000A0D55 /* M80AttributedLabel.m */, B278AAFE1F398926000A0D55 /* M80AttributedLabelAttachment.h */, B278AAFF1F398926000A0D55 /* M80AttributedLabelAttachment.m */, B278AB001F398926000A0D55 /* M80AttributedLabelDefines.h */, B278AB011F398926000A0D55 /* M80AttributedLabelURL.h */, B278AB021F398926000A0D55 /* M80AttributedLabelURL.m */, B278AB031F398926000A0D55 /* NSMutableAttributedString+M80.h */, B278AB041F398926000A0D55 /* NSMutableAttributedString+M80.m */, ); path = M80AttributedLabel; sourceTree = "<group>"; }; B2ACA02021217D970032305E /* Service */ = { isa = PBXGroup; children = ( B22AC45A1E9F6774008625E6 /* ZSRankDetailWebService.swift */, B2ACA02121217DFB0032305E /* ZSRankService.swift */, ); path = Service; sourceTree = "<group>"; }; B2AF57E82675FB840080A065 /* images */ = { isa = PBXGroup; children = ( B2AF57E92675FB850080A065 /* qs_bookshelf.png */, B2AF57EA2675FB850080A065 /* style.bundle */, B2AF57EB2675FB850080A065 /* regular_verify.png */, B2AF57EC2675FB850080A065 /* qs_readerMain.png */, B2AF57ED2675FB850080A065 /* qs_changeSource.png */, B2AF57EE2675FB850080A065 /* zhuishushenqi.png */, B2AF57EF2675FB850080A065 /* qs_reader.png */, ); path = images; sourceTree = SOURCE_ROOT; }; B2B02E3B1EA891E700A6880A /* CategoryDetail */ = { isa = PBXGroup; children = ( B2B02E421EA8920F00A6880A /* ZSCategoryDetailViewController.swift */, B22AC44E1E9F1C7D008625E6 /* ZSCatelogItemViewController.swift */, B22AC44C1E9F1C62008625E6 /* ZSBaseSegmentItemViewController.swift */, ); path = CategoryDetail; sourceTree = "<group>"; }; B2B02E4A1EA9A9CA00A6880A /* Category */ = { isa = PBXGroup; children = ( B2B02E4B1EA9A9CA00A6880A /* ZSCatelogViewController.swift */, ); path = Category; sourceTree = "<group>"; }; B2B4B9F61E7EA4A8000CC201 /* zhuishushenqi */ = { isa = PBXGroup; children = ( B2091C2A22B158F500AD550A /* NewVersion */, B2EE7CF61F3BFC7B00BE997C /* Localizable.strings */, B255707B1F3A075600C35A34 /* App */, B2B4BD781E7EE284000CC201 /* Client */, B2B4B9F91E7EA4A8000CC201 /* Base */, B2B4BA131E7EA4A8000CC201 /* Extension */, B2B4BA221E7EA4A8000CC201 /* RightSide */, B2B4BA571E7EA4A8000CC201 /* Root */, B22204961EE8FC45008E1902 /* Splash */, B26286BE1EEA46C7005A1A8A /* IntroducePage */, B2B4BA651E7EA4A8000CC201 /* TXTReader */, B278AA9F1F396773000A0D55 /* Vendor */, B2AF57E82675FB840080A065 /* images */, B2B4BBDC1E7EA4AB000CC201 /* ViewController.swift */, B2B4B9F81E7EA4A8000CC201 /* Assets.xcassets */, B2B4BA0E1E7EA4A8000CC201 /* LaunchScreen.storyboard */, B2B4BA101E7EA4A8000CC201 /* Main.storyboard */, B2B4BA211E7EA4A8000CC201 /* Info.plist */, B2B4BBDD1E7EA4AB000CC201 /* zhuishushenqi-Bridge-Header.h */, ); path = zhuishushenqi; sourceTree = "<group>"; }; B2B4B9F91E7EA4A8000CC201 /* Base */ = { isa = PBXGroup; children = ( 3D9325E320B2C4220049CDBF /* ViewManager */, 3D9325E420B2C4220049CDBF /* ViewModel */, B2B4B9FA1E7EA4A8000CC201 /* Controllers */, B2B4B9FD1E7EA4A8000CC201 /* Models */, B2B4BA011E7EA4A8000CC201 /* Views */, ); path = Base; sourceTree = "<group>"; }; B2B4B9FA1E7EA4A8000CC201 /* Controllers */ = { isa = PBXGroup; children = ( B2B4B9FB1E7EA4A8000CC201 /* BaseViewController.swift */, B2B4B9FC1E7EA4A8000CC201 /* SideViewController.swift */, 3DE77D2520C8E99500A86DF0 /* ZSBaseTableViewController.swift */, B2ACA01D212179F60032305E /* ZSSegmentViewController.swift */, 3DECC38B22460DD000EEB146 /* ZSSegmenuViewController.swift */, B2ACA05E212416AE0032305E /* ZSBaseNavigationViewController.swift */, ); path = Controllers; sourceTree = "<group>"; }; B2B4B9FD1E7EA4A8000CC201 /* Models */ = { isa = PBXGroup; children = ( B2B4B9FE1E7EA4A8000CC201 /* SwiftyJSON.swift */, B2B4B9FF1E7EA4A8000CC201 /* XYCBaseModel.h */, B2B4BA001E7EA4A8000CC201 /* XYCBaseModel.m */, 3DFBDEDC21F1A7EC00AED519 /* ZSCacheHelper.swift */, ); path = Models; sourceTree = "<group>"; }; B2B4BA011E7EA4A8000CC201 /* Views */ = { isa = PBXGroup; children = ( B2B4BA021E7EA4A8000CC201 /* CommunityView.swift */, B2B4BA031E7EA4A8000CC201 /* DarkStarView.swift */, B2B4BA041E7EA4A8000CC201 /* DarkView.swift */, B2B4BA071E7EA4A8000CC201 /* EmptyView.swift */, B2B4BA081E7EA4A8000CC201 /* EmptyView.xib */, B2B4BA091E7EA4A8000CC201 /* LightStarView.swift */, B2B4BA0A1E7EA4A8000CC201 /* LightView.swift */, B2B4BA0B1E7EA4A8000CC201 /* RateView.swift */, B2B4BA0C1E7EA4A8000CC201 /* V2FPSLabel.swift */, B2B4BA0D1E7EA4A8000CC201 /* XYCActionSheet.swift */, B22AC4561E9F5520008625E6 /* QSLoadingView.swift */, B27D7E9F1F4D593D00866341 /* UIScrollView+StateView.h */, B27D7EA01F4D593D00866341 /* UIScrollView+StateView.m */, 3D968CF7213392F500DF3279 /* ZSBaseCellAdapter.swift */, 3D968CF9213396B700DF3279 /* ZSBaseSectionAdapter.swift */, ); path = Views; sourceTree = "<group>"; }; B2B4BA131E7EA4A8000CC201 /* Extension */ = { isa = PBXGroup; children = ( 3D2330E121F6F38100EFE522 /* SwiftStdlib */, B2FB91FC1EB1EEDF000C990A /* Reachability.swift */, B2F778BB1EAF31DB004B4362 /* UITableView+FINAutomaticHeightCell.swift */, B2B4BA141E7EA4A8000CC201 /* Date+Extension.swift */, B2B4BA151E7EA4A8000CC201 /* DateIntervalFormatter+formatter.swift */, B2B4BA181E7EA4A8000CC201 /* NSDate+Extension.h */, B2B4BA191E7EA4A8000CC201 /* NSDate+Extension.m */, 3DDEEB00214B5D59003D12DB /* UIFont+ZSExtension.h */, 3DDEEB01214B5D59003D12DB /* UIFont+ZSExtension.m */, B2B4BA1A1E7EA4A8000CC201 /* String+QSExtension.swift */, B251F89B2030350E004AAF95 /* Dictionary+QSExtension.swift */, B24B3F5520ECA04B0098ACE5 /* Array+ZSExtension.swift */, B2B4BA1B1E7EA4A8000CC201 /* UIImageView+zhuishu.swift */, 3DBC089E22CB80DB0004B3F4 /* UIButton+Extension.swift */, B2B4BA1C1E7EA4A8000CC201 /* UILabel+zhuishu.swift */, B2B4BA1D1E7EA4A8000CC201 /* UINavigationItem+BackItem.h */, B2B4BA1E1E7EA4A8000CC201 /* UINavigationItem+BackItem.m */, B2B4BA1F1E7EA4A8000CC201 /* UITableView+QSGeneric.swift */, 3D9325E720B2C66A0049CDBF /* UITableViewCell+ZSExtension.swift */, B27D7E851F4BE04300866341 /* UICollectionView+QSExtension.swift */, B2B4BA201E7EA4A8000CC201 /* UITableView+swizzling.swift */, B29B4F9C1E821E61008852E3 /* UIImage+QSData.swift */, B258BB231EA4B201006E5802 /* UIView+ScreenShot.swift */, B2414D9B1EA602ED005A5758 /* UIViewController+Alert.swift */, B28FFC841EAFAED600C27FF9 /* NSString+Encode.h */, B28FFC851EAFAED600C27FF9 /* NSString+Encode.m */, B258F31924A0E67800E17943 /* copy.txt */, B2CA942A1EB331DC00D60BF2 /* UIColor+Theme.swift */, B286F019202981DD0069565B /* NSObject+Extension.swift */, B29A6D022029C5B400AC4C73 /* NotificationCenter+QSExtension.swift */, B252D4DD211006670091858E /* Alamofire+ZSExtension.swift */, 3D9325ED20B2E07F0049CDBF /* Value.swift */, B263E2E8211EBFEC001819FB /* SQLite+Extension.swift */, 3DDEEAFE214B5D39003D12DB /* UIFont+Extension.swift */, 3D33B4D222BB6C40000C2D8D /* DispatchTime+Extension.swift */, B258F317249BB8E400E17943 /* ApplicationExtension.swift */, B253EE392784328500F6C6D9 /* UIStoryboardExtension.swift */, B253EE3E2784436300F6C6D9 /* UserDefaultsExtension.swift */, ); path = Extension; sourceTree = "<group>"; }; B2B4BA221E7EA4A8000CC201 /* RightSide */ = { isa = PBXGroup; children = ( B2580E211E9B279A00455AFE /* Network */, B2B4BA231E7EA4A8000CC201 /* Category */, B2B4BA351E7EA4A8000CC201 /* Ranking */, B2B4BA3F1E7EA4A8000CC201 /* Search */, B2B4BA461E7EA4A8000CC201 /* Topic */, ); path = RightSide; sourceTree = "<group>"; }; B2B4BA231E7EA4A8000CC201 /* Category */ = { isa = PBXGroup; children = ( 3D998935223FD5AA00EA33D5 /* ViewModel */, B2B4BA241E7EA4A8000CC201 /* Controllers */, B2B4BA271E7EA4A8000CC201 /* Models */, B2B4BA321E7EA4A8000CC201 /* Views */, ); path = Category; sourceTree = "<group>"; }; B2B4BA241E7EA4A8000CC201 /* Controllers */ = { isa = PBXGroup; children = ( B2B02E4A1EA9A9CA00A6880A /* Category */, B2B02E3B1EA891E700A6880A /* CategoryDetail */, ); path = Controllers; sourceTree = "<group>"; }; B2B4BA271E7EA4A8000CC201 /* Models */ = { isa = PBXGroup; children = ( B22AC4581E9F6767008625E6 /* ZSCatelogParameterModel.swift */, 3D99893A223FEC5900EA33D5 /* ZSCatelogModel.swift */, 3DB24B612243CBD200DCE8B2 /* ZSCatelogHeaderView.swift */, ); path = Models; sourceTree = "<group>"; }; B2B4BA321E7EA4A8000CC201 /* Views */ = { isa = PBXGroup; children = ( 3DB24B5C2240F5F100DCE8B2 /* ZSCatelogCell.swift */, ); path = Views; sourceTree = "<group>"; }; B2B4BA351E7EA4A8000CC201 /* Ranking */ = { isa = PBXGroup; children = ( B25A70E0223E961900D0182D /* ViewModel */, B2ACA02021217D970032305E /* Service */, B2B4BA361E7EA4A8000CC201 /* Controllers */, B2B4BA391E7EA4A8000CC201 /* Models */, B2B4BA3B1E7EA4A8000CC201 /* Views */, ); path = Ranking; sourceTree = "<group>"; }; B2B4BA361E7EA4A8000CC201 /* Controllers */ = { isa = PBXGroup; children = ( B22AC4541E9F1E8F008625E6 /* QSRankViewController.swift */, B22AC4601E9F695D008625E6 /* ZSRankViewController.swift */, ); path = Controllers; sourceTree = "<group>"; }; B2B4BA391E7EA4A8000CC201 /* Models */ = { isa = PBXGroup; children = ( B2B4BA3A1E7EA4A8000CC201 /* QSRankModel.swift */, ); path = Models; sourceTree = "<group>"; }; B2B4BA3B1E7EA4A8000CC201 /* Views */ = { isa = PBXGroup; children = ( B278AA961F396073000A0D55 /* ReadHistoryCell.swift */, B278AA971F396073000A0D55 /* ReadHistoryCell.xib */, B2B4BA3C1E7EA4A8000CC201 /* RankingViewCell.swift */, B2B4BA3D1E7EA4A8000CC201 /* TopDetailCell.swift */, B2B4BA3E1E7EA4A8000CC201 /* TopDetailCell.xib */, ); path = Views; sourceTree = "<group>"; }; B2B4BA3F1E7EA4A8000CC201 /* Search */ = { isa = PBXGroup; children = ( B2B4BA401E7EA4A8000CC201 /* Controllers */, B2B4BA431E7EA4A8000CC201 /* Models */, B2B4BA441E7EA4A8000CC201 /* Views */, B2580E151E9B236000455AFE /* QSSearchInteractor.swift */, B2580E161E9B236000455AFE /* QSSearchPresenter.swift */, B2580E171E9B236000455AFE /* QSSearchProtocols.swift */, B2580E181E9B236000455AFE /* QSSearchRouter.swift */, B2580E191E9B236000455AFE /* QSSearchViewController.swift */, B203BB581E9E0F4400F9C052 /* QSSearchViewController+SearchBar.swift */, B203BB641E9E119400F9C052 /* QSSearchViewController+Transition.swift */, B24B3F5320E888D60098ACE5 /* ZSSearchResultViewController.swift */, B2580E241E9B6AF500455AFE /* QSSearchHeaderView.swift */, B2FCAA8C1E9C85600064837C /* QSHistoryHeaderView.swift */, B22AC4391E9E6626008625E6 /* QSSearchResultTable.swift */, B22AC44A1E9F0AE7008625E6 /* QSSearchAutoCompleteTable.swift */, ); path = Search; sourceTree = "<group>"; }; B2B4BA401E7EA4A8000CC201 /* Controllers */ = { isa = PBXGroup; children = ( B2B4BA411E7EA4A8000CC201 /* SearchDetailViewController.swift */, ); path = Controllers; sourceTree = "<group>"; }; B2B4BA431E7EA4A8000CC201 /* Models */ = { isa = PBXGroup; children = ( B2FCAA8A1E9C82CE0064837C /* QSSearchItem.swift */, ); path = Models; sourceTree = "<group>"; }; B2B4BA441E7EA4A8000CC201 /* Views */ = { isa = PBXGroup; children = ( B2B4BA451E7EA4A8000CC201 /* SearchView.swift */, B203BB661E9E142C00F9C052 /* QSHistoryCell.swift */, B203BB671E9E142C00F9C052 /* QSHistoryCell.xib */, B24B3F4B20E534390098ACE5 /* ZSSearchViewCell.swift */, B24B3F4C20E534390098ACE5 /* ZSSearchViewCell.xib */, B24B3F4F20E8885A0098ACE5 /* ZSSearchHeaderView.swift */, B24B3F5120E888920098ACE5 /* ZSHistoryHeaderView.swift */, ); path = Views; sourceTree = "<group>"; }; B2B4BA461E7EA4A8000CC201 /* Topic */ = { isa = PBXGroup; children = ( 3DECC38222452A7500EEB146 /* ViewModel */, B2B4BA471E7EA4A8000CC201 /* Controllers */, B2B4BA4B1E7EA4A8000CC201 /* Models */, B2B4BA4F1E7EA4A8000CC201 /* Views */, ); path = Topic; sourceTree = "<group>"; }; B2B4BA471E7EA4A8000CC201 /* Controllers */ = { isa = PBXGroup; children = ( B2583CAC1EA78280004178F3 /* Filter */, B2583CAE1EA78280004178F3 /* ThemeTopic */, B2583CB01EA78280004178F3 /* TopicDetail */, ); path = Controllers; sourceTree = "<group>"; }; B2B4BA4B1E7EA4A8000CC201 /* Models */ = { isa = PBXGroup; children = ( B2B4BA4C1E7EA4A8000CC201 /* ThemeTopicModel.swift */, B2B4BA4D1E7EA4A8000CC201 /* TopicDetailHeader.swift */, B2B4BA4E1E7EA4A8000CC201 /* TopicDetailModel.swift */, 3DECC385224533EB00EEB146 /* ZSFilterThemeModel.swift */, ); path = Models; sourceTree = "<group>"; }; B2B4BA4F1E7EA4A8000CC201 /* Views */ = { isa = PBXGroup; children = ( 3DECC3872245379300EEB146 /* ZSFilterThemeCell.swift */, B2B4BA511E7EA4A8000CC201 /* ThemeTopicCell.swift */, B2B4BA521E7EA4A8000CC201 /* ThemeTopicCell.xib */, B2B4BA531E7EA4A8000CC201 /* TopicDetailCell.swift */, B2B4BA541E7EA4A8000CC201 /* TopicDetailCell.xib */, B2B4BA551E7EA4A8000CC201 /* TopicDetailHeaderCell.swift */, B2B4BA561E7EA4A8000CC201 /* TopicDetailHeaderCell.xib */, ); path = Views; sourceTree = "<group>"; }; B2B4BA571E7EA4A8000CC201 /* Root */ = { isa = PBXGroup; children = ( 3DE77D2820C90E8900A86DF0 /* Service */, 3D9325EA20B2C8C50049CDBF /* ViewModel */, B2B4BA581E7EA4A8000CC201 /* Controllers */, B2B4BA5E1E7EA4A8000CC201 /* Models */, B2B4BA5F1E7EA4A8000CC201 /* Views */, ); path = Root; sourceTree = "<group>"; }; B2B4BA581E7EA4A8000CC201 /* Controllers */ = { isa = PBXGroup; children = ( B278AA8A1F395F30000A0D55 /* LookBookViewController.swift */, B278AA8B1F395F30000A0D55 /* ReadHistoryViewController.swift */, B2B4BA591E7EA4A8000CC201 /* DynamicViewController.swift */, B2B4BA5A1E7EA4A8000CC201 /* LeftViewController.swift */, B29CFB6C217AD6D30006F294 /* ZSMyViewController.swift */, B29CFB7E217B37DC0006F294 /* ZSUserInfoViewController.swift */, B27B752021E5061100EFC2D7 /* ZSUserAccountViewController.swift */, 3D850DFF21E61E1800F23DDB /* ZSVoucherViewController.swift */, B2A7CD8A2180D6E90067D25B /* ZSModifyNicknameViewController.swift */, B2B4BA5B1E7EA4A8000CC201 /* RightViewController.swift */, B252D4E3211037380091858E /* ZSShelfViewController.swift */, 3D1F67A0217DBA1B000FB968 /* ZSLoginViewController.swift */, B2B4BA5D1E7EA4A8000CC201 /* RootViewController.swift */, 3D9325E020B2BCFB0049CDBF /* ZSRootViewController.swift */, B2A39C472110447300D6308E /* ZSLocalShelfViewController.swift */, B252D4A9210EF8770091858E /* ZSImportBookViewController.swift */, 3D5280DF20C8D2BB00252D67 /* ZSForumViewController.swift */, B2ACA060212444870032305E /* ZSSettingViewController.swift */, B2ACA06D212944AC0032305E /* ZSWebViewController.swift */, 3DECC3E622464F9300EEB146 /* ZSVoiceBookCategoryViewController.swift */, B2B02E501EA9A9F000A6880A /* ZSVoiceBookSegmentViewController.swift */, 3DECC38922460B6100EEB146 /* ZSVoicePlayViewController.swift */, 3DECC38D224611BC00EEB146 /* ZSVoicePlayerViewController.swift */, 3DECC38F224611EB00EEB146 /* ZSVoicePlayListViewController.swift */, 3DAC238D21E1D66A00E77891 /* ZSWebStoreViewController.swift */, 3DAC238F21E1D73300E77891 /* ZSWebViewControllerDelegate.swift */, 3DAC239121E1DE7400E77891 /* ZSWebItem.swift */, B2B4BA5C1E7EA4A8000CC201 /* RootViewController+FetchData.swift */, B2B4BD761E7ECEE0000CC201 /* RootViewController+Subviews.swift */, ); path = Controllers; sourceTree = "<group>"; }; B2B4BA5E1E7EA4A8000CC201 /* Models */ = { isa = PBXGroup; children = ( B29B4F9E1E822992008852E3 /* QSHotModel.swift */, 3D9D857B20CA439D003FEDFE /* ZSShelfMessage.swift */, B29CFAE72179B7270006F294 /* ZSQQUser.swift */, B29CFB74217B1DAB0006F294 /* ZSAccount.swift */, B29CFB76217B1FEC0006F294 /* ZSCoin.swift */, B29CFB7A217B2F3A0006F294 /* ZSUserDetail.swift */, B29CFB7C217B35E90006F294 /* ZSUserBind.swift */, B2A7CD80217C5E5E0067D25B /* ZSUserBookshelf.swift */, 3DAC23CD21E32DA500E77891 /* ZSYJSchemeHandle.swift */, 3D850DFD21E61C9800F23DDB /* ZSVoucher.swift */, 3DC93D54224A6264004FC392 /* ZSVoiceAlbum.swift */, B2683CD822C8F65300FE9CF0 /* ZSWebJumpHandler.swift */, B2683CF022C8FC4C00FE9CF0 /* ZSWebUserHandler.swift */, B2683CF222C8FCE900FE9CF0 /* ZSWebToolHandler.swift */, B2683CF622C8FE4E00FE9CF0 /* ZSWebBIHandler.swift */, B2683CF822C8FE8A00FE9CF0 /* ZSWebSpeakHandler.swift */, B2683CF422C8FD6500FE9CF0 /* ZSWebContext.swift */, B2683CFA22C9111600FE9CF0 /* ZSConfigUtil.swift */, ); path = Models; sourceTree = "<group>"; }; B2B4BA5F1E7EA4A8000CC201 /* Views */ = { isa = PBXGroup; children = ( 3DECC39122461E1900EEB146 /* ZSVoicePlayerView.swift */, 3DECC39322461E4900EEB146 /* ZSVoicePlayProgressView.swift */, 3DECC3E822467F9A00EEB146 /* ZSVoiceSegmentView.swift */, B2B02E4E1EA9A9F000A6880A /* ZSVoiceCategoryHeaderView.swift */, B2B02E4F1EA9A9F000A6880A /* ZSVoiceCategoryCell.swift */, 3D62EE5822604768002FFA39 /* ZSVoicePlayerCatelogView.swift */, B2B02E511EA9A9F000A6880A /* ZSVoicePlayerCatelogHeaderView.swift */, B278AA901F395FA4000A0D55 /* QSHelpViewCell.swift */, B278AA911F395FA4000A0D55 /* QSHelpViewCell.xib */, B2ACA069212915240032305E /* ZSReviewsCell.swift */, B2ACA06B212915AC0032305E /* ZSReviewsCell.xib */, B278AA921F395FA4000A0D55 /* QSSegmentDropView.swift */, B29B4FA01E8257BC008852E3 /* DynamicCell.swift */, B29B4FA11E8257BC008852E3 /* DynamicCell.xib */, B2B4BA601E7EA4A8000CC201 /* BarButton.swift */, B2B4BA611E7EA4A8000CC201 /* RightTableViewCell.swift */, B2B4BA621E7EA4A8000CC201 /* RootNavigationView.swift */, B2B4BA631E7EA4A8000CC201 /* SegMenu.swift */, B2B4BA641E7EA4A8000CC201 /* SwipableCell.swift */, 3DFCF6F321E9984000D3A8EC /* ZSSwipeCell.swift */, B263ACDD1EE679830096CE80 /* HomeListViewCell.swift */, B28FFC911EB03EA300C27FF9 /* QSHomeDeleteBtn.swift */, B222A2C21EEE6EE9007CD57F /* QSLaunchRecView.swift */, B200D2211EEE44AB003A42E3 /* QSLaunchRecView.xib */, B29CFB6E217ADA690006F294 /* ZSMyCell.swift */, B29CFB80217B395D0006F294 /* ZSUserBindCell.swift */, B29CFB70217AE84B0006F294 /* ZSMyHeaderView.swift */, 3D1F67A2217DBA94000FB968 /* ZSLoginView.swift */, B2A7CD84217F36B20067D25B /* ZSThirdLoginView.swift */, 3D1F67A4217DCE9B000FB968 /* ZSLeftViewCell.swift */, B2A7CD86218016060067D25B /* ZSLoginVerifyView.swift */, 3DFCF6EE21E6470000D3A8EC /* ZSVoucherView.swift */, ); path = Views; sourceTree = "<group>"; }; B2B4BA651E7EA4A8000CC201 /* TXTReader */ = { isa = PBXGroup; children = ( 3DD0840321569850008E3B4A /* Speech */, B2C0DE9F20D4CB340057DC84 /* BookComment */, B2C0DEBA20D4CB340057DC84 /* BookDetail */, B2C0DEFE20D4CB340057DC84 /* Category */, B2C0DF0B20D4CB340057DC84 /* Community */, B2C0DEAB20D4CB340057DC84 /* Reader */, ); path = TXTReader; sourceTree = "<group>"; }; B2B4BD781E7EE284000CC201 /* Client */ = { isa = PBXGroup; children = ( B278AA651F3954FF000A0D55 /* QSAPI.swift */, B2B4BD791E7EE2B1000CC201 /* QSNetworkManager.swift */, ); path = Client; sourceTree = "<group>"; }; B2BB5B8D1D8BDF8E00379217 = { isa = PBXGroup; children = ( B2EE7CFD1F3C0C3400BE997C /* Script.sh */, B2B4B9F61E7EA4A8000CC201 /* zhuishushenqi */, B2BB5BAD1D8BDF8E00379217 /* zhuishushenqiTests */, B2BB5BB81D8BDF8E00379217 /* zhuishushenqiUITests */, B2BB5B971D8BDF8E00379217 /* Products */, 96FE9B86D6BF6F1F079B71F4 /* Frameworks */, C1E1DE6D9D769BBDE25682F9 /* Pods */, ); sourceTree = "<group>"; }; B2BB5B971D8BDF8E00379217 /* Products */ = { isa = PBXGroup; children = ( B2BB5B961D8BDF8E00379217 /* zhuishushenqi.app */, B2BB5BAA1D8BDF8E00379217 /* zhuishushenqiTests.xctest */, B2BB5BB51D8BDF8E00379217 /* zhuishushenqiUITests.xctest */, ); name = Products; sourceTree = "<group>"; }; B2BB5BAD1D8BDF8E00379217 /* zhuishushenqiTests */ = { isa = PBXGroup; children = ( B2BB5BAE1D8BDF8E00379217 /* zhuishushenqiTests.swift */, B2BB5BB01D8BDF8E00379217 /* Info.plist */, ); path = zhuishushenqiTests; sourceTree = "<group>"; }; B2BB5BB81D8BDF8E00379217 /* zhuishushenqiUITests */ = { isa = PBXGroup; children = ( B2BB5BB91D8BDF8E00379217 /* zhuishushenqiUITests.swift */, B2BB5BBB1D8BDF8E00379217 /* Info.plist */, ); path = zhuishushenqiUITests; sourceTree = "<group>"; }; B2C0DE9F20D4CB340057DC84 /* BookComment */ = { isa = PBXGroup; children = ( 3D5671FA2174CABA0049B2FF /* Model */, B2C0DEA020D4CB340057DC84 /* ViewModel */, B2C0DEA720D4CB340057DC84 /* View */, B2C0DEA820D4CB340057DC84 /* Service */, ); path = BookComment; sourceTree = "<group>"; }; B2C0DEA020D4CB340057DC84 /* ViewModel */ = { isa = PBXGroup; children = ( 3D968CFB2135391400DF3279 /* ZSBookCTViewModel.swift */, ); path = ViewModel; sourceTree = "<group>"; }; B2C0DEA720D4CB340057DC84 /* View */ = { isa = PBXGroup; children = ( B2C0DF7220D4CB6C0057DC84 /* BookCommentViewController.swift */, B2C0DF7420D4CB6C0057DC84 /* QSBookCommentInteractor.swift */, B2C0DF7120D4CB6C0057DC84 /* QSBookCommentPresenter.swift */, B2C0DF7620D4CB6D0057DC84 /* QSBookCommentProtocols.swift */, B2C0DF7520D4CB6C0057DC84 /* QSBookCommentRouter.swift */, B2C0DF7020D4CB6C0057DC84 /* QSBookCommentViewController.swift */, B2A7CDEA2184526D0067D25B /* ZSBookReviewViewController.swift */, B2C0DF7320D4CB6C0057DC84 /* TXTReader.storyboard */, B2C0DF8420D7C1D30057DC84 /* ZSHottwitterViewController.swift */, B2A7CDEC218453900067D25B /* ZSReviewDetailView.swift */, B2A7CDF0218469860067D25B /* ZSFeelingView.swift */, B2A7CDEE218453DC0067D25B /* ZSBestReviewView.swift */, B25EB92921933A6D000D3657 /* ZSWriteReview.swift */, ); path = View; sourceTree = "<group>"; }; B2C0DEA820D4CB340057DC84 /* Service */ = { isa = PBXGroup; children = ( B2C0DF7E20D4CC480057DC84 /* ZSBookCommentService.swift */, 3D968CFD2135398600DF3279 /* ZSBookCTService.swift */, ); path = Service; sourceTree = "<group>"; }; B2C0DEAB20D4CB340057DC84 /* Reader */ = { isa = PBXGroup; children = ( 3DF2D27620EB375A004E73B6 /* Model */, 3DF2D27420EB375A004E73B6 /* Service */, 3DF2D27320EB375A004E73B6 /* View */, 3DF2D27520EB375A004E73B6 /* ViewModel */, B2C0DEAD20D4CB340057DC84 /* QSTextRouter.swift */, B2C0DEAE20D4CB340057DC84 /* QSMoreSettingController.swift */, 3D2006D2216F443A00C326B4 /* ZSSpeakerViewController.swift */, 3D2006D4216F478C00C326B4 /* ZSSpeakerCell.swift */, B2C0DEAF20D4CB340057DC84 /* QSReaderViewController.swift */, B2C0DEB020D4CB340057DC84 /* QSReaderBackgroundViewController.swift */, B2C0DEB120D4CB340057DC84 /* QSTextReaderController.swift */, B2C0DEB320D4CB340057DC84 /* QSTextProtocols.swift */, B2C0DEB720D4CB340057DC84 /* TXTReaderViewController.swift */, B2C0DEB920D4CB340057DC84 /* PageViewController.swift */, ); path = Reader; sourceTree = "<group>"; }; B2C0DEBA20D4CB340057DC84 /* BookDetail */ = { isa = PBXGroup; children = ( B2C0DEBB20D4CB340057DC84 /* ViewModel */, B2C0DEBC20D4CB340057DC84 /* QSInterestedViewController.swift */, B2C0DEBD20D4CB340057DC84 /* Models */, B2C0DED520D4CB340057DC84 /* QSBookDetailViewController.swift */, B2C0DED620D4CB340057DC84 /* Model */, B2C0DED720D4CB340057DC84 /* View */, B2C0DED820D4CB340057DC84 /* QSBookDetailProtocols.swift */, B2C0DED920D4CB340057DC84 /* Service */, B2C0DEDA20D4CB340057DC84 /* QSBookDetailInteractor.swift */, B2C0DEDB20D4CB340057DC84 /* Views */, B2C0DEFB20D4CB340057DC84 /* QSBookDetailRouter.swift */, B2C0DEFC20D4CB340057DC84 /* BookDetailViewController.swift */, B2C0DEFD20D4CB340057DC84 /* QSBookDetailPresenter.swift */, B2B02E3C1EA891E700A6880A /* ZSDetailViewController.swift */, 3D2840292284267C009463A3 /* ZSDetailInfoCell.swift */, 3D28402722841281009463A3 /* ZSDetailSection.swift */, ); path = BookDetail; sourceTree = "<group>"; }; B2C0DEBB20D4CB340057DC84 /* ViewModel */ = { isa = PBXGroup; children = ( ); path = ViewModel; sourceTree = "<group>"; }; B2C0DEBD20D4CB340057DC84 /* Models */ = { isa = PBXGroup; children = ( B2C0DEBE20D4CB340057DC84 /* UpdateInfo.swift */, B2C0DEBF20D4CB340057DC84 /* QSReaderParse.swift */, B2C0DEC020D4CB340057DC84 /* BookCommentDetail.swift */, B2C0DEC120D4CB340057DC84 /* BookComment.swift */, B2C0DEC220D4CB340057DC84 /* QSHotComment.swift */, B2C0DEC320D4CB340057DC84 /* BookShelfInfo.swift */, B2C0DEC420D4CB340057DC84 /* QSRecord.swift */, B2C0DEC520D4CB340057DC84 /* User.swift */, B2C0DEC620D4CB340057DC84 /* QSReaderSetting.swift */, B2C0DEC720D4CB340057DC84 /* QSBookList.swift */, B2C0DEC820D4CB340057DC84 /* Book.swift */, B2C0DEC920D4CB340057DC84 /* QSBook.swift */, B2C0DECA20D4CB340057DC84 /* QSReaderViewFlowLayout.swift */, B2C0DECB20D4CB340057DC84 /* Chapters.json */, B2C0DECC20D4CB340057DC84 /* QSChapter.swift */, B2C0DECD20D4CB340057DC84 /* ResourceModel.swift */, B2C0DECE20D4CB340057DC84 /* PageInfo.swift */, B2C0DECF20D4CB340057DC84 /* BookDetail.swift */, B2C0DED020D4CB340057DC84 /* BookShelf.swift */, 3DE393E821A3B84600890488 /* BookShelf.json */, 3DE393E621A3B7B600890488 /* ZSReadRecord.swift */, B2C0DED120D4CB340057DC84 /* QSRecomment.swift */, B2C0DED220D4CB340057DC84 /* ChapterInfo.json */, B2C0DED320D4CB340057DC84 /* QSPage.swift */, B2C0DED420D4CB340057DC84 /* ChapterInfo.swift */, ); path = Models; sourceTree = "<group>"; }; B2C0DED620D4CB340057DC84 /* Model */ = { isa = PBXGroup; children = ( ); path = Model; sourceTree = "<group>"; }; B2C0DED720D4CB340057DC84 /* View */ = { isa = PBXGroup; children = ( ); path = View; sourceTree = "<group>"; }; B2C0DED920D4CB340057DC84 /* Service */ = { isa = PBXGroup; children = ( ); path = Service; sourceTree = "<group>"; }; B2C0DEDB20D4CB340057DC84 /* Views */ = { isa = PBXGroup; children = ( B2C0DEDC20D4CB340057DC84 /* ToolBar.swift */, B2C0DEDD20D4CB340057DC84 /* QSBookListViewCell.xib */, B2C0DEDE20D4CB340057DC84 /* .DS_Store~HEAD */, B2C0DEDF20D4CB340057DC84 /* QSBatteryView.swift */, B2C0DEE020D4CB340057DC84 /* ChangeSourceCell.xib */, B2C0DEE120D4CB340057DC84 /* UserfulCell.xib */, B2C0DEE220D4CB340057DC84 /* ProgressView.swift */, B2C0DEE320D4CB340057DC84 /* BookCommentViewCell.swift */, B2C0DEE420D4CB340057DC84 /* QSBookDetailRateView.swift */, B2C0DEE520D4CB340057DC84 /* QSRecommendCell.swift */, B2C0DEE620D4CB340057DC84 /* BookCommentViewCell.xib */, B2C0DEE720D4CB340057DC84 /* BookCommentCell.xib */, B2C0DEE820D4CB340057DC84 /* UserfulCell.swift */, B2C0DEE920D4CB340057DC84 /* QSDiscussCell.swift */, B2C0DEEC20D4CB340057DC84 /* ChangeSourceCell.swift */, B2C0DEED20D4CB340057DC84 /* CategoryButton.swift */, B2C0DEEE20D4CB340057DC84 /* QSRecommendCell.xib */, B2C0DEEF20D4CB340057DC84 /* QSBookDetailTagsView.swift */, B2C0DEF020D4CB340057DC84 /* QSDiscussCell.xib */, B2C0DEF120D4CB340057DC84 /* HotCommentCell.swift */, B2C0DEF220D4CB340057DC84 /* CategoryTableViewCell.swift */, B2C0DEF320D4CB340057DC84 /* HotCommentCell.xib */, B2C0DEF420D4CB340057DC84 /* BookDetailHeader.xib */, B2C0DEF520D4CB340057DC84 /* BookCommentCell.swift */, B2C0DEF620D4CB340057DC84 /* BookDetailHeader.swift */, B2C0DEF720D4CB340057DC84 /* CategoryTableViewCell.xib */, B2C0DEF820D4CB340057DC84 /* QSBookDetailContentView.swift */, B2C0DEF920D4CB340057DC84 /* QSBookListViewCell.swift */, B2C0DEFA20D4CB340057DC84 /* PageView.swift */, ); path = Views; sourceTree = "<group>"; }; B2C0DEFE20D4CB340057DC84 /* Category */ = { isa = PBXGroup; children = ( B2C0DEFF20D4CB340057DC84 /* ViewModel */, B2C0DF0020D4CB340057DC84 /* ChangeSourceViewController.swift */, B2C0DF0120D4CB340057DC84 /* QSCategoryProtocols.swift */, B2C0DF0220D4CB340057DC84 /* CategoryController.swift */, B2C0DF0320D4CB340057DC84 /* QSCategoryPresenter.swift */, B2C0DF0420D4CB340057DC84 /* QSCategoryRouter.swift */, B2C0DF0520D4CB340057DC84 /* Model */, B2C0DF0620D4CB340057DC84 /* QSCategoryReaderViewController.swift */, B2C0DF0720D4CB340057DC84 /* View */, B2C0DF0820D4CB340057DC84 /* CategoryViewController.swift */, B2C0DF0920D4CB340057DC84 /* Service */, B2C0DF0A20D4CB340057DC84 /* QSCategoryInteractor.swift */, ); path = Category; sourceTree = "<group>"; }; B2C0DEFF20D4CB340057DC84 /* ViewModel */ = { isa = PBXGroup; children = ( ); path = ViewModel; sourceTree = "<group>"; }; B2C0DF0520D4CB340057DC84 /* Model */ = { isa = PBXGroup; children = ( ); path = Model; sourceTree = "<group>"; }; B2C0DF0720D4CB340057DC84 /* View */ = { isa = PBXGroup; children = ( ); path = View; sourceTree = "<group>"; }; B2C0DF0920D4CB340057DC84 /* Service */ = { isa = PBXGroup; children = ( ); path = Service; sourceTree = "<group>"; }; B2C0DF0B20D4CB340057DC84 /* Community */ = { isa = PBXGroup; children = ( B2C0DF0C20D4CB340057DC84 /* QSCommunityRouter.swift */, B2C0DF0D20D4CB340057DC84 /* ViewModel */, B2C0DF0E20D4CB340057DC84 /* QSCommunityViewController.swift */, B2C0DF0F20D4CB340057DC84 /* Model */, B2C0DF1020D4CB340057DC84 /* View */, B2C0DF1120D4CB340057DC84 /* Service */, B2C0DF1220D4CB340057DC84 /* QSCommunityProtocols.swift */, B2C0DF1320D4CB340057DC84 /* QSCommunityInteractor.swift */, B2C0DF1420D4CB340057DC84 /* QSCommunityPresenter.swift */, ); path = Community; sourceTree = "<group>"; }; B2C0DF0D20D4CB340057DC84 /* ViewModel */ = { isa = PBXGroup; children = ( ); path = ViewModel; sourceTree = "<group>"; }; B2C0DF0F20D4CB340057DC84 /* Model */ = { isa = PBXGroup; children = ( ); path = Model; sourceTree = "<group>"; }; B2C0DF1020D4CB340057DC84 /* View */ = { isa = PBXGroup; children = ( ); path = View; sourceTree = "<group>"; }; B2C0DF1120D4CB340057DC84 /* Service */ = { isa = PBXGroup; children = ( ); path = Service; sourceTree = "<group>"; }; B2CA80E821797B9C007A3EE7 /* ThirdLoginSDK */ = { isa = PBXGroup; children = ( B29CFAE32179A0DF0006F294 /* TencentOpenAPI.framework */, ); path = ThirdLoginSDK; sourceTree = "<group>"; }; B2D8688520050C2800B9AF01 /* CTDisplayText */ = { isa = PBXGroup; children = ( 3DDB6C942130F4BC00E8698D /* Source */, ); path = CTDisplayText; sourceTree = "<group>"; }; C1E1DE6D9D769BBDE25682F9 /* Pods */ = { isa = PBXGroup; children = ( B08901F972525F29A370AA86 /* Pods-zhuishushenqi.debug.xcconfig */, C17F4CC59FFA95D661BDB3BB /* Pods-zhuishushenqi.release.xcconfig */, ); name = Pods; sourceTree = "<group>"; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ B2BB5B951D8BDF8E00379217 /* zhuishushenqi */ = { isa = PBXNativeTarget; buildConfigurationList = B2BB5BBE1D8BDF8E00379217 /* Build configuration list for PBXNativeTarget "zhuishushenqi" */; buildPhases = ( 40CCB42994AEB110993A582E /* [CP] Check Pods Manifest.lock */, B2BB5B921D8BDF8E00379217 /* Sources */, B2BB5B931D8BDF8E00379217 /* Frameworks */, B2BB5B941D8BDF8E00379217 /* Resources */, B2EE7CE81F3BEEE000BE997C /* ShellScript */, 3DA90F2B22CDDC1E000306DA /* Embed Frameworks from Carthage */, 7DA515E8AF9C0CFD3E8EE750 /* [CP] Copy Pods Resources */, FD524380448A2648D747098B /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); dependencies = ( ); name = zhuishushenqi; productName = zhuishushenqi; productReference = B2BB5B961D8BDF8E00379217 /* zhuishushenqi.app */; productType = "com.apple.product-type.application"; }; B2BB5BA91D8BDF8E00379217 /* zhuishushenqiTests */ = { isa = PBXNativeTarget; buildConfigurationList = B2BB5BC11D8BDF8E00379217 /* Build configuration list for PBXNativeTarget "zhuishushenqiTests" */; buildPhases = ( B2BB5BA61D8BDF8E00379217 /* Sources */, B2BB5BA71D8BDF8E00379217 /* Frameworks */, B2BB5BA81D8BDF8E00379217 /* Resources */, ); buildRules = ( ); dependencies = ( B2BB5BAC1D8BDF8E00379217 /* PBXTargetDependency */, ); name = zhuishushenqiTests; productName = zhuishushenqiTests; productReference = B2BB5BAA1D8BDF8E00379217 /* zhuishushenqiTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; B2BB5BB41D8BDF8E00379217 /* zhuishushenqiUITests */ = { isa = PBXNativeTarget; buildConfigurationList = B2BB5BC41D8BDF8E00379217 /* Build configuration list for PBXNativeTarget "zhuishushenqiUITests" */; buildPhases = ( B2BB5BB11D8BDF8E00379217 /* Sources */, B2BB5BB21D8BDF8E00379217 /* Frameworks */, B2BB5BB31D8BDF8E00379217 /* Resources */, ); buildRules = ( ); dependencies = ( B2BB5BB71D8BDF8E00379217 /* PBXTargetDependency */, ); name = zhuishushenqiUITests; productName = zhuishushenqiUITests; productReference = B2BB5BB51D8BDF8E00379217 /* zhuishushenqiUITests.xctest */; productType = "com.apple.product-type.bundle.ui-testing"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ B2BB5B8E1D8BDF8E00379217 /* Project object */ = { isa = PBXProject; attributes = { CLASSPREFIX = ZS; LastSwiftUpdateCheck = 0730; LastUpgradeCheck = 0830; ORGANIZATIONNAME = QS; TargetAttributes = { B2BB5B951D8BDF8E00379217 = { CreatedOnToolsVersion = 7.3.1; DevelopmentTeam = L94HEL9RUT; LastSwiftMigration = 0810; ProvisioningStyle = Automatic; SystemCapabilities = { com.apple.InAppPurchase = { enabled = 0; }; }; }; B2BB5BA91D8BDF8E00379217 = { CreatedOnToolsVersion = 7.3.1; DevelopmentTeam = 8K684WSCKB; LastSwiftMigration = 0810; TestTargetID = B2BB5B951D8BDF8E00379217; }; B2BB5BB41D8BDF8E00379217 = { CreatedOnToolsVersion = 7.3.1; DevelopmentTeam = 8K684WSCKB; LastSwiftMigration = 0810; TestTargetID = B2BB5B951D8BDF8E00379217; }; }; }; buildConfigurationList = B2BB5B911D8BDF8E00379217 /* Build configuration list for PBXProject "zhuishushenqi" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( English, en, Base, "zh-Hans", "zh-Hant", ); mainGroup = B2BB5B8D1D8BDF8E00379217; productRefGroup = B2BB5B971D8BDF8E00379217 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( B2BB5B951D8BDF8E00379217 /* zhuishushenqi */, B2BB5BA91D8BDF8E00379217 /* zhuishushenqiTests */, B2BB5BB41D8BDF8E00379217 /* zhuishushenqiUITests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ B2BB5B941D8BDF8E00379217 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 3D3AB5DA214E99CC00E9C246 /* ypt.ttf in Resources */, B2B4BC1F1E7EA4AB000CC201 /* TopicDetailCell.xib in Resources */, B2C0DF5C20D4CB350057DC84 /* CategoryTableViewCell.xib in Resources */, 3D3AB5E0214E9ABD00E9C246 /* ltt.ttf in Resources */, B2B4BC111E7EA4AB000CC201 /* TopDetailCell.xib in Resources */, B252D4B1210EFF260091858E /* index.html in Resources */, B2C0DF5320D4CB350057DC84 /* QSRecommendCell.xib in Resources */, 3D3BA0002160C5D700B0C80E /* speaker.json in Resources */, B252D49C210EF3960091858E /* LICENSE.txt in Resources */, B2C0DF5520D4CB350057DC84 /* QSDiscussCell.xib in Resources */, B2C0DF3420D4CB350057DC84 /* Chapters.json in Resources */, B2C0DF4220D4CB350057DC84 /* QSBookListViewCell.xib in Resources */, B252D4C2210F0B990091858E /* s.css in Resources */, 3DD0842021569A65008E3B4A /* TTSResource in Resources */, B200D2221EEE44AB003A42E3 /* QSLaunchRecView.xib in Resources */, B2B4BBDF1E7EA4AB000CC201 /* Assets.xcassets in Resources */, 3D3BA0042160C77E00B0C80E /* speakers.plist in Resources */, B252D4B2210EFF260091858E /* upload.html in Resources */, 3D3AB5D6214E993E00E9C246 /* hkppt.ttf in Resources */, B2C0DF5920D4CB350057DC84 /* BookDetailHeader.xib in Resources */, B24B3F4E20E534390098ACE5 /* ZSSearchViewCell.xib in Resources */, B278AA941F395FA4000A0D55 /* QSHelpViewCell.xib in Resources */, B2AF57F62675FB850080A065 /* qs_reader.png in Resources */, B252D4C5210F0B990091858E /* theadbg.png in Resources */, B252D4C4210F0B990091858E /* titlebg.png in Resources */, B2B4BBF11E7EA4AB000CC201 /* Main.storyboard in Resources */, B2ACA06C212915AC0032305E /* ZSReviewsCell.xib in Resources */, B2C0DF4C20D4CB350057DC84 /* BookCommentCell.xib in Resources */, B2B4BBF01E7EA4AB000CC201 /* LaunchScreen.storyboard in Resources */, B21C736723657E1400E9D148 /* HtmlParserModelData.dat in Resources */, B2AF57F12675FB850080A065 /* style.bundle in Resources */, B2C0DF4520D4CB350057DC84 /* ChangeSourceCell.xib in Resources */, B2EE7CF41F3BFC7B00BE997C /* Localizable.strings in Resources */, B2C0DF7A20D4CB6D0057DC84 /* TXTReader.storyboard in Resources */, B21C735923657ACF00E9D148 /* tag.in in Resources */, B2B8A21722C70C07005527C8 /* mj_refresh_loading@2x.gif in Resources */, B2C0DF4B20D4CB350057DC84 /* BookCommentViewCell.xib in Resources */, B20D74171EED83690034516F /* QSLastIntroduceCell.xib in Resources */, 3D3AB5D2214E98AC00E9C246 /* hylst.ttf in Resources */, B29B4FA31E8257BC008852E3 /* DynamicCell.xib in Resources */, B2ACA06521244B3D0032305E /* ZSSetting.plist in Resources */, B2AF57F52675FB850080A065 /* zhuishushenqi.png in Resources */, B253EE3D2784358900F6C6D9 /* EncrtptorText in Resources */, B2B4BC211E7EA4AB000CC201 /* TopicDetailHeaderCell.xib in Resources */, B2C0DF4320D4CB350057DC84 /* .DS_Store~HEAD in Resources */, B2C0DF5820D4CB350057DC84 /* HotCommentCell.xib in Resources */, B2AF57F22675FB850080A065 /* regular_verify.png in Resources */, B278AA991F396074000A0D55 /* ReadHistoryCell.xib in Resources */, B252D4C3210F0B990091858E /* bg.png in Resources */, B21C735323657ACF00E9D148 /* char_ref.rl in Resources */, B252D4C7210F163C0091858E /* txt.jpeg in Resources */, 3D3AB5DE214E9A8300E9C246 /* fz-kt.ttf in Resources */, B200D2001EEDB1FE003A42E3 /* QSIntroduceReadCell.xib in Resources */, 3D0D709D23C88B30006902E8 /* HtmlParserModelData_edit.dat in Resources */, B2B8A21922C71641005527C8 /* mjRefreshHeadTitle.plist in Resources */, 3D3AB5DC214E99FE00E9C246 /* fz-wbt.ttf in Resources */, B2C0DF4620D4CB350057DC84 /* UserfulCell.xib in Resources */, B2B4BBEA1E7EA4AB000CC201 /* EmptyView.xib in Resources */, 3DE393E921A3B84600890488 /* BookShelf.json in Resources */, B20D74091EED7D250034516F /* QSIntroduceCell.xib in Resources */, B2AF57F02675FB850080A065 /* qs_bookshelf.png in Resources */, B2AF57F42675FB850080A065 /* qs_changeSource.png in Resources */, B2C0DF3B20D4CB350057DC84 /* ChapterInfo.json in Resources */, B2B4BC1D1E7EA4AB000CC201 /* ThemeTopicCell.xib in Resources */, 3DECC3E522464C5000EEB146 /* XMResource.bundle in Resources */, 3D3AB5D4214E98B800E9C246 /* Redocn.ttf in Resources */, B2AF57F32675FB850080A065 /* qs_readerMain.png in Resources */, B203BB691E9E142C00F9C052 /* QSHistoryCell.xib in Resources */, B252D49B210EF3960091858E /* README.markdown in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; B2BB5BA81D8BDF8E00379217 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; B2BB5BB31D8BDF8E00379217 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ 3DA90F2B22CDDC1E000306DA /* Embed Frameworks from Carthage */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( ); inputPaths = ( ); name = "Embed Frameworks from Carthage"; outputFileListPaths = ( ); outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "/usr/local/bin/Carthage copy-frameworks\n"; }; 40CCB42994AEB110993A582E /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( "${PODS_PODFILE_DIR_PATH}/Podfile.lock", "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputPaths = ( "$(DERIVED_FILE_DIR)/Pods-zhuishushenqi-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; 7DA515E8AF9C0CFD3E8EE750 /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( "${PODS_ROOT}/Target Support Files/Pods-zhuishushenqi/Pods-zhuishushenqi-resources.sh", "${PODS_ROOT}/Ads-CN/PangleSDK/LICENSE", "${PODS_ROOT}/Ads-CN/PangleSDK/BUAdSDK.bundle", "${PODS_ROOT}/Weibo_SDK/libWeiboSDK/WeiboSDK.bundle", ); name = "[CP] Copy Pods Resources"; outputPaths = ( "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/LICENSE", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/BUAdSDK.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/WeiboSDK.bundle", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-zhuishushenqi/Pods-zhuishushenqi-resources.sh\"\n"; showEnvVarsInLog = 0; }; B2EE7CE81F3BEEE000BE997C /* ShellScript */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( "", ); outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "#bash \"${BUILT_PRODUCTS_DIR}/${FRAMEWORKS_FOLDER_PATH}/Realm.framework/strip-frameworks.sh\"\n# Localizable.strings文件路径\n#localizableFile=\"${SRCROOT}/en.lproj/Localizable.strings\"\n# 生成的swift文件路径(根据个人习惯修改)\n#localizedFile=\"${SRCROOT}/${PROJECT_NAME}/Extension/LocalizedUtils.swift\"\n# 将localizable.strings中的文本转为swift格式的常量,存入一个临时文件\n#sed \"s/^\\\"/ static var localized_/g\" \"${localizableFile}\" | sed \"s/\\\" = \\\"/: String { return \\\"/g\" | sed \"s/;$/.localized }/g\" > \"${localizedFile}.tmp\"\n# 先将localized作为计算属性输出到目标文件\n#echo -e \"import Foundation\\n\\nextension String {\\n var localized: String { return NSLocalizedString(self, comment: self) }\" > \"${localizedFile}\"\n# 再将临时文件中的常量增量输出到目标文件\n#cat \"${localizedFile}.tmp\" >> \"${localizedFile}\"\n# 最后增量输出一个\"}\"到目标文件,完成输出\n# echo -e \"\\n}\" >> \"${localizedFile}\"\n# 删除临时文件\n# rm \"${localizedFile}.tmp\"\n\necho \"build_products_dir: ${BUILT_PRODUCTS_DIR}\"\nFILES=$(ls ${BUILT_PRODUCTS_DIR})\nfor file in $FILES\ndo \n if [ \"${file##*.}\"x = \"bundle\"x ];then\n echo \"bundle file name is :${file}\"\n echo ${TARGET_NAME}\n cp -rf \"${BUILT_PRODUCTS_DIR}/${file}\" \"${BUILT_PRODUCTS_DIR}/${TARGET_NAME}.app/\"\n fi\ndone\n"; }; FD524380448A2648D747098B /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( "${PODS_ROOT}/Target Support Files/Pods-zhuishushenqi/Pods-zhuishushenqi-frameworks.sh", "${BUILT_PRODUCTS_DIR}/Alamofire/Alamofire.framework", "${BUILT_PRODUCTS_DIR}/Cache/Cache.framework", "${BUILT_PRODUCTS_DIR}/CocoaAsyncSocket/CocoaAsyncSocket.framework", "${BUILT_PRODUCTS_DIR}/CocoaLumberjack/CocoaLumberjack.framework", "${BUILT_PRODUCTS_DIR}/FBRetainCycleDetector/FBRetainCycleDetector.framework", "${BUILT_PRODUCTS_DIR}/FLEX/FLEX.framework", "${BUILT_PRODUCTS_DIR}/FMDB/FMDB.framework", "${BUILT_PRODUCTS_DIR}/GoogleUtilities/GoogleUtilities.framework", "${BUILT_PRODUCTS_DIR}/HandyJSON/HandyJSON.framework", "${BUILT_PRODUCTS_DIR}/Kingfisher/Kingfisher.framework", "${BUILT_PRODUCTS_DIR}/MBProgressHUD/MBProgressHUD.framework", "${BUILT_PRODUCTS_DIR}/MJRefresh/MJRefresh.framework", "${BUILT_PRODUCTS_DIR}/MLeaksFinder/MLeaksFinder.framework", "${BUILT_PRODUCTS_DIR}/PKHUD/PKHUD.framework", "${BUILT_PRODUCTS_DIR}/PromisesObjC/FBLPromises.framework", "${BUILT_PRODUCTS_DIR}/RCBacktrace/RCBacktrace.framework", "${BUILT_PRODUCTS_DIR}/RxAlamofire/RxAlamofire.framework", "${BUILT_PRODUCTS_DIR}/RxCocoa/RxCocoa.framework", "${BUILT_PRODUCTS_DIR}/RxRelay/RxRelay.framework", "${BUILT_PRODUCTS_DIR}/RxSwift/RxSwift.framework", "${BUILT_PRODUCTS_DIR}/SQLite.swift/SQLite.framework", "${BUILT_PRODUCTS_DIR}/SnapKit/SnapKit.framework", "${BUILT_PRODUCTS_DIR}/UICircularProgressRing/UICircularProgressRing.framework", "${BUILT_PRODUCTS_DIR}/YYCategories/YYCategories.framework", "${BUILT_PRODUCTS_DIR}/YYImage/YYImage.framework", "${BUILT_PRODUCTS_DIR}/YYModel/YYModel.framework", "${BUILT_PRODUCTS_DIR}/YYText/YYText.framework", "${BUILT_PRODUCTS_DIR}/YungCache/YungCache.framework", "${BUILT_PRODUCTS_DIR}/YungNetworkTool/YungNetworkTool.framework", "${BUILT_PRODUCTS_DIR}/ZSAPI/ZSAPI.framework", "${BUILT_PRODUCTS_DIR}/ZSAppConfig/ZSAppConfig.framework", "${BUILT_PRODUCTS_DIR}/ZSExtension/ZSExtension.framework", "${BUILT_PRODUCTS_DIR}/Zip/Zip.framework", "${BUILT_PRODUCTS_DIR}/nanopb/nanopb.framework", ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Alamofire.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Cache.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/CocoaAsyncSocket.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/CocoaLumberjack.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FBRetainCycleDetector.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FLEX.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FMDB.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GoogleUtilities.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/HandyJSON.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Kingfisher.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MBProgressHUD.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MJRefresh.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MLeaksFinder.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/PKHUD.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FBLPromises.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RCBacktrace.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxAlamofire.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxCocoa.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxRelay.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxSwift.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SQLite.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SnapKit.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/UICircularProgressRing.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/YYCategories.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/YYImage.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/YYModel.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/YYText.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/YungCache.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/YungNetworkTool.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ZSAPI.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ZSAppConfig.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ZSExtension.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Zip.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/nanopb.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-zhuishushenqi/Pods-zhuishushenqi-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ B2BB5B921D8BDF8E00379217 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 3DD0842521569A95008E3B4A /* AKPickerView.m in Sources */, 3DFE8E812595D4280044DCEB /* ZSAddSourceTextField.m in Sources */, 3DDB6CAD2130F4BC00E8698D /* CTDisplayView.m in Sources */, 3D2330E321F6F39D00EFE522 /* FloatExtensions.swift in Sources */, B29CFB73217B1CC00006F294 /* ZSMyService.swift in Sources */, B2B4BBEE1E7EA4AB000CC201 /* V2FPSLabel.swift in Sources */, B25EB92A21933A6D000D3657 /* ZSWriteReview.swift in Sources */, 3D6CCAC423796155004D46CE /* ZSSourceCell.swift in Sources */, 3DD0842221569A74008E3B4A /* VoiceBook.swift in Sources */, B2ACA0632124467F0032305E /* ZSSettingViewModel.swift in Sources */, B2C0DF4A20D4CB350057DC84 /* QSRecommendCell.swift in Sources */, 3D05BCC023C5EAFE001EAB2A /* ZSShelfManager.swift in Sources */, B2B4BC1C1E7EA4AB000CC201 /* ThemeTopicCell.swift in Sources */, 3D1F67A3217DBA94000FB968 /* ZSLoginView.swift in Sources */, 3DBC089322CB54DD0004B3F4 /* ZSCommunityCell.swift in Sources */, B20D74161EED83690034516F /* QSLastIntroduceCell.swift in Sources */, B2B4BBEC1E7EA4AB000CC201 /* LightView.swift in Sources */, B29CFB77217B1FEC0006F294 /* ZSCoin.swift in Sources */, 3D1FFDF023C9A68F0017ECE7 /* ZSReaderBottomBigBar.swift in Sources */, B2B02E2C1EA8505200A6880A /* QSTopicDetailPresenter.swift in Sources */, B2C0DF2B20D4CB350057DC84 /* QSHotComment.swift in Sources */, B2583CBC1EA782C2004178F3 /* QSThemeTopicProtocols.swift in Sources */, B2C0DF4420D4CB350057DC84 /* QSBatteryView.swift in Sources */, B2C0DF6C20D4CB350057DC84 /* QSCommunityViewController.swift in Sources */, B2683CD922C8F65300FE9CF0 /* ZSWebJumpHandler.swift in Sources */, B2C0DF6020D4CB350057DC84 /* QSBookDetailRouter.swift in Sources */, B252D48B210EF3960091858E /* HTTPRedirectResponse.m in Sources */, 3D31FFB12216E1850011D275 /* ZSChapterPayView.swift in Sources */, B29CFB6B217A3D8F0006F294 /* ZSLogin.swift in Sources */, B2B8A21B22C71A23005527C8 /* ZSBookShelfHeaderView.swift in Sources */, 3DFCF6F421E9984100D3A8EC /* ZSSwipeCell.swift in Sources */, B26286CA1EEA46DD005A1A8A /* ZSIntroducePage.swift in Sources */, B22AC4591E9F6767008625E6 /* ZSCatelogParameterModel.swift in Sources */, B22AC45B1E9F6774008625E6 /* ZSRankDetailWebService.swift in Sources */, B286F01A202981DD0069565B /* NSObject+Extension.swift in Sources */, B2683CF122C8FC4C00FE9CF0 /* ZSWebUserHandler.swift in Sources */, B2C0DF5720D4CB350057DC84 /* CategoryTableViewCell.swift in Sources */, B21C735B23657ACF00E9D148 /* parser.c in Sources */, 3D5280E020C8D2BB00252D67 /* ZSForumViewController.swift in Sources */, B2B02E2B1EA8505200A6880A /* QSTopicDetailInteractor.swift in Sources */, B255707F1F3A075600C35A34 /* Config.swift in Sources */, 3DECC38422452F9E00EEB146 /* ZSFilterThemeViewModel.swift in Sources */, B2A7CD87218016060067D25B /* ZSLoginVerifyView.swift in Sources */, B2C0DF7B20D4CB6D0057DC84 /* QSBookCommentInteractor.swift in Sources */, B2A7CDED218453900067D25B /* ZSReviewDetailView.swift in Sources */, B2C0DF2320D4CB350057DC84 /* ZSReaderWebService.swift in Sources */, B2B02E541EA9A9F000A6880A /* ZSVoiceCategoryHeaderView.swift in Sources */, B2C0DF2E20D4CB350057DC84 /* User.swift in Sources */, B2C0DF2120D4CB350057DC84 /* ZSReaderViewModel.swift in Sources */, B2B02E4C1EA9A9CA00A6880A /* ZSCatelogViewController.swift in Sources */, 3D1FFEF2249B562500832576 /* ZSShelfStorage.swift in Sources */, B24B3F5620ECA04B0098ACE5 /* Array+ZSExtension.swift in Sources */, B24B3F5020E8885A0098ACE5 /* ZSSearchHeaderView.swift in Sources */, 3DB1FDB12334A8BE00CAC8C0 /* ZSReaderCache.swift in Sources */, B21C735623657ACF00E9D148 /* char_ref.c in Sources */, 3DDEEAFB214A8EC8003D12DB /* ZSFontService.swift in Sources */, 3D1F67A1217DBA1B000FB968 /* ZSLoginViewController.swift in Sources */, 3DD0842D2157604D008E3B4A /* ZSSpeechView.swift in Sources */, 3DDB6CAF2130F4BC00E8698D /* MagnifiterView.m in Sources */, B2B4BBFC1E7EA4AB000CC201 /* UITableView+swizzling.swift in Sources */, B2A39C482110447300D6308E /* ZSLocalShelfViewController.swift in Sources */, 3D968CF8213392F500DF3279 /* ZSBaseCellAdapter.swift in Sources */, B2580E1C1E9B236000455AFE /* QSSearchPresenter.swift in Sources */, B2B4BC231E7EA4AB000CC201 /* LeftViewController.swift in Sources */, B2C0DF1F20D4CB350057DC84 /* QSReaderBackgroundViewController.swift in Sources */, B2683D0122D089FE00FE9CF0 /* ZSDynamicHeaderView.swift in Sources */, B2FCAA8D1E9C85600064837C /* QSHistoryHeaderView.swift in Sources */, B21C735A23657ACF00E9D148 /* tag.c in Sources */, 3D88A7872670EBCB00C87AB5 /* ZSFloatingManager.swift in Sources */, B2C0DF6A20D4CB350057DC84 /* QSCategoryInteractor.swift in Sources */, B2C0DF5A20D4CB350057DC84 /* BookCommentCell.swift in Sources */, B27D7E861F4BE04300866341 /* UICollectionView+QSExtension.swift in Sources */, 3DE393D9219D505600890488 /* ZSBoughtInfo.swift in Sources */, 3DE77D2620C8E99500A86DF0 /* ZSBaseTableViewController.swift in Sources */, 3DE77D2A20C90EA300A86DF0 /* ZSRootWebService.swift in Sources */, B252D494210EF3960091858E /* DDData.m in Sources */, B2C0DF3220D4CB350057DC84 /* QSBook.swift in Sources */, B203BB681E9E142C00F9C052 /* QSHistoryCell.swift in Sources */, B2B4BBF41E7EA4AB000CC201 /* DateIntervalFormatter+formatter.swift in Sources */, B278AB081F398926000A0D55 /* NSMutableAttributedString+M80.m in Sources */, B2C0DF4020D4CB350057DC84 /* QSBookDetailInteractor.swift in Sources */, B29CFB75217B1DAB0006F294 /* ZSAccount.swift in Sources */, B25CF00922FAA732002ABE30 /* ZSPostReviewHelpful.swift in Sources */, 3DD0840B21569902008E3B4A /* Speaker.swift in Sources */, 3D63E171235EFAE70015B7D3 /* ZSTopSearchBar.swift in Sources */, 3D65504D21957B410064BA0C /* ZSChapterSelectView.swift in Sources */, 3DE393EB21A3EB2D00890488 /* ZSJSON.swift in Sources */, 3D9325EC20B2C9120049CDBF /* ZSHomeViewModel.swift in Sources */, B2A39C4A2110453100D6308E /* ZSLocalShelfViewModel.swift in Sources */, B2C0DF6120D4CB350057DC84 /* BookDetailViewController.swift in Sources */, B2C0DF3E20D4CB350057DC84 /* QSBookDetailViewController.swift in Sources */, B2580E1E1E9B236000455AFE /* QSSearchRouter.swift in Sources */, B2B4BC0E1E7EA4AB000CC201 /* QSRankModel.swift in Sources */, 3D998939223FEA0400EA33D5 /* ZSCatelogViewModel.swift in Sources */, 3D1FFDF823CC586B0017ECE7 /* ReaderNavigationBar.swift in Sources */, 3D64030623BF5E8500D4B9EB /* ZSSearchHistory.swift in Sources */, 3D1FFDF423CC52D80017ECE7 /* ThemeManager.swift in Sources */, 3DBC089F22CB80DB0004B3F4 /* UIButton+Extension.swift in Sources */, 3D05BCBA23C5ACE8001EAB2A /* ZSSearchInfoBottomView.swift in Sources */, 3D1FFDF223C9A9600017ECE7 /* ZSReaderThemeSelectionView.swift in Sources */, B252D487210EF3960091858E /* HTTPDataResponse.m in Sources */, B2B4BBE01E7EA4AB000CC201 /* BaseViewController.swift in Sources */, B2414D9C1EA602ED005A5758 /* UIViewController+Alert.swift in Sources */, 3D2B4D0520EB6552008E3E81 /* ZSNoneAnimationViewController.swift in Sources */, B252D48C210EF3960091858E /* HTTPMessage.m in Sources */, B266C66A21A6FCC3006B5A15 /* ZSDBManager.m in Sources */, B2C0DF6820D4CB350057DC84 /* QSCategoryReaderViewController.swift in Sources */, B2B4BBE41E7EA4AB000CC201 /* CommunityView.swift in Sources */, 3DECC390224611EB00EEB146 /* ZSVoicePlayListViewController.swift in Sources */, B2C0DF7C20D4CB6D0057DC84 /* QSBookCommentRouter.swift in Sources */, B2C0DF5620D4CB350057DC84 /* HotCommentCell.swift in Sources */, B2580E1D1E9B236000455AFE /* QSSearchProtocols.swift in Sources */, 3DD0842B215750C8008E3B4A /* Network.swift in Sources */, B2C0DF5B20D4CB350057DC84 /* BookDetailHeader.swift in Sources */, B27B752121E5061100EFC2D7 /* ZSUserAccountViewController.swift in Sources */, B222A2C31EEE6EE9007CD57F /* QSLaunchRecView.swift in Sources */, B252D488210EF3960091858E /* HTTPDynamicFileResponse.m in Sources */, B22AC4511E9F1C8C008625E6 /* ZSRankViewModel.swift in Sources */, B2B4BC241E7EA4AB000CC201 /* RightViewController.swift in Sources */, B2C0DF6420D4CB350057DC84 /* QSCategoryProtocols.swift in Sources */, B2C0DF2A20D4CB350057DC84 /* BookComment.swift in Sources */, B2C0DF7F20D4CC480057DC84 /* ZSBookCommentService.swift in Sources */, B2C0DF2F20D4CB350057DC84 /* QSReaderSetting.swift in Sources */, 3D55F4DD22D47CEF00AE0E2B /* ZSNormalViewController.swift in Sources */, B22AC4611E9F695D008625E6 /* ZSRankViewController.swift in Sources */, B2C0DF7720D4CB6D0057DC84 /* QSBookCommentViewController.swift in Sources */, B2C0DF5D20D4CB350057DC84 /* QSBookDetailContentView.swift in Sources */, 3DECC386224533EB00EEB146 /* ZSFilterThemeModel.swift in Sources */, B252D4E0211033F20091858E /* ZSShelfWebService.swift in Sources */, B22AC45D1E9F678D008625E6 /* ZSRankDetailViewModel.swift in Sources */, B2683D1722D1F08300FE9CF0 /* ZSSearchViewController.swift in Sources */, B278AA951F395FA4000A0D55 /* QSSegmentDropView.swift in Sources */, 3DF6238D277970270049FD73 /* AppOpenAdManager.swift in Sources */, B2B4BC121E7EA4AB000CC201 /* SearchDetailViewController.swift in Sources */, B2ACA7772787085200B8B275 /* ZSCommunityHot.swift in Sources */, B2C0DF4720D4CB350057DC84 /* ProgressView.swift in Sources */, 3D968CF62133917900DF3279 /* ZSCellAdapterProtocol.swift in Sources */, B278AA8C1F395F30000A0D55 /* LookBookViewController.swift in Sources */, 3DEFD99123BDB12200116ECD /* ZSRefreshTextHeader.swift in Sources */, B2B4BC261E7EA4AB000CC201 /* RootViewController.swift in Sources */, 3D1FFEF0249B523300832576 /* ZSSyncStorage.swift in Sources */, B2580E231E9B284900455AFE /* QSHotwords.swift in Sources */, B2C63A3223C1A44B0083C987 /* ZSReaderDownloader.swift in Sources */, 3DECC39222461E1900EEB146 /* ZSVoicePlayerView.swift in Sources */, B2C0DF4820D4CB350057DC84 /* BookCommentViewCell.swift in Sources */, B21C736523657B3F00E9D148 /* AikanHtmlParser.m in Sources */, 3DF2D27820EB5773004E73B6 /* ZSChapterInfo.swift in Sources */, B2C0DF2420D4CB350057DC84 /* TXTReaderViewController.swift in Sources */, 3DDB6CB02130F4BC00E8698D /* CoreTextLinkData.m in Sources */, 3D05BCBE23C5D424001EAB2A /* AikanParserModel.swift in Sources */, 3D17A02B22D474A7001FAC0C /* ZSHorizonalViewController.swift in Sources */, 3DF623A72779B09A0049FD73 /* BUAdManager.swift in Sources */, B29CFB79217B21C60006F294 /* ZSMyViewModel.swift in Sources */, B2B02E531EA9A9F000A6880A /* ZSThemeTopicViewModel.swift in Sources */, 3D448E5922C501C40099B6E8 /* NavigationBar.swift in Sources */, B2C0DF3620D4CB350057DC84 /* ResourceModel.swift in Sources */, 3DDEEAFD214A8EE1003D12DB /* ZSFontViewModel.swift in Sources */, B2C63A3423C1A57A0083C987 /* ZSBookMemoryCache.swift in Sources */, B2B02E2E1EA8505200A6880A /* QSTopicDetailRouter.swift in Sources */, 3D82F9EB22C9DD5D00F035CB /* ZSMineNavigationBar.swift in Sources */, 3DDEEAFF214B5D3A003D12DB /* UIFont+Extension.swift in Sources */, 3D82F9EF22CA01A100F035CB /* ZSMineMenuItem.swift in Sources */, B21C736223657B1200E9D148 /* AikanParserModel.m in Sources */, B28FFC861EAFAED600C27FF9 /* NSString+Encode.m in Sources */, 3D55F4D922D47B3800AE0E2B /* ZSVerticalViewController.swift in Sources */, B25CF00A22FAA732002ABE30 /* ZSPostReviewBook.swift in Sources */, B2A7CDEB2184526D0067D25B /* ZSBookReviewViewController.swift in Sources */, B253EE3F2784436300F6C6D9 /* UserDefaultsExtension.swift in Sources */, B25CEFF422F92AE4002ABE30 /* ZSForumPageViewController.swift in Sources */, B253EE3A2784328500F6C6D9 /* UIStoryboardExtension.swift in Sources */, B255707E1F3A075600C35A34 /* AppDelegate.swift in Sources */, B21C735C23657ACF00E9D148 /* tokenizer.c in Sources */, B21C736B2367304800E9D148 /* ZSBookInfoHeaderView.swift in Sources */, B258F318249BB8E500E17943 /* ApplicationExtension.swift in Sources */, B21C735D23657ACF00E9D148 /* string_piece.c in Sources */, B2B4BD771E7ECEE0000CC201 /* RootViewController+Subviews.swift in Sources */, B2A02F7C20F337C20034DC64 /* ZSHorizonalMoveViewController.swift in Sources */, B263ACDF1EE679830096CE80 /* HomeListViewCell.swift in Sources */, B2C0DF2C20D4CB350057DC84 /* BookShelfInfo.swift in Sources */, B278AA981F396073000A0D55 /* ReadHistoryCell.swift in Sources */, B2B4BC141E7EA4AB000CC201 /* SearchView.swift in Sources */, B209975123DBFA2D00E37FD3 /* ZSBookLocalShelfViewController.swift in Sources */, B2C0DF2520D4CB350057DC84 /* PageViewController.swift in Sources */, 3DDB6CAC2130F4BC00E8698D /* CoreTextData.m in Sources */, B25CEFEE22F9268A002ABE30 /* MarkupParser.swift in Sources */, B21C735423657ACF00E9D148 /* vector.c in Sources */, B2683D0722D17FCD00FE9CF0 /* ZSNotificationViewController.swift in Sources */, B25D1AD9230293BB00B1F786 /* ZSForumPageCell.swift in Sources */, B2CA942B1EB331DC00D60BF2 /* UIColor+Theme.swift in Sources */, B2B4BC291E7EA4AB000CC201 /* RootNavigationView.swift in Sources */, B2C0DF6920D4CB350057DC84 /* CategoryViewController.swift in Sources */, B2B4BBED1E7EA4AB000CC201 /* RateView.swift in Sources */, B24B3F4D20E534390098ACE5 /* ZSSearchViewCell.swift in Sources */, 3DECC38E224611BC00EEB146 /* ZSVoicePlayerViewController.swift in Sources */, B22AC4571E9F5520008625E6 /* QSLoadingView.swift in Sources */, B2C0DF5220D4CB350057DC84 /* CategoryButton.swift in Sources */, B2B4BC191E7EA4AB000CC201 /* TopicDetailHeader.swift in Sources */, B2583CBB1EA782C2004178F3 /* QSThemeTopicPresenter.swift in Sources */, 3D2330E021F6F05A00EFE522 /* ZSAppInfo.swift in Sources */, 3DE3AF7F23CF061A00D74C1F /* ZSPageTableViewCell.swift in Sources */, B2583CB41EA78280004178F3 /* TopicDetailViewController.swift in Sources */, B2C0DF4D20D4CB350057DC84 /* UserfulCell.swift in Sources */, 3D9325E120B2BCFB0049CDBF /* ZSRootViewController.swift in Sources */, B2C0DF2920D4CB350057DC84 /* BookCommentDetail.swift in Sources */, B2C0DF7920D4CB6D0057DC84 /* BookCommentViewController.swift in Sources */, 3D33B4D322BB6C40000C2D8D /* DispatchTime+Extension.swift in Sources */, B2583CBD1EA782C2004178F3 /* QSThemeTopicRouter.swift in Sources */, ADCB1BEE278732E300289C83 /* ZSCommunityHotCell.swift in Sources */, B2C0DF7820D4CB6D0057DC84 /* QSBookCommentPresenter.swift in Sources */, B2B4BBE31E7EA4AB000CC201 /* XYCBaseModel.m in Sources */, 3DDB6CB52130F63300E8698D /* RegexKitLite.m in Sources */, 3D968CFA213396B700DF3279 /* ZSBaseSectionAdapter.swift in Sources */, 3D448E5B22C501CC0099B6E8 /* ZSBookStoreViewController.swift in Sources */, B2C0DF2220D4CB350057DC84 /* QSTextProtocols.swift in Sources */, B2C0DF5F20D4CB350057DC84 /* PageView.swift in Sources */, B2C0DF3120D4CB350057DC84 /* Book.swift in Sources */, B252D495210EF3960091858E /* DDRange.m in Sources */, B251F89C2030350E004AAF95 /* Dictionary+QSExtension.swift in Sources */, B22AC4531E9F1CA8008625E6 /* ZSCatelogDetailViewModel.swift in Sources */, B21C735E23657ACF00E9D148 /* OCGumbo+Query.m in Sources */, B2B4BC101E7EA4AB000CC201 /* TopDetailCell.swift in Sources */, 3DECC3962246298300EEB146 /* ZSVoicePlayViewModel.swift in Sources */, B2B4BC0F1E7EA4AB000CC201 /* RankingViewCell.swift in Sources */, 3D1FFDF623CC52E10017ECE7 /* Color.swift in Sources */, 3D6CCABE237952B3004D46CE /* ZSAddSourceViewController.swift in Sources */, B2CA80F021797EA2007A3EE7 /* ZSThirdLogin.swift in Sources */, 3D850DFE21E61C9800F23DDB /* ZSVoucher.swift in Sources */, B2C0DF3C20D4CB350057DC84 /* QSPage.swift in Sources */, 3D88A7882670EBCB00C87AB5 /* ZSMemoryFloatingView.swift in Sources */, B2ACA05F212416AE0032305E /* ZSBaseNavigationViewController.swift in Sources */, B2C0DF6720D4CB350057DC84 /* QSCategoryRouter.swift in Sources */, 3D63E175235F02670015B7D3 /* ZSSearchHotView.swift in Sources */, B278AB071F398926000A0D55 /* M80AttributedLabelURL.m in Sources */, B2B4BBE11E7EA4AB000CC201 /* SideViewController.swift in Sources */, 3D6CCABC23794D2C004D46CE /* ZSSetting.swift in Sources */, B2580E251E9B6AF500455AFE /* QSSearchHeaderView.swift in Sources */, B252D4AA210EF8770091858E /* ZSImportBookViewController.swift in Sources */, B2B4BC221E7EA4AB000CC201 /* DynamicViewController.swift in Sources */, B2FB91FD1EB1EEDF000C990A /* Reachability.swift in Sources */, B258BB241EA4B201006E5802 /* UIView+ScreenShot.swift in Sources */, 3D9325E820B2C66A0049CDBF /* UITableViewCell+ZSExtension.swift in Sources */, 3D29C7AB233381A600113A25 /* ZSReaderVCProtocol.swift in Sources */, B252D492210EF3960091858E /* HTTPAuthenticationRequest.m in Sources */, 3DAC239021E1D73300E77891 /* ZSWebViewControllerDelegate.swift in Sources */, B2ACA01E212179F60032305E /* ZSSegmentViewController.swift in Sources */, B252D496210EF3960091858E /* HTTPServer.m in Sources */, B200D1FF1EEDB1FE003A42E3 /* QSIntroduceReadCell.swift in Sources */, 3DB24B5D2240F5F100DCE8B2 /* ZSCatelogCell.swift in Sources */, 3D968CFC2135391400DF3279 /* ZSBookCTViewModel.swift in Sources */, B25570871F3A963300C35A34 /* Theme.swift in Sources */, B252D497210EF3960091858E /* DAVConnection.m in Sources */, B252D4A5210EF7070091858E /* ZSHTTPConnection.m in Sources */, B252D4DC210F583B0091858E /* MonitorFileChangeHelp.m in Sources */, B29CFB6D217AD6D30006F294 /* ZSMyViewController.swift in Sources */, 3D998937223FDFB900EA33D5 /* ZSSegmentBaseViewModel.swift in Sources */, B278AAD71F396773000A0D55 /* YTKKeyValueStore.m in Sources */, B25570811F3A07BB00C35A34 /* AppStyle.swift in Sources */, B24BED5023017CB500CF8C4D /* ZSForumPageFooterView.swift in Sources */, 3D17A02722D47364001FAC0C /* ZSReaderController.swift in Sources */, 3D63E177235F03C40015B7D3 /* ZSSearchBookViewModel.swift in Sources */, B2C0DF3920D4CB350057DC84 /* BookShelf.swift in Sources */, B2683CFD22D0811000FE9CF0 /* ZSUserDynamicViewController.swift in Sources */, B2C0DF2620D4CB350057DC84 /* QSInterestedViewController.swift in Sources */, 3D968CFE2135398600DF3279 /* ZSBookCTService.swift in Sources */, B2ACA061212444870032305E /* ZSSettingViewController.swift in Sources */, B2C0DF5E20D4CB350057DC84 /* QSBookListViewCell.swift in Sources */, 3DFCF6EF21E6470000D3A8EC /* ZSVoucherView.swift in Sources */, B2C0DF2D20D4CB350057DC84 /* QSRecord.swift in Sources */, B29CFB7F217B37DC0006F294 /* ZSUserInfoViewController.swift in Sources */, 3DD084322157A14C008E3B4A /* PcmPlayer.m in Sources */, 3D55F4DB22D47CAD00AE0E2B /* ZSReader.swift in Sources */, 3D9325E620B2C44A0049CDBF /* ZSBaseTableViewManger.swift in Sources */, 3DDB6CA82130F4BC00E8698D /* CoreTextUtils.m in Sources */, 3D448E5D22C501DA0099B6E8 /* ZSCommunityViewController.swift in Sources */, 3DAC239221E1DE7400E77891 /* ZSWebItem.swift in Sources */, 3DECC3882245379400EEB146 /* ZSFilterThemeCell.swift in Sources */, B2C0DF1C20D4CB350057DC84 /* QSTextRouter.swift in Sources */, B2C63A3623C1A6C80083C987 /* ZSBookChapter.swift in Sources */, B29B4F9F1E822992008852E3 /* QSHotModel.swift in Sources */, B2662EBC21A7DAC5008AD5C6 /* ZSDBPropertyModel.m in Sources */, 3D5671FC2174CADD0049B2FF /* ZSBookCTLayoutModel.swift in Sources */, B21C735123657ACF00E9D148 /* string_buffer.c in Sources */, B2E072781F3AD99500C63347 /* Reader.swift in Sources */, 3D448E6122C501E90099B6E8 /* ZSMineViewController.swift in Sources */, B2C0DF2720D4CB350057DC84 /* UpdateInfo.swift in Sources */, B2B4BC2A1E7EA4AB000CC201 /* SegMenu.swift in Sources */, B2683D0922D1830A00FE9CF0 /* ZSNotificationCell.swift in Sources */, B24B3F5220E888920098ACE5 /* ZSHistoryHeaderView.swift in Sources */, 3D65504B219577DC0064BA0C /* ZSMultiplePayView.swift in Sources */, B2B02E2F1EA8505200A6880A /* QSTopicDetailViewController.swift in Sources */, B2C0DF8520D7C1D30057DC84 /* ZSHottwitterViewController.swift in Sources */, B2C63A3823C1ABA60083C987 /* ZSBookDiskCache.swift in Sources */, B203BB591E9E0F4400F9C052 /* QSSearchViewController+SearchBar.swift in Sources */, B24BED4E2301692800CF8C4D /* ZSForumToolBar.swift in Sources */, 3D1FFDEE23C89A4E0017ECE7 /* ZSShelfTableViewCell.swift in Sources */, B252D4AD210EFA660091858E /* ZSHTTPTool.m in Sources */, B252D490210EF3960091858E /* MultipartFormDataParser.m in Sources */, 3DBC089722CB78DB0004B3F4 /* ZSCommunityViewModel.swift in Sources */, B2840F3422C85B75009C9116 /* ZSBookShelfViewModel.swift in Sources */, B2683CF722C8FE4E00FE9CF0 /* ZSWebBIHandler.swift in Sources */, 3D63E18F23603CD80015B7D3 /* ZSSearchRecommendView.swift in Sources */, B2B4BBF81E7EA4AB000CC201 /* UIImageView+zhuishu.swift in Sources */, B2B4BBFB1E7EA4AB000CC201 /* UITableView+QSGeneric.swift in Sources */, B2ACA02221217DFB0032305E /* ZSRankService.swift in Sources */, B2C0DF3D20D4CB350057DC84 /* ChapterInfo.swift in Sources */, B2C0DF5420D4CB350057DC84 /* QSBookDetailTagsView.swift in Sources */, B2B4BBF71E7EA4AB000CC201 /* String+QSExtension.swift in Sources */, B24BED522301865400CF8C4D /* ZSForumComment.swift in Sources */, 3D850E0021E61E1800F23DDB /* ZSVoucherViewController.swift in Sources */, B21C736923672D6400E9D148 /* ZSSearchInfoViewController.swift in Sources */, 3DDB6CAB2130F4BC00E8698D /* CTFrameParserConfig.m in Sources */, B2C0DF2020D4CB350057DC84 /* QSTextReaderController.swift in Sources */, B25CEFF122F9268A002ABE30 /* ZSTouchAnchorView.swift in Sources */, B29CFB7B217B2F3A0006F294 /* ZSUserDetail.swift in Sources */, B2B4BC271E7EA4AB000CC201 /* BarButton.swift in Sources */, B29E275F230A87E800BA6E33 /* ZSForumTextView.swift in Sources */, B2C0DF2820D4CB350057DC84 /* QSReaderParse.swift in Sources */, B29CFB71217AE84B0006F294 /* ZSMyHeaderView.swift in Sources */, B22AC44D1E9F1C62008625E6 /* ZSBaseSegmentItemViewController.swift in Sources */, B25CEFF822F97937002ABE30 /* ZSForumPageHeaderView.swift in Sources */, B222C08C23C379E000D66E3C /* ZSReadHistory.swift in Sources */, B2B4BBF91E7EA4AB000CC201 /* UILabel+zhuishu.swift in Sources */, B252D491210EF3960091858E /* MultipartMessageHeader.m in Sources */, B252D4DE211006670091858E /* Alamofire+ZSExtension.swift in Sources */, 3DECC38A22460B6200EEB146 /* ZSVoicePlayViewController.swift in Sources */, B2C0DF5120D4CB350057DC84 /* ChangeSourceCell.swift in Sources */, B29A6D032029C5B400AC4C73 /* NotificationCenter+QSExtension.swift in Sources */, B252D498210EF3960091858E /* DELETEResponse.m in Sources */, B2C0DF3520D4CB350057DC84 /* QSChapter.swift in Sources */, B2C0DF1D20D4CB350057DC84 /* QSMoreSettingController.swift in Sources */, 3DE393E721A3B7B600890488 /* ZSReadRecord.swift in Sources */, B2B4BBF31E7EA4AB000CC201 /* Date+Extension.swift in Sources */, 3DDB6CA92130F4BC00E8698D /* CTFrameParser.m in Sources */, 3DB24B622243CBD200DCE8B2 /* ZSCatelogHeaderView.swift in Sources */, B2C0DF7D20D4CB6D0057DC84 /* QSBookCommentProtocols.swift in Sources */, B25CEFF622F92C5E002ABE30 /* ZSForumViewModel.swift in Sources */, 3D1FFDFA23CC5B880017ECE7 /* ReaderBar.swift in Sources */, B252D48A210EF3960091858E /* HTTPAsyncFileResponse.m in Sources */, B25CEFEF22F9268A002ABE30 /* ZSDisplayView.swift in Sources */, B29B4FA21E8257BC008852E3 /* DynamicCell.swift in Sources */, B22AC44B1E9F0AE7008625E6 /* QSSearchAutoCompleteTable.swift in Sources */, B222C08A23C1CAA800D66E3C /* ZSSearchInfoTableViewCell.swift in Sources */, B278AA931F395FA4000A0D55 /* QSHelpViewCell.swift in Sources */, B2B4BC1E1E7EA4AB000CC201 /* TopicDetailCell.swift in Sources */, 3DECC3E722464F9300EEB146 /* ZSVoiceBookCategoryViewController.swift in Sources */, B2C0DF3A20D4CB350057DC84 /* QSRecomment.swift in Sources */, B2B4BBE91E7EA4AB000CC201 /* EmptyView.swift in Sources */, B2683D1122D1B1C900FE9CF0 /* ZSDiscoverItem.swift in Sources */, 3DF2D27A20EB5C1C004E73B6 /* ZSChapterBody.swift in Sources */, B2B4BBE21E7EA4AB000CC201 /* SwiftyJSON.swift in Sources */, 3D08F242212C2DC6007B3D19 /* ZSDiscussViewModel.swift in Sources */, B2683D0F22D1AE7900FE9CF0 /* ZSDiscoverHeaderView.swift in Sources */, B2B02E481EA8920F00A6880A /* ZSCategoryDetailViewController.swift in Sources */, B2BCE0C42306530A00E52903 /* ZSForumPageTitleHeaderView.swift in Sources */, 3D1E315523D1B30700A2359E /* ZSToast.swift in Sources */, B2683D1522D1CDA900FE9CF0 /* ZSMineViewModel.swift in Sources */, B21C735523657ACF00E9D148 /* attribute.c in Sources */, 3DAC238E21E1D66A00E77891 /* ZSWebStoreViewController.swift in Sources */, B2F778BC1EAF31DB004B4362 /* UITableView+FINAutomaticHeightCell.swift in Sources */, B25EB93221949AF1000D3657 /* FBEncryptorAES.m in Sources */, B21C732B2365774B00E9D148 /* ZSSearchResultCell.swift in Sources */, 3DDEEAF9214A8E3F003D12DB /* ZSFontViewController.swift in Sources */, B25570891F3A96E500C35A34 /* BookManager.swift in Sources */, 3D2006D3216F443A00C326B4 /* ZSSpeakerViewController.swift in Sources */, B2A7CDF1218469860067D25B /* ZSFeelingView.swift in Sources */, 3D2330E521F9CC6F00EFE522 /* IntExtensions.swift in Sources */, B2683CF522C8FD6500FE9CF0 /* ZSWebContext.swift in Sources */, 3D6CCAC02379550C004D46CE /* ZSSourcesViewController.swift in Sources */, 3D63E16F235EF9E60015B7D3 /* ZSSearchResultView.swift in Sources */, 3D6CCAC2237956FB004D46CE /* ZSSourceManager.swift in Sources */, 3D2330DE21F5F04700EFE522 /* ZSBookDownloader.swift in Sources */, 3D2006D5216F478C00C326B4 /* ZSSpeakerCell.swift in Sources */, 3D17A02922D47426001FAC0C /* ZSPageViewController.swift in Sources */, 3DC93D55224A6264004FC392 /* ZSVoiceAlbum.swift in Sources */, B25CEFFA22FA7BB2002ABE30 /* ZSPostReview.swift in Sources */, B29CFAE82179B7270006F294 /* ZSQQUser.swift in Sources */, 3D88A78B2670EBCB00C87AB5 /* ZSFloatingView.swift in Sources */, B2B4BD091E7EA4AC000CC201 /* ViewController.swift in Sources */, B2A7CD89218017380067D25B /* ZSMobileLogin.swift in Sources */, B263E2E9211EBFEC001819FB /* SQLite+Extension.swift in Sources */, B25CEFF022F9268A002ABE30 /* CTSettings.swift in Sources */, B2B02E561EA9A9F000A6880A /* ZSVoiceBookSegmentViewController.swift in Sources */, B2C0DF6220D4CB350057DC84 /* QSBookDetailPresenter.swift in Sources */, 3DE3AF8123D0622C00D74C1F /* ZSShelfOperatingView.swift in Sources */, 3D31FFAF2216BDAD0011D275 /* ZSReaderViewModel+Bought.swift in Sources */, B2683CF322C8FCE900FE9CF0 /* ZSWebToolHandler.swift in Sources */, B2583CBA1EA782C2004178F3 /* QSThemeTopicInteractor.swift in Sources */, B2B4BBE61E7EA4AB000CC201 /* DarkView.swift in Sources */, B252D489210EF3960091858E /* HTTPFileResponse.m in Sources */, 3DBC088F22CB02030004B3F4 /* ZSCommunityNavigationBar.swift in Sources */, B2B4BBEB1E7EA4AB000CC201 /* LightStarView.swift in Sources */, 3D63E16B235EF9550015B7D3 /* ZSSearchBookViewController.swift in Sources */, B28FFC921EB03EA300C27FF9 /* QSHomeDeleteBtn.swift in Sources */, B252D49A210EF3960091858E /* PUTResponse.m in Sources */, 3D9325EE20B2E07F0049CDBF /* Value.swift in Sources */, B2C63A3B23C1ADC50083C987 /* ZSBookCache.swift in Sources */, B21C735823657ACF00E9D148 /* error.c in Sources */, B252D4E4211037380091858E /* ZSShelfViewController.swift in Sources */, B2C0DF8120D4CC5A0057DC84 /* ZSBaseService.swift in Sources */, B2B02E2D1EA8505200A6880A /* QSTopicDetailProtocols.swift in Sources */, B289DD7C23E5C51A004ED039 /* ZSReaderStyleSelectionView.swift in Sources */, B2583CBE1EA782C2004178F3 /* QSThemeTopicViewController.swift in Sources */, AD065949258C66D1009009FA /* ZSRegularVerifyViewController.swift in Sources */, B2B02E571EA9A9F000A6880A /* ZSVoicePlayerCatelogHeaderView.swift in Sources */, 3D88A78A2670EBCB00C87AB5 /* ZSFloatingViewController.swift in Sources */, B2C0DF6320D4CB350057DC84 /* ChangeSourceViewController.swift in Sources */, 3D448E5F22C501E10099B6E8 /* ZSDiscoverViewController.swift in Sources */, B24B3F5420E888D60098ACE5 /* ZSSearchResultViewController.swift in Sources */, B25CF00822FAA732002ABE30 /* ZSPostReviewAuthor.swift in Sources */, B22204A21EE9284F008E1902 /* QSSplashScreen.swift in Sources */, B2C0DF6F20D4CB350057DC84 /* QSCommunityPresenter.swift in Sources */, 3DBC089522CB56C60004B3F4 /* ZSInsertedBookScoreView.swift in Sources */, B252D48F210EF3960091858E /* MultipartMessageHeaderField.m in Sources */, 3D1F67A5217DCE9B000FB968 /* ZSLeftViewCell.swift in Sources */, B29CFB81217B395D0006F294 /* ZSUserBindCell.swift in Sources */, B29B4F9D1E821E61008852E3 /* UIImage+QSData.swift in Sources */, 3DFBDEDD21F1A7ED00AED519 /* ZSCacheHelper.swift in Sources */, B2580E1B1E9B236000455AFE /* QSSearchInteractor.swift in Sources */, B27D7EA11F4D593D00866341 /* UIScrollView+StateView.m in Sources */, B2B4BC181E7EA4AB000CC201 /* ThemeTopicModel.swift in Sources */, B2B4BC201E7EA4AB000CC201 /* TopicDetailHeaderCell.swift in Sources */, B2B4BC281E7EA4AB000CC201 /* RightTableViewCell.swift in Sources */, 3D9D857C20CA439D003FEDFE /* ZSShelfMessage.swift in Sources */, 3D63E17B235F18F40015B7D3 /* ZSHotWord.swift in Sources */, B2B02E551EA9A9F000A6880A /* ZSVoiceCategoryCell.swift in Sources */, 3D1330CB23C6FA9200D81EF4 /* ZSReaderTouchArea.swift in Sources */, 3D05BCB423C592E2001EAB2A /* ZSReaderToolbar.swift in Sources */, B2683CFF22D083B200FE9CF0 /* ZSNoNetworkView.swift in Sources */, B2B4BBF61E7EA4AB000CC201 /* NSDate+Extension.m in Sources */, 3D63E16D235EF9B80015B7D3 /* ZSSearchBookView.swift in Sources */, 3D82F9ED22C9EFA800F035CB /* ZSDetailButtonCell.swift in Sources */, B2A7CD81217C5E5E0067D25B /* ZSUserBookshelf.swift in Sources */, 3DECC38C22460DD000EEB146 /* ZSSegmenuViewController.swift in Sources */, B25EB92F2194999E000D3657 /* FBEncryptorAESUtils.mm in Sources */, B252D493210EF3960091858E /* DDNumber.m in Sources */, 3D096BF722CF76A70005C9EA /* ZSRefreshFooter.swift in Sources */, B2FCAA8B1E9C82CE0064837C /* QSSearchItem.swift in Sources */, B2C0DF6E20D4CB350057DC84 /* QSCommunityInteractor.swift in Sources */, 3D99893B223FEC5900EA33D5 /* ZSCatelogModel.swift in Sources */, B23505C02783F8EC00D444C7 /* SplashViewController.swift in Sources */, B2B4BC1A1E7EA4AB000CC201 /* TopicDetailModel.swift in Sources */, 3DD0841E21569A54008E3B4A /* TTSConfig.swift in Sources */, B2C0DF6520D4CB350057DC84 /* CategoryController.swift in Sources */, B2B4BC251E7EA4AB000CC201 /* RootViewController+FetchData.swift in Sources */, B252D48E210EF3960091858E /* WebSocket.m in Sources */, B20D74081EED7D250034516F /* QSIntroduceCell.swift in Sources */, B2B4BBEF1E7EA4AB000CC201 /* XYCActionSheet.swift in Sources */, B2C0DF3F20D4CB350057DC84 /* QSBookDetailProtocols.swift in Sources */, 3D05BCB823C59314001EAB2A /* ZSReaderBottomBar.swift in Sources */, B2C0DF4920D4CB350057DC84 /* QSBookDetailRateView.swift in Sources */, 3DAC23CE21E32DA500E77891 /* ZSYJSchemeHandle.swift in Sources */, B278AA661F3954FF000A0D55 /* QSAPI.swift in Sources */, 3D28402822841281009463A3 /* ZSDetailSection.swift in Sources */, B2683D0322D0940200FE9CF0 /* ZSDynamicViewModel.swift in Sources */, B2C0DF6B20D4CB350057DC84 /* QSCommunityRouter.swift in Sources */, B2B4BBFA1E7EA4AB000CC201 /* UINavigationItem+BackItem.m in Sources */, 3DDB6CAA2130F4BC00E8698D /* CoreTextImageData.m in Sources */, 3DABFC2B22B9183D00ECB5E3 /* ZSTabBarController.swift in Sources */, B2A7CD8B2180D6E90067D25B /* ZSModifyNicknameViewController.swift in Sources */, B252D4E2211035060091858E /* ZSShelfViewModel.swift in Sources */, B2A7CDEF218453DC0067D25B /* ZSBestReviewView.swift in Sources */, 3D29C7AD23347E6900113A25 /* ZSReaderBaseViewModel.swift in Sources */, B2B4BC2B1E7EA4AB000CC201 /* SwipableCell.swift in Sources */, B25EB92C2194986C000D3657 /* ZSEncryptorAESUtils.swift in Sources */, B2B4BD7A1E7EE2B1000CC201 /* QSNetworkManager.swift in Sources */, 3D88A7892670EBCB00C87AB5 /* ZSFloatingWindow.swift in Sources */, 3DECC39422461E4900EEB146 /* ZSVoicePlayProgressView.swift in Sources */, B2683D0522D0977C00FE9CF0 /* ZSFollowings.swift in Sources */, 3D2B4D0320EB616D008E3E81 /* ZSReaderViewController.swift in Sources */, B21C735223657ACF00E9D148 /* util.c in Sources */, B2683D0D22D1911900FE9CF0 /* ZSNotificationViewModel.swift in Sources */, B2C0DF3720D4CB350057DC84 /* PageInfo.swift in Sources */, B2B02E3D1EA891E700A6880A /* ZSDetailViewController.swift in Sources */, B29CFAE62179B6E50006F294 /* ZSLoginService.swift in Sources */, 3DECC3E922467F9B00EEB146 /* ZSVoiceSegmentView.swift in Sources */, B2A7CD85217F36B20067D25B /* ZSThirdLoginView.swift in Sources */, B2683CFB22C9111600FE9CF0 /* ZSConfigUtil.swift in Sources */, B2C0DF3820D4CB350057DC84 /* BookDetail.swift in Sources */, 3D08F244212C2DFC007B3D19 /* ZSDiscussWebService.swift in Sources */, 3D2006D1216F36BE00C326B4 /* ZSReaderBaseViewController.swift in Sources */, B2A02F8020F33F710034DC64 /* ZSReaderManager.swift in Sources */, B203BB651E9E119400F9C052 /* QSSearchViewController+Transition.swift in Sources */, 3DBC089122CB37C10004B3F4 /* ZSDiscoverNavigationBar.swift in Sources */, B22AC44F1E9F1C7D008625E6 /* ZSCatelogItemViewController.swift in Sources */, B2B4BBE51E7EA4AB000CC201 /* DarkStarView.swift in Sources */, B2683CF922C8FE8A00FE9CF0 /* ZSWebSpeakHandler.swift in Sources */, 3D448E5822C501C40099B6E8 /* ZSBookShelfViewController.swift in Sources */, 3D62EE5922604768002FFA39 /* ZSVoicePlayerCatelogView.swift in Sources */, B2C0DF4120D4CB350057DC84 /* ToolBar.swift in Sources */, 3D63E173235F021F0015B7D3 /* ZSHeaderSearchCell.swift in Sources */, B2583CB21EA78280004178F3 /* ZSFilterThemeViewController.swift in Sources */, B2683D0B22D188A600FE9CF0 /* ZSNotification.swift in Sources */, 3D63E179235F14BF0015B7D3 /* ZSSearchHotwords.swift in Sources */, B2C0DF6620D4CB350057DC84 /* QSCategoryPresenter.swift in Sources */, 3D1330CD23C7239000D81EF4 /* ZSReaderCatalogViewController.swift in Sources */, B2A02F7E20F33A1A0034DC64 /* ZSHorizonalMoveCell.swift in Sources */, B29CFB6F217ADA690006F294 /* ZSMyCell.swift in Sources */, B21C735723657ACF00E9D148 /* utf8.c in Sources */, B2683D1322D1C57800FE9CF0 /* ZSMineHeaderView.swift in Sources */, 3DDEEB02214B5D59003D12DB /* UIFont+ZSExtension.m in Sources */, 3D58635F20CE1C96002AD3CC /* ZSProtocol.swift in Sources */, B278AA8D1F395F30000A0D55 /* ReadHistoryViewController.swift in Sources */, 3D252CE0219175FD0051B60D /* ZSDatabase.swift in Sources */, B2C0DF6D20D4CB350057DC84 /* QSCommunityProtocols.swift in Sources */, B2ACA06A212915240032305E /* ZSReviewsCell.swift in Sources */, B2C0DF3020D4CB350057DC84 /* QSBookList.swift in Sources */, B22AC43A1E9E6626008625E6 /* QSSearchResultTable.swift in Sources */, 3D05BCB623C592FE001EAB2A /* ZSReaderTopbar.swift in Sources */, B253EE422786E95C00F6C6D9 /* ZSNetwork.swift in Sources */, B278AB061F398926000A0D55 /* M80AttributedLabelAttachment.m in Sources */, B2ACA06E212944AC0032305E /* ZSWebViewController.swift in Sources */, B29CFB7D217B35EA0006F294 /* ZSUserBind.swift in Sources */, 3DE393DB219D514100890488 /* ZSBookBoughtViewModel.swift in Sources */, B22AC4551E9F1E8F008625E6 /* QSRankViewController.swift in Sources */, 3D63E191236043250015B7D3 /* ZSHeaderSearch.swift in Sources */, B252D499210EF3960091858E /* DAVResponse.m in Sources */, B2C0DF3320D4CB350057DC84 /* QSReaderViewFlowLayout.swift in Sources */, 3D28402A2284267C009463A3 /* ZSDetailInfoCell.swift in Sources */, B2580E1F1E9B236000455AFE /* QSSearchViewController.swift in Sources */, 3DDB6CAE2130F4BC00E8698D /* UIView+frameAdjust.m in Sources */, 3D2B4D0720EB68AC008E3E81 /* ZSReaderProtocol.swift in Sources */, 3D865B55219B1975001294EB /* ZSChapterSelectModel.swift in Sources */, B21C735F23657ACF00E9D148 /* OCGumbo.m in Sources */, B252D48D210EF3960091858E /* HTTPConnection.m in Sources */, B2C0DF1E20D4CB350057DC84 /* QSReaderViewController.swift in Sources */, B278AB051F398926000A0D55 /* M80AttributedLabel.m in Sources */, B2C0DF4E20D4CB350057DC84 /* QSDiscussCell.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; B2BB5BA61D8BDF8E00379217 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( B2BB5BAF1D8BDF8E00379217 /* zhuishushenqiTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; B2BB5BB11D8BDF8E00379217 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( B2BB5BBA1D8BDF8E00379217 /* zhuishushenqiUITests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ B2BB5BAC1D8BDF8E00379217 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = B2BB5B951D8BDF8E00379217 /* zhuishushenqi */; targetProxy = B2BB5BAB1D8BDF8E00379217 /* PBXContainerItemProxy */; }; B2BB5BB71D8BDF8E00379217 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = B2BB5B951D8BDF8E00379217 /* zhuishushenqi */; targetProxy = B2BB5BB61D8BDF8E00379217 /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ B2B4BA0E1E7EA4A8000CC201 /* LaunchScreen.storyboard */ = { isa = PBXVariantGroup; children = ( B2B4BA0F1E7EA4A8000CC201 /* Base */, ); name = LaunchScreen.storyboard; sourceTree = "<group>"; }; B2B4BA101E7EA4A8000CC201 /* Main.storyboard */ = { isa = PBXVariantGroup; children = ( B2B4BA111E7EA4A8000CC201 /* Base */, ); name = Main.storyboard; sourceTree = "<group>"; }; B2EE7CF61F3BFC7B00BE997C /* Localizable.strings */ = { isa = PBXVariantGroup; children = ( B2EE7CF51F3BFC7B00BE997C /* en */, B2EE7CFB1F3C063F00BE997C /* zh-Hans */, B2EE7CFC1F3C064800BE997C /* zh-Hant */, ); name = Localizable.strings; path = ..; sourceTree = "<group>"; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ B2BB5BBC1D8BDF8E00379217 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 9.3; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 4.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; B2BB5BBD1D8BDF8E00379217 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 9.3; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; SWIFT_VERSION = 4.0; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; name = Release; }; B2BB5BBF1D8BDF8E00379217 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = B08901F972525F29A370AA86 /* Pods-zhuishushenqi.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = L94HEL9RUT; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)", "$(PROJECT_DIR)/zhuishushenqi/TXTReader/Speech/Model", "$(PROJECT_DIR)/zhuishushenqi/Vendor/ThirdLoginSDK", ); GCC_PREPROCESSOR_DEFINITIONS = ( "$(inherited)", "COCOAPODS=1", ); HEADER_SEARCH_PATHS = ( "$(inherited)", /usr/include/libxml2, ); INFOPLIST_FILE = zhuishushenqi/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 10.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; LIBRARY_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/zhuishushenqi/Vendor/ThirdLoginSDK/WeChatSDK1.8.3", "$(PROJECT_DIR)/zhuishushenqi/Vendor/ThirdLoginSDK/libWeiboSDK", "$(PROJECT_DIR)/zhuishushenqi/Vendor/XimalayaSDK_iOS_5.5.1", "$(PROJECT_DIR)/zhuishushenqi/Vendor/uchardet", ); MACOSX_DEPLOYMENT_TARGET = 8.0; OTHER_LDFLAGS = ( "$(inherited)", "-ObjC", ); OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-D\" \"DEBUG\""; PRODUCT_BUNDLE_IDENTIFIER = com.ifmoc.ZhuiShuShenQicopy; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = ""; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "$(SRCROOT)/zhuishushenqi/zhuishushenqi-Bridge-Header.h"; SWIFT_SWIFT3_OBJC_INFERENCE = On; SWIFT_VERSION = 5.0; }; name = Debug; }; B2BB5BC01D8BDF8E00379217 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = C17F4CC59FFA95D661BDB3BB /* Pods-zhuishushenqi.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = L94HEL9RUT; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)", "$(PROJECT_DIR)/zhuishushenqi/TXTReader/Speech/Model", "$(PROJECT_DIR)/zhuishushenqi/Vendor/ThirdLoginSDK", ); HEADER_SEARCH_PATHS = ( "$(inherited)", /usr/include/libxml2, ); INFOPLIST_FILE = zhuishushenqi/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 10.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; LIBRARY_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/zhuishushenqi/Vendor/ThirdLoginSDK/WeChatSDK1.8.3", "$(PROJECT_DIR)/zhuishushenqi/Vendor/ThirdLoginSDK/libWeiboSDK", "$(PROJECT_DIR)/zhuishushenqi/Vendor/XimalayaSDK_iOS_5.5.1", "$(PROJECT_DIR)/zhuishushenqi/Vendor/uchardet", ); MACOSX_DEPLOYMENT_TARGET = 8.0; OTHER_LDFLAGS = ( "$(inherited)", "-ObjC", ); OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\""; PRODUCT_BUNDLE_IDENTIFIER = com.ifmoc.ZhuiShuShenQicopy; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = ""; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "$(SRCROOT)/zhuishushenqi/zhuishushenqi-Bridge-Header.h"; SWIFT_SWIFT3_OBJC_INFERENCE = On; SWIFT_VERSION = 5.0; }; name = Release; }; B2BB5BC21D8BDF8E00379217 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; BUNDLE_LOADER = "$(TEST_HOST)"; INFOPLIST_FILE = zhuishushenqiTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = CNY.zhuishushenqiTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/zhuishushenqi.app/zhuishushenqi"; }; name = Debug; }; B2BB5BC31D8BDF8E00379217 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; BUNDLE_LOADER = "$(TEST_HOST)"; INFOPLIST_FILE = zhuishushenqiTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = CNY.zhuishushenqiTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/zhuishushenqi.app/zhuishushenqi"; }; name = Release; }; B2BB5BC51D8BDF8E00379217 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; INFOPLIST_FILE = zhuishushenqiUITests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = CNY.zhuishushenqiUITests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TEST_TARGET_NAME = zhuishushenqi; }; name = Debug; }; B2BB5BC61D8BDF8E00379217 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; INFOPLIST_FILE = zhuishushenqiUITests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = CNY.zhuishushenqiUITests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TEST_TARGET_NAME = zhuishushenqi; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ B2BB5B911D8BDF8E00379217 /* Build configuration list for PBXProject "zhuishushenqi" */ = { isa = XCConfigurationList; buildConfigurations = ( B2BB5BBC1D8BDF8E00379217 /* Debug */, B2BB5BBD1D8BDF8E00379217 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; B2BB5BBE1D8BDF8E00379217 /* Build configuration list for PBXNativeTarget "zhuishushenqi" */ = { isa = XCConfigurationList; buildConfigurations = ( B2BB5BBF1D8BDF8E00379217 /* Debug */, B2BB5BC01D8BDF8E00379217 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; B2BB5BC11D8BDF8E00379217 /* Build configuration list for PBXNativeTarget "zhuishushenqiTests" */ = { isa = XCConfigurationList; buildConfigurations = ( B2BB5BC21D8BDF8E00379217 /* Debug */, B2BB5BC31D8BDF8E00379217 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; B2BB5BC41D8BDF8E00379217 /* Build configuration list for PBXNativeTarget "zhuishushenqiUITests" */ = { isa = XCConfigurationList; buildConfigurations = ( B2BB5BC51D8BDF8E00379217 /* Debug */, B2BB5BC61D8BDF8E00379217 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = B2BB5B8E1D8BDF8E00379217 /* Project object */; } ================================================ FILE: zhuishushenqi.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ <?xml version="1.0" encoding="UTF-8"?> <Workspace version = "1.0"> <FileRef location = "self:zhuishushenqi.xcodeproj"> </FileRef> </Workspace> ================================================ FILE: zhuishushenqi.xcodeproj/project.xcworkspace/xcshareddata/zhuishushenqi.xcscmblueprint ================================================ { "DVTSourceControlWorkspaceBlueprintPrimaryRemoteRepositoryKey" : "8EAE421A06CC5A2DA4924CB8CA9D1557C230C9E4", "DVTSourceControlWorkspaceBlueprintWorkingCopyRepositoryLocationsKey" : { }, "DVTSourceControlWorkspaceBlueprintWorkingCopyStatesKey" : { "8EAE421A06CC5A2DA4924CB8CA9D1557C230C9E4" : 9223372036854775807, "207AB710B2CF8977BF5E9345336B930D3171DF4B" : 9223372036854775807 }, "DVTSourceControlWorkspaceBlueprintIdentifierKey" : "7644B9BC-DD25-482A-8102-8CC05A4B3F4A", "DVTSourceControlWorkspaceBlueprintWorkingCopyPathsKey" : { "8EAE421A06CC5A2DA4924CB8CA9D1557C230C9E4" : "zhuishushenqi\/", "207AB710B2CF8977BF5E9345336B930D3171DF4B" : "..\/iOS-New\/TXTReader" }, "DVTSourceControlWorkspaceBlueprintNameKey" : "zhuishushenqi", "DVTSourceControlWorkspaceBlueprintVersion" : 204, "DVTSourceControlWorkspaceBlueprintRelativePathToProjectKey" : "zhuishushenqi.xcodeproj", "DVTSourceControlWorkspaceBlueprintRemoteRepositoriesKey" : [ { "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "github.com:TomasEdison\/TXTReader.git", "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "207AB710B2CF8977BF5E9345336B930D3171DF4B" }, { "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "github.com:TomasEdison\/zhuishushenqi.git", "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "8EAE421A06CC5A2DA4924CB8CA9D1557C230C9E4" } ] } ================================================ FILE: zhuishushenqi.xcodeproj/xcshareddata/xcschemes/zhuishushenqi.xcscheme ================================================ <?xml version="1.0" encoding="UTF-8"?> <Scheme LastUpgradeVersion = "1020" version = "1.3"> <BuildAction parallelizeBuildables = "YES" buildImplicitDependencies = "YES"> <BuildActionEntries> <BuildActionEntry buildForTesting = "YES" buildForRunning = "YES" buildForProfiling = "YES" buildForArchiving = "YES" buildForAnalyzing = "YES"> <BuildableReference BuildableIdentifier = "primary" BlueprintIdentifier = "B2BB5B951D8BDF8E00379217" BuildableName = "zhuishushenqi.app" BlueprintName = "zhuishushenqi" ReferencedContainer = "container:zhuishushenqi.xcodeproj"> </BuildableReference> </BuildActionEntry> </BuildActionEntries> </BuildAction> <TestAction buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" shouldUseLaunchSchemeArgsEnv = "YES"> <MacroExpansion> <BuildableReference BuildableIdentifier = "primary" BlueprintIdentifier = "B2BB5B951D8BDF8E00379217" BuildableName = "zhuishushenqi.app" BlueprintName = "zhuishushenqi" ReferencedContainer = "container:zhuishushenqi.xcodeproj"> </BuildableReference> </MacroExpansion> <Testables> <TestableReference skipped = "NO"> <BuildableReference BuildableIdentifier = "primary" BlueprintIdentifier = "B2BB5BA91D8BDF8E00379217" BuildableName = "zhuishushenqiTests.xctest" BlueprintName = "zhuishushenqiTests" ReferencedContainer = "container:zhuishushenqi.xcodeproj"> </BuildableReference> </TestableReference> <TestableReference skipped = "NO"> <BuildableReference BuildableIdentifier = "primary" BlueprintIdentifier = "B2BB5BB41D8BDF8E00379217" BuildableName = "zhuishushenqiUITests.xctest" BlueprintName = "zhuishushenqiUITests" ReferencedContainer = "container:zhuishushenqi.xcodeproj"> </BuildableReference> </TestableReference> </Testables> </TestAction> <LaunchAction buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" disableMainThreadChecker = "YES" launchStyle = "0" useCustomWorkingDirectory = "NO" ignoresPersistentStateOnLaunch = "NO" debugDocumentVersioning = "YES" debugServiceExtension = "internal" allowLocationSimulation = "YES"> <BuildableProductRunnable runnableDebuggingMode = "0"> <BuildableReference BuildableIdentifier = "primary" BlueprintIdentifier = "B2BB5B951D8BDF8E00379217" BuildableName = "zhuishushenqi.app" BlueprintName = "zhuishushenqi" ReferencedContainer = "container:zhuishushenqi.xcodeproj"> </BuildableReference> </BuildableProductRunnable> <EnvironmentVariables> <EnvironmentVariable key = "DYLD_PRINT_STATISTICS" value = "1" isEnabled = "YES"> </EnvironmentVariable> </EnvironmentVariables> </LaunchAction> <ProfileAction buildConfiguration = "Release" shouldUseLaunchSchemeArgsEnv = "YES" savedToolIdentifier = "" useCustomWorkingDirectory = "NO" debugDocumentVersioning = "YES"> <BuildableProductRunnable runnableDebuggingMode = "0"> <BuildableReference BuildableIdentifier = "primary" BlueprintIdentifier = "B2BB5B951D8BDF8E00379217" BuildableName = "zhuishushenqi.app" BlueprintName = "zhuishushenqi" ReferencedContainer = "container:zhuishushenqi.xcodeproj"> </BuildableReference> </BuildableProductRunnable> </ProfileAction> <AnalyzeAction buildConfiguration = "Debug"> </AnalyzeAction> <ArchiveAction buildConfiguration = "Release" revealArchiveInOrganizer = "YES"> </ArchiveAction> </Scheme> ================================================ FILE: zhuishushenqi.xcworkspace/contents.xcworkspacedata ================================================ <?xml version="1.0" encoding="UTF-8"?> <Workspace version = "1.0"> <FileRef location = "group:zhuishushenqi.xcodeproj"> </FileRef> <FileRef location = "group:Pods/Pods.xcodeproj"> </FileRef> </Workspace> ================================================ FILE: zhuishushenqi.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>IDEDidComputeMac32BitWarning</key> <true/> </dict> </plist> ================================================ FILE: zhuishushenqiTests/Info.plist ================================================ <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>CFBundleDevelopmentRegion</key> <string>en</string> <key>CFBundleExecutable</key> <string>$(EXECUTABLE_NAME)</string> <key>CFBundleIdentifier</key> <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string> <key>CFBundleInfoDictionaryVersion</key> <string>6.0</string> <key>CFBundleName</key> <string>$(PRODUCT_NAME)</string> <key>CFBundlePackageType</key> <string>BNDL</string> <key>CFBundleShortVersionString</key> <string>1.0</string> <key>CFBundleSignature</key> <string>????</string> <key>CFBundleVersion</key> <string>1</string> </dict> </plist> ================================================ FILE: zhuishushenqiTests/zhuishushenqiTests.swift ================================================ // // zhuishushenqiTests.swift // zhuishushenqiTests // // Created by Nory Cao on 16/9/16. // Copyright © 2016年 QS. All rights reserved. // import XCTest @testable import zhuishushenqi class zhuishushenqiTests: XCTestCase { override func setUp() { super.setUp() // Put setup code here. This method is called before the invocation of each test method in the class. } override func tearDown() { // Put teardown code here. This method is called after the invocation of each test method in the class. super.tearDown() } func testExample() { // This is an example of a functional test case. // Use XCTAssert and related functions to verify your tests produce the correct results. } func testPerformanceExample() { // This is an example of a performance test case. self.measure { // Put the code you want to measure the time of here. } } } ================================================ FILE: zhuishushenqiUITests/Info.plist ================================================ <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>CFBundleDevelopmentRegion</key> <string>en</string> <key>CFBundleExecutable</key> <string>$(EXECUTABLE_NAME)</string> <key>CFBundleIdentifier</key> <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string> <key>CFBundleInfoDictionaryVersion</key> <string>6.0</string> <key>CFBundleName</key> <string>$(PRODUCT_NAME)</string> <key>CFBundlePackageType</key> <string>BNDL</string> <key>CFBundleShortVersionString</key> <string>1.0</string> <key>CFBundleSignature</key> <string>????</string> <key>CFBundleVersion</key> <string>1</string> </dict> </plist> ================================================ FILE: zhuishushenqiUITests/zhuishushenqiUITests.swift ================================================ // // zhuishushenqiUITests.swift // zhuishushenqiUITests // // Created by Nory Cao on 16/9/16. // Copyright © 2016年 QS. All rights reserved. // import XCTest class zhuishushenqiUITests: XCTestCase { override func setUp() { super.setUp() // Put setup code here. This method is called before the invocation of each test method in the class. // In UI tests it is usually best to stop immediately when a failure occurs. continueAfterFailure = false // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method. XCUIApplication().launch() // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. } override func tearDown() { // Put teardown code here. This method is called after the invocation of each test method in the class. super.tearDown() } func testExample() { // Use recording to get started writing UI tests. // Use XCTAssert and related functions to verify your tests produce the correct results. } }