diff --git a/.gitignore b/.gitignore index 5bbd5ad..9d61d2a 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ html/ output/ mdn/.id-list mdn/developer.mozilla.org/ +node_modules/ highlighter/ diff --git a/build.sh b/build.sh index 063f9f0..7d6f21d 100755 --- a/build.sh +++ b/build.sh @@ -15,6 +15,7 @@ declare -r WATTSI_LATEST=140 # Shared state variables throughout this script LOCAL_WATTSI=true WATTSI_RESULT=0 +USE_BIKESHED=false DO_UPDATE=true DO_LINT=true DO_HIGHLIGHT=true @@ -37,6 +38,7 @@ HTML_GIT_CLONE_OPTIONS=${HTML_GIT_CLONE_OPTIONS:-"--depth=2"} # This is used by child scripts, and so we export it export HTML_CACHE +export USE_BIKESHED # Used specifically when the Dockerfile calls this script SKIP_BUILD_UPDATE_CHECK=${SKIP_BUILD_UPDATE_CHECK:-false} @@ -85,14 +87,16 @@ function main { exit 0 fi - checkWattsi - ensureHighlighterInstalled + if [[ $USE_BIKESHED != "true" ]]; then + checkWattsi + ensureHighlighterInstalled - doLint + doLint - updateRemoteDataFiles + updateRemoteDataFiles - startHighlightServer + startHighlightServer + fi processSource "source" "default" @@ -146,6 +150,7 @@ function processCommandLineArgs { echo " $0 help Show this usage statement." echo echo "Build options:" + echo " -b|--bikeshed Use Bikeshed instead of Wattsi. (experimental)" echo " -d|--docker Use Docker to build in a container." echo " -r|--remote Use the build server." echo " -s|--serve After building, serve the results on http://localhost:$SERVE_PORT." @@ -176,6 +181,10 @@ function processCommandLineArgs { DO_HIGHLIGHT=false SINGLE_PAGE_ONLY=true ;; + -b|--bikeshed) + USE_BIKESHED=true + SINGLE_PAGE_ONLY=true + ;; -d|--docker) USE_DOCKER=true ;; @@ -663,34 +672,49 @@ function processSource { cargo run "${cargo_args[@]}" <"$HTML_SOURCE/$source_location" >"$HTML_TEMP/source-whatwg-complete" fi - runWattsi "$HTML_TEMP/source-whatwg-complete" "$HTML_TEMP/wattsi-output" - if [[ $WATTSI_RESULT == "0" ]]; then - if [[ $LOCAL_WATTSI != "true" ]]; then - "$QUIET" || grep -v '^$' "$HTML_TEMP/wattsi-output.txt" # trim blank lines - fi + if [[ $USE_BIKESHED == "true" ]]; then + clearDir "$HTML_TEMP/bikeshed-output" + + # TODO: port to html-build Rust code + node wattsi2bikeshed.js "$HTML_TEMP/source-whatwg-complete" "$HTML_TEMP/source-whatwg-complete.bs" + + local bikeshed_args=( --force ) + $DO_UPDATE || bikeshed_args+=( --no-update ) + bikeshed "${bikeshed_args[@]}" spec "$HTML_TEMP/source-whatwg-complete.bs" "$HTML_TEMP/bikeshed-output/index.html" --md-Text-Macro="SHA $HTML_SHA" --md-Text-Macro="COMMIT-SHA $HTML_SHA" else - if [[ $LOCAL_WATTSI != "true" ]]; then - "$QUIET" || grep -v '^$' "$HTML_TEMP/wattsi-output.txt" # trim blank lines - fi - if [[ $WATTSI_RESULT == "65" ]]; then - echo - echo "There were errors. Running again to show the original line numbers." - echo - runWattsi "$HTML_SOURCE/$source_location" "$HTML_TEMP/wattsi-raw-source-output" + runWattsi "$HTML_TEMP/source-whatwg-complete" "$HTML_TEMP/wattsi-output" + if [[ $WATTSI_RESULT == "0" ]]; then + if [[ $LOCAL_WATTSI != "true" ]]; then + "$QUIET" || grep -v '^$' "$HTML_TEMP/wattsi-output.txt" # trim blank lines + fi + else if [[ $LOCAL_WATTSI != "true" ]]; then - grep -v '^$' "$HTML_TEMP/wattsi-output.txt" # trim blank lines + "$QUIET" || grep -v '^$' "$HTML_TEMP/wattsi-output.txt" # trim blank lines + fi + if [[ $WATTSI_RESULT == "65" ]]; then + echo + echo "There were errors. Running again to show the original line numbers." + echo + runWattsi "$HTML_SOURCE/$source_location" "$HTML_TEMP/wattsi-raw-source-output" + if [[ $LOCAL_WATTSI != "true" ]]; then + grep -v '^$' "$HTML_TEMP/wattsi-output.txt" # trim blank lines + fi fi + echo + echo "There were errors. Stopping." + exit "$WATTSI_RESULT" fi - echo - echo "There were errors. Stopping." - exit "$WATTSI_RESULT" fi # Keep the list of files copied from $HTML_SOURCE in sync with `doServerBuild` if [[ $build_type == "default" ]]; then # Singlepage HTML - mv "$HTML_TEMP/wattsi-output/index-html" "$HTML_OUTPUT/index.html" + if [[ $USE_BIKESHED == "true" ]]; then + mv "$HTML_TEMP/bikeshed-output/index.html" "$HTML_OUTPUT/index.html" + else + mv "$HTML_TEMP/wattsi-output/index-html" "$HTML_OUTPUT/index.html" + fi if [[ $SINGLE_PAGE_ONLY == "false" ]]; then # Singlepage Commit Snapshot @@ -706,7 +730,9 @@ function processSource { fi cp -p entities/out/entities.json "$HTML_OUTPUT" - cp -p "$HTML_TEMP/wattsi-output/xrefs.json" "$HTML_OUTPUT" + if [[ $USE_BIKESHED == "false" ]]; then + cp -p "$HTML_TEMP/wattsi-output/xrefs.json" "$HTML_OUTPUT" + fi clearDir "$HTML_TEMP" diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..e8828dc --- /dev/null +++ b/package-lock.json @@ -0,0 +1,686 @@ +{ + "name": "html-build", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "jsdom": "^26.0.0" + } + }, + "node_modules/@asamuzakjp/css-color": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-2.8.3.tgz", + "integrity": "sha512-GIc76d9UI1hCvOATjZPyHFmE5qhRccp3/zGfMPapK3jBi+yocEzp6BBB0UnfRYP9NP4FANqUZYb0hnfs3TM3hw==", + "dependencies": { + "@csstools/css-calc": "^2.1.1", + "@csstools/css-color-parser": "^3.0.7", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "lru-cache": "^10.4.3" + } + }, + "node_modules/@csstools/color-helpers": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.0.2.tgz", + "integrity": "sha512-JqWH1vsgdGcw2RR6VliXXdA0/59LttzlU8UlRT/iUUsEeWfYq8I+K0yhihEUTTHLRm1EXvpsCx3083EU15ecsA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@csstools/css-calc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.2.tgz", + "integrity": "sha512-TklMyb3uBB28b5uQdxjReG4L80NxAqgrECqLZFQbyLekwwlcDDS8r3f07DKqeo8C4926Br0gf/ZDe17Zv4wIuw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.0.8.tgz", + "integrity": "sha512-pdwotQjCCnRPuNi06jFuP68cykU1f3ZWExLe/8MQ1LOs8Xq+fTkYgd+2V8mWUWMrOn9iS2HftPVaMZDaXzGbhQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "dependencies": { + "@csstools/color-helpers": "^5.0.2", + "@csstools/css-calc": "^2.1.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.4.tgz", + "integrity": "sha512-Up7rBoV77rv29d3uKHUIVubz1BTcgyUK72IvCQAbfbMv584xHcGKCKbWh7i8hPrRJ7qU4Y8IO3IY9m+iTB7P3A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^3.0.3" + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.3.tgz", + "integrity": "sha512-UJnjoFsmxfKUdNYdWgOB0mWUypuLvAfQPH1+pyvRJs6euowbFkFC6P13w1l8mJyi3vxYMxc9kld5jZEGRQs6bw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/agent-base": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", + "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", + "engines": { + "node": ">= 14" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cssstyle": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.2.1.tgz", + "integrity": "sha512-9+vem03dMXG7gDmZ62uqmRiMRNtinIZ9ZyuF6BdxzfOD+FdN5hretzynkn0ReS2DO2GSw76RWHs0UmJPI2zUjw==", + "dependencies": { + "@asamuzakjp/css-color": "^2.8.2", + "rrweb-cssom": "^0.8.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/data-urls": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", + "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", + "dependencies": { + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decimal.js": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.5.0.tgz", + "integrity": "sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw==" + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/form-data": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", + "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/html-encoding-sniffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", + "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", + "dependencies": { + "whatwg-encoding": "^3.1.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==" + }, + "node_modules/jsdom": { + "version": "26.0.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-26.0.0.tgz", + "integrity": "sha512-BZYDGVAIriBWTpIxYzrXjv3E/4u8+/pSG5bQdIYCbNCGOvsPkDQfTVLAIXAf9ETdCpduCVTkDe2NNZ8NIwUVzw==", + "dependencies": { + "cssstyle": "^4.2.1", + "data-urls": "^5.0.0", + "decimal.js": "^10.4.3", + "form-data": "^4.0.1", + "html-encoding-sniffer": "^4.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.6", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.16", + "parse5": "^7.2.1", + "rrweb-cssom": "^0.8.0", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^5.0.0", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^3.1.1", + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.1.0", + "ws": "^8.18.0", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "canvas": "^3.0.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/nwsapi": { + "version": "2.2.16", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.16.tgz", + "integrity": "sha512-F1I/bimDpj3ncaNDhfyMWuFqmQDBwDB0Fogc2qpL3BWvkQteFD/8BzWuIRl83rq0DXfm8SGt/HFhLXZyljTXcQ==" + }, + "node_modules/parse5": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz", + "integrity": "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==", + "dependencies": { + "entities": "^4.5.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/rrweb-cssom": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", + "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==" + }, + "node_modules/tldts": { + "version": "6.1.82", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.82.tgz", + "integrity": "sha512-KCTjNL9F7j8MzxgfTgjT+v21oYH38OidFty7dH00maWANAI2IsLw2AnThtTJi9HKALHZKQQWnNebYheadacD+g==", + "dependencies": { + "tldts-core": "^6.1.82" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "6.1.82", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.82.tgz", + "integrity": "sha512-Jabl32m21tt/d/PbDO88R43F8aY98Piiz6BVH9ShUlOAiiAELhEqwrAmBocjAqnCfoUeIsRU+h3IEzZd318F3w==" + }, + "node_modules/tough-cookie": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz", + "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==", + "dependencies": { + "tldts": "^6.1.32" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tr46": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.0.0.tgz", + "integrity": "sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-url": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.1.1.tgz", + "integrity": "sha512-mDGf9diDad/giZ/Sm9Xi2YcyzaFpbdLpJPr+E9fSkyQ7KpQD4SdFcugkRQYzhmfI4KeV4Qpnn2sKPdo+kmsgRQ==", + "dependencies": { + "tr46": "^5.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "engines": { + "node": ">=18" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..9e88d15 --- /dev/null +++ b/package.json @@ -0,0 +1,7 @@ +{ + "private": true, + "type": "module", + "dependencies": { + "jsdom": "^26.0.0" + } +} diff --git a/src/bikeshed.rs b/src/bikeshed.rs new file mode 100644 index 0000000..e4a4a07 --- /dev/null +++ b/src/bikeshed.rs @@ -0,0 +1,21 @@ +//! Convert source to Bikeshed syntax. + +use crate::dom_utils::NodeHandleExt; +use markup5ever_rcdom::Handle; + +pub struct Processor { +} + +impl Processor { + pub fn new() -> Self { + Processor { + } + } + + pub fn visit(&mut self, node: &Handle) { + // Remove the
+ +Group: WHATWG +H1: HTML +Shortname: html +Text Macro: TWITTER htmlstandard +Text Macro: LATESTRD 2025-01 +Abstract: HTML is Bikeshed. +Indent: 1 +Markup Shorthands: css off +Complain About: accidental-2119 off, missing-example-ids off +Include MDN Panels: false +`; + +const kCrossRefAttribute = 'data-x'; + +function replaceWithChildren(elem) { + while (elem.firstChild) { + elem.parentNode.insertBefore(elem.firstChild, elem); + } + elem.remove(); +} + +function isElement(node) { + return node?.nodeType === 1; +} + +function isText(node) { + return node?.nodeType === 3; +} + +const markup = /[\[\]{}<>&]/g; +function hasMarkup(text) { + return markup.test(text); +} + +// Get the "topic" for cross-references like Wattsi: +// https://github.com/whatwg/wattsi/blob/b9c28036a2a174f7f87315164f001120596a95f1/src/wattsi.pas#L882-L894 +function getTopic(elem) { + let result; + while (true) { + if (elem.hasAttribute(kCrossRefAttribute)) { + result = elem.getAttribute(kCrossRefAttribute); + break; + } else if (isElement(elem.firstChild) && elem.firstChild === elem.lastChild) { + elem = elem.firstChild; + continue; + } else { + result = elem.textContent; + break; + } + } + // This matches Wattsi's MungeStringToTopic in spirit, + // but perhaps not in every detail: + return result + .replaceAll('#', '') + .replaceAll(/\s+/g, ' ') + .toLowerCase() + .trim(); +} + +// Convert a topic to an ID like Wattsi: +// https://github.com/whatwg/wattsi/blob/b9c28036a2a174f7f87315164f001120596a95f1/src/wattsi.pas#L786-L832 +function getId(topic) { + // Note: no toLowerCase() because this is already done in getTopic(). + return topic + .replaceAll(/["?`]/g, '') + .replaceAll(/[\s<>\[\\\]^{|}%]+/g, '-'); +} + +// Get the linking text like Bikeshed: +// https://github.com/speced/bikeshed/blob/50d0ec772915adcd5cec0c2989a27fa761d70e71/bikeshed/h/dom.py#L174-L201 +// +// Also approximate the additional munging Bikeshed does here: +// https://github.com/speced/bikeshed/blob/f3fd50cc3a67ecbffb562b16252237aeaa2b4eae/bikeshed/refs/manager.py#L291-L297 +function getBikeshedLinkTextSet(elem) { + const texts = new Set(); + + const dataLt = elem.getAttribute('data-lt'); + if (dataLt === '') { + return texts; + } + + function add(lt) { + lt = lt.trim().replaceAll(/\s+/g, ' '); + // These are the extra bits from addLocalDfns in Bikeshed: + lt = lt.replaceAll("’", "'"); + // TODO: line-ending em dashes or -- (if they exist in HTML) + // TODO: only lowercase dfn types that Bikeshed would if lowercasing + // everything results in collisions that Bikeshed doesn't have. + lt = lt.toLowerCase(); + texts.add(lt); + } + + if (dataLt) { + // TODO: what's the `rawText in ["|", "||", "|||"]` condition for? + dataLt.split('|').map(add); + } else { + switch (elem.localName) { + case 'dfn': + case 'a': + add(elem.textContent); + break; + case 'h2': + case 'h3': + case 'h4': + case 'h5': + case 'h6': + add((elem.querySelector('.content') ?? elem).textContent); + break; + } + } + + const dataLocalLt = elem.getAttribute('data-local-lt'); + if (dataLocalLt) { + if (dataLocalLt.includes('|')) { + console.warn('Ignoring data-local-lt value containing |:', dataLocalLt); + } else { + add(dataLocalLt); + } + } + + return texts; +} + +// Get the *first* linking text like Bikeshed: +// https://github.com/speced/bikeshed/blob/50d0ec772915adcd5cec0c2989a27fa761d70e71/bikeshed/h/dom.py#L215-L220 +function getBikeshedLinkText(elem) { + for (const text of getBikeshedLinkTextSet(elem)) { + return text; + } + return null; +} + +// Add for and lt to ensure that Bikeshed will link the to the right . +function ensureLink(a, dfn, dfnLtCounts) { + if (dfn.hasAttribute('for')) { + a.setAttribute('for', dfn.getAttribute('for')); + // TODO: don't add when it's already unambiguous. + } + + const dfnLts = getBikeshedLinkTextSet(dfn); + if (dfnLts.size === 0) { + console.warn('No linking text for', dfn.outerHTML); + return; + } + const aLt = getBikeshedLinkText(a); + if (!aLt) { + console.warn('No linking text for', a.outerHTML); + return; + } + + if (a.hasAttribute('for')) { + // TODO: look in dfnLts when that tracks + return; + } + + for (const lt of dfnLts) { + if (dfnLtCounts.get(lt) === 1) { + // This is a unique linking text. + // Note: data-lt is rewritten to lt later. It would also work to remove + // any data-lt attribute here and just add lt. + a.setAttribute('data-lt', lt); + return; + } + } + + if (!dfn.hasAttribute('data-local-lt')) { + if (!dfn.id) { + console.warn('No id for dfn', dfn.outerHTML); + return; + } + // Use a prefix to make the linking text unique. The prefix is "xxx-"" + // because class="XXX" is used as a FIXME/TODO in HTML, and these + // local-lt attributes should be removed over time. + dfn.setAttribute('data-local-lt', `xxx-${dfn.id}`); + } + + a.setAttribute('data-lt', dfn.getAttribute('data-local-lt')); +} + +function convert(infile, outfile) { + const source = readFileSync(infile, 'utf-8'); + const dom = new JSDOM(source); + const document = dom.window.document; + + document.body.prepend(JSDOM.fragment(boilerplate)); + + for (const dt of document.querySelectorAll('#ref-list dt')) { + const node = dt.firstChild; + if (isText(node) && node.data.startsWith('[')) { + node.data = '\\' + node.data; + } + } + + // TODO: handle w-* variant attributes like Wattsi does: + // https://github.com/whatwg/wattsi/blob/b9c28036a2a174f7f87315164f001120596a95f1/src/wattsi.pas#L735-L759 + + // Scan all definitions + const crossRefs = new Map(); // map from Wattsi topic to + const dfnLtCounts = new Map(); // map from Bikeshed link text to number of uses in + for (const dfn of document.querySelectorAll('dfn')) { + const topic = getTopic(dfn); + if (topic === '') { + // This isn't a linkable definition and Wattsi outputs a plain + // with no attributes. The closest thing in Bikeshed is a definition + // with no linking text that is not exported. + dfn.setAttribute('data-lt', ''); + dfn.setAttribute('noexport', ''); + continue; + } + if (crossRefs.has(topic)) { + console.warn('Duplicate topic:', topic); + } + crossRefs.set(topic, dfn); + + if (!dfn.hasAttribute('id')) { + // TODO: avoid if Bikeshed would generate the same ID + dfn.setAttribute('id', getId(topic)); + } + + const lts = getBikeshedLinkTextSet(dfn); + + // Remove "new" from the linking text of constructors. + if (dfn.hasAttribute('constructor') && !dfn.hasAttribute('data-lt')) { + for (const lt of lts) { + if (lt.startsWith('new ')) { + dfn.setAttribute('data-lt', lt.substring(4)); + break; + } + } + } + + // Remove leading "document." from linking text of document.write/writeln. + if (dfn.hasAttribute('method') && dfn.getAttribute('for') === 'Document' && + !dfn.hasAttribute('data-lt')) { + for (const lt of lts) { + if (lt.startsWith('document.')) { + dfn.setAttribute('data-lt', lt.substring(9)); + break; + } + } + } + + // Count uses of each Bikeshed linking text + if (dfn.hasAttribute('for')) { + // TODO: track as well + continue; + } + for (const lt of lts) { + const count = (dfnLtCounts.get(lt) ?? 0) + 1 + dfnLtCounts.set(lt, count); + } + } + + // Track used s in order to identify the unused ones. + const usedDfns = new Set(); + + // Replace with the inner
or a new .
+ // TODO: align more closely with Wattsi:
+ // https://github.com/whatwg/wattsi/blob/b9c28036a2a174f7f87315164f001120596a95f1/src/wattsi.pas#L1454-L1487
+ const spans = document.querySelectorAll('span');
+ for (const [i, span] of Object.entries(spans)) {
+ // Don't touch any span with a descendent span.
+ if (span.contains(spans[+i + 1])) {
+ // TODO: vet for weird cases that need fixing
+ continue;
+ }
+ // Leave dev/nodev alone here.
+ if (span.hasAttribute('w-dev') || span.hasAttribute('w-nodev')) {
+ continue;
+ }
+ // Leave in SVG alone.
+ if (span.hasAttribute('xmlns')) {
+ continue;
+ }
+
+ // Empty data-x="" means it's not a link.
+ if (span.getAttribute(kCrossRefAttribute) === '') {
+ continue;
+ }
+
+ // An empty span with an ID is used to preserve old IDs.
+ // TODO: hoist to oldids attribute for Bikeshed
+ if (span.hasAttribute('id') && span.firstChild === null) {
+ continue;
+ }
+
+ const topic = getTopic(span);
+ const dfn = crossRefs.get(topic);
+ if (!dfn) {
+ // TODO: vet these cases for any that should actually be linked
+ // console.log(span.outerHTML);
+ continue;
+ }
+
+ if (span.hasAttribute('subdfn')) {
+ // TODO: generate an ID based on the linked term, like Wattsi:
+ // https://github.com/whatwg/wattsi/blob/b9c28036a2a174f7f87315164f001120596a95f1/src/wattsi.pas#L943-L961
+ span.removeAttribute('subdfn');
+ }
+
+ // For foo
and "SyntaxError
",
+ // drop the outer and depend on the linking logic. Note that this
+ // excludes the surrounding quotes from the link text, which is a minor change.
+ // The element is further transformed in a following step.
+ function isQuote(node) {
+ return false; // <- hack to disable unwrapping
+ return isText(node) && node.data === '"';
+ }
+ const code = span.querySelector('code');
+ if (code && (
+ // is the single child
+ (span.childNodes.length === 1 && span.firstChild === code) ||
+ // has surrounding " text nodes
+ (span.childNodes.length === 3 &&
+ isQuote(span.firstChild) && isQuote(span.lastChild) &&
+ span.firstChild.nextSibling === code))) {
+ if (span.hasAttributes()) {
+ console.warn('Discarding attributes:', span.outerHTML);
+ }
+ replaceWithChildren(span);
+ continue;
+ }
+
+ // Output a instead of .
+ const a = document.createElement('a');
+
+ for (const name of span.getAttributeNames()) {
+ const value = span.getAttribute(name);
+ switch (name) {
+ case 'data-x':
+ break;
+ case 'data-lt':
+ case 'id':
+ // Copy over.
+ a.setAttribute(name, value);
+ break;
+ default:
+ console.warn('Unhandled attribute:', name);
+ }
+ }
+ // Move the children over to .
+ while (span.firstChild) {
+ a.appendChild(span.firstChild);
+ }
+ span.replaceWith(a);
+
+ ensureLink(a, dfn, dfnLtCounts);
+ usedDfns.add(dfn);
+ }
+
+ // Wrap with . Wattsi handling is here:
+ // https://github.com/whatwg/wattsi/blob/b9c28036a2a174f7f87315164f001120596a95f1/src/wattsi.pas#L1454-L1487
+ for (const i of document.querySelectorAll('i[data-x]')) {
+ if (i.closest('dfn')) {
+ continue;
+ }
+
+ const topic = getTopic(i);
+ const dfn = crossRefs.get(topic);
+ if (!dfn) {
+ continue;
+ // TODO: vet these cases for any that should actually be linked
+ // console.log(i.outerHTML);
+ }
+
+ const a = document.createElement('a');
+ i.parentNode.insertBefore(a, i);
+ a.appendChild(i);
+
+ ensureLink(a, dfn, dfnLtCounts);
+ usedDfns.add(dfn);
+ }
+
+ for (const code of document.querySelectorAll('pre > code')) {
+ const pre = code.parentNode;
+ if (code.hasAttribute('class')) {
+ switch (code.className) {
+ case 'idl':
+ pre.className = 'idl';
+ break;
+ case 'js':
+ pre.className = 'lang-javascript';
+ break;
+ case 'abnf':
+ case 'css':
+ case 'html':
+ case 'json':
+ pre.className = `lang-${code.className}`;
+ break;
+ default:
+ console.warn('Unhandled class:', code.className);
+ }
+ code.removeAttribute('class');
+ }
+ if (code.getAttribute(kCrossRefAttribute) === '') {
+ code.removeAttribute(kCrossRefAttribute);
+ }
+ if (code.hasAttributes()) {
+ console.warn('Discarding attributes:', code.outerHTML);
+ }
+ replaceWithChildren(code);
+ }
+
+ // Link to the right thing.
+ for (const code of document.querySelectorAll('code')) {
+ // shouldn't be linked.
+ if (code.hasAttribute('undefined')) {
+ code.removeAttribute('undefined');
+ continue;
+ }
+
+ // inside or should be left untouched.
+ if (code.closest('a, dfn')) {
+ continue;
+ }
+
+ const topic = getTopic(code);
+ if (topic === '') {
+ continue;
+ }
+
+ const dfn = crossRefs.get(topic);
+ if (!dfn) {
+ console.warn('No found for topic:', topic);
+ continue;
+ }
+
+ if (code.hasAttribute('subdfn')) {
+ // TODO: generate an ID based on the linked term, like Wattsi:
+ // https://github.com/whatwg/wattsi/blob/b9c28036a2a174f7f87315164f001120596a95f1/src/wattsi.pas#L943-L961
+ code.removeAttribute('subdfn');
+ }
+
+ const a = document.createElement('a');
+ for (const name of code.getAttributeNames()) {
+ a.setAttribute(name, code.getAttribute(name));
+ code.removeAttribute(name);
+ }
+ code.replaceWith(a);
+ a.appendChild(code);
+
+ ensureLink(a, dfn, dfnLtCounts);
+ usedDfns.add(dfn);
+ }
+
+ // Rewrite data-lt to lt and data-local-lt to local-lt.
+ for (const elem of document.querySelectorAll('[data-lt]')) {
+ elem.setAttribute('lt', elem.getAttribute('data-lt'));
+ elem.removeAttribute('data-lt');
+ }
+ for (const elem of document.querySelectorAll('[data-local-lt]')) {
+ elem.setAttribute('local-lt', elem.getAttribute('data-local-lt'));
+ elem.removeAttribute('data-local-lt');
+ }
+
+ for (const elem of document.querySelectorAll('[data-x]')) {
+ elem.removeAttribute(kCrossRefAttribute);
+ }
+
+ for (const elem of document.querySelectorAll('[data-x-href]')) {
+ // TODO
+ elem.removeAttribute('data-x-href');
+ }
+
+ // Add noexport to unused s to silence Bikeshed warnings about them.
+ // TODO: vet for cases that are accidentally unused.
+ for (const dfn of crossRefs.values()) {
+ if (usedDfns.has(dfn)) {
+ continue;
+ }
+ // This is unused by Wattsi rules.
+ if (dfn.hasAttribute('data-export') || dfn.hasAttribute('export')) {
+ continue;
+ }
+ dfn.setAttribute('noexport', '');
+ }
+
+ // Simplify to Bikeshed autolinks.
+ for (const a of document.querySelectorAll('a')) {
+ break;
+ const hasSingleTextNode = isText(a.firstChild) && a.firstChild === a.lastChild;
+ if (hasSingleTextNode && !a.hasAttributes()) {
+ const text = a.firstChild.data;
+ if (!hasMarkup(text)) {
+ a.replaceWith(`[=${text}=]`);
+ }
+ }
+ // TODO: handle .
+ }
+
+ const output = document.body.innerHTML
+ .replaceAll('[[', '\\[[');
+
+ writeFileSync(outfile, output, 'utf-8');
+}
+
+convert(process.argv[2], process.argv[3]);