tungsten 0.6.3 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.build-complete +1 -1
- package/dist/Brands.d.ts +16 -0
- package/dist/Brands.d.ts.map +1 -0
- package/dist/Brands.js +22 -0
- package/dist/Components.d.ts +4 -1
- package/dist/Components.d.ts.map +1 -1
- package/dist/Components.js +24 -6
- package/dist/ContentNode.d.ts +3 -2
- package/dist/ContentNode.d.ts.map +1 -1
- package/dist/ContentNode.js +11 -0
- package/dist/Framework.d.ts.map +1 -1
- package/dist/Framework.js +23 -4
- package/dist/FrameworkConfig.d.ts +9 -1
- package/dist/FrameworkConfig.d.ts.map +1 -1
- package/dist/FrameworkConfig.types.test.d.ts +2 -0
- package/dist/FrameworkConfig.types.test.d.ts.map +1 -0
- package/dist/FrameworkConfig.types.test.js +6 -0
- package/dist/FunctionComponent.d.ts +1 -0
- package/dist/FunctionComponent.d.ts.map +1 -1
- package/dist/FunctionComponentStandIn.d.ts.map +1 -1
- package/dist/Instance/Instance.d.ts +21 -9
- package/dist/Instance/Instance.d.ts.map +1 -1
- package/dist/Instance/Instance.js +42 -38
- package/dist/Instance/InstanceConfig.d.ts +3 -1
- package/dist/Instance/InstanceConfig.d.ts.map +1 -1
- package/dist/RenderedContentNode.d.ts.map +1 -1
- package/dist/RenderingNode.d.ts.map +1 -1
- package/dist/Servers/DevServer.d.ts +4 -2
- package/dist/Servers/DevServer.d.ts.map +1 -1
- package/dist/Servers/DevServer.js +72 -21
- package/dist/Servers/ProdServer.d.ts +5 -2
- package/dist/Servers/ProdServer.d.ts.map +1 -1
- package/dist/Servers/ProdServer.js +95 -19
- package/dist/Servers/StaticSiteServer.d.ts +1 -1
- package/dist/Servers/StaticSiteServer.d.ts.map +1 -1
- package/dist/Servers/TungstenServer.d.ts +2 -1
- package/dist/Servers/TungstenServer.d.ts.map +1 -1
- package/dist/Servers/TungstenServer.js +7 -0
- package/dist/Servers/cacheVersioned.d.ts +3 -0
- package/dist/Servers/cacheVersioned.d.ts.map +1 -0
- package/dist/Servers/cacheVersioned.js +23 -0
- package/dist/Servers/devErrorPageAutoRetry.d.ts.map +1 -1
- package/dist/Servers/devErrorPageAutoRetry.js +1 -0
- package/dist/Servers/initStores.d.ts +4 -4
- package/dist/Servers/initStores.d.ts.map +1 -1
- package/dist/Servers/initStores.js +2 -2
- package/dist/Servers/loadEnvsDev.d.ts.map +1 -1
- package/dist/Servers/loadEnvsDev.js +7 -6
- package/dist/Servers/resetRegistrations.d.ts +2 -0
- package/dist/Servers/resetRegistrations.d.ts.map +1 -0
- package/dist/Servers/resetRegistrations.js +13 -0
- package/dist/Servers/routing.d.ts +3 -2
- package/dist/Servers/routing.d.ts.map +1 -1
- package/dist/Servers/routing.js +12 -7
- package/dist/Servers/stubInstance.d.ts +8 -0
- package/dist/Servers/stubInstance.d.ts.map +1 -0
- package/dist/Servers/stubInstance.js +40 -0
- package/dist/Store/InternalStoreSchema.d.ts +1 -1
- package/dist/Store/Store.js +2 -1
- package/dist/Store/StoreSchemaClass.d.ts +6 -3
- package/dist/Store/StoreSchemaClass.d.ts.map +1 -1
- package/dist/Store/StoreSchemaClass.js +8 -1
- package/dist/Store/TypeSafeStore.js +2 -1
- package/dist/Store/createStores.d.ts +3 -3
- package/dist/Store/createStores.d.ts.map +1 -1
- package/dist/Store/createStores.js +7 -2
- package/dist/Store/getInternalStore.d.ts.map +1 -1
- package/dist/Store/registerStore.d.ts.map +1 -1
- package/dist/Store/registerStore.js +2 -2
- package/dist/Store/storeCreators.d.ts +2 -2
- package/dist/Store/storeCreators.d.ts.map +1 -1
- package/dist/TungstenApp.d.ts +3 -3
- package/dist/TungstenApp.d.ts.map +1 -1
- package/dist/_client/cjsHelpers.d.ts.map +1 -1
- package/dist/_client/cjsHelpers.js +5 -3
- package/dist/_client/client.d.ts +2 -0
- package/dist/_client/client.d.ts.map +1 -1
- package/dist/_client/client.js +10 -3
- package/dist/api/getApiRoutes.d.ts.map +1 -1
- package/dist/api/getExport.d.ts +1 -1
- package/dist/api/getExport.d.ts.map +1 -1
- package/dist/api/getExport.js +2 -1
- package/dist/api/getExportDev.d.ts +1 -1
- package/dist/api/getExportDev.d.ts.map +1 -1
- package/dist/appBuilder.d.ts +69 -0
- package/dist/appBuilder.d.ts.map +1 -0
- package/dist/appBuilder.js +98 -0
- package/dist/appBuilder.types.test.d.ts +110 -0
- package/dist/appBuilder.types.test.d.ts.map +1 -0
- package/dist/appBuilder.types.test.js +240 -0
- package/dist/babelPlugins/BabelTransformBindDirect.js +7 -7
- package/dist/babelPlugins/BabelTransformBindVar.js +7 -7
- package/dist/babelPlugins/BabelTransformComponentState.d.ts.map +1 -1
- package/dist/babelPlugins/BabelTransformComponentStyles.d.ts.map +1 -1
- package/dist/babelPlugins/BabelTransformComponentStyles.js +5 -5
- package/dist/babelPlugins/BabelTransformCssImports.d.ts.map +1 -1
- package/dist/babelPlugins/BabelTransformCssImports.js +2 -2
- package/dist/babelPlugins/BabelTransformDeproxyState.d.ts.map +1 -1
- package/dist/babelPlugins/BabelTransformPackageImports.d.ts.map +1 -1
- package/dist/babelPlugins/BabelTransformPackageImports.js +11 -6
- package/dist/babelPlugins/BabelTransformRemoveDevBlocks.d.ts.map +1 -1
- package/dist/babelPlugins/BabelTransformRemoveServerBlocks.d.ts.map +1 -1
- package/dist/babelPlugins/BabelTypes.d.ts.map +1 -1
- package/dist/babelPlugins/transformPackageImports/BabelTransformDependencyImportsCjs.d.ts.map +1 -1
- package/dist/babelPlugins/transformPackageImports/BabelTransformDependencyImportsCjs.js +1 -1
- package/dist/babelPlugins/transformPackageImports/BabelTransformDependencyImportsEsm.d.ts.map +1 -1
- package/dist/babelPlugins/transformPackageImports/BabelTransformDependencyImportsEsm.js +2 -2
- package/dist/bin/commands/compile.d.ts.map +1 -1
- package/dist/bin/commands/compile.js +2 -1
- package/dist/build/CompilationInfo.d.ts +3 -0
- package/dist/build/CompilationInfo.d.ts.map +1 -1
- package/dist/build/assetVersions.d.ts +9 -0
- package/dist/build/assetVersions.d.ts.map +1 -0
- package/dist/build/assetVersions.js +39 -0
- package/dist/build/babel.user.common.config.d.ts.map +1 -1
- package/dist/build/bundle.d.ts.map +1 -1
- package/dist/build/bundle.js +2 -1
- package/dist/build/bundleBabelOptions.d.ts.map +1 -1
- package/dist/build/clientRoutesFile.d.ts +2 -0
- package/dist/build/clientRoutesFile.d.ts.map +1 -0
- package/dist/build/clientRoutesFile.js +13 -0
- package/dist/build/compileJavascript.d.ts.map +1 -1
- package/dist/build/compileJavascript.js +4 -3
- package/dist/build/compileStyles.d.ts +2 -1
- package/dist/build/compileStyles.d.ts.map +1 -1
- package/dist/build/compileStyles.js +38 -83
- package/dist/build/compileUserApp.d.ts.map +1 -1
- package/dist/build/componentStylesCache.d.ts.map +1 -1
- package/dist/build/componentStylesCache.js +3 -2
- package/dist/build/copyAssets.d.ts.map +1 -1
- package/dist/build/copyAssets.js +2 -1
- package/dist/build/copyPackageModulesToPackagesFolder.d.ts.map +1 -1
- package/dist/build/copyPackageModulesToPackagesFolder.js +1 -1
- package/dist/build/copyPublicFiles.d.ts.map +1 -1
- package/dist/build/copyPublicFiles.js +2 -1
- package/dist/build/createClientFiles.d.ts.map +1 -1
- package/dist/build/createClientFiles.js +9 -5
- package/dist/build/createInstanceConfigFile.d.ts +1 -1
- package/dist/build/createInstanceConfigFile.d.ts.map +1 -1
- package/dist/build/createInstanceConfigFile.js +9 -3
- package/dist/build/frameworkDestLocation.d.ts.map +1 -1
- package/dist/build/frameworkDestLocation.js +5 -6
- package/dist/build/frameworkStylesheets.d.ts.map +1 -1
- package/dist/build/frameworkStylesheets.js +1 -1
- package/dist/build/makeBabelRawConfig.d.ts.map +1 -1
- package/dist/build/makeBabelUserDevConfig.d.ts +2 -2
- package/dist/build/makeBabelUserDevConfig.d.ts.map +1 -1
- package/dist/build/makeBabelUserProdConfig.d.ts.map +1 -1
- package/dist/build/rollup.user.prod.config.d.ts.map +1 -1
- package/dist/build/rollup.user.prod.config.js +5 -3
- package/dist/build/rollupProdConstants.d.ts +5 -0
- package/dist/build/rollupProdConstants.d.ts.map +1 -0
- package/dist/build/rollupProdConstants.js +21 -0
- package/dist/cacheControl.d.ts +13 -0
- package/dist/cacheControl.d.ts.map +1 -0
- package/dist/cacheControl.js +22 -0
- package/dist/compat/types.d.ts +1 -1
- package/dist/compat/types.d.ts.map +1 -1
- package/dist/componentHelpers.d.ts +19 -0
- package/dist/componentHelpers.d.ts.map +1 -0
- package/dist/componentHelpers.js +31 -0
- package/dist/componentProxyHandlers.d.ts.map +1 -1
- package/dist/components/Fragment.d.ts +8 -0
- package/dist/components/Fragment.d.ts.map +1 -0
- package/dist/components/Fragment.js +10 -0
- package/dist/components/Link.d.ts +4 -3
- package/dist/components/Link.d.ts.map +1 -1
- package/dist/components/Link.js +1 -0
- package/dist/components/NoEscape.d.ts +8 -0
- package/dist/components/NoEscape.d.ts.map +1 -0
- package/dist/components/NoEscape.js +6 -0
- package/dist/components/RouteRedirectComponent.d.ts.map +1 -1
- package/dist/components/RouteRedirectComponent.js +1 -1
- package/dist/components/WebRedirectComponent.d.ts.map +1 -1
- package/dist/components/WebRedirectComponent.js +1 -1
- package/dist/components/bindable/BindVariable.d.ts +1 -0
- package/dist/components/bindable/BindVariable.d.ts.map +1 -1
- package/dist/components/bindable/BindableCheckbox.d.ts +7 -7
- package/dist/components/bindable/BindableCheckbox.d.ts.map +1 -1
- package/dist/components/bindable/BindableInput.d.ts +7 -7
- package/dist/components/bindable/BindableInput.d.ts.map +1 -1
- package/dist/components/bindable/BindableInputNonString.d.ts +2 -2
- package/dist/components/bindable/BindableInputNonString.d.ts.map +1 -1
- package/dist/components/bindable/BindableInputString.d.ts +2 -2
- package/dist/components/bindable/BindableInputString.d.ts.map +1 -1
- package/dist/components/bindable/BindableInputTyped.d.ts +3 -3
- package/dist/components/bindable/BindableInputTyped.d.ts.map +1 -1
- package/dist/components/bindable/BindableInputTypedLegacy.d.ts +3 -3
- package/dist/components/bindable/BindableInputTypedLegacy.d.ts.map +1 -1
- package/dist/components/bindable/BindableRadio.js +2 -2
- package/dist/components/bindable/BindableSelect.d.ts +13 -10
- package/dist/components/bindable/BindableSelect.d.ts.map +1 -1
- package/dist/components/bindable/BindableSelectNaive.js +3 -3
- package/dist/components/bindable/BindableTextarea.d.ts +7 -7
- package/dist/components/bindable/BindableTextarea.d.ts.map +1 -1
- package/dist/components/bindable/bindableCandidates.comparison.d.ts +136 -0
- package/dist/components/bindable/bindableCandidates.comparison.d.ts.map +1 -0
- package/dist/components/bindable/bindableCandidates.comparison.js +268 -0
- package/dist/components/bindable/bindableProps.types.test.d.ts +50 -0
- package/dist/components/bindable/bindableProps.types.test.d.ts.map +1 -0
- package/dist/components/bindable/bindableProps.types.test.js +252 -0
- package/dist/components/library/RichTextEditor/RichTextEditor.d.ts.map +1 -1
- package/dist/components/library/RichTextEditor/contentTree.d.ts.map +1 -1
- package/dist/components/makeTypedLink.d.ts +9 -0
- package/dist/components/makeTypedLink.d.ts.map +1 -0
- package/dist/components/makeTypedLink.js +15 -0
- package/dist/context/registerContext.d.ts +22 -0
- package/dist/context/registerContext.d.ts.map +1 -0
- package/dist/context/registerContext.js +18 -0
- package/dist/env/getEnvSchemas.d.ts.map +1 -1
- package/dist/env/getEnvSchemas.js +2 -1
- package/dist/fileVersions.d.ts +9 -0
- package/dist/fileVersions.d.ts.map +1 -0
- package/dist/fileVersions.js +28 -0
- package/dist/findUserConfig.d.ts +1 -1
- package/dist/findUserConfig.d.ts.map +1 -1
- package/dist/findUserConfig.js +2 -1
- package/dist/frameworkConfigSchema.d.ts +16 -2
- package/dist/frameworkConfigSchema.d.ts.map +1 -1
- package/dist/frameworkConfigSchema.js +15 -3
- package/dist/getComponent.d.ts.map +1 -1
- package/dist/getComponent.js +4 -3
- package/dist/getRoutes.d.ts.map +1 -1
- package/dist/getRoutes.js +9 -8
- package/dist/getUserConfig.d.ts.map +1 -1
- package/dist/hmr/applyHmrModule.d.ts.map +1 -1
- package/dist/hmr/componentIdentity.js +1 -1
- package/dist/hooks/getCurrentComponent.d.ts +1 -1
- package/dist/hooks/getCurrentComponent.d.ts.map +1 -1
- package/dist/hydrate/hydrate.d.ts +5 -1
- package/dist/hydrate/hydrate.d.ts.map +1 -1
- package/dist/hydrate/hydrate.js +86 -52
- package/dist/hydrate/hydrateGlobalData.d.ts +4 -3
- package/dist/hydrate/hydrateGlobalData.d.ts.map +1 -1
- package/dist/hydrate/reconcileHydratedAttributes.d.ts +2 -1
- package/dist/hydrate/reconcileHydratedAttributes.d.ts.map +1 -1
- package/dist/hydrate/reconcileHydratedAttributes.js +4 -3
- package/dist/hydrate/renderHydrationMismatchBanner.d.ts.map +1 -1
- package/dist/hydrate/renderHydrationMismatchBanner.js +2 -1
- package/dist/hydrate/stubRouting.d.ts +8 -0
- package/dist/hydrate/stubRouting.d.ts.map +1 -0
- package/dist/hydrate/stubRouting.js +25 -0
- package/dist/importDevModuleWithRetry.d.ts +2 -1
- package/dist/importDevModuleWithRetry.d.ts.map +1 -1
- package/dist/importDevModuleWithRetry.js +2 -1
- package/dist/importModule.d.ts +3 -0
- package/dist/importModule.d.ts.map +1 -0
- package/dist/importModule.js +4 -0
- package/dist/index.js +2 -1
- package/dist/indexInner.d.ts +30 -7
- package/dist/indexInner.d.ts.map +1 -1
- package/dist/indexInner.js +37 -2
- package/dist/json.d.ts +12 -0
- package/dist/json.d.ts.map +1 -0
- package/dist/json.js +8 -0
- package/dist/jsxHelpers.d.ts +3 -0
- package/dist/jsxHelpers.d.ts.map +1 -0
- package/dist/jsxHelpers.js +6 -0
- package/dist/jsxRuntime.d.ts +1 -0
- package/dist/jsxRuntime.d.ts.map +1 -1
- package/dist/jsxRuntime.js +3 -1
- package/dist/jsxRuntimeSignals.d.ts.map +1 -1
- package/dist/jsxRuntimeSignals.js +1 -0
- package/dist/makeFinalConfig.d.ts.map +1 -1
- package/dist/makeFinalConfig.js +2 -0
- package/dist/makeInstanceConfigFromFrameworkConfig.d.ts.map +1 -1
- package/dist/makeInstanceConfigFromFrameworkConfig.js +2 -0
- package/dist/mergeConfigs.d.ts.map +1 -1
- package/dist/mergeConfigs.js +2 -6
- package/dist/nbsp.d.ts +1 -2
- package/dist/nbsp.d.ts.map +1 -1
- package/dist/nbsp.js +3 -7
- package/dist/objectMap.d.ts +1 -1
- package/dist/objectMap.d.ts.map +1 -1
- package/dist/pipeline/PipelineRequestHandler.d.ts +25 -0
- package/dist/pipeline/PipelineRequestHandler.d.ts.map +1 -0
- package/dist/pipeline/PipelineRequestHandler.js +104 -0
- package/dist/pipeline/contextStore.d.ts +12 -0
- package/dist/pipeline/contextStore.d.ts.map +1 -0
- package/dist/pipeline/contextStore.js +13 -0
- package/dist/pipeline/hydratePipeline.d.ts +4 -0
- package/dist/pipeline/hydratePipeline.d.ts.map +1 -0
- package/dist/pipeline/hydratePipeline.js +83 -0
- package/dist/pipeline/importRoutesModule.d.ts +6 -0
- package/dist/pipeline/importRoutesModule.d.ts.map +1 -0
- package/dist/pipeline/importRoutesModule.js +33 -0
- package/dist/pipeline/networkedTransport.d.ts +11 -0
- package/dist/pipeline/networkedTransport.d.ts.map +1 -0
- package/dist/pipeline/networkedTransport.js +54 -0
- package/dist/pipeline/pipeline.d.ts +107 -0
- package/dist/pipeline/pipeline.d.ts.map +1 -0
- package/dist/pipeline/pipeline.js +113 -0
- package/dist/pipeline/pipeline.types.test.d.ts +760 -0
- package/dist/pipeline/pipeline.types.test.d.ts.map +1 -0
- package/dist/pipeline/pipeline.types.test.js +344 -0
- package/dist/pipeline/pipelineDefinition.d.ts +78 -0
- package/dist/pipeline/pipelineDefinition.d.ts.map +1 -0
- package/dist/pipeline/pipelineDefinition.js +11 -0
- package/dist/pipeline/pipelineSplit.types.test.d.ts +2445 -0
- package/dist/pipeline/pipelineSplit.types.test.d.ts.map +1 -0
- package/dist/pipeline/pipelineSplit.types.test.js +440 -0
- package/dist/pipeline/runPipeline.d.ts +29 -0
- package/dist/pipeline/runPipeline.d.ts.map +1 -0
- package/dist/pipeline/runPipeline.js +71 -0
- package/dist/pipeline/runSplitPipeline.d.ts +10 -0
- package/dist/pipeline/runSplitPipeline.d.ts.map +1 -0
- package/dist/pipeline/runSplitPipeline.js +52 -0
- package/dist/pipeline/splitPipeline.d.ts +62 -0
- package/dist/pipeline/splitPipeline.d.ts.map +1 -0
- package/dist/pipeline/splitPipeline.js +76 -0
- package/dist/pop.js +1 -1
- package/dist/rawHtml/filterContentNode.d.ts.map +1 -1
- package/dist/render/HeadData.d.ts +3 -1
- package/dist/render/HeadData.d.ts.map +1 -1
- package/dist/render/classComponentNodeToRenderingTree.d.ts.map +1 -1
- package/dist/render/componentNodeToRenderingTree.d.ts.map +1 -1
- package/dist/render/contentToHtml.d.ts +2 -2
- package/dist/render/contentToHtml.d.ts.map +1 -1
- package/dist/render/contentToHtml.js +2 -1
- package/dist/render/contentToHtmlDev.d.ts +1 -1
- package/dist/render/contentToHtmlDev.d.ts.map +1 -1
- package/dist/render/contentTreeToRenderingTree.d.ts.map +1 -1
- package/dist/render/contentTreeToRenderingTree.js +6 -2
- package/dist/render/functionComponentNodeToRenderingTree.d.ts.map +1 -1
- package/dist/render/functionComponentNodeToRenderingTree.js +2 -2
- package/dist/render/instantiateComponent.d.ts.map +1 -1
- package/dist/render/processHead.d.ts.map +1 -1
- package/dist/render/processHead.js +12 -18
- package/dist/render/renderingTreeToHtml.d.ts.map +1 -1
- package/dist/render/renderingTreeToHtml.js +6 -3
- package/dist/render/rootToHtml.d.ts +2 -2
- package/dist/render/rootToHtml.d.ts.map +1 -1
- package/dist/requests/fetch/delete.d.ts.map +1 -1
- package/dist/requests/fetch/delete.js +5 -2
- package/dist/requests/fetch/get.js +1 -1
- package/dist/requests/fetch/patch.d.ts.map +1 -1
- package/dist/requests/fetch/patch.js +5 -2
- package/dist/requests/fetch/post.d.ts.map +1 -1
- package/dist/requests/fetch/post.js +5 -2
- package/dist/requests/fetch/put.d.ts.map +1 -1
- package/dist/requests/fetch/put.js +5 -2
- package/dist/routesLocationParts.d.ts +6 -0
- package/dist/routesLocationParts.d.ts.map +1 -0
- package/dist/routesLocationParts.js +10 -0
- package/dist/routing/AppRoutes.d.ts +5 -3
- package/dist/routing/AppRoutes.d.ts.map +1 -1
- package/dist/routing/MatchedRouteInfo.d.ts +13 -0
- package/dist/routing/MatchedRouteInfo.d.ts.map +1 -0
- package/dist/routing/MatchedRouteInfo.js +1 -0
- package/dist/routing/RouteContentFunction.d.ts +1 -2
- package/dist/routing/RouteContentFunction.d.ts.map +1 -1
- package/dist/routing/RouteDataFunction.type.d.ts +4 -2
- package/dist/routing/RouteDataFunction.type.d.ts.map +1 -1
- package/dist/routing/RoutePaths.d.ts +12 -0
- package/dist/routing/RoutePaths.d.ts.map +1 -0
- package/dist/routing/RouteTypes.d.ts +6 -4
- package/dist/routing/RouteTypes.d.ts.map +1 -1
- package/dist/routing/Routes.d.ts +20 -11
- package/dist/routing/Routes.d.ts.map +1 -1
- package/dist/routing/RoutesFunction.d.ts +11 -2
- package/dist/routing/RoutesFunction.d.ts.map +1 -1
- package/dist/routing/Serializable.d.ts +5 -0
- package/dist/routing/Serializable.d.ts.map +1 -0
- package/dist/routing/Serializable.js +1 -0
- package/dist/routing/builder.d.ts +100 -0
- package/dist/routing/builder.d.ts.map +1 -0
- package/dist/routing/builder.js +219 -0
- package/dist/routing/builder.trial.d.ts +2 -0
- package/dist/routing/builder.trial.d.ts.map +1 -0
- package/dist/routing/builder.trial.js +85 -0
- package/dist/routing/defineRoutes.d.ts +8 -0
- package/dist/routing/defineRoutes.d.ts.map +1 -0
- package/dist/routing/defineRoutes.js +3 -0
- package/dist/routing/pyramidRouting.d.ts +2 -0
- package/dist/routing/pyramidRouting.d.ts.map +1 -0
- package/dist/routing/pyramidRouting.js +13 -0
- package/dist/routing/redirectOf.d.ts +11 -0
- package/dist/routing/redirectOf.d.ts.map +1 -0
- package/dist/routing/redirectOf.js +32 -0
- package/dist/routing/registerGlobalData.d.ts +11 -3
- package/dist/routing/registerGlobalData.d.ts.map +1 -1
- package/dist/routing/registerGlobalData.js +6 -0
- package/dist/routing/registerParts.types.test.d.ts +2 -0
- package/dist/routing/registerParts.types.test.d.ts.map +1 -0
- package/dist/routing/registerParts.types.test.js +139 -0
- package/dist/routing/registerRoutes.d.ts +10 -0
- package/dist/routing/registerRoutes.d.ts.map +1 -0
- package/dist/routing/registerRoutes.js +9 -0
- package/dist/routing/registerRouting.d.ts +36 -0
- package/dist/routing/registerRouting.d.ts.map +1 -0
- package/dist/routing/registerRouting.js +34 -0
- package/dist/routing/registerRouting.types.test.d.ts +80 -0
- package/dist/routing/registerRouting.types.test.d.ts.map +1 -0
- package/dist/routing/registerRouting.types.test.js +120 -0
- package/dist/routing/registrationTypes.d.ts +11 -0
- package/dist/routing/registrationTypes.d.ts.map +1 -0
- package/dist/routing/registrationTypes.js +1 -0
- package/dist/routing/resolveAppRoutes.d.ts +5 -0
- package/dist/routing/resolveAppRoutes.d.ts.map +1 -0
- package/dist/routing/resolveAppRoutes.js +25 -0
- package/dist/routing/routesArray.d.ts +23 -0
- package/dist/routing/routesArray.d.ts.map +1 -0
- package/dist/routing/routesArray.js +20 -0
- package/dist/routing/routesArray.types.test.d.ts +2 -0
- package/dist/routing/routesArray.types.test.d.ts.map +1 -0
- package/dist/routing/routesArray.types.test.js +132 -0
- package/dist/routing/routingBuilder.d.ts +34 -0
- package/dist/routing/routingBuilder.d.ts.map +1 -0
- package/dist/routing/routingBuilder.js +32 -0
- package/dist/routing/routingBuilder.types.test.d.ts +2 -0
- package/dist/routing/routingBuilder.types.test.d.ts.map +1 -0
- package/dist/routing/routingBuilder.types.test.js +171 -0
- package/dist/routing/routingRegistry.d.ts +19 -0
- package/dist/routing/routingRegistry.d.ts.map +1 -0
- package/dist/routing/routingRegistry.js +29 -0
- package/dist/staticGen/getAppRoutes.d.ts +2 -2
- package/dist/staticGen/getAppRoutes.d.ts.map +1 -1
- package/dist/staticGen/getAppRoutes.js +9 -6
- package/dist/staticGen/staticGen.d.ts.map +1 -1
- package/dist/staticGen/staticGen.js +8 -2
- package/dist/style/styleContentSxy.d.ts.map +1 -1
- package/dist/sync/index.d.ts.map +1 -1
- package/dist/sync/index.js +2 -0
- package/dist/syncServer/index.cjs +1 -2
- package/dist/syncServer/preload.js +2 -0
- package/dist/testing/contentToString.d.ts +13 -0
- package/dist/testing/contentToString.d.ts.map +1 -0
- package/dist/testing/contentToString.js +105 -0
- package/dist/testing/createTestApp.d.ts +25 -1
- package/dist/testing/createTestApp.d.ts.map +1 -1
- package/dist/testing/createTestApp.js +12 -3
- package/dist/testing/display.d.ts +2 -0
- package/dist/testing/display.d.ts.map +1 -0
- package/dist/testing/display.js +37 -0
- package/dist/testing/ensure.d.ts +119 -0
- package/dist/testing/ensure.d.ts.map +1 -0
- package/dist/testing/ensure.js +664 -0
- package/dist/testing/ensure.trial.d.ts +2 -0
- package/dist/testing/ensure.trial.d.ts.map +1 -0
- package/dist/testing/ensure.trial.js +9 -0
- package/dist/testing/ensure.types.test.d.ts +2 -0
- package/dist/testing/ensure.types.test.d.ts.map +1 -0
- package/dist/testing/ensure.types.test.js +34 -0
- package/dist/testing/helpers/contentHtmlElements.d.ts.map +1 -1
- package/dist/testing/host.d.ts +86 -15
- package/dist/testing/host.d.ts.map +1 -1
- package/dist/testing/host.js +82 -23
- package/dist/testing/host.types.test.d.ts +2 -0
- package/dist/testing/host.types.test.d.ts.map +1 -0
- package/dist/testing/host.types.test.js +81 -0
- package/dist/testing/hydrateTestApp.d.ts +17 -0
- package/dist/testing/hydrateTestApp.d.ts.map +1 -1
- package/dist/testing/hydrateTestApp.js +27 -6
- package/dist/testing/index.d.ts +5 -3
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +3 -1
- package/dist/testing/pipeline/createTestPacketApp.d.ts +30 -0
- package/dist/testing/pipeline/createTestPacketApp.d.ts.map +1 -0
- package/dist/testing/pipeline/createTestPacketApp.js +45 -0
- package/dist/testing/pipeline/createTestPacketApp.types.test.d.ts +3 -0
- package/dist/testing/pipeline/createTestPacketApp.types.test.d.ts.map +1 -0
- package/dist/testing/pipeline/createTestPacketApp.types.test.js +101 -0
- package/dist/testing/pipeline/createTestPipelineApp.d.ts +70 -0
- package/dist/testing/pipeline/createTestPipelineApp.d.ts.map +1 -0
- package/dist/testing/pipeline/createTestPipelineApp.js +90 -0
- package/dist/testing/pipeline/createTestPipelineApp.types.test.d.ts +6 -0
- package/dist/testing/pipeline/createTestPipelineApp.types.test.d.ts.map +1 -0
- package/dist/testing/pipeline/createTestPipelineApp.types.test.js +146 -0
- package/dist/testing/pipeline/hostPipeline.d.ts +25 -0
- package/dist/testing/pipeline/hostPipeline.d.ts.map +1 -0
- package/dist/testing/pipeline/hostPipeline.js +18 -0
- package/dist/testing/pipeline/hydrateTestPipelineApp.d.ts +30 -0
- package/dist/testing/pipeline/hydrateTestPipelineApp.d.ts.map +1 -0
- package/dist/testing/pipeline/hydrateTestPipelineApp.js +193 -0
- package/dist/testing/pipeline/hydrateTestPipelineApp.types.test.d.ts +2 -0
- package/dist/testing/pipeline/hydrateTestPipelineApp.types.test.d.ts.map +1 -0
- package/dist/testing/pipeline/hydrateTestPipelineApp.types.test.js +27 -0
- package/dist/testing/pipeline/index.d.ts +5 -0
- package/dist/testing/pipeline/index.d.ts.map +1 -0
- package/dist/testing/pipeline/index.js +4 -0
- package/dist/testing/pipeline/pipelineTestApp.d.ts +34 -0
- package/dist/testing/pipeline/pipelineTestApp.d.ts.map +1 -0
- package/dist/testing/pipeline/pipelineTestApp.js +100 -0
- package/dist/testing/pipeline/testPipelineRoute.d.ts +2 -0
- package/dist/testing/pipeline/testPipelineRoute.d.ts.map +1 -0
- package/dist/testing/pipeline/testPipelineRoute.js +1 -0
- package/dist/testing/resolveContent.d.ts +18 -0
- package/dist/testing/resolveContent.d.ts.map +1 -0
- package/dist/testing/resolveContent.js +73 -0
- package/dist/testing/testingComponentPackage.d.ts +3 -1
- package/dist/testing/testingComponentPackage.d.ts.map +1 -1
- package/dist/testing/testingComponentPackage.js +16 -1
- package/dist/update/integrateUpdateVdom.d.ts.map +1 -1
- package/dist/update/integrateUpdateVdom.js +35 -31
- package/dist/update/integrateUpdateVdomPrebuild.js +20 -20
- package/dist/validChildren.d.ts.map +1 -1
- package/dist/zones/react.d.ts.map +1 -1
- package/package.json +23 -19
- package/types/helpers.d.ts +11 -2
- package/types/jsx/html.d.ts +29 -25
- package/types/jsx/jsx.d.ts +8 -8
- package/types/modules.d.ts +14 -3
- package/types/testing.d.ts +0 -0
- package/vendor/babel-core.js +5 -5
- package/vendor/babel-parser.js +1 -1
- package/vendor/babel-preset-react.js +32 -34
- package/vendor/babel-preset-typescript.js +5 -5
- package/vendor/chalk-extensions.js +3 -146
- package/vendor/chokidar.js +311 -5
- package/vendor/chunk-B34JUlnS.js +169 -0
- package/vendor/{chunk-Bu8ZRE-c.js → chunk-B9ttgKqg.js} +28 -25
- package/vendor/{chunk-D2EnpcLg.js → chunk-BGvapINa.js} +3 -164
- package/vendor/{chunk-Cqrj-Zqr.js → chunk-BmAiqUzD.js} +22 -8
- package/vendor/{chunk-BiUcnLhT.js → chunk-But4T5N_.js} +8 -3
- package/vendor/{chunk-OUnVY4Wm.js → chunk-DuR6SGzI.js} +162 -35
- package/vendor/css-select.js +3383 -0
- package/vendor/dotenv.js +2 -2
- package/vendor/express.js +61 -10
- package/vendor/manifest.json +4 -3
- package/vendor/postcss.js +1 -1
- package/vendor/precinct.js +5914 -3591
- package/vendor/valibot.js +418 -504
- package/dist/Servers/MatchedRouteInfo.d.ts +0 -11
- package/dist/Servers/MatchedRouteInfo.d.ts.map +0 -1
- package/dist/routing/__fluentRouteBuilder.d.ts +0 -80
- package/dist/routing/__fluentRouteBuilder.d.ts.map +0 -1
- package/dist/routing/__fluentRouteBuilder.js +0 -183
- package/dist/routing/__fluentRoutes.d.ts +0 -2
- package/dist/routing/__fluentRoutes.d.ts.map +0 -1
- package/dist/routing/__fluentRoutes.js +0 -59
- package/vendor/chunk-Hy-cCmu-.js +0 -23
- /package/dist/{Servers/MatchedRouteInfo.js → routing/RoutePaths.js} +0 -0
|
@@ -0,0 +1,3383 @@
|
|
|
1
|
+
import { createRequire as __tungstenCreateRequire } from 'node:module';
|
|
2
|
+
import { fileURLToPath as __tungstenFileURLToPath } from 'node:url';
|
|
3
|
+
import { dirname as __tungstenDirname } from 'node:path';
|
|
4
|
+
const require = __tungstenCreateRequire(import.meta.url);
|
|
5
|
+
const __filename = __tungstenFileURLToPath(import.meta.url);
|
|
6
|
+
const __dirname = __tungstenDirname(__filename);
|
|
7
|
+
function trueFunc() {
|
|
8
|
+
return true;
|
|
9
|
+
}
|
|
10
|
+
function falseFunc() {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Discriminants for selector token kinds. */
|
|
15
|
+
var SelectorType;
|
|
16
|
+
(function (SelectorType) {
|
|
17
|
+
SelectorType["Attribute"] = "attribute";
|
|
18
|
+
SelectorType["Pseudo"] = "pseudo";
|
|
19
|
+
SelectorType["PseudoElement"] = "pseudo-element";
|
|
20
|
+
SelectorType["Tag"] = "tag";
|
|
21
|
+
SelectorType["Universal"] = "universal";
|
|
22
|
+
// Traversals
|
|
23
|
+
SelectorType["Adjacent"] = "adjacent";
|
|
24
|
+
SelectorType["Child"] = "child";
|
|
25
|
+
SelectorType["Descendant"] = "descendant";
|
|
26
|
+
SelectorType["Parent"] = "parent";
|
|
27
|
+
SelectorType["Sibling"] = "sibling";
|
|
28
|
+
SelectorType["ColumnCombinator"] = "column-combinator";
|
|
29
|
+
})(SelectorType || (SelectorType = {}));
|
|
30
|
+
/** Operators available for attribute selectors. */
|
|
31
|
+
var AttributeAction;
|
|
32
|
+
(function (AttributeAction) {
|
|
33
|
+
AttributeAction["Any"] = "any";
|
|
34
|
+
AttributeAction["Element"] = "element";
|
|
35
|
+
AttributeAction["End"] = "end";
|
|
36
|
+
AttributeAction["Equals"] = "equals";
|
|
37
|
+
AttributeAction["Exists"] = "exists";
|
|
38
|
+
AttributeAction["Hyphen"] = "hyphen";
|
|
39
|
+
AttributeAction["Not"] = "not";
|
|
40
|
+
AttributeAction["Start"] = "start";
|
|
41
|
+
})(AttributeAction || (AttributeAction = {}));
|
|
42
|
+
|
|
43
|
+
const reName = /^[^#\\]?(?:\\(?:[\da-f]{1,6}\s?|.)|[\w\u00B0-\uFFFF-])+/;
|
|
44
|
+
const reEscape = /\\([\da-f]{1,6}\s?|(\s)|.)/gi;
|
|
45
|
+
var CharCode;
|
|
46
|
+
(function (CharCode) {
|
|
47
|
+
CharCode[CharCode["LeftParenthesis"] = 40] = "LeftParenthesis";
|
|
48
|
+
CharCode[CharCode["RightParenthesis"] = 41] = "RightParenthesis";
|
|
49
|
+
CharCode[CharCode["LeftSquareBracket"] = 91] = "LeftSquareBracket";
|
|
50
|
+
CharCode[CharCode["RightSquareBracket"] = 93] = "RightSquareBracket";
|
|
51
|
+
CharCode[CharCode["Comma"] = 44] = "Comma";
|
|
52
|
+
CharCode[CharCode["Period"] = 46] = "Period";
|
|
53
|
+
CharCode[CharCode["Colon"] = 58] = "Colon";
|
|
54
|
+
CharCode[CharCode["SingleQuote"] = 39] = "SingleQuote";
|
|
55
|
+
CharCode[CharCode["DoubleQuote"] = 34] = "DoubleQuote";
|
|
56
|
+
CharCode[CharCode["Plus"] = 43] = "Plus";
|
|
57
|
+
CharCode[CharCode["Tilde"] = 126] = "Tilde";
|
|
58
|
+
CharCode[CharCode["QuestionMark"] = 63] = "QuestionMark";
|
|
59
|
+
CharCode[CharCode["ExclamationMark"] = 33] = "ExclamationMark";
|
|
60
|
+
CharCode[CharCode["Slash"] = 47] = "Slash";
|
|
61
|
+
CharCode[CharCode["Equal"] = 61] = "Equal";
|
|
62
|
+
CharCode[CharCode["Dollar"] = 36] = "Dollar";
|
|
63
|
+
CharCode[CharCode["Pipe"] = 124] = "Pipe";
|
|
64
|
+
CharCode[CharCode["Circumflex"] = 94] = "Circumflex";
|
|
65
|
+
CharCode[CharCode["Asterisk"] = 42] = "Asterisk";
|
|
66
|
+
CharCode[CharCode["GreaterThan"] = 62] = "GreaterThan";
|
|
67
|
+
CharCode[CharCode["LessThan"] = 60] = "LessThan";
|
|
68
|
+
CharCode[CharCode["Hash"] = 35] = "Hash";
|
|
69
|
+
CharCode[CharCode["LowerI"] = 105] = "LowerI";
|
|
70
|
+
CharCode[CharCode["LowerS"] = 115] = "LowerS";
|
|
71
|
+
CharCode[CharCode["BackSlash"] = 92] = "BackSlash";
|
|
72
|
+
// Whitespace
|
|
73
|
+
CharCode[CharCode["Space"] = 32] = "Space";
|
|
74
|
+
CharCode[CharCode["Tab"] = 9] = "Tab";
|
|
75
|
+
CharCode[CharCode["NewLine"] = 10] = "NewLine";
|
|
76
|
+
CharCode[CharCode["FormFeed"] = 12] = "FormFeed";
|
|
77
|
+
CharCode[CharCode["CarriageReturn"] = 13] = "CarriageReturn";
|
|
78
|
+
})(CharCode || (CharCode = {}));
|
|
79
|
+
const actionTypes = new Map([
|
|
80
|
+
[CharCode.Tilde, AttributeAction.Element],
|
|
81
|
+
[CharCode.Circumflex, AttributeAction.Start],
|
|
82
|
+
[CharCode.Dollar, AttributeAction.End],
|
|
83
|
+
[CharCode.Asterisk, AttributeAction.Any],
|
|
84
|
+
[CharCode.ExclamationMark, AttributeAction.Not],
|
|
85
|
+
[CharCode.Pipe, AttributeAction.Hyphen],
|
|
86
|
+
]);
|
|
87
|
+
// Pseudos, whose data property is parsed as well.
|
|
88
|
+
const unpackPseudos = new Set([
|
|
89
|
+
"has",
|
|
90
|
+
"not",
|
|
91
|
+
"matches",
|
|
92
|
+
"is",
|
|
93
|
+
"where",
|
|
94
|
+
"host",
|
|
95
|
+
"host-context",
|
|
96
|
+
]);
|
|
97
|
+
/**
|
|
98
|
+
* Pseudo elements defined in CSS Level 1 and CSS Level 2 can be written with
|
|
99
|
+
* a single colon; eg. :before will turn into ::before.
|
|
100
|
+
* @see {@link https://www.w3.org/TR/2018/WD-selectors-4-20181121/#pseudo-element-syntax}
|
|
101
|
+
*/
|
|
102
|
+
const pseudosToPseudoElements = new Set([
|
|
103
|
+
"before",
|
|
104
|
+
"after",
|
|
105
|
+
"first-line",
|
|
106
|
+
"first-letter",
|
|
107
|
+
]);
|
|
108
|
+
/**
|
|
109
|
+
* Checks whether a specific selector is a traversal.
|
|
110
|
+
* This is useful eg. in swapping the order of elements that
|
|
111
|
+
* are not traversals.
|
|
112
|
+
* @param selector Selector to check.
|
|
113
|
+
*/
|
|
114
|
+
function isTraversal$1(selector) {
|
|
115
|
+
switch (selector.type) {
|
|
116
|
+
case SelectorType.Adjacent:
|
|
117
|
+
case SelectorType.Child:
|
|
118
|
+
case SelectorType.Descendant:
|
|
119
|
+
case SelectorType.Parent:
|
|
120
|
+
case SelectorType.Sibling:
|
|
121
|
+
case SelectorType.ColumnCombinator: {
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
case SelectorType.Attribute:
|
|
125
|
+
case SelectorType.Pseudo:
|
|
126
|
+
case SelectorType.PseudoElement:
|
|
127
|
+
case SelectorType.Tag:
|
|
128
|
+
case SelectorType.Universal: {
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
const stripQuotesFromPseudos = new Set(["contains", "icontains"]);
|
|
134
|
+
// Unescape function taken from https://github.com/jquery/sizzle/blob/master/src/sizzle.js#L152
|
|
135
|
+
function funescape(_, escaped, escapedWhitespace) {
|
|
136
|
+
const high = Number.parseInt(escaped, 16) - 0x1_00_00;
|
|
137
|
+
// NaN means non-codepoint
|
|
138
|
+
return Number.isNaN(high) || escapedWhitespace
|
|
139
|
+
? escaped
|
|
140
|
+
: high < 0
|
|
141
|
+
? // BMP codepoint
|
|
142
|
+
String.fromCharCode(high + 0x1_00_00)
|
|
143
|
+
: // Supplemental Plane codepoint (surrogate pair)
|
|
144
|
+
String.fromCharCode((high >> 10) | 0xd8_00, (high & 0x3_ff) | 0xdc_00);
|
|
145
|
+
}
|
|
146
|
+
function unescapeCSS(cssString) {
|
|
147
|
+
return cssString.replace(reEscape, funescape);
|
|
148
|
+
}
|
|
149
|
+
function isQuote(c) {
|
|
150
|
+
return c === CharCode.SingleQuote || c === CharCode.DoubleQuote;
|
|
151
|
+
}
|
|
152
|
+
function isWhitespace(c) {
|
|
153
|
+
return (c === CharCode.Space ||
|
|
154
|
+
c === CharCode.Tab ||
|
|
155
|
+
c === CharCode.NewLine ||
|
|
156
|
+
c === CharCode.FormFeed ||
|
|
157
|
+
c === CharCode.CarriageReturn);
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Parses `selector`.
|
|
161
|
+
* @param selector Selector to parse.
|
|
162
|
+
* @returns Returns a two-dimensional array.
|
|
163
|
+
* The first dimension represents selectors separated by commas (eg. `sub1, sub2`),
|
|
164
|
+
* the second contains the relevant tokens for that selector.
|
|
165
|
+
*/
|
|
166
|
+
function parse$1(selector) {
|
|
167
|
+
const subselects = [];
|
|
168
|
+
const endIndex = parseSelector(subselects, `${selector}`, 0);
|
|
169
|
+
if (endIndex < selector.length) {
|
|
170
|
+
throw new Error(`Unmatched selector: ${selector.slice(endIndex)}`);
|
|
171
|
+
}
|
|
172
|
+
return subselects;
|
|
173
|
+
}
|
|
174
|
+
function parseSelector(subselects, selector, selectorIndex) {
|
|
175
|
+
let tokens = [];
|
|
176
|
+
function getName(offset) {
|
|
177
|
+
const match = selector.slice(selectorIndex + offset).match(reName);
|
|
178
|
+
if (!match) {
|
|
179
|
+
throw new Error(`Expected name, found ${selector.slice(selectorIndex)}`);
|
|
180
|
+
}
|
|
181
|
+
const [name] = match;
|
|
182
|
+
selectorIndex += offset + name.length;
|
|
183
|
+
return unescapeCSS(name);
|
|
184
|
+
}
|
|
185
|
+
function stripWhitespace(offset) {
|
|
186
|
+
selectorIndex += offset;
|
|
187
|
+
while (selectorIndex < selector.length &&
|
|
188
|
+
isWhitespace(selector.charCodeAt(selectorIndex))) {
|
|
189
|
+
selectorIndex++;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
function readValueWithParenthesis() {
|
|
193
|
+
selectorIndex += 1;
|
|
194
|
+
const start = selectorIndex;
|
|
195
|
+
for (let counter = 1; selectorIndex < selector.length; selectorIndex++) {
|
|
196
|
+
switch (selector.charCodeAt(selectorIndex)) {
|
|
197
|
+
case CharCode.BackSlash: {
|
|
198
|
+
// Skip next character
|
|
199
|
+
selectorIndex += 1;
|
|
200
|
+
break;
|
|
201
|
+
}
|
|
202
|
+
case CharCode.LeftParenthesis: {
|
|
203
|
+
counter += 1;
|
|
204
|
+
break;
|
|
205
|
+
}
|
|
206
|
+
case CharCode.RightParenthesis: {
|
|
207
|
+
counter -= 1;
|
|
208
|
+
if (counter === 0) {
|
|
209
|
+
return unescapeCSS(selector.slice(start, selectorIndex++));
|
|
210
|
+
}
|
|
211
|
+
break;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
throw new Error("Parenthesis not matched");
|
|
216
|
+
}
|
|
217
|
+
function ensureNotTraversal() {
|
|
218
|
+
if (tokens.length > 0 && isTraversal$1(tokens[tokens.length - 1])) {
|
|
219
|
+
throw new Error("Did not expect successive traversals.");
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
function addTraversal(type) {
|
|
223
|
+
if (tokens.length > 0 &&
|
|
224
|
+
tokens[tokens.length - 1].type === SelectorType.Descendant) {
|
|
225
|
+
tokens[tokens.length - 1].type = type;
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
ensureNotTraversal();
|
|
229
|
+
tokens.push({ type });
|
|
230
|
+
}
|
|
231
|
+
function addSpecialAttribute(name, action) {
|
|
232
|
+
tokens.push({
|
|
233
|
+
type: SelectorType.Attribute,
|
|
234
|
+
name,
|
|
235
|
+
action,
|
|
236
|
+
value: getName(1),
|
|
237
|
+
namespace: null,
|
|
238
|
+
ignoreCase: "quirks",
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* We have finished parsing the current part of the selector.
|
|
243
|
+
*
|
|
244
|
+
* Remove descendant tokens at the end if they exist,
|
|
245
|
+
* and return the last index, so that parsing can be
|
|
246
|
+
* picked up from here.
|
|
247
|
+
*/
|
|
248
|
+
function finalizeSubselector() {
|
|
249
|
+
if (tokens.length > 0 &&
|
|
250
|
+
tokens[tokens.length - 1].type === SelectorType.Descendant) {
|
|
251
|
+
tokens.pop();
|
|
252
|
+
}
|
|
253
|
+
if (tokens.length === 0) {
|
|
254
|
+
throw new Error("Empty sub-selector");
|
|
255
|
+
}
|
|
256
|
+
subselects.push(tokens);
|
|
257
|
+
}
|
|
258
|
+
stripWhitespace(0);
|
|
259
|
+
if (selector.length === selectorIndex) {
|
|
260
|
+
return selectorIndex;
|
|
261
|
+
}
|
|
262
|
+
loop: while (selectorIndex < selector.length) {
|
|
263
|
+
const firstChar = selector.charCodeAt(selectorIndex);
|
|
264
|
+
switch (firstChar) {
|
|
265
|
+
// Whitespace
|
|
266
|
+
case CharCode.Space:
|
|
267
|
+
case CharCode.Tab:
|
|
268
|
+
case CharCode.NewLine:
|
|
269
|
+
case CharCode.FormFeed:
|
|
270
|
+
case CharCode.CarriageReturn: {
|
|
271
|
+
if (tokens.length === 0 ||
|
|
272
|
+
tokens[0].type !== SelectorType.Descendant) {
|
|
273
|
+
ensureNotTraversal();
|
|
274
|
+
tokens.push({ type: SelectorType.Descendant });
|
|
275
|
+
}
|
|
276
|
+
stripWhitespace(1);
|
|
277
|
+
break;
|
|
278
|
+
}
|
|
279
|
+
// Traversals
|
|
280
|
+
case CharCode.GreaterThan: {
|
|
281
|
+
addTraversal(SelectorType.Child);
|
|
282
|
+
stripWhitespace(1);
|
|
283
|
+
break;
|
|
284
|
+
}
|
|
285
|
+
case CharCode.LessThan: {
|
|
286
|
+
addTraversal(SelectorType.Parent);
|
|
287
|
+
stripWhitespace(1);
|
|
288
|
+
break;
|
|
289
|
+
}
|
|
290
|
+
case CharCode.Tilde: {
|
|
291
|
+
addTraversal(SelectorType.Sibling);
|
|
292
|
+
stripWhitespace(1);
|
|
293
|
+
break;
|
|
294
|
+
}
|
|
295
|
+
case CharCode.Plus: {
|
|
296
|
+
addTraversal(SelectorType.Adjacent);
|
|
297
|
+
stripWhitespace(1);
|
|
298
|
+
break;
|
|
299
|
+
}
|
|
300
|
+
// Special attribute selectors: .class, #id
|
|
301
|
+
case CharCode.Period: {
|
|
302
|
+
addSpecialAttribute("class", AttributeAction.Element);
|
|
303
|
+
break;
|
|
304
|
+
}
|
|
305
|
+
case CharCode.Hash: {
|
|
306
|
+
addSpecialAttribute("id", AttributeAction.Equals);
|
|
307
|
+
break;
|
|
308
|
+
}
|
|
309
|
+
case CharCode.LeftSquareBracket: {
|
|
310
|
+
stripWhitespace(1);
|
|
311
|
+
// Determine attribute name and namespace
|
|
312
|
+
let name;
|
|
313
|
+
let namespace = null;
|
|
314
|
+
if (selector.charCodeAt(selectorIndex) === CharCode.Pipe) {
|
|
315
|
+
// Equivalent to no namespace
|
|
316
|
+
name = getName(1);
|
|
317
|
+
}
|
|
318
|
+
else if (selector.startsWith("*|", selectorIndex)) {
|
|
319
|
+
namespace = "*";
|
|
320
|
+
name = getName(2);
|
|
321
|
+
}
|
|
322
|
+
else {
|
|
323
|
+
name = getName(0);
|
|
324
|
+
if (selector.charCodeAt(selectorIndex) === CharCode.Pipe &&
|
|
325
|
+
selector.charCodeAt(selectorIndex + 1) !==
|
|
326
|
+
CharCode.Equal) {
|
|
327
|
+
namespace = name;
|
|
328
|
+
name = getName(1);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
stripWhitespace(0);
|
|
332
|
+
// Determine comparison operation
|
|
333
|
+
let action = AttributeAction.Exists;
|
|
334
|
+
const possibleAction = actionTypes.get(selector.charCodeAt(selectorIndex));
|
|
335
|
+
if (possibleAction) {
|
|
336
|
+
action = possibleAction;
|
|
337
|
+
if (selector.charCodeAt(selectorIndex + 1) !==
|
|
338
|
+
CharCode.Equal) {
|
|
339
|
+
throw new Error("Expected `=`");
|
|
340
|
+
}
|
|
341
|
+
stripWhitespace(2);
|
|
342
|
+
}
|
|
343
|
+
else if (selector.charCodeAt(selectorIndex) === CharCode.Equal) {
|
|
344
|
+
action = AttributeAction.Equals;
|
|
345
|
+
stripWhitespace(1);
|
|
346
|
+
}
|
|
347
|
+
// Determine value
|
|
348
|
+
let value = "";
|
|
349
|
+
let ignoreCase = null;
|
|
350
|
+
if (action !== "exists") {
|
|
351
|
+
if (isQuote(selector.charCodeAt(selectorIndex))) {
|
|
352
|
+
const quote = selector.charCodeAt(selectorIndex);
|
|
353
|
+
selectorIndex += 1;
|
|
354
|
+
const sectionStart = selectorIndex;
|
|
355
|
+
while (selectorIndex < selector.length &&
|
|
356
|
+
selector.charCodeAt(selectorIndex) !== quote) {
|
|
357
|
+
selectorIndex +=
|
|
358
|
+
// Skip next character if it is escaped
|
|
359
|
+
selector.charCodeAt(selectorIndex) ===
|
|
360
|
+
CharCode.BackSlash
|
|
361
|
+
? 2
|
|
362
|
+
: 1;
|
|
363
|
+
}
|
|
364
|
+
if (selector.charCodeAt(selectorIndex) !== quote) {
|
|
365
|
+
throw new Error("Attribute value didn't end");
|
|
366
|
+
}
|
|
367
|
+
value = unescapeCSS(selector.slice(sectionStart, selectorIndex));
|
|
368
|
+
selectorIndex += 1;
|
|
369
|
+
}
|
|
370
|
+
else {
|
|
371
|
+
const valueStart = selectorIndex;
|
|
372
|
+
while (selectorIndex < selector.length &&
|
|
373
|
+
!isWhitespace(selector.charCodeAt(selectorIndex)) &&
|
|
374
|
+
selector.charCodeAt(selectorIndex) !==
|
|
375
|
+
CharCode.RightSquareBracket) {
|
|
376
|
+
selectorIndex +=
|
|
377
|
+
// Skip next character if it is escaped
|
|
378
|
+
selector.charCodeAt(selectorIndex) ===
|
|
379
|
+
CharCode.BackSlash
|
|
380
|
+
? 2
|
|
381
|
+
: 1;
|
|
382
|
+
}
|
|
383
|
+
value = unescapeCSS(selector.slice(valueStart, selectorIndex));
|
|
384
|
+
}
|
|
385
|
+
stripWhitespace(0);
|
|
386
|
+
// See if we have a force ignore flag
|
|
387
|
+
switch (selector.charCodeAt(selectorIndex) | 0x20) {
|
|
388
|
+
// If the forceIgnore flag is set (either `i` or `s`), use that value
|
|
389
|
+
case CharCode.LowerI: {
|
|
390
|
+
ignoreCase = true;
|
|
391
|
+
stripWhitespace(1);
|
|
392
|
+
break;
|
|
393
|
+
}
|
|
394
|
+
case CharCode.LowerS: {
|
|
395
|
+
ignoreCase = false;
|
|
396
|
+
stripWhitespace(1);
|
|
397
|
+
break;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
if (selector.charCodeAt(selectorIndex) !==
|
|
402
|
+
CharCode.RightSquareBracket) {
|
|
403
|
+
throw new Error("Attribute selector didn't terminate");
|
|
404
|
+
}
|
|
405
|
+
selectorIndex += 1;
|
|
406
|
+
const attributeSelector = {
|
|
407
|
+
type: SelectorType.Attribute,
|
|
408
|
+
name,
|
|
409
|
+
action,
|
|
410
|
+
value,
|
|
411
|
+
namespace,
|
|
412
|
+
ignoreCase,
|
|
413
|
+
};
|
|
414
|
+
tokens.push(attributeSelector);
|
|
415
|
+
break;
|
|
416
|
+
}
|
|
417
|
+
case CharCode.Colon: {
|
|
418
|
+
if (selector.charCodeAt(selectorIndex + 1) === CharCode.Colon) {
|
|
419
|
+
tokens.push({
|
|
420
|
+
type: SelectorType.PseudoElement,
|
|
421
|
+
name: getName(2).toLowerCase(),
|
|
422
|
+
data: selector.charCodeAt(selectorIndex) ===
|
|
423
|
+
CharCode.LeftParenthesis
|
|
424
|
+
? readValueWithParenthesis()
|
|
425
|
+
: null,
|
|
426
|
+
});
|
|
427
|
+
break;
|
|
428
|
+
}
|
|
429
|
+
const name = getName(1).toLowerCase();
|
|
430
|
+
if (pseudosToPseudoElements.has(name)) {
|
|
431
|
+
tokens.push({
|
|
432
|
+
type: SelectorType.PseudoElement,
|
|
433
|
+
name,
|
|
434
|
+
data: null,
|
|
435
|
+
});
|
|
436
|
+
break;
|
|
437
|
+
}
|
|
438
|
+
let data = null;
|
|
439
|
+
if (selector.charCodeAt(selectorIndex) ===
|
|
440
|
+
CharCode.LeftParenthesis) {
|
|
441
|
+
if (unpackPseudos.has(name)) {
|
|
442
|
+
if (isQuote(selector.charCodeAt(selectorIndex + 1))) {
|
|
443
|
+
throw new Error(`Pseudo-selector ${name} cannot be quoted`);
|
|
444
|
+
}
|
|
445
|
+
data = [];
|
|
446
|
+
selectorIndex = parseSelector(data, selector, selectorIndex + 1);
|
|
447
|
+
if (selector.charCodeAt(selectorIndex) !==
|
|
448
|
+
CharCode.RightParenthesis) {
|
|
449
|
+
throw new Error(`Missing closing parenthesis in :${name} (${selector})`);
|
|
450
|
+
}
|
|
451
|
+
selectorIndex += 1;
|
|
452
|
+
}
|
|
453
|
+
else {
|
|
454
|
+
data = readValueWithParenthesis();
|
|
455
|
+
if (stripQuotesFromPseudos.has(name)) {
|
|
456
|
+
const quot = data.charCodeAt(0);
|
|
457
|
+
if (quot === data.charCodeAt(data.length - 1) &&
|
|
458
|
+
isQuote(quot)) {
|
|
459
|
+
data = data.slice(1, -1);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
data = unescapeCSS(data);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
tokens.push({ type: SelectorType.Pseudo, name, data });
|
|
466
|
+
break;
|
|
467
|
+
}
|
|
468
|
+
case CharCode.Comma: {
|
|
469
|
+
finalizeSubselector();
|
|
470
|
+
tokens = [];
|
|
471
|
+
stripWhitespace(1);
|
|
472
|
+
break;
|
|
473
|
+
}
|
|
474
|
+
default: {
|
|
475
|
+
if (selector.startsWith("/*", selectorIndex)) {
|
|
476
|
+
const endIndex = selector.indexOf("*/", selectorIndex + 2);
|
|
477
|
+
if (endIndex === -1) {
|
|
478
|
+
throw new Error("Comment was not terminated");
|
|
479
|
+
}
|
|
480
|
+
selectorIndex = endIndex + 2;
|
|
481
|
+
// Remove leading whitespace
|
|
482
|
+
if (tokens.length === 0) {
|
|
483
|
+
stripWhitespace(0);
|
|
484
|
+
}
|
|
485
|
+
break;
|
|
486
|
+
}
|
|
487
|
+
let namespace = null;
|
|
488
|
+
let name;
|
|
489
|
+
if (firstChar === CharCode.Asterisk) {
|
|
490
|
+
selectorIndex += 1;
|
|
491
|
+
name = "*";
|
|
492
|
+
}
|
|
493
|
+
else if (firstChar === CharCode.Pipe) {
|
|
494
|
+
name = "";
|
|
495
|
+
if (selector.charCodeAt(selectorIndex + 1) === CharCode.Pipe) {
|
|
496
|
+
addTraversal(SelectorType.ColumnCombinator);
|
|
497
|
+
stripWhitespace(2);
|
|
498
|
+
break;
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
else if (reName.test(selector.slice(selectorIndex))) {
|
|
502
|
+
name = getName(0);
|
|
503
|
+
}
|
|
504
|
+
else {
|
|
505
|
+
break loop;
|
|
506
|
+
}
|
|
507
|
+
if (selector.charCodeAt(selectorIndex) === CharCode.Pipe &&
|
|
508
|
+
selector.charCodeAt(selectorIndex + 1) !== CharCode.Pipe) {
|
|
509
|
+
namespace = name;
|
|
510
|
+
if (selector.charCodeAt(selectorIndex + 1) ===
|
|
511
|
+
CharCode.Asterisk) {
|
|
512
|
+
name = "*";
|
|
513
|
+
selectorIndex += 2;
|
|
514
|
+
}
|
|
515
|
+
else {
|
|
516
|
+
name = getName(1);
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
tokens.push(name === "*"
|
|
520
|
+
? { type: SelectorType.Universal, namespace }
|
|
521
|
+
: { type: SelectorType.Tag, name, namespace });
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
finalizeSubselector();
|
|
526
|
+
return selectorIndex;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
/** Types of elements found in htmlparser2's DOM */
|
|
530
|
+
var ElementType;
|
|
531
|
+
(function (ElementType) {
|
|
532
|
+
/** Type for the root element of a document */
|
|
533
|
+
ElementType["Root"] = "root";
|
|
534
|
+
/** Type for Text */
|
|
535
|
+
ElementType["Text"] = "text";
|
|
536
|
+
/** Type for <? ... ?> */
|
|
537
|
+
ElementType["Directive"] = "directive";
|
|
538
|
+
/** Type for <!-- ... --> */
|
|
539
|
+
ElementType["Comment"] = "comment";
|
|
540
|
+
/** Type for <script> tags */
|
|
541
|
+
ElementType["Script"] = "script";
|
|
542
|
+
/** Type for <style> tags */
|
|
543
|
+
ElementType["Style"] = "style";
|
|
544
|
+
/** Type for Any tag */
|
|
545
|
+
ElementType["Tag"] = "tag";
|
|
546
|
+
/** Type for <![CDATA[ ... ]]> */
|
|
547
|
+
ElementType["CDATA"] = "cdata";
|
|
548
|
+
/** Type for <!doctype ...> */
|
|
549
|
+
ElementType["Doctype"] = "doctype";
|
|
550
|
+
})(ElementType || (ElementType = {}));
|
|
551
|
+
/**
|
|
552
|
+
* Tests whether an element is a tag or not.
|
|
553
|
+
* @param element Element to test
|
|
554
|
+
* @param element.type Node type discriminator to check.
|
|
555
|
+
*/
|
|
556
|
+
function isTag$1(element) {
|
|
557
|
+
return (element.type === ElementType.Tag ||
|
|
558
|
+
element.type === ElementType.Script ||
|
|
559
|
+
element.type === ElementType.Style);
|
|
560
|
+
}
|
|
561
|
+
// Exports for backwards compatibility
|
|
562
|
+
/** Type for the root element of a document */
|
|
563
|
+
// eslint-disable-next-line prefer-destructuring
|
|
564
|
+
const Root = ElementType.Root;
|
|
565
|
+
/** Type for Text */
|
|
566
|
+
// eslint-disable-next-line prefer-destructuring
|
|
567
|
+
const Text = ElementType.Text;
|
|
568
|
+
/** Type for <? ... ?> */
|
|
569
|
+
// eslint-disable-next-line prefer-destructuring
|
|
570
|
+
const Directive = ElementType.Directive;
|
|
571
|
+
/** Type for <!-- ... --> */
|
|
572
|
+
// eslint-disable-next-line prefer-destructuring
|
|
573
|
+
const Comment = ElementType.Comment;
|
|
574
|
+
/** Type for <script> tags */
|
|
575
|
+
// eslint-disable-next-line prefer-destructuring
|
|
576
|
+
const Script = ElementType.Script;
|
|
577
|
+
/** Type for <style> tags */
|
|
578
|
+
// eslint-disable-next-line prefer-destructuring
|
|
579
|
+
const Style = ElementType.Style;
|
|
580
|
+
/** Type for Any tag */
|
|
581
|
+
// eslint-disable-next-line prefer-destructuring
|
|
582
|
+
const Tag = ElementType.Tag;
|
|
583
|
+
/** Type for <![CDATA[ ... ]]> */
|
|
584
|
+
// eslint-disable-next-line prefer-destructuring
|
|
585
|
+
const CDATA = ElementType.CDATA;
|
|
586
|
+
/** Type for <!doctype ...> */
|
|
587
|
+
// eslint-disable-next-line prefer-destructuring
|
|
588
|
+
ElementType.Doctype;
|
|
589
|
+
|
|
590
|
+
/**
|
|
591
|
+
* Checks if `node` is an element node.
|
|
592
|
+
* @param node Node to check.
|
|
593
|
+
* @returns `true` if the node is an element node.
|
|
594
|
+
*/
|
|
595
|
+
function isTag(node) {
|
|
596
|
+
return isTag$1(node);
|
|
597
|
+
}
|
|
598
|
+
/**
|
|
599
|
+
* Checks if `node` is a CDATA node.
|
|
600
|
+
* @param node Node to check.
|
|
601
|
+
* @returns `true` if the node is a CDATA node.
|
|
602
|
+
*/
|
|
603
|
+
function isCDATA(node) {
|
|
604
|
+
return node.type === ElementType.CDATA;
|
|
605
|
+
}
|
|
606
|
+
/**
|
|
607
|
+
* Checks if `node` is a text node.
|
|
608
|
+
* @param node Node to check.
|
|
609
|
+
* @returns `true` if the node is a text node.
|
|
610
|
+
*/
|
|
611
|
+
function isText(node) {
|
|
612
|
+
return node.type === ElementType.Text;
|
|
613
|
+
}
|
|
614
|
+
/**
|
|
615
|
+
* Checks if `node` is a comment node.
|
|
616
|
+
* @param node Node to check.
|
|
617
|
+
* @returns `true` if the node is a comment node.
|
|
618
|
+
*/
|
|
619
|
+
function isComment(node) {
|
|
620
|
+
return node.type === ElementType.Comment;
|
|
621
|
+
}
|
|
622
|
+
/**
|
|
623
|
+
* Checks if `node` has children.
|
|
624
|
+
* @param node Node to check.
|
|
625
|
+
* @returns `true` if the node has children.
|
|
626
|
+
*/
|
|
627
|
+
function hasChildren(node) {
|
|
628
|
+
return Object.hasOwn(node, "children");
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
/**
|
|
632
|
+
* Search a node and its children for nodes passing a test function. If `node` is not an array, it will be wrapped in one.
|
|
633
|
+
*
|
|
634
|
+
* @category Querying
|
|
635
|
+
* @param test Function to test nodes on.
|
|
636
|
+
* @param node Node to search. Will be included in the result set if it matches.
|
|
637
|
+
* @param recurse Also consider child nodes.
|
|
638
|
+
* @param limit Maximum number of nodes to return.
|
|
639
|
+
* @returns All nodes passing `test`.
|
|
640
|
+
*/
|
|
641
|
+
function filter(test, node, recurse = true, limit = Number.POSITIVE_INFINITY) {
|
|
642
|
+
return find(test, Array.isArray(node) ? node : [node], recurse, limit);
|
|
643
|
+
}
|
|
644
|
+
/**
|
|
645
|
+
* Search an array of nodes and their children for nodes passing a test function.
|
|
646
|
+
*
|
|
647
|
+
* @category Querying
|
|
648
|
+
* @param test Function to test nodes on.
|
|
649
|
+
* @param nodes Array of nodes to search.
|
|
650
|
+
* @param recurse Also consider child nodes.
|
|
651
|
+
* @param limit Maximum number of nodes to return.
|
|
652
|
+
* @returns All nodes passing `test`.
|
|
653
|
+
*/
|
|
654
|
+
function find(test, nodes, recurse, limit) {
|
|
655
|
+
const result = [];
|
|
656
|
+
/** Stack of the arrays we are looking at. */
|
|
657
|
+
const nodeStack = [Array.isArray(nodes) ? nodes : [nodes]];
|
|
658
|
+
/** Stack of the indices within the arrays. */
|
|
659
|
+
const indexStack = [0];
|
|
660
|
+
for (;;) {
|
|
661
|
+
// First, check if the current array has any more elements to look at.
|
|
662
|
+
if (indexStack[0] >= nodeStack[0].length) {
|
|
663
|
+
// If we have no more arrays to look at, we are done.
|
|
664
|
+
if (indexStack.length === 1) {
|
|
665
|
+
return result;
|
|
666
|
+
}
|
|
667
|
+
// Otherwise, remove the current array from the stack.
|
|
668
|
+
nodeStack.shift();
|
|
669
|
+
indexStack.shift();
|
|
670
|
+
// Loop back to the start to continue with the next array.
|
|
671
|
+
continue;
|
|
672
|
+
}
|
|
673
|
+
const element = nodeStack[0][indexStack[0]++];
|
|
674
|
+
if (test(element)) {
|
|
675
|
+
result.push(element);
|
|
676
|
+
if (--limit <= 0)
|
|
677
|
+
return result;
|
|
678
|
+
}
|
|
679
|
+
if (recurse && hasChildren(element) && element.children.length > 0) {
|
|
680
|
+
/*
|
|
681
|
+
* Add the children to the stack. We are depth-first, so this is
|
|
682
|
+
* the next array we look at.
|
|
683
|
+
*/
|
|
684
|
+
indexStack.unshift(0);
|
|
685
|
+
nodeStack.unshift(element.children);
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
/**
|
|
690
|
+
* Finds one element in a tree that passes a test.
|
|
691
|
+
*
|
|
692
|
+
* @category Querying
|
|
693
|
+
* @param test Function to test nodes on.
|
|
694
|
+
* @param nodes Node or array of nodes to search.
|
|
695
|
+
* @param recurse Also consider child nodes.
|
|
696
|
+
* @returns The first node that passes `test`.
|
|
697
|
+
*/
|
|
698
|
+
function findOne$1(test, nodes, recurse = true) {
|
|
699
|
+
const searchedNodes = Array.isArray(nodes) ? nodes : [nodes];
|
|
700
|
+
for (const node of searchedNodes) {
|
|
701
|
+
if (isTag(node) && test(node)) {
|
|
702
|
+
return node;
|
|
703
|
+
}
|
|
704
|
+
if (recurse && hasChildren(node) && node.children.length > 0) {
|
|
705
|
+
const found = findOne$1(test, node.children, true);
|
|
706
|
+
if (found)
|
|
707
|
+
return found;
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
return null;
|
|
711
|
+
}
|
|
712
|
+
/**
|
|
713
|
+
* Checks if a tree of nodes contains at least one node passing a test.
|
|
714
|
+
*
|
|
715
|
+
* @category Querying
|
|
716
|
+
* @param test Function to test nodes on.
|
|
717
|
+
* @param nodes Array of nodes to search.
|
|
718
|
+
* @returns Whether a tree of nodes contains at least one node passing the test.
|
|
719
|
+
*/
|
|
720
|
+
function existsOne(test, nodes) {
|
|
721
|
+
return (Array.isArray(nodes) ? nodes : [nodes]).some((node) => (isTag(node) && test(node)) ||
|
|
722
|
+
(hasChildren(node) && existsOne(test, node.children)));
|
|
723
|
+
}
|
|
724
|
+
/**
|
|
725
|
+
* Search an array of nodes and their children for elements passing a test function.
|
|
726
|
+
*
|
|
727
|
+
* Same as `find`, but limited to elements and with less options, leading to reduced complexity.
|
|
728
|
+
*
|
|
729
|
+
* @category Querying
|
|
730
|
+
* @param test Function to test nodes on.
|
|
731
|
+
* @param nodes Array of nodes to search.
|
|
732
|
+
* @returns All nodes passing `test`.
|
|
733
|
+
*/
|
|
734
|
+
function findAll$1(test, nodes) {
|
|
735
|
+
const result = [];
|
|
736
|
+
const nodeStack = [Array.isArray(nodes) ? nodes : [nodes]];
|
|
737
|
+
const indexStack = [0];
|
|
738
|
+
for (;;) {
|
|
739
|
+
if (indexStack[0] >= nodeStack[0].length) {
|
|
740
|
+
if (nodeStack.length === 1) {
|
|
741
|
+
return result;
|
|
742
|
+
}
|
|
743
|
+
// Otherwise, remove the current array from the stack.
|
|
744
|
+
nodeStack.shift();
|
|
745
|
+
indexStack.shift();
|
|
746
|
+
// Loop back to the start to continue with the next array.
|
|
747
|
+
continue;
|
|
748
|
+
}
|
|
749
|
+
const element = nodeStack[0][indexStack[0]++];
|
|
750
|
+
if (isTag(element) && test(element))
|
|
751
|
+
result.push(element);
|
|
752
|
+
if (hasChildren(element) && element.children.length > 0) {
|
|
753
|
+
indexStack.unshift(0);
|
|
754
|
+
nodeStack.unshift(element.children);
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
/**
|
|
760
|
+
* A map of functions to check nodes against.
|
|
761
|
+
*/
|
|
762
|
+
const Checks = {
|
|
763
|
+
tag_name(name) {
|
|
764
|
+
if (typeof name === "function") {
|
|
765
|
+
return (element) => isTag(element) && name(element.name);
|
|
766
|
+
}
|
|
767
|
+
if (name === "*") {
|
|
768
|
+
return isTag;
|
|
769
|
+
}
|
|
770
|
+
return (element) => isTag(element) && element.name === name;
|
|
771
|
+
},
|
|
772
|
+
tag_type(type) {
|
|
773
|
+
if (typeof type === "function") {
|
|
774
|
+
return (element) => type(element.type);
|
|
775
|
+
}
|
|
776
|
+
return (element) => element.type === type;
|
|
777
|
+
},
|
|
778
|
+
tag_contains(data) {
|
|
779
|
+
if (typeof data === "function") {
|
|
780
|
+
return (element) => isText(element) && data(element.data);
|
|
781
|
+
}
|
|
782
|
+
return (element) => isText(element) && element.data === data;
|
|
783
|
+
},
|
|
784
|
+
};
|
|
785
|
+
/**
|
|
786
|
+
* Returns a function to check whether a node has an attribute with a particular
|
|
787
|
+
* value.
|
|
788
|
+
*
|
|
789
|
+
* @param attrib Attribute to check.
|
|
790
|
+
* @param value Attribute value to look for.
|
|
791
|
+
* @returns A function to check whether the a node has an attribute with a
|
|
792
|
+
* particular value.
|
|
793
|
+
*/
|
|
794
|
+
function getAttribCheck(attrib, value) {
|
|
795
|
+
if (typeof value === "function") {
|
|
796
|
+
return (element) => isTag(element) && value(element.attribs[attrib]);
|
|
797
|
+
}
|
|
798
|
+
return (element) => isTag(element) && element.attribs[attrib] === value;
|
|
799
|
+
}
|
|
800
|
+
/**
|
|
801
|
+
* Returns a function that returns `true` if either of the input functions
|
|
802
|
+
* returns `true` for a node.
|
|
803
|
+
*
|
|
804
|
+
* @param a First function to combine.
|
|
805
|
+
* @param b Second function to combine.
|
|
806
|
+
* @returns A function taking a node and returning `true` if either of the input
|
|
807
|
+
* functions returns `true` for the node.
|
|
808
|
+
*/
|
|
809
|
+
function combineFuncs(a, b) {
|
|
810
|
+
return (element) => a(element) || b(element);
|
|
811
|
+
}
|
|
812
|
+
/**
|
|
813
|
+
* Returns a function that executes all checks in `options` and returns `true`
|
|
814
|
+
* if any of them match a node.
|
|
815
|
+
*
|
|
816
|
+
* @param options An object describing nodes to look for.
|
|
817
|
+
* @returns A function that executes all checks in `options` and returns `true`
|
|
818
|
+
* if any of them match a node.
|
|
819
|
+
*/
|
|
820
|
+
function compileTest(options) {
|
|
821
|
+
const funcs = Object.keys(options).map((key) => {
|
|
822
|
+
const value = options[key];
|
|
823
|
+
return Object.hasOwn(Checks, key)
|
|
824
|
+
? Checks[key](value)
|
|
825
|
+
: getAttribCheck(key, value);
|
|
826
|
+
});
|
|
827
|
+
return funcs.length === 0 ? null : funcs.reduce(combineFuncs);
|
|
828
|
+
}
|
|
829
|
+
/**
|
|
830
|
+
* Checks whether a node matches the description in `options`.
|
|
831
|
+
*
|
|
832
|
+
* @category Legacy Query Functions
|
|
833
|
+
* @param options An object describing nodes to look for.
|
|
834
|
+
* @param node The element to test.
|
|
835
|
+
* @returns Whether the element matches the description in `options`.
|
|
836
|
+
*/
|
|
837
|
+
function testElement(options, node) {
|
|
838
|
+
const test = compileTest(options);
|
|
839
|
+
return test ? test(node) : true;
|
|
840
|
+
}
|
|
841
|
+
/**
|
|
842
|
+
* Returns all nodes that match `options`.
|
|
843
|
+
*
|
|
844
|
+
* @category Legacy Query Functions
|
|
845
|
+
* @param options An object describing nodes to look for.
|
|
846
|
+
* @param nodes Nodes to search through.
|
|
847
|
+
* @param recurse Also consider child nodes.
|
|
848
|
+
* @param limit Maximum number of nodes to return.
|
|
849
|
+
* @returns All nodes that match `options`.
|
|
850
|
+
*/
|
|
851
|
+
function getElements(options, nodes, recurse, limit = Number.POSITIVE_INFINITY) {
|
|
852
|
+
const test = compileTest(options);
|
|
853
|
+
return test ? filter(test, nodes, recurse, limit) : [];
|
|
854
|
+
}
|
|
855
|
+
/**
|
|
856
|
+
* Returns the node with the supplied ID.
|
|
857
|
+
*
|
|
858
|
+
* @category Legacy Query Functions
|
|
859
|
+
* @param id The unique ID attribute value to look for.
|
|
860
|
+
* @param nodes Nodes to search through.
|
|
861
|
+
* @param recurse Also consider child nodes.
|
|
862
|
+
* @returns The node with the supplied ID.
|
|
863
|
+
*/
|
|
864
|
+
function getElementById(id, nodes, recurse = true) {
|
|
865
|
+
if (!Array.isArray(nodes))
|
|
866
|
+
nodes = [nodes];
|
|
867
|
+
return findOne$1(getAttribCheck("id", id), nodes, recurse);
|
|
868
|
+
}
|
|
869
|
+
/**
|
|
870
|
+
* Returns all nodes with the supplied `tagName`.
|
|
871
|
+
*
|
|
872
|
+
* @category Legacy Query Functions
|
|
873
|
+
* @param tagName Tag name to search for.
|
|
874
|
+
* @param nodes Nodes to search through.
|
|
875
|
+
* @param recurse Also consider child nodes.
|
|
876
|
+
* @param limit Maximum number of nodes to return.
|
|
877
|
+
* @returns All nodes with the supplied `tagName`.
|
|
878
|
+
*/
|
|
879
|
+
function getElementsByTagName(tagName, nodes, recurse = true, limit = Number.POSITIVE_INFINITY) {
|
|
880
|
+
return filter(Checks["tag_name"](tagName), nodes, recurse, limit);
|
|
881
|
+
}
|
|
882
|
+
/**
|
|
883
|
+
* Returns all nodes with the supplied `className`.
|
|
884
|
+
*
|
|
885
|
+
* @category Legacy Query Functions
|
|
886
|
+
* @param className Class name to search for.
|
|
887
|
+
* @param nodes Nodes to search through.
|
|
888
|
+
* @param recurse Also consider child nodes.
|
|
889
|
+
* @param limit Maximum number of nodes to return.
|
|
890
|
+
* @returns All nodes with the supplied `className`.
|
|
891
|
+
*/
|
|
892
|
+
function getElementsByClassName(className, nodes, recurse = true, limit = Number.POSITIVE_INFINITY) {
|
|
893
|
+
return filter(getAttribCheck("class", className), nodes, recurse, limit);
|
|
894
|
+
}
|
|
895
|
+
/**
|
|
896
|
+
* Returns all nodes with the supplied `type`.
|
|
897
|
+
*
|
|
898
|
+
* @category Legacy Query Functions
|
|
899
|
+
* @param type Element type to look for.
|
|
900
|
+
* @param nodes Nodes to search through.
|
|
901
|
+
* @param recurse Also consider child nodes.
|
|
902
|
+
* @param limit Maximum number of nodes to return.
|
|
903
|
+
* @returns All nodes with the supplied `type`.
|
|
904
|
+
*/
|
|
905
|
+
function getElementsByTagType(type, nodes, recurse = true, limit = Number.POSITIVE_INFINITY) {
|
|
906
|
+
return filter(Checks["tag_type"](type), nodes, recurse, limit);
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
/**
|
|
910
|
+
* Get the named reference for an XML special character or U+00A0.
|
|
911
|
+
* @param char Code unit matched by one of the escape regexes.
|
|
912
|
+
*/
|
|
913
|
+
function getEscape(char) {
|
|
914
|
+
return char === 34
|
|
915
|
+
? """
|
|
916
|
+
: char === 38
|
|
917
|
+
? "&"
|
|
918
|
+
: char === 39
|
|
919
|
+
? "'"
|
|
920
|
+
: char === 60
|
|
921
|
+
? "<"
|
|
922
|
+
: char === 62
|
|
923
|
+
? ">"
|
|
924
|
+
: " ";
|
|
925
|
+
}
|
|
926
|
+
/**
|
|
927
|
+
* Bitset for ASCII characters that need to be escaped in XML.
|
|
928
|
+
*/
|
|
929
|
+
const XML_BITSET_VALUE = 0x50_00_00_c4; // 32..63 -> 34 ("),38 (&),39 ('),60 (<),62 (>)
|
|
930
|
+
/**
|
|
931
|
+
* Matches exactly the characters `encodeXML` escapes: the five XML special
|
|
932
|
+
* characters plus every non-ASCII code unit (lone surrogates included — no
|
|
933
|
+
* `u` flag). Kept in sync with `XML_BITSET_VALUE`.
|
|
934
|
+
*
|
|
935
|
+
* Shared with `encodeNonAsciiHTML` in `encode.ts`. Because the regex is
|
|
936
|
+
* stateful (`g` flag), every call site must set `lastIndex` before use.
|
|
937
|
+
*/
|
|
938
|
+
// eslint-disable-next-line unicorn/prefer-unicode-code-point-escapes -- the `\u{...}` form requires the `u` flag, which we deliberately omit so lone surrogates match by code unit
|
|
939
|
+
const xmlEncodeRegex = /["&'<>\u0080-\uFFFF]/g;
|
|
940
|
+
/**
|
|
941
|
+
* Whether `code` (a UTF-16 code unit) is escaped by {@link encodeXML}: a
|
|
942
|
+
* non-ASCII unit, or one of the five XML specials flagged in
|
|
943
|
+
* `XML_BITSET_VALUE` (which is only meaningful for code units 32-63).
|
|
944
|
+
* @param code Code unit to test.
|
|
945
|
+
*/
|
|
946
|
+
function isXmlEscapable(code) {
|
|
947
|
+
return (code >= 0x80 ||
|
|
948
|
+
(code >= 32 && code < 64 && ((XML_BITSET_VALUE >>> code) & 1) === 1));
|
|
949
|
+
}
|
|
950
|
+
/**
|
|
951
|
+
* Encodes all non-ASCII characters, as well as characters not valid in XML
|
|
952
|
+
* documents using XML entities.
|
|
953
|
+
*
|
|
954
|
+
* If a character has no equivalent entity, a numeric hexadecimal reference
|
|
955
|
+
* (eg. `ü`) will be used.
|
|
956
|
+
* @param input Input string to encode.
|
|
957
|
+
*/
|
|
958
|
+
function encodeXML(input) {
|
|
959
|
+
const { length } = input;
|
|
960
|
+
let out;
|
|
961
|
+
let last = 0;
|
|
962
|
+
let index = 0;
|
|
963
|
+
while (index < length) {
|
|
964
|
+
const char = input.charCodeAt(index);
|
|
965
|
+
/*
|
|
966
|
+
* Find the next character to escape: scan a short window inline
|
|
967
|
+
* (escapable characters cluster in markup-heavy input), then fall
|
|
968
|
+
* back to the regex, which skips clean spans in native code.
|
|
969
|
+
*/
|
|
970
|
+
if (!isXmlEscapable(char)) {
|
|
971
|
+
const bound = Math.min(index + 32, length);
|
|
972
|
+
let next = index + 1;
|
|
973
|
+
while (next < bound && !isXmlEscapable(input.charCodeAt(next))) {
|
|
974
|
+
next++;
|
|
975
|
+
}
|
|
976
|
+
if (next < bound) {
|
|
977
|
+
index = next;
|
|
978
|
+
continue;
|
|
979
|
+
}
|
|
980
|
+
if (next >= length)
|
|
981
|
+
break;
|
|
982
|
+
xmlEncodeRegex.lastIndex = next;
|
|
983
|
+
if (!xmlEncodeRegex.test(input))
|
|
984
|
+
break;
|
|
985
|
+
index = xmlEncodeRegex.lastIndex - 1;
|
|
986
|
+
continue;
|
|
987
|
+
}
|
|
988
|
+
if (out === undefined)
|
|
989
|
+
out = input.substring(0, index);
|
|
990
|
+
else if (last !== index)
|
|
991
|
+
out += input.substring(last, index);
|
|
992
|
+
if (char < 64) {
|
|
993
|
+
// Known replacement
|
|
994
|
+
out += getEscape(char);
|
|
995
|
+
last = index += 1;
|
|
996
|
+
continue;
|
|
997
|
+
}
|
|
998
|
+
// Non-ASCII: encode as numeric entity (handle surrogate pair)
|
|
999
|
+
const cp = input.codePointAt(index);
|
|
1000
|
+
out += `&#x${cp.toString(16)};`;
|
|
1001
|
+
if (cp !== char)
|
|
1002
|
+
index++; // Skip trailing surrogate
|
|
1003
|
+
last = index += 1;
|
|
1004
|
+
}
|
|
1005
|
+
if (out === undefined)
|
|
1006
|
+
return input;
|
|
1007
|
+
if (last < length)
|
|
1008
|
+
out += input.substr(last);
|
|
1009
|
+
return out;
|
|
1010
|
+
}
|
|
1011
|
+
/**
|
|
1012
|
+
* Escape `data` using `re`, mapping each matched character to its entity.
|
|
1013
|
+
* Every match is one UTF-16 code unit, so its index is `lastIndex - 1`.
|
|
1014
|
+
* @param re Global regex matching exactly the characters to escape
|
|
1015
|
+
* (`"`, `&`, `'`, `<`, `>`, `\u00A0` at most).
|
|
1016
|
+
* @param data String to escape.
|
|
1017
|
+
*/
|
|
1018
|
+
function escapeWithRegex(re, data) {
|
|
1019
|
+
re.lastIndex = 0;
|
|
1020
|
+
if (!re.test(data))
|
|
1021
|
+
return data;
|
|
1022
|
+
let out = "";
|
|
1023
|
+
let last = 0;
|
|
1024
|
+
do {
|
|
1025
|
+
const index = re.lastIndex - 1;
|
|
1026
|
+
if (last !== index)
|
|
1027
|
+
out += data.substring(last, index);
|
|
1028
|
+
const char = data.charCodeAt(index);
|
|
1029
|
+
out += getEscape(char);
|
|
1030
|
+
last = index + 1;
|
|
1031
|
+
} while (re.test(data));
|
|
1032
|
+
return out + data.substring(last);
|
|
1033
|
+
}
|
|
1034
|
+
const attributeEscapeRegex = /["&\u{A0}]/gu;
|
|
1035
|
+
/**
|
|
1036
|
+
* Encodes all characters that have to be escaped in HTML attributes,
|
|
1037
|
+
* following {@link https://html.spec.whatwg.org/multipage/parsing.html#escapingString}.
|
|
1038
|
+
* @param data String to escape.
|
|
1039
|
+
*/
|
|
1040
|
+
function escapeAttribute(data) {
|
|
1041
|
+
return escapeWithRegex(attributeEscapeRegex, data);
|
|
1042
|
+
}
|
|
1043
|
+
const textEscapeRegex = /[&<>\u{A0}]/gu;
|
|
1044
|
+
/**
|
|
1045
|
+
* Encodes all characters that have to be escaped in HTML text,
|
|
1046
|
+
* following {@link https://html.spec.whatwg.org/multipage/parsing.html#escapingString}.
|
|
1047
|
+
* @param data String to escape.
|
|
1048
|
+
*/
|
|
1049
|
+
function escapeText(data) {
|
|
1050
|
+
return escapeWithRegex(textEscapeRegex, data);
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
/**
|
|
1054
|
+
* Mixed-case SVG and MathML element names recognized in foreign content.
|
|
1055
|
+
* @see https://html.spec.whatwg.org/multipage/parsing.html#parsing-main-inforeign
|
|
1056
|
+
*/
|
|
1057
|
+
const elementNames = new Map("altGlyph altGlyphDef altGlyphItem animateColor animateMotion animateTransform clipPath feBlend feColorMatrix feComponentTransfer feComposite feConvolveMatrix feDiffuseLighting feDisplacementMap feDistantLight feDropShadow feFlood feFuncA feFuncB feFuncG feFuncR feGaussianBlur feImage feMerge feMergeNode feMorphology feOffset fePointLight feSpecularLighting feSpotLight feTile feTurbulence foreignObject glyphRef linearGradient radialGradient textPath"
|
|
1058
|
+
.split(" ")
|
|
1059
|
+
.map((name) => [name.toLowerCase(), name]));
|
|
1060
|
+
/**
|
|
1061
|
+
* Mixed-case SVG and MathML attribute names recognized in foreign content.
|
|
1062
|
+
* @see https://html.spec.whatwg.org/multipage/parsing.html#parsing-main-inforeign
|
|
1063
|
+
*/
|
|
1064
|
+
const attributeNames = new Map("definitionURL attributeName attributeType baseFrequency baseProfile calcMode clipPathUnits diffuseConstant edgeMode filterUnits glyphRef gradientTransform gradientUnits kernelMatrix kernelUnitLength keyPoints keySplines keyTimes lengthAdjust limitingConeAngle markerHeight markerUnits markerWidth maskContentUnits maskUnits numOctaves pathLength patternContentUnits patternTransform patternUnits pointsAtX pointsAtY pointsAtZ preserveAlpha preserveAspectRatio primitiveUnits refX refY repeatCount repeatDur requiredExtensions requiredFeatures specularConstant specularExponent spreadMethod startOffset stdDeviation stitchTiles surfaceScale systemLanguage tableValues targetX targetY textLength viewBox viewTarget xChannelSelector yChannelSelector zoomAndPan"
|
|
1065
|
+
.split(" ")
|
|
1066
|
+
.map((name) => [name.toLowerCase(), name]));
|
|
1067
|
+
|
|
1068
|
+
// ── Constants ────────────────────────────────────────────────────────
|
|
1069
|
+
/** Elements whose text content is never entity-encoded. */
|
|
1070
|
+
const unencodedElements = new Set("style script xmp iframe noembed noframes plaintext noscript".split(" "));
|
|
1071
|
+
/** HTML void elements — they cannot have children. */
|
|
1072
|
+
const voidElements = new Set("area base basefont br col command embed frame hr img input isindex keygen link meta param source track wbr".split(" "));
|
|
1073
|
+
/** Elements that switch the parser into foreign (XML-like) mode. */
|
|
1074
|
+
const foreignElements = new Set(["svg", "math"]);
|
|
1075
|
+
/**
|
|
1076
|
+
* Foreign-mode integration points: children of these elements are parsed
|
|
1077
|
+
* as HTML again, not as foreign content.
|
|
1078
|
+
*/
|
|
1079
|
+
const foreignModeIntegrationPoints = new Set("mi mo mn ms mtext annotation-xml foreignObject desc title".split(" "));
|
|
1080
|
+
// ── Public API ───────────────────────────────────────────────────────
|
|
1081
|
+
/**
|
|
1082
|
+
* Renders a DOM node or an array of DOM nodes to a string.
|
|
1083
|
+
*
|
|
1084
|
+
* Can be thought of as the equivalent of the `outerHTML` of the passed
|
|
1085
|
+
* node(s).
|
|
1086
|
+
* @param node Node to be rendered.
|
|
1087
|
+
* @param options Changes serialization behavior
|
|
1088
|
+
*/
|
|
1089
|
+
function render(node, options = {}) {
|
|
1090
|
+
const nodes = "length" in node ? node : [node];
|
|
1091
|
+
/*
|
|
1092
|
+
* `xmlMode` is threaded as a separate argument through the internal
|
|
1093
|
+
* functions so that foreign-mode transitions (svg/mathml ↔ html) can
|
|
1094
|
+
* adjust it without copying the options object on every element.
|
|
1095
|
+
*/
|
|
1096
|
+
const xmlMode = options.xmlMode ?? false;
|
|
1097
|
+
let output = "";
|
|
1098
|
+
// eslint-disable-next-line unicorn/no-for-loop
|
|
1099
|
+
for (let index = 0; index < nodes.length; index++) {
|
|
1100
|
+
output += renderNode(nodes[index], options, xmlMode);
|
|
1101
|
+
}
|
|
1102
|
+
return output;
|
|
1103
|
+
}
|
|
1104
|
+
// ── Internal rendering ───────────────────────────────────────────────
|
|
1105
|
+
/**
|
|
1106
|
+
* Render an array of child nodes (skips the single-node wrapping in `render`).
|
|
1107
|
+
* @param children The child nodes to render.
|
|
1108
|
+
* @param options The serialization options.
|
|
1109
|
+
* @param xmlMode The XML mode to use.
|
|
1110
|
+
*/
|
|
1111
|
+
function renderChildren(children, options, xmlMode) {
|
|
1112
|
+
let output = "";
|
|
1113
|
+
// eslint-disable-next-line unicorn/no-for-loop
|
|
1114
|
+
for (let index = 0; index < children.length; index++) {
|
|
1115
|
+
output += renderNode(children[index], options, xmlMode);
|
|
1116
|
+
}
|
|
1117
|
+
return output;
|
|
1118
|
+
}
|
|
1119
|
+
function renderNode(node, options, xmlMode) {
|
|
1120
|
+
switch (node.type) {
|
|
1121
|
+
case Root: {
|
|
1122
|
+
return renderChildren(node.children, options, xmlMode);
|
|
1123
|
+
}
|
|
1124
|
+
case Directive: {
|
|
1125
|
+
return `<${node.data}>`;
|
|
1126
|
+
}
|
|
1127
|
+
case Comment: {
|
|
1128
|
+
return `<!--${node.data}-->`;
|
|
1129
|
+
}
|
|
1130
|
+
case CDATA: {
|
|
1131
|
+
return `<![CDATA[${node.children[0].data}]]>`;
|
|
1132
|
+
}
|
|
1133
|
+
case Script:
|
|
1134
|
+
case Style:
|
|
1135
|
+
case Tag: {
|
|
1136
|
+
return renderTag(node, options, xmlMode);
|
|
1137
|
+
}
|
|
1138
|
+
case Text: {
|
|
1139
|
+
const element = node;
|
|
1140
|
+
const data = element.data || "";
|
|
1141
|
+
/*
|
|
1142
|
+
* Skip encoding when entities weren't decoded on input, or when
|
|
1143
|
+
* inside a raw-text element (script, style, etc.) in HTML mode.
|
|
1144
|
+
*/
|
|
1145
|
+
if ((options.encodeEntities ?? options.decodeEntities) !== false &&
|
|
1146
|
+
!(!xmlMode &&
|
|
1147
|
+
element.parent &&
|
|
1148
|
+
unencodedElements.has(element.parent.name))) {
|
|
1149
|
+
// `xmlMode: "foreign"` is truthy
|
|
1150
|
+
return xmlMode || options.encodeEntities !== "utf8"
|
|
1151
|
+
? encodeXML(data)
|
|
1152
|
+
: escapeText(data);
|
|
1153
|
+
}
|
|
1154
|
+
return data;
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
function renderTag(element, options, xmlMode) {
|
|
1159
|
+
if (xmlMode === "foreign") {
|
|
1160
|
+
// Correct lowercase element names back to their canonical mixed-case form
|
|
1161
|
+
element.name = elementNames.get(element.name) ?? element.name;
|
|
1162
|
+
// Integration points exit foreign mode: their children are HTML
|
|
1163
|
+
if (element.parent &&
|
|
1164
|
+
foreignModeIntegrationPoints.has(element.parent.name)) {
|
|
1165
|
+
xmlMode = false;
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
if (!xmlMode && foreignElements.has(element.name)) {
|
|
1169
|
+
xmlMode = "foreign";
|
|
1170
|
+
}
|
|
1171
|
+
const { name, children } = element;
|
|
1172
|
+
// Cache the void-element check — used for both self-closing and closing-tag logic
|
|
1173
|
+
const isVoid = !xmlMode && voidElements.has(name);
|
|
1174
|
+
let tag = `<${name}${formatAttributes(element.attribs, options, xmlMode)}`;
|
|
1175
|
+
if (children.length === 0 &&
|
|
1176
|
+
(xmlMode
|
|
1177
|
+
? options.selfClosingTags !== false
|
|
1178
|
+
: options.selfClosingTags && isVoid)) {
|
|
1179
|
+
// XML: `<br/>`, HTML: `<br />`
|
|
1180
|
+
tag += xmlMode ? "/>" : " />";
|
|
1181
|
+
}
|
|
1182
|
+
else {
|
|
1183
|
+
tag += ">";
|
|
1184
|
+
if (children.length > 0) {
|
|
1185
|
+
tag += renderChildren(children, options, xmlMode);
|
|
1186
|
+
}
|
|
1187
|
+
if (!isVoid) {
|
|
1188
|
+
tag += `</${name}>`;
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
return tag;
|
|
1192
|
+
}
|
|
1193
|
+
// ── Attribute formatting ─────────────────────────────────────────────
|
|
1194
|
+
function replaceQuotes(value) {
|
|
1195
|
+
return value.replaceAll('"', """);
|
|
1196
|
+
}
|
|
1197
|
+
/**
|
|
1198
|
+
* Serialize an element's attribute map to a string.
|
|
1199
|
+
*
|
|
1200
|
+
* Returns a string with a leading space before each attribute, or an
|
|
1201
|
+
* empty string if there are no attributes. This convention lets the
|
|
1202
|
+
* caller unconditionally concatenate the result onto the tag name.
|
|
1203
|
+
* @param attributes
|
|
1204
|
+
* @param options
|
|
1205
|
+
* @param xmlMode
|
|
1206
|
+
*/
|
|
1207
|
+
function formatAttributes(attributes, options, xmlMode) {
|
|
1208
|
+
if (!attributes)
|
|
1209
|
+
return "";
|
|
1210
|
+
/*
|
|
1211
|
+
* Pick the right encoder:
|
|
1212
|
+
* - Encoding disabled → only escape double-quotes (for valid attributes)
|
|
1213
|
+
* - XML / non-utf8 → full numeric entity encoding (encodeXML)
|
|
1214
|
+
* - HTML + utf8 → minimal escaping (escapeAttribute)
|
|
1215
|
+
*/
|
|
1216
|
+
const encode = (options.encodeEntities ?? options.decodeEntities) === false
|
|
1217
|
+
? replaceQuotes
|
|
1218
|
+
: xmlMode || options.encodeEntities !== "utf8"
|
|
1219
|
+
? encodeXML
|
|
1220
|
+
: escapeAttribute;
|
|
1221
|
+
const isForeign = xmlMode === "foreign";
|
|
1222
|
+
const showEmpty = !!(options.emptyAttrs ?? xmlMode);
|
|
1223
|
+
let result = "";
|
|
1224
|
+
for (const key in attributes) {
|
|
1225
|
+
if (!Object.hasOwn(attributes, key))
|
|
1226
|
+
continue;
|
|
1227
|
+
const value = attributes[key];
|
|
1228
|
+
const k = isForeign ? (attributeNames.get(key) ?? key) : key;
|
|
1229
|
+
result +=
|
|
1230
|
+
!showEmpty && (value == null || value === "")
|
|
1231
|
+
? ` ${k}`
|
|
1232
|
+
: ` ${k}="${encode(value == null ? "" : String(value))}"`;
|
|
1233
|
+
}
|
|
1234
|
+
return result;
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
/**
|
|
1238
|
+
* @category Stringify
|
|
1239
|
+
* @deprecated Use the `dom-serializer` module directly.
|
|
1240
|
+
* @param node Node to get the outer HTML of.
|
|
1241
|
+
* @param options Options for serialization.
|
|
1242
|
+
* @returns `node`'s outer HTML.
|
|
1243
|
+
*/
|
|
1244
|
+
function getOuterHTML(node, options) {
|
|
1245
|
+
return render(node, options);
|
|
1246
|
+
}
|
|
1247
|
+
/**
|
|
1248
|
+
* @category Stringify
|
|
1249
|
+
* @deprecated Use the `dom-serializer` module directly.
|
|
1250
|
+
* @param node Node to get the inner HTML of.
|
|
1251
|
+
* @param options Options for serialization.
|
|
1252
|
+
* @returns `node`'s inner HTML.
|
|
1253
|
+
*/
|
|
1254
|
+
function getInnerHTML(node, options) {
|
|
1255
|
+
return hasChildren(node)
|
|
1256
|
+
? node.children.map((node) => getOuterHTML(node, options)).join("")
|
|
1257
|
+
: "";
|
|
1258
|
+
}
|
|
1259
|
+
/**
|
|
1260
|
+
* Get a node's inner text. Same as `textContent`, but inserts newlines for `<br>` tags. Ignores comments.
|
|
1261
|
+
*
|
|
1262
|
+
* @category Stringify
|
|
1263
|
+
* @deprecated Use `textContent` instead.
|
|
1264
|
+
* @param node Node to get the inner text of.
|
|
1265
|
+
* @returns `node`'s inner text.
|
|
1266
|
+
*/
|
|
1267
|
+
function getText(node) {
|
|
1268
|
+
if (Array.isArray(node))
|
|
1269
|
+
return node.map(getText).join("");
|
|
1270
|
+
if (isTag(node))
|
|
1271
|
+
return node.name === "br" ? "\n" : getText(node.children);
|
|
1272
|
+
if (isCDATA(node))
|
|
1273
|
+
return getText(node.children);
|
|
1274
|
+
if (isText(node))
|
|
1275
|
+
return node.data;
|
|
1276
|
+
return "";
|
|
1277
|
+
}
|
|
1278
|
+
/**
|
|
1279
|
+
* Get a node's text content. Ignores comments.
|
|
1280
|
+
*
|
|
1281
|
+
* @category Stringify
|
|
1282
|
+
* @param node Node to get the text content of.
|
|
1283
|
+
* @returns `node`'s text content.
|
|
1284
|
+
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Node/textContent}
|
|
1285
|
+
*/
|
|
1286
|
+
function textContent(node) {
|
|
1287
|
+
if (Array.isArray(node))
|
|
1288
|
+
return node.map(textContent).join("");
|
|
1289
|
+
if (hasChildren(node) && !isComment(node)) {
|
|
1290
|
+
return textContent(node.children);
|
|
1291
|
+
}
|
|
1292
|
+
if (isText(node))
|
|
1293
|
+
return node.data;
|
|
1294
|
+
return "";
|
|
1295
|
+
}
|
|
1296
|
+
/**
|
|
1297
|
+
* Get a node's inner text, ignoring `<script>` and `<style>` tags. Ignores comments.
|
|
1298
|
+
*
|
|
1299
|
+
* @category Stringify
|
|
1300
|
+
* @param node Node to get the inner text of.
|
|
1301
|
+
* @returns `node`'s inner text.
|
|
1302
|
+
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Node/innerText}
|
|
1303
|
+
*/
|
|
1304
|
+
function innerText(node) {
|
|
1305
|
+
if (Array.isArray(node))
|
|
1306
|
+
return node.map(innerText).join("");
|
|
1307
|
+
if (hasChildren(node) && (node.type === ElementType.Tag || isCDATA(node))) {
|
|
1308
|
+
return innerText(node.children);
|
|
1309
|
+
}
|
|
1310
|
+
if (isText(node))
|
|
1311
|
+
return node.data;
|
|
1312
|
+
return "";
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
/**
|
|
1316
|
+
* Get the feed object from the root of a DOM tree.
|
|
1317
|
+
*
|
|
1318
|
+
* @category Feeds
|
|
1319
|
+
* @param document The DOM to extract the feed from.
|
|
1320
|
+
* @returns The feed.
|
|
1321
|
+
*/
|
|
1322
|
+
function getFeed(document) {
|
|
1323
|
+
const feedRoot = getOneElement(isValidFeed, document);
|
|
1324
|
+
return feedRoot
|
|
1325
|
+
? feedRoot.name === "feed"
|
|
1326
|
+
? getAtomFeed(feedRoot)
|
|
1327
|
+
: getRssFeed(feedRoot)
|
|
1328
|
+
: null;
|
|
1329
|
+
}
|
|
1330
|
+
/**
|
|
1331
|
+
* Parse an Atom feed.
|
|
1332
|
+
*
|
|
1333
|
+
* @param feedRoot The root of the feed.
|
|
1334
|
+
* @returns The parsed feed.
|
|
1335
|
+
*/
|
|
1336
|
+
function getAtomFeed(feedRoot) {
|
|
1337
|
+
const childs = feedRoot.children;
|
|
1338
|
+
const feed = {
|
|
1339
|
+
type: "atom",
|
|
1340
|
+
items: getElementsByTagName("entry", childs).map((item) => {
|
|
1341
|
+
const { children } = item;
|
|
1342
|
+
const entry = { media: getMediaElements(children) };
|
|
1343
|
+
addConditionally(entry, "id", "id", children);
|
|
1344
|
+
addConditionally(entry, "title", "title", children);
|
|
1345
|
+
const href = getOneElement("link", children)?.attribs["href"];
|
|
1346
|
+
if (href) {
|
|
1347
|
+
entry.link = href;
|
|
1348
|
+
}
|
|
1349
|
+
const description = fetch("summary", children) || fetch("content", children);
|
|
1350
|
+
if (description) {
|
|
1351
|
+
entry.description = description;
|
|
1352
|
+
}
|
|
1353
|
+
const pubDate = fetch("updated", children);
|
|
1354
|
+
if (pubDate) {
|
|
1355
|
+
entry.pubDate = new Date(pubDate);
|
|
1356
|
+
}
|
|
1357
|
+
return entry;
|
|
1358
|
+
}),
|
|
1359
|
+
};
|
|
1360
|
+
addConditionally(feed, "id", "id", childs);
|
|
1361
|
+
addConditionally(feed, "title", "title", childs);
|
|
1362
|
+
const href = getOneElement("link", childs)?.attribs["href"];
|
|
1363
|
+
if (href) {
|
|
1364
|
+
feed.link = href;
|
|
1365
|
+
}
|
|
1366
|
+
addConditionally(feed, "description", "subtitle", childs);
|
|
1367
|
+
const updated = fetch("updated", childs);
|
|
1368
|
+
if (updated) {
|
|
1369
|
+
feed.updated = new Date(updated);
|
|
1370
|
+
}
|
|
1371
|
+
addConditionally(feed, "author", "email", childs, true);
|
|
1372
|
+
return feed;
|
|
1373
|
+
}
|
|
1374
|
+
/**
|
|
1375
|
+
* Parse a RSS feed.
|
|
1376
|
+
*
|
|
1377
|
+
* @param feedRoot The root of the feed.
|
|
1378
|
+
* @returns The parsed feed.
|
|
1379
|
+
*/
|
|
1380
|
+
function getRssFeed(feedRoot) {
|
|
1381
|
+
const childs = getOneElement("channel", feedRoot.children)?.children ?? [];
|
|
1382
|
+
const feed = {
|
|
1383
|
+
type: feedRoot.name.substr(0, 3),
|
|
1384
|
+
id: "",
|
|
1385
|
+
items: getElementsByTagName("item", feedRoot.children).map((item) => {
|
|
1386
|
+
const { children } = item;
|
|
1387
|
+
const entry = { media: getMediaElements(children) };
|
|
1388
|
+
addConditionally(entry, "id", "guid", children);
|
|
1389
|
+
addConditionally(entry, "title", "title", children);
|
|
1390
|
+
addConditionally(entry, "link", "link", children);
|
|
1391
|
+
addConditionally(entry, "description", "description", children);
|
|
1392
|
+
const pubDate = fetch("pubDate", children) || fetch("dc:date", children);
|
|
1393
|
+
if (pubDate)
|
|
1394
|
+
entry.pubDate = new Date(pubDate);
|
|
1395
|
+
return entry;
|
|
1396
|
+
}),
|
|
1397
|
+
};
|
|
1398
|
+
addConditionally(feed, "title", "title", childs);
|
|
1399
|
+
addConditionally(feed, "link", "link", childs);
|
|
1400
|
+
addConditionally(feed, "description", "description", childs);
|
|
1401
|
+
const updated = fetch("lastBuildDate", childs);
|
|
1402
|
+
if (updated) {
|
|
1403
|
+
feed.updated = new Date(updated);
|
|
1404
|
+
}
|
|
1405
|
+
addConditionally(feed, "author", "managingEditor", childs, true);
|
|
1406
|
+
return feed;
|
|
1407
|
+
}
|
|
1408
|
+
const MEDIA_KEYS_STRING = ["url", "type", "lang"];
|
|
1409
|
+
const MEDIA_KEYS_INT = [
|
|
1410
|
+
"fileSize",
|
|
1411
|
+
"bitrate",
|
|
1412
|
+
"framerate",
|
|
1413
|
+
"samplingrate",
|
|
1414
|
+
"channels",
|
|
1415
|
+
"duration",
|
|
1416
|
+
"height",
|
|
1417
|
+
"width",
|
|
1418
|
+
];
|
|
1419
|
+
/**
|
|
1420
|
+
* Get all media elements of a feed item.
|
|
1421
|
+
*
|
|
1422
|
+
* @param where Nodes to search in.
|
|
1423
|
+
* @returns Media elements.
|
|
1424
|
+
*/
|
|
1425
|
+
function getMediaElements(where) {
|
|
1426
|
+
return getElementsByTagName("media:content", where).map((element) => {
|
|
1427
|
+
const { attribs } = element;
|
|
1428
|
+
const media = {
|
|
1429
|
+
medium: attribs["medium"],
|
|
1430
|
+
isDefault: !!attribs["isDefault"],
|
|
1431
|
+
};
|
|
1432
|
+
for (const attrib of MEDIA_KEYS_STRING) {
|
|
1433
|
+
if (attribs[attrib]) {
|
|
1434
|
+
media[attrib] = attribs[attrib];
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1437
|
+
for (const attrib of MEDIA_KEYS_INT) {
|
|
1438
|
+
if (attribs[attrib]) {
|
|
1439
|
+
media[attrib] = Number.parseInt(attribs[attrib], 10);
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
if (attribs["expression"]) {
|
|
1443
|
+
media.expression = attribs["expression"];
|
|
1444
|
+
}
|
|
1445
|
+
return media;
|
|
1446
|
+
});
|
|
1447
|
+
}
|
|
1448
|
+
/**
|
|
1449
|
+
* Get one element by tag name.
|
|
1450
|
+
*
|
|
1451
|
+
* @param tagName Tag name to look for
|
|
1452
|
+
* @param node Node to search in
|
|
1453
|
+
* @returns The element or null
|
|
1454
|
+
*/
|
|
1455
|
+
function getOneElement(tagName, node) {
|
|
1456
|
+
return getElementsByTagName(tagName, node, true, 1)[0];
|
|
1457
|
+
}
|
|
1458
|
+
/**
|
|
1459
|
+
* Get the text content of an element with a certain tag name.
|
|
1460
|
+
*
|
|
1461
|
+
* @param tagName Tag name to look for.
|
|
1462
|
+
* @param where Node to search in.
|
|
1463
|
+
* @param recurse Whether to recurse into child nodes.
|
|
1464
|
+
* @returns The text content of the element.
|
|
1465
|
+
*/
|
|
1466
|
+
function fetch(tagName, where, recurse = false) {
|
|
1467
|
+
return textContent(getElementsByTagName(tagName, where, recurse, 1)).trim();
|
|
1468
|
+
}
|
|
1469
|
+
/**
|
|
1470
|
+
* Adds a property to an object if it has a value.
|
|
1471
|
+
*
|
|
1472
|
+
* @param object Object to be extended.
|
|
1473
|
+
* @param property Property name.
|
|
1474
|
+
* @param tagName Tag name that contains the conditionally added property.
|
|
1475
|
+
* @param where Element to search for the property.
|
|
1476
|
+
* @param recurse Whether to recurse into child nodes.
|
|
1477
|
+
*/
|
|
1478
|
+
function addConditionally(object, property, tagName, where, recurse = false) {
|
|
1479
|
+
const value = fetch(tagName, where, recurse);
|
|
1480
|
+
if (value)
|
|
1481
|
+
object[property] = value;
|
|
1482
|
+
}
|
|
1483
|
+
/**
|
|
1484
|
+
* Checks if an element is a feed root node.
|
|
1485
|
+
*
|
|
1486
|
+
* @param value The name of the element to check.
|
|
1487
|
+
* @returns Whether an element is a feed root node.
|
|
1488
|
+
*/
|
|
1489
|
+
function isValidFeed(value) {
|
|
1490
|
+
return value === "rss" || value === "feed" || value === "rdf:RDF";
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1493
|
+
/**
|
|
1494
|
+
* Given an array of nodes, remove any member that is contained by another
|
|
1495
|
+
* member.
|
|
1496
|
+
*
|
|
1497
|
+
* @category Helpers
|
|
1498
|
+
* @param nodes Nodes to filter.
|
|
1499
|
+
* @returns Remaining nodes that aren't contained by other nodes.
|
|
1500
|
+
*/
|
|
1501
|
+
function removeSubsets(nodes) {
|
|
1502
|
+
let index = nodes.length;
|
|
1503
|
+
/*
|
|
1504
|
+
* Check if each node (or one of its ancestors) is already contained in the
|
|
1505
|
+
* array.
|
|
1506
|
+
*/
|
|
1507
|
+
while (--index >= 0) {
|
|
1508
|
+
const node = nodes[index];
|
|
1509
|
+
/*
|
|
1510
|
+
* Remove the node if it is not unique.
|
|
1511
|
+
* We are going through the array from the end, so we only
|
|
1512
|
+
* have to check nodes that preceed the node under consideration in the array.
|
|
1513
|
+
*/
|
|
1514
|
+
if (index > 0 && nodes.lastIndexOf(node, index - 1) >= 0) {
|
|
1515
|
+
nodes.splice(index, 1);
|
|
1516
|
+
continue;
|
|
1517
|
+
}
|
|
1518
|
+
for (let ancestor = node.parent; ancestor; ancestor = ancestor.parent) {
|
|
1519
|
+
if (nodes.includes(ancestor)) {
|
|
1520
|
+
nodes.splice(index, 1);
|
|
1521
|
+
break;
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1525
|
+
return nodes;
|
|
1526
|
+
}
|
|
1527
|
+
/**
|
|
1528
|
+
* @category Helpers
|
|
1529
|
+
* @see {@link http://dom.spec.whatwg.org/#dom-node-comparedocumentposition}
|
|
1530
|
+
*/
|
|
1531
|
+
var DocumentPosition;
|
|
1532
|
+
(function (DocumentPosition) {
|
|
1533
|
+
DocumentPosition[DocumentPosition["DISCONNECTED"] = 1] = "DISCONNECTED";
|
|
1534
|
+
DocumentPosition[DocumentPosition["PRECEDING"] = 2] = "PRECEDING";
|
|
1535
|
+
DocumentPosition[DocumentPosition["FOLLOWING"] = 4] = "FOLLOWING";
|
|
1536
|
+
DocumentPosition[DocumentPosition["CONTAINS"] = 8] = "CONTAINS";
|
|
1537
|
+
DocumentPosition[DocumentPosition["CONTAINED_BY"] = 16] = "CONTAINED_BY";
|
|
1538
|
+
})(DocumentPosition || (DocumentPosition = {}));
|
|
1539
|
+
/**
|
|
1540
|
+
* Compare the position of one node against another node in any other document,
|
|
1541
|
+
* returning a bitmask with the values from {@link DocumentPosition}.
|
|
1542
|
+
*
|
|
1543
|
+
* Document order:
|
|
1544
|
+
* > There is an ordering, document order, defined on all the nodes in the
|
|
1545
|
+
* > document corresponding to the order in which the first character of the
|
|
1546
|
+
* > XML representation of each node occurs in the XML representation of the
|
|
1547
|
+
* > document after expansion of general entities. Thus, the document element
|
|
1548
|
+
* > node will be the first node. Element nodes occur before their children.
|
|
1549
|
+
* > Thus, document order orders element nodes in order of the occurrence of
|
|
1550
|
+
* > their start-tag in the XML (after expansion of entities). The attribute
|
|
1551
|
+
* > nodes of an element occur after the element and before its children. The
|
|
1552
|
+
* > relative order of attribute nodes is implementation-dependent.
|
|
1553
|
+
*
|
|
1554
|
+
* Source:
|
|
1555
|
+
* http://www.w3.org/TR/DOM-Level-3-Core/glossary.html#dt-document-order
|
|
1556
|
+
*
|
|
1557
|
+
* @category Helpers
|
|
1558
|
+
* @param nodeA The first node to use in the comparison
|
|
1559
|
+
* @param nodeB The second node to use in the comparison
|
|
1560
|
+
* @returns A bitmask describing the input nodes' relative position.
|
|
1561
|
+
*
|
|
1562
|
+
* See http://dom.spec.whatwg.org/#dom-node-comparedocumentposition for
|
|
1563
|
+
* a description of these values.
|
|
1564
|
+
*/
|
|
1565
|
+
function compareDocumentPosition(nodeA, nodeB) {
|
|
1566
|
+
const aParents = [];
|
|
1567
|
+
const bParents = [];
|
|
1568
|
+
if (nodeA === nodeB) {
|
|
1569
|
+
return 0;
|
|
1570
|
+
}
|
|
1571
|
+
let current = hasChildren(nodeA) ? nodeA : nodeA.parent;
|
|
1572
|
+
while (current) {
|
|
1573
|
+
aParents.unshift(current);
|
|
1574
|
+
current = current.parent;
|
|
1575
|
+
}
|
|
1576
|
+
current = hasChildren(nodeB) ? nodeB : nodeB.parent;
|
|
1577
|
+
while (current) {
|
|
1578
|
+
bParents.unshift(current);
|
|
1579
|
+
current = current.parent;
|
|
1580
|
+
}
|
|
1581
|
+
const maxIndex = Math.min(aParents.length, bParents.length);
|
|
1582
|
+
let index = 0;
|
|
1583
|
+
while (index < maxIndex && aParents[index] === bParents[index]) {
|
|
1584
|
+
index++;
|
|
1585
|
+
}
|
|
1586
|
+
if (index === 0) {
|
|
1587
|
+
return DocumentPosition.DISCONNECTED;
|
|
1588
|
+
}
|
|
1589
|
+
const sharedParent = aParents[index - 1];
|
|
1590
|
+
const siblings = sharedParent.children;
|
|
1591
|
+
const aSibling = aParents[index];
|
|
1592
|
+
const bSibling = bParents[index];
|
|
1593
|
+
if (siblings.indexOf(aSibling) > siblings.indexOf(bSibling)) {
|
|
1594
|
+
if (sharedParent === nodeB) {
|
|
1595
|
+
return DocumentPosition.FOLLOWING | DocumentPosition.CONTAINED_BY;
|
|
1596
|
+
}
|
|
1597
|
+
return DocumentPosition.FOLLOWING;
|
|
1598
|
+
}
|
|
1599
|
+
if (sharedParent === nodeA) {
|
|
1600
|
+
return DocumentPosition.PRECEDING | DocumentPosition.CONTAINS;
|
|
1601
|
+
}
|
|
1602
|
+
return DocumentPosition.PRECEDING;
|
|
1603
|
+
}
|
|
1604
|
+
/**
|
|
1605
|
+
* Sort an array of nodes based on their relative position in the document,
|
|
1606
|
+
* removing any duplicate nodes. If the array contains nodes that do not belong
|
|
1607
|
+
* to the same document, sort order is unspecified.
|
|
1608
|
+
*
|
|
1609
|
+
* @category Helpers
|
|
1610
|
+
* @param nodes Array of DOM nodes.
|
|
1611
|
+
* @returns Collection of unique nodes, sorted in document order.
|
|
1612
|
+
*/
|
|
1613
|
+
function uniqueSort(nodes) {
|
|
1614
|
+
nodes = nodes.filter((node, index, array) => !array.includes(node, index + 1));
|
|
1615
|
+
nodes.sort((a, b) => {
|
|
1616
|
+
const relative = compareDocumentPosition(a, b);
|
|
1617
|
+
if (relative & DocumentPosition.PRECEDING) {
|
|
1618
|
+
return -1;
|
|
1619
|
+
}
|
|
1620
|
+
if (relative & DocumentPosition.FOLLOWING) {
|
|
1621
|
+
return 1;
|
|
1622
|
+
}
|
|
1623
|
+
return 0;
|
|
1624
|
+
});
|
|
1625
|
+
return nodes;
|
|
1626
|
+
}
|
|
1627
|
+
|
|
1628
|
+
/**
|
|
1629
|
+
* Remove an element from the dom
|
|
1630
|
+
*
|
|
1631
|
+
* @category Manipulation
|
|
1632
|
+
* @param element The element to be removed.
|
|
1633
|
+
*/
|
|
1634
|
+
function removeElement(element) {
|
|
1635
|
+
if (element.prev)
|
|
1636
|
+
element.prev.next = element.next;
|
|
1637
|
+
if (element.next)
|
|
1638
|
+
element.next.prev = element.prev;
|
|
1639
|
+
if (element.parent) {
|
|
1640
|
+
const childs = element.parent.children;
|
|
1641
|
+
const childsIndex = childs.lastIndexOf(element);
|
|
1642
|
+
if (childsIndex !== -1) {
|
|
1643
|
+
childs.splice(childsIndex, 1);
|
|
1644
|
+
}
|
|
1645
|
+
}
|
|
1646
|
+
element.next = null;
|
|
1647
|
+
element.prev = null;
|
|
1648
|
+
element.parent = null;
|
|
1649
|
+
}
|
|
1650
|
+
/**
|
|
1651
|
+
* Replace an element in the dom
|
|
1652
|
+
*
|
|
1653
|
+
* @category Manipulation
|
|
1654
|
+
* @param element The element to be replaced.
|
|
1655
|
+
* @param replacement The element to be added
|
|
1656
|
+
*/
|
|
1657
|
+
function replaceElement(element, replacement) {
|
|
1658
|
+
replacement.prev = element.prev;
|
|
1659
|
+
if (replacement.prev) {
|
|
1660
|
+
replacement.prev.next = replacement;
|
|
1661
|
+
}
|
|
1662
|
+
replacement.next = element.next;
|
|
1663
|
+
if (replacement.next) {
|
|
1664
|
+
replacement.next.prev = replacement;
|
|
1665
|
+
}
|
|
1666
|
+
replacement.parent = element.parent;
|
|
1667
|
+
if (replacement.parent) {
|
|
1668
|
+
const { children } = replacement.parent;
|
|
1669
|
+
const elementIndex = children.lastIndexOf(element);
|
|
1670
|
+
if (elementIndex === -1) {
|
|
1671
|
+
return;
|
|
1672
|
+
}
|
|
1673
|
+
children[elementIndex] = replacement;
|
|
1674
|
+
element.parent = null;
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1677
|
+
/**
|
|
1678
|
+
* Append a child to an element.
|
|
1679
|
+
*
|
|
1680
|
+
* @category Manipulation
|
|
1681
|
+
* @param parent The element to append to.
|
|
1682
|
+
* @param child The element to be added as a child.
|
|
1683
|
+
*/
|
|
1684
|
+
function appendChild(parent, child) {
|
|
1685
|
+
removeElement(child);
|
|
1686
|
+
child.next = null;
|
|
1687
|
+
child.parent = parent;
|
|
1688
|
+
if (parent.children.push(child) > 1) {
|
|
1689
|
+
const sibling = parent.children[parent.children.length - 2];
|
|
1690
|
+
sibling.next = child;
|
|
1691
|
+
child.prev = sibling;
|
|
1692
|
+
}
|
|
1693
|
+
else {
|
|
1694
|
+
child.prev = null;
|
|
1695
|
+
}
|
|
1696
|
+
}
|
|
1697
|
+
/**
|
|
1698
|
+
* Append an element after another.
|
|
1699
|
+
*
|
|
1700
|
+
* @category Manipulation
|
|
1701
|
+
* @param element The element to append after.
|
|
1702
|
+
* @param next The element be added.
|
|
1703
|
+
*/
|
|
1704
|
+
function append(element, next) {
|
|
1705
|
+
removeElement(next);
|
|
1706
|
+
const { parent } = element;
|
|
1707
|
+
const currentNext = element.next;
|
|
1708
|
+
next.next = currentNext;
|
|
1709
|
+
next.prev = element;
|
|
1710
|
+
element.next = next;
|
|
1711
|
+
next.parent = parent;
|
|
1712
|
+
if (currentNext) {
|
|
1713
|
+
currentNext.prev = next;
|
|
1714
|
+
if (parent) {
|
|
1715
|
+
const childs = parent.children;
|
|
1716
|
+
childs.splice(childs.lastIndexOf(currentNext), 0, next);
|
|
1717
|
+
}
|
|
1718
|
+
}
|
|
1719
|
+
else if (parent) {
|
|
1720
|
+
parent.children.push(next);
|
|
1721
|
+
}
|
|
1722
|
+
}
|
|
1723
|
+
/**
|
|
1724
|
+
* Prepend a child to an element.
|
|
1725
|
+
*
|
|
1726
|
+
* @category Manipulation
|
|
1727
|
+
* @param parent The element to prepend before.
|
|
1728
|
+
* @param child The element to be added as a child.
|
|
1729
|
+
*/
|
|
1730
|
+
function prependChild(parent, child) {
|
|
1731
|
+
removeElement(child);
|
|
1732
|
+
child.parent = parent;
|
|
1733
|
+
child.prev = null;
|
|
1734
|
+
if (parent.children.unshift(child) === 1) {
|
|
1735
|
+
child.next = null;
|
|
1736
|
+
}
|
|
1737
|
+
else {
|
|
1738
|
+
const sibling = parent.children[1];
|
|
1739
|
+
sibling.prev = child;
|
|
1740
|
+
child.next = sibling;
|
|
1741
|
+
}
|
|
1742
|
+
}
|
|
1743
|
+
/**
|
|
1744
|
+
* Prepend an element before another.
|
|
1745
|
+
*
|
|
1746
|
+
* @category Manipulation
|
|
1747
|
+
* @param element The element to prepend before.
|
|
1748
|
+
* @param previous The element to be added.
|
|
1749
|
+
*/
|
|
1750
|
+
function prepend(element, previous) {
|
|
1751
|
+
removeElement(previous);
|
|
1752
|
+
const { parent } = element;
|
|
1753
|
+
if (parent) {
|
|
1754
|
+
const childs = parent.children;
|
|
1755
|
+
childs.splice(childs.indexOf(element), 0, previous);
|
|
1756
|
+
}
|
|
1757
|
+
if (element.prev) {
|
|
1758
|
+
element.prev.next = previous;
|
|
1759
|
+
}
|
|
1760
|
+
previous.parent = parent;
|
|
1761
|
+
previous.prev = element.prev;
|
|
1762
|
+
previous.next = element;
|
|
1763
|
+
element.prev = previous;
|
|
1764
|
+
}
|
|
1765
|
+
|
|
1766
|
+
/**
|
|
1767
|
+
* Get a node's children.
|
|
1768
|
+
*
|
|
1769
|
+
* @category Traversal
|
|
1770
|
+
* @param element Node to get the children of.
|
|
1771
|
+
* @returns `element`'s children, or an empty array.
|
|
1772
|
+
*/
|
|
1773
|
+
function getChildren(element) {
|
|
1774
|
+
return hasChildren(element) ? element.children : [];
|
|
1775
|
+
}
|
|
1776
|
+
function getParent(element) {
|
|
1777
|
+
return element.parent || null;
|
|
1778
|
+
}
|
|
1779
|
+
/**
|
|
1780
|
+
* Gets an elements siblings, including the element itself.
|
|
1781
|
+
*
|
|
1782
|
+
* Attempts to get the children through the element's parent first. If we don't
|
|
1783
|
+
* have a parent (the element is a root node), we walk the element's `prev` &
|
|
1784
|
+
* `next` to get all remaining nodes.
|
|
1785
|
+
*
|
|
1786
|
+
* @category Traversal
|
|
1787
|
+
* @param element Element to get the siblings of.
|
|
1788
|
+
* @returns `element`'s siblings, including `element`.
|
|
1789
|
+
*/
|
|
1790
|
+
function getSiblings(element) {
|
|
1791
|
+
const parent = getParent(element);
|
|
1792
|
+
if (parent != null)
|
|
1793
|
+
return getChildren(parent);
|
|
1794
|
+
const siblings = [element];
|
|
1795
|
+
let { prev, next } = element;
|
|
1796
|
+
while (prev != null) {
|
|
1797
|
+
siblings.unshift(prev);
|
|
1798
|
+
({ prev } = prev);
|
|
1799
|
+
}
|
|
1800
|
+
while (next != null) {
|
|
1801
|
+
siblings.push(next);
|
|
1802
|
+
({ next } = next);
|
|
1803
|
+
}
|
|
1804
|
+
return siblings;
|
|
1805
|
+
}
|
|
1806
|
+
/**
|
|
1807
|
+
* Gets an attribute from an element.
|
|
1808
|
+
*
|
|
1809
|
+
* @category Traversal
|
|
1810
|
+
* @param element Element to check.
|
|
1811
|
+
* @param name Attribute name to retrieve.
|
|
1812
|
+
* @returns The element's attribute value, or `undefined`.
|
|
1813
|
+
*/
|
|
1814
|
+
function getAttributeValue(element, name) {
|
|
1815
|
+
const { attribs } = element;
|
|
1816
|
+
return attribs?.[name];
|
|
1817
|
+
}
|
|
1818
|
+
/**
|
|
1819
|
+
* Checks whether an element has an attribute.
|
|
1820
|
+
*
|
|
1821
|
+
* @category Traversal
|
|
1822
|
+
* @param element Element to check.
|
|
1823
|
+
* @param name Attribute name to look for.
|
|
1824
|
+
* @returns Returns whether `element` has the attribute `name`.
|
|
1825
|
+
*/
|
|
1826
|
+
function hasAttrib(element, name) {
|
|
1827
|
+
const { attribs } = element;
|
|
1828
|
+
return (attribs != null && Object.hasOwn(attribs, name) && attribs[name] != null);
|
|
1829
|
+
}
|
|
1830
|
+
/**
|
|
1831
|
+
* Get the tag name of an element.
|
|
1832
|
+
*
|
|
1833
|
+
* @category Traversal
|
|
1834
|
+
* @param element The element to get the name for.
|
|
1835
|
+
* @returns The tag name of `element`.
|
|
1836
|
+
*/
|
|
1837
|
+
function getName(element) {
|
|
1838
|
+
return element.name;
|
|
1839
|
+
}
|
|
1840
|
+
/**
|
|
1841
|
+
* Returns the next element sibling of a node.
|
|
1842
|
+
*
|
|
1843
|
+
* @category Traversal
|
|
1844
|
+
* @param element The element to get the next sibling of.
|
|
1845
|
+
* @returns `element`'s next sibling that is a tag, or `null` if there is no next
|
|
1846
|
+
* sibling.
|
|
1847
|
+
*/
|
|
1848
|
+
function nextElementSibling(element) {
|
|
1849
|
+
let { next } = element;
|
|
1850
|
+
while (next !== null && !isTag(next))
|
|
1851
|
+
({ next } = next);
|
|
1852
|
+
return next;
|
|
1853
|
+
}
|
|
1854
|
+
/**
|
|
1855
|
+
* Returns the previous element sibling of a node.
|
|
1856
|
+
*
|
|
1857
|
+
* @category Traversal
|
|
1858
|
+
* @param element The element to get the previous sibling of.
|
|
1859
|
+
* @returns `element`'s previous sibling that is a tag, or `null` if there is no
|
|
1860
|
+
* previous sibling.
|
|
1861
|
+
*/
|
|
1862
|
+
// eslint-disable-next-line unicorn/prevent-abbreviations -- Keep public API name for backwards compatibility.
|
|
1863
|
+
function prevElementSibling(element) {
|
|
1864
|
+
let { prev } = element;
|
|
1865
|
+
while (prev !== null && !isTag(prev))
|
|
1866
|
+
({ prev } = prev);
|
|
1867
|
+
return prev;
|
|
1868
|
+
}
|
|
1869
|
+
|
|
1870
|
+
var DomUtils = /*#__PURE__*/Object.freeze({
|
|
1871
|
+
__proto__: null,
|
|
1872
|
+
get DocumentPosition () { return DocumentPosition; },
|
|
1873
|
+
append: append,
|
|
1874
|
+
appendChild: appendChild,
|
|
1875
|
+
compareDocumentPosition: compareDocumentPosition,
|
|
1876
|
+
existsOne: existsOne,
|
|
1877
|
+
filter: filter,
|
|
1878
|
+
find: find,
|
|
1879
|
+
findAll: findAll$1,
|
|
1880
|
+
findOne: findOne$1,
|
|
1881
|
+
getAttributeValue: getAttributeValue,
|
|
1882
|
+
getChildren: getChildren,
|
|
1883
|
+
getElementById: getElementById,
|
|
1884
|
+
getElements: getElements,
|
|
1885
|
+
getElementsByClassName: getElementsByClassName,
|
|
1886
|
+
getElementsByTagName: getElementsByTagName,
|
|
1887
|
+
getElementsByTagType: getElementsByTagType,
|
|
1888
|
+
getFeed: getFeed,
|
|
1889
|
+
getInnerHTML: getInnerHTML,
|
|
1890
|
+
getName: getName,
|
|
1891
|
+
getOuterHTML: getOuterHTML,
|
|
1892
|
+
getParent: getParent,
|
|
1893
|
+
getSiblings: getSiblings,
|
|
1894
|
+
getText: getText,
|
|
1895
|
+
hasAttrib: hasAttrib,
|
|
1896
|
+
innerText: innerText,
|
|
1897
|
+
nextElementSibling: nextElementSibling,
|
|
1898
|
+
prepend: prepend,
|
|
1899
|
+
prependChild: prependChild,
|
|
1900
|
+
prevElementSibling: prevElementSibling,
|
|
1901
|
+
removeElement: removeElement,
|
|
1902
|
+
removeSubsets: removeSubsets,
|
|
1903
|
+
replaceElement: replaceElement,
|
|
1904
|
+
testElement: testElement,
|
|
1905
|
+
textContent: textContent,
|
|
1906
|
+
uniqueSort: uniqueSort
|
|
1907
|
+
});
|
|
1908
|
+
|
|
1909
|
+
/**
|
|
1910
|
+
* All reserved characters in a regex, used for escaping.
|
|
1911
|
+
*
|
|
1912
|
+
* Taken from XRegExp, (c) 2007-2020 Steven Levithan under the MIT license
|
|
1913
|
+
* https://github.com/slevithan/xregexp/blob/95eeebeb8fac8754d54eafe2b4743661ac1cf028/src/xregexp.js#L794
|
|
1914
|
+
*/
|
|
1915
|
+
const reChars = /[-[\]{}()*+?.,\\^$|#\s]/g;
|
|
1916
|
+
const whitespaceRe = /\s/;
|
|
1917
|
+
function escapeRegex(value) {
|
|
1918
|
+
return value.replace(reChars, "\\$&");
|
|
1919
|
+
}
|
|
1920
|
+
/**
|
|
1921
|
+
* Attributes that are case-insensitive in HTML.
|
|
1922
|
+
* @see https://html.spec.whatwg.org/multipage/semantics-other.html#case-sensitivity-of-selectors
|
|
1923
|
+
*/
|
|
1924
|
+
const caseInsensitiveAttributes = new Set([
|
|
1925
|
+
"accept",
|
|
1926
|
+
"accept-charset",
|
|
1927
|
+
"align",
|
|
1928
|
+
"alink",
|
|
1929
|
+
"axis",
|
|
1930
|
+
"bgcolor",
|
|
1931
|
+
"charset",
|
|
1932
|
+
"checked",
|
|
1933
|
+
"clear",
|
|
1934
|
+
"codetype",
|
|
1935
|
+
"color",
|
|
1936
|
+
"compact",
|
|
1937
|
+
"declare",
|
|
1938
|
+
"defer",
|
|
1939
|
+
"dir",
|
|
1940
|
+
"direction",
|
|
1941
|
+
"disabled",
|
|
1942
|
+
"enctype",
|
|
1943
|
+
"face",
|
|
1944
|
+
"frame",
|
|
1945
|
+
"hreflang",
|
|
1946
|
+
"http-equiv",
|
|
1947
|
+
"lang",
|
|
1948
|
+
"language",
|
|
1949
|
+
"link",
|
|
1950
|
+
"media",
|
|
1951
|
+
"method",
|
|
1952
|
+
"multiple",
|
|
1953
|
+
"nohref",
|
|
1954
|
+
"noresize",
|
|
1955
|
+
"noshade",
|
|
1956
|
+
"nowrap",
|
|
1957
|
+
"readonly",
|
|
1958
|
+
"rel",
|
|
1959
|
+
"rev",
|
|
1960
|
+
"rules",
|
|
1961
|
+
"scope",
|
|
1962
|
+
"scrolling",
|
|
1963
|
+
"selected",
|
|
1964
|
+
"shape",
|
|
1965
|
+
"target",
|
|
1966
|
+
"text",
|
|
1967
|
+
"type",
|
|
1968
|
+
"valign",
|
|
1969
|
+
"valuetype",
|
|
1970
|
+
"vlink",
|
|
1971
|
+
]);
|
|
1972
|
+
function shouldIgnoreCase(selector, options) {
|
|
1973
|
+
return typeof selector.ignoreCase === "boolean"
|
|
1974
|
+
? selector.ignoreCase
|
|
1975
|
+
: selector.ignoreCase === "quirks"
|
|
1976
|
+
? !!options.quirksMode
|
|
1977
|
+
: !options.xmlMode && caseInsensitiveAttributes.has(selector.name);
|
|
1978
|
+
}
|
|
1979
|
+
/**
|
|
1980
|
+
* Attribute selectors
|
|
1981
|
+
*/
|
|
1982
|
+
const attributeRules = {
|
|
1983
|
+
equals(next, data, options) {
|
|
1984
|
+
const { adapter } = options;
|
|
1985
|
+
const { name } = data;
|
|
1986
|
+
let { value } = data;
|
|
1987
|
+
if (shouldIgnoreCase(data, options)) {
|
|
1988
|
+
value = value.toLowerCase();
|
|
1989
|
+
return (element) => {
|
|
1990
|
+
const attribute = adapter.getAttributeValue(element, name);
|
|
1991
|
+
return (attribute != null &&
|
|
1992
|
+
attribute.length === value.length &&
|
|
1993
|
+
attribute.toLowerCase() === value &&
|
|
1994
|
+
next(element));
|
|
1995
|
+
};
|
|
1996
|
+
}
|
|
1997
|
+
return (element) => adapter.getAttributeValue(element, name) === value && next(element);
|
|
1998
|
+
},
|
|
1999
|
+
hyphen(next, data, options) {
|
|
2000
|
+
const { adapter } = options;
|
|
2001
|
+
const { name } = data;
|
|
2002
|
+
let { value } = data;
|
|
2003
|
+
const { length } = value;
|
|
2004
|
+
if (shouldIgnoreCase(data, options)) {
|
|
2005
|
+
value = value.toLowerCase();
|
|
2006
|
+
return function hyphenIC(element) {
|
|
2007
|
+
const attribute = adapter.getAttributeValue(element, name);
|
|
2008
|
+
return (attribute != null &&
|
|
2009
|
+
(attribute.length === length ||
|
|
2010
|
+
attribute.charAt(length) === "-") &&
|
|
2011
|
+
attribute.substr(0, length).toLowerCase() === value &&
|
|
2012
|
+
next(element));
|
|
2013
|
+
};
|
|
2014
|
+
}
|
|
2015
|
+
return function hyphen(element) {
|
|
2016
|
+
const attribute = adapter.getAttributeValue(element, name);
|
|
2017
|
+
return (attribute != null &&
|
|
2018
|
+
(attribute.length === length ||
|
|
2019
|
+
attribute.charAt(length) === "-") &&
|
|
2020
|
+
attribute.substr(0, length) === value &&
|
|
2021
|
+
next(element));
|
|
2022
|
+
};
|
|
2023
|
+
},
|
|
2024
|
+
element(next, data, options) {
|
|
2025
|
+
const { adapter } = options;
|
|
2026
|
+
const { name, value } = data;
|
|
2027
|
+
if (whitespaceRe.test(value)) {
|
|
2028
|
+
return falseFunc;
|
|
2029
|
+
}
|
|
2030
|
+
const regex = new RegExp(`(?:^|\\s)${escapeRegex(value)}(?:$|\\s)`, shouldIgnoreCase(data, options) ? "i" : "");
|
|
2031
|
+
return function element(node) {
|
|
2032
|
+
const attribute = adapter.getAttributeValue(node, name);
|
|
2033
|
+
return (attribute != null &&
|
|
2034
|
+
attribute.length >= value.length &&
|
|
2035
|
+
regex.test(attribute) &&
|
|
2036
|
+
next(node));
|
|
2037
|
+
};
|
|
2038
|
+
},
|
|
2039
|
+
exists(next, { name }, { adapter }) {
|
|
2040
|
+
return (element) => adapter.hasAttrib(element, name) && next(element);
|
|
2041
|
+
},
|
|
2042
|
+
start(next, data, options) {
|
|
2043
|
+
const { adapter } = options;
|
|
2044
|
+
const { name } = data;
|
|
2045
|
+
let { value } = data;
|
|
2046
|
+
const { length } = value;
|
|
2047
|
+
if (length === 0) {
|
|
2048
|
+
return falseFunc;
|
|
2049
|
+
}
|
|
2050
|
+
if (shouldIgnoreCase(data, options)) {
|
|
2051
|
+
value = value.toLowerCase();
|
|
2052
|
+
return (element) => {
|
|
2053
|
+
const attribute = adapter.getAttributeValue(element, name);
|
|
2054
|
+
return (attribute != null &&
|
|
2055
|
+
attribute.length >= length &&
|
|
2056
|
+
attribute.substr(0, length).toLowerCase() === value &&
|
|
2057
|
+
next(element));
|
|
2058
|
+
};
|
|
2059
|
+
}
|
|
2060
|
+
return (element) => !!adapter.getAttributeValue(element, name)?.startsWith(value) &&
|
|
2061
|
+
next(element);
|
|
2062
|
+
},
|
|
2063
|
+
end(next, data, options) {
|
|
2064
|
+
const { adapter } = options;
|
|
2065
|
+
const { name } = data;
|
|
2066
|
+
let { value } = data;
|
|
2067
|
+
const length = -value.length;
|
|
2068
|
+
if (length === 0) {
|
|
2069
|
+
return falseFunc;
|
|
2070
|
+
}
|
|
2071
|
+
if (shouldIgnoreCase(data, options)) {
|
|
2072
|
+
value = value.toLowerCase();
|
|
2073
|
+
return (element) => adapter
|
|
2074
|
+
.getAttributeValue(element, name)
|
|
2075
|
+
?.substr(length)
|
|
2076
|
+
.toLowerCase() === value && next(element);
|
|
2077
|
+
}
|
|
2078
|
+
return (element) => !!adapter.getAttributeValue(element, name)?.endsWith(value) &&
|
|
2079
|
+
next(element);
|
|
2080
|
+
},
|
|
2081
|
+
any(next, data, options) {
|
|
2082
|
+
const { adapter } = options;
|
|
2083
|
+
const { name, value } = data;
|
|
2084
|
+
if (value === "") {
|
|
2085
|
+
return falseFunc;
|
|
2086
|
+
}
|
|
2087
|
+
if (shouldIgnoreCase(data, options)) {
|
|
2088
|
+
const regex = new RegExp(escapeRegex(value), "i");
|
|
2089
|
+
return function anyIC(element) {
|
|
2090
|
+
const attribute = adapter.getAttributeValue(element, name);
|
|
2091
|
+
return (attribute != null &&
|
|
2092
|
+
attribute.length >= value.length &&
|
|
2093
|
+
regex.test(attribute) &&
|
|
2094
|
+
next(element));
|
|
2095
|
+
};
|
|
2096
|
+
}
|
|
2097
|
+
return (element) => !!adapter.getAttributeValue(element, name)?.includes(value) &&
|
|
2098
|
+
next(element);
|
|
2099
|
+
},
|
|
2100
|
+
not(next, data, options) {
|
|
2101
|
+
const { adapter } = options;
|
|
2102
|
+
const { name } = data;
|
|
2103
|
+
let { value } = data;
|
|
2104
|
+
if (value === "") {
|
|
2105
|
+
return (element) => !!adapter.getAttributeValue(element, name) && next(element);
|
|
2106
|
+
}
|
|
2107
|
+
if (shouldIgnoreCase(data, options)) {
|
|
2108
|
+
value = value.toLowerCase();
|
|
2109
|
+
return (element) => {
|
|
2110
|
+
const attribute = adapter.getAttributeValue(element, name);
|
|
2111
|
+
return ((attribute == null ||
|
|
2112
|
+
attribute.length !== value.length ||
|
|
2113
|
+
attribute.toLowerCase() !== value) &&
|
|
2114
|
+
next(element));
|
|
2115
|
+
};
|
|
2116
|
+
}
|
|
2117
|
+
return (element) => adapter.getAttributeValue(element, name) !== value && next(element);
|
|
2118
|
+
},
|
|
2119
|
+
};
|
|
2120
|
+
|
|
2121
|
+
/**
|
|
2122
|
+
* Find all elements matching the query. If not in XML mode, the query will ignore
|
|
2123
|
+
* the contents of `<template>` elements.
|
|
2124
|
+
* @param query - Function that returns true if the element matches the query.
|
|
2125
|
+
* @param nodes - Nodes to query. If a node is an element, its children will be queried.
|
|
2126
|
+
* @param options - Options for querying the document.
|
|
2127
|
+
* @returns All matching elements.
|
|
2128
|
+
*/
|
|
2129
|
+
function findAll(query, nodes, options) {
|
|
2130
|
+
const { adapter, xmlMode = false } = options;
|
|
2131
|
+
const result = [];
|
|
2132
|
+
/** Stack of the arrays we are looking at. */
|
|
2133
|
+
const nodeStack = [nodes];
|
|
2134
|
+
/** Stack of the indices within the arrays. */
|
|
2135
|
+
const indexStack = [0];
|
|
2136
|
+
for (;;) {
|
|
2137
|
+
// First, check if the current array has any more elements to look at.
|
|
2138
|
+
if (indexStack[0] >= nodeStack[0].length) {
|
|
2139
|
+
// If we have no more arrays to look at, we are done.
|
|
2140
|
+
if (nodeStack.length === 1) {
|
|
2141
|
+
return result;
|
|
2142
|
+
}
|
|
2143
|
+
nodeStack.shift();
|
|
2144
|
+
indexStack.shift();
|
|
2145
|
+
// Loop back to the start to continue with the next array.
|
|
2146
|
+
continue;
|
|
2147
|
+
}
|
|
2148
|
+
const element = nodeStack[0][indexStack[0]++];
|
|
2149
|
+
if (!adapter.isTag(element)) {
|
|
2150
|
+
continue;
|
|
2151
|
+
}
|
|
2152
|
+
if (query(element)) {
|
|
2153
|
+
result.push(element);
|
|
2154
|
+
}
|
|
2155
|
+
if (xmlMode || adapter.getName(element) !== "template") {
|
|
2156
|
+
/*
|
|
2157
|
+
* Add the children to the stack. We are depth-first, so this is
|
|
2158
|
+
* the next array we look at.
|
|
2159
|
+
*/
|
|
2160
|
+
const children = adapter.getChildren(element);
|
|
2161
|
+
if (children.length > 0) {
|
|
2162
|
+
nodeStack.unshift(children);
|
|
2163
|
+
indexStack.unshift(0);
|
|
2164
|
+
}
|
|
2165
|
+
}
|
|
2166
|
+
}
|
|
2167
|
+
}
|
|
2168
|
+
/**
|
|
2169
|
+
* Find the first element matching the query. If not in XML mode, the query will ignore
|
|
2170
|
+
* the contents of `<template>` elements.
|
|
2171
|
+
* @param query - Function that returns true if the element matches the query.
|
|
2172
|
+
* @param nodes - Nodes to query. If a node is an element, its children will be queried.
|
|
2173
|
+
* @param options - Options for querying the document.
|
|
2174
|
+
* @returns The first matching element, or null if there was no match.
|
|
2175
|
+
*/
|
|
2176
|
+
function findOne(query, nodes, options) {
|
|
2177
|
+
const { adapter, xmlMode = false } = options;
|
|
2178
|
+
/** Stack of the arrays we are looking at. */
|
|
2179
|
+
const nodeStack = [nodes];
|
|
2180
|
+
/** Stack of the indices within the arrays. */
|
|
2181
|
+
const indexStack = [0];
|
|
2182
|
+
for (;;) {
|
|
2183
|
+
// First, check if the current array has any more elements to look at.
|
|
2184
|
+
if (indexStack[0] >= nodeStack[0].length) {
|
|
2185
|
+
// If we have no more arrays to look at, we are done.
|
|
2186
|
+
if (nodeStack.length === 1) {
|
|
2187
|
+
return null;
|
|
2188
|
+
}
|
|
2189
|
+
nodeStack.shift();
|
|
2190
|
+
indexStack.shift();
|
|
2191
|
+
// Loop back to the start to continue with the next array.
|
|
2192
|
+
continue;
|
|
2193
|
+
}
|
|
2194
|
+
const element = nodeStack[0][indexStack[0]++];
|
|
2195
|
+
if (!adapter.isTag(element)) {
|
|
2196
|
+
continue;
|
|
2197
|
+
}
|
|
2198
|
+
if (query(element)) {
|
|
2199
|
+
return element;
|
|
2200
|
+
}
|
|
2201
|
+
if (xmlMode || adapter.getName(element) !== "template") {
|
|
2202
|
+
/*
|
|
2203
|
+
* Add the children to the stack. We are depth-first, so this is
|
|
2204
|
+
* the next array we look at.
|
|
2205
|
+
*/
|
|
2206
|
+
const children = adapter.getChildren(element);
|
|
2207
|
+
if (children.length > 0) {
|
|
2208
|
+
nodeStack.unshift(children);
|
|
2209
|
+
indexStack.unshift(0);
|
|
2210
|
+
}
|
|
2211
|
+
}
|
|
2212
|
+
}
|
|
2213
|
+
}
|
|
2214
|
+
/**
|
|
2215
|
+
* Get all element siblings after the provided node.
|
|
2216
|
+
* @param element Element candidate being tested.
|
|
2217
|
+
* @param adapter Adapter implementation used for DOM operations.
|
|
2218
|
+
*/
|
|
2219
|
+
function getNextSiblings(element, adapter) {
|
|
2220
|
+
const siblings = adapter.getSiblings(element);
|
|
2221
|
+
if (siblings.length <= 1) {
|
|
2222
|
+
return [];
|
|
2223
|
+
}
|
|
2224
|
+
const elementIndex = siblings.indexOf(element);
|
|
2225
|
+
if (elementIndex === -1 || elementIndex === siblings.length - 1) {
|
|
2226
|
+
return [];
|
|
2227
|
+
}
|
|
2228
|
+
return siblings.slice(elementIndex + 1).filter(adapter.isTag);
|
|
2229
|
+
}
|
|
2230
|
+
/**
|
|
2231
|
+
* Get the parent element of a node.
|
|
2232
|
+
* @param node Node to inspect.
|
|
2233
|
+
* @param adapter Adapter implementation used for DOM operations.
|
|
2234
|
+
*/
|
|
2235
|
+
function getElementParent(node, adapter) {
|
|
2236
|
+
const parent = adapter.getParent(node);
|
|
2237
|
+
return parent != null && adapter.isTag(parent) ? parent : null;
|
|
2238
|
+
}
|
|
2239
|
+
|
|
2240
|
+
/**
|
|
2241
|
+
* Only text controls can be made read-only, since for other controls (such
|
|
2242
|
+
* as checkboxes and buttons) there is no useful distinction between being
|
|
2243
|
+
* read-only and being disabled.
|
|
2244
|
+
* @see {@link https://html.spec.whatwg.org/multipage/input.html#attr-input-readonly}
|
|
2245
|
+
*/
|
|
2246
|
+
const textControl = "input:is([type=text i],[type=search i],[type=url i],[type=tel i],[type=email i],[type=password i],[type=date i],[type=month i],[type=week i],[type=time i],[type=datetime-local i],[type=number i])";
|
|
2247
|
+
/**
|
|
2248
|
+
* Aliases are pseudos that are expressed as selectors.
|
|
2249
|
+
*/
|
|
2250
|
+
const aliases = {
|
|
2251
|
+
// Links
|
|
2252
|
+
"any-link": ":is(a, area, link)[href]",
|
|
2253
|
+
link: ":any-link:not(:visited)",
|
|
2254
|
+
// Forms
|
|
2255
|
+
// https://html.spec.whatwg.org/multipage/scripting.html#disabled-elements
|
|
2256
|
+
disabled: `:is(
|
|
2257
|
+
:is(button, input, select, textarea, optgroup, option)[disabled],
|
|
2258
|
+
optgroup[disabled] > option,
|
|
2259
|
+
fieldset[disabled]:not(fieldset[disabled] legend:first-of-type *)
|
|
2260
|
+
)`,
|
|
2261
|
+
enabled: ":is(button, input, select, textarea, optgroup, option, fieldset):not(:disabled)",
|
|
2262
|
+
checked: ":is(:is(input[type=radio], input[type=checkbox])[checked], :selected)",
|
|
2263
|
+
required: ":is(input, select, textarea)[required]",
|
|
2264
|
+
optional: ":is(input, select, textarea):not([required])",
|
|
2265
|
+
"read-only": `[readonly]:is(textarea, ${textControl})`,
|
|
2266
|
+
"read-write": `:not([readonly]):is(textarea, ${textControl})`,
|
|
2267
|
+
// JQuery extensions
|
|
2268
|
+
/**
|
|
2269
|
+
* `:selected` matches option elements that have the `selected` attribute,
|
|
2270
|
+
* or are the first option element in a select element that does not have
|
|
2271
|
+
* the `multiple` attribute and does not have any option elements with the
|
|
2272
|
+
* `selected` attribute.
|
|
2273
|
+
* @see https://html.spec.whatwg.org/multipage/form-elements.html#concept-option-selectedness
|
|
2274
|
+
*/
|
|
2275
|
+
selected: "option:is([selected], select:not([multiple]):not(:has(> option[selected])) > :first-of-type)",
|
|
2276
|
+
checkbox: "[type=checkbox]",
|
|
2277
|
+
file: "[type=file]",
|
|
2278
|
+
password: "[type=password]",
|
|
2279
|
+
radio: "[type=radio]",
|
|
2280
|
+
reset: "[type=reset]",
|
|
2281
|
+
image: "[type=image]",
|
|
2282
|
+
submit: "[type=submit]",
|
|
2283
|
+
parent: ":not(:empty)",
|
|
2284
|
+
header: ":is(h1, h2, h3, h4, h5, h6)",
|
|
2285
|
+
button: ":is(button, input[type=button])",
|
|
2286
|
+
input: ":is(input, textarea, select, button)",
|
|
2287
|
+
text: "input:is(:not([type!='']), [type=text])",
|
|
2288
|
+
};
|
|
2289
|
+
|
|
2290
|
+
/**
|
|
2291
|
+
* Returns a function that checks if an elements index matches the given rule
|
|
2292
|
+
* highly optimized to return the fastest solution.
|
|
2293
|
+
* @param parsed A tuple [a, b], as returned by `parse`.
|
|
2294
|
+
* @returns A highly optimized function that returns whether an index matches the nth-check.
|
|
2295
|
+
* @example
|
|
2296
|
+
*
|
|
2297
|
+
* ```js
|
|
2298
|
+
* const check = nthCheck.compile([2, 3]);
|
|
2299
|
+
*
|
|
2300
|
+
* check(0); // `false`
|
|
2301
|
+
* check(1); // `false`
|
|
2302
|
+
* check(2); // `true`
|
|
2303
|
+
* check(3); // `false`
|
|
2304
|
+
* check(4); // `true`
|
|
2305
|
+
* check(5); // `false`
|
|
2306
|
+
* check(6); // `true`
|
|
2307
|
+
* ```
|
|
2308
|
+
*/
|
|
2309
|
+
function compile$1(parsed) {
|
|
2310
|
+
const a = parsed[0];
|
|
2311
|
+
// Subtract 1 from `b`, to convert from one- to zero-indexed.
|
|
2312
|
+
const b = parsed[1] - 1;
|
|
2313
|
+
/*
|
|
2314
|
+
* When `b <= 0`, `a * n` won't be lead to any matches for `a < 0`.
|
|
2315
|
+
* Besides, the specification states that no elements are
|
|
2316
|
+
* matched when `a` and `b` are 0.
|
|
2317
|
+
*
|
|
2318
|
+
* `b < 0` here as we subtracted 1 from `b` above.
|
|
2319
|
+
*/
|
|
2320
|
+
if (b < 0 && a <= 0)
|
|
2321
|
+
return falseFunc;
|
|
2322
|
+
// When `a` is in the range -1..1, it matches any element (so only `b` is checked).
|
|
2323
|
+
if (a === -1)
|
|
2324
|
+
return (index) => index <= b;
|
|
2325
|
+
if (a === 0)
|
|
2326
|
+
return (index) => index === b;
|
|
2327
|
+
// When `b <= 0` and `a === 1`, they match any element.
|
|
2328
|
+
if (a === 1)
|
|
2329
|
+
return b < 0 ? trueFunc : (index) => index >= b;
|
|
2330
|
+
/*
|
|
2331
|
+
* Otherwise, modulo can be used to check if there is a match.
|
|
2332
|
+
*
|
|
2333
|
+
* Modulo doesn't care about the sign, so let's use `a`s absolute value.
|
|
2334
|
+
*/
|
|
2335
|
+
const absA = Math.abs(a);
|
|
2336
|
+
// Get `b mod a`, + a if this is negative.
|
|
2337
|
+
const bModulo = ((b % absA) + absA) % absA;
|
|
2338
|
+
return a > 1
|
|
2339
|
+
? (index) => index >= b && index % absA === bModulo
|
|
2340
|
+
: (index) => index <= b && index % absA === bModulo;
|
|
2341
|
+
}
|
|
2342
|
+
|
|
2343
|
+
// Following http://www.w3.org/TR/css3-selectors/#nth-child-pseudo
|
|
2344
|
+
// Whitespace as per https://www.w3.org/TR/selectors-3/#lex is " \t\r\n\f"
|
|
2345
|
+
const whitespace = new Set([9, 10, 12, 13, 32]);
|
|
2346
|
+
const ZERO = "0".charCodeAt(0);
|
|
2347
|
+
const NINE = "9".charCodeAt(0);
|
|
2348
|
+
/**
|
|
2349
|
+
* Parses an expression.
|
|
2350
|
+
* @param formula CSS nth-formula to parse.
|
|
2351
|
+
* @throws {Error} An `Error` if parsing fails.
|
|
2352
|
+
* @returns An array containing the integer step size and the integer offset of the nth rule.
|
|
2353
|
+
* @example nthCheck.parse("2n+3"); // returns [2, 3]
|
|
2354
|
+
*/
|
|
2355
|
+
function parse(formula) {
|
|
2356
|
+
formula = formula.trim().toLowerCase();
|
|
2357
|
+
switch (formula) {
|
|
2358
|
+
case "even": {
|
|
2359
|
+
return [2, 0];
|
|
2360
|
+
}
|
|
2361
|
+
case "odd": {
|
|
2362
|
+
return [2, 1];
|
|
2363
|
+
}
|
|
2364
|
+
}
|
|
2365
|
+
// Parse [ ['-'|'+']? INTEGER? {N} [ S* ['-'|'+'] S* INTEGER ]?
|
|
2366
|
+
let index = 0;
|
|
2367
|
+
let a = 0;
|
|
2368
|
+
let sign = readSign();
|
|
2369
|
+
let number = readNumber();
|
|
2370
|
+
if (index < formula.length && formula.charAt(index) === "n") {
|
|
2371
|
+
index++;
|
|
2372
|
+
a = sign * (number ?? 1);
|
|
2373
|
+
skipWhitespace();
|
|
2374
|
+
if (index < formula.length) {
|
|
2375
|
+
sign = readSign();
|
|
2376
|
+
skipWhitespace();
|
|
2377
|
+
number = readNumber();
|
|
2378
|
+
}
|
|
2379
|
+
else {
|
|
2380
|
+
sign = number = 0;
|
|
2381
|
+
}
|
|
2382
|
+
}
|
|
2383
|
+
// Throw if there is anything else
|
|
2384
|
+
if (number === null || index < formula.length) {
|
|
2385
|
+
throw new Error(`n-th rule couldn't be parsed ('${formula}')`);
|
|
2386
|
+
}
|
|
2387
|
+
return [a, sign * number];
|
|
2388
|
+
function readSign() {
|
|
2389
|
+
switch (formula.charAt(index)) {
|
|
2390
|
+
case "-": {
|
|
2391
|
+
index++;
|
|
2392
|
+
return -1;
|
|
2393
|
+
}
|
|
2394
|
+
case "+": {
|
|
2395
|
+
index++;
|
|
2396
|
+
break;
|
|
2397
|
+
}
|
|
2398
|
+
}
|
|
2399
|
+
return 1;
|
|
2400
|
+
}
|
|
2401
|
+
function readNumber() {
|
|
2402
|
+
const start = index;
|
|
2403
|
+
let value = 0;
|
|
2404
|
+
while (index < formula.length &&
|
|
2405
|
+
formula.charCodeAt(index) >= ZERO &&
|
|
2406
|
+
formula.charCodeAt(index) <= NINE) {
|
|
2407
|
+
value = value * 10 + (formula.charCodeAt(index) - ZERO);
|
|
2408
|
+
index++;
|
|
2409
|
+
}
|
|
2410
|
+
// Return `null` if we didn't read anything.
|
|
2411
|
+
return index === start ? null : value;
|
|
2412
|
+
}
|
|
2413
|
+
function skipWhitespace() {
|
|
2414
|
+
while (index < formula.length &&
|
|
2415
|
+
whitespace.has(formula.charCodeAt(index))) {
|
|
2416
|
+
index++;
|
|
2417
|
+
}
|
|
2418
|
+
}
|
|
2419
|
+
}
|
|
2420
|
+
|
|
2421
|
+
/**
|
|
2422
|
+
* Parses and compiles a formula to a highly optimized function.
|
|
2423
|
+
* Combination of {@link parse} and {@link compile}.
|
|
2424
|
+
*
|
|
2425
|
+
* If the formula doesn't match any elements,
|
|
2426
|
+
* it returns [`boolbase`](https://github.com/fb55/boolbase)'s `falseFunc`.
|
|
2427
|
+
* Otherwise, a function accepting an _index_ is returned, which returns
|
|
2428
|
+
* whether or not the passed _index_ matches the formula.
|
|
2429
|
+
*
|
|
2430
|
+
* Note: The nth-rule starts counting at `1`, the returned function at `0`.
|
|
2431
|
+
* @param formula The formula to compile.
|
|
2432
|
+
* @example
|
|
2433
|
+
* const check = nthCheck("2n+3");
|
|
2434
|
+
*
|
|
2435
|
+
* check(0); // `false`
|
|
2436
|
+
* check(1); // `false`
|
|
2437
|
+
* check(2); // `true`
|
|
2438
|
+
* check(3); // `false`
|
|
2439
|
+
* check(4); // `true`
|
|
2440
|
+
* check(5); // `false`
|
|
2441
|
+
* check(6); // `true`
|
|
2442
|
+
*/
|
|
2443
|
+
function nthCheck(formula) {
|
|
2444
|
+
return compile$1(parse(formula));
|
|
2445
|
+
}
|
|
2446
|
+
|
|
2447
|
+
/**
|
|
2448
|
+
* Some selectors such as `:contains` and (non-relative) `:has` will only be
|
|
2449
|
+
* able to match elements if their parents match the selector (as they contain
|
|
2450
|
+
* a subset of the elements that the parent contains).
|
|
2451
|
+
*
|
|
2452
|
+
* This function wraps the given `matches` function in a function that caches
|
|
2453
|
+
* the results of the parent elements, so that the `matches` function only
|
|
2454
|
+
* needs to be called once for each subtree.
|
|
2455
|
+
* @param next Matcher to run after this matcher succeeds.
|
|
2456
|
+
* @param options Configuration object for cache behavior.
|
|
2457
|
+
* @param options.adapter Adapter implementation used for DOM access.
|
|
2458
|
+
* @param options.cacheResults Whether results should be memoized by input root.
|
|
2459
|
+
* @param matches Compiled matcher function to wrap with caching.
|
|
2460
|
+
*/
|
|
2461
|
+
function cacheParentResults(next, { adapter, cacheResults }, matches) {
|
|
2462
|
+
if (cacheResults === false || typeof WeakMap === "undefined") {
|
|
2463
|
+
return (element) => next(element) && matches(element);
|
|
2464
|
+
}
|
|
2465
|
+
// Use a cache to avoid re-checking children of an element.
|
|
2466
|
+
// @ts-expect-error `Node` is not extending object
|
|
2467
|
+
const resultCache = new WeakMap();
|
|
2468
|
+
function addResultToCache(element) {
|
|
2469
|
+
const result = matches(element);
|
|
2470
|
+
resultCache.set(element, result);
|
|
2471
|
+
return result;
|
|
2472
|
+
}
|
|
2473
|
+
return function cachedMatcher(element) {
|
|
2474
|
+
if (!next(element)) {
|
|
2475
|
+
return false;
|
|
2476
|
+
}
|
|
2477
|
+
if (resultCache.has(element)) {
|
|
2478
|
+
return resultCache.get(element) ?? false;
|
|
2479
|
+
}
|
|
2480
|
+
// Check all of the element's parents.
|
|
2481
|
+
let node = element;
|
|
2482
|
+
do {
|
|
2483
|
+
const parent = getElementParent(node, adapter);
|
|
2484
|
+
if (parent === null) {
|
|
2485
|
+
return addResultToCache(element);
|
|
2486
|
+
}
|
|
2487
|
+
node = parent;
|
|
2488
|
+
} while (!resultCache.has(node));
|
|
2489
|
+
return resultCache.get(node) ? addResultToCache(element) : false;
|
|
2490
|
+
};
|
|
2491
|
+
}
|
|
2492
|
+
|
|
2493
|
+
/**
|
|
2494
|
+
* Create a copy of options, omitting `context` and `rootFunc`.
|
|
2495
|
+
*
|
|
2496
|
+
* This is used when compiling nested selectors (e.g. inside `:is`, `:not`,
|
|
2497
|
+
* `:nth-child(… of S)`) so that the parent compilation state doesn't leak.
|
|
2498
|
+
*/
|
|
2499
|
+
function copyOptions(options) {
|
|
2500
|
+
// Omit context and rootFunc so parent compilation state doesn't leak.
|
|
2501
|
+
const { context: _, rootFunc: __, ...copied } = options;
|
|
2502
|
+
return copied;
|
|
2503
|
+
}
|
|
2504
|
+
|
|
2505
|
+
/**
|
|
2506
|
+
* RFC 4647 extended filtering with pre-split subtags.
|
|
2507
|
+
* @param tag - Lowercased subtags of the element's language value.
|
|
2508
|
+
* @param range - Lowercased subtags of the language range to match against.
|
|
2509
|
+
*/
|
|
2510
|
+
function extendedFilter(tag, range) {
|
|
2511
|
+
if (range[0] !== "*" && range[0] !== tag[0])
|
|
2512
|
+
return false;
|
|
2513
|
+
let tagIndex = 1;
|
|
2514
|
+
for (let rangeIndex = 1; rangeIndex < range.length; rangeIndex++) {
|
|
2515
|
+
if (range[rangeIndex] === "*")
|
|
2516
|
+
continue;
|
|
2517
|
+
// Skip non-singleton tag subtags until we find a match.
|
|
2518
|
+
while (tagIndex < tag.length && tag[tagIndex] !== range[rangeIndex]) {
|
|
2519
|
+
if (tag[tagIndex++].length <= 1)
|
|
2520
|
+
return false;
|
|
2521
|
+
}
|
|
2522
|
+
if (tagIndex >= tag.length)
|
|
2523
|
+
return false;
|
|
2524
|
+
tagIndex++;
|
|
2525
|
+
}
|
|
2526
|
+
return true;
|
|
2527
|
+
}
|
|
2528
|
+
/** @see {@link https://www.w3.org/TR/selectors-4/#the-nth-child-pseudo} */
|
|
2529
|
+
const nthOfRegex = /^(.+?)\s+of\s+(.+)$/is;
|
|
2530
|
+
function compileNth(reverse, ofType) {
|
|
2531
|
+
return function nth(next, rule, options, context, compileToken) {
|
|
2532
|
+
const { adapter, equals } = options;
|
|
2533
|
+
const ofMatch = ofType ? null : rule.match(nthOfRegex);
|
|
2534
|
+
const nthCheck$1 = nthCheck(ofMatch ? ofMatch[1].trim() : rule);
|
|
2535
|
+
if (nthCheck$1 === falseFunc)
|
|
2536
|
+
return falseFunc;
|
|
2537
|
+
const ofSelector = ofMatch && compileToken
|
|
2538
|
+
? compileToken(parse$1(ofMatch[2].trim()), copyOptions(options), context)
|
|
2539
|
+
: undefined;
|
|
2540
|
+
if (ofSelector === falseFunc)
|
|
2541
|
+
return falseFunc;
|
|
2542
|
+
if (nthCheck$1 === trueFunc && !ofSelector) {
|
|
2543
|
+
return (element) => getElementParent(element, adapter) !== null && next(element);
|
|
2544
|
+
}
|
|
2545
|
+
const shouldCount = ofSelector
|
|
2546
|
+
? (_element, sibling) => ofSelector(sibling)
|
|
2547
|
+
: ofType
|
|
2548
|
+
? (element, sibling) => adapter.getName(sibling) === adapter.getName(element)
|
|
2549
|
+
: trueFunc;
|
|
2550
|
+
if (reverse) {
|
|
2551
|
+
return function nthLast(element) {
|
|
2552
|
+
if (ofSelector && !ofSelector(element))
|
|
2553
|
+
return false;
|
|
2554
|
+
const siblings = adapter.getSiblings(element);
|
|
2555
|
+
let pos = 0;
|
|
2556
|
+
for (let index = siblings.length - 1; index >= 0; index--) {
|
|
2557
|
+
const sibling = siblings[index];
|
|
2558
|
+
if (equals(element, sibling))
|
|
2559
|
+
break;
|
|
2560
|
+
if (adapter.isTag(sibling) && shouldCount(element, sibling))
|
|
2561
|
+
pos++;
|
|
2562
|
+
}
|
|
2563
|
+
return nthCheck$1(pos) && next(element);
|
|
2564
|
+
};
|
|
2565
|
+
}
|
|
2566
|
+
return function nth(element) {
|
|
2567
|
+
if (ofSelector && !ofSelector(element))
|
|
2568
|
+
return false;
|
|
2569
|
+
const siblings = adapter.getSiblings(element);
|
|
2570
|
+
let pos = 0;
|
|
2571
|
+
for (const sibling of siblings) {
|
|
2572
|
+
if (equals(element, sibling))
|
|
2573
|
+
break;
|
|
2574
|
+
if (adapter.isTag(sibling) && shouldCount(element, sibling))
|
|
2575
|
+
pos++;
|
|
2576
|
+
}
|
|
2577
|
+
return nthCheck$1(pos) && next(element);
|
|
2578
|
+
};
|
|
2579
|
+
};
|
|
2580
|
+
}
|
|
2581
|
+
/**
|
|
2582
|
+
* Pre-compiled pseudo filters.
|
|
2583
|
+
*/
|
|
2584
|
+
const filters = {
|
|
2585
|
+
contains(next, text, options) {
|
|
2586
|
+
const { getText } = options.adapter;
|
|
2587
|
+
return cacheParentResults(next, options, (element) => getText(element).includes(text));
|
|
2588
|
+
},
|
|
2589
|
+
icontains(next, text, options) {
|
|
2590
|
+
const itext = text.toLowerCase();
|
|
2591
|
+
const { getText } = options.adapter;
|
|
2592
|
+
return cacheParentResults(next, options, (element) => getText(element).toLowerCase().includes(itext));
|
|
2593
|
+
},
|
|
2594
|
+
// Location specific methods
|
|
2595
|
+
"nth-child": compileNth(false, false),
|
|
2596
|
+
"nth-last-child": compileNth(true, false),
|
|
2597
|
+
"nth-of-type": compileNth(false, true),
|
|
2598
|
+
"nth-last-of-type": compileNth(true, true),
|
|
2599
|
+
// TODO determine the actual root element
|
|
2600
|
+
root(next, _rule, { adapter }) {
|
|
2601
|
+
return (element) => getElementParent(element, adapter) === null && next(element);
|
|
2602
|
+
},
|
|
2603
|
+
scope(next, rule, options, context) {
|
|
2604
|
+
const { equals } = options;
|
|
2605
|
+
if (!context || context.length === 0) {
|
|
2606
|
+
// Equivalent to :root
|
|
2607
|
+
return filters["root"](next, rule, options);
|
|
2608
|
+
}
|
|
2609
|
+
if (context.length === 1) {
|
|
2610
|
+
// NOTE: can't be unpacked, as :has uses this for side-effects
|
|
2611
|
+
return (element) => equals(context[0], element) && next(element);
|
|
2612
|
+
}
|
|
2613
|
+
return (element) => context.includes(element) && next(element);
|
|
2614
|
+
},
|
|
2615
|
+
lang(next, code, { adapter }) {
|
|
2616
|
+
const ranges = code
|
|
2617
|
+
.split(",")
|
|
2618
|
+
.map((r) => r.trim())
|
|
2619
|
+
.filter((r) => r.length > 0)
|
|
2620
|
+
.map((r) => r
|
|
2621
|
+
.replace(/^['"]|['"]$/g, "")
|
|
2622
|
+
.toLowerCase()
|
|
2623
|
+
.split("-"));
|
|
2624
|
+
return function lang(element) {
|
|
2625
|
+
let node = element;
|
|
2626
|
+
while (node != null) {
|
|
2627
|
+
const value = adapter.getAttributeValue(node, "xml:lang") ??
|
|
2628
|
+
adapter.getAttributeValue(node, "lang");
|
|
2629
|
+
if (value != null) {
|
|
2630
|
+
if (!value) {
|
|
2631
|
+
return ranges.some((r) => r[0] === "") && next(element);
|
|
2632
|
+
}
|
|
2633
|
+
const tag = value.toLowerCase().split("-");
|
|
2634
|
+
return (ranges.some((r) => extendedFilter(tag, r)) &&
|
|
2635
|
+
next(element));
|
|
2636
|
+
}
|
|
2637
|
+
const parent = adapter.getParent(node);
|
|
2638
|
+
node =
|
|
2639
|
+
parent != null && adapter.isTag(parent)
|
|
2640
|
+
? parent
|
|
2641
|
+
: null;
|
|
2642
|
+
}
|
|
2643
|
+
return ranges.some((r) => r[0] === "") && next(element);
|
|
2644
|
+
};
|
|
2645
|
+
},
|
|
2646
|
+
hover: dynamicStatePseudo("isHovered"),
|
|
2647
|
+
visited: dynamicStatePseudo("isVisited"),
|
|
2648
|
+
active: dynamicStatePseudo("isActive"),
|
|
2649
|
+
};
|
|
2650
|
+
/**
|
|
2651
|
+
* Dynamic state pseudos. These depend on optional Adapter methods.
|
|
2652
|
+
* @param name The name of the adapter method to call.
|
|
2653
|
+
* @returns Pseudo for the `filters` object.
|
|
2654
|
+
*/
|
|
2655
|
+
function dynamicStatePseudo(name) {
|
|
2656
|
+
return function dynamicPseudo(next, _rule, { adapter }) {
|
|
2657
|
+
const filterFunction = adapter[name];
|
|
2658
|
+
if (typeof filterFunction !== "function") {
|
|
2659
|
+
return falseFunc;
|
|
2660
|
+
}
|
|
2661
|
+
return function active(element) {
|
|
2662
|
+
return filterFunction(element) && next(element);
|
|
2663
|
+
};
|
|
2664
|
+
};
|
|
2665
|
+
}
|
|
2666
|
+
|
|
2667
|
+
/**
|
|
2668
|
+
* CSS limits the characters considered as whitespace to space, tab & line
|
|
2669
|
+
* feed. We add carriage returns as htmlparser2 doesn't normalize them to
|
|
2670
|
+
* line feeds.
|
|
2671
|
+
* @see {@link https://www.w3.org/TR/css-text-3/#white-space}
|
|
2672
|
+
*/
|
|
2673
|
+
const isDocumentWhiteSpace = /^[ \t\r\n]*$/;
|
|
2674
|
+
// While filters are precompiled, pseudos get called when they are needed
|
|
2675
|
+
/** Runtime pseudo selector implementations. */
|
|
2676
|
+
const pseudos = {
|
|
2677
|
+
empty(element, { adapter }) {
|
|
2678
|
+
const children = adapter.getChildren(element);
|
|
2679
|
+
return (
|
|
2680
|
+
// First, make sure the tag does not have any element children.
|
|
2681
|
+
children.every((element) => !adapter.isTag(element)) &&
|
|
2682
|
+
// Then, check that the text content is only whitespace.
|
|
2683
|
+
children.every((element) =>
|
|
2684
|
+
// FIXME: `getText` call is potentially expensive.
|
|
2685
|
+
isDocumentWhiteSpace.test(adapter.getText(element))));
|
|
2686
|
+
},
|
|
2687
|
+
"first-child"(element, { adapter, equals }) {
|
|
2688
|
+
if (adapter.prevElementSibling) {
|
|
2689
|
+
return adapter.prevElementSibling(element) == null;
|
|
2690
|
+
}
|
|
2691
|
+
const firstChild = adapter
|
|
2692
|
+
.getSiblings(element)
|
|
2693
|
+
.find((sibling) => adapter.isTag(sibling));
|
|
2694
|
+
return firstChild != null && equals(element, firstChild);
|
|
2695
|
+
},
|
|
2696
|
+
"last-child"(element, { adapter, equals }) {
|
|
2697
|
+
const siblings = adapter.getSiblings(element);
|
|
2698
|
+
for (let index = siblings.length - 1; index >= 0; index--) {
|
|
2699
|
+
if (equals(element, siblings[index])) {
|
|
2700
|
+
return true;
|
|
2701
|
+
}
|
|
2702
|
+
if (adapter.isTag(siblings[index])) {
|
|
2703
|
+
break;
|
|
2704
|
+
}
|
|
2705
|
+
}
|
|
2706
|
+
return false;
|
|
2707
|
+
},
|
|
2708
|
+
"first-of-type"(element, { adapter, equals }) {
|
|
2709
|
+
const siblings = adapter.getSiblings(element);
|
|
2710
|
+
const elementName = adapter.getName(element);
|
|
2711
|
+
for (const currentSibling of siblings) {
|
|
2712
|
+
if (equals(element, currentSibling)) {
|
|
2713
|
+
return true;
|
|
2714
|
+
}
|
|
2715
|
+
if (adapter.isTag(currentSibling) &&
|
|
2716
|
+
adapter.getName(currentSibling) === elementName) {
|
|
2717
|
+
break;
|
|
2718
|
+
}
|
|
2719
|
+
}
|
|
2720
|
+
return false;
|
|
2721
|
+
},
|
|
2722
|
+
"last-of-type"(element, { adapter, equals }) {
|
|
2723
|
+
const siblings = adapter.getSiblings(element);
|
|
2724
|
+
const elementName = adapter.getName(element);
|
|
2725
|
+
for (let index = siblings.length - 1; index >= 0; index--) {
|
|
2726
|
+
const currentSibling = siblings[index];
|
|
2727
|
+
if (equals(element, currentSibling)) {
|
|
2728
|
+
return true;
|
|
2729
|
+
}
|
|
2730
|
+
if (adapter.isTag(currentSibling) &&
|
|
2731
|
+
adapter.getName(currentSibling) === elementName) {
|
|
2732
|
+
break;
|
|
2733
|
+
}
|
|
2734
|
+
}
|
|
2735
|
+
return false;
|
|
2736
|
+
},
|
|
2737
|
+
"only-of-type"(element, { adapter, equals }) {
|
|
2738
|
+
const elementName = adapter.getName(element);
|
|
2739
|
+
return adapter
|
|
2740
|
+
.getSiblings(element)
|
|
2741
|
+
.every((sibling) => equals(element, sibling) ||
|
|
2742
|
+
!adapter.isTag(sibling) ||
|
|
2743
|
+
adapter.getName(sibling) !== elementName);
|
|
2744
|
+
},
|
|
2745
|
+
"only-child"(element, { adapter, equals }) {
|
|
2746
|
+
return adapter
|
|
2747
|
+
.getSiblings(element)
|
|
2748
|
+
.every((sibling) => equals(element, sibling) || !adapter.isTag(sibling));
|
|
2749
|
+
},
|
|
2750
|
+
};
|
|
2751
|
+
/**
|
|
2752
|
+
* Validate pseudo selector argument arity.
|
|
2753
|
+
* @param pseudoClassCondition Pseudo-function implementation to wrap.
|
|
2754
|
+
* @param name Name of the pseudo selector.
|
|
2755
|
+
* @param subselect Subselector passed to the pseudo-function.
|
|
2756
|
+
* @param argumentIndex Index of the argument parser to apply.
|
|
2757
|
+
*/
|
|
2758
|
+
function verifyPseudoArguments(pseudoClassCondition, name, subselect, argumentIndex) {
|
|
2759
|
+
if (subselect === null) {
|
|
2760
|
+
if (pseudoClassCondition.length > argumentIndex) {
|
|
2761
|
+
throw new Error(`Pseudo-class :${name} requires an argument`);
|
|
2762
|
+
}
|
|
2763
|
+
}
|
|
2764
|
+
else if (pseudoClassCondition.length === argumentIndex) {
|
|
2765
|
+
throw new Error(`Pseudo-class :${name} doesn't have any arguments`);
|
|
2766
|
+
}
|
|
2767
|
+
}
|
|
2768
|
+
|
|
2769
|
+
/**
|
|
2770
|
+
* Check whether a selector token performs traversal.
|
|
2771
|
+
* @param token Selector token(s) to compile.
|
|
2772
|
+
*/
|
|
2773
|
+
function isTraversal(token) {
|
|
2774
|
+
return token.type === "_flexibleDescendant" || isTraversal$1(token);
|
|
2775
|
+
}
|
|
2776
|
+
/**
|
|
2777
|
+
* Sort the parts of the passed selector, as there is potential for
|
|
2778
|
+
* optimization (some types of selectors are faster than others).
|
|
2779
|
+
* @param array Selector to sort
|
|
2780
|
+
*/
|
|
2781
|
+
function sortRules(array) {
|
|
2782
|
+
const ratings = array.map(getQuality);
|
|
2783
|
+
for (let index = 1; index < array.length; index++) {
|
|
2784
|
+
const procNew = ratings[index];
|
|
2785
|
+
if (procNew < 0) {
|
|
2786
|
+
continue;
|
|
2787
|
+
}
|
|
2788
|
+
// Use insertion sort to move the token to the correct position.
|
|
2789
|
+
for (let currentIndex = index; currentIndex > 0 && procNew < ratings[currentIndex - 1]; currentIndex--) {
|
|
2790
|
+
const token = array[currentIndex];
|
|
2791
|
+
array[currentIndex] = array[currentIndex - 1];
|
|
2792
|
+
array[currentIndex - 1] = token;
|
|
2793
|
+
ratings[currentIndex] = ratings[currentIndex - 1];
|
|
2794
|
+
ratings[currentIndex - 1] = procNew;
|
|
2795
|
+
}
|
|
2796
|
+
}
|
|
2797
|
+
}
|
|
2798
|
+
function getAttributeQuality(token) {
|
|
2799
|
+
switch (token.action) {
|
|
2800
|
+
case AttributeAction.Exists: {
|
|
2801
|
+
return 10;
|
|
2802
|
+
}
|
|
2803
|
+
case AttributeAction.Equals: {
|
|
2804
|
+
// Prefer ID selectors (eg. #ID)
|
|
2805
|
+
return token.name === "id" ? 9 : 8;
|
|
2806
|
+
}
|
|
2807
|
+
case AttributeAction.Not: {
|
|
2808
|
+
return 7;
|
|
2809
|
+
}
|
|
2810
|
+
case AttributeAction.Start: {
|
|
2811
|
+
return 6;
|
|
2812
|
+
}
|
|
2813
|
+
case AttributeAction.End: {
|
|
2814
|
+
return 6;
|
|
2815
|
+
}
|
|
2816
|
+
case AttributeAction.Any: {
|
|
2817
|
+
return 5;
|
|
2818
|
+
}
|
|
2819
|
+
case AttributeAction.Hyphen: {
|
|
2820
|
+
return 4;
|
|
2821
|
+
}
|
|
2822
|
+
case AttributeAction.Element: {
|
|
2823
|
+
return 3;
|
|
2824
|
+
}
|
|
2825
|
+
}
|
|
2826
|
+
}
|
|
2827
|
+
/**
|
|
2828
|
+
* Determine the quality of the passed token. The higher the number, the
|
|
2829
|
+
* faster the token is to execute.
|
|
2830
|
+
* @param token Token to get the quality of.
|
|
2831
|
+
* @returns The token's quality.
|
|
2832
|
+
*/
|
|
2833
|
+
function getQuality(token) {
|
|
2834
|
+
switch (token.type) {
|
|
2835
|
+
case SelectorType.Universal: {
|
|
2836
|
+
return 50;
|
|
2837
|
+
}
|
|
2838
|
+
case SelectorType.Tag: {
|
|
2839
|
+
return 30;
|
|
2840
|
+
}
|
|
2841
|
+
case SelectorType.Attribute: {
|
|
2842
|
+
return Math.floor(getAttributeQuality(token) /
|
|
2843
|
+
// `ignoreCase` adds some overhead, half the result if applicable.
|
|
2844
|
+
(token.ignoreCase ? 2 : 1));
|
|
2845
|
+
}
|
|
2846
|
+
case SelectorType.Pseudo: {
|
|
2847
|
+
return token.data
|
|
2848
|
+
? token.name === "has" ||
|
|
2849
|
+
token.name === "contains" ||
|
|
2850
|
+
token.name === "icontains"
|
|
2851
|
+
? // Expensive in any case — run as late as possible.
|
|
2852
|
+
0
|
|
2853
|
+
: Array.isArray(token.data)
|
|
2854
|
+
? // Eg. `:is`, `:not`
|
|
2855
|
+
Math.max(
|
|
2856
|
+
// If we have traversals, try to avoid executing this selector
|
|
2857
|
+
0, Math.min(...token.data.map((d) => Math.min(...d.map(getQuality)))))
|
|
2858
|
+
: 2
|
|
2859
|
+
: 3;
|
|
2860
|
+
}
|
|
2861
|
+
default: {
|
|
2862
|
+
return -1;
|
|
2863
|
+
}
|
|
2864
|
+
}
|
|
2865
|
+
}
|
|
2866
|
+
/**
|
|
2867
|
+
* Check whether a token or nested token includes `:scope`.
|
|
2868
|
+
* @param t Selector token under inspection.
|
|
2869
|
+
*/
|
|
2870
|
+
function includesScopePseudo(t) {
|
|
2871
|
+
return (t.type === SelectorType.Pseudo &&
|
|
2872
|
+
(t.name === "scope" ||
|
|
2873
|
+
(Array.isArray(t.data) &&
|
|
2874
|
+
t.data.some((data) => data.some(includesScopePseudo)))));
|
|
2875
|
+
}
|
|
2876
|
+
|
|
2877
|
+
/** Used as a placeholder for :has. Will be replaced with the actual element. */
|
|
2878
|
+
const PLACEHOLDER_ELEMENT = {};
|
|
2879
|
+
/**
|
|
2880
|
+
* Check if the selector has any properties that rely on the current element.
|
|
2881
|
+
* If not, we can cache the result of the selector.
|
|
2882
|
+
*
|
|
2883
|
+
* We can't cache selectors that start with a traversal (e.g. `>`, `+`, `~`),
|
|
2884
|
+
* or include a `:scope`.
|
|
2885
|
+
* @param selector - The selector to check.
|
|
2886
|
+
* @returns Whether the selector has any properties that rely on the current element.
|
|
2887
|
+
*/
|
|
2888
|
+
function hasDependsOnCurrentElement(selector) {
|
|
2889
|
+
return selector.some((sel) => sel.length > 0 &&
|
|
2890
|
+
(isTraversal(sel[0]) || sel.some(includesScopePseudo)));
|
|
2891
|
+
}
|
|
2892
|
+
const is$1 = (next, token, options, context, compileToken) => {
|
|
2893
|
+
const compiledToken = compileToken(token, copyOptions(options), context);
|
|
2894
|
+
return compiledToken === trueFunc
|
|
2895
|
+
? next
|
|
2896
|
+
: compiledToken === falseFunc
|
|
2897
|
+
? falseFunc
|
|
2898
|
+
: (element) => compiledToken(element) && next(element);
|
|
2899
|
+
};
|
|
2900
|
+
/*
|
|
2901
|
+
* :not, :has, :is, :matches and :where have to compile selectors
|
|
2902
|
+
* doing this in src/pseudos.ts would lead to circular dependencies,
|
|
2903
|
+
* so we add them here
|
|
2904
|
+
*/
|
|
2905
|
+
/** Pseudo selectors that compile nested selectors. */
|
|
2906
|
+
const subselects = {
|
|
2907
|
+
is: is$1,
|
|
2908
|
+
/**
|
|
2909
|
+
* `:matches` and `:where` are aliases for `:is`.
|
|
2910
|
+
*/
|
|
2911
|
+
matches: is$1,
|
|
2912
|
+
where: is$1,
|
|
2913
|
+
not(next, token, options, context, compileToken) {
|
|
2914
|
+
const compiledToken = compileToken(token, copyOptions(options), context);
|
|
2915
|
+
return compiledToken === falseFunc
|
|
2916
|
+
? next
|
|
2917
|
+
: compiledToken === trueFunc
|
|
2918
|
+
? falseFunc
|
|
2919
|
+
: (element) => !compiledToken(element) && next(element);
|
|
2920
|
+
},
|
|
2921
|
+
has(next, subselect, options, _context, compileToken) {
|
|
2922
|
+
const { adapter } = options;
|
|
2923
|
+
const copiedOptions = copyOptions(options);
|
|
2924
|
+
copiedOptions.relativeSelector = true;
|
|
2925
|
+
const context = subselect.some((s) => s.some(isTraversal))
|
|
2926
|
+
? // Used as a placeholder. Will be replaced with the actual element.
|
|
2927
|
+
[PLACEHOLDER_ELEMENT]
|
|
2928
|
+
: undefined;
|
|
2929
|
+
const skipCache = hasDependsOnCurrentElement(subselect);
|
|
2930
|
+
const compiled = compileToken(subselect, copiedOptions, context);
|
|
2931
|
+
if (compiled === falseFunc) {
|
|
2932
|
+
return falseFunc;
|
|
2933
|
+
}
|
|
2934
|
+
// If `compiled` is `trueFunc`, we can skip this.
|
|
2935
|
+
if (context && compiled !== trueFunc) {
|
|
2936
|
+
return skipCache
|
|
2937
|
+
? (element) => {
|
|
2938
|
+
if (!next(element)) {
|
|
2939
|
+
return false;
|
|
2940
|
+
}
|
|
2941
|
+
context[0] = element;
|
|
2942
|
+
const childs = adapter.getChildren(element);
|
|
2943
|
+
return (findOne(compiled, compiled.shouldTestNextSiblings
|
|
2944
|
+
? [
|
|
2945
|
+
...childs,
|
|
2946
|
+
...getNextSiblings(element, adapter),
|
|
2947
|
+
]
|
|
2948
|
+
: childs, options) !== null);
|
|
2949
|
+
}
|
|
2950
|
+
: cacheParentResults(next, options, (element) => {
|
|
2951
|
+
context[0] = element;
|
|
2952
|
+
return (findOne(compiled, adapter.getChildren(element), options) !== null);
|
|
2953
|
+
});
|
|
2954
|
+
}
|
|
2955
|
+
const hasOne = (element) => findOne(compiled, adapter.getChildren(element), options) !== null;
|
|
2956
|
+
return skipCache
|
|
2957
|
+
? (element) => next(element) && hasOne(element)
|
|
2958
|
+
: cacheParentResults(next, options, hasOne);
|
|
2959
|
+
},
|
|
2960
|
+
};
|
|
2961
|
+
|
|
2962
|
+
/*
|
|
2963
|
+
* Pseudo selectors
|
|
2964
|
+
*
|
|
2965
|
+
* Pseudo selectors are available in three forms:
|
|
2966
|
+
*
|
|
2967
|
+
* 1. Filters are called when the selector is compiled and return a function
|
|
2968
|
+
* that has to return either false, or the results of `next()`.
|
|
2969
|
+
* 2. Pseudos are called on execution. They have to return a boolean.
|
|
2970
|
+
* 3. Subselects work like filters, but have an embedded selector that will be run separately.
|
|
2971
|
+
*
|
|
2972
|
+
* Filters are great if you want to do some pre-processing, or change the call order
|
|
2973
|
+
* of `next()` and your code.
|
|
2974
|
+
* Pseudos should be used to implement simple checks.
|
|
2975
|
+
*/
|
|
2976
|
+
/**
|
|
2977
|
+
* Compile a pseudo selector into an executable query function.
|
|
2978
|
+
* @param next Matcher to run after this matcher succeeds.
|
|
2979
|
+
* @param selector Selector used to match elements.
|
|
2980
|
+
* @param options Options that control this operation.
|
|
2981
|
+
* @param context Context nodes used to scope selector matching.
|
|
2982
|
+
* @param compileToken Function used to compile nested selector tokens.
|
|
2983
|
+
*/
|
|
2984
|
+
function compilePseudoSelector(next, selector, options, context, compileToken) {
|
|
2985
|
+
const { name, data } = selector;
|
|
2986
|
+
if (Array.isArray(data)) {
|
|
2987
|
+
if (!(name in subselects)) {
|
|
2988
|
+
throw new Error(`Unknown pseudo-class :${name}(${data})`);
|
|
2989
|
+
}
|
|
2990
|
+
return subselects[name](next, data, options, context, compileToken);
|
|
2991
|
+
}
|
|
2992
|
+
const userPseudo = options.pseudos?.[name];
|
|
2993
|
+
const stringPseudo = typeof userPseudo === "string" ? userPseudo : aliases[name];
|
|
2994
|
+
if (typeof stringPseudo === "string") {
|
|
2995
|
+
if (data != null) {
|
|
2996
|
+
throw new Error(`Pseudo ${name} doesn't have any arguments`);
|
|
2997
|
+
}
|
|
2998
|
+
// The alias has to be parsed here, to make sure options are respected.
|
|
2999
|
+
const alias = parse$1(stringPseudo);
|
|
3000
|
+
return subselects["is"](next, alias, options, context, compileToken);
|
|
3001
|
+
}
|
|
3002
|
+
if (typeof userPseudo === "function") {
|
|
3003
|
+
verifyPseudoArguments(userPseudo, name, data, 1);
|
|
3004
|
+
return (element) => userPseudo(element, data) && next(element);
|
|
3005
|
+
}
|
|
3006
|
+
if (name in filters) {
|
|
3007
|
+
return filters[name](next, data, options, context, compileToken);
|
|
3008
|
+
}
|
|
3009
|
+
if (name in pseudos) {
|
|
3010
|
+
const pseudo = pseudos[name];
|
|
3011
|
+
verifyPseudoArguments(pseudo, name, data, 2);
|
|
3012
|
+
return (element) => pseudo(element, options, data) && next(element);
|
|
3013
|
+
}
|
|
3014
|
+
throw new Error(`Unknown pseudo-class :${name}`);
|
|
3015
|
+
}
|
|
3016
|
+
|
|
3017
|
+
/*
|
|
3018
|
+
* All available rules
|
|
3019
|
+
*/
|
|
3020
|
+
/**
|
|
3021
|
+
* Compile a single selector token.
|
|
3022
|
+
* @param next Matcher to run after this matcher succeeds.
|
|
3023
|
+
* @param selector Selector used to match elements.
|
|
3024
|
+
* @param options Options that control this operation.
|
|
3025
|
+
* @param context Context nodes used to scope selector matching.
|
|
3026
|
+
* @param compileToken Function used to compile nested selector tokens.
|
|
3027
|
+
* @param hasExpensiveSubselector Whether the selector contains expensive subselectors.
|
|
3028
|
+
*/
|
|
3029
|
+
function compileGeneralSelector(next, selector, options, context, compileToken, hasExpensiveSubselector) {
|
|
3030
|
+
const { adapter, equals, cacheResults } = options;
|
|
3031
|
+
switch (selector.type) {
|
|
3032
|
+
case SelectorType.PseudoElement: {
|
|
3033
|
+
throw new Error("Pseudo-elements are not supported by css-select");
|
|
3034
|
+
}
|
|
3035
|
+
case SelectorType.ColumnCombinator: {
|
|
3036
|
+
throw new Error("Column combinators are not yet supported by css-select");
|
|
3037
|
+
}
|
|
3038
|
+
case SelectorType.Attribute: {
|
|
3039
|
+
if (selector.namespace != null) {
|
|
3040
|
+
throw new Error("Namespaced attributes are not yet supported by css-select");
|
|
3041
|
+
}
|
|
3042
|
+
if (!options.xmlMode || options.lowerCaseAttributeNames) {
|
|
3043
|
+
selector.name = selector.name.toLowerCase();
|
|
3044
|
+
}
|
|
3045
|
+
return attributeRules[selector.action](next, selector, options);
|
|
3046
|
+
}
|
|
3047
|
+
case SelectorType.Pseudo: {
|
|
3048
|
+
return compilePseudoSelector(next, selector, options, context, compileToken);
|
|
3049
|
+
}
|
|
3050
|
+
// Tags
|
|
3051
|
+
case SelectorType.Tag: {
|
|
3052
|
+
if (selector.namespace != null) {
|
|
3053
|
+
throw new Error("Namespaced tag names are not yet supported by css-select");
|
|
3054
|
+
}
|
|
3055
|
+
let { name } = selector;
|
|
3056
|
+
if (!options.xmlMode || options.lowerCaseTags) {
|
|
3057
|
+
name = name.toLowerCase();
|
|
3058
|
+
}
|
|
3059
|
+
return function tag(element) {
|
|
3060
|
+
return adapter.getName(element) === name && next(element);
|
|
3061
|
+
};
|
|
3062
|
+
}
|
|
3063
|
+
// Traversal
|
|
3064
|
+
case SelectorType.Descendant: {
|
|
3065
|
+
if (!hasExpensiveSubselector ||
|
|
3066
|
+
cacheResults === false ||
|
|
3067
|
+
typeof WeakMap === "undefined") {
|
|
3068
|
+
return function descendant(element) {
|
|
3069
|
+
let current = element;
|
|
3070
|
+
while ((current = getElementParent(current, adapter))) {
|
|
3071
|
+
if (next(current)) {
|
|
3072
|
+
return true;
|
|
3073
|
+
}
|
|
3074
|
+
}
|
|
3075
|
+
return false;
|
|
3076
|
+
};
|
|
3077
|
+
}
|
|
3078
|
+
const resultCache = new WeakMap();
|
|
3079
|
+
return function cachedDescendant(element) {
|
|
3080
|
+
let current = element;
|
|
3081
|
+
let result;
|
|
3082
|
+
while ((current = getElementParent(current, adapter))) {
|
|
3083
|
+
const cached = resultCache.get(current);
|
|
3084
|
+
if (cached === undefined) {
|
|
3085
|
+
result ??= { matches: false };
|
|
3086
|
+
result.matches = next(current);
|
|
3087
|
+
resultCache.set(current, result);
|
|
3088
|
+
if (result.matches) {
|
|
3089
|
+
return true;
|
|
3090
|
+
}
|
|
3091
|
+
}
|
|
3092
|
+
else {
|
|
3093
|
+
if (result) {
|
|
3094
|
+
result.matches = cached.matches;
|
|
3095
|
+
}
|
|
3096
|
+
return cached.matches;
|
|
3097
|
+
}
|
|
3098
|
+
}
|
|
3099
|
+
return false;
|
|
3100
|
+
};
|
|
3101
|
+
}
|
|
3102
|
+
case "_flexibleDescendant": {
|
|
3103
|
+
// Include element itself, only used while querying an array
|
|
3104
|
+
return function flexibleDescendant(element) {
|
|
3105
|
+
let current = element;
|
|
3106
|
+
do {
|
|
3107
|
+
if (next(current)) {
|
|
3108
|
+
return true;
|
|
3109
|
+
}
|
|
3110
|
+
current = getElementParent(current, adapter);
|
|
3111
|
+
} while (current);
|
|
3112
|
+
return false;
|
|
3113
|
+
};
|
|
3114
|
+
}
|
|
3115
|
+
case SelectorType.Parent: {
|
|
3116
|
+
return function parent(element) {
|
|
3117
|
+
return adapter
|
|
3118
|
+
.getChildren(element)
|
|
3119
|
+
.some((element) => adapter.isTag(element) && next(element));
|
|
3120
|
+
};
|
|
3121
|
+
}
|
|
3122
|
+
case SelectorType.Child: {
|
|
3123
|
+
return function child(element) {
|
|
3124
|
+
const parent = getElementParent(element, adapter);
|
|
3125
|
+
return parent !== null && next(parent);
|
|
3126
|
+
};
|
|
3127
|
+
}
|
|
3128
|
+
case SelectorType.Sibling: {
|
|
3129
|
+
return function sibling(element) {
|
|
3130
|
+
const siblings = adapter.getSiblings(element);
|
|
3131
|
+
for (const currentSibling of siblings) {
|
|
3132
|
+
if (equals(element, currentSibling)) {
|
|
3133
|
+
break;
|
|
3134
|
+
}
|
|
3135
|
+
if (adapter.isTag(currentSibling) && next(currentSibling)) {
|
|
3136
|
+
return true;
|
|
3137
|
+
}
|
|
3138
|
+
}
|
|
3139
|
+
return false;
|
|
3140
|
+
};
|
|
3141
|
+
}
|
|
3142
|
+
case SelectorType.Adjacent: {
|
|
3143
|
+
if (adapter.prevElementSibling) {
|
|
3144
|
+
return function adjacent(element) {
|
|
3145
|
+
// biome-ignore lint/style/noNonNullAssertion: checked by if statement
|
|
3146
|
+
const previous = adapter.prevElementSibling(element);
|
|
3147
|
+
return previous != null && next(previous);
|
|
3148
|
+
};
|
|
3149
|
+
}
|
|
3150
|
+
return function adjacent(element) {
|
|
3151
|
+
const siblings = adapter.getSiblings(element);
|
|
3152
|
+
let lastElement;
|
|
3153
|
+
for (const currentSibling of siblings) {
|
|
3154
|
+
if (equals(element, currentSibling)) {
|
|
3155
|
+
break;
|
|
3156
|
+
}
|
|
3157
|
+
if (adapter.isTag(currentSibling)) {
|
|
3158
|
+
lastElement = currentSibling;
|
|
3159
|
+
}
|
|
3160
|
+
}
|
|
3161
|
+
return !!lastElement && next(lastElement);
|
|
3162
|
+
};
|
|
3163
|
+
}
|
|
3164
|
+
case SelectorType.Universal: {
|
|
3165
|
+
if (selector.namespace != null && selector.namespace !== "*") {
|
|
3166
|
+
throw new Error("Namespaced universal selectors are not yet supported by css-select");
|
|
3167
|
+
}
|
|
3168
|
+
return next;
|
|
3169
|
+
}
|
|
3170
|
+
}
|
|
3171
|
+
}
|
|
3172
|
+
|
|
3173
|
+
const DESCENDANT_TOKEN = { type: SelectorType.Descendant };
|
|
3174
|
+
const FLEXIBLE_DESCENDANT_TOKEN = {
|
|
3175
|
+
type: "_flexibleDescendant",
|
|
3176
|
+
};
|
|
3177
|
+
const SCOPE_TOKEN = {
|
|
3178
|
+
type: SelectorType.Pseudo,
|
|
3179
|
+
name: "scope",
|
|
3180
|
+
data: null,
|
|
3181
|
+
};
|
|
3182
|
+
/*
|
|
3183
|
+
* CSS 4 Spec (Draft): 3.4.1. Absolutizing a Relative Selector
|
|
3184
|
+
* http://www.w3.org/TR/selectors4/#absolutizing
|
|
3185
|
+
*/
|
|
3186
|
+
function absolutize(token, { adapter }, context) {
|
|
3187
|
+
// TODO Use better check if the context is a document
|
|
3188
|
+
const hasContext = !!context?.every((element) => element === PLACEHOLDER_ELEMENT ||
|
|
3189
|
+
(adapter.isTag(element) &&
|
|
3190
|
+
getElementParent(element, adapter) !== null));
|
|
3191
|
+
for (const t of token) {
|
|
3192
|
+
if (t.length > 0 &&
|
|
3193
|
+
isTraversal(t[0]) &&
|
|
3194
|
+
t[0].type !== SelectorType.Descendant) ;
|
|
3195
|
+
else if (hasContext && !t.some(includesScopePseudo)) {
|
|
3196
|
+
t.unshift(DESCENDANT_TOKEN);
|
|
3197
|
+
}
|
|
3198
|
+
else {
|
|
3199
|
+
continue;
|
|
3200
|
+
}
|
|
3201
|
+
t.unshift(SCOPE_TOKEN);
|
|
3202
|
+
}
|
|
3203
|
+
}
|
|
3204
|
+
/**
|
|
3205
|
+
* Compile a parsed selector token into an executable query function.
|
|
3206
|
+
* @param token Selector token(s) to compile.
|
|
3207
|
+
* @param options Options that control this operation.
|
|
3208
|
+
* @param compilationContext Compilation context for relative selector handling.
|
|
3209
|
+
*/
|
|
3210
|
+
function compileToken(token, options, compilationContext) {
|
|
3211
|
+
for (const rules of token) {
|
|
3212
|
+
sortRules(rules);
|
|
3213
|
+
}
|
|
3214
|
+
const { context = compilationContext, rootFunc: rootFunction = trueFunc, } = options;
|
|
3215
|
+
const isArrayContext = Array.isArray(context);
|
|
3216
|
+
const finalContext = context && (Array.isArray(context) ? context : [context]);
|
|
3217
|
+
// Check if the selector is relative
|
|
3218
|
+
if (options.relativeSelector !== false) {
|
|
3219
|
+
absolutize(token, options, finalContext);
|
|
3220
|
+
}
|
|
3221
|
+
else if (token.some((t) => t.length > 0 && isTraversal(t[0]))) {
|
|
3222
|
+
throw new Error("Relative selectors are not allowed when the `relativeSelector` option is disabled");
|
|
3223
|
+
}
|
|
3224
|
+
let shouldTestNextSiblings = false;
|
|
3225
|
+
let query = falseFunc;
|
|
3226
|
+
combineLoop: for (const rules of token) {
|
|
3227
|
+
if (rules.length >= 2) {
|
|
3228
|
+
const [first, second] = rules;
|
|
3229
|
+
if (first.type !== SelectorType.Pseudo || first.name !== "scope") ;
|
|
3230
|
+
else if (isArrayContext &&
|
|
3231
|
+
second.type === SelectorType.Descendant) {
|
|
3232
|
+
rules[1] = FLEXIBLE_DESCENDANT_TOKEN;
|
|
3233
|
+
}
|
|
3234
|
+
else if (second.type === SelectorType.Adjacent ||
|
|
3235
|
+
second.type === SelectorType.Sibling) {
|
|
3236
|
+
shouldTestNextSiblings = true;
|
|
3237
|
+
}
|
|
3238
|
+
}
|
|
3239
|
+
let next = rootFunction;
|
|
3240
|
+
let hasExpensiveSubselector = false;
|
|
3241
|
+
for (const rule of rules) {
|
|
3242
|
+
next = compileGeneralSelector(next, rule, options, finalContext, compileToken, hasExpensiveSubselector);
|
|
3243
|
+
const quality = getQuality(rule);
|
|
3244
|
+
if (quality === 0) {
|
|
3245
|
+
hasExpensiveSubselector = true;
|
|
3246
|
+
}
|
|
3247
|
+
// If the sub-selector won't match any elements, skip it.
|
|
3248
|
+
if (next === falseFunc) {
|
|
3249
|
+
continue combineLoop;
|
|
3250
|
+
}
|
|
3251
|
+
}
|
|
3252
|
+
// If we have a function that always returns true, we can stop here.
|
|
3253
|
+
if (next === rootFunction) {
|
|
3254
|
+
return rootFunction;
|
|
3255
|
+
}
|
|
3256
|
+
query = query === falseFunc ? next : or(query, next);
|
|
3257
|
+
}
|
|
3258
|
+
query.shouldTestNextSiblings = shouldTestNextSiblings;
|
|
3259
|
+
return query;
|
|
3260
|
+
}
|
|
3261
|
+
function or(a, b) {
|
|
3262
|
+
return (element) => a(element) || b(element);
|
|
3263
|
+
}
|
|
3264
|
+
|
|
3265
|
+
const defaultEquals = (a, b) => a === b;
|
|
3266
|
+
const defaultOptions = {
|
|
3267
|
+
adapter: { ...DomUtils, isTag },
|
|
3268
|
+
equals: defaultEquals,
|
|
3269
|
+
};
|
|
3270
|
+
function convertOptionFormats(options) {
|
|
3271
|
+
/*
|
|
3272
|
+
* We force one format of options to the other one.
|
|
3273
|
+
*/
|
|
3274
|
+
// @ts-expect-error Default options may have incompatible `Node` / `ElementNode`.
|
|
3275
|
+
const finalOptions = options ?? defaultOptions;
|
|
3276
|
+
// @ts-expect-error Same as above.
|
|
3277
|
+
finalOptions.adapter ??= defaultOptions.adapter;
|
|
3278
|
+
// @ts-expect-error `equals` does not exist on `Options`
|
|
3279
|
+
finalOptions.equals ??= finalOptions.adapter?.equals ?? defaultEquals;
|
|
3280
|
+
return finalOptions;
|
|
3281
|
+
}
|
|
3282
|
+
/**
|
|
3283
|
+
* Compiles a selector to an executable function.
|
|
3284
|
+
*
|
|
3285
|
+
* The returned function checks if each passed node is an element. Use
|
|
3286
|
+
* `_compileUnsafe` to skip this check.
|
|
3287
|
+
* @param selector Selector to compile.
|
|
3288
|
+
* @param options Compilation options.
|
|
3289
|
+
* @param context Optional context for the selector.
|
|
3290
|
+
*/
|
|
3291
|
+
function compile(selector, options, context) {
|
|
3292
|
+
const convertedOptions = convertOptionFormats(options);
|
|
3293
|
+
const next = _compileUnsafe(selector, convertedOptions, context);
|
|
3294
|
+
return next === falseFunc
|
|
3295
|
+
? falseFunc
|
|
3296
|
+
: (element) => convertedOptions.adapter.isTag(element) && next(element);
|
|
3297
|
+
}
|
|
3298
|
+
/**
|
|
3299
|
+
* Like `compile`, but does not add a check if elements are tags.
|
|
3300
|
+
* @param selector Selector used to match elements.
|
|
3301
|
+
* @param options Options that control this operation.
|
|
3302
|
+
* @param context Context nodes used to scope selector matching.
|
|
3303
|
+
*/
|
|
3304
|
+
function _compileUnsafe(selector, options, context) {
|
|
3305
|
+
return compileToken(typeof selector === "string" ? parse$1(selector) : selector, convertOptionFormats(options), context);
|
|
3306
|
+
}
|
|
3307
|
+
function getSelectorFunction(searchFunction) {
|
|
3308
|
+
return function select(query, elements, options) {
|
|
3309
|
+
const convertedOptions = convertOptionFormats(options);
|
|
3310
|
+
if (typeof query !== "function") {
|
|
3311
|
+
query = _compileUnsafe(query, convertedOptions, elements);
|
|
3312
|
+
}
|
|
3313
|
+
const filteredElements = prepareContext(elements, convertedOptions.adapter, query.shouldTestNextSiblings);
|
|
3314
|
+
return searchFunction(query, filteredElements, convertedOptions);
|
|
3315
|
+
};
|
|
3316
|
+
}
|
|
3317
|
+
/**
|
|
3318
|
+
* Normalize a query context and optionally include next siblings.
|
|
3319
|
+
* @param elements Elements to test against sibling-dependent selectors.
|
|
3320
|
+
* @param adapter Adapter implementation used for DOM operations.
|
|
3321
|
+
* @param shouldTestNextSiblings Whether sibling combinators should include following siblings.
|
|
3322
|
+
*/
|
|
3323
|
+
function prepareContext(elements, adapter, shouldTestNextSiblings = false) {
|
|
3324
|
+
/*
|
|
3325
|
+
* Add siblings if the query requires them.
|
|
3326
|
+
* See https://github.com/fb55/css-select/pull/43#issuecomment-225414692
|
|
3327
|
+
*/
|
|
3328
|
+
if (shouldTestNextSiblings) {
|
|
3329
|
+
elements = appendNextSiblings(elements, adapter);
|
|
3330
|
+
}
|
|
3331
|
+
return Array.isArray(elements)
|
|
3332
|
+
? adapter.removeSubsets(elements)
|
|
3333
|
+
: adapter.getChildren(elements);
|
|
3334
|
+
}
|
|
3335
|
+
function appendNextSiblings(element, adapter) {
|
|
3336
|
+
// Order matters because jQuery seems to check the children before the siblings
|
|
3337
|
+
const elements = Array.isArray(element) ? [...element] : [element];
|
|
3338
|
+
const elementsLength = elements.length;
|
|
3339
|
+
for (let index = 0; index < elementsLength; index++) {
|
|
3340
|
+
const nextSiblings = getNextSiblings(elements[index], adapter);
|
|
3341
|
+
elements.push(...nextSiblings);
|
|
3342
|
+
}
|
|
3343
|
+
return elements;
|
|
3344
|
+
}
|
|
3345
|
+
/**
|
|
3346
|
+
* @template Node The generic Node type for the DOM adapter being used.
|
|
3347
|
+
* @template ElementNode The Node type for elements for the DOM adapter being used.
|
|
3348
|
+
* @param elems Elements to query. If it is an element, its children will be queried.
|
|
3349
|
+
* @param query can be either a CSS selector string or a compiled query function.
|
|
3350
|
+
* @param [options] options for querying the document.
|
|
3351
|
+
* @see compile for supported selector queries.
|
|
3352
|
+
* @returns All matching elements.
|
|
3353
|
+
*/
|
|
3354
|
+
const selectAll = getSelectorFunction((query, elements, options) => query === falseFunc || !elements || elements.length === 0
|
|
3355
|
+
? []
|
|
3356
|
+
: findAll(query, elements, options));
|
|
3357
|
+
/**
|
|
3358
|
+
* @template Node The generic Node type for the DOM adapter being used.
|
|
3359
|
+
* @template ElementNode The Node type for elements for the DOM adapter being used.
|
|
3360
|
+
* @param elems Elements to query. If it is an element, its children will be queried.
|
|
3361
|
+
* @param query can be either a CSS selector string or a compiled query function.
|
|
3362
|
+
* @param [options] options for querying the document.
|
|
3363
|
+
* @see compile for supported selector queries.
|
|
3364
|
+
* @returns the first match, or null if there was no match.
|
|
3365
|
+
*/
|
|
3366
|
+
const selectOne = getSelectorFunction((query, elements, options) => query === falseFunc || !elements || elements.length === 0
|
|
3367
|
+
? null
|
|
3368
|
+
: findOne(query, elements, options));
|
|
3369
|
+
/**
|
|
3370
|
+
* Tests whether or not an element is matched by query.
|
|
3371
|
+
* @template Node The generic Node type for the DOM adapter being used.
|
|
3372
|
+
* @template ElementNode The Node type for elements for the DOM adapter being used.
|
|
3373
|
+
* @param element The element to test if it matches the query.
|
|
3374
|
+
* @param query can be either a CSS selector string or a compiled query function.
|
|
3375
|
+
* @param [options] options for querying the document.
|
|
3376
|
+
* @see compile for supported selector queries.
|
|
3377
|
+
* @returns Whether the element matches the query.
|
|
3378
|
+
*/
|
|
3379
|
+
function is(element, query, options) {
|
|
3380
|
+
return (typeof query === "function" ? query : compile(query, options))(element);
|
|
3381
|
+
}
|
|
3382
|
+
|
|
3383
|
+
export { _compileUnsafe, compile, selectAll as default, is, prepareContext, selectAll, selectOne };
|