diff --git a/.gitattributes b/.gitattributes index 1e4aed154..b16894c76 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,4 +1,4 @@ -# Set default behaviour for some text-like files, in case users don't have core.autocrlf set. +# Set default behaviour for some text-like files, in case users don't have webodfcore.autocrlf set. *.cmake text eol=native diff --git a/.github/workflows/desktop.yml b/.github/workflows/desktop.yml new file mode 100644 index 000000000..791aceae7 --- /dev/null +++ b/.github/workflows/desktop.yml @@ -0,0 +1,125 @@ +# Builds the viewer of OpenDocument for the desktop on the three systems it +# runs on, and runs the tests of the library in the webengine of qt on each of +# them. Two of those systems are ones no one of this project owns, which is +# what this is for: the branches "if (WIN32)" and "if (APPLE)" of the build are +# never run anywhere else. +name: Viewer of the desktop + +on: + push: + branches: [master] + pull_request: + workflow_dispatch: + +jobs: + desktop: + name: ${{ matrix.name }} + runs-on: ${{ matrix.runner }} + strategy: + fail-fast: false + matrix: + include: + - name: Linux + runner: ubuntu-latest + qt: linux_gcc_64 + - name: Windows + runner: windows-latest + qt: win64_msvc2022_64 + # The runner is held at macos 15: the sdk of macos 26 dropped the + # framework AGL, that the configuration of Qt 6.8 still asks for, + # and the link of the runtime fails without it. + - name: macOS + runner: macos-15 + qt: clang_64 + + steps: + # The whole history: the version of the build comes from "git describe", + # which a shallow checkout gives nothing to describe. + - uses: actions/checkout@v5 + with: + fetch-depth: 0 + + - uses: actions/setup-node@v5 + with: + node-version: 22 + cache: npm + + # A java runtime runs the closure compiler and rhino. + - uses: actions/setup-java@v5 + with: + distribution: temurin + java-version: 21 + + # Qt WebEngine is an extension since 6.8, in a repository of its own; the + # action asks for it as a module, and webchannel and positioning come + # with it. + - uses: jurplel/install-qt-action@v4 + with: + version: 6.8.2 + arch: ${{ matrix.qt }} + modules: qtwebengine qtwebchannel qtpositioning + cache: true + + # The webengine reads these on linux, where a runner carries no desktop. + - name: Libraries the engine reads + if: runner.os == 'Linux' + run: | + sudo apt-get update + sudo apt-get install --no-install-recommends -y \ + libnss3 libxkbcommon-x11-0 libxcomposite1 libxdamage1 \ + libxrandr2 libxtst6 libasound2t64 fonts-dejavu-core + + - name: Dependencies of the build + run: npm ci + + # No generator is named, so cmake takes the one of visual studio on + # windows, which finds the compiler of microsoft itself: nothing has to + # put it in the environment beforehand. MinGW, that Qt WebEngine does + # not compile with, is never the one taken. + - name: Configure + run: > + cmake -S . -B build + -DWEBODF_DESKTOP=ON + -DWEBODF_QTJSRUNTIME=ON + -DCMAKE_BUILD_TYPE=Release + + # "--config" holds for the generators of more than one configuration, + # the one of visual studio among them, that pay no heed to + # "CMAKE_BUILD_TYPE"; the others take no harm from it. + + # The library first: a viewer built on a library that fails its tests is + # worth nothing. The target runs every suite, in node, in rhino, on the + # sources and on the file the compiler wrote, and in the webengine of qt. + - name: Tests of the library + env: + QT_QPA_PLATFORM: offscreen + QTWEBENGINE_CHROMIUM_FLAGS: --disable-gpu --no-sandbox + run: cmake --build build --config Release --target webodf.js-tests + + - name: Viewer + run: cmake --build build --config Release --target product-opendocumentviewer-desktop + + # What the tools of the platform gather beside the program: the whole of + # it runs on a machine where qt is not installed. + # The prefix is absolute: the script that qt writes to deploy the + # libraries builds the path of "qt.conf" from it and refuses a relative + # one. + - name: Install what is deployed + run: > + cmake --install build --config Release + --prefix ${{ github.workspace }}/build/dist + + # An archive rather than the files: a bundle of macos holds symbolic + # links, that an artifact of loose files loses. + - name: Gather what was installed + run: cmake -E tar czf ../opendocumentviewer-${{ matrix.name }}.tar.gz . + working-directory: build/dist + + # "archive: false" hands the file over as it is: an artifact is wrapped + # in a zip otherwise, and whoever downloads it unpacks twice. The name + # of the artifact is then the name of the file. + - uses: actions/upload-artifact@v7 + with: + path: build/opendocumentviewer-${{ matrix.name }}.tar.gz + archive: false + if-no-files-found: error diff --git a/.github/workflows/library.yml b/.github/workflows/library.yml new file mode 100644 index 000000000..c3fa2ebb2 --- /dev/null +++ b/.github/workflows/library.yml @@ -0,0 +1,59 @@ +# Builds the library the short way, with node alone, and runs what that build +# runs: the tests in node, the same ones in rhino, and the check of the types. +# It is the short one, that answers on every push, where the build of the +# viewers is the long one. +name: Library + +on: + push: + branches: [master] + pull_request: + workflow_dispatch: + +jobs: + library: + name: Library, with node + runs-on: ubuntu-latest + + steps: + # The whole history: the version of the build comes from "git describe", + # which a shallow checkout gives nothing to describe. + - uses: actions/checkout@v5 + with: + fetch-depth: 0 + + - uses: actions/setup-node@v5 + with: + node-version: 22 + cache: npm + + # A java runtime runs the closure compiler and rhino. + - uses: actions/setup-java@v5 + with: + distribution: temurin + java-version: 21 + + - name: Dependencies of the build + run: npm ci + + - name: Types of the library and of the tests + run: | + npm run check + npm run check:tests + + - name: Tests, in node + run: npm test + + - name: Tests, in rhino + run: npm run test:rhino + + - name: Library + run: npm run build + + # "archive: false" hands the library over as it is, where an artifact + # is wrapped in a zip otherwise. + - uses: actions/upload-artifact@v7 + with: + path: dist/webodf.js + archive: false + if-no-files-found: error diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 000000000..dd4f3cedf --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,154 @@ +# Builds every product a machine without a key can make, and publishes them +# with the release of github. Gitlab builds and publishes its own, see +# ".gitlab-ci.yml": neither forge pushes a file to the other. What needs a +# signature is left out: the apk of android, the bundle of macos and the +# application of ios, whose keys belong to whoever publishes them, see +# "README-Products.md". +name: Release + +on: + push: + tags: ['v*'] + workflow_dispatch: + +jobs: + release: + name: Release of github + runs-on: ubuntu-latest + permissions: + contents: write + + steps: + - uses: actions/checkout@v5 + with: + fetch-depth: 0 + + - uses: actions/setup-node@v5 + with: + node-version: 22 + cache: npm + + # The library, that no machine of the matrix writes: it is the package + # the other projects depend on. + - name: Package of the library + run: | + npm ci + mkdir -p products + npm pack --pack-destination products + + # The release is opened before the products are built, so that each + # machine has one to hand its own to. A release that stands already is + # written to rather than opened again, so that the workflow can be run + # a second time on the same tag. + - name: Release of github + env: + GH_TOKEN: ${{ github.token }} + TAG: ${{ github.ref_name }} + run: | + if gh release view "$TAG" >/dev/null 2>&1; then + gh release upload "$TAG" products/* --clobber + else + gh release create "$TAG" --title "$TAG" \ + --notes "The products of $TAG." products/* + fi + + products: + name: Products of ${{ matrix.name }} + needs: release + runs-on: ${{ matrix.runner }} + permissions: + contents: write + + strategy: + fail-fast: false + matrix: + include: + - name: Linux + runner: ubuntu-latest + qt: linux_gcc_64 + - name: Windows + runner: windows-latest + qt: win64_msvc2022_64 + # The runner is held at macos 15: the sdk of macos 26 dropped the + # framework AGL, that the configuration of Qt 6.8 still asks for, + # and the link of the runtime fails without it. + - name: macOS + runner: macos-15 + qt: clang_64 + + steps: + # The whole history: the version of the build comes from "git describe", + # which a shallow checkout gives nothing to describe. + - uses: actions/checkout@v5 + with: + fetch-depth: 0 + + - uses: actions/setup-node@v5 + with: + node-version: 22 + cache: npm + + # A java runtime runs the closure compiler and rhino. + - uses: actions/setup-java@v5 + with: + distribution: temurin + java-version: 21 + + # Qt WebEngine is an extension since 6.8, in a repository of its own; the + # modules are the ones the viewer links against. + - uses: jurplel/install-qt-action@v4 + with: + version: 6.8.2 + arch: ${{ matrix.qt }} + modules: qtwebengine qtwebchannel qtpositioning + cache: true + + # The tools that write the packages of linux: what is not found is + # skipped by the build, which says so at the end. + - name: Tools of the packages + if: runner.os == 'Linux' + run: | + sudo apt-get update + sudo apt-get install --no-install-recommends -y \ + rpm libnss3 libxkbcommon-x11-0 libxcomposite1 libxdamage1 \ + libxrandr2 libxtst6 libasound2t64 fonts-dejavu-core + + - name: Dependencies of the build + run: npm ci + + # No generator is named, so cmake takes the one of visual studio on + # windows, which finds the compiler of microsoft itself: nothing has to + # put it in the environment beforehand. MinGW, that Qt WebEngine does + # not compile with, is never the one taken. + - name: Configure + run: > + cmake -S . -B build + -DWEBODF_DESKTOP=ON + -DWEBODF_PROGRAMS=ON + -DWEBODF_QTJSRUNTIME=ON + -DCMAKE_BUILD_TYPE=Release + + # The library first: a product built on a library that fails its tests is + # worth nothing. + - name: Tests of the library + env: + QT_QPA_PLATFORM: offscreen + QTWEBENGINE_CHROMIUM_FLAGS: --disable-gpu --no-sandbox + run: cmake --build build --config Release --target webodf.js-tests + + # One target gathers them all in "build/products", and names what this + # machine had no tool to write. + - name: Products + run: cmake --build build --config Release --target products + + # Each machine hands its own products to the release, one file at a + # time: an artifact would wrap them in a zip of its own, and whoever + # downloads a package of debian wants that file and not a zip holding + # it. + # The shell is named: powershell, the one of windows, hands the star + # over as it stands rather than naming the files it stands for. + - name: Products of this machine, in the release + shell: bash + env: + GH_TOKEN: ${{ github.token }} + run: gh release upload ${{ github.ref_name }} build/products/* --clobber diff --git a/.gitignore b/.gitignore index fdf3b92d3..1be0a3f5a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,39 +1,47 @@ -/webodf/tests/simple_slide.odp -#android/bin -#android/gen -nativeQtClient/nativeQtClient.pro.user -programs/firefoxextension/content/web/viewer.html -programs/firefoxextension/install.rdf -programs/ios/WebODF.xcodeproj/project.xcworkspace -programs/ios/WebODF.xcodeproj/xcuserdata -programs/ios/www/ZoomIn.png -programs/ios/www/ZoomOut.png -programs/ios/www/app/ -programs/ios/www/sencha-touch.css -programs/ios/www/sencha-touch.js -programs/ios/www/webodf.js +# What an editor or a system leaves beside the sources. .DS_Store -webodf/webodf.css.js -build -*.sw? .idea -node_modules/.bin/ -node_modules/requirejs/ -webodf/coverage/ -node_modules/karma-chrome-launcher/ -node_modules/karma-coffee-preprocessor/ -node_modules/karma-coverage/ -node_modules/karma-firefox-launcher/ -node_modules/karma-html2js-preprocessor/ -node_modules/karma-jasmine/ -node_modules/karma-junit-reporter/ -node_modules/karma-phantomjs-launcher/ -node_modules/karma-requirejs/ -node_modules/karma-script-launcher/ -node_modules/karma/ -fontFaceDeclsTest.odt -test.odt -simple.odt -simpleFrame.odt -webodf/tests/*.odt -webodf/tests/odf/newloadsave.odt +*.sw? + +# What cmake leaves where it is run: a configuration made in the sources rather +# than in a directory of its own writes these two, and a stale cache in the +# sources makes every later build refuse to configure. +/CMakeCache.txt +/CMakeFiles/ +/cmake.log + +# The build with cmake, that is made in a directory of its own. +build + +# The build with node: its outputs, the tools it downloads and the report of +# the coverage. +dist +.tools +coverage + +# Only the runtime dependency is versioned, see README-Building.md. The tools +# of the build are installed with npm. +node_modules/* +!node_modules/.package-lock.json +!node_modules/@xmldom + +# The css of the viewer, written as a string of javascript by the build. +webodf/webodf.css.js + +# The documents the tests write beside the ones they read. +# What an office leaves beside a document it has open. +.~lock.*# + +# What a test writes, that goes to a directory of its own, see +# "webodf/tests/CMakeLists.txt". +webodf/tests/out/ + +# The library is copied into the assets of the viewer of android. +programs/opendocumentviewer-android/src/main/assets/webodf.js +# The pages of the format are written by cmake from the text of "programs/text", +# among the assets, as the library is: gradle reads them from there. +programs/opendocumentviewer-android/src/main/assets/about.en.html +programs/opendocumentviewer-android/src/main/assets/about.fr.html +programs/opendocumentviewer-android/.gradle +programs/opendocumentviewer-android/build +programs/opendocumentviewer-android/local.properties diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 000000000..7ec25eb71 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,123 @@ +# Builds the products and publishes them with the release of gitlab. Github +# builds and publishes its own, see ".github/workflows/release.yml": neither +# forge pushes a file to the other. +# +# The runners of gitlab.com are of linux, so the products are the ones of +# linux and the ones no platform is needed for: the archive of the desktop, +# the packages of debian and of fedora, the add-ons and the library. The +# viewers of windows and of macos are built by the workflow of github, on the +# machines of those systems. +# +# What needs a key of its own is left out, as there too: the apk of android, +# the bundle of macos and the application of ios. + +stages: + - products + - release + +variables: + # The whole history: the version of the build comes from "git describe", + # which a shallow clone gives nothing to describe. + GIT_DEPTH: "0" + PACKAGE: ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/webodf + +products: + stage: products + # The image carries node 22, which the build asks for, on debian trixie, + # whose packages carry the qt 6.8 the viewer is built against: the node of + # trixie itself is a 20 and is too old. + image: node:22-trixie + rules: + - if: $CI_COMMIT_TAG =~ /^v[0-9]/ + before_script: + - apt-get update + - > + apt-get install --no-install-recommends -y + build-essential cmake ninja-build git curl ca-certificates + default-jre-headless rpm + qt6-base-dev qt6-webengine-dev qt6-webengine-dev-tools + libgl-dev fonts-dejavu-core + - npm ci + script: + - > + cmake -S . -B build + -DWEBODF_DESKTOP=ON + -DWEBODF_PROGRAMS=ON + -DWEBODF_QTJSRUNTIME=ON + -DCMAKE_BUILD_TYPE=Release + # The library first: a product built on a library that fails its tests is + # worth nothing. + - QT_QPA_PLATFORM=offscreen QTWEBENGINE_CHROMIUM_FLAGS="--disable-gpu --no-sandbox" + cmake --build build --target webodf.js-tests + # One target gathers them all in "build/products", and names what this + # machine had no tool to write. + - cmake --build build --target products + - mkdir -p build/products + - npm pack --pack-destination build/products + # Each product goes to the generic package registry of the project, whose + # token is the one of the job: no token of a person is needed. One file of + # json is written beside each product, which is the link the release is + # given, so that the release has only to send them. + - | + version=${CI_COMMIT_TAG#v} + mkdir -p links + for file in build/products/*; do + name=$(basename "$file") + url=$PACKAGE/$version/$name + curl --fail --silent --show-error --upload-file "$file" \ + --header "JOB-TOKEN: $CI_JOB_TOKEN" "$url" + printf '{"name":"%s","url":"%s","link_type":"package","direct_asset_path":"/%s"}' \ + "$name" "$url" "$name" > "links/$name.json" + done + artifacts: + paths: + - build/products + - links + expire_in: 1 week + +release: + stage: release + # The image is the one of the products, for the client of http it holds: the + # image of "release-cli" holds none, and its "update" takes no link, so the + # api of the releases is called instead. + image: node:22-trixie + rules: + - if: $CI_COMMIT_TAG =~ /^v[0-9]/ + needs: + - job: products + artifacts: true + before_script: + - apt-get update + - apt-get install --no-install-recommends -y curl ca-certificates + script: + # A release that stands already is given the links it has not got yet, + # rather than opened again, so that the pipeline can be run a second time + # on the same tag. A link that is there answers 400, which is not a + # failure here. + - | + set -eu + api=${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/releases + auth="JOB-TOKEN: $CI_JOB_TOKEN" + json='Content-Type: application/json' + code=$(curl --silent --output /dev/null --write-out '%{http_code}' \ + --header "$auth" "$api/$CI_COMMIT_TAG") + if [ "$code" = "404" ]; then + links=$(cat links/*.json | sed 's/}{/},{/g') + curl --fail --silent --show-error --request POST \ + --header "$auth" --header "$json" \ + --data "{\"tag_name\":\"$CI_COMMIT_TAG\", + \"name\":\"$CI_COMMIT_TAG\", + \"description\":\"The products of $CI_COMMIT_TAG.\", + \"assets\":{\"links\":[$links]}}" \ + "$api" + else + for link in links/*.json; do + code=$(curl --silent --output /dev/null --write-out '%{http_code}' \ + --request POST --header "$auth" --header "$json" \ + --data @"$link" "$api/$CI_COMMIT_TAG/assets/links") + case $code in + 2*|400) ;; + *) echo "the link of $link was refused ($code)"; exit 1 ;; + esac + done + fi diff --git a/CMakeLists.txt b/CMakeLists.txt index 54a56fe5b..7b8f58226 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,10 +1,16 @@ # This makefile 'compiles' WebODF using various tools, instruments the code and # builds and packages programs that use WebODF. -# WebODF is mostly a JavaScript project. CMake needs to know about the C++ parts -project (WebODF C CXX) +# The project needs cmake 3.10 and works with the policies of cmake 3.24. The +# most useful of them is CMP0135: the archives of the tools are downloaded +# from an url, so the timestamps of the files they hold have to be the ones of +# the extraction, otherwise a change of the url does not rebuild what depends +# on them. Older versions of cmake read the range as its first version only. +cmake_minimum_required(VERSION 3.10...3.24) +message("CMake version: ${CMAKE_VERSION}") -cmake_minimum_required(VERSION 2.8.11) +# WebODF is mostly a JavaScript project. CMake needs to know about the C++ parts +project(WebODF C CXX) # Require separate build dir if (CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR) @@ -38,9 +44,21 @@ else (OVERRULED_WEBODF_VERSION) set(WEBODF_VERSION ${CMAKE_MATCH_1}) else (CMAKE_MATCH_1) # get version number from git - exec_program(git ${CMAKE_CURRENT_SOURCE_DIR} - ARGS describe --tags --dirty --match "v[0-9]*" - OUTPUT_VARIABLE GIT_WEBODF_VERSION) + # A build of a tag is named after that tag alone: a release is what the + # tag says, and a file written in the tree while it is built, which a + # machine of integration does, would else add "-dirty" to the name of + # every product. + execute_process(COMMAND git describe --tags --exact-match --match "v[0-9]*" + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + OUTPUT_VARIABLE GIT_WEBODF_VERSION + OUTPUT_STRIP_TRAILING_WHITESPACE + ERROR_QUIET) + if (NOT GIT_WEBODF_VERSION) + execute_process(COMMAND git describe --tags --dirty --match "v[0-9]*" + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + OUTPUT_VARIABLE GIT_WEBODF_VERSION + OUTPUT_STRIP_TRAILING_WHITESPACE) + endif () # check version number from git string(REGEX MATCH "^v([0-9]+.[0-9]+.[0-9]+((-[0-9]+-[0-9a-z]+)?(-dirty)?)?)$" CHECKED_WEBODF_VERSION "${GIT_WEBODF_VERSION}") if (CMAKE_MATCH_1) @@ -52,29 +70,95 @@ else (OVERRULED_WEBODF_VERSION) endif (OVERRULED_WEBODF_VERSION) message(STATUS "WebODF version " ${WEBODF_VERSION}) +# The version is read once, when the build is configured, and it names the +# packages, so a build made after a commit would keep the version of the one +# before. Reading these two files here ties the configuration to them: cmake +# runs itself again, before make, as soon as a commit or a checkout writes +# them, and the version follows the sources without anything to remember. +foreach (GIT_FILE .git/HEAD .git/index) + if (EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${GIT_FILE}) + configure_file(${CMAKE_CURRENT_SOURCE_DIR}/${GIT_FILE} + ${CMAKE_CURRENT_BINARY_DIR}/git-watch/${GIT_FILE} COPYONLY) + endif() +endforeach() + +# The manifest of an add-on only takes up to four numbers separated by dots, +# where git describe adds the number of commits and the hash of the last one: +# "0.5.10-161-gc2572a4a" is written "0.5.10.161", so that the builds between +# two tags still follow each other. A tagged build is left as it is. +string(REGEX REPLACE "^([0-9]+(\\.[0-9]+)*)-([0-9]+)-g[0-9a-f]+.*$" "\\1.\\3" + WEBODF_MANIFEST_VERSION "${WEBODF_VERSION}") +string(REGEX REPLACE "-dirty$" "" WEBODF_MANIFEST_VERSION "${WEBODF_MANIFEST_VERSION}") + ######################################################### ## Find installed dependencies ######################################################### -set(QT_MIN_VERSION "5.1.1") -find_package(Qt5Network) -find_package(Qt5Xml) -find_package(Qt5PrintSupport) -find_package(Qt5WebKitWidgets) +# qtjsruntime runs the tests in the webengine of qt, see "webodf/tests". It is +# opt-in, as it needs the modules of qt, that weigh more than the rest of the +# build together, while "npm run test:browser" runs the same tests in a browser +# that is installed anyway. Without the option, the modules are not looked for +# at all, so no warning is reported for something that is not asked for. +# +# It ran in Qt WebKit until 2026, that Qt dropped in 5.6, in 2016, and that +# Debian stopped packaging in 13: the program was rewritten for webengine, that +# is the blink of chromium, see "programs/qtjsruntime". +option(WEBODF_QTJSRUNTIME "Build qtjsruntime, that runs the tests in the webengine of qt" OFF) + +# The viewer of OpenDocument for the desktop is a window of qt around the page +# the library draws in, see "programs/opendocumentviewer-desktop". It is opt-in +# for the same reason, and it is built by the option alone, without +# WEBODF_PROGRAMS, as it needs neither Dojo nor the editors. +option(WEBODF_DESKTOP "Build the viewer of OpenDocument for the desktop, in qt" OFF) + +# 6.4 is the oldest version the two programs are written for, the one of Debian +# 12. Everything they use was already in the first release of Qt 6: a channel to +# the page, a collection of scripts on the profile, a handler of a url scheme, +# and printing to a pdf. +set(BUILD_QTJSRUNTIME FALSE) +set(BUILD_DESKTOP FALSE) +if (WEBODF_QTJSRUNTIME OR WEBODF_DESKTOP) + find_package(Qt6 6.4 COMPONENTS Core Gui Widgets PrintSupport WebChannel WebEngineCore WebEngineWidgets) + + if (Qt6_FOUND) + set(BUILD_QTJSRUNTIME ${WEBODF_QTJSRUNTIME}) + set(BUILD_DESKTOP ${WEBODF_DESKTOP}) + else () + message(FATAL_ERROR "Qt6 with the modules Core Gui Widgets PrintSupport WebChannel WebEngineCore WebEngineWidgets was not found, so neither qtjsruntime nor the viewer for the desktop can be built. On Debian 12 and later: apt-get install qt6-base-dev qt6-base-dev-tools qt6-webchannel-dev qt6-webengine-dev qt6-webengine-dev-tools") + endif () +endif () -if (Qt5Network_FOUND AND Qt5Xml_FOUND AND Qt5PrintSupport_FOUND AND Qt5WebKitWidgets_FOUND) - set(BUILD_QTJSRUNTIME TRUE) +# Java runs the closure compiler and rhino, that check the types of the +# sources and run the tests in a second engine. Neither writes the library: +# terser does, with node. So java is looked for and not required, and a +# machine without it builds everything but the checks, which is what a build +# in a sandbox does, see the flatpak of the viewer. +find_package(Java COMPONENTS Runtime) +if (Java_JAVA_EXECUTABLE) + set(WEBODF_HAS_JAVA TRUE) else () - message(WARNING "Qt5 with modules Qt5Network Qt5Xmle Qt5PrintSupport Qt5WebKitWidgets was not found. qtjsruntime will no be built.") - set(BUILD_QTJSRUNTIME FALSE) + set(WEBODF_HAS_JAVA FALSE) + message(STATUS "No java was found: the types of the sources are not checked, and the tests are not run in rhino.") endif () -# java runtime is needed for Closure Compiler -find_package(Java COMPONENTS Runtime REQUIRED) +# A build that is given a library that was built already builds nothing of it: +# it neither runs node nor asks for it. That is what a build in a sandbox +# does, where there is neither node nor a network to install its tools from, +# see the flatpak of the viewer of the desktop. +set(WEBODF_PREBUILT_LIBRARY "" CACHE FILEPATH + "A webodf.js that was built already, to be used instead of building one") +if (WEBODF_PREBUILT_LIBRARY AND NOT EXISTS "${WEBODF_PREBUILT_LIBRARY}") + message(FATAL_ERROR + "WEBODF_PREBUILT_LIBRARY names ${WEBODF_PREBUILT_LIBRARY}, which is not there.") +endif () # Node.js will be downloaded on Windows systems, so check for installed version is below -SET(REQUIRED_NODEJS_VERSION 0.10.5) +# 22.22.2 is the oldest release of node jsdom runs on, that the tests need. +# The tools of the build are tested with 24, the release under long term +# support. The exact range is in the field "engines" of "package.json", that +# cmake cannot express. +SET(REQUIRED_NODEJS_VERSION 22.22.2) ######################################################### @@ -92,92 +176,104 @@ else ( IS_DIRECTORY $ENV{WEBODF_DOWNLOAD_DIR} ) endif ( IS_DIRECTORY $ENV{WEBODF_DOWNLOAD_DIR} ) MESSAGE ( STATUS "external downloads will be stored/expected in: ${EXTERNALS_DOWNLOAD_DIR}" ) +# The two jars are downloaded for java to run them: a build without java, or +# one that was given a library and checks nothing, asks nothing of the +# network. A sandbox has no network at all, see the flatpak of the viewer. +if (WEBODF_HAS_JAVA AND NOT WEBODF_PREBUILT_LIBRARY) + # Closure Compiler ExternalProject_Add( ClosureCompiler DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} - URL "http://dl.google.com/closure-compiler/compiler-20160911.tar.gz" - URL_MD5 7e85253436e492aa580ce3c3b8f585e7 + URL "https://repo1.maven.org/maven2/com/google/javascript/closure-compiler/v20260804/closure-compiler-v20260804.jar" + # Without the hash, cmake has no way to tell whether the file it already has + # is the right one, so it downloads it again at every build, see the target + # Gradle below. + URL_HASH SHA256=0cb86a4b96769c679b4fc2d2dc0e5acd0abd0dff932945b90fbe327247466a29 + DOWNLOAD_NO_EXTRACT TRUE CONFIGURE_COMMAND "" BUILD_COMMAND "" INSTALL_COMMAND "" ) -set(CLOSURE_JAR ${CMAKE_BINARY_DIR}/ClosureCompiler-prefix/src/ClosureCompiler/closure-compiler-v20160911.jar) +# Same version as the build with node, see scripts/lib/closure.js. +set(CLOSURE_JAR ${EXTERNALS_DOWNLOAD_DIR}/closure-compiler-v20260804.jar) # Rhino ExternalProject_Add( Rhino DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} - URL "http://ftp.mozilla.org/pub/js/rhino1_7R3.zip" - URL_MD5 99d94103662a8d0b571e247a77432ac5 + URL "https://repo1.maven.org/maven2/org/mozilla/rhino-all/1.9.1/rhino-all-1.9.1.jar" + URL_HASH SHA256=1cc2b468a51857747dcb29ae533e352a2abc04e81c5aa61e397dc774dd395329 + DOWNLOAD_NO_EXTRACT TRUE CONFIGURE_COMMAND "" BUILD_COMMAND "" INSTALL_COMMAND "" ) -set(RHINO ${CMAKE_BINARY_DIR}/Rhino-prefix/src/Rhino/js.jar) +# Same version as the build with node, see scripts/lib/rhino.js. +set(RHINO ${EXTERNALS_DOWNLOAD_DIR}/rhino-all-1.9.1.jar) -# JSDoc -ExternalProject_Add( - JsDoc - DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} - URL "https://storage.googleapis.com/google-code-archive-downloads/v2/code.google.com/jsdoc-toolkit/jsdoc_toolkit-2.4.0.zip" - URL_MD5 a8f78f5ecd24b54501147b2af341a231 - CONFIGURE_COMMAND "" - BUILD_COMMAND "" - INSTALL_COMMAND "" +endif () + + +# Node.js is expected to be installed, on every platform. Windows used to +# download a single node.exe, but the build needs npm as well, that comes with +# the installer only, and the url that binary was at holds the releases of +# 2013 alone. +SET(NODEJS_VERSION 0.0.0) +# Debian uses nodejs as binary name, due to conflict with node package (Amateur Packet Radio Node Program) +# https://github.com/joyent/node/wiki/Installing-Node.js-via-package-manager +FIND_PROGRAM(NODEJS_EXECUTABLE + NAMES nodejs node + PATHS "" + DOC "Path to Node.js executable" ) -set(JSDOCDIR ${CMAKE_BINARY_DIR}/JsDoc-prefix/src/JsDoc/jsdoc-toolkit) - -# Node.js -set(NODEVERSION 0.10.20) -if(WIN32) - # On windows, it is significantly faster and more reliable to download - # a pre-built 32-bit binary - set(NODE_BIN_URL "http://nodejs.org/dist/v${NODEVERSION}/node.exe") - set(NODE_BIN_MD5 "3bc43fbbfcddc376d5769e9757bd0bca") - file(DOWNLOAD "${NODE_BIN_URL}" "${EXTERNALS_DOWNLOAD_DIR}/node-download.exe" - SHOW_PROGRESS - EXPECTED_MD5 ${NODE_BIN_MD5} - ) - set(NODE ${CMAKE_BINARY_DIR}/NodeJS-prefix/bin/node.exe) - set(NPM ${CMAKE_BINARY_DIR}/NodeJS-prefix/bin/npm.exe) - execute_process(COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_BINARY_DIR}/NodeJS-prefix - COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_BINARY_DIR}/NodeJS-prefix/bin - COMMAND ${CMAKE_COMMAND} -E copy ${EXTERNALS_DOWNLOAD_DIR}/node-download.exe "${NODE}" - ) - add_custom_target(NodeJS DEPENDS "${NODE}") -else(WIN32) - SET(NODEJS_VERSION 0.0.0) - # Debian uses nodejs as binary name, due to conflict with node package (Amateur Packet Radio Node Program) - # https://github.com/joyent/node/wiki/Installing-Node.js-via-package-manager - FIND_PROGRAM(NODEJS_EXECUTABLE - NAMES nodejs node - PATHS "" - DOC "Path to Node.js executable" - ) - IF(NODEJS_EXECUTABLE) - EXEC_PROGRAM(${NODEJS_EXECUTABLE} ARGS --version OUTPUT_VARIABLE NODEJS_VERSION) - string(SUBSTRING ${NODEJS_VERSION} 1 -1 NODEJS_VERSION) - MESSAGE (STATUS "Installed Node.js found: ${NODEJS_EXECUTABLE} - ${NODEJS_VERSION}") - ELSE(NODEJS_EXECUTABLE) - MESSAGE (STATUS "No installed Node.js found. On platforms other than Windows, Node.js is not downloaded, but expected to be installed.") - ENDIF(NODEJS_EXECUTABLE) - - # fail if NodeJS version requirement is not satisfied - if (${NODEJS_VERSION} VERSION_LESS ${REQUIRED_NODEJS_VERSION}) - message(FATAL_ERROR "Node.js is required in version " ${REQUIRED_NODEJS_VERSION} " or later") - else() - message(STATUS "good Node.js found: " ${NODEJS_VERSION} " (" ${REQUIRED_NODEJS_VERSION} " required.)") - endif() - set(NODE ${NODEJS_EXECUTABLE}) - FIND_PROGRAM(NPM - NAMES npm - PATHS "" - DOC "Path to NPM executable" - ) - MESSAGE (STATUS "npm found: " ${NPM}) - add_custom_target(NodeJS DEPENDS "${NODEJS_EXECUTABLE}") -endif(WIN32) +IF(NODEJS_EXECUTABLE) + execute_process(COMMAND ${NODEJS_EXECUTABLE} --version + OUTPUT_VARIABLE NODEJS_VERSION + ERROR_VARIABLE NODEJS_SAID + RESULT_VARIABLE NODEJS_ANSWERED + OUTPUT_STRIP_TRAILING_WHITESPACE) + # A node that is there and answers nothing is a node that cannot be used, + # and it is said as much: the version was read as it stood, so an answer + # of nothing ended the build on "string sub-command SUBSTRING requires + # four arguments", which tells a reader nothing of node. + if (NOT NODEJS_ANSWERED EQUAL 0 OR NODEJS_VERSION STREQUAL "") + message(FATAL_ERROR "Node.js was found at " ${NODEJS_EXECUTABLE} + " but it answered nothing when asked for its version" + " (\"${NODEJS_EXECUTABLE} --version\" said \"${NODEJS_SAID}\")." + "\nThe path may be one that cmake kept from a build that went" + " before, of a node that is no longer installed: run it again" + " with -U NODEJS_EXECUTABLE -U NPM.") + endif () + string(SUBSTRING "${NODEJS_VERSION}" 1 -1 NODEJS_VERSION) + MESSAGE (STATUS "Installed Node.js found: ${NODEJS_EXECUTABLE} - ${NODEJS_VERSION}") +ELSE(NODEJS_EXECUTABLE) + MESSAGE (STATUS "No installed Node.js found. Node.js is not downloaded, but expected to be installed.") +ENDIF(NODEJS_EXECUTABLE) + +# fail if NodeJS version requirement is not satisfied. find_program() caches +# what it finds, so a node that was installed later, by nvm for instance, is +# only seen once the entry is dropped: the path is named here, as it is often +# an older one that is still in the cache. +if (WEBODF_PREBUILT_LIBRARY) + message(STATUS "The library is taken from ${WEBODF_PREBUILT_LIBRARY}: node is not needed.") +elseif (NODEJS_VERSION VERSION_LESS REQUIRED_NODEJS_VERSION) + message(FATAL_ERROR "Node.js is required in version " + ${REQUIRED_NODEJS_VERSION} " or later, but " + ${NODEJS_EXECUTABLE} " is " ${NODEJS_VERSION} + ".\nIf a newer node is installed, cmake kept the path it found before:" + " run it again with -U NODEJS_EXECUTABLE -U NPM.") +else() + message(STATUS "good Node.js found: " ${NODEJS_VERSION} " (" ${REQUIRED_NODEJS_VERSION} " required.)") +endif() +set(NODE ${NODEJS_EXECUTABLE}) +FIND_PROGRAM(NPM + NAMES npm + PATHS "" + DOC "Path to NPM executable" +) +MESSAGE (STATUS "npm found: " ${NPM}) +add_custom_target(NodeJS DEPENDS "${NODEJS_EXECUTABLE}") # copy node_modules directory from source to build # (this is needed if a module is required in there) @@ -190,90 +286,108 @@ add_custom_target(copy_node_modules ALL COMMENT copying node_modules from source to build ) -# Android -# If android sdk is properly installed, cmake only needs to know where to find -# the executable 'android'. -# The variable ANDROID_SDK_DIR can be provided if a specific android sdk version -# is desired or if the android executable is not in the path. -if (ANDROID_SDK_DIR) - set(ANDROID ${ANDROID_SDK_DIR}/tools/android) -else (ANDROID_SDK_DIR) - find_program(ANDROID NAMES android DOC "Path to the Android executable.") -endif(ANDROID_SDK_DIR) -FIND_PROGRAM(ANT NAMES ant DOC "Path to the Ant executable.") -if(ANDROID AND ANT) - set(BUILD_APK TRUE) - message(STATUS "Found Android and Ant: building an APK for Android.") - message(STATUS "android: ${ANDROID}") - message(STATUS "ant: ${ANT}") -else() - message(STATUS "Android was not found: APK will not be built.") -endif() +# The coverage is measured by c8, that reads the one V8 records while it runs +# the tests: it neither parses nor rewrites the sources, so it does not +# constrain the version of ECMAScript the library is written in. It replaces +# JSCoverage, whose last release, 0.5.1 of 2010, bundles SpiderMonkey 1.7 and +# does not build any more with gcc 14 or clang 16. +# +# It is a target of its own, never built by "all", as it is only useful to +# measure the coverage and it runs the whole suite of the tests again. +add_custom_target(coverage + COMMAND ${NPM} run coverage + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + COMMENT "Measuring the coverage of the tests with c8" + USES_TERMINAL +) + +# The programs, the editors and the extensions, need Dojo, that is downloaded +# from the registry of npm, see below. They are built only when they are asked +# for, so that the library builds without them. +option(WEBODF_PROGRAMS "Build the editors and the extensions of programs/" OFF) + -# JSCoverage -if(WIN32) - # JSCoverage only builds with Cygwin/MiniGW - # Rather than force a dependency on a specific compiler, download binaries - ExternalProject_Add( - JSCoverage - DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} - URL "http://siliconforks.com/jscoverage/download/jscoverage-0.5.1-windows.zip" - CONFIGURE_COMMAND "" - BUILD_COMMAND "" - INSTALL_COMMAND "" - TEST_COMMAND "" - ) - set(JSCOVERAGE ${CMAKE_BINARY_DIR}/JSCoverage-prefix/src/JSCoverage/jscoverage) -elseif(APPLE) -else() - ExternalProject_Add( - JSCoverage - DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} - URL "http://siliconforks.com/jscoverage/download/jscoverage-0.5.1.tar.bz2" - URL_MD5 a70d79a6759367fbcc0bcc18d6866ff3 - PATCH_COMMAND cat ${CMAKE_CURRENT_SOURCE_DIR}/JSCoverage.patch | patch -p1 - CONFIGURE_COMMAND "./configure" - BUILD_COMMAND make -j${NProcessors} - BUILD_IN_SOURCE 1 - INSTALL_COMMAND "" - ) - set(JSCOVERAGE ${CMAKE_BINARY_DIR}/JSCoverage-prefix/src/JSCoverage/jscoverage) -endif() # Dojo +# The source release of dojotoolkit.org is not published any more, so the four +# packages are taken from the registry of npm, where they are still released. +set(DOJO_VERSION 1.17.3) +if (WEBODF_PROGRAMS) ExternalProject_Add( - Dojo + DojoCore DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} - URL "http://download.dojotoolkit.org/release-1.8.1/dojo-release-1.8.1-src.tar.gz" - URL_MD5 9b80b9a736b81c336accd832f3c3aea2 + URL "https://registry.npmjs.org/dojo/-/dojo-${DOJO_VERSION}.tgz" + # Without the hash, cmake downloads the archive again at every build, as it + # cannot tell whether the one it holds is the right one. + URL_HASH SHA256=84d7e0e59a024885631276f2ae44064b778fbb83d12ecfa0d3fc93f44c9d38b1 CONFIGURE_COMMAND "" BUILD_COMMAND "" INSTALL_COMMAND "" ) -set(DOJO ${CMAKE_BINARY_DIR}/Dojo-prefix/src/Dojo) - -# Dojo requires a patch on windows -if(WIN32) - set(DOJOPATCH_URL "http://bugs.dojotoolkit.org/raw-attachment/ticket/15413/node-win-1.8.patch") - set(DOJOPATCH_MD5 "51eae664ddbe919c28c4e3082748cd19") - set(DOJOPATCH ${EXTERNALS_DOWNLOAD_DIR}/dojo-node.patch) - - file(DOWNLOAD "${DOJOPATCH_URL}" "${DOJOPATCH}" - SHOW_PROGRESS - EXPECTED_MD5 ${DOJOPATCH_MD5} - ) - - - ExternalProject_Add_Step(Dojo applyPatch - COMMAND cat ${DOJOPATCH} | patch -p0 -d ${CMAKE_BINARY_DIR}/Dojo-prefix/src/Dojo/util/ - DEPENDEES build - ) -endif(WIN32) +ExternalProject_Add( + DojoDijit + DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} + URL "https://registry.npmjs.org/dijit/-/dijit-${DOJO_VERSION}.tgz" + URL_HASH SHA256=d1e0e41d21ebfd0214ec4c1956816d04efcdf68a84c4aa85600a7c34846b5a26 + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND "" +) +ExternalProject_Add( + DojoDojox + DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} + URL "https://registry.npmjs.org/dojox/-/dojox-${DOJO_VERSION}.tgz" + URL_HASH SHA256=ba6fa9e334a9519247afc7cf52a72f57458078d8e4ef1074ee4ca4e826b7203e + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND "" +) +ExternalProject_Add( + DojoUtil + DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} + URL "https://registry.npmjs.org/dojo-util/-/dojo-util-${DOJO_VERSION}.tgz" + URL_HASH SHA256=1811801438bd81f306834b2c864d6345d649797a32088b3333a7f6732a97e484 + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND "" +) +add_custom_target(Dojo DEPENDS DojoCore DojoDijit DojoDojox DojoUtil) +set(DOJO_CORE ${CMAKE_BINARY_DIR}/DojoCore-prefix/src/DojoCore) +set(DOJO_DIJIT ${CMAKE_BINARY_DIR}/DojoDijit-prefix/src/DojoDijit) +set(DOJO_DOJOX ${CMAKE_BINARY_DIR}/DojoDojox-prefix/src/DojoDojox) +set(DOJO_UTIL ${CMAKE_BINARY_DIR}/DojoUtil-prefix/src/DojoUtil) +endif (WEBODF_PROGRAMS) ############################## ## Define custom macros ############################## +# The pages of the products tell what the OpenDocument format is worth, and they +# tell it in the same words: the text is written once, in "programs/text", and +# inserted where a page names it, "@FORMAT_TEXT@". A page is a template, +# "*.html.in", and the page itself is written into the build directory. +# +# The text is read here, and it is named as a dependency of the configuration, +# so that cmake runs itself again when it is revised: the pages then follow +# without anything to remember. +set(TEXT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/programs/text) +foreach (LANGUAGE en fr) + file(READ ${TEXT_DIR}/format.${LANGUAGE}.html FORMAT_TEXT_${LANGUAGE}) + # The newline an editor writes at the end of a file would become a blank line + # in every page, so the text is taken without what closes it. + string(REGEX REPLACE "[ \t\r\n]+$" "" FORMAT_TEXT_${LANGUAGE} + "${FORMAT_TEXT_${LANGUAGE}}") + set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS + ${TEXT_DIR}/format.${LANGUAGE}.html) +endforeach () + +# INSERT_TEXT (template output language) +# Writes the page of a product, with the text of the format in it. +macro(INSERT_TEXT _template _output _language) + set(FORMAT_TEXT "${FORMAT_TEXT_${_language}}") + configure_file(${_template} ${_output} @ONLY) +endmacro(INSERT_TEXT) + # COPY_FILES (varname srcdir tgtdir files) # Creates a target that copies the listed files from the srcdir to the tgtdir, # preserving their relative path. @@ -323,13 +437,31 @@ set(LIBJSLICENSEFILE ${CMAKE_CURRENT_SOURCE_DIR}/AGPL-3.0.txt) set(WEBODFJS_DIR ${CMAKE_CURRENT_BINARY_DIR}/webodf.js-${WEBODF_VERSION}) set(WEBODFJS_ZIP webodf.js-${WEBODF_VERSION}.zip) -set(WODOTEXTEDITORBUILDDIR ${CMAKE_CURRENT_BINARY_DIR}/wodotexteditor) - -set(WODOCOLLABTEXTEDITORBUILDDIR ${CMAKE_BINARY_DIR}/wodocollabtexteditor) -set(WODOCOLLABTEXTEDITOR_ZIP ${CMAKE_CURRENT_BINARY_DIR}/wodocollabtexteditor-${WEBODF_VERSION}.zip) - -set(FIREFOX_EXTENSION_ODFVIEWER_DIR ${CMAKE_CURRENT_BINARY_DIR}/firefox-extension-odfviewer-${WEBODF_VERSION}) -set(FIREFOX_EXTENSION_ODFVIEWER ${FIREFOX_EXTENSION_ODFVIEWER_DIR}.xpi) +set(OPENDOCUMENTTEXTEDITORBUILDDIR ${CMAKE_CURRENT_BINARY_DIR}/opendocumenttexteditor) + +set(OPENDOCUMENTTEXTCOLLABBUILDDIR ${CMAKE_BINARY_DIR}/opendocumenttextcollab) +set(OPENDOCUMENTTEXTCOLLAB_ZIP ${CMAKE_CURRENT_BINARY_DIR}/opendocumenttextcollab-${WEBODF_VERSION}.zip) + +# The add-on is packed twice, from the same scripts: the manifest version 3 +# needs Firefox 109, of 2023, and the version 2 reaches back to Firefox 48, of +# 2016. See "README-Products.md". +set(OPENDOCUMENTVIEWER_FIREFOX_DIR ${CMAKE_CURRENT_BINARY_DIR}/opendocumentviewer-firefox-${WEBODF_VERSION}) +set(OPENDOCUMENTVIEWER_FIREFOX ${OPENDOCUMENTVIEWER_FIREFOX_DIR}.xpi) +set(OPENDOCUMENTVIEWER_FIREFOX_MV2_DIR ${CMAKE_CURRENT_BINARY_DIR}/opendocumentviewer-firefox-mv2-${WEBODF_VERSION}) +set(OPENDOCUMENTVIEWER_FIREFOX_MV2 ${OPENDOCUMENTVIEWER_FIREFOX_MV2_DIR}.xpi) +# Chrome takes a zip, and a file of rules rather than a script: it dropped the +# blocking webRequest the two others redirect with. +set(OPENDOCUMENTVIEWER_CHROME_DIR ${CMAKE_CURRENT_BINARY_DIR}/opendocumentviewer-chrome-${WEBODF_VERSION}) +set(OPENDOCUMENTVIEWER_CHROME ${OPENDOCUMENTVIEWER_CHROME_DIR}.zip) + +# Thunderbird reads the attachments of the messages, which no request carries, +# so it gets a script of its own. The version 3 needs Thunderbird 128, of 2024, +# and the version 2 reaches back to the 98, of 2022, the first that opens a +# menu on an attachment. +set(OPENDOCUMENTVIEWER_THUNDERBIRD_DIR ${CMAKE_CURRENT_BINARY_DIR}/opendocumentviewer-thunderbird-${WEBODF_VERSION}) +set(OPENDOCUMENTVIEWER_THUNDERBIRD ${OPENDOCUMENTVIEWER_THUNDERBIRD_DIR}.xpi) +set(OPENDOCUMENTVIEWER_THUNDERBIRD_MV2_DIR ${CMAKE_CURRENT_BINARY_DIR}/opendocumentviewer-thunderbird-mv2-${WEBODF_VERSION}) +set(OPENDOCUMENTVIEWER_THUNDERBIRD_MV2 ${OPENDOCUMENTVIEWER_THUNDERBIRD_MV2_DIR}.xpi) #################### @@ -345,14 +477,150 @@ else() endif() + +############################# +## The products of the build +############################# + +# A product is a file someone is given: an add-on, a zip of the library, an +# apk. Each one is written where the target that packs it stands, so the list +# is kept here: the target "products" packs them all and tells where each one +# was written, as the paths are of no help to remember. The target is named +# beside the path, so that the list follows the targets and not the files, +# whose names hold the version of the build. +set_property(GLOBAL PROPERTY WEBODF_PRODUCTS "") +set_property(GLOBAL PROPERTY WEBODF_PRODUCT_NAMES "") +set_property(GLOBAL PROPERTY WEBODF_PRODUCT_TARGETS "") +set_property(GLOBAL PROPERTY WEBODF_PRODUCTS_MISSING "") +macro(WEBODF_PRODUCT target path) + set_property(GLOBAL APPEND PROPERTY WEBODF_PRODUCTS ${path}) + set_property(GLOBAL APPEND PROPERTY WEBODF_PRODUCT_TARGETS ${target}) + get_filename_component(WEBODF_PRODUCT_NAME_OF ${path} NAME) + set_property(GLOBAL APPEND PROPERTY WEBODF_PRODUCT_NAMES + ${WEBODF_PRODUCT_NAME_OF}) +endmacro(WEBODF_PRODUCT) + +# A product a tool of the machine writes among the products itself, whose name +# that tool settles: it is named in the list and not copied again, and it is +# looked for by the target rather than by its path. +macro(WEBODF_PRODUCT_MADE target name) + set_property(GLOBAL APPEND PROPERTY WEBODF_PRODUCT_TARGETS ${target}) + set_property(GLOBAL APPEND PROPERTY WEBODF_PRODUCT_NAMES ${name}) +endmacro(WEBODF_PRODUCT_MADE) + +# What this machine has no tool to make: it is named at the end beside what +# was made, so that a reader of the list is not left wondering whether a +# product is missing because it failed or because nothing here could write it. +macro(WEBODF_PRODUCT_MISSING what tool) + set_property(GLOBAL APPEND PROPERTY WEBODF_PRODUCTS_MISSING + "${what}, for want of ${tool}") +endmacro(WEBODF_PRODUCT_MISSING) + ############################# ## Build Library and programs ############################# # the lib -add_subdirectory(webodf) +if (WEBODF_PREBUILT_LIBRARY) + # The library was built already and is taken as it is: nothing of it is + # read, checked or written here, and neither node nor java is asked for. + # A program of this build reads it where it would have been written. + add_custom_command( + OUTPUT ${CMAKE_BINARY_DIR}/webodf/webodf.js + COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_BINARY_DIR}/webodf + COMMAND ${CMAKE_COMMAND} -E copy_if_different + ${WEBODF_PREBUILT_LIBRARY} ${CMAKE_BINARY_DIR}/webodf/webodf.js + DEPENDS ${WEBODF_PREBUILT_LIBRARY} + COMMENT "The library, taken from ${WEBODF_PREBUILT_LIBRARY}") + add_custom_target(webodf.js-target + DEPENDS ${CMAKE_BINARY_DIR}/webodf/webodf.js) +else () + add_subdirectory(webodf) +endif () # the programs/components -add_subdirectory(programs) +if (WEBODF_PROGRAMS) + add_subdirectory(programs) +else () + # Two programs are built by their own option, without the others: the tests + # of the library are run with the first one, and the second needs neither + # Dojo nor the editors. + if (BUILD_QTJSRUNTIME) + add_subdirectory(programs/qtjsruntime) + endif () + if (BUILD_DESKTOP) + add_subdirectory(programs/opendocumentviewer-desktop) + endif () +endif () + +# The target that runs every test of this build, beside "products" that packs +# every product: the tests of the library, in each engine the build has, and +# the ones of the programs that carry some. +add_custom_target(tests + COMMAND ${CMAKE_COMMAND} -E echo "" + COMMAND ${CMAKE_COMMAND} -E echo "Every test of this build has run.") +# A build that was given a library reads none of its sources, so there is +# nothing of the library to test here: only the programs are. +if (TARGET webodf.js-tests) + add_dependencies(tests webodf.js-tests) +endif () +foreach (WEBODF_TEST_TARGET + test-opendocumenttexteditor + test-opendocumenttextcollab + test-opendocumentviewer-webext + test-opendocumentviewer-thunderbird) + if (TARGET ${WEBODF_TEST_TARGET}) + add_dependencies(tests ${WEBODF_TEST_TARGET}) + endif () +endforeach () + +# The target that packs every product of this build and gathers them in one +# place. Each product is written where the target that packs it stands, deep +# in the tree of the build, so "make products" or "ninja products" copies them +# all into "products/" and tells what is there: no one has to remember where +# an add-on or an apk was written. +get_property(WEBODF_PRODUCT_FILES GLOBAL PROPERTY WEBODF_PRODUCTS) +get_property(WEBODF_PRODUCT_TARGET_LIST GLOBAL PROPERTY WEBODF_PRODUCT_TARGETS) +if (WEBODF_PRODUCT_FILES) + set(WEBODF_PRODUCTS_DIR ${CMAKE_BINARY_DIR}/products) + get_property(WEBODF_PRODUCT_NAME_LIST GLOBAL PROPERTY WEBODF_PRODUCT_NAMES) + string(REPLACE ";" "," WEBODF_PRODUCT_NAMES_ARG "${WEBODF_PRODUCT_NAME_LIST}") + # What a build that went before left in the directory is taken away: the + # products carry the version they were built from, so they would pile up + # under names of their own, and a reader would not know which build a + # product is of. + set(WEBODF_PRODUCT_STEPS + COMMAND ${CMAKE_COMMAND} -E make_directory ${WEBODF_PRODUCTS_DIR} + COMMAND ${CMAKE_COMMAND} + -DDIR=${WEBODF_PRODUCTS_DIR} + "-DKEEP=${WEBODF_PRODUCT_NAMES_ARG}" + -P ${CMAKE_CURRENT_SOURCE_DIR}/scripts/cmake/keep-products.cmake) + foreach (WEBODF_PRODUCT_FILE ${WEBODF_PRODUCT_FILES}) + list(APPEND WEBODF_PRODUCT_STEPS + COMMAND ${CMAKE_COMMAND} -E copy_if_different + ${WEBODF_PRODUCT_FILE} ${WEBODF_PRODUCTS_DIR}) + endforeach () + list(APPEND WEBODF_PRODUCT_STEPS + COMMAND ${CMAKE_COMMAND} -E echo "" + COMMAND ${CMAKE_COMMAND} -E echo + "The products of this build are in ${WEBODF_PRODUCTS_DIR}:") + foreach (WEBODF_PRODUCT_NAME ${WEBODF_PRODUCT_NAME_LIST}) + list(APPEND WEBODF_PRODUCT_STEPS + COMMAND ${CMAKE_COMMAND} -E echo " ${WEBODF_PRODUCT_NAME}") + endforeach () + get_property(WEBODF_MISSING GLOBAL PROPERTY WEBODF_PRODUCTS_MISSING) + if (WEBODF_MISSING) + list(APPEND WEBODF_PRODUCT_STEPS + COMMAND ${CMAKE_COMMAND} -E echo "" + COMMAND ${CMAKE_COMMAND} -E echo + "This machine has no tool to make:") + foreach (WEBODF_MISSING_ONE ${WEBODF_MISSING}) + list(APPEND WEBODF_PRODUCT_STEPS + COMMAND ${CMAKE_COMMAND} -E echo " ${WEBODF_MISSING_ONE}") + endforeach () + endif () + add_custom_target(products ${WEBODF_PRODUCT_STEPS}) + add_dependencies(products ${WEBODF_PRODUCT_TARGET_LIST}) +endif () # vim:expandtab diff --git a/ChangeLog.md b/ChangeLog.md index d2be39134..e60a5a050 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,10 +1,86 @@ +# Changes between 0.5.10 and 0.6.0 + +## WebODF + +### Features + +* A text is drawn over pages, as it is printed, and no longer as one run of + text: each page is a box of its own, of the size the document writes, with + the header and the foot of its master page. A paragraph and a table that + cross the end of a page are cut there. +* The pages are read one at a time or two to a row, as a book is read, and the + first page may stand alone on the right. +* The lists, the headings and the chapters are numbered as an office numbers + them, from the outline style of the document and from the list styles, and + the numbers run on from page to page. +* The tabs of a text are laid at the stops its style writes, left, centre, + right and character. +* The notes of the foot are drawn at the foot of the page their number stands + on, and the text of that page is shorter by as much. +* A page and a section are written in the columns they ask for. +* The formulas a text holds are drawn, read from the MathML the package + carries, where an office writes an image beside them that a package need not + hold. +* The links of a document are followed when a reader clicks one. +* The spacings of two entries of an index are added, as an office adds them: + the table of contents of the schema of OpenDocument is drawn on the pages an + office draws it on, and every chapter begins within a page of where an office + begins it. + +### Fixes + +* A document that holds no styles, no automatic styles and no master styles is + drawn, where a reader looked for a page layout in nothing at all and threw. +* A table wider than the text of the page is drawn to the width of the text, + and a word longer than its column is broken, as an office breaks it. +* A table whose style says it may not be cut between two of its rows is written + whole on the page that follows. +* A tab stop written for a page of another size is drawn against the edge of + the text rather than in the margin. +* The fonts of a document are asked for by name before the text is broken into + pages, as an engine may hold its fonts ready before it draws with them. + +### Performance + +* A page is measured in one reading rather than one node at a time, and read + from what was written last rather than from its head. +* The first pages are drawn as soon as they are broken, and the rest follow a + few at a time. +* The style a paragraph names is read once, and an element of thousands of + nodes is parted before it is written on a page. + +## Products + +* A viewer of OpenDocument for the desktop, in qt, with its AppImage, its + flatpak, its deb, its rpm and its installer of windows. +* The add-ons of firefox, of chrome and of thunderbird, in manifest v2 and v3. +* A viewer for android and one for ios. +* Docnosis reads a document against the schema of the standard. +* Every product of the build is made by one command, and the build says what is + wanting where a tool is not installed. + +## Documentation + +* What a program may lean on in the library is written in "PUBLIC-API.md". +* The readmes say how each product is built, tried and handed over, and what + each of them runs on. + +# Changes between 0.5.9 and 0.5.10 + +## WebODF + +### Fixes + +* Save an empty `` element where a document holds no + `` ([#918](https://github.com/webodf/WebODF/pull/918)) + # Changes between 0.5.8 and 0.5.9 ## WebODF ## Fixes -* Fix an issue where ODF zip files were incorrectly generated ([#917](https://github.com/kogmbh/WebODF/pull/917)) +* Fix an issue where ODF zip files were incorrectly generated ([#917](https://github.com/webodf/WebODF/pull/917)) # Changes between 0.5.7 and 0.5.8 @@ -12,15 +88,15 @@ ### Fixes -* Fix chrome selections that cannot be collapsd by clicking inside them ([#905](https://github.com/kogmbh/WebODF/issues/905)) -* Fix Inserted images being 1cm by 1cm in LibreOffice/OO ([#904](https://github.com/kogmbh/WebODF/issues/904)) -* Fix exported zip file being uncompressed ([#21]https://github.com/kogmbh/WebODF/issues/21) +* Fix chrome selections that cannot be collapsd by clicking inside them ([#905](https://github.com/webodf/WebODF/issues/905)) +* Fix Inserted images being 1cm by 1cm in LibreOffice/OO ([#904](https://github.com/webodf/WebODF/issues/904)) +* Fix exported zip file being uncompressed ([#21]https://github.com/webodf/WebODF/issues/21) ## Wodo.TextEditor ### Fixes -* Disable custom buttons save/saveAs/close/download when there is no session ([#893](https://github.com/kogmbh/WebODF/pull/893)) +* Disable custom buttons save/saveAs/close/download when there is no session ([#893](https://github.com/webodf/WebODF/pull/893)) # Changes between 0.5.6 and 0.5.7 @@ -28,13 +104,13 @@ ### Fixes -* Fix breaking all empty annotations on merging the paragraph they are contained in with the one before ([#877](https://github.com/kogmbh/WebODF/pull/877))) -* Fix error message popup on deleting an annotation starting at the end of a paragraph or styled range ([#880](https://github.com/kogmbh/WebODF/pull/880))) -* Fix wrong style information for text in annotations ([#881](https://github.com/kogmbh/WebODF/pull/881))) +* Fix breaking all empty annotations on merging the paragraph they are contained in with the one before ([#877](https://github.com/webodf/WebODF/pull/877))) +* Fix error message popup on deleting an annotation starting at the end of a paragraph or styled range ([#880](https://github.com/webodf/WebODF/pull/880))) +* Fix wrong style information for text in annotations ([#881](https://github.com/webodf/WebODF/pull/881))) ### Improvements -* In OpAddAnnotation support annotated ranges with 0 length ([#879](https://github.com/kogmbh/WebODF/pull/879))) +* In OpAddAnnotation support annotated ranges with 0 length ([#879](https://github.com/webodf/WebODF/pull/879))) ### Breaking changes @@ -46,7 +122,7 @@ See also section about WebODF ### Improvements -* Add a "review" modus where users can add, edit and remove own annotations, but not modify the actual document content ([#883](https://github.com/kogmbh/WebODF/pull/883))) +* Add a "review" modus where users can add, edit and remove own annotations, but not modify the actual document content ([#883](https://github.com/webodf/WebODF/pull/883))) # Changes between 0.5.5 and 0.5.6 @@ -55,7 +131,7 @@ See also section about WebODF ### Fixes -* No longer fail due to possible Byte Order Marks in ODF-internal XML files with Chromium ([#872](https://github.com/kogmbh/WebODF/issues/872))) +* No longer fail due to possible Byte Order Marks in ODF-internal XML files with Chromium ([#872](https://github.com/webodf/WebODF/issues/872))) ## Wodo.TextEditor @@ -63,7 +139,7 @@ See also section about WebODF ### Improvements -* Add options for "Save as" and "Download" buttons in Wodo.TextEditor ([#865](https://github.com/kogmbh/WebODF/pull/865))) +* Add options for "Save as" and "Download" buttons in Wodo.TextEditor ([#865](https://github.com/webodf/WebODF/pull/865))) # Changes between 0.5.4 and 0.5.5 @@ -72,11 +148,11 @@ See also section about WebODF ### Improvements -* Add a "documentModified" state with change signal to UndoManager classes ([#857](https://github.com/kogmbh/WebODF/pull/857))) +* Add a "documentModified" state with change signal to UndoManager classes ([#857](https://github.com/webodf/WebODF/pull/857))) ### Fixes -* No longer fail on "draw:master-page-name" attributes values with non-alphabetic chars ([#742](https://github.com/kogmbh/WebODF/pull/742))) +* No longer fail on "draw:master-page-name" attributes values with non-alphabetic chars ([#742](https://github.com/webodf/WebODF/pull/742))) ## Wodo.TextEditor @@ -84,15 +160,15 @@ See also section about WebODF ### Improvements -* Add a "documentModified" state with change signal ([#857](https://github.com/kogmbh/WebODF/pull/857))) +* Add a "documentModified" state with change signal ([#857](https://github.com/webodf/WebODF/pull/857))) ### Fixes -* Fix wrongly enabled hyperlink tools with no document loaded ([#833](https://github.com/kogmbh/WebODF/pull/833)) -* Prevent Cross-Site Scripting from style names and font names ([#849](https://github.com/kogmbh/WebODF/pull/849)) (CVE-2015-3012) -* Avoid badly rendered toolbar element with subsets of tools ([#855](https://github.com/kogmbh/WebODF/pull/855))) -* Prevent Cross-Site Scripting from links ([#850](https://github.com/kogmbh/WebODF/pull/850)) (CVE-2015-3012) -* Prevent browser translation service breaking the editor logic ([#862](https://github.com/kogmbh/WebODF/pull/862))) +* Fix wrongly enabled hyperlink tools with no document loaded ([#833](https://github.com/webodf/WebODF/pull/833)) +* Prevent Cross-Site Scripting from style names and font names ([#849](https://github.com/webodf/WebODF/pull/849)) (CVE-2015-3012) +* Avoid badly rendered toolbar element with subsets of tools ([#855](https://github.com/webodf/WebODF/pull/855))) +* Prevent Cross-Site Scripting from links ([#850](https://github.com/webodf/WebODF/pull/850)) (CVE-2015-3012) +* Prevent browser translation service breaking the editor logic ([#862](https://github.com/webodf/WebODF/pull/862))) # Changes between 0.5.3 and 0.5.4 @@ -100,8 +176,8 @@ See also section about WebODF ### Fixes -* Only highlight ODF fields in edit mode ([#816](https://github.com/kogmbh/WebODF/issues/816)) -* Prevent Cross-Site Scripting from file names ([#851](https://github.com/kogmbh/WebODF/pull/851)) (CVE-2014-9716) +* Only highlight ODF fields in edit mode ([#816](https://github.com/webodf/WebODF/issues/816)) +* Prevent Cross-Site Scripting from file names ([#851](https://github.com/webodf/WebODF/pull/851)) (CVE-2014-9716) ## Wodo.TextEditor See also section about WebODF @@ -117,17 +193,17 @@ See also section about WebODF ### Improvements -* Add support for double line-through in Firefox (Chrome/Safari + IE don't support this feature) ([#758](https://github.com/kogmbh/WebODF/pull/758)) -* Add support for subscript & superscript ([#755](https://github.com/kogmbh/WebODF/pull/755)) +* Add support for double line-through in Firefox (Chrome/Safari + IE don't support this feature) ([#758](https://github.com/webodf/WebODF/pull/758)) +* Add support for subscript & superscript ([#755](https://github.com/webodf/WebODF/pull/755)) * In odf.OdfContainer allow creation of document template types as well as querying and setting the template state of the document ### Fixes -* Fixed occasional crash when splitting a paragraph ([#723](https://github.com/kogmbh/WebODF/issues/723)) +* Fixed occasional crash when splitting a paragraph ([#723](https://github.com/webodf/WebODF/issues/723)) * Keep IME composition menu & avatar in the correct position when entering characters * Allow screen-readers to read the document content correctly in OSX 10.8+ versions of Safari -* Scroll newly created annotations completely into view ([#486](https://github.com/kogmbh/WebODF/issues/486)) -* Improve line ending detection when word-wrapping occurs ([#774](https://github.com/kogmbh/WebODF/pull/774)) +* Scroll newly created annotations completely into view ([#486](https://github.com/webodf/WebODF/issues/486)) +* Improve line ending detection when word-wrapping occurs ([#774](https://github.com/webodf/WebODF/pull/774)) ## Wodo.TextEditor @@ -148,7 +224,7 @@ See also section about WebODF ### Fixes * For ODP files sometimes template elements from the master pages were rendered inside the actual slides. -* Navigation via home/end keys, or up/down cursor keys is more reliable on all browsers. ([#555](https://github.com/kogmbh/WebODF/issues/555), [#405](https://github.com/kogmbh/WebODF/issues/405), [#224](https://github.com/kogmbh/WebODF/issues/224), [#185](https://github.com/kogmbh/WebODF/issues/185), [#124](https://github.com/kogmbh/WebODF/issues/124), [#98](https://github.com/kogmbh/WebODF/issues/98)) +* Navigation via home/end keys, or up/down cursor keys is more reliable on all browsers. ([#555](https://github.com/webodf/WebODF/issues/555), [#405](https://github.com/webodf/WebODF/issues/405), [#224](https://github.com/webodf/WebODF/issues/224), [#185](https://github.com/webodf/WebODF/issues/185), [#124](https://github.com/webodf/WebODF/issues/124), [#98](https://github.com/webodf/WebODF/issues/98)) * More elements from master pages are now correctly positioned when displayed inside slides. * In slides hide elements of class "header", "footer", "page-number" and "date-time" from master pages when configured so. @@ -160,7 +236,7 @@ See also section about WebODF ### Improvements * numbering of multi-level lists is now well supported in rendering, including display of only a subset of the list numbers and continued numbering from previous lists (both `text:continue-numbering` and `text:continue-list`) -([#565](https://github.com/kogmbh/WebODF/pull/565)) +([#565](https://github.com/webodf/WebODF/pull/565)) ### Fixes @@ -175,6 +251,6 @@ See also section about WebODF * Start-up of editor no longer hangs in some browsers Two different bugs were fixed which so far broke the start-up with Safari and other browsers using older WebKit versions as well as the default browser on Android 4.0.3 -([#693](https://github.com/kogmbh/WebODF/issues/693)) +([#693](https://github.com/webodf/WebODF/issues/693)) * All toolbar elements are now disabled when no document is loaded. -([#709](https://github.com/kogmbh/WebODF/issues/709)) +([#709](https://github.com/webodf/WebODF/issues/709)) diff --git a/JSCoverage.patch b/JSCoverage.patch deleted file mode 100644 index 332db9c49..000000000 --- a/JSCoverage.patch +++ /dev/null @@ -1,25 +0,0 @@ -diff -Naurw jscoverage-0.5.1-orig/js/Makefile.in jscoverage-0.5.1/js/Makefile.in ---- jscoverage-0.5.1-orig/js/Makefile.in 2010-09-23 14:00:00.000000000 +0200 -+++ jscoverage-0.5.1/js/Makefile.in 2019-02-07 23:57:30.292080849 +0100 -@@ -431,6 +431,7 @@ - NSPR_STATIC_PATH = $(DIST)/lib - endif - -+CXXFLAGS += -fpermissive - ifdef MOZ_SHARK - CFLAGS += -F/System/Library/PrivateFrameworks - CXXFLAGS += -F/System/Library/PrivateFrameworks -diff -Naurw jscoverage-0.5.1-orig/util.c jscoverage-0.5.1/util.c ---- jscoverage-0.5.1-orig/util.c 2010-09-23 14:00:00.000000000 +0200 -+++ jscoverage-0.5.1/util.c 2019-02-07 23:58:18.369297990 +0100 -@@ -478,6 +478,10 @@ - p->next = head; - head = p; - } -+ else if (S_ISDIR(buf.st_mode)) { -+ head = recursive_dir_list(root, entry_wrt_root, head); -+ free(entry_wrt_root); -+ } - else { - fatal("refusing to follow symbolic link: %s", entry); - } diff --git a/LicenseHeaderTemplate.js b/LicenseHeaderTemplate.js index 426000d1c..aac1e0f6c 100644 --- a/LicenseHeaderTemplate.js +++ b/LicenseHeaderTemplate.js @@ -18,6 +18,6 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ diff --git a/PUBLIC-API.md b/PUBLIC-API.md new file mode 100644 index 000000000..9dd5d074d --- /dev/null +++ b/PUBLIC-API.md @@ -0,0 +1,161 @@ +# The public API of WebODF + +This says what a program may lean on: the objects the library offers, what +they answer and what they promise. Everything else in `webodf/lib` is the +inside of the library and may change without warning. + +The library is one file, `webodf.js`, built as +[README-Building.md](README-Building.md) says. It writes two namespaces of its +own, `odf` and `gui`, and nothing else in the page. + +```html + +
+ +``` + +## What the library runs in + +The library reads the elements of a document by their namespace, writes the +rules of the pages in a sheet of the document, lays the pages in boxes that +keep the spacings of an office apart, and asks the engine for the fonts a +document names before it breaks it into pages. An engine that has all of that +draws a document; one that has not draws nothing. + +| engine | floor | +|--------------|-------| +| Chrome | 88 | +| Firefox | 78 | +| Safari | 15.4 | +| Qt WebEngine | 6.4 | + +Internet Explorer draws nothing of a document, whatever its version: it has no +DOM that reads a namespace. The sources are written in the third edition of +the language, and the compiler is told so, but that says how the file is +parsed and not what it needs to run. + +The same document is not broken into the same number of pages by two engines: +the lines are not of the same height, and eight hundred pages of one are eight +hundred and sixty of another. A program that counts on a number of pages +counts on the engine as well. + +## odf.OdfCanvas + +A canvas draws one document in one element of a page. It is made with the +element to draw in, and the element is emptied of whatever it held. + +### Reading a document + +| call | what it does | +|---------------------------------------------|------------------------------------------------------------------------------------------------| +| `load(url)` | Read the document at that address and draw it. A canvas that held another document forgets it. | +| `odfContainer()` | The `odf.OdfContainer` of the document that is drawn, or nothing before one is read. | +| `setOdfContainer(container, suppressEvent)` | Draw a document that was read another way. | +| `save(callback)` | Write the document back, as it stands, and answer the bytes to the callback. | +| `destroy(callback)` | Take the document out of the page and let go of what it held. | + +### Being told what happens + +`addListener(name, handler)` asks to be told of one of these: + +| name | when | what the handler is given | +|--------------------|------------------------------------------------|---------------------------| +| `statereadychange` | the document is read, or could not be | the `odf.OdfContainer` | +| `pagesdrawn` | every page of a text has been broken and drawn | nothing | +| `click` | the reader clicks in the document | the event | + +A document that cannot be read is answered all the same: the container says +`odf.OdfContainer.INVALID` rather than `DONE`. + +```js +canvas.addListener("statereadychange", function (container) { + if (container.state === odf.OdfContainer.INVALID) { + return; + } + canvas.fitSmart(window.innerWidth); +}); +``` + +### Drawing a text over pages + +A text is drawn as one run of text until it is asked otherwise. What follows +is of a text; a presentation and a drawing are drawn slide by slide whatever +is asked here. + +| call | what it does | +|-------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `setPaginated(on)` | Break the text into pages, or draw it as one run again. | +| `isPaginated()` | Whether it is broken into pages. | +| `setPageMode(mode)` | `"pages"` lays the pages one under another, each page a box of its own; `"columns"` lays them beside one another, each page a column; `"flow"` writes the text as one run, as a page of the web is written. | +| `setPagesPerRow(n)` | How many pages stand side by side on a row: one to scroll a document, two to read a book. | +| `setFirstPageOnItsOwn(alone)` | Whether the first page stands alone on the right of its row, as the first page of a book does. | +| `refreshNumbering()` | Write the labels of the lists and of the headings again, after an editor has changed one. | +| `pageBoxAt(x)` | Where the page that holds that place begins and ends across, or nothing when the text is drawn as one run. | + +Breaking a document of many pages takes time: it is done a few pages at a +time, so a reader sees the first of them at once, and `pagesdrawn` says when +the last of them is drawn. + +### The size the document is drawn at + +| call | what it does | +|-------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `setZoomLevel(zoom)`, `getZoomLevel()` | The size the document is drawn at, one being the size it was written at. | +| `fitToWidth(width)` | Scale the document to that width, up or down. | +| `fitSmart(width, height)` | Scale it down to that width, and to that height where one is given, but never up: a document is read at the size it was written at where the window holds it. | +| `fitToHeight(height)`, `fitToContainingElement(w, h)` | The same, of a height, or of the element the canvas draws in. | +| `getSizer()` | The element the document is drawn in, which is what the zoom is set on. | +| `getElement()` | The element the canvas was made with. | +| `refreshSize()` | Read the size of the window again, after it has changed. | + +### Slides + +`showFirstPage()`, `showNextPage()`, `showPreviousPage()` and `showPage(n)` +move through the slides of a presentation. + +### Annotations + +`enableAnnotations(on, showRemoveButton)` draws the annotations of a document +in a lane beside the text. `addAnnotation`, `forgetAnnotation`, +`refreshAnnotations` and `getAnnotationViewManager` are for an editor that +writes them. + +## odf.OdfContainer + +The document itself: the parts of the package and the tree of the document. + +| call | what it does | +|--------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `new odf.OdfContainer(url, onchange)` | Read a document from an address; the callback is told at every turn of its state. | +| `state` | `odf.OdfContainer.LOADING`, `DONE` or `INVALID`. | +| `rootElement` | The document, whose `body`, `styles`, `automaticStyles`, `masterStyles`, `meta` and `settings` are the parts of it. A document that holds none of one of these is given an empty one, so a reader never has nothing to read. | +| `getDocumentType()` | `"text"`, `"presentation"`, `"spreadsheet"` or `"drawing"`. | +| `getContentElement()` | The element of the body that holds what is written. | +| `getPart(path)`, `getPartData(path, callback)` | A part of the package: an image, a formula, a chart. | +| `setBlob(path, mimetype, content)`, `removeBlob(path)` | Put a part in the package or take it out. | +| `save(callback)`, `getUrl()` | Write the package back, and the address it was read from. | +| `isTemplate()`, `setIsTemplate(on)` | Whether the document is a template, `.ott` rather than `.odt`. | + +## The viewer of the desktop + +The window of the viewer speaks to the page it draws in through +`window.viewer`, see `programs/opendocumentviewer-desktop/assets/viewer.js`: + +| call | what it does | +|------------------------------------|------------------------------------------------------------------------| +| `load()` | Draw the document the window serves. | +| `unload()` | Put the document away and show what the window shows with none. | +| `zoomBy(factor)`, `setZoom(level)` | Zoom in and out. | +| `fit()` | Draw the page as wide as the window allows. | +| `setPages(perRow, firstAlone)` | One page to a row, two, or two with the first on the right of its own. | + +## What is not the public API + +The objects under `ops`, `gui` and `webodfcore` are those of the editor and of +the inside of the library. They are written for the editors that live in this +repository and they change with them. A program that leans on them leans on +something that may be written otherwise tomorrow. diff --git a/README-Building.md b/README-Building.md index 80a3e30ed..a16e3fcdd 100644 --- a/README-Building.md +++ b/README-Building.md @@ -1,91 +1,702 @@ -## Building WebODF on Linux +## Two ways to build + +The library "webodf.js" is built in two ways. + +- **With cmake**, described first, as it was done originally: it builds the + library and it is the only way to build everything else, the viewers, the + add-ons, the editors and the tools, listed in "README-Products.md". The + products of "programs/" are behind the option WEBODF_PROGRAMS, off by + default, since they are the longest to build and need Dojo. The closure + compiler, Rhino and Dojo are downloaded from maven central and from the + registry of npm, at the same versions as the build with node uses. +- **With node**, described in "Building with node" below: the shortest way to + the library alone, and to nothing else. It needs node only, and java for the + optional check of the types. + +Both produce the same file, byte for byte: the build with cmake writes the +library by running "scripts/build.js" as well. It still compiles the sources +with the closure compiler, but only to check their types, as it compiles with +SIMPLE_OPTIMIZATIONS, which neither folds the definition IS_COMPILED_CODE nor +drops what it makes unreachable: the loader of the classes and the runner of the +scripts stayed in a library that never calls them, with the eval() they read a +file with. + +So "npm install" is needed for the build with cmake too, as terser minifies the +library. + +Some parts of the build with cmake are opt-in, so that a plain build stays short +and needs nothing but node and cmake: + +| Option | What it adds | +|--------------------|---------------------------------------------------------| +| WEBODF_PROGRAMS | The editors and the extensions of "programs/" | +| WEBODF_QTJSRUNTIME | qtjsruntime, that runs the tests in the webengine of qt | +| WEBODF_DESKTOP | The viewer of OpenDocument for the desktop, in qt | +| WEBODF_ANDROID | The viewer of OpenDocument for android, with its sdk | +| WEBODF_IOS | The viewer of OpenDocument for iOS, on macOS alone | + +Four more settings say how the build is run rather than what it holds: + +| Setting | What it says | +|-------------------------|-------------------------------------------------------------------------------| +| WEBODF_PACKAGE_FLATPAK | OFF leaves the flatpak out of "products", the rest being packed still | +| WEBODF_PREBUILT_LIBRARY | A "webodf.js" built elsewhere is taken as it stands, so node is not run | +| WEBODF_TESTS_ON_SCREEN | The window of qtjsruntime is shown while the tests run, hidden otherwise | +| WEBODF_DOWNLOAD_DIR | An environment variable: the directory the downloads are kept in | -For creating the file "webodf.js" out of the sources CMake and Java needs to be installed. +WEBODF_PREBUILT_LIBRARY is what a sandbox is built with, where neither node nor +the network is at hand: the flatpak of the viewer builds the C++ alone against +the library the build outside made, see "Products". -Another optional, but recommended requirement are the Qt5 libs, which are used to create and run tests. +WEBODF_QTJSRUNTIME and WEBODF_DESKTOP need Qt 6.4 or later, with the modules +Core, Gui, Widgets, PrintSupport, WebChannel, WebEngineCore and +WebEngineWidgets, that Debian 12 and later install with: -Further requirements, like the [Closure Compiler][], will be conveniently downloaded automatically during the build, as usually the latest version will be used, -which might not yet be available as a package. So during (first) build also a connection to the internet will be needed. -Downloaded requirements will be cached in the build directory. -[Closure Compiler]: https://developers.google.com/closure/compiler/ +```sh +apt-get install qt6-base-dev qt6-base-dev-tools qt6-webchannel-dev qt6-webengine-dev qt6-webengine-dev-tools +``` -With the requirements installed, either download the zip file from https://github.com/kogmbh/WebODF/archive/master.zip and unzip it +Two libraries the modules of qt look for are packaged apart, and cmake reports +them as "Could NOT find XKB" and "Could NOT find Cups" when they are absent. +Neither stops the build: xkbcommon is the keyboard of the platform, and cups is +the printing of the system, that the export to pdf does not go through, as it +is drawn by the webengine itself. - wget https://github.com/kogmbh/WebODF/archive/master.zip - unzip master.zip - mv WebODF-master webodf +```sh +apt-get install libxkbcommon-dev libxkbcommon-x11-dev libcups2-dev +``` -or get the complete repo with git: +qtjsruntime ran in Qt WebKit until 2026, that Qt dropped in 5.6, in 2016, and +that Debian stopped packaging in Debian 13: the program was rewritten for +webengine, which is the blink of chromium. Its option stays off because the +modules of qt weigh more than the rest of the build together, and because "npm run test:browser" +runs the same suite in a browser that is installed anyway. - git clone https://github.com/kogmbh/WebODF.git webodf +On a machine without a screen, a build server for instance, the platform without +one is used: -For building now in the same directory where either of above commands were done the following commands should be entered: +```sh +QT_QPA_PLATFORM=offscreen make -C build test-qtjsruntime +``` - mkdir build - cd build - cmake ../webodf - make webodf.js-target +It runs the whole suite of the browser, 35 files of tests where the run with +node runs 3: the others need a dom, a layout and computed styles, which is what +this program is kept for. -A successful run will yield the file "webodf.js" in the subfolder "build/webodf/" (among other things), from where you can then copy it and use for your website. +WEBODF_DESKTOP builds the viewer for the desktop, which is a window of qt around +the page the library draws in, see "README-Products.md". It is built by that +option alone, without WEBODF_PROGRAMS, as it needs neither Dojo nor the +editors. -### Dependencies on Ubuntu -For a Ubuntu 18.04 distribution you can satisfy the build dependencies with: +## The builds that answer by themselves - apt-get install libqt5webkit5-dev default-jdk +Two workflows of GitHub Actions are in ".github/workflows": +* "library.yml" builds the library with node and runs what that build runs: the + types of the library and of the tests, the tests in node and the same ones in + rhino. It is the short one, and it answers on every push. +* "desktop.yml" builds the viewer of the desktop on linux, on windows and on + macos, and runs every suite of tests on each of them, in the webengine of qt + among the others. What it installs is gathered as an artifact, so a viewer + that no one here can build is downloaded and tried. + +The second is what tells whether the branches "if (WIN32)" and "if (APPLE)" of +the build hold: they run nowhere else. Qt is installed by [install-qt-action](https://github.com/jurplel/install-qt-action), +that takes it from the repository of Qt with the module of WebEngine, and +windows is given the compiler of microsoft, that WebEngine needs. -## Building WebODF on Windows +The history is fetched whole, as the version of the build comes from "git describe", +and a shallow checkout gives it nothing to describe. -The following steps have been tested with the Microsoft C\C++ compilers that are installed with Visual Studio 2010. It may be possible to use MinGW but it has not been verified. -* Visual Studio 2010 (or [Visual Studio 2010 Express][] works as well) -* [Visual Studio 2010 Service Pack 1][] -* [Qt 5.2.1 x86 installer](http://download.qt-project.org/official_releases/qt/5.2/5.2.1/qt-opensource-windows-x86-msvc2010-5.2.1.exe) for Visual Studio 2010 -* [CMake 2.8.12.2 x86](http://www.cmake.org/files/v2.8/cmake-2.8.12.2-win32-x86.exe) -* [Java Runtime 1.7](http://java.com/en/download/index.jsp) (or more recent) -* [Git for Windows][] +## Coverage -[Visual Studio 2010 Express]: http://www.visualstudio.com/en-us/downloads#d-2010-express -[Visual Studio 2010 Service Pack 1]: http://www.microsoft.com/en-us/download/details.aspx?id=23691 -[Git for Windows]: http://msysgit.github.io/ +The coverage of the tests is measured by [c8][], with node and with cmake: -### Visual Studio 2010 +```sh +npm run coverage +make coverage +``` -We only need the C\C++ compilers but it is easier to get this by installing Visual Studio 2010. It can also be obtained from the Windows 7 SDK but I would -recommend the above. To avoid issues with CMake, [Visual Studio 2010 Service Pack 1][] also needs to be downloaded and installed. +By default it writes a table on the terminal and a report to browse in "coverage/index.html". +Another reporter is chosen by passing it through: -### Git for Windows +```sh +node scripts/coverage.js --reporter=lcov +``` -[Git][Git for Windows] itself isn't strictly necessary, but some Unix programs like cat are used during the build. -As you will generally need git to download the source, this is easiest way to get the msys utilities. +c8 reads the coverage V8 records while it runs, so it neither parses nor +rewrites the sources: the version of ECMAScript the library is written in does +not matter to it. The tests run on the bundle, that is written with a source map +so that the coverage is reported on the files of "webodf/lib". -### Setup PATH variable +It replaces JSCoverage, that instrumented the sources for the target +"instrumented" of the original build. Its last release, 0.5.1 of 2010, bundles +SpiderMonkey 1.7, whose autoconf 2.13 probe declares "main()" without a return +type: gcc 14 and clang 16 reject it, as -Wimplicit-int is an error in C23, so it +does not build any more on a recent distribution. -Add the following directories to the PATH variable +[c8]: https://github.com/bcoe/c8 -* CMake path e.g `C:\Program Files (x86)\CMake 2.8\bin` -* QMake path e.g `C:\QtSDK\bin` -* Unix tools path e.g `C:\Program Files (x86)\Git\bin` (Git installer will add this automatically if you select the add Unix tools to PATH option during install) -### Building webodf.js -These commands should be entered from the Visual Studio 2010 command prompt so that msbuild will be added to the PATH +## Building WebODF on Linux + +For creating the file "webodf.js" out of the sources cmake and node need to be +installed. Java is looked for and not required: it runs the checks of the types +and the tests in rhino, and a machine without it builds everything else. - git clone https://github.com/kogmbh/WebODF.git webodf - md build - cd build - cmake -G "Visual Studio 10" ..\webodf - msbuild WebODF.sln +Another optional, but recommended requirement are the Qt 6 libs, which are used +to run the tests in the webengine of qt. +Further requirements, like the [Closure Compiler][], will be conveniently +downloaded automatically during the build, as usually the latest version will be +used, which might not yet be available as a package. So during (first) build +also a connection to the internet will be needed. Downloaded requirements will +be cached in the build directory. + +[Closure Compiler]: https://developers.google.com/closure/compiler/ -## Building WebODF on OSX 10.7.5 (Lion) or OSX 10.9.5 (Mavericks) +With the requirements installed, either download the zip file from https://github.com/webodf/WebODF/archive/master.zip +and unzip it: -Qt5 can be installed via homebrew, but will not be linked by default. CMake must be instructed where to find this package by -specifying the Qt5 location in CMAKE_PATH_PREFIX environment variable: +```sh +wget https://github.com/webodf/WebODF/archive/master.zip +unzip master.zip +mv WebODF-master webodf +``` - cmake -DCMAKE_PREFIX_PATH=/usr/local/Cellar/qt5/5.4.1 ../webodf - -If the build process returns an error `(libuv) Failed to create kqueue (24)`, -this can be resolved by increasing the limit on the number of open file descriptors: +or get the complete repo with git: + +```sh +git clone https://github.com/webodf/WebODF.git webodf +``` + +For building now in the same directory where either of above commands were done +the following commands should be entered: + +```sh +mkdir build +cd build +cmake -S ../webodf +make webodf.js-target +``` + +A successful run will yield the file "webodf.js" in the subfolder "build/webodf/", +among other things, from where you can then copy it and use for your website. + +CMake writes the files of another builder when it is asked to, and ninja builds +the same thing in less time, as it starts every step it can at once where make +walks the directories: + +```sh +cmake -S ../webodf -G Ninja +ninja webodf.js-target +``` + +The package is "ninja-build" on Debian and Ubuntu, and the command it installs +is "ninja". + +Only the wait changes: the file that comes out is the same, byte for byte. A +directory is bound to the builder it was configured with, so a build that was +made with make is configured again in a directory of its own, or with +"--fresh". + +CMake keeps the paths of the programs it finds in "build/CMakeCache.txt", so a +node that is installed afterwards, with nvm for instance, is not seen: it keeps +using the one it found the first time, and reports it as too old. The entries +are dropped with: + +```sh +cmake -S ../webodf -U NODEJS_EXECUTABLE -U NPM +``` + +### Dependencies on Debian and Ubuntu + +The build needs cmake and node, that is installed apart, see "Building with +node" below for the version, and it takes a java runtime for the checks where +one is installed: + +```sh +apt-get install cmake default-jre +``` + +The generator Ninja is another package, and it is optional: it is asked for by +"-G Ninja" and it builds in less time than make, see above. + +```sh +apt-get install ninja-build +``` + +The modules of Qt are only needed by the option WEBODF_QTJSRUNTIME, see "Two ways to build" +above. + +The ways the viewer of the desktop is handed over each ask for a tool of their +own, see "README-Products.md". None of them is needed to build the viewer: the +target "products" makes what the tools of the machine allow, and says at the +end which tool is missing for the rest. + +```sh +apt-get install dpkg-dev rpm flatpak flatpak-builder libfuse2t64 +flatpak remote-add --user --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo +flatpak install --user flathub org.kde.Platform//6.9 org.kde.Sdk//6.9 \ + io.qt.qtwebengine.BaseApp//6.9 +``` + +| Target | The tool it asks for | Where it comes from | +|-------------------|---------------------------|--------------------------------| +| package-archive | none, cmake writes it | | +| package-deb | dpkg-deb, dpkg-shlibdeps | "dpkg" and "dpkg-dev" | +| package-rpm | rpmbuild | "rpm" | +| package-appimage | linuxdeploy, its plugin of qt, appimagetool | not packaged by Debian, see below | +| package-flatpak | flatpak-builder, and the runtime of KDE | "flatpak-builder", and Flathub | + +The runtime and the base app are installed for the user and not for the +system: a build dir is written by the user who builds, and taking a package +that root installed apart into it asks to change the owner of files, which the +kernel refuses ("error: fchownat: Operation not permitted"). An installation +of the user keeps the owner in the attributes of the files and changes +nothing. Each installation carries its own remotes, hence the first line. + +The flatpak asks for three things and not one: the tool that builds it, the +runtime of KDE it is built against, which weighs some two gigabytes, and the +base app of qt, which carries the webengine the viewer is drawn in and that +the runtime of KDE does not carry. The two last are installed from Flathub, +the third line above. Both are looked for when cmake +is run, and what is missing is named among the products that could not be made: + +``` +This machine has no tool to make: + the flatpak, for want of io.qt.qtwebengine.BaseApp//6.9 +``` + +A branch of that runtime is declared end of life as soon as a newer one is +out, so no version is written in the manifest: the newest branch of +"org.kde.Sdk" the machine has is taken, and the manifest is written with it. +Install a newer one and run cmake again, and the flatpak follows. A build that +wants one branch and not another names it: + +```sh +cmake -S . -B build -DWEBODF_FLATPAK_RUNTIME=6.9 +``` + +A machine that will not build a flatpak at all leaves it out, and "products" +makes everything else without naming it as missing: + +```sh +cmake -S . -B build -DWEBODF_PACKAGE_FLATPAK=OFF +``` + +The branch of the runtime is the version of qt it carries, and the viewer is +built with qt 6.4 or newer, so branches older than that are left aside: a +machine that kept only one of them is told that it has no runtime to build +against, and a branch named by hand that is older stops the configuration at +once rather than the build later. + +A tool that is installed afterwards is only seen when cmake is run again, as +what was found is kept in the cache of the build, see "Building everything +again without downloading it again" below. + +The tools of the AppImage are AppImages themselves, packaged by no +distribution: they are taken from the releases of their projects, made +runnable and put in the path under the names "linuxdeploy", +"linuxdeploy-plugin-qt" and "appimagetool", which are the names cmake looks +for. + +```sh +mkdir -p ~/.local/bin +cd ~/.local/bin +wget -O linuxdeploy https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage +wget -O linuxdeploy-plugin-qt https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous/linuxdeploy-plugin-qt-x86_64.AppImage +wget -O appimagetool https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-x86_64.AppImage +chmod +x linuxdeploy linuxdeploy-plugin-qt appimagetool +``` + +An AppImage is run by fuse, which "libfuse2t64" carries, or with +"--appimage-extract-and-run" where fuse is not allowed. Once they are in the +path, cmake is run again for them to be found, as it keeps what it found +before: + +```sh +cmake -S . -B build -U LINUXDEPLOY -U LINUXDEPLOY_QT -U APPIMAGETOOL +ninja -C build products +``` + +Nothing of this builds the viewer for windows or for macos from a machine of +linux: those systems build it themselves, which is what the runner of the +build is for, see ".github/workflows/desktop.yml". + + +## Building WebODF on Windows + +The library alone needs node and cmake, java for the checks, and nothing of the +compiler: it is javascript. The compiler is needed by the two products of qt, qtjsruntime and +the viewer of the desktop, and it has to be the one of Microsoft, as Qt +WebEngine does not compile with MinGW. + +* [Visual Studio 2022](https://visualstudio.microsoft.com/downloads/), the + Build Tools alone are enough, with the workload "Desktop development with C++" +* [The installer of Qt 6](https://www.qt.io/download-qt-installer), version 6.8 + or later, architecture "MSVC 2022 64-bit", with the modules WebEngine and + WebChannel, for the options WEBODF_QTJSRUNTIME and WEBODF_DESKTOP only. Since + 6.8 WebEngine is an extension, in a repository of its own +* [CMake](https://cmake.org/download/) +* [A java runtime](https://www.java.com/en/download/), 17 or later, that runs + the closure compiler and rhino +* [Node](https://nodejs.org/), 20 or later +* [Git for Windows](https://gitforwindows.org/), which also brings the unix + tools the build calls, "cat" among them + +Everything of that list is installed at once by the script +"programs/opendocumentviewer-desktop/data/setup-windows.ps1", that a machine of +its own is set up with, see "README-Products.md". It is what the build of +windows is tested with, as no one of this project owns such a machine. + +Only x86_64 and ARM64 are built: Qt 6 dropped the 32 bits. + +### Building webodf.js - ulimit -n 8192 +The commands are entered in the "x64 Native Tools Command Prompt for VS 2022", +that puts the compiler in the path: + +```sh +git clone https://github.com/webodf/WebODF.git webodf +md build +cd build +cmake -G Ninja -DCMAKE_BUILD_TYPE=Release ..\webodf +cmake --build . +``` + +The generator "Visual Studio 17 2022" builds as well, and it is slower. + + +## Building WebODF on macOS + +Qt 6 is installed by homebrew, "brew install qt", or by the installer of qt, +and homebrew does not link it by default. CMake is told where it is by the +variable CMAKE_PREFIX_PATH: + +```sh +cmake -DCMAKE_PREFIX_PATH=$(brew --prefix qt) ../webodf +``` + +If the build process returns an error `(libuv) Failed to create kqueue (24)`, +this can be resolved by increasing the limit on the number of open file +descriptors: + +```sh +ulimit -n 8192 +``` + +The viewer of the desktop is packed as a bundle, signed and notarised, which is +another matter, see "README-Products.md". + +## Javascript dependencies + +Unlike most node projects, the runtime dependency "@xmldom/xmldom" (about 400 kB) +is versioned inside the directory "node_modules", and it has been so since 2012 +(commit "include node_modules directory with xmldom"), so that WebODF can be run +from a simple checkout, without npm and without network access. The tools of the +build (terser, jsdoc) are not versioned: they are installed with "npm install" +and the file ".gitignore" keeps only "@xmldom". + +This dependency is used by the file "webodf/lib/runtime.js", that needs a +DOMParser when WebODF runs inside node, for example for the command line tools. +A browser uses its own native parser, so this dependency is never included in +the compiled file "webodf.js". + +As a consequence, an update of a dependency must commit the versioned copy along +with the file "package.json": + +```sh +npm install +git add package.json package-lock.json node_modules +``` + +Without it, a fresh checkout keeps running the previous version, whatever the +file "package.json" says. + +JSZip is versioned too, but differently: the file "webodf/lib/externs/JSZip.js" +holds its distribution, that is concatenated into "webodf.js", as a browser +needs it to read and write the zip an ODF document is. It is updated by hand, +from the file "dist/jszip.js" of the package, whose wrapper is replaced by the +one the previous copy carries: the class is attached to the shared object +"externs" rather than to the global scope, and the modules of its bundle are +hidden from the commonjs of node, that would otherwise capture the export. The +call to new Buffer() of its module "nodeBuffer" is patched as well, as node +deprecated it. + +The version in use is JSZip 2.6.1, the last of its line. JSZip 3 is not used +yet, for two reasons only: its api is asynchronous, so the three calls of Zip.js +would go through the promises it ships with, and it weighs 17 kB more once +minified, since it added a layer of streams. Both versions run on the same +browsers, IE 6 included. + +## Building with node + +The library is also built without cmake and without java, with node only. It is +the shortest way to "webodf.js", and it does not download anything but the +packages of npm. It builds the library and nothing else: every other product +is built by cmake, see "README-Products.md". + +Node 22.22.2 or later is needed, both here and for the build with cmake: 22 is +the oldest release that is still maintained, and jsdom, that the tests use, runs +on none of its earlier ones. The exact range is in the field "engines" of +"package.json"; the tools are tested with node 24, the release under long term +support. Node is expected to be installed, on every platform, npm along with it: +it used to be downloaded on Windows, but only the binary of node, without the +npm the build needs. + +```sh +npm install +npm run build +``` + +The result is written in "dist/webodf.js". The sources are concatenated in the +order of their dependencies, taken from the file "webodf/lib/manifest.json", +then minified with terser. The output has the same size as the one of the +closure compiler used with SIMPLE_OPTIMIZATIONS, that the original build used. + +Each command builds one output, so only what is needed is built: + +```sh +npm run build # the library, in "dist/webodf.js" +npm run doc # the documentation of the api, in "dist/docs" +npm run check # check the types with the closure compiler (java is needed) +npm run check:tests # check the types of the library and of the tests +npm test # run the tests with node +npm run test:rhino # run the tests with Rhino, on a java virtual machine +npm run test:browser # run the tests in a browser, with all the suites +npm run test:extension # run the add-on of Chrome and check it shows a document +npm run all # check, test, build and doc +``` + +The command "npm run check" uses the closure compiler as a type checker only: +it writes no output. The jar is downloaded once from maven central into the +directory ".tools" and the version is pinned in "scripts/lib/closure.js". + +"npm run check:tests" checks the tests as well, as the target "compiled.js" of +the build with cmake does, so that both builds report the same errors. The +library alone is fully typed, the tests are not: they declare mocks that are +partial on purpose, so reportUnknownTypes is off for them. + +It ends with a second pass over the libraries packaged with "webodf.js", JSZip +for now, that only reads their jsdoc, as the target "simplecompiled.js" of the +build with cmake does. Their types are not checked, since they are not written +for the compiler, but an annotation it cannot parse stops the build, so the +check with node has to see it too. + +```sh +CLOSURE_VERSION=v20240101 npm run check # another version +CLOSURE_JAR=/path/to/compiler.jar npm run check # another copy +``` + +The groups of checks removed from the compilers newer than 2016 are dropped +automatically when an older one is used. The compiler of 2016, that the project +used until now, does not check the sources any more: they use globalThis, that +it does not know. + +The configuration for karma, in "webodf/tools/karma.conf.js", is kept and is +still generated by "webodf/tools/updateJS.js" during a build with cmake, but the +command "npm run test:browser" replaces it and needs no extra package. + +### Running with Rhino + +Rhino runs javascript on a java virtual machine and gives a second engine for +the tests, besides node. The jar of Rhino is downloaded from maven central into +".tools", like the one of the closure compiler, and the version is set in +"scripts/lib/rhino.js" or with the environment variable RHINO_VERSION. + +The file "tests.js" selects the suites from what the runtime provides, so each +engine runs what it can: + +| Engine | Suites | What it provides | +|------------------|--------|-----------------------------------------| +| a browser | 35 | everything, with a layout and its css | +| node, with jsdom | 26 | a dom with a range and a tree walker | +| node, alone | 3 | the package xmldom, without a range | +| Rhino | 3 | the dom of java, lists without an index | + +The tests with node use jsdom when it is installed, which is the case after +"npm install": it is a development dependency, the library itself does not use +it. Three suites are added only in a browser: two measure where the text is +drawn, that needs a layout engine, and one compares the rules of a stylesheet +one by one, that needs a css parser rejecting the same rules. + +The runtime for Rhino could not even start between 2013 and now, so its own +tests had never been run. + +### Running the tests in a browser + +The command "npm run test:browser" serves the tests over http, since a page +loaded from a file cannot read them, then opens them in a chromium found on the +system. The browser is not downloaded: set WEBODF_BROWSER to its path, or +install one with "npx playwright install chromium". + +Two tests fail today, and they are not a defect of the browser: + +- RuntimeTests.testRead reads the raw bytes of a file that starts with a byte + order mark. The runtime asks for them with the trick of the mime type + "charset=x-user-defined", but a recent chromium decodes the answer as utf-8 + and removes the mark first. Reading the answer as an array buffer, as it is + done since a long time, would fix it. +- MaliciousDocumentTests.loadInjectionDocument reads a document the same way. + +### Outputs + +| Path | Built by | Content | +|-----------------------------------|----------|----------------------------------------| +| `dist/webodf.js` | node | the library, minified | +| `dist/docs/` | node | documentation of the api | +| `build/webodf/webodf.js` | cmake | the library, minified | +| `build/webodf/webodf-debug.js` | cmake | the library, readable | +| `build/webodf/webodf-compiled.js` | cmake | the library without its license header | +| `build/webodf/simplecompiled.js` | cmake | the library and its tests, run by node | +| `build/webodf/webodf.css.js` | cmake | the css of the viewer, as a string | +| `build/webodf/webodfversion.js` | cmake | the version, from `git describe` | + +The build with node writes in "dist" and needs no other directory: the version +and the css are generated in memory, not in files. The build with cmake writes +in the directory given to cmake, usually "build", next to the sources, and +keeps its downloads there. + +Both libraries are the same file, byte for byte: the sources concatenated in the +order of their dependencies, with IS_COMPILED_CODE set to true so that the +runtime does not load the classes one by one, minified by terser. The build +with cmake runs "scripts/build.js" as well, see "Two ways to build" above. + +### Building everything again without downloading it again + +The build directory holds two kinds of files: what was made here, and what was +fetched from elsewhere. Only the first has to go. + +```sh +ninja -C build -t clean # every file a rule wrote, nothing else +ninja -C build products +``` + +"ninja -t clean" leaves the archives of "build/downloads", some 210 MB, and it +leaves the directories the external projects were unpacked into, +"build/*-prefix". Nothing is asked of the network again. + +When even the cmake cache is to go, keep the downloads elsewhere so that they +outlive the directory: + +```sh +export WEBODF_DOWNLOAD_DIR=$HOME/.cache/webodf-downloads +mkdir -p "$WEBODF_DOWNLOAD_DIR" +mv build/downloads/* "$WEBODF_DOWNLOAD_DIR"/ +rm -rf build +cmake -S . -B build -G Ninja +ninja -C build products +``` + +The variable is read at configure time and printed back: "external downloads +will be stored/expected in: ...". The external projects are unpacked again, +which costs no network, only some minutes. + +A build directory cannot be shared between two machines, nor between a container +and its host. Cmake writes into its cache the path of every tool it found — +cmake itself, node, npm, java, the Android SDK — and those paths are read again +without being checked. A cache made in a container names tools the host does not +have, and the build fails with "cmake: not found", an empty version of node or +an SDK that is not there. Give each machine a build directory of its own, and +share only "$WEBODF_DOWNLOAD_DIR". + +To make cmake look for the tools again without losing the rest of the cache, +unset the variables that hold them and configure again, from outside the source +directory: + +```sh +cmake -S . -B build -U NODEJS_EXECUTABLE -U NPM -U DPKG_DEB -U RPMBUILD +``` + +### Running the add-ons without installing them + +The three packages of "programs/opendocumentviewer-webext", see "README-Products.md", are +loaded from their directory, with a profile of their own, so that nothing has to +be clicked and nothing is kept: + +```sh +npx web-ext run --source-dir build/opendocumentviewer-firefox-mv2-x.y.z/ +chromium --user-data-dir=$(mktemp -d) --no-first-run \ + --load-extension=build/opendocumentviewer-chrome-x.y.z/ +``` + +web-ext writes a temporary profile, installs the add-on in it and follows the +changes of the files; "--firefox" chooses the binary and "--url" opens a page at +once. Open the page after the add-on is installed, not with it: +a page that is asked for while Firefox is still starting is not redirected, and +the add-on looks broken when it is not. Firefox refuses an unsigned xpi, but not +a directory loaded this way, which is what "about:debugging" does by hand. + +Chrome keeps the profile of "--user-data-dir", hence the temporary directory, +and "--load-extension" only takes a directory, never a zip. + +"npm run test:extension" does all of it for Chrome: it builds the package, +serves a document under a type that says nothing, asks the browser for it and +checks that the viewer of the add-on drew it. It needs a chromium, like +"npm run test:browser", and the library of "dist". + +It is worth running: a rule Chrome refuses is dropped without a word, and the +documents are downloaded as if the add-on were not installed. Neither the linter +of addons.mozilla.org nor the closure compiler sees it. Firefox is not driven, +as web-ext is not a dependency of this project. + +### What the closure compiler is used for + +The library is not compiled with it any more, only checked. It is worth keeping, +because the sources are annotated with types in their jsdoc and the check is +strict: every expression must have a known type. It helps to find real issues, +and nine were found when merging repositories, among them a null document, a +variable used for two different types and a type of record missing a member that +the code used. + +## Measuring + +The benchmark measures the library on a document of a hundred pages, and +writes the times of some twenty actions: + +```sh +make -C build benchmark-html +xdg-open build/programs/benchmark/index.html +``` + +The whole of it is another target, that measures the four documents, of one, +ten, a hundred and a thousand pages, and writes every action of every document +in one table, of an action to a line and a document to a column, which is read +across: + +```sh +make -C build benchmark-html-all +xdg-open build/programs/benchmark/all.html +``` + +Both are a matter of minutes: the last action, that selects the whole document +and removes it, takes half a minute on a hundred pages and far more on a +thousand, as one operation is made for each paragraph of the selection and the +document holds eleven thousand of them. It is left out by a parameter when the +wait is too long: + +``` +index.html?includeSlow=false +``` + +The two are the same page with other parameters, that are given by hand as +well: + +``` +index.html?fileUrl=1page.odt,10pages.odt&layout=matrix +``` + +and a single document, or a document of your own, by: + +``` +index.html?fileUrl=1000pages.odt +index.html?fileUrl=/path/to/document.odt&includeSlow=false +``` + +The two last columns of the table of one document, "km/h" and "pages/h", are +the distance the cursor travelled over the time it took: they are read as a +rate, and they are a joke of the authors of the benchmark rather than a +measure of the library. The table of every document holds the times alone. diff --git a/README-Products.md b/README-Products.md index 3862d968f..b3c18bc5c 100644 --- a/README-Products.md +++ b/README-Products.md @@ -1,62 +1,1031 @@ ## Products -The WebODF repository not only contains sources for the library webodf.js, but also a few products based on it. This is the complete list of products that can be created ("x.y.z" is a placeholder for the actual version number): +The library "webodf.js" alone is built with node too, and this is the simplest +way to get it, see "README-Building.md". The products below, that bundle the +library with the editors, the extensions and their documentation, are built +with cmake only. + +The WebODF repository not only contains sources for the library webodf.js, but +also a few products based on it. This is the complete list of products that can +be created ("x.y.z" is a placeholder for the actual version number): + +### What each product runs on + +A product runs where the engine it is drawn in has what the library asks of +it: the elements of a document are read by their namespace, the rules of the +pages are written in a sheet of the document, and the pages are laid in +columns that keep the spacings of an office apart. The floors below follow +from that, and from what each store and each system asks of a package. + +| product | floor | what sets it | +|--------------------------------|------------------------------------|-------------------------------------------------------------------| +| library "webodf.js" | Chrome 88, Firefox 78, Safari 15.4 | flex, `contain`, `insertRule` with `@namespace`, `document.fonts` | +| add-on for Firefox (mv3) | Firefox 109 | `strict_min_version` of the manifest | +| add-on for Firefox (mv2) | Firefox 52 | `strict_min_version` of the manifest | +| add-on for Chrome | Chrome 88 | `minimum_chrome_version` of the manifest | +| add-on for Thunderbird | Thunderbird 128 (mv3), 98 (mv2) | `strict_min_version` of the manifest | +| viewer of the desktop | Qt 6.4 | `find_package(Qt6 6.4)`, the one of Debian 12 | +| viewer of the desktop, windows | Windows 10 | `MinVersion` of the installer | +| viewer of the desktop, macos | macOS 12 | `CMAKE_OSX_DEPLOYMENT_TARGET`, 12.0 by default | +| viewer of android | Android 5 (API 21) | `minSdk` of the build, built against API 36 | +| viewer of ios | iOS 15 | `deploymentTarget` of the project | + +Internet Explorer draws nothing of a document, whatever its version: it has no +DOM that reads a namespace, which everything here leans on. The sources are +still written in the third edition of the language, and the compiler is told +so, but that says how the file is parsed and not what it needs to run. + +None of them is built by a plain "make": they are asked for by their own +target, from the build directory, and they need the option WEBODF_PROGRAMS, +see "README-Building.md". The targets are listed by: + +```sh +make -C build help +``` + +Each product answers to "product-", that builds it and runs the tests that go +with it, and to "build-" and "test-", that do one or the other. The commands +below are run from the build directory, or prefixed with "make -C build" from +the sources, since the makefiles are written there and not next to the sources. +Every product is written at the root of that directory, next to "webodf/". ### webodf.js library with API documentation -This product bundles the file webodf.js, the debug version webodf-debug.js and API documentation into one zip file. +This product bundles the file webodf.js, the debug version webodf-debug.js and +API documentation into one zip file. With a prepared setup for building, you execute this command: - make product-library +```sh +make product-library +``` -This creates a file "webodf.js-x.y.z.zip" in the same folder, which can be copied and unzipped on a system where you want to develop using the webodf.js library. +This creates a file "webodf.js-x.y.z.zip" in the same folder, which can be +copied and unzipped on a system where you want to develop using the webodf.js library. Download the latest officially released version from the [WebODF homepage](http://webodf.org/download). ### Wodo.TextEditor component -For those who want to get an OpenDocument Text editor with just a few lines of JavaScript in their HTML5 app, the component Wodo.TextEditor is the right choice. +For those who want to get an OpenDocument Text editor with just a few lines of +JavaScript in their HTML5 app, the component Wodo.TextEditor is the right choice. -This product bundles a [HOWTO](https://github.com/kogmbh/WebODF/blob/master/programs/editor/HOWTO-wodotexteditor.md), example files, API documentation and a subdirectory with all files belonging to the component in one zip file. +This product bundles a [HOWTO](programs/editor/HOWTO-wodotexteditor.md), +example files, API documentation and a subdirectory with all files belonging to +the component in one zip file. With a prepared setup for building, you execute this command: - make product-wodotexteditor +```sh +make product-opendocumenttexteditor +``` + +It creates a file "opendocumenttexteditor-x.y.z.zip", which can be copied and +used on a system where you want to develop using the component. Unzip it there +and read the included HOWTO.md file. + +See the online demo on [webodf.org/demo](http://webodf.org/demo) and download +the latest officially released version from the [WebODF homepage](http://webodf.org/download). + +#### Trying the editor -It creates a file "wodotexteditor-x.y.z.zip", which can be copied and used on a system where you want to develop using the component. -Unzip it there and read the included HOWTO.md file. +The editor reads its own files by request, so it is served rather than opened: +a page that is opened from a disk reaches none of them. -See the online demo on [webodf.org/demo](http://webodf.org/demo) and download the latest officially released version from the [WebODF homepage](http://webodf.org/download). +```sh +ninja -C build products +cd build/opendocumenttexteditor +python3 -m http.server 8098 +``` + +| Page | What it shows | +|-------------------|------------------------------------------------------| +| texteditor.html | the editor on "welcome.odt", the document it carries | +| localeditor.html | a document of the disk, opened and written again | +| revieweditor.html | the same, with the annotations of a review | + +What is worth looking at, in that order: the document is drawn with its styles +and its picture; the toolbar answers, bold, italic, the styles of a paragraph, +undo; a word typed in the text stays there and the cursor follows it; +"localeditor.html" opens an "*.odt" of the disk and writes it again; and the +console of the browser reports nothing. + +The tests of the library cover none of that: they draw documents, they do not +write in them. ### Wodo.CollabTextEditor component -For those who want to get an OpenDocument Text editor for collaborative editing in their HTML5 app, the component Wodo.CollabTextEditor is a good choice. +For those who want to get an OpenDocument Text editor for collaborative editing +in their HTML5 app, the component Wodo.CollabTextEditor is a good choice. -There is currently no documentation for it, besides what is in the code. Wodo.CollabTextEditor is not a complete solution itself, but has some abstraction layers which have to be implemented by adapters to the respective server systems. See the demo file ["splitscreeneditor.js"](programs/editor/splitscreeneditor.js) for an example application by a client-side server with an example adapter. -This product bundles a subdirectory with all files belonging to the component in one zip file. +There is currently no documentation for it, besides what is in the code. +Wodo.CollabTextEditor is not a complete solution itself, but has some +abstraction layers which have to be implemented by adapters to the respective +server systems. + +Nothing of it is opened as the editor of one writer is: the product carries no +page at all, only the component and what it draws with. A page of its own has +to load it, and a server of sessions has to answer it, which is what an +adapter is written for. The server the demonstrations of the time answered to +is gone, so what can be told of this component here is that it is built and +that its files are whole. See the demo file ["splitscreeneditor.js"](programs/editor/splitscreeneditor.js) +for an example application by a client-side server with an example adapter. +This product bundles a subdirectory with all files belonging to the component in +one zip file. With a prepared setup for building, you execute this command: - make product-wodocollabtexteditor +```sh +make product-opendocumenttextcollab +``` -It creates a file "wodocollabtexteditor-x.y.z.zip", which can be copied and used on a system where you want to develop using the component. -Unzip it there and move the subdirectory "wodo" to your deployment. +It creates a file "opendocumenttextcollab-x.y.z.zip", which can be copied and +used on a system where you want to develop using the component. Unzip it there +and move the subdirectory "wodo" to your deployment. Download the latest officially released version from the [WebODF homepage](http://webodf.org/download). -### Firefox Add-on ODF Viewer +### OpenDocument Viewer, the add-ons of the browsers -This Firefox add-on enables to view files in the OpenDocument format directly in your Firefox browser, without installing a big office suite. +This Firefox add-on enables to view files in the OpenDocument format directly in +your Firefox browser, without installing a big office suite. With a prepared setup for building, you execute this command: - make product-firefoxextension +```sh +make product-opendocumentviewer-webext +``` + +This creates three files: + +- "opendocumentviewer-firefox-x.y.z.xpi", of the manifest version 3, that + Firefox reads from its version 109, of 2023; +- "opendocumentviewer-firefox-mv2-x.y.z.xpi", of the manifest version 2, that + it reads from its version 52, of 2017, and still reads today; +- "opendocumentviewer-chrome-x.y.z.zip", for Chrome from its version 88, of + 2021, and the browsers built on it, Edge and Opera among them. + +Chrome for Android runs no extension at all, whatever its version, so only its +desktop releases are reached. Firefox for Android runs the two xpi. + +The two xpi pass "addons-linter", the tool addons.mozilla.org validates a +submission with, without an error. Two warnings are left, both on +"data_collection_permissions": it is newer than the versions of Firefox the +manifests reach back to, that ignore the keys they do not know, and it is +declared nonetheless, as a submission without it is refused. + +Chrome dropped the blocking webRequest the two others redirect with, so its +package sends the documents to the viewer with a rule of +declarativeNetRequest, from a service worker. The rule is written by the +worker rather than read from a file of the package, as the url it redirects to +has to be an absolute one, and the identifier of the extension is only known +once it is installed. + +The rule matches the nine extensions of the format in the url, where the +background script of Firefox also reads the content type: a document a server +sends under a generic type, or under a url without an extension, is missed. A +rule only reads the response headers from Chrome 128, and asking for that +would leave out forty of its releases. + +The page of the welcome comes in two, and each package carries the one of its +own under the same name: in a browser a link is enough to read a document, where +Thunderbird needs the menu of an attachment, and telling one the ways of the +other would be telling it wrong. They are written from +"welcome-browser.*.html.in" and "welcome-thunderbird.*.html.in", and both hold +the text of the format, see "One text for every product" below. + +### Running the packages without installing them + +Both browsers load a package from a directory, with a profile of their own, so +that nothing has to be clicked and nothing is kept: + +```sh +npx web-ext run --source-dir build/opendocumentviewer-firefox-mv2-x.y.z/ +chromium --user-data-dir=$(mktemp -d) --no-first-run \ + --load-extension=build/opendocumentviewer-chrome-x.y.z/ +``` + +web-ext writes a temporary profile, installs the add-on in it and follows the +changes of the files. Firefox refuses an unsigned xpi, but not a directory +loaded this way, which is also what "about:debugging" does by hand. + +Chrome keeps the profile of --user-data-dir, hence the temporary directory, +and --load-extension only takes a directory, never a zip. + +The version of an add-on only takes up to four numbers separated by dots, +where git describe adds the number of commits and a hash: the build writes +"0.5.10-161-gc2572a4a" as "0.5.10.161", so that the builds between two tags +still follow each other. + +That version is read when the build is configured, not when it is made, and it +names the packages. A build made after a commit would therefore carry the +version of the commit before. Cmake watches ".git/HEAD" and ".git/index" for +that reason: a commit or a checkout writes one of them, cmake configures itself +again before make runs, and the packages are named after the sources they hold. +Nothing has to be remembered, and "cmake ." by hand is only needed when the +options change. + +An xpi is a zip whose "manifest.json" sits at its root. The two hold the same +scripts, only their manifest differs: the version 3 declares the hosts apart, +in "host_permissions", and its web accessible resources as objects. Firefox +keeps reading the version 2, unlike Chrome, so the version 2 alone would reach +every Firefox in use; the version 3 is built as well because it is the one +addons.mozilla.org asks for. + +The add-on was published on addons.mozilla.org, and it is not listed there any +more: the page of "webodf" answers 404 and a search of the store returns +nothing. The xpi of the build is installed by hand, see above. + +The description the stores are given, that the field "description" of the +manifests holds a shortened form of, as Chrome only takes 132 characters +there: + +> OpenDocument Viewer reads the documents of the OpenDocument format, the +> ones of LibreOffice, OpenOffice and Collabora: text (.odt, .fodt, .ott), +> spreadsheets (.ods, .fods, .ots) and presentations (.odp, .fodp, .otp). +> +> A document opens in the browser, at once, with no download and no office +> suite to install. +> +> The add-on is light, half a megabyte, where the readers of the format that +> are installed apart weigh a hundred times that. +> +> OpenDocument is the first format of office documents that was approved as +> an international standard, ISO/IEC 26300, in 2006, and the only one that +> works as one: it is written in the open by OASIS, it belongs to no company, +> and several programs of several makers write and read the whole of it. +> +> A document written today is still read in twenty years, by whoever, with +> whatever. + +An add-on has to be signed by Mozilla to install in a release Firefox, whoever +distributes it. + +The add-on replaces the bootstrapped one of 2012, that declared an XPCOM +stream converter and targeted the versions 6 to 15 of Firefox: that kind of +add-on stopped loading in Firefox 57, of 2017. + +Its background script watches the responses whose content type, or whose +extension when the server sends a generic one, is one of the nine of the +OpenDocument format, and sends them to the page "viewer.html" of the add-on, +that reads the document with WebODF. "webodf.js" is a file of its own inside +the package, as the content security policy of an add-on forbids the script +that used to be written inside the page. + +A document that is read from the disk is opened from the page itself, with +"open a local document": webRequest only watches http, https and the web +sockets, so a file:// url never reaches the background script, and Firefox +offers to save it or to open it with an office suite as it would without the +add-on. The stream converter that was replaced sat under all of them, so it +caught those as well; no api of a WebExtension does. + +A text is drawn over pages, which the library breaks it into: the pages of a +text are in no odt at all — the file holds a flow of paragraphs, and where a +page ends is decided by whoever draws it. Each page is a box of the size the +master page of the document gives it, and what does not fit in one is cut +there, a paragraph between two of its words and a table between two of its +rows, with the rows of its head written again. A presentation is another +matter, its slides being written apart in the file, and it is drawn one slide +at a time. + +A reader asks for it, and for the way the pages are laid out, of the canvas: + +```js +canvas.setPaginated(true); // pages, one under another +canvas.setPagesPerRow(2); // two to a row, as a book is read +canvas.setFirstPageOnItsOwn(true); // the first page on the right, as a book +canvas.setPageMode("columns"); // every page on one row, scrolled sideways +canvas.setPageMode("flow"); // one run of text, cut nowhere +``` + +The pages are broken a few at a time: the first of them are drawn in half a +second where a document of eight hundred pages takes minutes, and a reader +reads them while the rest is broken. + +The add-on holds nothing of its own outside the browser. It asks for +"webRequest" and "webRequestBlocking" to send the responses to its page, for +the hosts to read them, and for "downloads" to save the document the page +shows, since a page of an add-on may not save a file of another origin by +itself. + +Only the APIs Firefox has carried since its version 47 are used, so the +version 2 runs down to the version 52 its manifest asks for, that is the one +the key "author" needs. Chrome is out of reach for both: its manifest version +3 wants a service worker as background, that Firefox does not support, and it +dropped the blocking webRequest this add-on redirects with. + +### OpenDocument Viewer for Thunderbird + +The same viewer reads the attachments of the messages, so that a document that +arrives by mail is read in Thunderbird, without saving it and without an office +suite. + +An attachment never travels over http: it is a part of the message itself, +addressed in "mailbox://" or "imap://", that webRequest never sees. The way of +the browsers does not reach it, and the add-on takes another one: it reads the +attachment as a file with "messages.getAttachmentFile" and opens it in a tab of +the viewer. That is the path the button "open a local document" already takes, +so "viewer.html" is used as it is. + +The document is opened from an entry of the menu of the attachment, and from +the menu of a message that carries one, where it is shown once the attachments +have been read. There is no button of its own in the header of a message: +Thunderbird disables such a button but never hides it, and one that is grey on +nearly every message is noise. + +The manager of the add-ons of Thunderbird shows two texts. The short one, that +sits under the name, is the field "description" of the manifest, and it is one +sentence. The long one, under the tab of the details, is not in the package at +all: it is the text of the listing of addons.thunderbird.net, written there at +the submission, and the manager reads it from the store. The one below is +that text. + +> Reads the OpenDocument attachments of a message inside Thunderbird, without +> saving them and without an office suite. +> +> How it is used: +> - right-click an attachment and choose "Open in OpenDocument Viewer"; +> - or right-click the message that carries it, which offers the same entry, +> and a submenu when it carries several documents; +> - the document opens in a tab, at the size of the page, and nothing is +> written to the disk. +> +> It reads the text (.odt, .ott, .fodt), the spreadsheets (.ods, .ots, .fods) +> and the presentations (.odp, .otp, .fodp) that LibreOffice and every other +> office suite write. +> +> Why this format rather than the other one: OpenDocument is the first format +> of office documents approved as an international standard, ISO/IEC 26300, in +> 2006, and the only one that works as one. It is written in the open by OASIS, +> it belongs to no company, and several programs of several makers write and +> read all of it. OOXML was approved in 2008 with a transitional form, meant +> for the older documents and to be dropped from the standard, and that form is +> still what the office suites write today, Microsoft 365 among them, on the +> desktop as on the web. +> +> It holds no permission on the network, reads no message but the one that is +> shown, and is free software, under the AGPL 3. + +Neither text takes a link, so the way to the page that tells the whole of it is +the button of the options, the wrench of that same page, which "options_ui" +points at "welcome.html": a page that holds nothing but the choice of the +language, since the manager opens a single address and knows none. + +The page of the welcome is written once for each language it is translated in, +"welcome.fr.html" beside "welcome.en.html", and the one of the language of +Thunderbird is opened, English being the one the others fall on. Its opening +is written three times, in the attributes of the title and of the first +sentence: the add-on greets a reader when it is installed, says it is up to +date when it is updated, and tells what it is when it is read from the +manager. A page of +prose is read and corrected far more easily as a page than as a file of +sentences apart, which is why the messages of i18n are not used for it. + +A page of welcome is opened when the add-on is installed, and once more when +it is updated from a version that never showed it, a flag of the storage +telling one from the other. It says where the entries of the menu are, how the +settings are changed so that a double click opens the document in Thunderbird, +and what the format is worth against the other one. The pictures of the menus +are read from "skin/default/menu-attachment.png" and +"skin/default/menu-message.png". They carry the words of Thunderbird, so one +is kept for each language they are taken in, named after it: +"menu-message.fr.png" beside "menu-message.png". The page tries the language +of the reader, then that language alone, "pt" for "pt-BR", then the English +one. Each figure that finds no picture at all leaves the page rather than +showing a hole, so the page holds with none of them, with one, or with the +whole set. + +A double click on an attachment is not answered for: Thunderbird carries no api +for that, and what it runs is the program the settings of the system name, +LibreOffice for a document of this format. Neither does an add-on write those +settings: no api reads or writes the handlers of the types, and only an +experiment, that runs privileged code and breaks at every release, reaches +them. An add-on adds a way of opening an attachment, it does not take the one +of the system over. + +The entry is shown on the documents alone. An attachment is one when its type +is one of the nine of the format, or, for the servers that send everything as +an octet stream, when its name ends in one of the nine extensions. + +The add-on asks for "menus", to add the entry, for "messagesRead", to read the +attachment of the message that is shown, and for "downloads", as the viewer +saves the document it draws. It reads no other message, and no network. + +```sh +make -C build product-opendocumentviewer-thunderbird +``` + +It is packed twice as well: the version 3 needs Thunderbird 128, of 2024, and +the version 2 reaches back to the 98, of 2022. That floor is the one of the +menu on an attachment; every other call the add-on makes is older: the button +of the header comes from the 71, the list of the messages that are shown and +its event from the 81, and the attachments of a message from the 88. +Thunderbird carries no service worker, so both hold their background as +scripts. The add-on is published on +[addons.thunderbird.net](https://addons.thunderbird.net/), which is not AMO, +and is signed there. + +The linter of Mozilla reads those packages as well, and reports two warnings +on each that belong to Firefox and not to Thunderbird: the permission +"messagesRead", that it does not know, and the key +"data_collection_permissions", that AMO asks for since Firefox 140 and that no +Thunderbird of those versions reads. + +### OpenDocument Viewer for Android + +This application shows a document in the OpenDocument format on Android, so +that the documents a phone receives are read without an office suite. It +registers for the nine types of the format, so the system offers it when one +is opened. + +Started on its own, from the list of the applications, it shows an empty page +that asks for a document, and opens the picker of the system when it is touched: +the system reads the file and hands it over, so the viewer holds no permission +to reach the storage. It carries no bar and no button, as there would be one of +each. + +It runs from Android 5.0, the release the web view began to be updated apart +from the system in, so it holds a recent engine even on an old phone. + +With a prepared setup for building, from the build directory: + +```sh +cmake -S ../webodf -DWEBODF_PROGRAMS=ON -DWEBODF_ANDROID=ON +make product-opendocumentviewer-android +``` + +It is behind the option WEBODF_ANDROID, as it needs the sdk of android and a +jdk, that the build does not download. Gradle is downloaded, as the closure +compiler and Rhino are: its version is the one the plugin of android asks for, +9 for the plugin 9, where Debian 13 packages the 4.4.1 of 2017. The sdk is read from +-DANDROID_SDK, from $ANDROID_HOME or $ANDROID_SDK_ROOT, from a +"local.properties" that is already there, or from "~/Android/Sdk" and +"/usr/lib/android-sdk". What is looked for in each of them is a "platforms" +directory: a machine may hold a "/usr/lib/android-sdk" of the tools of the +platform alone, which is not an sdk to build with, beside a whole one +elsewhere. Cmake stops with a message when it finds none, and +keeps what it found in its cache, so that the option or the variable is given +once and not at every build. It writes "local.properties" itself, which is how +gradle reads the sdk when make runs from another shell. + +A variable that is exported apart, "ANDROID_HOME=/path/to/sdk; cmake ...", is +never seen by cmake: the semicolon ends the command, and the assignment stays +in the shell. It is written in the same command as cmake, with no semicolon, +or exported, or given as -DANDROID_SDK. +The command line tools that install the sdk are at https://developer.android.com/studio#command-tools, +and the packages the build needs are: + +```sh +sdkmanager "platforms;android-36" "build-tools;36.0.0" "platform-tools" +``` + +The apk that is written is not signed, as only its author may sign it, so it +installs nowhere as it is. A second one is built for a test, that gradle signs +with the key it writes for that: + +```sh +make opendocumentviewer-android-debug +``` + +It is written next to the other, in "build/programs/opendocumentviewer-android/build/ +outputs/apk/debug/opendocumentviewer-android-debug.apk", and installs on a device with +"adb install", or in Waydroid, that runs Android on a Linux desktop: + +```sh +waydroid session start +waydroid app install .../opendocumentviewer-android-debug.apk +``` + +A document is read from the shared storage of Waydroid, "~/.local/share/waydroid/data/media/0", +that Android sees as "/sdcard": copy one there and open it with a file manager, +that offers the viewer among the applications that read the format. + +Waydroid needs a web view of its own, that its images hold; the viewer draws +nothing without one. + +The texts of the stores are in the sources, under +"programs/opendocumentviewer-android/src/main/fastlane/metadata/android/", in +the layout fastlane defines: a directory for each locale, with "title.txt", a +"short_description.txt" of 80 characters at most, a "full_description.txt" of +4000, and a changelog for each version code. F-Droid reads them from the +repository at each build, so the description of the store is written and +reviewed with the code; Google Play does not, and its console asks for the +same texts by hand, or its publishing api does. + +The icon is drawn once, in 512 pixels, and the sizes are cut from it. F-Droid +reads it in "images/icon.png" of the locale, transparent, as it draws it on +the ground of its own card. Google Play refuses a transparency and masks the +corners itself, so it is given an opaque copy, "store/icon-play.png", that is +uploaded by hand and is in no package. The launcher of android gets an +adaptive icon, "mipmap-anydpi-v26/icon.xml": a white ground and a drawing that +holds in the 72dp of the 108dp that every shape of every maker shows, with the +sizes of the five densities beside it, and a square icon with a margin for the +launchers before android 8. + +The sdk is the only part of the build that is not free software, and the reason +this product is off by default. The build tools are free: Debian builds them +from the sources of AOSP and ships them in main. The platform is not free: it +comes from the servers of Google, under the Android Software Development Kit License Agreement, +that gradle asks to accept and writes in "$ANDROID_HOME/licenses/". Debian may +not redistribute it, and only packages an installer that downloads it, in +non-free. Those terms cover the sdk, not what is built with it: the apk stays +under the license of WebODF. + +It is a web view that reads a page and the library from the assets of the +application, with no framework, no plugin and no dependency at all: the sources +are in "programs/opendocumentviewer-android" and hold one class. Everything is served +over "https://webodf.invalid/", from the requests the web view is intercepted +on, as a page loaded from "file://" may not read another file with XMLHttpRequest, +which is how the library reads a document. Only the four files of the viewer and +the one document are served, each compared by its name and never used to build a +path. The document the system hands over comes as a "content://" uri, that a web +view may not read, so it is copied into the cache first. + +Nothing the viewer does reaches the network. The application asks for no +permission, INTERNET included, so the system refuses a connection whatever +happens. The web view answers every request itself, and answers with nothing at +all when the address is not one of its files: a document that holds an image or +a style sheet of the web tells no server that it was opened. Safe Browsing is +turned off in the manifest, as there is no address to check, which spares the +web view from asking Google for its lists, and the metrics it may report are +turned off as well. + +The web view is still the one of the system, kept up to date by Google, and what +it does on its own, such as reading the configuration of its field trials, +belongs to it rather than to this application. + +It replaces the product of "programs/cordova", that drove cordova 3.5, of 2014, +and built android with ant, which google dropped in 2015 for gradle, through the +executable "android", that the sdk replaced by "sdkmanager" in 2018. + +### OpenDocument Viewer for the desktop + +This program shows a document of the OpenDocument format on a desktop, on linux, +on windows and on macos: a window of qt around the same page the add-ons of the +browsers and the viewer for android draw a document in. There is one place where +the reading of the format lives, and one behaviour to keep in step. + +It reads the text (.odt), the spreadsheets (.ods), the presentations (.odp) and +the drawings (.odg), with their templates. A document is opened from the menu, +by dropping it in the window, or by naming it on the command line, which is how +a file manager opens one. It is drawn at the size it was written at, and only +scaled down when it is wider than the window; the menu of the display zooms it, +sets it back to its own size, or fits it to the width. + +A document is printed, and written as a pdf, by the printing of the engine. + +The menu of the help, and the foot of the empty screen, lead to a page that +tells what the viewer does and what the format is worth, in French or in +English, after the language of the system. The part about the format is the same +text as the one the add-ons and the viewer for android show, and it is the same +file: see "One text for every product" below. + +With a prepared setup for building, from the build directory: + +```sh +cmake -S ../webodf -DWEBODF_DESKTOP=ON +make product-opendocumentviewer-desktop +``` + +It is behind the option WEBODF_DESKTOP, as it needs the modules of qt, and it +is built by that option alone: it needs neither Dojo nor the editors, so +WEBODF_PROGRAMS is not asked for. The modules are the ones qtjsruntime needs, +see "README-Building.md". + +The program is one file: the page, its style, its script, the library and the +icon are all put in it, so it runs wherever it is copied. It is installed with +what a desktop needs to offer it for a document: + +```sh +make install +``` + +It writes the program in "bin", an entry in "share/applications" that names the +nine types of the format, and the icon under the name of the entry, as the +specification of the freedesktop asks. A double click on a document then offers +the viewer among the applications that read the format. + +#### Trying the viewer that was built + +The program that was built is run where it stands, without installing it and +without packing it: + +```sh +build/programs/opendocumentviewer-desktop/opendocumentviewer-desktop +build/programs/opendocumentviewer-desktop/opendocumentviewer-desktop a-document.odt +``` + +Named with a document, it opens it; named with nothing, it opens the empty +screen. The library it draws with is put in it when it is linked, so a fix of +the library is seen only once the program is linked again: "make -C build +product-opendocumentviewer-desktop" answers for both. + +The archive holds the same program with the entry of the menu and the icon, for +a machine where it is to be tried as it is handed over: + +```sh +mkdir -p /tmp/viewer +tar xzf build/products/opendocumentviewer-x.y.z-linux-x86_64.tar.gz -C /tmp/viewer +/tmp/viewer/bin/opendocumentviewer-desktop a-document.odt +``` + +The page and the document it shows are served by the program itself, under a +scheme of its own, "odf:", see "programs/opendocumentviewer-desktop/viewerscheme.cpp": +they are one origin that way, which is what the page needs to read the document, +and the disk is not opened to it for that, as the one document that was chosen +is served, at one address, whichever file it is. + +#### The viewer on windows + +Nothing in the program is of linux, and the build writes what windows asks for: +the icon in the format of its own, made from the icon of the project at the +sizes windows draws it at, the version that the properties of the file show, and +a program that opens no terminal behind its window. + +Qt WebEngine does not compile with MinGW, which the documentation of Qt states, +so the compiler is the one of Microsoft. The program is of 64 bits, and only of +64 bits: Qt 6 is built for x86_64 and for arm64, and no longer for a windows of +32 bits. The property WIN32_EXECUTABLE of cmake, that the build sets, is named +after the interface of windows and not after an architecture: it says that the +program opens a window rather than a terminal. What is needed, beside the sources: + +* Visual Studio Build Tools, for the compiler and the linker; +* Qt 6 with the modules of the viewer, WebEngine among them, from the installer + of Qt; +* CMake, Ninja, node and a java runtime, as on linux. + +```sh +cmake -S ..\webodf -DWEBODF_DESKTOP=ON -DWEBODF_QTJSRUNTIME=ON +cmake --build . --config Release --target test-qtjsruntime +cmake --build . --config Release --target product-opendocumentviewer-desktop +cmake --install . --config Release --prefix dist +``` + +The generator of visual studio holds more than one configuration and pays no +heed to "CMAKE_BUILD_TYPE": the configuration is named at each build and at the +installation, otherwise the build writes "Debug" and the installation looks for +"Release". + +The first target runs the tests of the library in the webengine of qt, the whole +suite of the browser: it is what tells that the library behaves on windows as it +does elsewhere, which nobody has known since Qt WebKit died. It costs nothing to +ask for once the modules of qt are there, see "README-Building.md". + +The installation runs "windeployqt", which gathers the libraries of qt, +"QtWebEngineProcess.exe" and the resources it reads beside the program: the +directory then runs on a machine where qt is not installed. That is what the +script of the installer takes: + +```sh +iscc programs\opendocumentviewer-desktop\opendocumentviewer.iss +``` + +It is written for [Inno Setup](https://jrsoftware.org/isinfo.php), and it +declares the nine types of the format under one identifier, in +"OpenWithProgids": the viewer is then offered beside the office suite of the +machine, in "Open with", and it never takes the place of what a double click +opens. Windows warns about a program that is not signed, so a certificate is +needed for an installer that is handed to others. + +##### A machine to build on + +The build needs a windows, and there is none to cross build from: the compiler +of Microsoft does not run elsewhere, and MinGW, that does, is the one WebEngine +refuses. A virtual machine answers, in VirtualBox as in QEMU. Microsoft offers +an evaluation of windows, and a machine that is already prepared for +development, at https://developer.microsoft.com/windows/downloads/. Give it 8 GB +of memory and 60 GB of disk: Qt with WebEngine and the build tools of Visual +Studio weigh some 30 GB together, and the build writes as much again. + +Everything is then installed by one script, in a terminal opened as an +administrator: + +```powershell +powershell -ExecutionPolicy Bypass -File programs\opendocumentviewer-desktop\data\setup-windows.ps1 +``` + +It takes the tools from winget and Qt from its own repository, with +[aqtinstall](https://github.com/miurahr/aqtinstall), the installer of Qt asking +for an account that a script cannot answer for. Since Qt 6.8, WebEngine is an +extension rather than a module, in a repository of its own, so the script checks +that it was written and says what to do when it was not. It ends by printing the +commands above. + +A machine without a graphics card, which a virtual one often is, draws the +documents all the same: chromium falls back on the processor. Should a window +stay empty, the fallback is asked for by hand: + +```powershell +set QTWEBENGINE_CHROMIUM_FLAGS=--disable-gpu +``` + +#### The viewer on macos + +The same program, and the same build: what macos asks for beyond it is a bundle +rather than a plain executable, an icon in the format of its own, made from the +same drawing as the one of windows, and a "Info.plist" that names the eleven +types the viewer reads. + +```sh +cmake -S ../webodf -DWEBODF_DESKTOP=ON -DWEBODF_QTJSRUNTIME=ON \ + -DCMAKE_PREFIX_PATH=$HOME/Qt/6.8.2/macos +cmake --build . --target test-qtjsruntime +cmake --build . --target product-opendocumentviewer-desktop +cmake --install . --prefix dist +``` + +The installation runs "macdeployqt", which gathers the frameworks of qt, +"QtWebEngineProcess.app" and the resources it reads inside the bundle: +"OpenDocumentViewer.app" then runs on a machine where qt is not installed. The +name of the bundle holds no space, as the script that qt writes to deploy the +libraries does not quote the path it is given; the name a desktop shows is +"OpenDocument Viewer", from the plist. + +One thing of macos is in the code rather than in the build: a document opened by +a double click, or by "Open with", is not named on the command line there. The +system sends it to the application once it is running, as an event, which +"main.cpp" listens for. Without it the viewer would open its empty window and +forget what it was asked for. + +The bundle is neither signed nor notarised by the build. Without that, macos +refuses to open it save through the menu of the context, and says it comes from +an unidentified developer. Both need an account of the developer program of +Apple, at a hundred dollars a year: + +```sh +codesign --deep --force --options runtime --sign "Developer ID Application: ..." \ + dist/OpenDocumentViewer.app +xcrun notarytool submit --wait ... +xcrun stapler staple dist/OpenDocumentViewer.app +``` + +### One place for every product + +Every product is written where the target that packs it stands, deep in the +tree of the build. They are gathered in one place by: + +```sh +make -C build products # or: ninja -C build products +``` + +which makes each one, gathers it in "products/" of the build and names it. The +add-ons of the browsers and of Thunderbird, the two editors, the apk of android, +docnosis, the archive of the viewer of the desktop and the packages of it a +system installs are all there, each one named after the version of the sources +it was built from, so a product tells which sources it holds. + +A way of handing the viewer over that the machine has no tool to write is named +at the end, with the tool that is wanting, so that a product that is missing is +not taken for one that failed: + +``` +The products of this build are in build/products: + docnosis-0.5.10-314-g3b87b4ee.zip + opendocumentviewer-firefox-0.5.10-314-g3b87b4ee.xpi + opendocumentviewer-0.5.10-314-g3b87b4ee-linux-x86_64.tar.gz + opendocumentviewer-0.5.10-314-g3b87b4ee-x86_64.deb + opendocumenttexteditor-0.5.10-314-g3b87b4ee.zip + +This machine has no tool to make: + the package of fedora, for want of rpmbuild + the AppImage, for want of linuxdeploy + the flatpak, for want of flatpak-builder +``` + +The tests are run the same way, by one target: + +```sh +make -C build tests +``` + +### The release of the products + +A tag that names a version publishes the products with the release, on both +forges and apart: github builds and publishes its own, see +".github/workflows/release.yml", gitlab builds and publishes its own, see +".gitlab-ci.yml". Neither pushes a file to the other, and neither holds a +token of the other: github signs its release with the token of the run, and +gitlab with the token of the job. + +The runners of gitlab are of linux, so what asks for a machine of windows or +of macos is built by github alone. What asks for a key of its own is built by +neither, as a key belongs to whoever publishes and not to a forge. + +| Product | github | gitlab | Why | +|---------------------------------|--------|--------|-------------------------------| +| library, tgz | yes | yes | node alone builds it | +| add-ons of the browsers, xpi | yes | yes | node alone builds them | +| the two editors, zip | yes | yes | node alone builds them | +| archive of the desktop, linux | yes | yes | a runner of linux | +| package of debian, deb | yes | yes | dpkg-deb, of linux | +| package of fedora, rpm | yes | yes | rpmbuild, of linux | +| AppImage | no | no | linuxdeploy, not there | +| flatpak | no | no | flatpak-builder, not there | +| archive of the desktop, windows | yes | no | a runner of windows | +| archive of the desktop, macos | yes | no | a runner of macos | +| apk of android | no | no | it is signed by its publisher | +| bundle of macos, signed | no | no | it is notarised by Apple | +| application of ios | no | no | it is signed by its publisher | + +The AppImage and the flatpak would be built by adding their tools to the +runner of linux: the targets are there and skip themselves when the tool is +wanting, as they do on any machine. + +### Handing the viewer of the desktop over + +A program of the desktop is not a file that is downloaded and read, as an +add-on is: it is installed. Five ways are written here, from the shortest to +the most finished. Each one is a target of its own, for a build that wants one +of them alone: + +```sh +make -C build package-deb +``` + +and "products" makes the ones the tools of the machine allow, with everything +else the build is for, see "One place for every product" above. + +| Target | What it writes | What it asks of the machine | +|------------------|------------------------------------------------|-----------------------------------------------------------| +| package-archive | "opendocumentviewer-x.y.z-linux-x86_64.tar.gz" | qt 6 of the system, unpacked by hand | +| package-deb | "opendocumentviewer-x.y.z-x86_64.deb" | dpkg-deb, and qt 6 named as a dependency | +| package-rpm | "opendocumentviewer-x.y.z-x86_64.rpm" | rpmbuild, and qt 6 named as a dependency | +| package-appimage | "OpenDocumentViewer-x.y.z-x86_64.AppImage" | linuxdeploy and its plugin of qt, and it carries qt | +| package-flatpak | "org.webodf.OpenDocumentViewer-x.y.z.flatpak" | flatpak-builder and the runtime of KDE, and it carries qt | + +Every one of them is written among the products of the build. + +The archive and the packages of a system name the qt of the machine rather +than carrying it, which is what a distribution asks for: a program that +carries its own qt is a program that no one updates when qt is fixed. The two +universal packages carry it, which is what someone who runs another +distribution than the one the package was made on needs. + +The manifest of the flatpak names the runtime of KDE, that carries qt, and the +base app of qt, that carries its webengine, and gives the sandbox what a reader needs and no more: a window, +and the documents the reader opens. It reaches no network, as a document is +drawn on the machine and nothing of it is sent. + +That runtime is installed apart from the tool, and the build asks for both when +cmake is run rather than in the middle of the build, where a runtime that is +not there reads as a broken build: + +```sh +flatpak remote-add --user --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo +flatpak install --user flathub org.kde.Platform//6.9 org.kde.Sdk//6.9 \ + io.qt.qtwebengine.BaseApp//6.9 +cmake -S . -B build +``` + +The branch is the newest one the machine has, read when cmake is run, as a +branch of the runtime is declared end of life as soon as a newer one is out; +"-DWEBODF_FLATPAK_RUNTIME=6.9" names another. Without any of them, "products" +is made without the flatpak and says so at the end, and +"make -C build package-flatpak" tells what to install. + +### OpenDocument Viewer for iOS + +Apple allows no engine of the web but its own on iOS, so a viewer there is a +shell around WKWebView, the view of the system, which is the WebKit of Safari. +It is what Cordova did in 2012, in the project that was in the attic, +with the UIWebView of the time: the same architecture, without the framework +between, and with the engine of today, that compiles the javascript rather than +reading it. + +The shell is written in Swift, in "Sources", and it is the one of android in the +words of another system: the page, the library, the way a document is served and +the way a link is followed are the same, see "ViewerActivity.java" beside it. +Everything is served under "odf://viewer/", by a handler of that scheme, so that +the page and the document are of one origin: it is what lets the library read a +document with a request, and no name a document holds reaches anything else. No +request of this viewer ever leaves the device. + +The page it shows is the page of android, file for file: cmake copies it, with +the library and the pages about the format, into "Resources". + +```sh +cmake -S ../webodf -DWEBODF_IOS=ON +make product-opendocumentviewer-ios +``` + +The application itself is built by Xcode, on a mac: nothing else builds one for +iOS. The project is written by [XcodeGen](https://github.com/yonaskolb/XcodeGen) +from "project.yml", a project of Xcode being a file no one writes by hand: + +```sh +brew install xcodegen +cd programs/opendocumentviewer-ios +xcodegen generate +open OpenDocumentViewer.xcodeproj +``` + +What is needed beyond the code is not code: a mac with Xcode, an account of the +developer program of Apple, that costs a hundred dollars a year, the signature +of the application, and the review of the App Store. There is no F-Droid there, +and no way to hand an application over outside the store, save to the devices of +the developer. + +### One text for every product + +Three products tell what the OpenDocument format is worth, in French and in +English: the add-ons of the browsers, in their page of the welcome, the viewer +for android and the one for the desktop, in their page about. It is one text, +written once, in "programs/text/format.en.html" and "programs/text/format.fr.html". + +A page that shows it is a template, "*.html.in", that names it where it goes: + +```html +@FORMAT_TEXT@ +``` + +Cmake reads the text and writes the page, see the macro INSERT_TEXT in the +CMakeLists of the root. The page of a product is written where that product +reads it: in the build directory for the add-ons and for the viewer of the +desktop, which reads it from its resources, and among the assets for android, +where gradle reads it, as the library is copied there as well. Those pages are +not in the repository, only their templates and the text. + +The text is named as a dependency of the configuration, so cmake runs itself +again as soon as it is revised, and the three products follow. A revision is +therefore made in one file, and it cannot be forgotten in another. + +### Products that are not built any more + +One product of "programs/cordova" is still declared, and no version of its +toolchain is available: it is kept until it is either brought up to date or +dropped. + +"make product-opendocumentviewer-firefoxos" packs it for Firefox OS. The system was abandoned +by Mozilla in 2016, but it lives on through its forks, B2G OS, KaiOS and +Capyloon, so the product is worth reviving rather than dropping. KaiOS in +particular runs on feature phones that no office suite serves, which is the kind +of place a viewer of the OpenDocument format is the most useful. + +### docnosis, that tells whether a document keeps to the standard + +A page, and nothing else: a document is dropped on it, and it says what it is +made of and whether it holds to OpenDocument. The document is read in the page +and goes nowhere — nothing is uploaded, and no server sees it, which is what +sets it apart from [the validator of the ODF Toolkit](https://odfvalidator.org/), +of [odftoolkit.org](https://odftoolkit.org/), that reads the same schemas in +java, on a server. + +```sh +cmake -S ../webodf -DWEBODF_PROGRAMS=ON +make product-docnosis +``` + +It writes "docnosis-x.y.z.zip", that holds the page, the schemas and the sources +of the library: the validator of Relax NG is left out of "webodf.js", as no +viewer uses it, so the page reads the library from its sources. A document is +dropped on the page, or named in the address, which is how it is run without +hands: + +``` +index.html?file=document.odt +``` + +What it tells of a document: + +* the type it declares, and whether the package says the same; +* the version of the standard it was written to, from 1.0 to 1.4, the schemas + of every one of them being there, as published by OASIS; +* whether it holds to the schema of that version; +* what it holds that the standard does not define, named by the namespace it is + written under, "loext" for LibreOffice. + +That last one is the point of the whole thing. A document of LibreOffice does +not fit the schema, and it is not broken for that: the standard says that a +program writes its additions under a name of its own and that a reader that does +not know them ignores them, see the text of the format. Reporting them apart +from the errors is what keeps a validator from calling a sound document broken. + +### The products that were + +A viewer of documents has been written for whatever ran a web view, since 2012, +and the list is kept here: what a thing was, and what became of it. The code is +in the history of the repository, and it is taken out of the commit that dropped +it: + +```sh +git log --diff-filter=D -- programs/touchui +git show ^:programs/touchui/index.html +``` -This creates a file "firefox-extension-odfviewer-x.y.z.xpi", which can be directly installed as add-on in Firefox browsers. +| Product | What it was | What became of it | +|---------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| nativeQtClient | A window of qt 4, with a tree of files, that showed a document in a QWebView. It carried "programs/touchui" | Dropped in 2026, commit 3b64c38a. The viewer of the desktop, in qt 6, does what it did | +| The client for iOS | Cordova 1.8 around a UIWebView, of 2012, that Apple stopped accepting | Dropped in 2026, commit 3b64c38a. Written again in Swift, around WKWebView | +| The client of the BlackBerry PlayBook | A widget of WebWorks, of 2012, with an extension of its own to read a file. The tablet was abandoned in 2014, BlackBerry 10 in 2022, and the servers that signed an application are gone | Dropped in 2026, commit 3b64c38a. Nothing replaces it, as nothing runs it | +| The viewer for android of cordova | Cordova 3.5, of 2014, built with ant, that google dropped in 2015 | Replaced in 2026 by "programs/opendocumentviewer-android", that is a web view and no framework | +| The viewer for Firefox OS | Cordova as well, packed as a widget of the system | Still declared, and still unbuildable, see above | +| qtjsruntime in Qt WebKit | The tests of the library, run in the webkit of qt | Written again in 2026 for the webengine of qt 6, WebKit having left qt in 5.6 | +| "programs/touchui" | The touch interface of 2012, written with Sencha Touch: a browser of files and a view of a document. It was never packed on its own, the client of qt and the one of the PlayBook carried it | Dropped in 2026, commit de1bf464, with the externs of Ext JS that went with it. [Sencha Touch was merged into Ext JS](https://www.sencha.com/products/touch/), which is sold rather than free, and the viewers of today need no framework at all: a page and the library | -Download and install the latest officially released version from [Mozilla's Add-on website](https://addons.mozilla.org/firefox/addon/webodf/). +Two products were envisaged and never written: one for KaiOS, which the package +of Firefox OS is the beginning of, and one for macos, which is the viewer of the +desktop once it is packed as a bundle, signed and notarised. diff --git a/README.md b/README.md index 4ba715b6f..d265067b5 100644 --- a/README.md +++ b/README.md @@ -2,14 +2,20 @@ WebODF is a ODF JavaScript library originally created by KO GmbH. -It makes it easy to add Open Document Format (ODF) support to your website and to your mobile or desktop application. It uses HTML and CSS to display ODF documents. +It makes it easy to add Open Document Format (ODF) support to your website and +to your mobile or desktop application. It uses HTML and CSS to display ODF +documents. * Visit the project homepage at: [WebODF](https://webodf.org) * Want some live demos? Visit: [WebODF Demos](https://webodf.org/demos/) * Get in contact: + * the issues of the repository, that are read * Slack: webodf.slack.com, use the [self-invite](https://join.slack.com/t/webodf/shared_invite/enQtNTQ1NDAyNDU1NjY2LWFlZDg1NzBjY2IzY2RmMzhhMTcwZjM1YjJjOTRmMjM4Yzg1MzhjODY5N2MwOWQwMWNiNzhlZTVlYjI3MDY5YTc) - * [mailing list](https://lists.opendocsociety.org/mailman/listinfo/webodf) or - * IRC (#webodf auf freenode, [Web access](http://webchat.freenode.net/?nick=webodfcurious_gh&channels=webodf)) + * the [mailing list](https://lists.nlnet.nl/archives/list/webodf@nlnet.nl/), + that NLnet hosts + +The channel of freenode was the other way, and it answers no more: freenode was +abandoned in 2021. ### License @@ -17,35 +23,61 @@ WebODF is a Free Software project. All code is available under the AGPL. If you are interested in using WebODF in your commercial product (and do not want to disclose your sources / obey AGPL), -get in touch at [the license page](https://webodf.org/about/license.html) for a license suited to your needs. +get in touch at [the license page](https://webodf.org/about/license.html) for a +license suited to your needs. ### Creating webodf.js... -webodf.js is compiled by using the Closure Compiler. This compiler concatenates and compacts all JavaScript files, so that they are smaller and execute faster. CMake is used to setup the buildsystem, so webodf.js can be created: +webodf.js is compiled by using the Closure Compiler. This compiler concatenates +and compacts all JavaScript files, so that they are smaller and execute faster. +CMake is used to setup the buildsystem, so webodf.js can be created: + +```sh +git clone https://github.com/webodf/WebODF.git webodf +mkdir build +cd build +cmake -S ../webodf +make webodf.js-target +``` - git clone https://github.com/kogmbh/WebODF.git webodf - mkdir build - cd build - cmake ../webodf - make webodf.js-target +A successful run will yield the file "webodf.js" in the subfolder "build/webodf/", +among other things, from where you can then copy it and use for your website. -A successful run will yield the file "webodf.js" in the subfolder "build/webodf/" (among other things), from where you can then copy it and use for your website. +For more details about preparing the build of webodf.js , e.g. on Windows or OSX, +please study ["README-Building.md"](README-Building.md). -For more details about preparing the build of webodf.js , e.g. on Windows or OSX, please study ["README-Building.md"](README-Building.md). +What a program may lean on in the library — the canvas that draws a document, +the container that holds it, and what each of them answers — is written in +["PUBLIC-API.md"](PUBLIC-API.md). ### ... and more -This repository not only contains code for the library webodf.js, but also a few products based on it. Here is the complete list: +This repository not only contains code for the library webodf.js, but also a few +products based on it. Here is the complete list: -build target | output location (in build/) | description | download/packages ------------------------------|---------------------------------------|------------------------------------|----- -webodf.js-target | webodf/webodf.js | the library | (see product-library) -product-library | webodf.js-x.y.z.zip | zip file with library and API docs | [WebODF homepage](http://webodf.org/download) -product-wodotexteditor | wodotexteditor-x.y.z.zip | simple to use editor component | [WebODF homepage](http://webodf.org/download) -product-wodocollabtexteditor | wodocollabtexteditor-x.y.z.zip | collaborative editor component | [WebODF homepage](http://webodf.org/download) -product-firefoxextension | firefox-extension-odfviewer-x.y.z.xpi | ODF viewer Firefox add-on | [Mozilla's Add-on website](https://addons.mozilla.org/firefox/addon/webodf/) +build target | output location (in build/) | description | +---------------------------------------|-----------------------------------------------------|----------------------------------------------| +webodf.js-target | webodf/webodf.js | the library as standalone for any js project | +product-library | node-webodf-x.y.z.tgz | the library and the classes it is made of | +product-opendocumenttexteditor | opendocumenttexteditor-x.y.z.zip | simple to use editor component | +product-opendocumenttextcollab | opendocumenttextcollab-x.y.z.zip | collaborative editor component | +product-opendocumentviewer-webext | opendocumentviewer-firefox-x.y.z.xpi | ODF viewer add-on for Firefox and Chrome | +product-opendocumentviewer-thunderbird | opendocumentviewer-thunderbird-x.y.z.xpi | ODF viewer add-on for Thunderbird | +product-opendocumentviewer-desktop | opendocumentviewer-x.y.z-*.tar.gz, .deb, .rpm, .zip | ODF viewer for linux, windows and macos | +product-opendocumentviewer-android | opendocumentviewer-x.y.z.apk | ODF viewer application for Android | ("x.y.z" is a placeholder for the actual version number) -For more details about the different products, please study ["README-Products.md"](README-Products.md). +A tag that names a version publishes these products with the release, on both +forges and apart: + +* [the releases of github](https://github.com/webodf/WebODF/releases) +* [the releases of gitlab](https://gitlab.com/Sempia/WebODF/-/releases) + +Github builds the viewers of windows and of macos, which its runners of those +systems alone can build; gitlab, whose runners are of linux, builds what a linux +builds. What asks for a key of its own is published by neither: the apk of +android, the bundle of macos and the application of ios. Which product lands +where is named in ["README-Products.md"](README-Products.md), which tells more +of the products as well. diff --git a/attic/CMakeLists.txt b/attic/CMakeLists.txt deleted file mode 100644 index 4be50f553..000000000 --- a/attic/CMakeLists.txt +++ /dev/null @@ -1 +0,0 @@ -add_subdirectory(programs) diff --git a/attic/README b/attic/README deleted file mode 100644 index c01a8a30e..000000000 --- a/attic/README +++ /dev/null @@ -1,2 +0,0 @@ -This directory is the place where we keep code that is not currently supported but might be revived in the future if someone would like to revive it. -Reviving welcome. diff --git a/attic/programs/CMakeLists.txt b/attic/programs/CMakeLists.txt deleted file mode 100644 index 2fbfeb114..000000000 --- a/attic/programs/CMakeLists.txt +++ /dev/null @@ -1,4 +0,0 @@ -add_subdirectory(nativeQtClient) - -add_subdirectory(playbook) - diff --git a/attic/programs/ios/Default-Landscape~ipad.png b/attic/programs/ios/Default-Landscape~ipad.png deleted file mode 100644 index 06bb96b39..000000000 Binary files a/attic/programs/ios/Default-Landscape~ipad.png and /dev/null differ diff --git a/attic/programs/ios/Default-Portrait~ipad.png b/attic/programs/ios/Default-Portrait~ipad.png deleted file mode 100644 index dbfed967a..000000000 Binary files a/attic/programs/ios/Default-Portrait~ipad.png and /dev/null differ diff --git a/attic/programs/ios/WebODF.xcodeproj/project.pbxproj b/attic/programs/ios/WebODF.xcodeproj/project.pbxproj deleted file mode 100644 index ac8217180..000000000 --- a/attic/programs/ios/WebODF.xcodeproj/project.pbxproj +++ /dev/null @@ -1,518 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 46; - objects = { - -/* Begin PBXBuildFile section */ - CB099EC714DAC535000D7B99 /* Default-Portrait~ipad.png in Resources */ = {isa = PBXBuildFile; fileRef = CB099EC614DAC535000D7B99 /* Default-Portrait~ipad.png */; }; - CB099EC914DAC53D000D7B99 /* Default-Landscape~ipad.png in Resources */ = {isa = PBXBuildFile; fileRef = CB099EC814DAC53D000D7B99 /* Default-Landscape~ipad.png */; }; - CB29ECBD14FFBBBB00CEAEE3 /* unzip.c in Sources */ = {isa = PBXBuildFile; fileRef = CB29ECBC14FFBBBB00CEAEE3 /* unzip.c */; }; - CB29ECBF14FFBC0500CEAEE3 /* ioapi.c in Sources */ = {isa = PBXBuildFile; fileRef = CB29ECBE14FFBC0500CEAEE3 /* ioapi.c */; }; - CB29ECC114FFBC1B00CEAEE3 /* mztools.c in Sources */ = {isa = PBXBuildFile; fileRef = CB29ECC014FFBC1B00CEAEE3 /* mztools.c */; }; - CB29ECC314FFBC5500CEAEE3 /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = CB29ECC214FFBC5500CEAEE3 /* libz.dylib */; }; - CB36D11814F68F7F0084BECB /* www in Resources */ = {isa = PBXBuildFile; fileRef = CB36D11714F68F7F0084BECB /* www */; }; - CB533D9114DABDA600C733F6 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CB533D9014DABDA600C733F6 /* Foundation.framework */; }; - CB533D9314DABDA600C733F6 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CB533D9214DABDA600C733F6 /* UIKit.framework */; }; - CB533D9514DABDA600C733F6 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CB533D9414DABDA600C733F6 /* CoreGraphics.framework */; }; - CB533D9714DABDA600C733F6 /* AddressBook.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CB533D9614DABDA600C733F6 /* AddressBook.framework */; }; - CB533D9914DABDA600C733F6 /* AddressBookUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CB533D9814DABDA600C733F6 /* AddressBookUI.framework */; }; - CB533D9B14DABDA600C733F6 /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CB533D9A14DABDA600C733F6 /* AudioToolbox.framework */; }; - CB533D9D14DABDA600C733F6 /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CB533D9C14DABDA600C733F6 /* AVFoundation.framework */; }; - CB533D9F14DABDA600C733F6 /* CoreLocation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CB533D9E14DABDA600C733F6 /* CoreLocation.framework */; }; - CB533DA114DABDA600C733F6 /* MediaPlayer.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CB533DA014DABDA600C733F6 /* MediaPlayer.framework */; }; - CB533DA314DABDA600C733F6 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CB533DA214DABDA600C733F6 /* QuartzCore.framework */; }; - CB533DA514DABDA600C733F6 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CB533DA414DABDA600C733F6 /* SystemConfiguration.framework */; }; - CB533DA714DABDA600C733F6 /* MobileCoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CB533DA614DABDA600C733F6 /* MobileCoreServices.framework */; }; - CB533DA914DABDA600C733F6 /* CoreMedia.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CB533DA814DABDA600C733F6 /* CoreMedia.framework */; }; - CB533DAF14DABDA600C733F6 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = CB533DAD14DABDA600C733F6 /* InfoPlist.strings */; }; - CB533DB114DABDA600C733F6 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = CB533DB014DABDA600C733F6 /* main.m */; }; - CB533DB914DABDA600C733F6 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = CB533DB714DABDA600C733F6 /* Localizable.strings */; }; - CB533DC014DABDA600C733F6 /* icon.png in Resources */ = {isa = PBXBuildFile; fileRef = CB533DBF14DABDA600C733F6 /* icon.png */; }; - CB533DC214DABDA600C733F6 /* icon@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = CB533DC114DABDA600C733F6 /* icon@2x.png */; }; - CB533DC414DABDA600C733F6 /* icon-72.png in Resources */ = {isa = PBXBuildFile; fileRef = CB533DC314DABDA600C733F6 /* icon-72.png */; }; - CB533DC714DABDA600C733F6 /* Default.png in Resources */ = {isa = PBXBuildFile; fileRef = CB533DC614DABDA600C733F6 /* Default.png */; }; - CB533DC914DABDA600C733F6 /* Default@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = CB533DC814DABDA600C733F6 /* Default@2x.png */; }; - CB533DCD14DABDA600C733F6 /* Cordova.plist in Resources */ = {isa = PBXBuildFile; fileRef = CB533DCC14DABDA600C733F6 /* Cordova.plist */; }; - CB533DD114DABDA600C733F6 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = CB533DD014DABDA600C733F6 /* AppDelegate.m */; }; - CB533DD414DABDA600C733F6 /* MainViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = CB533DD314DABDA600C733F6 /* MainViewController.m */; }; - CB533DD614DABDA600C733F6 /* MainViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = CB533DD514DABDA600C733F6 /* MainViewController.xib */; }; - CB588C8B1588F70900D9CC12 /* welcome.odt in Resources */ = {isa = PBXBuildFile; fileRef = CB588C8A1588F70900D9CC12 /* welcome.odt */; }; - CBD2B77314FF8E9700FC3A44 /* NativeZip.m in Sources */ = {isa = PBXBuildFile; fileRef = CBD2B77214FF8E9700FC3A44 /* NativeZip.m */; }; - CBDCA69D1504EAEB00C706C7 /* WebViewCache.m in Sources */ = {isa = PBXBuildFile; fileRef = CBDCA69C1504EAEB00C706C7 /* WebViewCache.m */; }; - CBE61BDB1588AC0900970DD8 /* Cordova.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CBE61BDA1588AC0900970DD8 /* Cordova.framework */; }; -/* End PBXBuildFile section */ - -/* Begin PBXFileReference section */ - CB099EC614DAC535000D7B99 /* Default-Portrait~ipad.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "Default-Portrait~ipad.png"; path = "../Default-Portrait~ipad.png"; sourceTree = ""; }; - CB099EC814DAC53D000D7B99 /* Default-Landscape~ipad.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "Default-Landscape~ipad.png"; path = "../Default-Landscape~ipad.png"; sourceTree = ""; }; - CB29ECBC14FFBBBB00CEAEE3 /* unzip.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = unzip.c; path = WebODF/Classes/minizip/unzip.c; sourceTree = ""; }; - CB29ECBE14FFBC0500CEAEE3 /* ioapi.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = ioapi.c; path = WebODF/Classes/minizip/ioapi.c; sourceTree = ""; }; - CB29ECC014FFBC1B00CEAEE3 /* mztools.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = mztools.c; path = WebODF/Classes/minizip/mztools.c; sourceTree = ""; }; - CB29ECC214FFBC5500CEAEE3 /* libz.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libz.dylib; path = usr/lib/libz.dylib; sourceTree = SDKROOT; }; - CB36D11714F68F7F0084BECB /* www */ = {isa = PBXFileReference; lastKnownFileType = folder; path = www; sourceTree = ""; }; - CB533D8C14DABDA500C733F6 /* KO Viewer.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "KO Viewer.app"; sourceTree = BUILT_PRODUCTS_DIR; }; - CB533D9014DABDA600C733F6 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; - CB533D9214DABDA600C733F6 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; - CB533D9414DABDA600C733F6 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; - CB533D9614DABDA600C733F6 /* AddressBook.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AddressBook.framework; path = System/Library/Frameworks/AddressBook.framework; sourceTree = SDKROOT; }; - CB533D9814DABDA600C733F6 /* AddressBookUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AddressBookUI.framework; path = System/Library/Frameworks/AddressBookUI.framework; sourceTree = SDKROOT; }; - CB533D9A14DABDA600C733F6 /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = System/Library/Frameworks/AudioToolbox.framework; sourceTree = SDKROOT; }; - CB533D9C14DABDA600C733F6 /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; }; - CB533D9E14DABDA600C733F6 /* CoreLocation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreLocation.framework; path = System/Library/Frameworks/CoreLocation.framework; sourceTree = SDKROOT; }; - CB533DA014DABDA600C733F6 /* MediaPlayer.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MediaPlayer.framework; path = System/Library/Frameworks/MediaPlayer.framework; sourceTree = SDKROOT; }; - CB533DA214DABDA600C733F6 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; - CB533DA414DABDA600C733F6 /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; }; - CB533DA614DABDA600C733F6 /* MobileCoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MobileCoreServices.framework; path = System/Library/Frameworks/MobileCoreServices.framework; sourceTree = SDKROOT; }; - CB533DA814DABDA600C733F6 /* CoreMedia.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreMedia.framework; path = System/Library/Frameworks/CoreMedia.framework; sourceTree = SDKROOT; }; - CB533DAC14DABDA600C733F6 /* WebODF-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "WebODF-Info.plist"; sourceTree = ""; }; - CB533DAE14DABDA600C733F6 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; - CB533DB014DABDA600C733F6 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; - CB533DB214DABDA600C733F6 /* WebODF-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "WebODF-Prefix.pch"; sourceTree = ""; }; - CB533DB814DABDA600C733F6 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = Resources/en.lproj/Localizable.strings; sourceTree = ""; }; - CB533DBF14DABDA600C733F6 /* icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = icon.png; path = Resources/icons/icon.png; sourceTree = ""; }; - CB533DC114DABDA600C733F6 /* icon@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "icon@2x.png"; path = "Resources/icons/icon@2x.png"; sourceTree = ""; }; - CB533DC314DABDA600C733F6 /* icon-72.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "icon-72.png"; path = "Resources/icons/icon-72.png"; sourceTree = ""; }; - CB533DC614DABDA600C733F6 /* Default.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = Default.png; path = Resources/splash/Default.png; sourceTree = ""; }; - CB533DC814DABDA600C733F6 /* Default@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "Default@2x.png"; path = "Resources/splash/Default@2x.png"; sourceTree = ""; }; - CB533DCC14DABDA600C733F6 /* Cordova.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Cordova.plist; sourceTree = ""; }; - CB533DCF14DABDA600C733F6 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = Classes/AppDelegate.h; sourceTree = ""; }; - CB533DD014DABDA600C733F6 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = AppDelegate.m; path = Classes/AppDelegate.m; sourceTree = ""; }; - CB533DD214DABDA600C733F6 /* MainViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = MainViewController.h; path = Classes/MainViewController.h; sourceTree = ""; }; - CB533DD314DABDA600C733F6 /* MainViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = MainViewController.m; path = Classes/MainViewController.m; sourceTree = ""; }; - CB533DD514DABDA600C733F6 /* MainViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = MainViewController.xib; path = Classes/MainViewController.xib; sourceTree = ""; }; - CB588C8A1588F70900D9CC12 /* welcome.odt */ = {isa = PBXFileReference; lastKnownFileType = file; path = welcome.odt; sourceTree = ""; }; - CBD2B77114FF8E9700FC3A44 /* NativeZip.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = NativeZip.h; path = Classes/NativeZip.h; sourceTree = ""; }; - CBD2B77214FF8E9700FC3A44 /* NativeZip.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = NativeZip.m; path = Classes/NativeZip.m; sourceTree = ""; }; - CBDCA69B1504EAEB00C706C7 /* WebViewCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = WebViewCache.h; path = Classes/WebViewCache.h; sourceTree = ""; }; - CBDCA69C1504EAEB00C706C7 /* WebViewCache.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = WebViewCache.m; path = Classes/WebViewCache.m; sourceTree = ""; }; - CBE61BDA1588AC0900970DD8 /* Cordova.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cordova.framework; path = ../../../../../Shared/Cordova/Frameworks/Cordova.framework; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - CB533D8614DABDA500C733F6 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - CBE61BDB1588AC0900970DD8 /* Cordova.framework in Frameworks */, - CB29ECC314FFBC5500CEAEE3 /* libz.dylib in Frameworks */, - CB533D9114DABDA600C733F6 /* Foundation.framework in Frameworks */, - CB533D9314DABDA600C733F6 /* UIKit.framework in Frameworks */, - CB533D9514DABDA600C733F6 /* CoreGraphics.framework in Frameworks */, - CB533D9714DABDA600C733F6 /* AddressBook.framework in Frameworks */, - CB533D9914DABDA600C733F6 /* AddressBookUI.framework in Frameworks */, - CB533D9B14DABDA600C733F6 /* AudioToolbox.framework in Frameworks */, - CB533D9D14DABDA600C733F6 /* AVFoundation.framework in Frameworks */, - CB533D9F14DABDA600C733F6 /* CoreLocation.framework in Frameworks */, - CB533DA114DABDA600C733F6 /* MediaPlayer.framework in Frameworks */, - CB533DA314DABDA600C733F6 /* QuartzCore.framework in Frameworks */, - CB533DA514DABDA600C733F6 /* SystemConfiguration.framework in Frameworks */, - CB533DA714DABDA600C733F6 /* MobileCoreServices.framework in Frameworks */, - CB533DA914DABDA600C733F6 /* CoreMedia.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - CB533D8914DABDA500C733F6 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - CB533D7E14DABDA500C733F6 = { - isa = PBXGroup; - children = ( - CB588C8A1588F70900D9CC12 /* welcome.odt */, - CBE61BDA1588AC0900970DD8 /* Cordova.framework */, - CB29ECC214FFBC5500CEAEE3 /* libz.dylib */, - CB29ECC014FFBC1B00CEAEE3 /* mztools.c */, - CB29ECBE14FFBC0500CEAEE3 /* ioapi.c */, - CB29ECBC14FFBBBB00CEAEE3 /* unzip.c */, - CB36D11714F68F7F0084BECB /* www */, - CB533DAA14DABDA600C733F6 /* WebODF */, - CB533D8F14DABDA500C733F6 /* Frameworks */, - CB533D8D14DABDA500C733F6 /* Products */, - ); - sourceTree = ""; - }; - CB533D8D14DABDA500C733F6 /* Products */ = { - isa = PBXGroup; - children = ( - CB533D8C14DABDA500C733F6 /* KO Viewer.app */, - ); - name = Products; - sourceTree = ""; - }; - CB533D8F14DABDA500C733F6 /* Frameworks */ = { - isa = PBXGroup; - children = ( - CB533D9014DABDA600C733F6 /* Foundation.framework */, - CB533D9214DABDA600C733F6 /* UIKit.framework */, - CB533D9414DABDA600C733F6 /* CoreGraphics.framework */, - CB533D9614DABDA600C733F6 /* AddressBook.framework */, - CB533D9814DABDA600C733F6 /* AddressBookUI.framework */, - CB533D9A14DABDA600C733F6 /* AudioToolbox.framework */, - CB533D9C14DABDA600C733F6 /* AVFoundation.framework */, - CB533D9E14DABDA600C733F6 /* CoreLocation.framework */, - CB533DA014DABDA600C733F6 /* MediaPlayer.framework */, - CB533DA214DABDA600C733F6 /* QuartzCore.framework */, - CB533DA414DABDA600C733F6 /* SystemConfiguration.framework */, - CB533DA614DABDA600C733F6 /* MobileCoreServices.framework */, - CB533DA814DABDA600C733F6 /* CoreMedia.framework */, - ); - name = Frameworks; - sourceTree = ""; - }; - CB533DAA14DABDA600C733F6 /* WebODF */ = { - isa = PBXGroup; - children = ( - CB533DB514DABDA600C733F6 /* Resources */, - CB533DCE14DABDA600C733F6 /* Classes */, - CB533DAB14DABDA600C733F6 /* Supporting Files */, - ); - path = WebODF; - sourceTree = ""; - }; - CB533DAB14DABDA600C733F6 /* Supporting Files */ = { - isa = PBXGroup; - children = ( - CB533DAC14DABDA600C733F6 /* WebODF-Info.plist */, - CB533DAD14DABDA600C733F6 /* InfoPlist.strings */, - CB533DB014DABDA600C733F6 /* main.m */, - CB533DB214DABDA600C733F6 /* WebODF-Prefix.pch */, - CB533DCC14DABDA600C733F6 /* Cordova.plist */, - CB533DD514DABDA600C733F6 /* MainViewController.xib */, - ); - name = "Supporting Files"; - sourceTree = ""; - }; - CB533DB514DABDA600C733F6 /* Resources */ = { - isa = PBXGroup; - children = ( - CB533DB614DABDA600C733F6 /* en.lproj */, - CB533DBE14DABDA600C733F6 /* icons */, - CB533DC514DABDA600C733F6 /* splash */, - ); - name = Resources; - sourceTree = ""; - }; - CB533DB614DABDA600C733F6 /* en.lproj */ = { - isa = PBXGroup; - children = ( - CB533DB714DABDA600C733F6 /* Localizable.strings */, - ); - name = en.lproj; - sourceTree = ""; - }; - CB533DBE14DABDA600C733F6 /* icons */ = { - isa = PBXGroup; - children = ( - CB533DBF14DABDA600C733F6 /* icon.png */, - CB533DC114DABDA600C733F6 /* icon@2x.png */, - CB533DC314DABDA600C733F6 /* icon-72.png */, - ); - name = icons; - sourceTree = ""; - }; - CB533DC514DABDA600C733F6 /* splash */ = { - isa = PBXGroup; - children = ( - CB099EC814DAC53D000D7B99 /* Default-Landscape~ipad.png */, - CB099EC614DAC535000D7B99 /* Default-Portrait~ipad.png */, - CB533DC614DABDA600C733F6 /* Default.png */, - CB533DC814DABDA600C733F6 /* Default@2x.png */, - ); - name = splash; - sourceTree = ""; - }; - CB533DCE14DABDA600C733F6 /* Classes */ = { - isa = PBXGroup; - children = ( - CBDCA69B1504EAEB00C706C7 /* WebViewCache.h */, - CBDCA69C1504EAEB00C706C7 /* WebViewCache.m */, - CB533DCF14DABDA600C733F6 /* AppDelegate.h */, - CB533DD014DABDA600C733F6 /* AppDelegate.m */, - CB533DD214DABDA600C733F6 /* MainViewController.h */, - CB533DD314DABDA600C733F6 /* MainViewController.m */, - CBD2B77114FF8E9700FC3A44 /* NativeZip.h */, - CBD2B77214FF8E9700FC3A44 /* NativeZip.m */, - ); - name = Classes; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - CB533D8B14DABDA500C733F6 /* KO Viewer */ = { - isa = PBXNativeTarget; - buildConfigurationList = CB533DDB14DABDA600C733F6 /* Build configuration list for PBXNativeTarget "KO Viewer" */; - buildPhases = ( - CB533D8514DABDA500C733F6 /* Sources */, - CB533D8614DABDA500C733F6 /* Frameworks */, - CB533D8714DABDA500C733F6 /* Resources */, - CB533D8814DABDA500C733F6 /* Sources */, - CB533D8914DABDA500C733F6 /* Frameworks */, - CB533D8A14DABDA500C733F6 /* ShellScript */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = "KO Viewer"; - productName = WebODF; - productReference = CB533D8C14DABDA500C733F6 /* KO Viewer.app */; - productType = "com.apple.product-type.application"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - CB533D8014DABDA500C733F6 /* Project object */ = { - isa = PBXProject; - attributes = { - LastUpgradeCheck = 0430; - }; - buildConfigurationList = CB533D8314DABDA500C733F6 /* Build configuration list for PBXProject "WebODF" */; - compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; - hasScannedForEncodings = 0; - knownRegions = ( - en, - es, - ); - mainGroup = CB533D7E14DABDA500C733F6; - productRefGroup = CB533D8D14DABDA500C733F6 /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - CB533D8B14DABDA500C733F6 /* KO Viewer */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - CB533D8714DABDA500C733F6 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - CB588C8B1588F70900D9CC12 /* welcome.odt in Resources */, - CB533DAF14DABDA600C733F6 /* InfoPlist.strings in Resources */, - CB533DB914DABDA600C733F6 /* Localizable.strings in Resources */, - CB533DC014DABDA600C733F6 /* icon.png in Resources */, - CB533DC214DABDA600C733F6 /* icon@2x.png in Resources */, - CB533DC414DABDA600C733F6 /* icon-72.png in Resources */, - CB533DC714DABDA600C733F6 /* Default.png in Resources */, - CB533DC914DABDA600C733F6 /* Default@2x.png in Resources */, - CB533DCD14DABDA600C733F6 /* Cordova.plist in Resources */, - CB533DD614DABDA600C733F6 /* MainViewController.xib in Resources */, - CB099EC714DAC535000D7B99 /* Default-Portrait~ipad.png in Resources */, - CB099EC914DAC53D000D7B99 /* Default-Landscape~ipad.png in Resources */, - CB36D11814F68F7F0084BECB /* www in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXShellScriptBuildPhase section */ - CB533D8A14DABDA500C733F6 /* ShellScript */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/bash; - shellScript = "rsync -a ../touchui/app/ www/app/\ncp ../touchui/sencha-touch.* www/\ncp ../touchui/*.png www/\nif [ ! -e www/webodf.js ]; then\n # webodf.js should be built\n if [ ! -e build ]; then mkdir build; fi\n cd build\n cmake -G Xcode ../../.. && make && cp webodf/webodf.js ..\n if [ ! -e webodf.js ]; then\n echo \"put webodf.js in the ios/www directory\"\n exit 1;\n fi\nfi\n"; - }; -/* End PBXShellScriptBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - CB533D8514DABDA500C733F6 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - CB533DB114DABDA600C733F6 /* main.m in Sources */, - CB533DD114DABDA600C733F6 /* AppDelegate.m in Sources */, - CB533DD414DABDA600C733F6 /* MainViewController.m in Sources */, - CBD2B77314FF8E9700FC3A44 /* NativeZip.m in Sources */, - CB29ECBD14FFBBBB00CEAEE3 /* unzip.c in Sources */, - CB29ECBF14FFBC0500CEAEE3 /* ioapi.c in Sources */, - CB29ECC114FFBC1B00CEAEE3 /* mztools.c in Sources */, - CBDCA69D1504EAEB00C706C7 /* WebViewCache.m in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - CB533D8814DABDA500C733F6 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin PBXVariantGroup section */ - CB533DAD14DABDA600C733F6 /* InfoPlist.strings */ = { - isa = PBXVariantGroup; - children = ( - CB533DAE14DABDA600C733F6 /* en */, - ); - name = InfoPlist.strings; - sourceTree = ""; - }; - CB533DB714DABDA600C733F6 /* Localizable.strings */ = { - isa = PBXVariantGroup; - children = ( - CB533DB814DABDA600C733F6 /* en */, - ); - name = Localizable.strings; - sourceTree = ""; - }; -/* End PBXVariantGroup section */ - -/* Begin XCBuildConfiguration section */ - CB533DD914DABDA600C733F6 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - ARCHS = "$(ARCHS_STANDARD_32_BIT)"; - CLANG_ENABLE_OBJC_ARC = NO; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_DYNAMIC_NO_PIC = NO; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_SYMBOLS_PRIVATE_EXTERN = NO; - GCC_VERSION = com.apple.compilers.llvmgcc42; - GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 3.0; - SDKROOT = iphoneos; - }; - name = Debug; - }; - CB533DDA14DABDA600C733F6 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - ARCHS = "$(ARCHS_STANDARD_32_BIT)"; - CLANG_ENABLE_OBJC_ARC = NO; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_VERSION = com.apple.compilers.llvmgcc42; - GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 3.0; - SDKROOT = iphoneos; - VALIDATE_PRODUCT = YES; - }; - name = Release; - }; - CB533DDC14DABDA600C733F6 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = YES; - CODE_SIGN_IDENTITY = "iPhone Developer: Jos van den Oever (GJ9RDPR233)"; - COPY_PHASE_STRIP = NO; - FRAMEWORK_SEARCH_PATHS = /Users/Shared/Cordova/Frameworks; - GCC_DYNAMIC_NO_PIC = NO; - GCC_PRECOMPILE_PREFIX_HEADER = YES; - GCC_PREFIX_HEADER = "WebODF/WebODF-Prefix.pch"; - GCC_PREPROCESSOR_DEFINITIONS = "PHONEGAP_FRAMEWORK=YES"; - GCC_THUMB_SUPPORT = NO; - GCC_VERSION = com.apple.compilers.llvm.clang.1_0; - INFOPLIST_FILE = "WebODF/WebODF-Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 5.0; - OTHER_LDFLAGS = ( - "-weak_framework", - UIKit, - "-weak_framework", - AVFoundation, - "-weak_framework", - CoreMedia, - "-weak_library", - /usr/lib/libSystem.B.dylib, - ); - PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE = "8A253628-DC77-4EEA-8543-53315AA93987"; - TARGETED_DEVICE_FAMILY = "1,2"; - WRAPPER_EXTENSION = app; - }; - name = Debug; - }; - CB533DDD14DABDA600C733F6 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = YES; - ARCHS = ( - armv6, - "$(ARCHS_STANDARD_32_BIT)", - ); - CODE_SIGN_IDENTITY = "iPhone Developer: Jos van den Oever (GJ9RDPR233)"; - COPY_PHASE_STRIP = YES; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - FRAMEWORK_SEARCH_PATHS = /Users/Shared/Cordova/Frameworks; - GCC_PRECOMPILE_PREFIX_HEADER = YES; - GCC_PREFIX_HEADER = "WebODF/WebODF-Prefix.pch"; - GCC_PREPROCESSOR_DEFINITIONS = "PHONEGAP_FRAMEWORK=YES"; - GCC_THUMB_SUPPORT = NO; - GCC_VERSION = com.apple.compilers.llvm.clang.1_0; - INFOPLIST_FILE = "WebODF/WebODF-Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 5.0; - OTHER_LDFLAGS = ( - "-weak_framework", - UIKit, - "-weak_framework", - AVFoundation, - "-weak_framework", - CoreMedia, - "-weak_library", - /usr/lib/libSystem.B.dylib, - ); - PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE = "8A253628-DC77-4EEA-8543-53315AA93987"; - TARGETED_DEVICE_FAMILY = "1,2"; - VALIDATE_PRODUCT = YES; - WRAPPER_EXTENSION = app; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - CB533D8314DABDA500C733F6 /* Build configuration list for PBXProject "WebODF" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - CB533DD914DABDA600C733F6 /* Debug */, - CB533DDA14DABDA600C733F6 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - CB533DDB14DABDA600C733F6 /* Build configuration list for PBXNativeTarget "KO Viewer" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - CB533DDC14DABDA600C733F6 /* Debug */, - CB533DDD14DABDA600C733F6 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = CB533D8014DABDA500C733F6 /* Project object */; -} diff --git a/attic/programs/ios/WebODF/Classes/AppDelegate.h b/attic/programs/ios/WebODF/Classes/AppDelegate.h deleted file mode 100644 index 1057ae84f..000000000 --- a/attic/programs/ios/WebODF/Classes/AppDelegate.h +++ /dev/null @@ -1,52 +0,0 @@ -/* - Licensed to the Apache Software Foundation (ASF) under one - or more contributor license agreements. See the NOTICE file - distributed with this work for additional information - regarding copyright ownership. The ASF licenses this file - to you under the Apache License, Version 2.0 (the - "License"); you may not use this file except in compliance - with the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, - software distributed under the License is distributed on an - "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - KIND, either express or implied. See the License for the - specific language governing permissions and limitations - under the License. - */ - -// -// AppDelegate.h -// WebODF -// -// Created by KO GmbH on 6/13/12. -// Copyright __MyCompanyName__ 2012. All rights reserved. -// - -#import -#define CORDOVA_FRAMEWORK -#ifdef CORDOVA_FRAMEWORK - #import -#else - #import "CDVViewController.h" -#endif -#import "WebViewCache.h" - - -@interface AppDelegate : NSObject < UIApplicationDelegate > { - -} - -// invoke string is passed to your app on launch, this is only valid if you -// edit WebODF-Info.plist to add a protocol -// a simple tutorial can be found here : -// http://iphonedevelopertips.com/cocoa/launching-your-own-application-via-a-custom-url-scheme.html - -@property (nonatomic, retain) IBOutlet UIWindow* window; -@property (nonatomic, retain) IBOutlet CDVViewController* viewController; -@property (nonatomic, retain) IBOutlet WebViewCache* cache; - -@end - diff --git a/attic/programs/ios/WebODF/Classes/AppDelegate.m b/attic/programs/ios/WebODF/Classes/AppDelegate.m deleted file mode 100644 index 7d6358d86..000000000 --- a/attic/programs/ios/WebODF/Classes/AppDelegate.m +++ /dev/null @@ -1,163 +0,0 @@ -/* - Licensed to the Apache Software Foundation (ASF) under one - or more contributor license agreements. See the NOTICE file - distributed with this work for additional information - regarding copyright ownership. The ASF licenses this file - to you under the Apache License, Version 2.0 (the - "License"); you may not use this file except in compliance - with the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, - software distributed under the License is distributed on an - "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - KIND, either express or implied. See the License for the - specific language governing permissions and limitations - under the License. - */ - -// -// AppDelegate.m -// WebODF -// -// Created by KO GmbH on 6/13/12. -// Copyright __MyCompanyName__ 2012. All rights reserved. -// - -#import "AppDelegate.h" -#import "MainViewController.h" - -#ifdef CORDOVA_FRAMEWORK - #import - #import -#else - #import "CDVPlugin.h" - #import "CDVURLProtocol.h" -#endif -#import "WebViewCache.h" - -@implementation AppDelegate - -@synthesize window, viewController, cache; - -- (id) init -{ - /** If you need to do any extra app-specific initialization, you can do it here - * -jm - **/ - NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage]; - [cookieStorage setCookieAcceptPolicy:NSHTTPCookieAcceptPolicyAlways]; - - [CDVURLProtocol registerURLProtocol]; - - return [super init]; -} - -#pragma UIApplicationDelegate implementation - -/** - * This is main kick off after the app inits, the views and Settings are setup here. (preferred - iOS4 and up) - */ -- (BOOL) application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions -{ - NSURL* url = [launchOptions objectForKey:UIApplicationLaunchOptionsURLKey]; - NSString* invokeString = nil; - - if (url && [url isKindOfClass:[NSURL class]]) { - invokeString = [url absoluteString]; - NSLog(@"WebODF launchOptions = %@", url); - } - - CGRect screenBounds = [[UIScreen mainScreen] bounds]; - self.window = [[[UIWindow alloc] initWithFrame:screenBounds] autorelease]; - self.window.autoresizesSubviews = YES; - - CGRect viewBounds = [[UIScreen mainScreen] applicationFrame]; - - self.viewController = [[[MainViewController alloc] init] autorelease]; - self.viewController.useSplashScreen = YES; - self.viewController.wwwFolderName = @"www"; - self.viewController.startPage = @"index.html"; - self.viewController.invokeString = invokeString; - self.viewController.view.frame = viewBounds; - - // check whether the current orientation is supported: if it is, keep it, rather than forcing a rotation - BOOL forceStartupRotation = YES; - UIDeviceOrientation curDevOrientation = [[UIDevice currentDevice] orientation]; - - if (UIDeviceOrientationUnknown == curDevOrientation) { - // UIDevice isn't firing orientation notifications yet… go look at the status bar - curDevOrientation = (UIDeviceOrientation)[[UIApplication sharedApplication] statusBarOrientation]; - } - - if (UIDeviceOrientationIsValidInterfaceOrientation(curDevOrientation)) { - for (NSNumber *orient in self.viewController.supportedOrientations) { - if ([orient intValue] == curDevOrientation) { - forceStartupRotation = NO; - break; - } - } - } - - if (forceStartupRotation) { - NSLog(@"supportedOrientations: %@", self.viewController.supportedOrientations); - // The first item in the supportedOrientations array is the start orientation (guaranteed to be at least Portrait) - UIInterfaceOrientation newOrient = [[self.viewController.supportedOrientations objectAtIndex:0] intValue]; - NSLog(@"AppDelegate forcing status bar to: %d from: %d", newOrient, curDevOrientation); - [[UIApplication sharedApplication] setStatusBarOrientation:newOrient]; - } - - [self.window addSubview:self.viewController.view]; - [self.window makeKeyAndVisible]; - - NSString *path = @"./cache"; - NSUInteger diskCapacity = 1*1024*1024; - NSUInteger memoryCapacity = 0*1024*1024; - - cache = [[WebViewCache alloc] initWithMemoryCapacity: memoryCapacity - diskCapacity: diskCapacity diskPath: path]; - [NSURLCache setSharedURLCache:cache]; - - // copy welcome.odt to the Documents folder - - - NSFileManager *fileManager = [NSFileManager defaultManager]; - NSError *error; - NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); - NSString *documentsDirectory = [paths objectAtIndex:0]; - - NSString *txtPath = [documentsDirectory stringByAppendingPathComponent:@"welcome.odt"]; - - if ([fileManager fileExistsAtPath:txtPath] == NO) { - NSString *resourcePath = [[NSBundle mainBundle] pathForResource:@"welcome" ofType:@"odt"]; - [fileManager copyItemAtPath:resourcePath toPath:txtPath error:&error]; - } - - return YES; -} - -// this happens while we are running ( in the background, or from within our own app ) -// only valid if WebODF-Info.plist specifies a protocol to handle -- (BOOL) application:(UIApplication*)application handleOpenURL:(NSURL*)url -{ - if (!url) { - return NO; - } - - // calls into javascript global function 'handleOpenURL' - NSString* jsString = [NSString stringWithFormat:@"handleOpenURL(\"%@\");", url]; - [self.viewController.webView stringByEvaluatingJavaScriptFromString:jsString]; - - // all plugins will get the notification, and their handlers will be called - [[NSNotificationCenter defaultCenter] postNotification:[NSNotification notificationWithName:CDVPluginHandleOpenURLNotification object:url]]; - - return YES; -} - -- (void) dealloc -{ - [super dealloc]; -} - -@end diff --git a/attic/programs/ios/WebODF/Classes/MainViewController.h b/attic/programs/ios/WebODF/Classes/MainViewController.h deleted file mode 100644 index 213b69572..000000000 --- a/attic/programs/ios/WebODF/Classes/MainViewController.h +++ /dev/null @@ -1,36 +0,0 @@ -/* - Licensed to the Apache Software Foundation (ASF) under one - or more contributor license agreements. See the NOTICE file - distributed with this work for additional information - regarding copyright ownership. The ASF licenses this file - to you under the Apache License, Version 2.0 (the - "License"); you may not use this file except in compliance - with the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, - software distributed under the License is distributed on an - "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - KIND, either express or implied. See the License for the - specific language governing permissions and limitations - under the License. - */ - -// -// MainViewController.h -// WebODF -// -// Created by KO GmbH on 6/13/12. -// Copyright __MyCompanyName__ 2012. All rights reserved. -// -#define CORDOVA_FRAMEWORK -#ifdef CORDOVA_FRAMEWORK - #import -#else - #import "CDVViewController.h" -#endif - -@interface MainViewController : CDVViewController - -@end diff --git a/attic/programs/ios/WebODF/Classes/MainViewController.m b/attic/programs/ios/WebODF/Classes/MainViewController.m deleted file mode 100644 index 0cefa4eed..000000000 --- a/attic/programs/ios/WebODF/Classes/MainViewController.m +++ /dev/null @@ -1,140 +0,0 @@ -/* - Licensed to the Apache Software Foundation (ASF) under one - or more contributor license agreements. See the NOTICE file - distributed with this work for additional information - regarding copyright ownership. The ASF licenses this file - to you under the Apache License, Version 2.0 (the - "License"); you may not use this file except in compliance - with the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, - software distributed under the License is distributed on an - "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - KIND, either express or implied. See the License for the - specific language governing permissions and limitations - under the License. - */ - -// -// MainViewController.h -// WebODF -// -// Created by KO GmbH on 6/13/12. -// Copyright __MyCompanyName__ 2012. All rights reserved. -// - -#import "MainViewController.h" - -@implementation MainViewController - -- (id) initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil -{ - self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; - if (self) { - // Custom initialization - } - return self; -} - -- (void) didReceiveMemoryWarning -{ - // Releases the view if it doesn't have a superview. - [super didReceiveMemoryWarning]; - - // Release any cached data, images, etc that aren't in use. -} - -#pragma mark - View lifecycle - -- (void) viewDidLoad -{ - [super viewDidLoad]; - // Do any additional setup after loading the view from its nib. -} - -- (void) viewDidUnload -{ - [super viewDidUnload]; - // Release any retained subviews of the main view. - // e.g. self.myOutlet = nil; -} - -- (BOOL) shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation -{ - // Return YES for supported orientations - return [super shouldAutorotateToInterfaceOrientation:interfaceOrientation]; -} - -/* Comment out the block below to over-ride */ -/* -- (CDVCordovaView*) newCordovaViewWithFrame:(CGRect)bounds -{ - return[super newCordovaViewWithFrame:bounds]; -} -*/ - -/* Comment out the block below to over-ride */ -/* -#pragma CDVCommandDelegate implementation - -- (id) getCommandInstance:(NSString*)className -{ - return [super getCommandInstance:className]; -} - -- (BOOL) execute:(CDVInvokedUrlCommand*)command -{ - return [super execute:command]; -} - -- (NSString*) pathForResource:(NSString*)resourcepath; -{ - return [super pathForResource:resourcepath]; -} - -- (void) registerPlugin:(CDVPlugin*)plugin withClassName:(NSString*)className -{ - return [super registerPlugin:plugin withClassName:className]; -} -*/ - -#pragma UIWebDelegate implementation - -- (void) webViewDidFinishLoad:(UIWebView*) theWebView -{ - // only valid if ___PROJECTNAME__-Info.plist specifies a protocol to handle - if (self.invokeString) - { - // this is passed before the deviceready event is fired, so you can access it in js when you receive deviceready - NSString* jsString = [NSString stringWithFormat:@"var invokeString = \"%@\";", self.invokeString]; - [theWebView stringByEvaluatingJavaScriptFromString:jsString]; - } - - // Black base color for background matches the native apps - theWebView.backgroundColor = [UIColor blackColor]; - - return [super webViewDidFinishLoad:theWebView]; -} - -/* Comment out the block below to over-ride */ -/* - -- (void) webViewDidStartLoad:(UIWebView*)theWebView -{ - return [super webViewDidStartLoad:theWebView]; -} - -- (void) webView:(UIWebView*)theWebView didFailLoadWithError:(NSError*)error -{ - return [super webView:theWebView didFailLoadWithError:error]; -} - -- (BOOL) webView:(UIWebView*)theWebView shouldStartLoadWithRequest:(NSURLRequest*)request navigationType:(UIWebViewNavigationType)navigationType -{ - return [super webView:theWebView shouldStartLoadWithRequest:request navigationType:navigationType]; -} -*/ - -@end diff --git a/attic/programs/ios/WebODF/Classes/MainViewController.xib b/attic/programs/ios/WebODF/Classes/MainViewController.xib deleted file mode 100644 index 9837f578c..000000000 --- a/attic/programs/ios/WebODF/Classes/MainViewController.xib +++ /dev/null @@ -1,118 +0,0 @@ - - - - 1280 - 11C25 - 1919 - 1138.11 - 566.00 - - com.apple.InterfaceBuilder.IBCocoaTouchPlugin - 916 - - - IBProxyObject - IBUIView - - - com.apple.InterfaceBuilder.IBCocoaTouchPlugin - - - PluginDependencyRecalculationVersion - - - - - IBFilesOwner - IBCocoaTouchFramework - - - IBFirstResponder - IBCocoaTouchFramework - - - - 274 - {{0, 20}, {320, 460}} - - - - 3 - MQA - - 2 - - - - IBCocoaTouchFramework - - - - - - - view - - - - 3 - - - - - - 0 - - - - - - 1 - - - - - -1 - - - File's Owner - - - -2 - - - - - - - MainViewController - com.apple.InterfaceBuilder.IBCocoaTouchPlugin - UIResponder - com.apple.InterfaceBuilder.IBCocoaTouchPlugin - com.apple.InterfaceBuilder.IBCocoaTouchPlugin - - - - - - 3 - - - - - MainViewController - UIViewController - - IBProjectSource - ./Classes/MainViewController.h - - - - - 0 - IBCocoaTouchFramework - YES - 3 - 916 - - diff --git a/attic/programs/ios/WebODF/Classes/NSData+Base64.h b/attic/programs/ios/WebODF/Classes/NSData+Base64.h deleted file mode 100644 index eb1ff485a..000000000 --- a/attic/programs/ios/WebODF/Classes/NSData+Base64.h +++ /dev/null @@ -1,33 +0,0 @@ -// -// NSData+Base64.h -// base64 -// -// Created by Matt Gallagher on 2009/06/03. -// Copyright 2009 Matt Gallagher. All rights reserved. -// -// Permission is given to use this source code file, free of charge, in any -// project, commercial or otherwise, entirely at your risk, with the condition -// that any redistribution (in part or whole) of source code must retain -// this copyright and permission notice. Attribution in compiled projects is -// appreciated but not required. -// - -#import - -void *NewBase64Decode( - const char *inputBuffer, - size_t length, - size_t *outputLength); - -char *NewBase64Encode( - const void *inputBuffer, - size_t length, - bool separateLines, - size_t *outputLength); - -@interface NSData (Base64) - -+ (NSData *)dataFromBase64String:(NSString *)aString; -- (NSString *)base64EncodedString; - -@end diff --git a/attic/programs/ios/WebODF/Classes/NSData+Base64.m b/attic/programs/ios/WebODF/Classes/NSData+Base64.m deleted file mode 100644 index 13f828d09..000000000 --- a/attic/programs/ios/WebODF/Classes/NSData+Base64.m +++ /dev/null @@ -1,299 +0,0 @@ -// -// NSData+Base64.m -// base64 -// -// Created by Matt Gallagher on 2009/06/03. -// Copyright 2009 Matt Gallagher. All rights reserved. -// -// Permission is given to use this source code file, free of charge, in any -// project, commercial or otherwise, entirely at your risk, with the condition -// that any redistribution (in part or whole) of source code must retain -// this copyright and permission notice. Attribution in compiled projects is -// appreciated but not required. -// - -#import "NSData+Base64.h" - -// -// Mapping from 6 bit pattern to ASCII character. -// -static unsigned char base64EncodeLookup[65] = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; - -// -// Definition for "masked-out" areas of the base64DecodeLookup mapping -// -#define xx 65 - -// -// Mapping from ASCII character to 6 bit pattern. -// -static unsigned char base64DecodeLookup[256] = -{ - xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, - xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, - xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, 62, xx, xx, xx, 63, - 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, xx, xx, xx, xx, xx, xx, - xx, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, - 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, xx, xx, xx, xx, xx, - xx, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, - 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, xx, xx, xx, xx, xx, - xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, - xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, - xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, - xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, - xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, - xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, - xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, - xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, -}; - -// -// Fundamental sizes of the binary and base64 encode/decode units in bytes -// -#define BINARY_UNIT_SIZE 3 -#define BASE64_UNIT_SIZE 4 - -// -// NewBase64Decode -// -// Decodes the base64 ASCII string in the inputBuffer to a newly malloced -// output buffer. -// -// inputBuffer - the source ASCII string for the decode -// length - the length of the string or -1 (to specify strlen should be used) -// outputLength - if not-NULL, on output will contain the decoded length -// -// returns the decoded buffer. Must be free'd by caller. Length is given by -// outputLength. -// -void *NewBase64Decode( - const char *inputBuffer, - size_t length, - size_t *outputLength) -{ - if (length == -1) - { - length = strlen(inputBuffer); - } - - size_t outputBufferSize = (length / BASE64_UNIT_SIZE) * BINARY_UNIT_SIZE; - unsigned char *outputBuffer = (unsigned char *)malloc(outputBufferSize); - - size_t i = 0; - size_t j = 0; - while (i < length) - { - // - // Accumulate 4 valid characters (ignore everything else) - // - unsigned char accumulated[BASE64_UNIT_SIZE]; - bzero(accumulated, sizeof(unsigned char) * BASE64_UNIT_SIZE); - size_t accumulateIndex = 0; - while (i < length) - { - unsigned char decode = base64DecodeLookup[inputBuffer[i++]]; - if (decode != xx) - { - accumulated[accumulateIndex] = decode; - accumulateIndex++; - - if (accumulateIndex == BASE64_UNIT_SIZE) - { - break; - } - } - } - - // - // Store the 6 bits from each of the 4 characters as 3 bytes - // - outputBuffer[j] = (accumulated[0] << 2) | (accumulated[1] >> 4); - outputBuffer[j + 1] = (accumulated[1] << 4) | (accumulated[2] >> 2); - outputBuffer[j + 2] = (accumulated[2] << 6) | accumulated[3]; - j += accumulateIndex - 1; - } - - if (outputLength) - { - *outputLength = j; - } - return outputBuffer; -} - -// -// NewBase64Decode -// -// Encodes the arbitrary data in the inputBuffer as base64 into a newly malloced -// output buffer. -// -// inputBuffer - the source data for the encode -// length - the length of the input in bytes -// separateLines - if zero, no CR/LF characters will be added. Otherwise -// a CR/LF pair will be added every 64 encoded chars. -// outputLength - if not-NULL, on output will contain the encoded length -// (not including terminating 0 char) -// -// returns the encoded buffer. Must be free'd by caller. Length is given by -// outputLength. -// -char *NewBase64Encode( - const void *buffer, - size_t length, - bool separateLines, - size_t *outputLength) -{ - const unsigned char *inputBuffer = (const unsigned char *)buffer; - - #define MAX_NUM_PADDING_CHARS 2 - #define OUTPUT_LINE_LENGTH 64 - #define INPUT_LINE_LENGTH ((OUTPUT_LINE_LENGTH / BASE64_UNIT_SIZE) * BINARY_UNIT_SIZE) - #define CR_LF_SIZE 0 - - // - // Byte accurate calculation of final buffer size - // - size_t outputBufferSize = - ((length / BINARY_UNIT_SIZE) - + ((length % BINARY_UNIT_SIZE) ? 1 : 0)) - * BASE64_UNIT_SIZE; - if (separateLines) - { - outputBufferSize += - (outputBufferSize / OUTPUT_LINE_LENGTH) * CR_LF_SIZE; - } - - // - // Include space for a terminating zero - // - outputBufferSize += 1; - - // - // Allocate the output buffer - // - char *outputBuffer = (char *)malloc(outputBufferSize); - if (!outputBuffer) - { - return NULL; - } - - size_t i = 0; - size_t j = 0; - const size_t lineLength = separateLines ? INPUT_LINE_LENGTH : length; - size_t lineEnd = lineLength; - - while (true) - { - if (lineEnd > length) - { - lineEnd = length; - } - - for (; i + BINARY_UNIT_SIZE - 1 < lineEnd; i += BINARY_UNIT_SIZE) - { - // - // Inner loop: turn 48 bytes into 64 base64 characters - // - outputBuffer[j++] = base64EncodeLookup[(inputBuffer[i] & 0xFC) >> 2]; - outputBuffer[j++] = base64EncodeLookup[((inputBuffer[i] & 0x03) << 4) - | ((inputBuffer[i + 1] & 0xF0) >> 4)]; - outputBuffer[j++] = base64EncodeLookup[((inputBuffer[i + 1] & 0x0F) << 2) - | ((inputBuffer[i + 2] & 0xC0) >> 6)]; - outputBuffer[j++] = base64EncodeLookup[inputBuffer[i + 2] & 0x3F]; - } - - if (lineEnd == length) - { - break; - } - - // - // Add the newline - // - //outputBuffer[j++] = '\r'; - //outputBuffer[j++] = '\n'; - lineEnd += lineLength; - } - - if (i + 1 < length) - { - // - // Handle the single '=' case - // - outputBuffer[j++] = base64EncodeLookup[(inputBuffer[i] & 0xFC) >> 2]; - outputBuffer[j++] = base64EncodeLookup[((inputBuffer[i] & 0x03) << 4) - | ((inputBuffer[i + 1] & 0xF0) >> 4)]; - outputBuffer[j++] = base64EncodeLookup[(inputBuffer[i + 1] & 0x0F) << 2]; - outputBuffer[j++] = '='; - } - else if (i < length) - { - // - // Handle the double '=' case - // - outputBuffer[j++] = base64EncodeLookup[(inputBuffer[i] & 0xFC) >> 2]; - outputBuffer[j++] = base64EncodeLookup[(inputBuffer[i] & 0x03) << 4]; - outputBuffer[j++] = '='; - outputBuffer[j++] = '='; - } - outputBuffer[j] = 0; - - // - // Set the output length and return the buffer - // - if (outputLength) - { - *outputLength = j; - } - return outputBuffer; -} - -@implementation NSData (Base64) - -// -// dataFromBase64String: -// -// Creates an NSData object containing the base64 decoded representation of -// the base64 string 'aString' -// -// Parameters: -// aString - the base64 string to decode -// -// returns the autoreleased NSData representation of the base64 string -// -+ (NSData *)dataFromBase64String:(NSString *)aString -{ - NSData *data = [aString dataUsingEncoding:NSASCIIStringEncoding]; - size_t outputLength; - void *outputBuffer = NewBase64Decode([data bytes], [data length], &outputLength); - NSData *result = [NSData dataWithBytes:outputBuffer length:outputLength]; - free(outputBuffer); - return result; -} - -// -// base64EncodedString -// -// Creates an NSString object that contains the base 64 encoding of the -// receiver's data. Lines are broken at 64 characters long. -// -// returns an autoreleased NSString being the base 64 representation of the -// receiver. -// -- (NSString *)base64EncodedString -{ - size_t outputLength; - char *outputBuffer = - NewBase64Encode([self bytes], [self length], true, &outputLength); - - NSString *result = - [[[NSString alloc] - initWithBytes:outputBuffer - length:outputLength - encoding:NSASCIIStringEncoding] - autorelease]; - free(outputBuffer); - return result; -} - -@end diff --git a/attic/programs/ios/WebODF/Classes/NativeZip.h b/attic/programs/ios/WebODF/Classes/NativeZip.h deleted file mode 100644 index 8f7177916..000000000 --- a/attic/programs/ios/WebODF/Classes/NativeZip.h +++ /dev/null @@ -1,13 +0,0 @@ -#import - -@interface NativeZip : CDVPlugin { - NSString* callbackID; -} - -@property (nonatomic, copy) NSString* callbackID; - -- (void) load:(BOOL)base64 arguments:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options; -- (void) loadAsString:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options; -- (void) loadAsDataURL:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options; - -@end \ No newline at end of file diff --git a/attic/programs/ios/WebODF/Classes/NativeZip.m b/attic/programs/ios/WebODF/Classes/NativeZip.m deleted file mode 100644 index 7ed75475b..000000000 --- a/attic/programs/ios/WebODF/Classes/NativeZip.m +++ /dev/null @@ -1,87 +0,0 @@ -#import "NativeZip.h" -#import "minizip/unzip.h" -#import "NSData+Base64.h" - -@implementation NativeZip -@synthesize callbackID; - --(void) load:(BOOL)base64 arguments:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options -{ - self.callbackID = [arguments objectAtIndex:0]; - NSString *zipPath = [arguments objectAtIndex:1]; - NSString *entryPath = [arguments objectAtIndex:2]; - NSString *mimetype = nil; - if (base64 == TRUE) { - mimetype = [arguments objectAtIndex:3]; - } - - const char* path = [ zipPath cStringUsingEncoding:NSUTF8StringEncoding ]; - unzFile unzipFile = unzOpen(path); - NSString* jsString = nil; - BOOL error = TRUE; - if (!unzipFile) { - jsString = [[NSString alloc] initWithString: @"cannot open file"]; - [jsString autorelease]; - } else { - path = [ entryPath cStringUsingEncoding:NSUTF8StringEncoding ]; - int r = unzLocateFile(unzipFile, path, 2); - if (r != UNZ_OK) { - jsString = [[NSString alloc] initWithString: @"cannot find entry"]; - [jsString autorelease]; - } else { - unz_file_info info; - r = unzGetCurrentFileInfo(unzipFile, &info, 0, 0, 0, 0, 0, 0); - if (r != UNZ_OK) { - jsString = [[NSString alloc] initWithString: @"cannot determine size"]; - [jsString autorelease]; - } else { - r = unzOpenCurrentFile(unzipFile); - if (r != UNZ_OK) { - jsString = [[NSString alloc] initWithString: @"cannot open entry"]; - [jsString autorelease]; - } else { - char* contents = malloc(info.uncompressed_size); - r = unzReadCurrentFile(unzipFile, contents, info.uncompressed_size); - if (r != info.uncompressed_size) { - jsString = [[NSString alloc] initWithString: @"cannot uncompress file"]; - [jsString autorelease]; - } else { - if (base64) { - NSData* readData = [NSData dataWithBytes:(const void *)contents length:sizeof(unsigned char)*info.uncompressed_size]; - jsString = [NSString stringWithFormat:@"data:%@;base64,%@", mimetype, [readData base64EncodedString]]; - } else { - jsString = [[NSString alloc] initWithUTF8String: contents]; - [jsString autorelease]; - } - } - unzCloseCurrentFile(unzipFile); - free(contents); - error = FALSE; - } - } - } - unzClose(unzipFile); - } - - CDVPluginResult* pluginResult = [CDVPluginResult - resultWithStatus:CDVCommandStatus_OK - messageAsString: jsString - ]; - if (!error) { - [self writeJavascript: [pluginResult toSuccessCallbackString:self.callbackID]]; - } else { - [self writeJavascript: [pluginResult toErrorCallbackString:self.callbackID]]; - } - //free(jsString); [jsString release]; -} - --(void)loadAsString:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options -{ - [self load:FALSE arguments:arguments withDict:options]; -} --(void)loadAsDataURL:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options -{ - [self load:TRUE arguments:arguments withDict:options]; -} - -@end diff --git a/attic/programs/ios/WebODF/Classes/WebViewCache.h b/attic/programs/ios/WebODF/Classes/WebViewCache.h deleted file mode 100644 index 216a0bf2b..000000000 --- a/attic/programs/ios/WebODF/Classes/WebViewCache.h +++ /dev/null @@ -1,15 +0,0 @@ -// -// WebCache.h -// KO Viewer -// -// Created by Tobias Hintze on 3/5/12. -// Copyright (c) 2012 KO GmbH. All rights reserved. -// - -#import - -@interface WebViewCache : NSURLCache - -- (NSData*)getSomeData:(NSString*)zip entry:(NSString*)entry; - -@end diff --git a/attic/programs/ios/WebODF/Classes/WebViewCache.m b/attic/programs/ios/WebODF/Classes/WebViewCache.m deleted file mode 100644 index 777bfa47a..000000000 --- a/attic/programs/ios/WebODF/Classes/WebViewCache.m +++ /dev/null @@ -1,74 +0,0 @@ -// -// WebCache.m -// KO Viewer -// -// Created by Tobias Hintze on 3/5/12. -// Copyright (c) 2012 KO GmbH. All rights reserved. -// - -#import "WebViewCache.h" -#import "minizip/unzip.h" - -@implementation WebViewCache - -- (NSCachedURLResponse*)cachedResponseForRequest:(NSURLRequest*)request -{ - [super removeAllCachedResponses]; - NSURL *url = [request URL]; - if ([url query]) { - NSData *somedata = [self getSomeData:[url path] entry:[url query]]; - NSHTTPURLResponse *response = [[NSHTTPURLResponse alloc] initWithURL:url - MIMEType:@"text/xml" - expectedContentLength:[somedata length] - textEncodingName:nil]; - NSCachedURLResponse *cachedResponse = [[NSCachedURLResponse alloc] - initWithResponse:response data:somedata]; - [response autorelease]; - [cachedResponse autorelease]; - return cachedResponse; - } - return [super cachedResponseForRequest:request]; -} - -- (NSData*)getSomeData:(NSString*)zip entry:(NSString*)entry -{ - NSLog(@"get some data: %@ %@", zip, entry); - const char* path = [ zip cStringUsingEncoding:NSUTF8StringEncoding ]; - unzFile unzipFile = unzOpen(path); - NSData *data = nil; - if (!unzipFile) { - NSLog(@"cannot open file %@", zip); - } else { - path = [ entry cStringUsingEncoding:NSUTF8StringEncoding ]; - int r = unzLocateFile(unzipFile, path, 2); - if (r != UNZ_OK) { - NSLog(@"cannot find entry %@", entry); - } else { - unz_file_info info; - r = unzGetCurrentFileInfo(unzipFile, &info, 0, 0, 0, 0, 0, 0); - if (r != UNZ_OK) { - NSLog(@"cannot determine size of %@", entry); - } else { - r = unzOpenCurrentFile(unzipFile); - if (r != UNZ_OK) { - NSLog(@"cannot open entry %@", entry); - } else { - char* contents = malloc(info.uncompressed_size); - r = unzReadCurrentFile(unzipFile, contents, info.uncompressed_size); - if (r != info.uncompressed_size) { - NSLog(@"cannot uncompress file %@", entry); - } else { - data = [NSData dataWithBytes:(const void *)contents length:sizeof(unsigned char)*info.uncompressed_size]; - NSLog(@"read file entry %li %@", info.uncompressed_size, entry); - } - unzCloseCurrentFile(unzipFile); - free(contents); - } - } - } - unzClose(unzipFile); - } - return data; -} - -@end diff --git a/attic/programs/ios/WebODF/Classes/minizip/.gitattributes b/attic/programs/ios/WebODF/Classes/minizip/.gitattributes deleted file mode 100644 index b15706354..000000000 --- a/attic/programs/ios/WebODF/Classes/minizip/.gitattributes +++ /dev/null @@ -1,4 +0,0 @@ -# Unset behaviour for some text-like files, as this is just a copy of original files, -# with obviously DOS line endings - -* binary diff --git a/attic/programs/ios/WebODF/Classes/minizip/crypt.h b/attic/programs/ios/WebODF/Classes/minizip/crypt.h deleted file mode 100644 index 622f4bc2e..000000000 --- a/attic/programs/ios/WebODF/Classes/minizip/crypt.h +++ /dev/null @@ -1,132 +0,0 @@ -/* crypt.h -- base code for crypt/uncrypt ZIPfile - - - Version 1.01e, February 12th, 2005 - - Copyright (C) 1998-2005 Gilles Vollant - - This code is a modified version of crypting code in Infozip distribution - - The encryption/decryption parts of this source code (as opposed to the - non-echoing password parts) were originally written in Europe. The - whole source package can be freely distributed, including from the USA. - (Prior to January 2000, re-export from the US was a violation of US law.) - - This encryption code is a direct transcription of the algorithm from - Roger Schlafly, described by Phil Katz in the file appnote.txt. This - file (appnote.txt) is distributed with the PKZIP program (even in the - version without encryption capabilities). - - If you don't need crypting in your application, just define symbols - NOCRYPT and NOUNCRYPT. - - This code support the "Traditional PKWARE Encryption". - - The new AES encryption added on Zip format by Winzip (see the page - http://www.winzip.com/aes_info.htm ) and PKWare PKZip 5.x Strong - Encryption is not supported. -*/ - -#define CRC32(c, b) ((*(pcrc_32_tab+(((int)(c) ^ (b)) & 0xff))) ^ ((c) >> 8)) - -/*********************************************************************** - * Return the next byte in the pseudo-random sequence - */ -static int decrypt_byte(unsigned long* pkeys, const unsigned long* pcrc_32_tab) -{ - unsigned temp; /* POTENTIAL BUG: temp*(temp^1) may overflow in an - * unpredictable manner on 16-bit systems; not a problem - * with any known compiler so far, though */ - - temp = ((unsigned)(*(pkeys+2)) & 0xffff) | 2; - return (int)(((temp * (temp ^ 1)) >> 8) & 0xff); -} - -/*********************************************************************** - * Update the encryption keys with the next byte of plain text - */ -static int update_keys(unsigned long* pkeys,const unsigned long* pcrc_32_tab,int c) -{ - (*(pkeys+0)) = CRC32((*(pkeys+0)), c); - (*(pkeys+1)) += (*(pkeys+0)) & 0xff; - (*(pkeys+1)) = (*(pkeys+1)) * 134775813L + 1; - { - register int keyshift = (int)((*(pkeys+1)) >> 24); - (*(pkeys+2)) = CRC32((*(pkeys+2)), keyshift); - } - return c; -} - - -/*********************************************************************** - * Initialize the encryption keys and the random header according to - * the given password. - */ -static void init_keys(const char* passwd,unsigned long* pkeys,const unsigned long* pcrc_32_tab) -{ - *(pkeys+0) = 305419896L; - *(pkeys+1) = 591751049L; - *(pkeys+2) = 878082192L; - while (*passwd != '\0') { - update_keys(pkeys,pcrc_32_tab,(int)*passwd); - passwd++; - } -} - -#define zdecode(pkeys,pcrc_32_tab,c) \ - (update_keys(pkeys,pcrc_32_tab,c ^= decrypt_byte(pkeys,pcrc_32_tab))) - -#define zencode(pkeys,pcrc_32_tab,c,t) \ - (t=decrypt_byte(pkeys,pcrc_32_tab), update_keys(pkeys,pcrc_32_tab,c), t^(c)) - -#ifdef INCLUDECRYPTINGCODE_IFCRYPTALLOWED - -#define RAND_HEAD_LEN 12 - /* "last resort" source for second part of crypt seed pattern */ -# ifndef ZCR_SEED2 -# define ZCR_SEED2 3141592654UL /* use PI as default pattern */ -# endif - -static int crypthead(passwd, buf, bufSize, pkeys, pcrc_32_tab, crcForCrypting) - const char *passwd; /* password string */ - unsigned char *buf; /* where to write header */ - int bufSize; - unsigned long* pkeys; - const unsigned long* pcrc_32_tab; - unsigned long crcForCrypting; -{ - int n; /* index in random header */ - int t; /* temporary */ - int c; /* random byte */ - unsigned char header[RAND_HEAD_LEN-2]; /* random header */ - static unsigned calls = 0; /* ensure different random header each time */ - - if (bufSize> 7) & 0xff; - header[n] = (unsigned char)zencode(pkeys, pcrc_32_tab, c, t); - } - /* Encrypt random header (last two bytes is high word of crc) */ - init_keys(passwd, pkeys, pcrc_32_tab); - for (n = 0; n < RAND_HEAD_LEN-2; n++) - { - buf[n] = (unsigned char)zencode(pkeys, pcrc_32_tab, header[n], t); - } - buf[n++] = zencode(pkeys, pcrc_32_tab, (int)(crcForCrypting >> 16) & 0xff, t); - buf[n++] = zencode(pkeys, pcrc_32_tab, (int)(crcForCrypting >> 24) & 0xff, t); - return n; -} - -#endif diff --git a/attic/programs/ios/WebODF/Classes/minizip/ioapi.c b/attic/programs/ios/WebODF/Classes/minizip/ioapi.c deleted file mode 100644 index 7f20c182f..000000000 --- a/attic/programs/ios/WebODF/Classes/minizip/ioapi.c +++ /dev/null @@ -1,177 +0,0 @@ -/* ioapi.c -- IO base function header for compress/uncompress .zip - files using zlib + zip or unzip API - - Version 1.01e, February 12th, 2005 - - Copyright (C) 1998-2005 Gilles Vollant -*/ - -#include -#include -#include - -#include "zlib.h" -#include "ioapi.h" - - - -/* I've found an old Unix (a SunOS 4.1.3_U1) without all SEEK_* defined.... */ - -#ifndef SEEK_CUR -#define SEEK_CUR 1 -#endif - -#ifndef SEEK_END -#define SEEK_END 2 -#endif - -#ifndef SEEK_SET -#define SEEK_SET 0 -#endif - -voidpf ZCALLBACK fopen_file_func OF(( - voidpf opaque, - const char* filename, - int mode)); - -uLong ZCALLBACK fread_file_func OF(( - voidpf opaque, - voidpf stream, - void* buf, - uLong size)); - -uLong ZCALLBACK fwrite_file_func OF(( - voidpf opaque, - voidpf stream, - const void* buf, - uLong size)); - -long ZCALLBACK ftell_file_func OF(( - voidpf opaque, - voidpf stream)); - -long ZCALLBACK fseek_file_func OF(( - voidpf opaque, - voidpf stream, - uLong offset, - int origin)); - -int ZCALLBACK fclose_file_func OF(( - voidpf opaque, - voidpf stream)); - -int ZCALLBACK ferror_file_func OF(( - voidpf opaque, - voidpf stream)); - - -voidpf ZCALLBACK fopen_file_func (opaque, filename, mode) - voidpf opaque; - const char* filename; - int mode; -{ - FILE* file = NULL; - const char* mode_fopen = NULL; - if ((mode & ZLIB_FILEFUNC_MODE_READWRITEFILTER)==ZLIB_FILEFUNC_MODE_READ) - mode_fopen = "rb"; - else - if (mode & ZLIB_FILEFUNC_MODE_EXISTING) - mode_fopen = "r+b"; - else - if (mode & ZLIB_FILEFUNC_MODE_CREATE) - mode_fopen = "wb"; - - if ((filename!=NULL) && (mode_fopen != NULL)) - file = fopen(filename, mode_fopen); - return file; -} - - -uLong ZCALLBACK fread_file_func (opaque, stream, buf, size) - voidpf opaque; - voidpf stream; - void* buf; - uLong size; -{ - uLong ret; - ret = (uLong)fread(buf, 1, (size_t)size, (FILE *)stream); - return ret; -} - - -uLong ZCALLBACK fwrite_file_func (opaque, stream, buf, size) - voidpf opaque; - voidpf stream; - const void* buf; - uLong size; -{ - uLong ret; - ret = (uLong)fwrite(buf, 1, (size_t)size, (FILE *)stream); - return ret; -} - -long ZCALLBACK ftell_file_func (opaque, stream) - voidpf opaque; - voidpf stream; -{ - long ret; - ret = ftell((FILE *)stream); - return ret; -} - -long ZCALLBACK fseek_file_func (opaque, stream, offset, origin) - voidpf opaque; - voidpf stream; - uLong offset; - int origin; -{ - int fseek_origin=0; - long ret; - switch (origin) - { - case ZLIB_FILEFUNC_SEEK_CUR : - fseek_origin = SEEK_CUR; - break; - case ZLIB_FILEFUNC_SEEK_END : - fseek_origin = SEEK_END; - break; - case ZLIB_FILEFUNC_SEEK_SET : - fseek_origin = SEEK_SET; - break; - default: return -1; - } - ret = 0; - fseek((FILE *)stream, offset, fseek_origin); - return ret; -} - -int ZCALLBACK fclose_file_func (opaque, stream) - voidpf opaque; - voidpf stream; -{ - int ret; - ret = fclose((FILE *)stream); - return ret; -} - -int ZCALLBACK ferror_file_func (opaque, stream) - voidpf opaque; - voidpf stream; -{ - int ret; - ret = ferror((FILE *)stream); - return ret; -} - -void fill_fopen_filefunc (pzlib_filefunc_def) - zlib_filefunc_def* pzlib_filefunc_def; -{ - pzlib_filefunc_def->zopen_file = fopen_file_func; - pzlib_filefunc_def->zread_file = fread_file_func; - pzlib_filefunc_def->zwrite_file = fwrite_file_func; - pzlib_filefunc_def->ztell_file = ftell_file_func; - pzlib_filefunc_def->zseek_file = fseek_file_func; - pzlib_filefunc_def->zclose_file = fclose_file_func; - pzlib_filefunc_def->zerror_file = ferror_file_func; - pzlib_filefunc_def->opaque = NULL; -} diff --git a/attic/programs/ios/WebODF/Classes/minizip/ioapi.h b/attic/programs/ios/WebODF/Classes/minizip/ioapi.h deleted file mode 100644 index e73a3b2bd..000000000 --- a/attic/programs/ios/WebODF/Classes/minizip/ioapi.h +++ /dev/null @@ -1,75 +0,0 @@ -/* ioapi.h -- IO base function header for compress/uncompress .zip - files using zlib + zip or unzip API - - Version 1.01e, February 12th, 2005 - - Copyright (C) 1998-2005 Gilles Vollant -*/ - -#ifndef _ZLIBIOAPI_H -#define _ZLIBIOAPI_H - - -#define ZLIB_FILEFUNC_SEEK_CUR (1) -#define ZLIB_FILEFUNC_SEEK_END (2) -#define ZLIB_FILEFUNC_SEEK_SET (0) - -#define ZLIB_FILEFUNC_MODE_READ (1) -#define ZLIB_FILEFUNC_MODE_WRITE (2) -#define ZLIB_FILEFUNC_MODE_READWRITEFILTER (3) - -#define ZLIB_FILEFUNC_MODE_EXISTING (4) -#define ZLIB_FILEFUNC_MODE_CREATE (8) - - -#ifndef ZCALLBACK - -#if (defined(WIN32) || defined (WINDOWS) || defined (_WINDOWS)) && defined(CALLBACK) && defined (USEWINDOWS_CALLBACK) -#define ZCALLBACK CALLBACK -#else -#define ZCALLBACK -#endif -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -typedef voidpf (ZCALLBACK *open_file_func) OF((voidpf opaque, const char* filename, int mode)); -typedef uLong (ZCALLBACK *read_file_func) OF((voidpf opaque, voidpf stream, void* buf, uLong size)); -typedef uLong (ZCALLBACK *write_file_func) OF((voidpf opaque, voidpf stream, const void* buf, uLong size)); -typedef long (ZCALLBACK *tell_file_func) OF((voidpf opaque, voidpf stream)); -typedef long (ZCALLBACK *seek_file_func) OF((voidpf opaque, voidpf stream, uLong offset, int origin)); -typedef int (ZCALLBACK *close_file_func) OF((voidpf opaque, voidpf stream)); -typedef int (ZCALLBACK *testerror_file_func) OF((voidpf opaque, voidpf stream)); - -typedef struct zlib_filefunc_def_s -{ - open_file_func zopen_file; - read_file_func zread_file; - write_file_func zwrite_file; - tell_file_func ztell_file; - seek_file_func zseek_file; - close_file_func zclose_file; - testerror_file_func zerror_file; - voidpf opaque; -} zlib_filefunc_def; - - - -void fill_fopen_filefunc OF((zlib_filefunc_def* pzlib_filefunc_def)); - -#define ZREAD(filefunc,filestream,buf,size) ((*((filefunc).zread_file))((filefunc).opaque,filestream,buf,size)) -#define ZWRITE(filefunc,filestream,buf,size) ((*((filefunc).zwrite_file))((filefunc).opaque,filestream,buf,size)) -#define ZTELL(filefunc,filestream) ((*((filefunc).ztell_file))((filefunc).opaque,filestream)) -#define ZSEEK(filefunc,filestream,pos,mode) ((*((filefunc).zseek_file))((filefunc).opaque,filestream,pos,mode)) -#define ZCLOSE(filefunc,filestream) ((*((filefunc).zclose_file))((filefunc).opaque,filestream)) -#define ZERROR(filefunc,filestream) ((*((filefunc).zerror_file))((filefunc).opaque,filestream)) - - -#ifdef __cplusplus -} -#endif - -#endif - diff --git a/attic/programs/ios/WebODF/Classes/minizip/mztools.c b/attic/programs/ios/WebODF/Classes/minizip/mztools.c deleted file mode 100644 index 74e6c7577..000000000 --- a/attic/programs/ios/WebODF/Classes/minizip/mztools.c +++ /dev/null @@ -1,28 +0,0 @@ -/* - Additional tools for Minizip - Code: Xavier Roche '2004 - License: Same as ZLIB (www.gzip.org) -*/ - -/* Code */ -#include -#include -#include -#include "zlib.h" -#include "unzip.h" - -#define READ_8(adr) ((unsigned char)*(adr)) -#define READ_16(adr) ( READ_8(adr) | (READ_8(adr+1) << 8) ) -#define READ_32(adr) ( READ_16(adr) | (READ_16((adr)+2) << 16) ) - -#define WRITE_8(buff, n) do { \ - *((unsigned char*)(buff)) = (unsigned char) ((n) & 0xff); \ -} while(0) -#define WRITE_16(buff, n) do { \ - WRITE_8((unsigned char*)(buff), n); \ - WRITE_8(((unsigned char*)(buff)) + 1, (n) >> 8); \ -} while(0) -#define WRITE_32(buff, n) do { \ - WRITE_16((unsigned char*)(buff), (n) & 0xffff); \ - WRITE_16((unsigned char*)(buff) + 2, (n) >> 16); \ -} while(0) diff --git a/attic/programs/ios/WebODF/Classes/minizip/mztools.h b/attic/programs/ios/WebODF/Classes/minizip/mztools.h deleted file mode 100644 index 82d1597ad..000000000 --- a/attic/programs/ios/WebODF/Classes/minizip/mztools.h +++ /dev/null @@ -1,31 +0,0 @@ -/* - Additional tools for Minizip - Code: Xavier Roche '2004 - License: Same as ZLIB (www.gzip.org) -*/ - -#ifndef _zip_tools_H -#define _zip_tools_H - -#ifdef __cplusplus -extern "C" { -#endif - -#ifndef _ZLIB_H -#include "zlib.h" -#endif - -#include "unzip.h" - -/* Repair a ZIP file (missing central directory) - file: file to recover - fileOut: output file after recovery - fileOutTmp: temporary file name used for recovery -*/ -extern int ZEXPORT unzRepair(const char* file, - const char* fileOut, - const char* fileOutTmp, - uLong* nRecovered, - uLong* bytesRecovered); - -#endif diff --git a/attic/programs/ios/WebODF/Classes/minizip/unzip.c b/attic/programs/ios/WebODF/Classes/minizip/unzip.c deleted file mode 100644 index 81aee6a14..000000000 --- a/attic/programs/ios/WebODF/Classes/minizip/unzip.c +++ /dev/null @@ -1,1597 +0,0 @@ -/* unzip.c -- IO for uncompress .zip files using zlib - Version 1.01e, February 12th, 2005 - - Copyright (C) 1998-2005 Gilles Vollant - - Read unzip.h for more info -*/ - -/* Decryption code comes from crypt.c by Info-ZIP but has been greatly reduced in terms of -compatibility with older software. The following is from the original crypt.c. Code -woven in by Terry Thorsen 1/2003. -*/ -/* - Copyright (c) 1990-2000 Info-ZIP. All rights reserved. - - See the accompanying file LICENSE, version 2000-Apr-09 or later - (the contents of which are also included in zip.h) for terms of use. - If, for some reason, all these files are missing, the Info-ZIP license - also may be found at: ftp://ftp.info-zip.org/pub/infozip/license.html -*/ -/* - crypt.c (full version) by Info-ZIP. Last revised: [see crypt.h] - - The encryption/decryption parts of this source code (as opposed to the - non-echoing password parts) were originally written in Europe. The - whole source package can be freely distributed, including from the USA. - (Prior to January 2000, re-export from the US was a violation of US law.) - */ - -/* - This encryption code is a direct transcription of the algorithm from - Roger Schlafly, described by Phil Katz in the file appnote.txt. This - file (appnote.txt) is distributed with the PKZIP program (even in the - version without encryption capabilities). - */ - - -#include -#include -#include -#include "zlib.h" -#include "unzip.h" - -#ifdef STDC -# include -# include -# include -#endif -#ifdef NO_ERRNO_H - extern int errno; -#else -# include -#endif - - -#ifndef local -# define local static -#endif -/* compile with -Dlocal if your debugger can't find static symbols */ - - -#ifndef CASESENSITIVITYDEFAULT_NO -# if !defined(unix) && !defined(CASESENSITIVITYDEFAULT_YES) -# define CASESENSITIVITYDEFAULT_NO -# endif -#endif - - -#ifndef UNZ_BUFSIZE -#define UNZ_BUFSIZE (16384) -#endif - -#ifndef UNZ_MAXFILENAMEINZIP -#define UNZ_MAXFILENAMEINZIP (256) -#endif - -#ifndef ALLOC -# define ALLOC(size) (malloc(size)) -#endif -#ifndef TRYFREE -# define TRYFREE(p) {if (p) free(p);} -#endif - -#define SIZECENTRALDIRITEM (0x2e) -#define SIZEZIPLOCALHEADER (0x1e) - - - - -const char unz_copyright[] = - " unzip 1.01 Copyright 1998-2004 Gilles Vollant - http://www.winimage.com/zLibDll"; - -/* unz_file_info_interntal contain internal info about a file in zipfile*/ -typedef struct unz_file_info_internal_s -{ - uLong offset_curfile;/* relative offset of local header 4 bytes */ -} unz_file_info_internal; - - -/* file_in_zip_read_info_s contain internal information about a file in zipfile, - when reading and decompress it */ -typedef struct -{ - char *read_buffer; /* internal buffer for compressed data */ - z_stream stream; /* zLib stream structure for inflate */ - - uLong pos_in_zipfile; /* position in byte on the zipfile, for fseek*/ - uLong stream_initialised; /* flag set if stream structure is initialised*/ - - uLong offset_local_extrafield;/* offset of the local extra field */ - uInt size_local_extrafield;/* size of the local extra field */ - uLong pos_local_extrafield; /* position in the local extra field in read*/ - - uLong crc32; /* crc32 of all data uncompressed */ - uLong crc32_wait; /* crc32 we must obtain after decompress all */ - uLong rest_read_compressed; /* number of byte to be decompressed */ - uLong rest_read_uncompressed;/*number of byte to be obtained after decomp*/ - zlib_filefunc_def z_filefunc; - voidpf filestream; /* io structore of the zipfile */ - uLong compression_method; /* compression method (0==store) */ - uLong byte_before_the_zipfile;/* byte before the zipfile, (>0 for sfx)*/ - int raw; -} file_in_zip_read_info_s; - - -/* unz_s contain internal information about the zipfile -*/ -typedef struct -{ - zlib_filefunc_def z_filefunc; - voidpf filestream; /* io structore of the zipfile */ - unz_global_info gi; /* public global information */ - uLong byte_before_the_zipfile;/* byte before the zipfile, (>0 for sfx)*/ - uLong num_file; /* number of the current file in the zipfile*/ - uLong pos_in_central_dir; /* pos of the current file in the central dir*/ - uLong current_file_ok; /* flag about the usability of the current file*/ - uLong central_pos; /* position of the beginning of the central dir*/ - - uLong size_central_dir; /* size of the central directory */ - uLong offset_central_dir; /* offset of start of central directory with - respect to the starting disk number */ - - unz_file_info cur_file_info; /* public info about the current file in zip*/ - unz_file_info_internal cur_file_info_internal; /* private info about it*/ - file_in_zip_read_info_s* pfile_in_zip_read; /* structure about the current - file if we are decompressing it */ - int encrypted; -# ifndef NOUNCRYPT - unsigned long keys[3]; /* keys defining the pseudo-random sequence */ - const unsigned long* pcrc_32_tab; -# endif -} unz_s; - - -#ifndef NOUNCRYPT -#include "crypt.h" -#endif - -/* =========================================================================== - Read a byte from a gz_stream; update next_in and avail_in. Return EOF - for end of file. - IN assertion: the stream s has been sucessfully opened for reading. -*/ - - -local int unzlocal_getByte OF(( - const zlib_filefunc_def* pzlib_filefunc_def, - voidpf filestream, - int *pi)); - -local int unzlocal_getByte(pzlib_filefunc_def,filestream,pi) - const zlib_filefunc_def* pzlib_filefunc_def; - voidpf filestream; - int *pi; -{ - unsigned char c; - int err = (int)ZREAD(*pzlib_filefunc_def,filestream,&c,1); - if (err==1) - { - *pi = (int)c; - return UNZ_OK; - } - else - { - if (ZERROR(*pzlib_filefunc_def,filestream)) - return UNZ_ERRNO; - else - return UNZ_EOF; - } -} - - -/* =========================================================================== - Reads a long in LSB order from the given gz_stream. Sets -*/ -local int unzlocal_getShort OF(( - const zlib_filefunc_def* pzlib_filefunc_def, - voidpf filestream, - uLong *pX)); - -local int unzlocal_getShort (pzlib_filefunc_def,filestream,pX) - const zlib_filefunc_def* pzlib_filefunc_def; - voidpf filestream; - uLong *pX; -{ - uLong x ; - int i; - int err; - - err = unzlocal_getByte(pzlib_filefunc_def,filestream,&i); - x = (uLong)i; - - if (err==UNZ_OK) - err = unzlocal_getByte(pzlib_filefunc_def,filestream,&i); - x += ((uLong)i)<<8; - - if (err==UNZ_OK) - *pX = x; - else - *pX = 0; - return err; -} - -local int unzlocal_getLong OF(( - const zlib_filefunc_def* pzlib_filefunc_def, - voidpf filestream, - uLong *pX)); - -local int unzlocal_getLong (pzlib_filefunc_def,filestream,pX) - const zlib_filefunc_def* pzlib_filefunc_def; - voidpf filestream; - uLong *pX; -{ - uLong x ; - int i; - int err; - - err = unzlocal_getByte(pzlib_filefunc_def,filestream,&i); - x = (uLong)i; - - if (err==UNZ_OK) - err = unzlocal_getByte(pzlib_filefunc_def,filestream,&i); - x += ((uLong)i)<<8; - - if (err==UNZ_OK) - err = unzlocal_getByte(pzlib_filefunc_def,filestream,&i); - x += ((uLong)i)<<16; - - if (err==UNZ_OK) - err = unzlocal_getByte(pzlib_filefunc_def,filestream,&i); - x += ((uLong)i)<<24; - - if (err==UNZ_OK) - *pX = x; - else - *pX = 0; - return err; -} - - -/* My own strcmpi / strcasecmp */ -local int strcmpcasenosensitive_internal (fileName1,fileName2) - const char* fileName1; - const char* fileName2; -{ - for (;;) - { - char c1=*(fileName1++); - char c2=*(fileName2++); - if ((c1>='a') && (c1<='z')) - c1 -= 0x20; - if ((c2>='a') && (c2<='z')) - c2 -= 0x20; - if (c1=='\0') - return ((c2=='\0') ? 0 : -1); - if (c2=='\0') - return 1; - if (c1c2) - return 1; - } -} - - -#ifdef CASESENSITIVITYDEFAULT_NO -#define CASESENSITIVITYDEFAULTVALUE 2 -#else -#define CASESENSITIVITYDEFAULTVALUE 1 -#endif - -#ifndef STRCMPCASENOSENTIVEFUNCTION -#define STRCMPCASENOSENTIVEFUNCTION strcmpcasenosensitive_internal -#endif - -/* - Compare two filename (fileName1,fileName2). - If iCaseSenisivity = 1, comparision is case sensitivity (like strcmp) - If iCaseSenisivity = 2, comparision is not case sensitivity (like strcmpi - or strcasecmp) - If iCaseSenisivity = 0, case sensitivity is defaut of your operating system - (like 1 on Unix, 2 on Windows) - -*/ -extern int ZEXPORT unzStringFileNameCompare (fileName1,fileName2,iCaseSensitivity) - const char* fileName1; - const char* fileName2; - int iCaseSensitivity; -{ - if (iCaseSensitivity==0) - iCaseSensitivity=CASESENSITIVITYDEFAULTVALUE; - - if (iCaseSensitivity==1) - return strcmp(fileName1,fileName2); - - return STRCMPCASENOSENTIVEFUNCTION(fileName1,fileName2); -} - -#ifndef BUFREADCOMMENT -#define BUFREADCOMMENT (0x400) -#endif - -/* - Locate the Central directory of a zipfile (at the end, just before - the global comment) -*/ -local uLong unzlocal_SearchCentralDir OF(( - const zlib_filefunc_def* pzlib_filefunc_def, - voidpf filestream)); - -local uLong unzlocal_SearchCentralDir(pzlib_filefunc_def,filestream) - const zlib_filefunc_def* pzlib_filefunc_def; - voidpf filestream; -{ - unsigned char* buf; - uLong uSizeFile; - uLong uBackRead; - uLong uMaxBack=0xffff; /* maximum size of global comment */ - uLong uPosFound=0; - - if (ZSEEK(*pzlib_filefunc_def,filestream,0,ZLIB_FILEFUNC_SEEK_END) != 0) - return 0; - - - uSizeFile = ZTELL(*pzlib_filefunc_def,filestream); - - if (uMaxBack>uSizeFile) - uMaxBack = uSizeFile; - - buf = (unsigned char*)ALLOC(BUFREADCOMMENT+4); - if (buf==NULL) - return 0; - - uBackRead = 4; - while (uBackReaduMaxBack) - uBackRead = uMaxBack; - else - uBackRead+=BUFREADCOMMENT; - uReadPos = uSizeFile-uBackRead ; - - uReadSize = ((BUFREADCOMMENT+4) < (uSizeFile-uReadPos)) ? - (BUFREADCOMMENT+4) : (uSizeFile-uReadPos); - if (ZSEEK(*pzlib_filefunc_def,filestream,uReadPos,ZLIB_FILEFUNC_SEEK_SET)!=0) - break; - - if (ZREAD(*pzlib_filefunc_def,filestream,buf,uReadSize)!=uReadSize) - break; - - for (i=(int)uReadSize-3; (i--)>0;) - if (((*(buf+i))==0x50) && ((*(buf+i+1))==0x4b) && - ((*(buf+i+2))==0x05) && ((*(buf+i+3))==0x06)) - { - uPosFound = uReadPos+i; - break; - } - - if (uPosFound!=0) - break; - } - TRYFREE(buf); - return uPosFound; -} - -/* - Open a Zip file. path contain the full pathname (by example, - on a Windows NT computer "c:\\test\\zlib114.zip" or on an Unix computer - "zlib/zlib114.zip". - If the zipfile cannot be opened (file doesn't exist or in not valid), the - return value is NULL. - Else, the return value is a unzFile Handle, usable with other function - of this unzip package. -*/ -extern unzFile ZEXPORT unzOpen2 (path, pzlib_filefunc_def) - const char *path; - zlib_filefunc_def* pzlib_filefunc_def; -{ - unz_s us; - unz_s *s; - uLong central_pos,uL; - - uLong number_disk; /* number of the current dist, used for - spaning ZIP, unsupported, always 0*/ - uLong number_disk_with_CD; /* number the the disk with central dir, used - for spaning ZIP, unsupported, always 0*/ - uLong number_entry_CD; /* total number of entries in - the central dir - (same than number_entry on nospan) */ - - int err=UNZ_OK; - - if (unz_copyright[0]!=' ') - return NULL; - - if (pzlib_filefunc_def==NULL) - fill_fopen_filefunc(&us.z_filefunc); - else - us.z_filefunc = *pzlib_filefunc_def; - - us.filestream= (*(us.z_filefunc.zopen_file))(us.z_filefunc.opaque, - path, - ZLIB_FILEFUNC_MODE_READ | - ZLIB_FILEFUNC_MODE_EXISTING); - if (us.filestream==NULL) - return NULL; - - central_pos = unzlocal_SearchCentralDir(&us.z_filefunc,us.filestream); - if (central_pos==0) - err=UNZ_ERRNO; - - if (ZSEEK(us.z_filefunc, us.filestream, - central_pos,ZLIB_FILEFUNC_SEEK_SET)!=0) - err=UNZ_ERRNO; - - /* the signature, already checked */ - if (unzlocal_getLong(&us.z_filefunc, us.filestream,&uL)!=UNZ_OK) - err=UNZ_ERRNO; - - /* number of this disk */ - if (unzlocal_getShort(&us.z_filefunc, us.filestream,&number_disk)!=UNZ_OK) - err=UNZ_ERRNO; - - /* number of the disk with the start of the central directory */ - if (unzlocal_getShort(&us.z_filefunc, us.filestream,&number_disk_with_CD)!=UNZ_OK) - err=UNZ_ERRNO; - - /* total number of entries in the central dir on this disk */ - if (unzlocal_getShort(&us.z_filefunc, us.filestream,&us.gi.number_entry)!=UNZ_OK) - err=UNZ_ERRNO; - - /* total number of entries in the central dir */ - if (unzlocal_getShort(&us.z_filefunc, us.filestream,&number_entry_CD)!=UNZ_OK) - err=UNZ_ERRNO; - - if ((number_entry_CD!=us.gi.number_entry) || - (number_disk_with_CD!=0) || - (number_disk!=0)) - err=UNZ_BADZIPFILE; - - /* size of the central directory */ - if (unzlocal_getLong(&us.z_filefunc, us.filestream,&us.size_central_dir)!=UNZ_OK) - err=UNZ_ERRNO; - - /* offset of start of central directory with respect to the - starting disk number */ - if (unzlocal_getLong(&us.z_filefunc, us.filestream,&us.offset_central_dir)!=UNZ_OK) - err=UNZ_ERRNO; - - /* zipfile comment length */ - if (unzlocal_getShort(&us.z_filefunc, us.filestream,&us.gi.size_comment)!=UNZ_OK) - err=UNZ_ERRNO; - - if ((central_pospfile_in_zip_read!=NULL) - unzCloseCurrentFile(file); - - ZCLOSE(s->z_filefunc, s->filestream); - TRYFREE(s); - return UNZ_OK; -} - - -/* - Write info about the ZipFile in the *pglobal_info structure. - No preparation of the structure is needed - return UNZ_OK if there is no problem. */ -extern int ZEXPORT unzGetGlobalInfo (file,pglobal_info) - unzFile file; - unz_global_info *pglobal_info; -{ - unz_s* s; - if (file==NULL) - return UNZ_PARAMERROR; - s=(unz_s*)file; - *pglobal_info=s->gi; - return UNZ_OK; -} - - -/* - Translate date/time from Dos format to tm_unz (readable more easilty) -*/ -local void unzlocal_DosDateToTmuDate (ulDosDate, ptm) - uLong ulDosDate; - tm_unz* ptm; -{ - uLong uDate; - uDate = (uLong)(ulDosDate>>16); - ptm->tm_mday = (uInt)(uDate&0x1f) ; - ptm->tm_mon = (uInt)((((uDate)&0x1E0)/0x20)-1) ; - ptm->tm_year = (uInt)(((uDate&0x0FE00)/0x0200)+1980) ; - - ptm->tm_hour = (uInt) ((ulDosDate &0xF800)/0x800); - ptm->tm_min = (uInt) ((ulDosDate&0x7E0)/0x20) ; - ptm->tm_sec = (uInt) (2*(ulDosDate&0x1f)) ; -} - -/* - Get Info about the current file in the zipfile, with internal only info -*/ -local int unzlocal_GetCurrentFileInfoInternal OF((unzFile file, - unz_file_info *pfile_info, - unz_file_info_internal - *pfile_info_internal, - char *szFileName, - uLong fileNameBufferSize, - void *extraField, - uLong extraFieldBufferSize, - char *szComment, - uLong commentBufferSize)); - -local int unzlocal_GetCurrentFileInfoInternal (file, - pfile_info, - pfile_info_internal, - szFileName, fileNameBufferSize, - extraField, extraFieldBufferSize, - szComment, commentBufferSize) - unzFile file; - unz_file_info *pfile_info; - unz_file_info_internal *pfile_info_internal; - char *szFileName; - uLong fileNameBufferSize; - void *extraField; - uLong extraFieldBufferSize; - char *szComment; - uLong commentBufferSize; -{ - unz_s* s; - unz_file_info file_info; - unz_file_info_internal file_info_internal; - int err=UNZ_OK; - uLong uMagic; - long lSeek=0; - - if (file==NULL) - return UNZ_PARAMERROR; - s=(unz_s*)file; - if (ZSEEK(s->z_filefunc, s->filestream, - s->pos_in_central_dir+s->byte_before_the_zipfile, - ZLIB_FILEFUNC_SEEK_SET)!=0) - err=UNZ_ERRNO; - - - /* we check the magic */ - if (err==UNZ_OK) - if (unzlocal_getLong(&s->z_filefunc, s->filestream,&uMagic) != UNZ_OK) - err=UNZ_ERRNO; - else if (uMagic!=0x02014b50) - err=UNZ_BADZIPFILE; - - if (unzlocal_getShort(&s->z_filefunc, s->filestream,&file_info.version) != UNZ_OK) - err=UNZ_ERRNO; - - if (unzlocal_getShort(&s->z_filefunc, s->filestream,&file_info.version_needed) != UNZ_OK) - err=UNZ_ERRNO; - - if (unzlocal_getShort(&s->z_filefunc, s->filestream,&file_info.flag) != UNZ_OK) - err=UNZ_ERRNO; - - if (unzlocal_getShort(&s->z_filefunc, s->filestream,&file_info.compression_method) != UNZ_OK) - err=UNZ_ERRNO; - - if (unzlocal_getLong(&s->z_filefunc, s->filestream,&file_info.dosDate) != UNZ_OK) - err=UNZ_ERRNO; - - unzlocal_DosDateToTmuDate(file_info.dosDate,&file_info.tmu_date); - - if (unzlocal_getLong(&s->z_filefunc, s->filestream,&file_info.crc) != UNZ_OK) - err=UNZ_ERRNO; - - if (unzlocal_getLong(&s->z_filefunc, s->filestream,&file_info.compressed_size) != UNZ_OK) - err=UNZ_ERRNO; - - if (unzlocal_getLong(&s->z_filefunc, s->filestream,&file_info.uncompressed_size) != UNZ_OK) - err=UNZ_ERRNO; - - if (unzlocal_getShort(&s->z_filefunc, s->filestream,&file_info.size_filename) != UNZ_OK) - err=UNZ_ERRNO; - - if (unzlocal_getShort(&s->z_filefunc, s->filestream,&file_info.size_file_extra) != UNZ_OK) - err=UNZ_ERRNO; - - if (unzlocal_getShort(&s->z_filefunc, s->filestream,&file_info.size_file_comment) != UNZ_OK) - err=UNZ_ERRNO; - - if (unzlocal_getShort(&s->z_filefunc, s->filestream,&file_info.disk_num_start) != UNZ_OK) - err=UNZ_ERRNO; - - if (unzlocal_getShort(&s->z_filefunc, s->filestream,&file_info.internal_fa) != UNZ_OK) - err=UNZ_ERRNO; - - if (unzlocal_getLong(&s->z_filefunc, s->filestream,&file_info.external_fa) != UNZ_OK) - err=UNZ_ERRNO; - - if (unzlocal_getLong(&s->z_filefunc, s->filestream,&file_info_internal.offset_curfile) != UNZ_OK) - err=UNZ_ERRNO; - - lSeek+=file_info.size_filename; - if ((err==UNZ_OK) && (szFileName!=NULL)) - { - uLong uSizeRead ; - if (file_info.size_filename0) && (fileNameBufferSize>0)) - if (ZREAD(s->z_filefunc, s->filestream,szFileName,uSizeRead)!=uSizeRead) - err=UNZ_ERRNO; - lSeek -= uSizeRead; - } - - - if ((err==UNZ_OK) && (extraField!=NULL)) - { - uLong uSizeRead ; - if (file_info.size_file_extraz_filefunc, s->filestream,lSeek,ZLIB_FILEFUNC_SEEK_CUR)==0) - lSeek=0; - else - err=UNZ_ERRNO; - if ((file_info.size_file_extra>0) && (extraFieldBufferSize>0)) - if (ZREAD(s->z_filefunc, s->filestream,extraField,uSizeRead)!=uSizeRead) - err=UNZ_ERRNO; - lSeek += file_info.size_file_extra - uSizeRead; - } - else - lSeek+=file_info.size_file_extra; - - - if ((err==UNZ_OK) && (szComment!=NULL)) - { - uLong uSizeRead ; - if (file_info.size_file_commentz_filefunc, s->filestream,lSeek,ZLIB_FILEFUNC_SEEK_CUR)==0) - lSeek=0; - else - err=UNZ_ERRNO; - if ((file_info.size_file_comment>0) && (commentBufferSize>0)) - if (ZREAD(s->z_filefunc, s->filestream,szComment,uSizeRead)!=uSizeRead) - err=UNZ_ERRNO; - lSeek+=file_info.size_file_comment - uSizeRead; - } - else - lSeek+=file_info.size_file_comment; - - if ((err==UNZ_OK) && (pfile_info!=NULL)) - *pfile_info=file_info; - - if ((err==UNZ_OK) && (pfile_info_internal!=NULL)) - *pfile_info_internal=file_info_internal; - - return err; -} - - - -/* - Write info about the ZipFile in the *pglobal_info structure. - No preparation of the structure is needed - return UNZ_OK if there is no problem. -*/ -extern int ZEXPORT unzGetCurrentFileInfo (file, - pfile_info, - szFileName, fileNameBufferSize, - extraField, extraFieldBufferSize, - szComment, commentBufferSize) - unzFile file; - unz_file_info *pfile_info; - char *szFileName; - uLong fileNameBufferSize; - void *extraField; - uLong extraFieldBufferSize; - char *szComment; - uLong commentBufferSize; -{ - return unzlocal_GetCurrentFileInfoInternal(file,pfile_info,NULL, - szFileName,fileNameBufferSize, - extraField,extraFieldBufferSize, - szComment,commentBufferSize); -} - -/* - Set the current file of the zipfile to the first file. - return UNZ_OK if there is no problem -*/ -extern int ZEXPORT unzGoToFirstFile (file) - unzFile file; -{ - int err=UNZ_OK; - unz_s* s; - if (file==NULL) - return UNZ_PARAMERROR; - s=(unz_s*)file; - s->pos_in_central_dir=s->offset_central_dir; - s->num_file=0; - err=unzlocal_GetCurrentFileInfoInternal(file,&s->cur_file_info, - &s->cur_file_info_internal, - NULL,0,NULL,0,NULL,0); - s->current_file_ok = (err == UNZ_OK); - return err; -} - -/* - Set the current file of the zipfile to the next file. - return UNZ_OK if there is no problem - return UNZ_END_OF_LIST_OF_FILE if the actual file was the latest. -*/ -extern int ZEXPORT unzGoToNextFile (file) - unzFile file; -{ - unz_s* s; - int err; - - if (file==NULL) - return UNZ_PARAMERROR; - s=(unz_s*)file; - if (!s->current_file_ok) - return UNZ_END_OF_LIST_OF_FILE; - if (s->gi.number_entry != 0xffff) /* 2^16 files overflow hack */ - if (s->num_file+1==s->gi.number_entry) - return UNZ_END_OF_LIST_OF_FILE; - - s->pos_in_central_dir += SIZECENTRALDIRITEM + s->cur_file_info.size_filename + - s->cur_file_info.size_file_extra + s->cur_file_info.size_file_comment ; - s->num_file++; - err = unzlocal_GetCurrentFileInfoInternal(file,&s->cur_file_info, - &s->cur_file_info_internal, - NULL,0,NULL,0,NULL,0); - s->current_file_ok = (err == UNZ_OK); - return err; -} - - -/* - Try locate the file szFileName in the zipfile. - For the iCaseSensitivity signification, see unzipStringFileNameCompare - - return value : - UNZ_OK if the file is found. It becomes the current file. - UNZ_END_OF_LIST_OF_FILE if the file is not found -*/ -extern int ZEXPORT unzLocateFile (file, szFileName, iCaseSensitivity) - unzFile file; - const char *szFileName; - int iCaseSensitivity; -{ - unz_s* s; - int err; - - /* We remember the 'current' position in the file so that we can jump - * back there if we fail. - */ - unz_file_info cur_file_infoSaved; - unz_file_info_internal cur_file_info_internalSaved; - uLong num_fileSaved; - uLong pos_in_central_dirSaved; - - - if (file==NULL) - return UNZ_PARAMERROR; - - if (strlen(szFileName)>=UNZ_MAXFILENAMEINZIP) - return UNZ_PARAMERROR; - - s=(unz_s*)file; - if (!s->current_file_ok) - return UNZ_END_OF_LIST_OF_FILE; - - /* Save the current state */ - num_fileSaved = s->num_file; - pos_in_central_dirSaved = s->pos_in_central_dir; - cur_file_infoSaved = s->cur_file_info; - cur_file_info_internalSaved = s->cur_file_info_internal; - - err = unzGoToFirstFile(file); - - while (err == UNZ_OK) - { - char szCurrentFileName[UNZ_MAXFILENAMEINZIP+1]; - err = unzGetCurrentFileInfo(file,NULL, - szCurrentFileName,sizeof(szCurrentFileName)-1, - NULL,0,NULL,0); - if (err == UNZ_OK) - { - if (unzStringFileNameCompare(szCurrentFileName, - szFileName,iCaseSensitivity)==0) - return UNZ_OK; - err = unzGoToNextFile(file); - } - } - - /* We failed, so restore the state of the 'current file' to where we - * were. - */ - s->num_file = num_fileSaved ; - s->pos_in_central_dir = pos_in_central_dirSaved ; - s->cur_file_info = cur_file_infoSaved; - s->cur_file_info_internal = cur_file_info_internalSaved; - return err; -} - - -/* -/////////////////////////////////////////// -// Contributed by Ryan Haksi (mailto://cryogen@infoserve.net) -// I need random access -// -// Further optimization could be realized by adding an ability -// to cache the directory in memory. The goal being a single -// comprehensive file read to put the file I need in a memory. -*/ - -/* -typedef struct unz_file_pos_s -{ - uLong pos_in_zip_directory; // offset in file - uLong num_of_file; // # of file -} unz_file_pos; -*/ - -extern int ZEXPORT unzGetFilePos(file, file_pos) - unzFile file; - unz_file_pos* file_pos; -{ - unz_s* s; - - if (file==NULL || file_pos==NULL) - return UNZ_PARAMERROR; - s=(unz_s*)file; - if (!s->current_file_ok) - return UNZ_END_OF_LIST_OF_FILE; - - file_pos->pos_in_zip_directory = s->pos_in_central_dir; - file_pos->num_of_file = s->num_file; - - return UNZ_OK; -} - -extern int ZEXPORT unzGoToFilePos(file, file_pos) - unzFile file; - unz_file_pos* file_pos; -{ - unz_s* s; - int err; - - if (file==NULL || file_pos==NULL) - return UNZ_PARAMERROR; - s=(unz_s*)file; - - /* jump to the right spot */ - s->pos_in_central_dir = file_pos->pos_in_zip_directory; - s->num_file = file_pos->num_of_file; - - /* set the current file */ - err = unzlocal_GetCurrentFileInfoInternal(file,&s->cur_file_info, - &s->cur_file_info_internal, - NULL,0,NULL,0,NULL,0); - /* return results */ - s->current_file_ok = (err == UNZ_OK); - return err; -} - -/* -// Unzip Helper Functions - should be here? -/////////////////////////////////////////// -*/ - -/* - Read the local header of the current zipfile - Check the coherency of the local header and info in the end of central - directory about this file - store in *piSizeVar the size of extra info in local header - (filename and size of extra field data) -*/ -local int unzlocal_CheckCurrentFileCoherencyHeader (s,piSizeVar, - poffset_local_extrafield, - psize_local_extrafield) - unz_s* s; - uInt* piSizeVar; - uLong *poffset_local_extrafield; - uInt *psize_local_extrafield; -{ - uLong uMagic,uData,uFlags; - uLong size_filename; - uLong size_extra_field; - int err=UNZ_OK; - - *piSizeVar = 0; - *poffset_local_extrafield = 0; - *psize_local_extrafield = 0; - - if (ZSEEK(s->z_filefunc, s->filestream,s->cur_file_info_internal.offset_curfile + - s->byte_before_the_zipfile,ZLIB_FILEFUNC_SEEK_SET)!=0) - return UNZ_ERRNO; - - - if (err==UNZ_OK) - if (unzlocal_getLong(&s->z_filefunc, s->filestream,&uMagic) != UNZ_OK) - err=UNZ_ERRNO; - else if (uMagic!=0x04034b50) - err=UNZ_BADZIPFILE; - - if (unzlocal_getShort(&s->z_filefunc, s->filestream,&uData) != UNZ_OK) - err=UNZ_ERRNO; -/* - else if ((err==UNZ_OK) && (uData!=s->cur_file_info.wVersion)) - err=UNZ_BADZIPFILE; -*/ - if (unzlocal_getShort(&s->z_filefunc, s->filestream,&uFlags) != UNZ_OK) - err=UNZ_ERRNO; - - if (unzlocal_getShort(&s->z_filefunc, s->filestream,&uData) != UNZ_OK) - err=UNZ_ERRNO; - else if ((err==UNZ_OK) && (uData!=s->cur_file_info.compression_method)) - err=UNZ_BADZIPFILE; - - if ((err==UNZ_OK) && (s->cur_file_info.compression_method!=0) && - (s->cur_file_info.compression_method!=Z_DEFLATED)) - err=UNZ_BADZIPFILE; - - if (unzlocal_getLong(&s->z_filefunc, s->filestream,&uData) != UNZ_OK) /* date/time */ - err=UNZ_ERRNO; - - if (unzlocal_getLong(&s->z_filefunc, s->filestream,&uData) != UNZ_OK) /* crc */ - err=UNZ_ERRNO; - else if ((err==UNZ_OK) && (uData!=s->cur_file_info.crc) && - ((uFlags & 8)==0)) - err=UNZ_BADZIPFILE; - - if (unzlocal_getLong(&s->z_filefunc, s->filestream,&uData) != UNZ_OK) /* size compr */ - err=UNZ_ERRNO; - else if ((err==UNZ_OK) && (uData!=s->cur_file_info.compressed_size) && - ((uFlags & 8)==0)) - err=UNZ_BADZIPFILE; - - if (unzlocal_getLong(&s->z_filefunc, s->filestream,&uData) != UNZ_OK) /* size uncompr */ - err=UNZ_ERRNO; - else if ((err==UNZ_OK) && (uData!=s->cur_file_info.uncompressed_size) && - ((uFlags & 8)==0)) - err=UNZ_BADZIPFILE; - - - if (unzlocal_getShort(&s->z_filefunc, s->filestream,&size_filename) != UNZ_OK) - err=UNZ_ERRNO; - else if ((err==UNZ_OK) && (size_filename!=s->cur_file_info.size_filename)) - err=UNZ_BADZIPFILE; - - *piSizeVar += (uInt)size_filename; - - if (unzlocal_getShort(&s->z_filefunc, s->filestream,&size_extra_field) != UNZ_OK) - err=UNZ_ERRNO; - *poffset_local_extrafield= s->cur_file_info_internal.offset_curfile + - SIZEZIPLOCALHEADER + size_filename; - *psize_local_extrafield = (uInt)size_extra_field; - - *piSizeVar += (uInt)size_extra_field; - - return err; -} - -/* - Open for reading data the current file in the zipfile. - If there is no error and the file is opened, the return value is UNZ_OK. -*/ -extern int ZEXPORT unzOpenCurrentFile3 (file, method, level, raw, password) - unzFile file; - int* method; - int* level; - int raw; - const char* password; -{ - int err=UNZ_OK; - uInt iSizeVar; - unz_s* s; - file_in_zip_read_info_s* pfile_in_zip_read_info; - uLong offset_local_extrafield; /* offset of the local extra field */ - uInt size_local_extrafield; /* size of the local extra field */ -# ifndef NOUNCRYPT - char source[12]; -# else - if (password != NULL) - return UNZ_PARAMERROR; -# endif - - if (file==NULL) - return UNZ_PARAMERROR; - s=(unz_s*)file; - if (!s->current_file_ok) - return UNZ_PARAMERROR; - - if (s->pfile_in_zip_read != NULL) - unzCloseCurrentFile(file); - - if (unzlocal_CheckCurrentFileCoherencyHeader(s,&iSizeVar, - &offset_local_extrafield,&size_local_extrafield)!=UNZ_OK) - return UNZ_BADZIPFILE; - - pfile_in_zip_read_info = (file_in_zip_read_info_s*) - ALLOC(sizeof(file_in_zip_read_info_s)); - if (pfile_in_zip_read_info==NULL) - return UNZ_INTERNALERROR; - - pfile_in_zip_read_info->read_buffer=(char*)ALLOC(UNZ_BUFSIZE); - pfile_in_zip_read_info->offset_local_extrafield = offset_local_extrafield; - pfile_in_zip_read_info->size_local_extrafield = size_local_extrafield; - pfile_in_zip_read_info->pos_local_extrafield=0; - pfile_in_zip_read_info->raw=raw; - - if (pfile_in_zip_read_info->read_buffer==NULL) - { - TRYFREE(pfile_in_zip_read_info); - return UNZ_INTERNALERROR; - } - - pfile_in_zip_read_info->stream_initialised=0; - - if (method!=NULL) - *method = (int)s->cur_file_info.compression_method; - - if (level!=NULL) - { - *level = 6; - switch (s->cur_file_info.flag & 0x06) - { - case 6 : *level = 1; break; - case 4 : *level = 2; break; - case 2 : *level = 9; break; - } - } - - if ((s->cur_file_info.compression_method!=0) && - (s->cur_file_info.compression_method!=Z_DEFLATED)) - err=UNZ_BADZIPFILE; - - pfile_in_zip_read_info->crc32_wait=s->cur_file_info.crc; - pfile_in_zip_read_info->crc32=0; - pfile_in_zip_read_info->compression_method = - s->cur_file_info.compression_method; - pfile_in_zip_read_info->filestream=s->filestream; - pfile_in_zip_read_info->z_filefunc=s->z_filefunc; - pfile_in_zip_read_info->byte_before_the_zipfile=s->byte_before_the_zipfile; - - pfile_in_zip_read_info->stream.total_out = 0; - - if ((s->cur_file_info.compression_method==Z_DEFLATED) && - (!raw)) - { - pfile_in_zip_read_info->stream.zalloc = (alloc_func)0; - pfile_in_zip_read_info->stream.zfree = (free_func)0; - pfile_in_zip_read_info->stream.opaque = (voidpf)0; - pfile_in_zip_read_info->stream.next_in = (voidpf)0; - pfile_in_zip_read_info->stream.avail_in = 0; - - err=inflateInit2(&pfile_in_zip_read_info->stream, -MAX_WBITS); - if (err == Z_OK) - pfile_in_zip_read_info->stream_initialised=1; - else - { - TRYFREE(pfile_in_zip_read_info); - return err; - } - /* windowBits is passed < 0 to tell that there is no zlib header. - * Note that in this case inflate *requires* an extra "dummy" byte - * after the compressed stream in order to complete decompression and - * return Z_STREAM_END. - * In unzip, i don't wait absolutely Z_STREAM_END because I known the - * size of both compressed and uncompressed data - */ - } - pfile_in_zip_read_info->rest_read_compressed = - s->cur_file_info.compressed_size ; - pfile_in_zip_read_info->rest_read_uncompressed = - s->cur_file_info.uncompressed_size ; - - - pfile_in_zip_read_info->pos_in_zipfile = - s->cur_file_info_internal.offset_curfile + SIZEZIPLOCALHEADER + - iSizeVar; - - pfile_in_zip_read_info->stream.avail_in = (uInt)0; - - s->pfile_in_zip_read = pfile_in_zip_read_info; - -# ifndef NOUNCRYPT - if (password != NULL) - { - int i; - s->pcrc_32_tab = get_crc_table(); - init_keys(password,s->keys,s->pcrc_32_tab); - if (ZSEEK(s->z_filefunc, s->filestream, - s->pfile_in_zip_read->pos_in_zipfile + - s->pfile_in_zip_read->byte_before_the_zipfile, - SEEK_SET)!=0) - return UNZ_INTERNALERROR; - if(ZREAD(s->z_filefunc, s->filestream,source, 12)<12) - return UNZ_INTERNALERROR; - - for (i = 0; i<12; i++) - zdecode(s->keys,s->pcrc_32_tab,source[i]); - - s->pfile_in_zip_read->pos_in_zipfile+=12; - s->encrypted=1; - } -# endif - - - return UNZ_OK; -} - -extern int ZEXPORT unzOpenCurrentFile (file) - unzFile file; -{ - return unzOpenCurrentFile3(file, NULL, NULL, 0, NULL); -} - -extern int ZEXPORT unzOpenCurrentFilePassword (file, password) - unzFile file; - const char* password; -{ - return unzOpenCurrentFile3(file, NULL, NULL, 0, password); -} - -extern int ZEXPORT unzOpenCurrentFile2 (file,method,level,raw) - unzFile file; - int* method; - int* level; - int raw; -{ - return unzOpenCurrentFile3(file, method, level, raw, NULL); -} - -/* - Read bytes from the current file. - buf contain buffer where data must be copied - len the size of buf. - - return the number of byte copied if somes bytes are copied - return 0 if the end of file was reached - return <0 with error code if there is an error - (UNZ_ERRNO for IO error, or zLib error for uncompress error) -*/ -extern int ZEXPORT unzReadCurrentFile (file, buf, len) - unzFile file; - voidp buf; - unsigned len; -{ - int err=UNZ_OK; - uInt iRead = 0; - unz_s* s; - file_in_zip_read_info_s* pfile_in_zip_read_info; - if (file==NULL) - return UNZ_PARAMERROR; - s=(unz_s*)file; - pfile_in_zip_read_info=s->pfile_in_zip_read; - - if (pfile_in_zip_read_info==NULL) - return UNZ_PARAMERROR; - - - if (pfile_in_zip_read_info->read_buffer == NULL) - return UNZ_END_OF_LIST_OF_FILE; - if (len==0) - return 0; - - pfile_in_zip_read_info->stream.next_out = (Bytef*)buf; - - pfile_in_zip_read_info->stream.avail_out = (uInt)len; - - if ((len>pfile_in_zip_read_info->rest_read_uncompressed) && - (!(pfile_in_zip_read_info->raw))) - pfile_in_zip_read_info->stream.avail_out = - (uInt)pfile_in_zip_read_info->rest_read_uncompressed; - - if ((len>pfile_in_zip_read_info->rest_read_compressed+ - pfile_in_zip_read_info->stream.avail_in) && - (pfile_in_zip_read_info->raw)) - pfile_in_zip_read_info->stream.avail_out = - (uInt)pfile_in_zip_read_info->rest_read_compressed+ - pfile_in_zip_read_info->stream.avail_in; - - while (pfile_in_zip_read_info->stream.avail_out>0) - { - if ((pfile_in_zip_read_info->stream.avail_in==0) && - (pfile_in_zip_read_info->rest_read_compressed>0)) - { - uInt uReadThis = UNZ_BUFSIZE; - if (pfile_in_zip_read_info->rest_read_compressedrest_read_compressed; - if (uReadThis == 0) - return UNZ_EOF; - if (ZSEEK(pfile_in_zip_read_info->z_filefunc, - pfile_in_zip_read_info->filestream, - pfile_in_zip_read_info->pos_in_zipfile + - pfile_in_zip_read_info->byte_before_the_zipfile, - ZLIB_FILEFUNC_SEEK_SET)!=0) - return UNZ_ERRNO; - if (ZREAD(pfile_in_zip_read_info->z_filefunc, - pfile_in_zip_read_info->filestream, - pfile_in_zip_read_info->read_buffer, - uReadThis)!=uReadThis) - return UNZ_ERRNO; - - -# ifndef NOUNCRYPT - if(s->encrypted) - { - uInt i; - for(i=0;iread_buffer[i] = - zdecode(s->keys,s->pcrc_32_tab, - pfile_in_zip_read_info->read_buffer[i]); - } -# endif - - - pfile_in_zip_read_info->pos_in_zipfile += uReadThis; - - pfile_in_zip_read_info->rest_read_compressed-=uReadThis; - - pfile_in_zip_read_info->stream.next_in = - (Bytef*)pfile_in_zip_read_info->read_buffer; - pfile_in_zip_read_info->stream.avail_in = (uInt)uReadThis; - } - - if ((pfile_in_zip_read_info->compression_method==0) || (pfile_in_zip_read_info->raw)) - { - uInt uDoCopy,i ; - - if ((pfile_in_zip_read_info->stream.avail_in == 0) && - (pfile_in_zip_read_info->rest_read_compressed == 0)) - return (iRead==0) ? UNZ_EOF : iRead; - - if (pfile_in_zip_read_info->stream.avail_out < - pfile_in_zip_read_info->stream.avail_in) - uDoCopy = pfile_in_zip_read_info->stream.avail_out ; - else - uDoCopy = pfile_in_zip_read_info->stream.avail_in ; - - for (i=0;istream.next_out+i) = - *(pfile_in_zip_read_info->stream.next_in+i); - - pfile_in_zip_read_info->crc32 = crc32(pfile_in_zip_read_info->crc32, - pfile_in_zip_read_info->stream.next_out, - uDoCopy); - pfile_in_zip_read_info->rest_read_uncompressed-=uDoCopy; - pfile_in_zip_read_info->stream.avail_in -= uDoCopy; - pfile_in_zip_read_info->stream.avail_out -= uDoCopy; - pfile_in_zip_read_info->stream.next_out += uDoCopy; - pfile_in_zip_read_info->stream.next_in += uDoCopy; - pfile_in_zip_read_info->stream.total_out += uDoCopy; - iRead += uDoCopy; - } - else - { - uLong uTotalOutBefore,uTotalOutAfter; - const Bytef *bufBefore; - uLong uOutThis; - int flush=Z_SYNC_FLUSH; - - uTotalOutBefore = pfile_in_zip_read_info->stream.total_out; - bufBefore = pfile_in_zip_read_info->stream.next_out; - - /* - if ((pfile_in_zip_read_info->rest_read_uncompressed == - pfile_in_zip_read_info->stream.avail_out) && - (pfile_in_zip_read_info->rest_read_compressed == 0)) - flush = Z_FINISH; - */ - err=inflate(&pfile_in_zip_read_info->stream,flush); - - if ((err>=0) && (pfile_in_zip_read_info->stream.msg!=NULL)) - err = Z_DATA_ERROR; - - uTotalOutAfter = pfile_in_zip_read_info->stream.total_out; - uOutThis = uTotalOutAfter-uTotalOutBefore; - - pfile_in_zip_read_info->crc32 = - crc32(pfile_in_zip_read_info->crc32,bufBefore, - (uInt)(uOutThis)); - - pfile_in_zip_read_info->rest_read_uncompressed -= - uOutThis; - - iRead += (uInt)(uTotalOutAfter - uTotalOutBefore); - - if (err==Z_STREAM_END) - return (iRead==0) ? UNZ_EOF : iRead; - if (err!=Z_OK) - break; - } - } - - if (err==Z_OK) - return iRead; - return err; -} - - -/* - Give the current position in uncompressed data -*/ -extern z_off_t ZEXPORT unztell (file) - unzFile file; -{ - unz_s* s; - file_in_zip_read_info_s* pfile_in_zip_read_info; - if (file==NULL) - return UNZ_PARAMERROR; - s=(unz_s*)file; - pfile_in_zip_read_info=s->pfile_in_zip_read; - - if (pfile_in_zip_read_info==NULL) - return UNZ_PARAMERROR; - - return (z_off_t)pfile_in_zip_read_info->stream.total_out; -} - - -/* - return 1 if the end of file was reached, 0 elsewhere -*/ -extern int ZEXPORT unzeof (file) - unzFile file; -{ - unz_s* s; - file_in_zip_read_info_s* pfile_in_zip_read_info; - if (file==NULL) - return UNZ_PARAMERROR; - s=(unz_s*)file; - pfile_in_zip_read_info=s->pfile_in_zip_read; - - if (pfile_in_zip_read_info==NULL) - return UNZ_PARAMERROR; - - if (pfile_in_zip_read_info->rest_read_uncompressed == 0) - return 1; - else - return 0; -} - - - -/* - Read extra field from the current file (opened by unzOpenCurrentFile) - This is the local-header version of the extra field (sometimes, there is - more info in the local-header version than in the central-header) - - if buf==NULL, it return the size of the local extra field that can be read - - if buf!=NULL, len is the size of the buffer, the extra header is copied in - buf. - the return value is the number of bytes copied in buf, or (if <0) - the error code -*/ -extern int ZEXPORT unzGetLocalExtrafield (file,buf,len) - unzFile file; - voidp buf; - unsigned len; -{ - unz_s* s; - file_in_zip_read_info_s* pfile_in_zip_read_info; - uInt read_now; - uLong size_to_read; - - if (file==NULL) - return UNZ_PARAMERROR; - s=(unz_s*)file; - pfile_in_zip_read_info=s->pfile_in_zip_read; - - if (pfile_in_zip_read_info==NULL) - return UNZ_PARAMERROR; - - size_to_read = (pfile_in_zip_read_info->size_local_extrafield - - pfile_in_zip_read_info->pos_local_extrafield); - - if (buf==NULL) - return (int)size_to_read; - - if (len>size_to_read) - read_now = (uInt)size_to_read; - else - read_now = (uInt)len ; - - if (read_now==0) - return 0; - - if (ZSEEK(pfile_in_zip_read_info->z_filefunc, - pfile_in_zip_read_info->filestream, - pfile_in_zip_read_info->offset_local_extrafield + - pfile_in_zip_read_info->pos_local_extrafield, - ZLIB_FILEFUNC_SEEK_SET)!=0) - return UNZ_ERRNO; - - if (ZREAD(pfile_in_zip_read_info->z_filefunc, - pfile_in_zip_read_info->filestream, - buf,read_now)!=read_now) - return UNZ_ERRNO; - - return (int)read_now; -} - -/* - Close the file in zip opened with unzipOpenCurrentFile - Return UNZ_CRCERROR if all the file was read but the CRC is not good -*/ -extern int ZEXPORT unzCloseCurrentFile (file) - unzFile file; -{ - int err=UNZ_OK; - - unz_s* s; - file_in_zip_read_info_s* pfile_in_zip_read_info; - if (file==NULL) - return UNZ_PARAMERROR; - s=(unz_s*)file; - pfile_in_zip_read_info=s->pfile_in_zip_read; - - if (pfile_in_zip_read_info==NULL) - return UNZ_PARAMERROR; - - - if ((pfile_in_zip_read_info->rest_read_uncompressed == 0) && - (!pfile_in_zip_read_info->raw)) - { - if (pfile_in_zip_read_info->crc32 != pfile_in_zip_read_info->crc32_wait) - err=UNZ_CRCERROR; - } - - - TRYFREE(pfile_in_zip_read_info->read_buffer); - pfile_in_zip_read_info->read_buffer = NULL; - if (pfile_in_zip_read_info->stream_initialised) - inflateEnd(&pfile_in_zip_read_info->stream); - - pfile_in_zip_read_info->stream_initialised = 0; - TRYFREE(pfile_in_zip_read_info); - - s->pfile_in_zip_read=NULL; - - return err; -} - - -/* - Get the global comment string of the ZipFile, in the szComment buffer. - uSizeBuf is the size of the szComment buffer. - return the number of byte copied or an error code <0 -*/ -extern int ZEXPORT unzGetGlobalComment (file, szComment, uSizeBuf) - unzFile file; - char *szComment; - uLong uSizeBuf; -{ - unz_s* s; - uLong uReadThis ; - if (file==NULL) - return UNZ_PARAMERROR; - s=(unz_s*)file; - - uReadThis = uSizeBuf; - if (uReadThis>s->gi.size_comment) - uReadThis = s->gi.size_comment; - - if (ZSEEK(s->z_filefunc,s->filestream,s->central_pos+22,ZLIB_FILEFUNC_SEEK_SET)!=0) - return UNZ_ERRNO; - - if (uReadThis>0) - { - *szComment='\0'; - if (ZREAD(s->z_filefunc,s->filestream,szComment,uReadThis)!=uReadThis) - return UNZ_ERRNO; - } - - if ((szComment != NULL) && (uSizeBuf > s->gi.size_comment)) - *(szComment+s->gi.size_comment)='\0'; - return (int)uReadThis; -} - -/* Additions by RX '2004 */ -extern uLong ZEXPORT unzGetOffset (file) - unzFile file; -{ - unz_s* s; - - if (file==NULL) - return UNZ_PARAMERROR; - s=(unz_s*)file; - if (!s->current_file_ok) - return 0; - if (s->gi.number_entry != 0 && s->gi.number_entry != 0xffff) - if (s->num_file==s->gi.number_entry) - return 0; - return s->pos_in_central_dir; -} - -extern int ZEXPORT unzSetOffset (file, pos) - unzFile file; - uLong pos; -{ - unz_s* s; - int err; - - if (file==NULL) - return UNZ_PARAMERROR; - s=(unz_s*)file; - - s->pos_in_central_dir = pos; - s->num_file = s->gi.number_entry; /* hack */ - err = unzlocal_GetCurrentFileInfoInternal(file,&s->cur_file_info, - &s->cur_file_info_internal, - NULL,0,NULL,0,NULL,0); - s->current_file_ok = (err == UNZ_OK); - return err; -} diff --git a/attic/programs/ios/WebODF/Classes/minizip/unzip.h b/attic/programs/ios/WebODF/Classes/minizip/unzip.h deleted file mode 100644 index c3206a058..000000000 --- a/attic/programs/ios/WebODF/Classes/minizip/unzip.h +++ /dev/null @@ -1,354 +0,0 @@ -/* unzip.h -- IO for uncompress .zip files using zlib - Version 1.01e, February 12th, 2005 - - Copyright (C) 1998-2005 Gilles Vollant - - This unzip package allow extract file from .ZIP file, compatible with PKZip 2.04g - WinZip, InfoZip tools and compatible. - - Multi volume ZipFile (span) are not supported. - Encryption compatible with pkzip 2.04g only supported - Old compressions used by old PKZip 1.x are not supported - - - I WAIT FEEDBACK at mail info@winimage.com - Visit also http://www.winimage.com/zLibDll/unzip.htm for evolution - - Condition of use and distribution are the same than zlib : - - This software is provided 'as-is', without any express or implied - warranty. In no event will the authors be held liable for any damages - arising from the use of this software. - - Permission is granted to anyone to use this software for any purpose, - including commercial applications, and to alter it and redistribute it - freely, subject to the following restrictions: - - 1. The origin of this software must not be misrepresented; you must not - claim that you wrote the original software. If you use this software - in a product, an acknowledgment in the product documentation would be - appreciated but is not required. - 2. Altered source versions must be plainly marked as such, and must not be - misrepresented as being the original software. - 3. This notice may not be removed or altered from any source distribution. - - -*/ - -/* for more info about .ZIP format, see - http://www.info-zip.org/pub/infozip/doc/appnote-981119-iz.zip - http://www.info-zip.org/pub/infozip/doc/ - PkWare has also a specification at : - ftp://ftp.pkware.com/probdesc.zip -*/ - -#ifndef _unz_H -#define _unz_H - -#ifdef __cplusplus -extern "C" { -#endif - -#ifndef _ZLIB_H -#include "zlib.h" -#endif - -#ifndef _ZLIBIOAPI_H -#include "ioapi.h" -#endif - -#if defined(STRICTUNZIP) || defined(STRICTZIPUNZIP) -/* like the STRICT of WIN32, we define a pointer that cannot be converted - from (void*) without cast */ -typedef struct TagunzFile__ { int unused; } unzFile__; -typedef unzFile__ *unzFile; -#else -typedef voidp unzFile; -#endif - - -#define UNZ_OK (0) -#define UNZ_END_OF_LIST_OF_FILE (-100) -#define UNZ_ERRNO (Z_ERRNO) -#define UNZ_EOF (0) -#define UNZ_PARAMERROR (-102) -#define UNZ_BADZIPFILE (-103) -#define UNZ_INTERNALERROR (-104) -#define UNZ_CRCERROR (-105) - -/* tm_unz contain date/time info */ -typedef struct tm_unz_s -{ - uInt tm_sec; /* seconds after the minute - [0,59] */ - uInt tm_min; /* minutes after the hour - [0,59] */ - uInt tm_hour; /* hours since midnight - [0,23] */ - uInt tm_mday; /* day of the month - [1,31] */ - uInt tm_mon; /* months since January - [0,11] */ - uInt tm_year; /* years - [1980..2044] */ -} tm_unz; - -/* unz_global_info structure contain global data about the ZIPfile - These data comes from the end of central dir */ -typedef struct unz_global_info_s -{ - uLong number_entry; /* total number of entries in - the central dir on this disk */ - uLong size_comment; /* size of the global comment of the zipfile */ -} unz_global_info; - - -/* unz_file_info contain information about a file in the zipfile */ -typedef struct unz_file_info_s -{ - uLong version; /* version made by 2 bytes */ - uLong version_needed; /* version needed to extract 2 bytes */ - uLong flag; /* general purpose bit flag 2 bytes */ - uLong compression_method; /* compression method 2 bytes */ - uLong dosDate; /* last mod file date in Dos fmt 4 bytes */ - uLong crc; /* crc-32 4 bytes */ - uLong compressed_size; /* compressed size 4 bytes */ - uLong uncompressed_size; /* uncompressed size 4 bytes */ - uLong size_filename; /* filename length 2 bytes */ - uLong size_file_extra; /* extra field length 2 bytes */ - uLong size_file_comment; /* file comment length 2 bytes */ - - uLong disk_num_start; /* disk number start 2 bytes */ - uLong internal_fa; /* internal file attributes 2 bytes */ - uLong external_fa; /* external file attributes 4 bytes */ - - tm_unz tmu_date; -} unz_file_info; - -extern int ZEXPORT unzStringFileNameCompare OF ((const char* fileName1, - const char* fileName2, - int iCaseSensitivity)); -/* - Compare two filename (fileName1,fileName2). - If iCaseSenisivity = 1, comparision is case sensitivity (like strcmp) - If iCaseSenisivity = 2, comparision is not case sensitivity (like strcmpi - or strcasecmp) - If iCaseSenisivity = 0, case sensitivity is defaut of your operating system - (like 1 on Unix, 2 on Windows) -*/ - - -extern unzFile ZEXPORT unzOpen OF((const char *path)); -/* - Open a Zip file. path contain the full pathname (by example, - on a Windows XP computer "c:\\zlib\\zlib113.zip" or on an Unix computer - "zlib/zlib113.zip". - If the zipfile cannot be opened (file don't exist or in not valid), the - return value is NULL. - Else, the return value is a unzFile Handle, usable with other function - of this unzip package. -*/ - -extern unzFile ZEXPORT unzOpen2 OF((const char *path, - zlib_filefunc_def* pzlib_filefunc_def)); -/* - Open a Zip file, like unzOpen, but provide a set of file low level API - for read/write the zip file (see ioapi.h) -*/ - -extern int ZEXPORT unzClose OF((unzFile file)); -/* - Close a ZipFile opened with unzipOpen. - If there is files inside the .Zip opened with unzOpenCurrentFile (see later), - these files MUST be closed with unzipCloseCurrentFile before call unzipClose. - return UNZ_OK if there is no problem. */ - -extern int ZEXPORT unzGetGlobalInfo OF((unzFile file, - unz_global_info *pglobal_info)); -/* - Write info about the ZipFile in the *pglobal_info structure. - No preparation of the structure is needed - return UNZ_OK if there is no problem. */ - - -extern int ZEXPORT unzGetGlobalComment OF((unzFile file, - char *szComment, - uLong uSizeBuf)); -/* - Get the global comment string of the ZipFile, in the szComment buffer. - uSizeBuf is the size of the szComment buffer. - return the number of byte copied or an error code <0 -*/ - - -/***************************************************************************/ -/* Unzip package allow you browse the directory of the zipfile */ - -extern int ZEXPORT unzGoToFirstFile OF((unzFile file)); -/* - Set the current file of the zipfile to the first file. - return UNZ_OK if there is no problem -*/ - -extern int ZEXPORT unzGoToNextFile OF((unzFile file)); -/* - Set the current file of the zipfile to the next file. - return UNZ_OK if there is no problem - return UNZ_END_OF_LIST_OF_FILE if the actual file was the latest. -*/ - -extern int ZEXPORT unzLocateFile OF((unzFile file, - const char *szFileName, - int iCaseSensitivity)); -/* - Try locate the file szFileName in the zipfile. - For the iCaseSensitivity signification, see unzStringFileNameCompare - - return value : - UNZ_OK if the file is found. It becomes the current file. - UNZ_END_OF_LIST_OF_FILE if the file is not found -*/ - - -/* ****************************************** */ -/* Ryan supplied functions */ -/* unz_file_info contain information about a file in the zipfile */ -typedef struct unz_file_pos_s -{ - uLong pos_in_zip_directory; /* offset in zip file directory */ - uLong num_of_file; /* # of file */ -} unz_file_pos; - -extern int ZEXPORT unzGetFilePos( - unzFile file, - unz_file_pos* file_pos); - -extern int ZEXPORT unzGoToFilePos( - unzFile file, - unz_file_pos* file_pos); - -/* ****************************************** */ - -extern int ZEXPORT unzGetCurrentFileInfo OF((unzFile file, - unz_file_info *pfile_info, - char *szFileName, - uLong fileNameBufferSize, - void *extraField, - uLong extraFieldBufferSize, - char *szComment, - uLong commentBufferSize)); -/* - Get Info about the current file - if pfile_info!=NULL, the *pfile_info structure will contain somes info about - the current file - if szFileName!=NULL, the filemane string will be copied in szFileName - (fileNameBufferSize is the size of the buffer) - if extraField!=NULL, the extra field information will be copied in extraField - (extraFieldBufferSize is the size of the buffer). - This is the Central-header version of the extra field - if szComment!=NULL, the comment string of the file will be copied in szComment - (commentBufferSize is the size of the buffer) -*/ - -/***************************************************************************/ -/* for reading the content of the current zipfile, you can open it, read data - from it, and close it (you can close it before reading all the file) - */ - -extern int ZEXPORT unzOpenCurrentFile OF((unzFile file)); -/* - Open for reading data the current file in the zipfile. - If there is no error, the return value is UNZ_OK. -*/ - -extern int ZEXPORT unzOpenCurrentFilePassword OF((unzFile file, - const char* password)); -/* - Open for reading data the current file in the zipfile. - password is a crypting password - If there is no error, the return value is UNZ_OK. -*/ - -extern int ZEXPORT unzOpenCurrentFile2 OF((unzFile file, - int* method, - int* level, - int raw)); -/* - Same than unzOpenCurrentFile, but open for read raw the file (not uncompress) - if raw==1 - *method will receive method of compression, *level will receive level of - compression - note : you can set level parameter as NULL (if you did not want known level, - but you CANNOT set method parameter as NULL -*/ - -extern int ZEXPORT unzOpenCurrentFile3 OF((unzFile file, - int* method, - int* level, - int raw, - const char* password)); -/* - Same than unzOpenCurrentFile, but open for read raw the file (not uncompress) - if raw==1 - *method will receive method of compression, *level will receive level of - compression - note : you can set level parameter as NULL (if you did not want known level, - but you CANNOT set method parameter as NULL -*/ - - -extern int ZEXPORT unzCloseCurrentFile OF((unzFile file)); -/* - Close the file in zip opened with unzOpenCurrentFile - Return UNZ_CRCERROR if all the file was read but the CRC is not good -*/ - -extern int ZEXPORT unzReadCurrentFile OF((unzFile file, - voidp buf, - unsigned len)); -/* - Read bytes from the current file (opened by unzOpenCurrentFile) - buf contain buffer where data must be copied - len the size of buf. - - return the number of byte copied if somes bytes are copied - return 0 if the end of file was reached - return <0 with error code if there is an error - (UNZ_ERRNO for IO error, or zLib error for uncompress error) -*/ - -extern z_off_t ZEXPORT unztell OF((unzFile file)); -/* - Give the current position in uncompressed data -*/ - -extern int ZEXPORT unzeof OF((unzFile file)); -/* - return 1 if the end of file was reached, 0 elsewhere -*/ - -extern int ZEXPORT unzGetLocalExtrafield OF((unzFile file, - voidp buf, - unsigned len)); -/* - Read extra field from the current file (opened by unzOpenCurrentFile) - This is the local-header version of the extra field (sometimes, there is - more info in the local-header version than in the central-header) - - if buf==NULL, it return the size of the local extra field - - if buf!=NULL, len is the size of the buffer, the extra header is copied in - buf. - the return value is the number of bytes copied in buf, or (if <0) - the error code -*/ - -/***************************************************************************/ - -/* Get the current file offset */ -extern uLong ZEXPORT unzGetOffset (unzFile file); - -/* Set the current file offset */ -extern int ZEXPORT unzSetOffset (unzFile file, uLong pos); - - - -#ifdef __cplusplus -} -#endif - -#endif /* _unz_H */ diff --git a/attic/programs/ios/WebODF/Classes/zip.h b/attic/programs/ios/WebODF/Classes/zip.h deleted file mode 100644 index a4f071605..000000000 --- a/attic/programs/ios/WebODF/Classes/zip.h +++ /dev/null @@ -1,15 +0,0 @@ -// -// Header.h -// WebODF -// -// Created by KO GmbH on 3/1/12. -// Copyright (c) 2012 __MyCompanyName__. All rights reserved. -// - -#ifndef WebODF_Header_h -#define WebODF_Header_h - -void readZipEntry(const char* zippath, const char* entrypath) {} - - -#endif diff --git a/attic/programs/ios/WebODF/Cordova.plist b/attic/programs/ios/WebODF/Cordova.plist deleted file mode 100644 index 4829e861a..000000000 --- a/attic/programs/ios/WebODF/Cordova.plist +++ /dev/null @@ -1,63 +0,0 @@ - - - - - UIWebViewBounce - - TopActivityIndicator - gray - EnableLocation - - EnableViewportScale - - AutoHideSplashScreen - - ShowSplashScreenSpinner - - MediaPlaybackRequiresUserAction - - AllowInlineMediaPlayback - - OpenAllWhitelistURLsInWebView - - ExternalHosts - - zipserver - - Plugins - - ZipClass - NativeZip - Logger - CDVLogger - Compass - CDVLocation - Accelerometer - CDVAccelerometer - Camera - CDVCamera - NetworkStatus - CDVConnection - Contacts - CDVContacts - Debug Console - CDVDebugConsole - File - CDVFile - FileTransfer - CDVFileTransfer - Geolocation - CDVLocation - Notification - CDVNotification - Media - CDVSound - Capture - CDVCapture - SplashScreen - CDVSplashScreen - Battery - CDVBattery - - - diff --git a/attic/programs/ios/WebODF/Resources/en.lproj/Localizable.strings b/attic/programs/ios/WebODF/Resources/en.lproj/Localizable.strings deleted file mode 100644 index 897268443..000000000 --- a/attic/programs/ios/WebODF/Resources/en.lproj/Localizable.strings +++ /dev/null @@ -1,25 +0,0 @@ -/* - Licensed to the Apache Software Foundation (ASF) under one - or more contributor license agreements. See the NOTICE file - distributed with this work for additional information - regarding copyright ownership. The ASF licenses this file - to you under the Apache License, Version 2.0 (the - "License"); you may not use this file except in compliance - with the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, - software distributed under the License is distributed on an - "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - KIND, either express or implied. See the License for the - specific language governing permissions and limitations - under the License. - */ - -// accessibility label for recording button -"toggle audio recording" = "toggle audio recording"; -// notification spoken by VoiceOver when timed recording finishes -"timed recording complete" = "timed recording complete"; -// accessibility hint for display of recorded elapsed time -"recorded time in minutes and seconds" = "recorded time in minutes and seconds"; \ No newline at end of file diff --git a/attic/programs/ios/WebODF/Resources/icons/icon-72.png b/attic/programs/ios/WebODF/Resources/icons/icon-72.png deleted file mode 100644 index 1aebf5d34..000000000 Binary files a/attic/programs/ios/WebODF/Resources/icons/icon-72.png and /dev/null differ diff --git a/attic/programs/ios/WebODF/Resources/icons/icon.png b/attic/programs/ios/WebODF/Resources/icons/icon.png deleted file mode 100644 index 9e654236c..000000000 Binary files a/attic/programs/ios/WebODF/Resources/icons/icon.png and /dev/null differ diff --git a/attic/programs/ios/WebODF/Resources/icons/icon@2x.png b/attic/programs/ios/WebODF/Resources/icons/icon@2x.png deleted file mode 100644 index b7ccb848e..000000000 Binary files a/attic/programs/ios/WebODF/Resources/icons/icon@2x.png and /dev/null differ diff --git a/attic/programs/ios/WebODF/Resources/splash/Default-Landscape~ipad.png b/attic/programs/ios/WebODF/Resources/splash/Default-Landscape~ipad.png deleted file mode 100644 index 06bb96b39..000000000 Binary files a/attic/programs/ios/WebODF/Resources/splash/Default-Landscape~ipad.png and /dev/null differ diff --git a/attic/programs/ios/WebODF/Resources/splash/Default-Portrait~ipad.png b/attic/programs/ios/WebODF/Resources/splash/Default-Portrait~ipad.png deleted file mode 100644 index dbfed967a..000000000 Binary files a/attic/programs/ios/WebODF/Resources/splash/Default-Portrait~ipad.png and /dev/null differ diff --git a/attic/programs/ios/WebODF/Resources/splash/Default.png b/attic/programs/ios/WebODF/Resources/splash/Default.png deleted file mode 100755 index fbf06e22d..000000000 Binary files a/attic/programs/ios/WebODF/Resources/splash/Default.png and /dev/null differ diff --git a/attic/programs/ios/WebODF/Resources/splash/Default@2x.png b/attic/programs/ios/WebODF/Resources/splash/Default@2x.png deleted file mode 100755 index e845a3f0b..000000000 Binary files a/attic/programs/ios/WebODF/Resources/splash/Default@2x.png and /dev/null differ diff --git a/attic/programs/ios/WebODF/WebODF-Info.plist b/attic/programs/ios/WebODF/WebODF-Info.plist deleted file mode 100644 index 18518ec4c..000000000 --- a/attic/programs/ios/WebODF/WebODF-Info.plist +++ /dev/null @@ -1,122 +0,0 @@ - - - - - CFBundleDevelopmentRegion - English - CFBundleDisplayName - ${PRODUCT_NAME} - CFBundleDocumentTypes - - - CFBundleTypeIconFiles - - icon.png - - CFBundleTypeName - OpenDocument Text - CFBundleTypeRole - Viewer - LSHandlerRank - Owner - LSItemContentTypes - - org.oasis.opendocument.text - - - - CFBundleTypeIconFiles - - icon.png - - CFBundleTypeName - OpenDocument Presentation - CFBundleTypeRole - Viewer - LSHandlerRank - Owner - LSItemContentTypes - - org.oasis.opendocument.presentation - - - - CFBundleExecutable - ${EXECUTABLE_NAME} - CFBundleIconFile - icon.png - CFBundleIconFiles - - icon.png - icon@2x.png - icon-72.png - - CFBundleIdentifier - WebODF-03 - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - ${PRODUCT_NAME} - CFBundlePackageType - APPL - CFBundleShortVersionString - 1.0 - CFBundleSignature - ???? - CFBundleVersion - 1.0 - LSRequiresIPhoneOS - - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UISupportedInterfaceOrientations~ipad - - UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeRight - - UTExportedTypeDeclarations - - - UTTypeConformsTo - - org.gnu.gnu-zip-archive - - UTTypeDescription - OpenDocument Text - UTTypeIdentifier - org.oasis.opendocument.text - UTTypeTagSpecification - - public.filename-extension - odt - public.mime-type - application/vnd.oasis.opendocument.text - - - - UTTypeConformsTo - - org.gnu.gnu-zip-archive - - UTTypeDescription - OpenDocument Presentation - UTTypeIdentifier - org.oasis.opendocument.presentation - UTTypeTagSpecification - - public.filename-extension - odp - public.mime-type - application/vnd.oasis.opendocument.presentation - - - - - diff --git a/attic/programs/ios/WebODF/WebODF-Prefix.pch b/attic/programs/ios/WebODF/WebODF-Prefix.pch deleted file mode 100644 index da48b6a1f..000000000 --- a/attic/programs/ios/WebODF/WebODF-Prefix.pch +++ /dev/null @@ -1,7 +0,0 @@ -// -// Prefix header for all source files of the 'WebODF' target in the 'WebODF' project -// - -#ifdef __OBJC__ - #import -#endif diff --git a/attic/programs/ios/WebODF/en.lproj/InfoPlist.strings b/attic/programs/ios/WebODF/en.lproj/InfoPlist.strings deleted file mode 100644 index 477b28ff8..000000000 --- a/attic/programs/ios/WebODF/en.lproj/InfoPlist.strings +++ /dev/null @@ -1,2 +0,0 @@ -/* Localized versions of Info.plist keys */ - diff --git a/attic/programs/ios/WebODF/main.m b/attic/programs/ios/WebODF/main.m deleted file mode 100644 index bda2f99ea..000000000 --- a/attic/programs/ios/WebODF/main.m +++ /dev/null @@ -1,32 +0,0 @@ -/* - Licensed to the Apache Software Foundation (ASF) under one - or more contributor license agreements. See the NOTICE file - distributed with this work for additional information - regarding copyright ownership. The ASF licenses this file - to you under the Apache License, Version 2.0 (the - "License"); you may not use this file except in compliance - with the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, - software distributed under the License is distributed on an - "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - KIND, either express or implied. See the License for the - specific language governing permissions and limitations - under the License. - */ -// -// main.m -// WebODF -// -// Created by KO GmbH on 2/2/12. -// Copyright __MyCompanyName__ 2012. All rights reserved. -// - -#import - -int main(int argc, char *argv[]) { - int retVal = UIApplicationMain(argc, argv, nil, @"AppDelegate"); - return retVal; -} diff --git a/attic/programs/ios/welcome.odt b/attic/programs/ios/welcome.odt deleted file mode 100644 index 9845da8c6..000000000 Binary files a/attic/programs/ios/welcome.odt and /dev/null differ diff --git a/attic/programs/ios/www/cordova-1.8.0.js b/attic/programs/ios/www/cordova-1.8.0.js deleted file mode 100644 index f03b4271e..000000000 --- a/attic/programs/ios/www/cordova-1.8.0.js +++ /dev/null @@ -1,5226 +0,0 @@ -// commit 109b8649b0e98597b147842a6f71999d2f7910f2 - -// File generated at :: Tue Jun 05 2012 14:10:19 GMT-0700 (PDT) - -/* - Licensed to the Apache Software Foundation (ASF) under one - or more contributor license agreements. See the NOTICE file - distributed with this work for additional information - regarding copyright ownership. The ASF licenses this file - to you under the Apache License, Version 2.0 (the - "License"); you may not use this file except in compliance - with the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, - software distributed under the License is distributed on an - "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - KIND, either express or implied. See the License for the - specific language governing permissions and limitations - under the License. -*/ - -;(function() { - -// file: lib/scripts/require.js -var require, - define; - -(function () { - var modules = {}; - - function build(module) { - var factory = module.factory; - module.exports = {}; - delete module.factory; - factory(require, module.exports, module); - return module.exports; - } - - require = function (id) { - if (!modules[id]) { - throw "module " + id + " not found"; - } - return modules[id].factory ? build(modules[id]) : modules[id].exports; - }; - - define = function (id, factory) { - if (modules[id]) { - throw "module " + id + " already defined"; - } - - modules[id] = { - id: id, - factory: factory - }; - }; - - define.remove = function (id) { - delete modules[id]; - }; - -})(); - -//Export for use in node -if (typeof module === "object" && typeof require === "function") { - module.exports.require = require; - module.exports.define = define; -} -// file: lib/cordova.js -define("cordova", function(require, exports, module) { -var channel = require('cordova/channel'); - -/** - * Listen for DOMContentLoaded and notify our channel subscribers. - */ -document.addEventListener('DOMContentLoaded', function() { - channel.onDOMContentLoaded.fire(); -}, false); -if (document.readyState == 'complete' || document.readyState == 'interactive') { - channel.onDOMContentLoaded.fire(); -} - -/** - * Intercept calls to addEventListener + removeEventListener and handle deviceready, - * resume, and pause events. - */ -var m_document_addEventListener = document.addEventListener; -var m_document_removeEventListener = document.removeEventListener; -var m_window_addEventListener = window.addEventListener; -var m_window_removeEventListener = window.removeEventListener; - -/** - * Houses custom event handlers to intercept on document + window event listeners. - */ -var documentEventHandlers = {}, - windowEventHandlers = {}; - -document.addEventListener = function(evt, handler, capture) { - var e = evt.toLowerCase(); - if (typeof documentEventHandlers[e] != 'undefined') { - if (evt === 'deviceready') { - documentEventHandlers[e].subscribeOnce(handler); - } else { - documentEventHandlers[e].subscribe(handler); - } - } else { - m_document_addEventListener.call(document, evt, handler, capture); - } -}; - -window.addEventListener = function(evt, handler, capture) { - var e = evt.toLowerCase(); - if (typeof windowEventHandlers[e] != 'undefined') { - windowEventHandlers[e].subscribe(handler); - } else { - m_window_addEventListener.call(window, evt, handler, capture); - } -}; - -document.removeEventListener = function(evt, handler, capture) { - var e = evt.toLowerCase(); - // If unsubcribing from an event that is handled by a plugin - if (typeof documentEventHandlers[e] != "undefined") { - documentEventHandlers[e].unsubscribe(handler); - } else { - m_document_removeEventListener.call(document, evt, handler, capture); - } -}; - -window.removeEventListener = function(evt, handler, capture) { - var e = evt.toLowerCase(); - // If unsubcribing from an event that is handled by a plugin - if (typeof windowEventHandlers[e] != "undefined") { - windowEventHandlers[e].unsubscribe(handler); - } else { - m_window_removeEventListener.call(window, evt, handler, capture); - } -}; - -function createEvent(type, data) { - var event = document.createEvent('Events'); - event.initEvent(type, false, false); - if (data) { - for (var i in data) { - if (data.hasOwnProperty(i)) { - event[i] = data[i]; - } - } - } - return event; -} - -if(typeof window.console === "undefined") { - window.console = { - log:function(){} - }; -} - -var cordova = { - define:define, - require:require, - /** - * Methods to add/remove your own addEventListener hijacking on document + window. - */ - addWindowEventHandler:function(event, opts) { - return (windowEventHandlers[event] = channel.create(event, opts)); - }, - addDocumentEventHandler:function(event, opts) { - return (documentEventHandlers[event] = channel.create(event, opts)); - }, - removeWindowEventHandler:function(event) { - delete windowEventHandlers[event]; - }, - removeDocumentEventHandler:function(event) { - delete documentEventHandlers[event]; - }, - /** - * Retreive original event handlers that were replaced by Cordova - * - * @return object - */ - getOriginalHandlers: function() { - return {'document': {'addEventListener': m_document_addEventListener, 'removeEventListener': m_document_removeEventListener}, - 'window': {'addEventListener': m_window_addEventListener, 'removeEventListener': m_window_removeEventListener}}; - }, - /** - * Method to fire event from native code - */ - fireDocumentEvent: function(type, data) { - var evt = createEvent(type, data); - if (typeof documentEventHandlers[type] != 'undefined') { - documentEventHandlers[type].fire(evt); - } else { - document.dispatchEvent(evt); - } - }, - fireWindowEvent: function(type, data) { - var evt = createEvent(type,data); - if (typeof windowEventHandlers[type] != 'undefined') { - windowEventHandlers[type].fire(evt); - } else { - window.dispatchEvent(evt); - } - }, - // TODO: this is Android only; think about how to do this better - shuttingDown:false, - UsePolling:false, - // END TODO - - // TODO: iOS only - // This queue holds the currently executing command and all pending - // commands executed with cordova.exec(). - commandQueue:[], - // Indicates if we're currently in the middle of flushing the command - // queue on the native side. - commandQueueFlushing:false, - // END TODO - /** - * Plugin callback mechanism. - */ - callbackId: 0, - callbacks: {}, - callbackStatus: { - NO_RESULT: 0, - OK: 1, - CLASS_NOT_FOUND_EXCEPTION: 2, - ILLEGAL_ACCESS_EXCEPTION: 3, - INSTANTIATION_EXCEPTION: 4, - MALFORMED_URL_EXCEPTION: 5, - IO_EXCEPTION: 6, - INVALID_ACTION: 7, - JSON_EXCEPTION: 8, - ERROR: 9 - }, - - /** - * Called by native code when returning successful result from an action. - * - * @param callbackId - * @param args - */ - callbackSuccess: function(callbackId, args) { - if (cordova.callbacks[callbackId]) { - - // If result is to be sent to callback - if (args.status == cordova.callbackStatus.OK) { - try { - if (cordova.callbacks[callbackId].success) { - cordova.callbacks[callbackId].success(args.message); - } - } - catch (e) { - console.log("Error in success callback: "+callbackId+" = "+e); - } - } - - // Clear callback if not expecting any more results - if (!args.keepCallback) { - delete cordova.callbacks[callbackId]; - } - } - }, - - /** - * Called by native code when returning error result from an action. - * - * @param callbackId - * @param args - */ - callbackError: function(callbackId, args) { - if (cordova.callbacks[callbackId]) { - try { - if (cordova.callbacks[callbackId].fail) { - cordova.callbacks[callbackId].fail(args.message); - } - } - catch (e) { - console.log("Error in error callback: "+callbackId+" = "+e); - } - - // Clear callback if not expecting any more results - if (!args.keepCallback) { - delete cordova.callbacks[callbackId]; - } - } - }, - // TODO: remove in 2.0. - addPlugin: function(name, obj) { - console.log("[DEPRECATION NOTICE] window.addPlugin and window.plugins will be removed in version 2.0."); - if (!window.plugins[name]) { - window.plugins[name] = obj; - } - else { - console.log("Error: Plugin "+name+" already exists."); - } - }, - - addConstructor: function(func) { - channel.onCordovaReady.subscribeOnce(function() { - try { - func(); - } catch(e) { - console.log("Failed to run constructor: " + e); - } - }); - } -}; - -// Register pause, resume and deviceready channels as events on document. -channel.onPause = cordova.addDocumentEventHandler('pause'); -channel.onResume = cordova.addDocumentEventHandler('resume'); -channel.onDeviceReady = cordova.addDocumentEventHandler('deviceready'); - -// Adds deprecation warnings to functions of an object (but only logs a message once) -function deprecateFunctions(obj, objLabel) { - var newObj = {}; - var logHash = {}; - for (var i in obj) { - if (obj.hasOwnProperty(i)) { - if (typeof obj[i] == 'function') { - newObj[i] = (function(prop){ - var oldFunk = obj[prop]; - var funkId = objLabel + '_' + prop; - return function() { - if (!logHash[funkId]) { - console.log('[DEPRECATION NOTICE] The "' + objLabel + '" global will be removed in version 2.0, please use lowercase "cordova".'); - logHash[funkId] = true; - } - oldFunk.apply(obj, arguments); - }; - })(i); - } else { - newObj[i] = (function(prop) { return obj[prop]; })(i); - } - } - } - return newObj; -} - -/** - * Legacy variable for plugin support - * TODO: remove in 2.0. - */ -if (!window.PhoneGap) { - window.PhoneGap = deprecateFunctions(cordova, 'PhoneGap'); -} -if (!window.Cordova) { - window.Cordova = deprecateFunctions(cordova, 'Cordova'); -} - -/** - * Plugins object - * TODO: remove in 2.0. - */ -if (!window.plugins) { - window.plugins = {}; -} - -module.exports = cordova; - -}); - -// file: lib/common/builder.js -define("cordova/builder", function(require, exports, module) { -var utils = require('cordova/utils'); - -function each(objects, func, context) { - for (var prop in objects) { - if (objects.hasOwnProperty(prop)) { - func.apply(context, [objects[prop], prop]); - } - } -} - -function include(parent, objects, clobber, merge) { - each(objects, function (obj, key) { - try { - var result = obj.path ? require(obj.path) : {}; - - if (clobber) { - // Clobber if it doesn't exist. - if (typeof parent[key] === 'undefined') { - parent[key] = result; - } else if (typeof obj.path !== 'undefined') { - // If merging, merge properties onto parent, otherwise, clobber. - if (merge) { - recursiveMerge(parent[key], result); - } else { - parent[key] = result; - } - } - result = parent[key]; - } else { - // Overwrite if not currently defined. - if (typeof parent[key] == 'undefined') { - parent[key] = result; - } else if (merge && typeof obj.path !== 'undefined') { - // If merging, merge parent onto result - recursiveMerge(result, parent[key]); - parent[key] = result; - } else { - // Set result to what already exists, so we can build children into it if they exist. - result = parent[key]; - } - } - - if (obj.children) { - include(result, obj.children, clobber, merge); - } - } catch(e) { - utils.alert('Exception building cordova JS globals: ' + e + ' for key "' + key + '"'); - } - }); -} - -/** - * Merge properties from one object onto another recursively. Properties from - * the src object will overwrite existing target property. - * - * @param target Object to merge properties into. - * @param src Object to merge properties from. - */ -function recursiveMerge(target, src) { - for (var prop in src) { - if (src.hasOwnProperty(prop)) { - if (typeof target.prototype !== 'undefined' && target.prototype.constructor === target) { - // If the target object is a constructor override off prototype. - target.prototype[prop] = src[prop]; - } else { - target[prop] = typeof src[prop] === 'object' ? recursiveMerge( - target[prop], src[prop]) : src[prop]; - } - } - } - return target; -} - -module.exports = { - build: function (objects) { - return { - intoButDontClobber: function (target) { - include(target, objects, false, false); - }, - intoAndClobber: function(target) { - include(target, objects, true, false); - }, - intoAndMerge: function(target) { - include(target, objects, true, true); - } - }; - } -}; - -}); - -// file: lib/common/channel.js -define("cordova/channel", function(require, exports, module) { -var utils = require('cordova/utils'); - -/** - * Custom pub-sub "channel" that can have functions subscribed to it - * This object is used to define and control firing of events for - * cordova initialization. - * - * The order of events during page load and Cordova startup is as follows: - * - * onDOMContentLoaded Internal event that is received when the web page is loaded and parsed. - * onNativeReady Internal event that indicates the Cordova native side is ready. - * onCordovaReady Internal event fired when all Cordova JavaScript objects have been created. - * onCordovaInfoReady Internal event fired when device properties are available. - * onCordovaConnectionReady Internal event fired when the connection property has been set. - * onDeviceReady User event fired to indicate that Cordova is ready - * onResume User event fired to indicate a start/resume lifecycle event - * onPause User event fired to indicate a pause lifecycle event - * onDestroy Internal event fired when app is being destroyed (User should use window.onunload event, not this one). - * - * The only Cordova events that user code should register for are: - * deviceready Cordova native code is initialized and Cordova APIs can be called from JavaScript - * pause App has moved to background - * resume App has returned to foreground - * - * Listeners can be registered as: - * document.addEventListener("deviceready", myDeviceReadyListener, false); - * document.addEventListener("resume", myResumeListener, false); - * document.addEventListener("pause", myPauseListener, false); - * - * The DOM lifecycle events should be used for saving and restoring state - * window.onload - * window.onunload - * - */ - -/** - * Channel - * @constructor - * @param type String the channel name - * @param opts Object options to pass into the channel, currently - * supports: - * onSubscribe: callback that fires when - * something subscribes to the Channel. Sets - * context to the Channel. - * onUnsubscribe: callback that fires when - * something unsubscribes to the Channel. Sets - * context to the Channel. - */ -var Channel = function(type, opts) { - this.type = type; - this.handlers = {}; - this.numHandlers = 0; - this.guid = 1; - this.fired = false; - this.enabled = true; - this.events = { - onSubscribe:null, - onUnsubscribe:null - }; - if (opts) { - if (opts.onSubscribe) this.events.onSubscribe = opts.onSubscribe; - if (opts.onUnsubscribe) this.events.onUnsubscribe = opts.onUnsubscribe; - } -}, - channel = { - /** - * Calls the provided function only after all of the channels specified - * have been fired. - */ - join: function (h, c) { - var i = c.length; - var len = i; - var f = function() { - if (!(--i)) h(); - }; - for (var j=0; j} phoneNumbers array of phone numbers -* @param {Array.} emails array of email addresses -* @param {Array.} addresses array of addresses -* @param {Array.} ims instant messaging user ids -* @param {Array.} organizations -* @param {DOMString} birthday contact's birthday -* @param {DOMString} note user notes about contact -* @param {Array.} photos -* @param {Array.} categories -* @param {Array.} urls contact's web sites -*/ -var Contact = function (id, displayName, name, nickname, phoneNumbers, emails, addresses, - ims, organizations, birthday, note, photos, categories, urls) { - this.id = id || null; - this.rawId = null; - this.displayName = displayName || null; - this.name = name || null; // ContactName - this.nickname = nickname || null; - this.phoneNumbers = phoneNumbers || null; // ContactField[] - this.emails = emails || null; // ContactField[] - this.addresses = addresses || null; // ContactAddress[] - this.ims = ims || null; // ContactField[] - this.organizations = organizations || null; // ContactOrganization[] - this.birthday = birthday || null; - this.note = note || null; - this.photos = photos || null; // ContactField[] - this.categories = categories || null; // ContactField[] - this.urls = urls || null; // ContactField[] -}; - -/** -* Removes contact from device storage. -* @param successCB success callback -* @param errorCB error callback -*/ -Contact.prototype.remove = function(successCB, errorCB) { - var fail = function(code) { - errorCB(new ContactError(code)); - }; - if (this.id === null) { - fail(ContactError.UNKNOWN_ERROR); - } - else { - exec(successCB, fail, "Contacts", "remove", [this.id]); - } -}; - -/** -* Creates a deep copy of this Contact. -* With the contact ID set to null. -* @return copy of this Contact -*/ -Contact.prototype.clone = function() { - var clonedContact = utils.clone(this); - var i; - clonedContact.id = null; - clonedContact.rawId = null; - // Loop through and clear out any id's in phones, emails, etc. - if (clonedContact.phoneNumbers) { - for (i = 0; i < clonedContact.phoneNumbers.length; i++) { - clonedContact.phoneNumbers[i].id = null; - } - } - if (clonedContact.emails) { - for (i = 0; i < clonedContact.emails.length; i++) { - clonedContact.emails[i].id = null; - } - } - if (clonedContact.addresses) { - for (i = 0; i < clonedContact.addresses.length; i++) { - clonedContact.addresses[i].id = null; - } - } - if (clonedContact.ims) { - for (i = 0; i < clonedContact.ims.length; i++) { - clonedContact.ims[i].id = null; - } - } - if (clonedContact.organizations) { - for (i = 0; i < clonedContact.organizations.length; i++) { - clonedContact.organizations[i].id = null; - } - } - if (clonedContact.categories) { - for (i = 0; i < clonedContact.categories.length; i++) { - clonedContact.categories[i].id = null; - } - } - if (clonedContact.photos) { - for (i = 0; i < clonedContact.photos.length; i++) { - clonedContact.photos[i].id = null; - } - } - if (clonedContact.urls) { - for (i = 0; i < clonedContact.urls.length; i++) { - clonedContact.urls[i].id = null; - } - } - return clonedContact; -}; - -/** -* Persists contact to device storage. -* @param successCB success callback -* @param errorCB error callback -*/ -Contact.prototype.save = function(successCB, errorCB) { - var fail = function(code) { - errorCB(new ContactError(code)); - }; - var success = function(result) { - if (result) { - if (typeof successCB === 'function') { - var fullContact = require('cordova/plugin/contacts').create(result); - successCB(convertIn(fullContact)); - } - } - else { - // no Entry object returned - fail(ContactError.UNKNOWN_ERROR); - } - }; - var dupContact = convertOut(utils.clone(this)); - exec(success, fail, "Contacts", "save", [dupContact]); -}; - - -module.exports = Contact; - -}); - -// file: lib/common/plugin/ContactAddress.js -define("cordova/plugin/ContactAddress", function(require, exports, module) { -/** -* Contact address. -* @constructor -* @param {DOMString} id unique identifier, should only be set by native code -* @param formatted // NOTE: not a W3C standard -* @param streetAddress -* @param locality -* @param region -* @param postalCode -* @param country -*/ - -var ContactAddress = function(pref, type, formatted, streetAddress, locality, region, postalCode, country) { - this.id = null; - this.pref = (typeof pref != 'undefined' ? pref : false); - this.type = type || null; - this.formatted = formatted || null; - this.streetAddress = streetAddress || null; - this.locality = locality || null; - this.region = region || null; - this.postalCode = postalCode || null; - this.country = country || null; -}; - -module.exports = ContactAddress; -}); - -// file: lib/common/plugin/ContactError.js -define("cordova/plugin/ContactError", function(require, exports, module) { -/** - * ContactError. - * An error code assigned by an implementation when an error has occured - * @constructor - */ -var ContactError = function(err) { - this.code = (typeof err != 'undefined' ? err : null); -}; - -/** - * Error codes - */ -ContactError.UNKNOWN_ERROR = 0; -ContactError.INVALID_ARGUMENT_ERROR = 1; -ContactError.TIMEOUT_ERROR = 2; -ContactError.PENDING_OPERATION_ERROR = 3; -ContactError.IO_ERROR = 4; -ContactError.NOT_SUPPORTED_ERROR = 5; -ContactError.PERMISSION_DENIED_ERROR = 20; - -module.exports = ContactError; -}); - -// file: lib/common/plugin/ContactField.js -define("cordova/plugin/ContactField", function(require, exports, module) { -/** -* Generic contact field. -* @constructor -* @param {DOMString} id unique identifier, should only be set by native code // NOTE: not a W3C standard -* @param type -* @param value -* @param pref -*/ -var ContactField = function(type, value, pref) { - this.id = null; - this.type = (type && type.toString()) || null; - this.value = (value && value.toString()) || null; - this.pref = (typeof pref != 'undefined' ? pref : false); -}; - -module.exports = ContactField; -}); - -// file: lib/common/plugin/ContactFindOptions.js -define("cordova/plugin/ContactFindOptions", function(require, exports, module) { -/** - * ContactFindOptions. - * @constructor - * @param filter used to match contacts against - * @param multiple boolean used to determine if more than one contact should be returned - */ - -var ContactFindOptions = function(filter, multiple) { - this.filter = filter || ''; - this.multiple = (typeof multiple != 'undefined' ? multiple : false); -}; - -module.exports = ContactFindOptions; -}); - -// file: lib/common/plugin/ContactName.js -define("cordova/plugin/ContactName", function(require, exports, module) { -/** -* Contact name. -* @constructor -* @param formatted // NOTE: not part of W3C standard -* @param familyName -* @param givenName -* @param middle -* @param prefix -* @param suffix -*/ -var ContactName = function(formatted, familyName, givenName, middle, prefix, suffix) { - this.formatted = formatted || null; - this.familyName = familyName || null; - this.givenName = givenName || null; - this.middleName = middle || null; - this.honorificPrefix = prefix || null; - this.honorificSuffix = suffix || null; -}; - -module.exports = ContactName; -}); - -// file: lib/common/plugin/ContactOrganization.js -define("cordova/plugin/ContactOrganization", function(require, exports, module) { -/** -* Contact organization. -* @constructor -* @param {DOMString} id unique identifier, should only be set by native code // NOTE: not a W3C standard -* @param name -* @param dept -* @param title -* @param startDate -* @param endDate -* @param location -* @param desc -*/ - -var ContactOrganization = function(pref, type, name, dept, title) { - this.id = null; - this.pref = (typeof pref != 'undefined' ? pref : false); - this.type = type || null; - this.name = name || null; - this.department = dept || null; - this.title = title || null; -}; - -module.exports = ContactOrganization; -}); - -// file: lib/common/plugin/Coordinates.js -define("cordova/plugin/Coordinates", function(require, exports, module) { -/** - * This class contains position information. - * @param {Object} lat - * @param {Object} lng - * @param {Object} alt - * @param {Object} acc - * @param {Object} head - * @param {Object} vel - * @param {Object} altacc - * @constructor - */ -var Coordinates = function(lat, lng, alt, acc, head, vel, altacc) { - /** - * The latitude of the position. - */ - this.latitude = lat; - /** - * The longitude of the position, - */ - this.longitude = lng; - /** - * The accuracy of the position. - */ - this.accuracy = acc; - /** - * The altitude of the position. - */ - this.altitude = (alt !== undefined ? alt : null); - /** - * The direction the device is moving at the position. - */ - this.heading = (head !== undefined ? head : null); - /** - * The velocity with which the device is moving at the position. - */ - this.speed = (vel !== undefined ? vel : null); - - if (this.speed === 0 || this.speed === null) { - this.heading = NaN; - } - - /** - * The altitude accuracy of the position. - */ - this.altitudeAccuracy = (altacc !== undefined) ? altacc : null; -}; - -module.exports = Coordinates; - -}); - -// file: lib/common/plugin/DirectoryEntry.js -define("cordova/plugin/DirectoryEntry", function(require, exports, module) { -var utils = require('cordova/utils'), - exec = require('cordova/exec'), - Entry = require('cordova/plugin/Entry'), - FileError = require('cordova/plugin/FileError'), - DirectoryReader = require('cordova/plugin/DirectoryReader'); - -/** - * An interface representing a directory on the file system. - * - * {boolean} isFile always false (readonly) - * {boolean} isDirectory always true (readonly) - * {DOMString} name of the directory, excluding the path leading to it (readonly) - * {DOMString} fullPath the absolute full path to the directory (readonly) - * {FileSystem} filesystem on which the directory resides (readonly) - */ -var DirectoryEntry = function(name, fullPath) { - DirectoryEntry.__super__.constructor.apply(this, [false, true, name, fullPath]); -}; - -utils.extend(DirectoryEntry, Entry); - -/** - * Creates a new DirectoryReader to read entries from this directory - */ -DirectoryEntry.prototype.createReader = function() { - return new DirectoryReader(this.fullPath); -}; - -/** - * Creates or looks up a directory - * - * @param {DOMString} path either a relative or absolute path from this directory in which to look up or create a directory - * @param {Flags} options to create or excluively create the directory - * @param {Function} successCallback is called with the new entry - * @param {Function} errorCallback is called with a FileError - */ -DirectoryEntry.prototype.getDirectory = function(path, options, successCallback, errorCallback) { - var win = typeof successCallback !== 'function' ? null : function(result) { - var entry = new DirectoryEntry(result.name, result.fullPath); - successCallback(entry); - }; - var fail = typeof errorCallback !== 'function' ? null : function(code) { - errorCallback(new FileError(code)); - }; - exec(win, fail, "File", "getDirectory", [this.fullPath, path, options]); -}; - -/** - * Deletes a directory and all of it's contents - * - * @param {Function} successCallback is called with no parameters - * @param {Function} errorCallback is called with a FileError - */ -DirectoryEntry.prototype.removeRecursively = function(successCallback, errorCallback) { - var fail = typeof errorCallback !== 'function' ? null : function(code) { - errorCallback(new FileError(code)); - }; - exec(successCallback, fail, "File", "removeRecursively", [this.fullPath]); -}; - -/** - * Creates or looks up a file - * - * @param {DOMString} path either a relative or absolute path from this directory in which to look up or create a file - * @param {Flags} options to create or excluively create the file - * @param {Function} successCallback is called with the new entry - * @param {Function} errorCallback is called with a FileError - */ -DirectoryEntry.prototype.getFile = function(path, options, successCallback, errorCallback) { - var win = typeof successCallback !== 'function' ? null : function(result) { - var FileEntry = require('cordova/plugin/FileEntry'); - var entry = new FileEntry(result.name, result.fullPath); - successCallback(entry); - }; - var fail = typeof errorCallback !== 'function' ? null : function(code) { - errorCallback(new FileError(code)); - }; - exec(win, fail, "File", "getFile", [this.fullPath, path, options]); -}; - -module.exports = DirectoryEntry; - -}); - -// file: lib/common/plugin/DirectoryReader.js -define("cordova/plugin/DirectoryReader", function(require, exports, module) { -var exec = require('cordova/exec'), - FileError = require('cordova/plugin/FileError') ; - -/** - * An interface that lists the files and directories in a directory. - */ -function DirectoryReader(path) { - this.path = path || null; -} - -/** - * Returns a list of entries from a directory. - * - * @param {Function} successCallback is called with a list of entries - * @param {Function} errorCallback is called with a FileError - */ -DirectoryReader.prototype.readEntries = function(successCallback, errorCallback) { - var win = typeof successCallback !== 'function' ? null : function(result) { - var retVal = []; - for (var i=0; i][;base64], - * - * @param file {File} File object containing file properties - */ -FileReader.prototype.readAsDataURL = function(file) { - this.fileName = ""; - if (typeof file.fullPath === "undefined") { - this.fileName = file; - } else { - this.fileName = file.fullPath; - } - - // Already loading something - if (this.readyState == FileReader.LOADING) { - throw new FileError(FileError.INVALID_STATE_ERR); - } - - // LOADING state - this.readyState = FileReader.LOADING; - - // If loadstart callback - if (typeof this.onloadstart === "function") { - this.onloadstart(new ProgressEvent("loadstart", {target:this})); - } - - var me = this; - - // Read file - exec( - // Success callback - function(r) { - // If DONE (cancelled), then don't do anything - if (me.readyState === FileReader.DONE) { - return; - } - - // DONE state - me.readyState = FileReader.DONE; - - // Save result - me.result = r; - - // If onload callback - if (typeof me.onload === "function") { - me.onload(new ProgressEvent("load", {target:me})); - } - - // If onloadend callback - if (typeof me.onloadend === "function") { - me.onloadend(new ProgressEvent("loadend", {target:me})); - } - }, - // Error callback - function(e) { - // If DONE (cancelled), then don't do anything - if (me.readyState === FileReader.DONE) { - return; - } - - // DONE state - me.readyState = FileReader.DONE; - - me.result = null; - - // Save error - me.error = new FileError(e); - - // If onerror callback - if (typeof me.onerror === "function") { - me.onerror(new ProgressEvent("error", {target:me})); - } - - // If onloadend callback - if (typeof me.onloadend === "function") { - me.onloadend(new ProgressEvent("loadend", {target:me})); - } - }, "File", "readAsDataURL", [this.fileName]); -}; - -/** - * Read file and return data as a binary data. - * - * @param file {File} File object containing file properties - */ -FileReader.prototype.readAsBinaryString = function(file) { - // TODO - Can't return binary data to browser. - console.log('method "readAsBinaryString" is not supported at this time.'); -}; - -/** - * Read file and return data as a binary data. - * - * @param file {File} File object containing file properties - */ -FileReader.prototype.readAsArrayBuffer = function(file) { - // TODO - Can't return binary data to browser. - console.log('This method is not supported at this time.'); -}; - -module.exports = FileReader; -}); - -// file: lib/common/plugin/FileSystem.js -define("cordova/plugin/FileSystem", function(require, exports, module) { -var DirectoryEntry = require('cordova/plugin/DirectoryEntry'); - -/** - * An interface representing a file system - * - * @constructor - * {DOMString} name the unique name of the file system (readonly) - * {DirectoryEntry} root directory of the file system (readonly) - */ -var FileSystem = function(name, root) { - this.name = name || null; - if (root) { - this.root = new DirectoryEntry(root.name, root.fullPath); - } -}; - -module.exports = FileSystem; -}); - -// file: lib/common/plugin/FileTransfer.js -define("cordova/plugin/FileTransfer", function(require, exports, module) { -var exec = require('cordova/exec'); - -/** - * FileTransfer uploads a file to a remote server. - * @constructor - */ -var FileTransfer = function() {}; - -/** -* Given an absolute file path, uploads a file on the device to a remote server -* using a multipart HTTP request. -* @param filePath {String} Full path of the file on the device -* @param server {String} URL of the server to receive the file -* @param successCallback (Function} Callback to be invoked when upload has completed -* @param errorCallback {Function} Callback to be invoked upon error -* @param options {FileUploadOptions} Optional parameters such as file name and mimetype -* @param trustAllHosts {Boolean} Optional trust all hosts (e.g. for self-signed certs), defaults to false -*/ -FileTransfer.prototype.upload = function(filePath, server, successCallback, errorCallback, options, trustAllHosts) { - // check for options - var fileKey = null; - var fileName = null; - var mimeType = null; - var params = null; - var chunkedMode = true; - if (options) { - fileKey = options.fileKey; - fileName = options.fileName; - mimeType = options.mimeType; - if (options.chunkedMode !== null || typeof options.chunkedMode != "undefined") { - chunkedMode = options.chunkedMode; - } - if (options.params) { - params = options.params; - } - else { - params = {}; - } - } - - exec(successCallback, errorCallback, 'FileTransfer', 'upload', [filePath, server, fileKey, fileName, mimeType, params, trustAllHosts, chunkedMode]); -}; - -/** - * Downloads a file form a given URL and saves it to the specified directory. - * @param source {String} URL of the server to receive the file - * @param target {String} Full path of the file on the device - * @param successCallback (Function} Callback to be invoked when upload has completed - * @param errorCallback {Function} Callback to be invoked upon error - */ -FileTransfer.prototype.download = function(source, target, successCallback, errorCallback) { - var win = function(result) { - var entry = null; - if (result.isDirectory) { - entry = new (require('cordova/plugin/DirectoryEntry'))(); - } - else if (result.isFile) { - entry = new (require('cordova/plugin/FileEntry'))(); - } - entry.isDirectory = result.isDirectory; - entry.isFile = result.isFile; - entry.name = result.name; - entry.fullPath = result.fullPath; - successCallback(entry); - }; - exec(win, errorCallback, 'FileTransfer', 'download', [source, target]); -}; - -module.exports = FileTransfer; - -}); - -// file: lib/common/plugin/FileTransferError.js -define("cordova/plugin/FileTransferError", function(require, exports, module) { -/** - * FileTransferError - * @constructor - */ -var FileTransferError = function(code) { - this.code = code || null; -}; - -FileTransferError.FILE_NOT_FOUND_ERR = 1; -FileTransferError.INVALID_URL_ERR = 2; -FileTransferError.CONNECTION_ERR = 3; - -module.exports = FileTransferError; -}); - -// file: lib/common/plugin/FileUploadOptions.js -define("cordova/plugin/FileUploadOptions", function(require, exports, module) { -/** - * Options to customize the HTTP request used to upload files. - * @constructor - * @param fileKey {String} Name of file request parameter. - * @param fileName {String} Filename to be used by the server. Defaults to image.jpg. - * @param mimeType {String} Mimetype of the uploaded file. Defaults to image/jpeg. - * @param params {Object} Object with key: value params to send to the server. - */ -var FileUploadOptions = function(fileKey, fileName, mimeType, params) { - this.fileKey = fileKey || null; - this.fileName = fileName || null; - this.mimeType = mimeType || null; - this.params = params || null; -}; - -module.exports = FileUploadOptions; -}); - -// file: lib/common/plugin/FileUploadResult.js -define("cordova/plugin/FileUploadResult", function(require, exports, module) { -/** - * FileUploadResult - * @constructor - */ -var FileUploadResult = function() { - this.bytesSent = 0; - this.responseCode = null; - this.response = null; -}; - -module.exports = FileUploadResult; -}); - -// file: lib/common/plugin/FileWriter.js -define("cordova/plugin/FileWriter", function(require, exports, module) { -var exec = require('cordova/exec'), - FileError = require('cordova/plugin/FileError'), - ProgressEvent = require('cordova/plugin/ProgressEvent'); - -/** - * This class writes to the mobile device file system. - * - * For Android: - * The root directory is the root of the file system. - * To write to the SD card, the file name is "sdcard/my_file.txt" - * - * @constructor - * @param file {File} File object containing file properties - * @param append if true write to the end of the file, otherwise overwrite the file - */ -var FileWriter = function(file) { - this.fileName = ""; - this.length = 0; - if (file) { - this.fileName = file.fullPath || file; - this.length = file.size || 0; - } - // default is to write at the beginning of the file - this.position = 0; - - this.readyState = 0; // EMPTY - - this.result = null; - - // Error - this.error = null; - - // Event handlers - this.onwritestart = null; // When writing starts - this.onprogress = null; // While writing the file, and reporting partial file data - this.onwrite = null; // When the write has successfully completed. - this.onwriteend = null; // When the request has completed (either in success or failure). - this.onabort = null; // When the write has been aborted. For instance, by invoking the abort() method. - this.onerror = null; // When the write has failed (see errors). -}; - -// States -FileWriter.INIT = 0; -FileWriter.WRITING = 1; -FileWriter.DONE = 2; - -/** - * Abort writing file. - */ -FileWriter.prototype.abort = function() { - // check for invalid state - if (this.readyState === FileWriter.DONE || this.readyState === FileWriter.INIT) { - throw new FileError(FileError.INVALID_STATE_ERR); - } - - // set error - this.error = new FileError(FileError.ABORT_ERR); - - this.readyState = FileWriter.DONE; - - // If abort callback - if (typeof this.onabort === "function") { - this.onabort(new ProgressEvent("abort", {"target":this})); - } - - // If write end callback - if (typeof this.onwriteend === "function") { - this.onwriteend(new ProgressEvent("writeend", {"target":this})); - } -}; - -/** - * Writes data to the file - * - * @param text to be written - */ -FileWriter.prototype.write = function(text) { - // Throw an exception if we are already writing a file - if (this.readyState === FileWriter.WRITING) { - throw new FileError(FileError.INVALID_STATE_ERR); - } - - // WRITING state - this.readyState = FileWriter.WRITING; - - var me = this; - - // If onwritestart callback - if (typeof me.onwritestart === "function") { - me.onwritestart(new ProgressEvent("writestart", {"target":me})); - } - - // Write file - exec( - // Success callback - function(r) { - // If DONE (cancelled), then don't do anything - if (me.readyState === FileWriter.DONE) { - return; - } - - // position always increases by bytes written because file would be extended - me.position += r; - // The length of the file is now where we are done writing. - - me.length = me.position; - - // DONE state - me.readyState = FileWriter.DONE; - - // If onwrite callback - if (typeof me.onwrite === "function") { - me.onwrite(new ProgressEvent("write", {"target":me})); - } - - // If onwriteend callback - if (typeof me.onwriteend === "function") { - me.onwriteend(new ProgressEvent("writeend", {"target":me})); - } - }, - // Error callback - function(e) { - // If DONE (cancelled), then don't do anything - if (me.readyState === FileWriter.DONE) { - return; - } - - // DONE state - me.readyState = FileWriter.DONE; - - // Save error - me.error = new FileError(e); - - // If onerror callback - if (typeof me.onerror === "function") { - me.onerror(new ProgressEvent("error", {"target":me})); - } - - // If onwriteend callback - if (typeof me.onwriteend === "function") { - me.onwriteend(new ProgressEvent("writeend", {"target":me})); - } - }, "File", "write", [this.fileName, text, this.position]); -}; - -/** - * Moves the file pointer to the location specified. - * - * If the offset is a negative number the position of the file - * pointer is rewound. If the offset is greater than the file - * size the position is set to the end of the file. - * - * @param offset is the location to move the file pointer to. - */ -FileWriter.prototype.seek = function(offset) { - // Throw an exception if we are already writing a file - if (this.readyState === FileWriter.WRITING) { - throw new FileError(FileError.INVALID_STATE_ERR); - } - - if (!offset && offset !== 0) { - return; - } - - // See back from end of file. - if (offset < 0) { - this.position = Math.max(offset + this.length, 0); - } - // Offset is bigger then file size so set position - // to the end of the file. - else if (offset > this.length) { - this.position = this.length; - } - // Offset is between 0 and file size so set the position - // to start writing. - else { - this.position = offset; - } -}; - -/** - * Truncates the file to the size specified. - * - * @param size to chop the file at. - */ -FileWriter.prototype.truncate = function(size) { - // Throw an exception if we are already writing a file - if (this.readyState === FileWriter.WRITING) { - throw new FileError(FileError.INVALID_STATE_ERR); - } - - // WRITING state - this.readyState = FileWriter.WRITING; - - var me = this; - - // If onwritestart callback - if (typeof me.onwritestart === "function") { - me.onwritestart(new ProgressEvent("writestart", {"target":this})); - } - - // Write file - exec( - // Success callback - function(r) { - // If DONE (cancelled), then don't do anything - if (me.readyState === FileWriter.DONE) { - return; - } - - // DONE state - me.readyState = FileWriter.DONE; - - // Update the length of the file - me.length = r; - me.position = Math.min(me.position, r); - - // If onwrite callback - if (typeof me.onwrite === "function") { - me.onwrite(new ProgressEvent("write", {"target":me})); - } - - // If onwriteend callback - if (typeof me.onwriteend === "function") { - me.onwriteend(new ProgressEvent("writeend", {"target":me})); - } - }, - // Error callback - function(e) { - // If DONE (cancelled), then don't do anything - if (me.readyState === FileWriter.DONE) { - return; - } - - // DONE state - me.readyState = FileWriter.DONE; - - // Save error - me.error = new FileError(e); - - // If onerror callback - if (typeof me.onerror === "function") { - me.onerror(new ProgressEvent("error", {"target":me})); - } - - // If onwriteend callback - if (typeof me.onwriteend === "function") { - me.onwriteend(new ProgressEvent("writeend", {"target":me})); - } - }, "File", "truncate", [this.fileName, size]); -}; - -module.exports = FileWriter; - -}); - -// file: lib/common/plugin/Flags.js -define("cordova/plugin/Flags", function(require, exports, module) { -/** - * Supplies arguments to methods that lookup or create files and directories. - * - * @param create - * {boolean} file or directory if it doesn't exist - * @param exclusive - * {boolean} used with create; if true the command will fail if - * target path exists - */ -function Flags(create, exclusive) { - this.create = create || false; - this.exclusive = exclusive || false; -} - -module.exports = Flags; -}); - -// file: lib/common/plugin/LocalFileSystem.js -define("cordova/plugin/LocalFileSystem", function(require, exports, module) { -var exec = require('cordova/exec'); - -/** - * Represents a local file system. - */ -var LocalFileSystem = function() { - -}; - -LocalFileSystem.TEMPORARY = 0; //temporary, with no guarantee of persistence -LocalFileSystem.PERSISTENT = 1; //persistent - -module.exports = LocalFileSystem; -}); - -// file: lib/common/plugin/Media.js -define("cordova/plugin/Media", function(require, exports, module) { -var utils = require('cordova/utils'), - exec = require('cordova/exec'); - -var mediaObjects = {}; - -/** - * This class provides access to the device media, interfaces to both sound and video - * - * @constructor - * @param src The file name or url to play - * @param successCallback The callback to be called when the file is done playing or recording. - * successCallback() - * @param errorCallback The callback to be called if there is an error. - * errorCallback(int errorCode) - OPTIONAL - * @param statusCallback The callback to be called when media status has changed. - * statusCallback(int statusCode) - OPTIONAL - */ -var Media = function(src, successCallback, errorCallback, statusCallback) { - - // successCallback optional - if (successCallback && (typeof successCallback !== "function")) { - console.log("Media Error: successCallback is not a function"); - return; - } - - // errorCallback optional - if (errorCallback && (typeof errorCallback !== "function")) { - console.log("Media Error: errorCallback is not a function"); - return; - } - - // statusCallback optional - if (statusCallback && (typeof statusCallback !== "function")) { - console.log("Media Error: statusCallback is not a function"); - return; - } - - this.id = utils.createUUID(); - mediaObjects[this.id] = this; - this.src = src; - this.successCallback = successCallback; - this.errorCallback = errorCallback; - this.statusCallback = statusCallback; - this._duration = -1; - this._position = -1; - exec(null, this.errorCallback, "Media", "create", [this.id, this.src]); -}; - -// Media messages -Media.MEDIA_STATE = 1; -Media.MEDIA_DURATION = 2; -Media.MEDIA_POSITION = 3; -Media.MEDIA_ERROR = 9; - -// Media states -Media.MEDIA_NONE = 0; -Media.MEDIA_STARTING = 1; -Media.MEDIA_RUNNING = 2; -Media.MEDIA_PAUSED = 3; -Media.MEDIA_STOPPED = 4; -Media.MEDIA_MSG = ["None", "Starting", "Running", "Paused", "Stopped"]; - -// "static" function to return existing objs. -Media.get = function(id) { - return mediaObjects[id]; -}; - -/** - * Start or resume playing audio file. - */ -Media.prototype.play = function(options) { - exec(null, null, "Media", "startPlayingAudio", [this.id, this.src, options]); -}; - -/** - * Stop playing audio file. - */ -Media.prototype.stop = function() { - var me = this; - exec(function() { - me._position = 0; - me.successCallback(); - }, this.errorCallback, "Media", "stopPlayingAudio", [this.id]); -}; - -/** - * Seek or jump to a new time in the track.. - */ -Media.prototype.seekTo = function(milliseconds) { - var me = this; - exec(function(p) { - me._position = p; - }, this.errorCallback, "Media", "seekToAudio", [this.id, milliseconds]); -}; - -/** - * Pause playing audio file. - */ -Media.prototype.pause = function() { - exec(null, this.errorCallback, "Media", "pausePlayingAudio", [this.id]); -}; - -/** - * Get duration of an audio file. - * The duration is only set for audio that is playing, paused or stopped. - * - * @return duration or -1 if not known. - */ -Media.prototype.getDuration = function() { - return this._duration; -}; - -/** - * Get position of audio. - */ -Media.prototype.getCurrentPosition = function(success, fail) { - var me = this; - exec(function(p) { - me._position = p; - success(p); - }, fail, "Media", "getCurrentPositionAudio", [this.id]); -}; - -/** - * Start recording audio file. - */ -Media.prototype.startRecord = function() { - exec(this.successCallback, this.errorCallback, "Media", "startRecordingAudio", [this.id, this.src]); -}; - -/** - * Stop recording audio file. - */ -Media.prototype.stopRecord = function() { - exec(this.successCallback, this.errorCallback, "Media", "stopRecordingAudio", [this.id]); -}; - -/** - * Release the resources. - */ -Media.prototype.release = function() { - exec(null, this.errorCallback, "Media", "release", [this.id]); -}; - -/** - * Adjust the volume. - */ -Media.prototype.setVolume = function(volume) { - exec(null, null, "Media", "setVolume", [this.id, volume]); -}; - -/** - * Audio has status update. - * PRIVATE - * - * @param id The media object id (string) - * @param status The status code (int) - * @param msg The status message (string) - */ -Media.onStatus = function(id, msg, value) { - var media = mediaObjects[id]; - // If state update - if (msg === Media.MEDIA_STATE) { - if (value === Media.MEDIA_STOPPED) { - if (media.successCallback) { - media.successCallback(); - } - } - if (media.statusCallback) { - media.statusCallback(value); - } - } - else if (msg === Media.MEDIA_DURATION) { - media._duration = value; - } - else if (msg === Media.MEDIA_ERROR) { - if (media.errorCallback) { - // value should be a MediaError object when msg == MEDIA_ERROR - media.errorCallback(value); - } - } - else if (msg === Media.MEDIA_POSITION) { - media._position = value; - } -}; - -module.exports = Media; -}); - -// file: lib/common/plugin/MediaError.js -define("cordova/plugin/MediaError", function(require, exports, module) { -/** - * This class contains information about any Media errors. - * @constructor - */ -var MediaError = function(code, msg) { - this.code = (code !== undefined ? code : null); - this.message = msg || ""; -}; - -MediaError.MEDIA_ERR_NONE_ACTIVE = 0; -MediaError.MEDIA_ERR_ABORTED = 1; -MediaError.MEDIA_ERR_NETWORK = 2; -MediaError.MEDIA_ERR_DECODE = 3; -MediaError.MEDIA_ERR_NONE_SUPPORTED = 4; - -module.exports = MediaError; -}); - -// file: lib/common/plugin/MediaFile.js -define("cordova/plugin/MediaFile", function(require, exports, module) { -var utils = require('cordova/utils'), - exec = require('cordova/exec'), - File = require('cordova/plugin/File'), - CaptureError = require('cordova/plugin/CaptureError'); -/** - * Represents a single file. - * - * name {DOMString} name of the file, without path information - * fullPath {DOMString} the full path of the file, including the name - * type {DOMString} mime type - * lastModifiedDate {Date} last modified date - * size {Number} size of the file in bytes - */ -var MediaFile = function(name, fullPath, type, lastModifiedDate, size){ - MediaFile.__super__.constructor.apply(this, arguments); -}; - -utils.extend(MediaFile, File); - -/** - * Request capture format data for a specific file and type - * - * @param {Function} successCB - * @param {Function} errorCB - */ -MediaFile.prototype.getFormatData = function(successCallback, errorCallback) { - if (typeof this.fullPath === "undefined" || this.fullPath === null) { - errorCallback(new CaptureError(CaptureError.CAPTURE_INVALID_ARGUMENT)); - } else { - exec(successCallback, errorCallback, "Capture", "getFormatData", [this.fullPath, this.type]); - } -}; - -// TODO: can we axe this? -/** - * Casts a PluginResult message property (array of objects) to an array of MediaFile objects - * (used in Objective-C and Android) - * - * @param {PluginResult} pluginResult - */ -MediaFile.cast = function(pluginResult) { - var mediaFiles = []; - for (var i=0; i.dispatchEvent - // need to first figure out how to implement EventTarget - } - } - return event; - }; - try { - var ev = createEvent({type:"abort",target:document}); - return function ProgressEvent(type, data) { - data.type = type; - return createEvent(data); - }; - } catch(e){ - */ - return function ProgressEvent(type, dict) { - this.type = type; - this.bubbles = false; - this.cancelBubble = false; - this.cancelable = false; - this.lengthComputable = false; - this.loaded = dict && dict.loaded ? dict.loaded : 0; - this.total = dict && dict.total ? dict.total : 0; - this.target = dict && dict.target ? dict.target : null; - }; - //} -})(); - -module.exports = ProgressEvent; -}); - -// file: lib/common/plugin/accelerometer.js -define("cordova/plugin/accelerometer", function(require, exports, module) { -/** - * This class provides access to device accelerometer data. - * @constructor - */ -var utils = require("cordova/utils"), - exec = require("cordova/exec"), - Acceleration = require('cordova/plugin/Acceleration'); - -// Is the accel sensor running? -var running = false; - -// Keeps reference to watchAcceleration calls. -var timers = {}; - -// Array of listeners; used to keep track of when we should call start and stop. -var listeners = []; - -// Last returned acceleration object from native -var accel = null; - -// Tells native to start. -function start() { - exec(function(a) { - var tempListeners = listeners.slice(0); - accel = new Acceleration(a.x, a.y, a.z, a.timestamp); - for (var i = 0, l = tempListeners.length; i < l; i++) { - tempListeners[i].win(accel); - } - }, function(e) { - var tempListeners = listeners.slice(0); - for (var i = 0, l = tempListeners.length; i < l; i++) { - tempListeners[i].fail(e); - } - }, "Accelerometer", "start", []); - running = true; -} - -// Tells native to stop. -function stop() { - exec(null, null, "Accelerometer", "stop", []); - running = false; -} - -// Adds a callback pair to the listeners array -function createCallbackPair(win, fail) { - return {win:win, fail:fail}; -} - -// Removes a win/fail listener pair from the listeners array -function removeListeners(l) { - var idx = listeners.indexOf(l); - if (idx > -1) { - listeners.splice(idx, 1); - if (listeners.length === 0) { - stop(); - } - } -} - -var accelerometer = { - /** - * Asynchronously aquires the current acceleration. - * - * @param {Function} successCallback The function to call when the acceleration data is available - * @param {Function} errorCallback The function to call when there is an error getting the acceleration data. (OPTIONAL) - * @param {AccelerationOptions} options The options for getting the accelerometer data such as timeout. (OPTIONAL) - */ - getCurrentAcceleration: function(successCallback, errorCallback, options) { - // successCallback required - if (typeof successCallback !== "function") { - throw "getCurrentAcceleration must be called with at least a success callback function as first parameter."; - } - - var p; - var win = function(a) { - successCallback(a); - removeListeners(p); - }; - var fail = function(e) { - errorCallback(e); - removeListeners(p); - }; - - p = createCallbackPair(win, fail); - listeners.push(p); - - if (!running) { - start(); - } - }, - - /** - * Asynchronously aquires the acceleration repeatedly at a given interval. - * - * @param {Function} successCallback The function to call each time the acceleration data is available - * @param {Function} errorCallback The function to call when there is an error getting the acceleration data. (OPTIONAL) - * @param {AccelerationOptions} options The options for getting the accelerometer data such as timeout. (OPTIONAL) - * @return String The watch id that must be passed to #clearWatch to stop watching. - */ - watchAcceleration: function(successCallback, errorCallback, options) { - // Default interval (10 sec) - var frequency = (options && options.frequency && typeof options.frequency == 'number') ? options.frequency : 10000; - - // successCallback required - if (typeof successCallback !== "function") { - throw "watchAcceleration must be called with at least a success callback function as first parameter."; - } - - // Keep reference to watch id, and report accel readings as often as defined in frequency - var id = utils.createUUID(); - - var p = createCallbackPair(function(){}, function(e) { - errorCallback(e); - removeListeners(p); - }); - listeners.push(p); - - timers[id] = { - timer:window.setInterval(function() { - if (accel) { - successCallback(accel); - } - }, frequency), - listeners:p - }; - - if (running) { - // If we're already running then immediately invoke the success callback - successCallback(accel); - } else { - start(); - } - - return id; - }, - - /** - * Clears the specified accelerometer watch. - * - * @param {String} id The id of the watch returned from #watchAcceleration. - */ - clearWatch: function(id) { - // Stop javascript timer & remove from timer list - if (id && timers[id]) { - window.clearInterval(timers[id].timer); - removeListeners(timers[id].listeners); - delete timers[id]; - } - } -}; - -module.exports = accelerometer; - -}); - -// file: lib/common/plugin/battery.js -define("cordova/plugin/battery", function(require, exports, module) { -/** - * This class contains information about the current battery status. - * @constructor - */ -var cordova = require('cordova'), - exec = require('cordova/exec'); - -function handlers() { - return battery.channels.batterystatus.numHandlers + - battery.channels.batterylow.numHandlers + - battery.channels.batterycritical.numHandlers; -} - -var Battery = function() { - this._level = null; - this._isPlugged = null; - // Create new event handlers on the window (returns a channel instance) - var subscriptionEvents = { - onSubscribe:this.onSubscribe, - onUnsubscribe:this.onUnsubscribe - }; - this.channels = { - batterystatus:cordova.addWindowEventHandler("batterystatus", subscriptionEvents), - batterylow:cordova.addWindowEventHandler("batterylow", subscriptionEvents), - batterycritical:cordova.addWindowEventHandler("batterycritical", subscriptionEvents) - }; -}; -/** - * Event handlers for when callbacks get registered for the battery. - * Keep track of how many handlers we have so we can start and stop the native battery listener - * appropriately (and hopefully save on battery life!). - */ -Battery.prototype.onSubscribe = function() { - var me = battery; - // If we just registered the first handler, make sure native listener is started. - if (handlers() === 1) { - exec(me._status, me._error, "Battery", "start", []); - } -}; - -Battery.prototype.onUnsubscribe = function() { - var me = battery; - - // If we just unregistered the last handler, make sure native listener is stopped. - if (handlers() === 0) { - exec(null, null, "Battery", "stop", []); - } -}; - -/** - * Callback for battery status - * - * @param {Object} info keys: level, isPlugged - */ -Battery.prototype._status = function(info) { - if (info) { - var me = battery; - var level = info.level; - if (me._level !== level || me._isPlugged !== info.isPlugged) { - // Fire batterystatus event - cordova.fireWindowEvent("batterystatus", info); - - // Fire low battery event - if (level === 20 || level === 5) { - if (level === 20) { - cordova.fireWindowEvent("batterylow", info); - } - else { - cordova.fireWindowEvent("batterycritical", info); - } - } - } - me._level = level; - me._isPlugged = info.isPlugged; - } -}; - -/** - * Error callback for battery start - */ -Battery.prototype._error = function(e) { - console.log("Error initializing Battery: " + e); -}; - -var battery = new Battery(); - -module.exports = battery; -}); - -// file: lib/common/plugin/capture.js -define("cordova/plugin/capture", function(require, exports, module) { -var exec = require('cordova/exec'), - MediaFile = require('cordova/plugin/MediaFile'); - -/** - * Launches a capture of different types. - * - * @param (DOMString} type - * @param {Function} successCB - * @param {Function} errorCB - * @param {CaptureVideoOptions} options - */ -function _capture(type, successCallback, errorCallback, options) { - var win = function(pluginResult) { - var mediaFiles = []; - var i; - for (i = 0; i < pluginResult.length; i++) { - var mediaFile = new MediaFile(); - mediaFile.name = pluginResult[i].name; - mediaFile.fullPath = pluginResult[i].fullPath; - mediaFile.type = pluginResult[i].type; - mediaFile.lastModifiedDate = pluginResult[i].lastModifiedDate; - mediaFile.size = pluginResult[i].size; - mediaFiles.push(mediaFile); - } - successCallback(mediaFiles); - }; - exec(win, errorCallback, "Capture", type, [options]); -} -/** - * The Capture interface exposes an interface to the camera and microphone of the hosting device. - */ -function Capture() { - this.supportedAudioModes = []; - this.supportedImageModes = []; - this.supportedVideoModes = []; -} - -/** - * Launch audio recorder application for recording audio clip(s). - * - * @param {Function} successCB - * @param {Function} errorCB - * @param {CaptureAudioOptions} options - */ -Capture.prototype.captureAudio = function(successCallback, errorCallback, options){ - _capture("captureAudio", successCallback, errorCallback, options); -}; - -/** - * Launch camera application for taking image(s). - * - * @param {Function} successCB - * @param {Function} errorCB - * @param {CaptureImageOptions} options - */ -Capture.prototype.captureImage = function(successCallback, errorCallback, options){ - _capture("captureImage", successCallback, errorCallback, options); -}; - -/** - * Launch device camera application for recording video(s). - * - * @param {Function} successCB - * @param {Function} errorCB - * @param {CaptureVideoOptions} options - */ -Capture.prototype.captureVideo = function(successCallback, errorCallback, options){ - _capture("captureVideo", successCallback, errorCallback, options); -}; - - -module.exports = new Capture(); - -}); - -// file: lib/common/plugin/compass.js -define("cordova/plugin/compass", function(require, exports, module) { -var exec = require('cordova/exec'), - utils = require('cordova/utils'), - CompassHeading = require('cordova/plugin/CompassHeading'), - CompassError = require('cordova/plugin/CompassError'), - timers = {}, - compass = { - /** - * Asynchronously acquires the current heading. - * @param {Function} successCallback The function to call when the heading - * data is available - * @param {Function} errorCallback The function to call when there is an error - * getting the heading data. - * @param {CompassOptions} options The options for getting the heading data (not used). - */ - getCurrentHeading:function(successCallback, errorCallback, options) { - // successCallback required - if (typeof successCallback !== "function") { - console.log("Compass Error: successCallback is not a function"); - return; - } - - // errorCallback optional - if (errorCallback && (typeof errorCallback !== "function")) { - console.log("Compass Error: errorCallback is not a function"); - return; - } - - var win = function(result) { - var ch = new CompassHeading(result.magneticHeading, result.trueHeading, result.headingAccuracy, result.timestamp); - successCallback(ch); - }; - var fail = function(code) { - var ce = new CompassError(code); - errorCallback(ce); - }; - - // Get heading - exec(win, fail, "Compass", "getHeading", [options]); - }, - - /** - * Asynchronously acquires the heading repeatedly at a given interval. - * @param {Function} successCallback The function to call each time the heading - * data is available - * @param {Function} errorCallback The function to call when there is an error - * getting the heading data. - * @param {HeadingOptions} options The options for getting the heading data - * such as timeout and the frequency of the watch. For iOS, filter parameter - * specifies to watch via a distance filter rather than time. - */ - watchHeading:function(successCallback, errorCallback, options) { - // Default interval (100 msec) - var frequency = (options !== undefined && options.frequency !== undefined) ? options.frequency : 100; - var filter = (options !== undefined && options.filter !== undefined) ? options.filter : 0; - - // successCallback required - if (typeof successCallback !== "function") { - console.log("Compass Error: successCallback is not a function"); - return; - } - - // errorCallback optional - if (errorCallback && (typeof errorCallback !== "function")) { - console.log("Compass Error: errorCallback is not a function"); - return; - } - - var id = utils.createUUID(); - if (filter > 0) { - // is an iOS request for watch by filter, no timer needed - timers[id] = "iOS"; - compass.getCurrentHeading(successCallback, errorCallback, options); - } else { - // Start watch timer to get headings - timers[id] = window.setInterval(function() { - compass.getCurrentHeading(successCallback, errorCallback); - }, frequency); - } - - return id; - }, - - /** - * Clears the specified heading watch. - * @param {String} watchId The ID of the watch returned from #watchHeading. - */ - clearWatch:function(id) { - // Stop javascript timer & remove from timer list - if (id && timers[id]) { - if (timers[id] != "iOS") { - clearInterval(timers[id]); - } else { - // is iOS watch by filter so call into device to stop - exec(null, null, "Compass", "stopHeading", []); - } - delete timers[id]; - } - } - }; - -module.exports = compass; -}); - -// file: lib/common/plugin/console-via-logger.js -define("cordova/plugin/console-via-logger", function(require, exports, module) { -//------------------------------------------------------------------------------ - -var logger = require("cordova/plugin/logger"); -var utils = require("cordova/utils"); - -//------------------------------------------------------------------------------ -// object that we're exporting -//------------------------------------------------------------------------------ -var console = module.exports; - -//------------------------------------------------------------------------------ -// copy of the original console object -//------------------------------------------------------------------------------ -var WinConsole = window.console; - -//------------------------------------------------------------------------------ -// whether to use the logger -//------------------------------------------------------------------------------ -var UseLogger = false; - -//------------------------------------------------------------------------------ -// Timers -//------------------------------------------------------------------------------ -var Timers = {}; - -//------------------------------------------------------------------------------ -// used for unimplemented methods -//------------------------------------------------------------------------------ -function noop() {} - -//------------------------------------------------------------------------------ -// used for unimplemented methods -//------------------------------------------------------------------------------ -console.useLogger = function (value) { - if (arguments.length) UseLogger = !!value; - - if (UseLogger) { - if (logger.useConsole()) { - throw new Error("console and logger are too intertwingly"); - } - } - - return UseLogger; -}; - -//------------------------------------------------------------------------------ -console.log = function() { - if (logger.useConsole()) return; - logger.log.apply(logger, [].slice.call(arguments)); -}; - -//------------------------------------------------------------------------------ -console.error = function() { - if (logger.useConsole()) return; - logger.error.apply(logger, [].slice.call(arguments)); -}; - -//------------------------------------------------------------------------------ -console.warn = function() { - if (logger.useConsole()) return; - logger.warn.apply(logger, [].slice.call(arguments)); -}; - -//------------------------------------------------------------------------------ -console.info = function() { - if (logger.useConsole()) return; - logger.info.apply(logger, [].slice.call(arguments)); -}; - -//------------------------------------------------------------------------------ -console.debug = function() { - if (logger.useConsole()) return; - logger.debug.apply(logger, [].slice.call(arguments)); -}; - -//------------------------------------------------------------------------------ -console.assert = function(expression) { - if (expression) return; - - var message = utils.vformat(arguments[1], [].slice.call(arguments, 2)); - console.log("ASSERT: " + message); -}; - -//------------------------------------------------------------------------------ -console.clear = function() {}; - -//------------------------------------------------------------------------------ -console.dir = function(object) { - console.log("%o", object); -}; - -//------------------------------------------------------------------------------ -console.dirxml = function(node) { - console.log(node.innerHTML); -}; - -//------------------------------------------------------------------------------ -console.trace = noop; - -//------------------------------------------------------------------------------ -console.group = console.log; - -//------------------------------------------------------------------------------ -console.groupCollapsed = console.log; - -//------------------------------------------------------------------------------ -console.groupEnd = noop; - -//------------------------------------------------------------------------------ -console.time = function(name) { - Timers[name] = new Date().valueOf(); -}; - -//------------------------------------------------------------------------------ -console.timeEnd = function(name) { - var timeStart = Timers[name]; - if (!timeStart) { - console.warn("unknown timer: " + name); - return; - } - - var timeElapsed = new Date().valueOf() - timeStart; - console.log(name + ": " + timeElapsed + "ms"); -}; - -//------------------------------------------------------------------------------ -console.timeStamp = noop; - -//------------------------------------------------------------------------------ -console.profile = noop; - -//------------------------------------------------------------------------------ -console.profileEnd = noop; - -//------------------------------------------------------------------------------ -console.count = noop; - -//------------------------------------------------------------------------------ -console.exception = console.log; - -//------------------------------------------------------------------------------ -console.table = function(data, columns) { - console.log("%o", data); -}; - -//------------------------------------------------------------------------------ -// return a new function that calls both functions passed as args -//------------------------------------------------------------------------------ -function wrapperedOrigCall(orgFunc, newFunc) { - return function() { - var args = [].slice.call(arguments); - try { orgFunc.apply(WinConsole, args); } catch (e) {} - try { newFunc.apply(console, args); } catch (e) {} - }; -} - -//------------------------------------------------------------------------------ -// For every function that exists in the original console object, that -// also exists in the new console object, wrap the new console method -// with one that calls both -//------------------------------------------------------------------------------ -for (var key in console) { - if (typeof WinConsole[key] == "function") { - console[key] = wrapperedOrigCall(WinConsole[key], console[key]); - } -} - -}); - -// file: lib/common/plugin/contacts.js -define("cordova/plugin/contacts", function(require, exports, module) { -var exec = require('cordova/exec'), - ContactError = require('cordova/plugin/ContactError'), - utils = require('cordova/utils'), - Contact = require('cordova/plugin/Contact'); - -/** -* Represents a group of Contacts. -* @constructor -*/ -var contacts = { - /** - * Returns an array of Contacts matching the search criteria. - * @param fields that should be searched - * @param successCB success callback - * @param errorCB error callback - * @param {ContactFindOptions} options that can be applied to contact searching - * @return array of Contacts matching search criteria - */ - find:function(fields, successCB, errorCB, options) { - if (!successCB) { - throw new TypeError("You must specify a success callback for the find command."); - } - if (!fields || (utils.isArray(fields) && fields.length === 0)) { - if (typeof errorCB === "function") { - errorCB(new ContactError(ContactError.INVALID_ARGUMENT_ERROR)); - } - } else { - var win = function(result) { - var cs = []; - for (var i = 0, l = result.length; i < l; i++) { - cs.push(contacts.create(result[i])); - } - successCB(cs); - }; - exec(win, errorCB, "Contacts", "search", [fields, options]); - } - }, - - /** - * This function creates a new contact, but it does not persist the contact - * to device storage. To persist the contact to device storage, invoke - * contact.save(). - * @param properties an object who's properties will be examined to create a new Contact - * @returns new Contact object - */ - create:function(properties) { - var i; - var contact = new Contact(); - for (i in properties) { - if (typeof contact[i] !== 'undefined' && properties.hasOwnProperty(i)) { - contact[i] = properties[i]; - } - } - return contact; - } -}; - -module.exports = contacts; - -}); - -// file: lib/common/plugin/geolocation.js -define("cordova/plugin/geolocation", function(require, exports, module) { -var utils = require('cordova/utils'), - exec = require('cordova/exec'), - PositionError = require('cordova/plugin/PositionError'), - Position = require('cordova/plugin/Position'); - -var timers = {}; // list of timers in use - -// Returns default params, overrides if provided with values -function parseParameters(options) { - var opt = { - maximumAge: 0, - enableHighAccuracy: false, - timeout: Infinity - }; - - if (options) { - if (options.maximumAge !== undefined && !isNaN(options.maximumAge) && options.maximumAge > 0) { - opt.maximumAge = options.maximumAge; - } - if (options.enableHighAccuracy !== undefined) { - opt.enableHighAccuracy = options.enableHighAccuracy; - } - if (options.timeout !== undefined && !isNaN(options.timeout)) { - if (options.timeout < 0) { - opt.timeout = 0; - } else { - opt.timeout = options.timeout; - } - } - } - - return opt; -} - -// Returns a timeout failure, closed over a specified timeout value and error callback. -function createTimeout(errorCallback, timeout) { - var t = setTimeout(function() { - clearTimeout(t); - t = null; - errorCallback({ - code:PositionError.TIMEOUT, - message:"Position retrieval timed out." - }); - }, timeout); - return t; -} - -var geolocation = { - lastPosition:null, // reference to last known (cached) position returned - /** - * Asynchronously aquires the current position. - * - * @param {Function} successCallback The function to call when the position data is available - * @param {Function} errorCallback The function to call when there is an error getting the heading position. (OPTIONAL) - * @param {PositionOptions} options The options for getting the position data. (OPTIONAL) - */ - getCurrentPosition:function(successCallback, errorCallback, options) { - if (arguments.length === 0) { - throw new Error("getCurrentPosition must be called with at least one argument."); - } - options = parseParameters(options); - - // Timer var that will fire an error callback if no position is retrieved from native - // before the "timeout" param provided expires - var timeoutTimer = null; - - var win = function(p) { - clearTimeout(timeoutTimer); - if (!timeoutTimer) { - // Timeout already happened, or native fired error callback for - // this geo request. - // Don't continue with success callback. - return; - } - var pos = new Position( - { - latitude:p.latitude, - longitude:p.longitude, - altitude:p.altitude, - accuracy:p.accuracy, - heading:p.heading, - velocity:p.velocity, - altitudeAccuracy:p.altitudeAccuracy - }, - (p.timestamp === undefined ? new Date() : ((p.timestamp instanceof Date) ? p.timestamp : new Date(p.timestamp))) - ); - geolocation.lastPosition = pos; - successCallback(pos); - }; - var fail = function(e) { - clearTimeout(timeoutTimer); - timeoutTimer = null; - var err = new PositionError(e.code, e.message); - if (errorCallback) { - errorCallback(err); - } - }; - - // Check our cached position, if its timestamp difference with current time is less than the maximumAge, then just - // fire the success callback with the cached position. - if (geolocation.lastPosition && options.maximumAge && (((new Date()).getTime() - geolocation.lastPosition.timestamp.getTime()) <= options.maximumAge)) { - successCallback(geolocation.lastPosition); - // If the cached position check failed and the timeout was set to 0, error out with a TIMEOUT error object. - } else if (options.timeout === 0) { - fail({ - code:PositionError.TIMEOUT, - message:"timeout value in PositionOptions set to 0 and no cached Position object available, or cached Position object's age exceed's provided PositionOptions' maximumAge parameter." - }); - // Otherwise we have to call into native to retrieve a position. - } else { - if (options.timeout !== Infinity) { - // If the timeout value was not set to Infinity (default), then - // set up a timeout function that will fire the error callback - // if no successful position was retrieved before timeout expired. - timeoutTimer = createTimeout(fail, options.timeout); - } else { - // This is here so the check in the win function doesn't mess stuff up - // may seem weird but this guarantees timeoutTimer is - // always truthy before we call into native - timeoutTimer = true; - } - exec(win, fail, "Geolocation", "getLocation", [options.enableHighAccuracy, options.maximumAge]); - } - return timeoutTimer; - }, - /** - * Asynchronously watches the geolocation for changes to geolocation. When a change occurs, - * the successCallback is called with the new location. - * - * @param {Function} successCallback The function to call each time the location data is available - * @param {Function} errorCallback The function to call when there is an error getting the location data. (OPTIONAL) - * @param {PositionOptions} options The options for getting the location data such as frequency. (OPTIONAL) - * @return String The watch id that must be passed to #clearWatch to stop watching. - */ - watchPosition:function(successCallback, errorCallback, options) { - if (arguments.length === 0) { - throw new Error("watchPosition must be called with at least one argument."); - } - options = parseParameters(options); - - var id = utils.createUUID(); - - // Tell device to get a position ASAP, and also retrieve a reference to the timeout timer generated in getCurrentPosition - timers[id] = geolocation.getCurrentPosition(successCallback, errorCallback, options); - - var fail = function(e) { - clearTimeout(timers[id]); - var err = new PositionError(e.code, e.message); - if (errorCallback) { - errorCallback(err); - } - }; - - var win = function(p) { - clearTimeout(timers[id]); - if (options.timeout !== Infinity) { - timers[id] = createTimeout(fail, options.timeout); - } - var pos = new Position( - { - latitude:p.latitude, - longitude:p.longitude, - altitude:p.altitude, - accuracy:p.accuracy, - heading:p.heading, - velocity:p.velocity, - altitudeAccuracy:p.altitudeAccuracy - }, - (p.timestamp === undefined ? new Date() : ((p.timestamp instanceof Date) ? p.timestamp : new Date(p.timestamp))) - ); - geolocation.lastPosition = pos; - successCallback(pos); - }; - - exec(win, fail, "Geolocation", "addWatch", [id, options.enableHighAccuracy]); - - return id; - }, - /** - * Clears the specified heading watch. - * - * @param {String} id The ID of the watch returned from #watchPosition - */ - clearWatch:function(id) { - if (id && timers[id] !== undefined) { - clearTimeout(timers[id]); - delete timers[id]; - exec(null, null, "Geolocation", "clearWatch", [id]); - } - } -}; - -module.exports = geolocation; - -}); - -// file: lib/ios/plugin/ios/Contact.js -define("cordova/plugin/ios/Contact", function(require, exports, module) { -var exec = require('cordova/exec'), - ContactError = require('cordova/plugin/ContactError'); - -/** - * Provides iOS Contact.display API. - */ -module.exports = { - display : function(errorCB, options) { - /* - * Display a contact using the iOS Contact Picker UI - * NOT part of W3C spec so no official documentation - * - * @param errorCB error callback - * @param options object - * allowsEditing: boolean AS STRING - * "true" to allow editing the contact - * "false" (default) display contact - */ - - if (this.id === null) { - if (typeof errorCB === "function") { - var errorObj = new ContactError(ContactError.UNKNOWN_ERROR); - errorCB(errorObj); - } - } - else { - exec(null, errorCB, "Contacts","displayContact", [this.id, options]); - } - } -}; -}); - -// file: lib/ios/plugin/ios/Entry.js -define("cordova/plugin/ios/Entry", function(require, exports, module) { -module.exports = { - toURL:function() { - // TODO: refactor path in a cross-platform way so we can eliminate - // these kinds of platform-specific hacks. - return "file://localhost" + this.fullPath; - }, - toURI: function() { - console.log("DEPRECATED: Update your code to use 'toURL'"); - return "file://localhost" + this.fullPath; - } -}; -}); - -// file: lib/ios/plugin/ios/FileReader.js -define("cordova/plugin/ios/FileReader", function(require, exports, module) { -var exec = require('cordova/exec'), - FileError = require('cordova/plugin/FileError'), - FileReader = require('cordova/plugin/FileReader'), - ProgressEvent = require('cordova/plugin/ProgressEvent'); - -module.exports = { - readAsText:function(file, encoding) { - // Figure out pathing - this.fileName = ''; - if (typeof file.fullPath === 'undefined') { - this.fileName = file; - } else { - this.fileName = file.fullPath; - } - - // Already loading something - if (this.readyState == FileReader.LOADING) { - throw new FileError(FileError.INVALID_STATE_ERR); - } - - // LOADING state - this.readyState = FileReader.LOADING; - - // If loadstart callback - if (typeof this.onloadstart === "function") { - this.onloadstart(new ProgressEvent("loadstart", {target:this})); - } - - // Default encoding is UTF-8 - var enc = encoding ? encoding : "UTF-8"; - - var me = this; - - // Read file - exec( - // Success callback - function(r) { - // If DONE (cancelled), then don't do anything - if (me.readyState === FileReader.DONE) { - return; - } - - // Save result - me.result = decodeURIComponent(r); - - // If onload callback - if (typeof me.onload === "function") { - me.onload(new ProgressEvent("load", {target:me})); - } - - // DONE state - me.readyState = FileReader.DONE; - - // If onloadend callback - if (typeof me.onloadend === "function") { - me.onloadend(new ProgressEvent("loadend", {target:me})); - } - }, - // Error callback - function(e) { - // If DONE (cancelled), then don't do anything - if (me.readyState === FileReader.DONE) { - return; - } - - // DONE state - me.readyState = FileReader.DONE; - - // null result - me.result = null; - - // Save error - me.error = new FileError(e); - - // If onerror callback - if (typeof me.onerror === "function") { - me.onerror(new ProgressEvent("error", {target:me})); - } - - // If onloadend callback - if (typeof me.onloadend === "function") { - me.onloadend(new ProgressEvent("loadend", {target:me})); - } - }, - "File", "readAsText", [this.fileName, enc]); - } -}; -}); - -// file: lib/ios/plugin/ios/console.js -define("cordova/plugin/ios/console", function(require, exports, module) { -var exec = require('cordova/exec'); - -/** - * This class provides access to the debugging console. - * @constructor - */ -var DebugConsole = function() { - this.winConsole = window.console; - this.logLevel = DebugConsole.INFO_LEVEL; -}; - -// from most verbose, to least verbose -DebugConsole.ALL_LEVEL = 1; // same as first level -DebugConsole.INFO_LEVEL = 1; -DebugConsole.WARN_LEVEL = 2; -DebugConsole.ERROR_LEVEL = 4; -DebugConsole.NONE_LEVEL = 8; - -DebugConsole.prototype.setLevel = function(level) { - this.logLevel = level; -}; - -var stringify = function(message) { - try { - if (typeof message === "object" && JSON && JSON.stringify) { - try { - return JSON.stringify(message); - } - catch (e) { - return "error JSON.stringify()ing argument: " + e; - } - } else { - return message.toString(); - } - } catch (e) { - return e.toString(); - } -}; - -/** - * Print a normal log message to the console - * @param {Object|String} message Message or object to print to the console - */ -DebugConsole.prototype.log = function(message) { - if (this.logLevel <= DebugConsole.INFO_LEVEL) { - exec(null, null, 'Debug Console', 'log', [ stringify(message), { logLevel: 'INFO' } ]); - } - else if (this.winConsole && this.winConsole.log) { - this.winConsole.log(message); - } -}; - -/** - * Print a warning message to the console - * @param {Object|String} message Message or object to print to the console - */ -DebugConsole.prototype.warn = function(message) { - if (this.logLevel <= DebugConsole.WARN_LEVEL) { - exec(null, null, 'Debug Console', 'log', [ stringify(message), { logLevel: 'WARN' } ]); - } - else if (this.winConsole && this.winConsole.warn) { - this.winConsole.warn(message); - } -}; - -/** - * Print an error message to the console - * @param {Object|String} message Message or object to print to the console - */ -DebugConsole.prototype.error = function(message) { - if (this.logLevel <= DebugConsole.ERROR_LEVEL) { - exec(null, null, 'Debug Console', 'log', [ stringify(message), { logLevel: 'ERROR' } ]); - } - else if (this.winConsole && this.winConsole.error){ - this.winConsole.error(message); - } -}; - -module.exports = new DebugConsole(); -}); - -// file: lib/ios/plugin/ios/contacts.js -define("cordova/plugin/ios/contacts", function(require, exports, module) { -var exec = require('cordova/exec'); - -/** - * Provides iOS enhanced contacts API. - */ -module.exports = { - newContactUI : function(successCallback) { - /* - * Create a contact using the iOS Contact Picker UI - * NOT part of W3C spec so no official documentation - * - * returns: the id of the created contact as param to successCallback - */ - exec(successCallback, null, "Contacts","newContact", []); - }, - chooseContact : function(successCallback, options) { - /* - * Select a contact using the iOS Contact Picker UI - * NOT part of W3C spec so no official documentation - * - * @param errorCB error callback - * @param options object - * allowsEditing: boolean AS STRING - * "true" to allow editing the contact - * "false" (default) display contact - * - * returns: the id of the selected contact as param to successCallback - */ - exec(successCallback, null, "Contacts","chooseContact", [options]); - } -}; -}); - -// file: lib/ios/plugin/ios/device.js -define("cordova/plugin/ios/device", function(require, exports, module) { -/** - * this represents the mobile device, and provides properties for inspecting the model, version, UUID of the - * phone, etc. - * @constructor - */ -var exec = require('cordova/exec'), - utils = require('cordova/utils'), - channel = require('cordova/channel'); - -var Device = function() { - this.platform = null; - this.version = null; - this.name = null; - this.cordova = null; - this.uuid = null; -}; - -Device.prototype.setInfo = function(info) { - try { - this.platform = info.platform; - this.version = info.version; - this.name = info.name; - this.cordova = info.cordova; - this.uuid = info.uuid; - channel.onCordovaInfoReady.fire(); - } catch(e) { - utils.alert('Error during device info setting in cordova/plugin/ios/device!'); - } -}; - -module.exports = new Device(); - -}); - -// file: lib/ios/plugin/ios/nativecomm.js -define("cordova/plugin/ios/nativecomm", function(require, exports, module) { -var cordova = require('cordova'); - -/** - * Called by native code to retrieve all queued commands and clear the queue. - */ -module.exports = function() { - var json = JSON.stringify(cordova.commandQueue); - cordova.commandQueue = []; - return json; -}; -}); - -// file: lib/ios/plugin/ios/notification.js -define("cordova/plugin/ios/notification", function(require, exports, module) { -var Media = require('cordova/plugin/Media'); - -module.exports = { - beep:function(count) { - (new Media('beep.wav')).play(); - } -}; -}); - -// file: lib/common/plugin/logger.js -define("cordova/plugin/logger", function(require, exports, module) { -//------------------------------------------------------------------------------ -// The logger module exports the following properties/functions: -// -// LOG - constant for the level LOG -// ERROR - constant for the level ERROR -// WARN - constant for the level WARN -// INFO - constant for the level INFO -// DEBUG - constant for the level DEBUG -// logLevel() - returns current log level -// logLevel(value) - sets and returns a new log level -// useConsole() - returns whether logger is using console -// useConsole(value) - sets and returns whether logger is using console -// log(message,...) - logs a message at level LOG -// error(message,...) - logs a message at level ERROR -// warn(message,...) - logs a message at level WARN -// info(message,...) - logs a message at level INFO -// debug(message,...) - logs a message at level DEBUG -// logLevel(level,message,...) - logs a message specified level -// -//------------------------------------------------------------------------------ - -var logger = exports; - -var exec = require('cordova/exec'); -var utils = require('cordova/utils'); - -var UseConsole = true; -var Queued = []; -var DeviceReady = false; -var CurrentLevel; - -/** - * Logging levels - */ - -var Levels = [ - "LOG", - "ERROR", - "WARN", - "INFO", - "DEBUG" -]; - -/* - * add the logging levels to the logger object and - * to a separate levelsMap object for testing - */ - -var LevelsMap = {}; -for (var i=0; i CurrentLevel) return; - - // queue the message if not yet at deviceready - if (!DeviceReady && !UseConsole) { - Queued.push([level, message]); - return; - } - - // if not using the console, use the native logger - if (!UseConsole) { - exec(null, null, "Logger", "logLevel", [level, message]); - return; - } - - // make sure console is not using logger - if (console.__usingCordovaLogger) { - throw new Error("console and logger are too intertwingly"); - } - - // log to the console - switch (level) { - case logger.LOG: console.log(message); break; - case logger.ERROR: console.log("ERROR: " + message); break; - case logger.WARN: console.log("WARN: " + message); break; - case logger.INFO: console.log("INFO: " + message); break; - case logger.DEBUG: console.log("DEBUG: " + message); break; - } -}; - -// when deviceready fires, log queued messages -logger.__onDeviceReady = function() { - if (DeviceReady) return; - - DeviceReady = true; - - for (var i=0; i 3) { - fail(FileError.SYNTAX_ERR); - } else { - // if successful, return a FileSystem object - var success = function(file_system) { - if (file_system) { - if (typeof successCallback === 'function') { - // grab the name and root from the file system object - var result = new FileSystem(file_system.name, file_system.root); - successCallback(result); - } - } - else { - // no FileSystem object returned - fail(FileError.NOT_FOUND_ERR); - } - }; - exec(success, fail, "File", "requestFileSystem", [type, size]); - } -}; - -module.exports = requestFileSystem; -}); - -// file: lib/common/plugin/resolveLocalFileSystemURI.js -define("cordova/plugin/resolveLocalFileSystemURI", function(require, exports, module) { -var DirectoryEntry = require('cordova/plugin/DirectoryEntry'), - FileEntry = require('cordova/plugin/FileEntry'), - FileError = require('cordova/plugin/FileError'), - exec = require('cordova/exec'); - -/** - * Look up file system Entry referred to by local URI. - * @param {DOMString} uri URI referring to a local file or directory - * @param successCallback invoked with Entry object corresponding to URI - * @param errorCallback invoked if error occurs retrieving file system entry - */ -module.exports = function(uri, successCallback, errorCallback) { - // error callback - var fail = function(error) { - if (typeof errorCallback === 'function') { - errorCallback(new FileError(error)); - } - }; - // sanity check for 'not:valid:filename' - if(!uri || uri.split(":").length > 2) { - setTimeout( function() { - fail(FileError.ENCODING_ERR); - },0); - return; - } - // if successful, return either a file or directory entry - var success = function(entry) { - var result; - if (entry) { - if (typeof successCallback === 'function') { - // create appropriate Entry object - result = (entry.isDirectory) ? new DirectoryEntry(entry.name, entry.fullPath) : new FileEntry(entry.name, entry.fullPath); - try { - successCallback(result); - } - catch (e) { - console.log('Error invoking callback: ' + e); - } - } - } - else { - // no Entry object returned - fail(FileError.NOT_FOUND_ERR); - } - }; - - exec(success, fail, "File", "resolveLocalFileSystemURI", [uri]); -}; - -}); - -// file: lib/common/plugin/splashscreen.js -define("cordova/plugin/splashscreen", function(require, exports, module) { -var exec = require('cordova/exec'); - -var splashscreen = { - hide:function() { - exec(null, null, "SplashScreen", "hide", []); - } -}; - -module.exports = splashscreen; -}); - -// file: lib/common/utils.js -define("cordova/utils", function(require, exports, module) { -var utils = exports; - -/** - * Returns an indication of whether the argument is an array or not - */ -utils.isArray = function(a) { - return Object.prototype.toString.call(a) == '[object Array]'; -}; - -/** - * Returns an indication of whether the argument is a Date or not - */ -utils.isDate = function(d) { - return Object.prototype.toString.call(d) == '[object Date]'; -}; - -/** - * Does a deep clone of the object. - */ -utils.clone = function(obj) { - if(!obj || typeof obj == 'function' || utils.isDate(obj) || typeof obj != 'object') { - return obj; - } - - var retVal, i; - - if(utils.isArray(obj)){ - retVal = []; - for(i = 0; i < obj.length; ++i){ - retVal.push(utils.clone(obj[i])); - } - return retVal; - } - - retVal = {}; - for(i in obj){ - if(!(i in retVal) || retVal[i] != obj[i]) { - retVal[i] = utils.clone(obj[i]); - } - } - return retVal; -}; - -/** - * Returns a wrappered version of the function - */ -utils.close = function(context, func, params) { - if (typeof params == 'undefined') { - return function() { - return func.apply(context, arguments); - }; - } else { - return function() { - return func.apply(context, params); - }; - } -}; - -/** - * Create a UUID - */ -utils.createUUID = function() { - return UUIDcreatePart(4) + '-' + - UUIDcreatePart(2) + '-' + - UUIDcreatePart(2) + '-' + - UUIDcreatePart(2) + '-' + - UUIDcreatePart(6); -}; - -/** - * Extends a child object from a parent object using classical inheritance - * pattern. - */ -utils.extend = (function() { - // proxy used to establish prototype chain - var F = function() {}; - // extend Child from Parent - return function(Child, Parent) { - F.prototype = Parent.prototype; - Child.prototype = new F(); - Child.__super__ = Parent.prototype; - Child.prototype.constructor = Child; - }; -}()); - -/** - * Alerts a message in any available way: alert or console.log. - */ -utils.alert = function(msg) { - if (alert) { - alert(msg); - } else if (console && console.log) { - console.log(msg); - } -}; - -/** - * Formats a string and arguments following it ala sprintf() - * - * see utils.vformat() for more information - */ -utils.format = function(formatString /* ,... */) { - var args = [].slice.call(arguments, 1); - return utils.vformat(formatString, args); -}; - -/** - * Formats a string and arguments following it ala vsprintf() - * - * format chars: - * %j - format arg as JSON - * %o - format arg as JSON - * %c - format arg as '' - * %% - replace with '%' - * any other char following % will format it's - * arg via toString(). - * - * for rationale, see FireBug's Console API: - * http://getfirebug.com/wiki/index.php/Console_API - */ -utils.vformat = function(formatString, args) { - if (formatString === null || formatString === undefined) return ""; - if (arguments.length == 1) return formatString.toString(); - if (typeof formatString != "string") return formatString.toString(); - - var pattern = /(.*?)%(.)(.*)/; - var rest = formatString; - var result = []; - - while (args.length) { - var arg = args.shift(); - var match = pattern.exec(rest); - - if (!match) break; - - rest = match[3]; - - result.push(match[1]); - - if (match[2] == '%') { - result.push('%'); - args.unshift(arg); - continue; - } - - result.push(formatted(arg, match[2])); - } - - result.push(rest); - - return result.join(''); -}; - -//------------------------------------------------------------------------------ -function UUIDcreatePart(length) { - var uuidpart = ""; - for (var i=0; i - - - WebODF - - - - - - - - - - - - - - - - diff --git a/attic/programs/ios/www/nativezip.js b/attic/programs/ios/www/nativezip.js deleted file mode 100644 index 5c548fb87..000000000 --- a/attic/programs/ios/www/nativezip.js +++ /dev/null @@ -1,102 +0,0 @@ -/** - * Copyright (C) 2012 KO GmbH - - * @licstart - * The JavaScript code in this page is free software: you can redistribute it - * and/or modify it under the terms of the GNU Affero General Public License - * (GNU AGPL) as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. The code is distributed - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU AGPL for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this code. If not, see . - * - * As additional permission under GNU AGPL version 3 section 7, you - * may distribute non-source (e.g., minimized or compacted) forms of - * that code without the copy of the GNU GPL normally required by - * section 4, provided you include this license notice and a URL - * through which recipients can access the Corresponding Source. - * - * As a special exception to the AGPL, any HTML file which merely makes function - * calls to this code, and for that purpose includes it by reference shall be - * deemed a separate work for copyright law purposes. In addition, the copyright - * holders of this code give you permission to combine this code with free - * software libraries that are released under the GNU LGPL. You may copy and - * distribute such a system following the terms of the GNU AGPL for this code - * and the LGPL for the libraries. If you modify this code, you may extend this - * exception to your version of the code, but you are not obligated to do so. - * If you do not wish to do so, delete this exception statement from your - * version. - * - * This license applies to this entire compilation. - * @licend - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ - */ -/*global runtime, core, XMLHttpRequest*/ - -core.Zip = function (url, entriesReadCallback) { - "use strict"; - // remove 'odf:' prefix - url = url.substr(4); - var zip = this; - this.load = function (filename, callback) { - //alert(filename); - callback(null, ""); - }; - this.loadAsString = function (filename, callback) { - alert("loadAsString"); - }; - this.loadAsDOM = function (filename, callback) { - var xhr = new XMLHttpRequest(); - function handleResult() { - var xml; - runtime.log("loading " + filename + " status " + xhr.status + " readyState " + xhr.readyState); - if (xhr.readyState === 4) { - xml = xhr.responseXML; - runtime.log("done accessing responseXML " + xml + " " + (xhr.responseText && xhr.responseText.length) - + " " + xhr.statusText); - runtime.log("statusText " + xhr.statusText); - if (xhr.status === 0 && !xml) { - // empty files are considered as errors - callback("File " + filename + " is not valid XML."); - } else if (xhr.status === 200 || xhr.status === 0) { - try { - callback(null, xml); - } catch (e) { - runtime.log(e); - } - } else { - // report error - callback(xhr.responseText || xhr.statusText); - } - } - } - xhr.open('GET', "http://zipserver" + url + "?" + filename, true); - xhr.onreadystatechange = handleResult; - xhr.send(null); - }; - this.loadAsDataURL = function (filename, mimetype, callback) { - callback(null, "http://zipserver" + url + "?" + filename); - }; - this.getEntries = function () { - alert("getEntries"); - }; - this.loadContentXmlAsFragments = function (filename, handler) { - // the javascript implementation simply reads the file - zip.loadAsString(filename, function (err, data) { - if (err) { - return handler.rootElementReady(err); - } - handler.rootElementReady(null, data, true); - }); - }; - this.save = function () { - alert("save"); - }; - this.write = function () { - alert("write"); - }; - entriesReadCallback(null, this); -}; diff --git a/attic/programs/nativeQtClient/CMakeLists.txt b/attic/programs/nativeQtClient/CMakeLists.txt deleted file mode 100644 index dd72fad52..000000000 --- a/attic/programs/nativeQtClient/CMakeLists.txt +++ /dev/null @@ -1,45 +0,0 @@ -include(${QT_USE_FILE}) -include_directories(${CMAKE_CURRENT_BINARY_DIR} ${QT_QTCORE_INCLUDE_DIR}) -QT4_WRAP_CPP(NATIVEQTCLIENT_MOC - odfview.h - ../qtjsruntime/nativeio.h - ../qtjsruntime/nam.h -) -file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/application.qrc - "") -foreach(FILE ${TOUCHUI_FILES} index.html scripts.js webodf.js) - if (IS_ABSOLUTE ${FILE}) - GET_FILENAME_COMPONENT(_wwwfile ${FILE} NAME) - else (IS_ABSOLUTE ${FILE}) - SET(_wwwfile ${FILE}) - endif (IS_ABSOLUTE ${FILE}) - file(APPEND ${CMAKE_CURRENT_BINARY_DIR}/application.qrc "www/${_wwwfile}\n") -endforeach(FILE ${TOUCHUI_FILES}) -file(APPEND ${CMAKE_CURRENT_BINARY_DIR}/application.qrc - "") -COPY_FILES(NATIVEDEPS ${CMAKE_SOURCE_DIR}/programs/touchui - ${CMAKE_CURRENT_BINARY_DIR}/www ${TOUCHUI_FILES}) -COPY_FILES(NATIVEDEPS ${CMAKE_CURRENT_SOURCE_DIR} - ${CMAKE_CURRENT_BINARY_DIR}/www scripts.js) -COPY_FILES(NATIVEDEPS ${CMAKE_SOURCE_DIR}/programs/touchui - ${CMAKE_CURRENT_BINARY_DIR}/www index.html) -QT4_ADD_RESOURCES(NATIVEQTCLIENT_RES - ${CMAKE_CURRENT_BINARY_DIR}/application.qrc) - -add_custom_target(nativeQtClientDepencencies DEPENDS ${NATIVEDEPS}) - -# creates a copy of the compiled webodf.js in the nativeQtClient build dir -add_custom_target(nativeQtClient-webodf.js-target - COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/webodf/webodf.js ${CMAKE_CURRENT_BINARY_DIR}/www -) -add_dependencies(nativeQtClient-webodf.js-target webodf.js-target) - -add_executable(nativeQtClient EXCLUDE_FROM_ALL - main.cpp - odfview.cpp - ../qtjsruntime/nativeio.cpp - odfpage.cpp ${NATIVEQTCLIENT_MOC} ${NATIVEQTCLIENT_UI} - ${NATIVEQTCLIENT_RES}) - -target_link_libraries(nativeQtClient ${QT_LIBRARIES}) -add_dependencies(nativeQtClient nativeQtClient-webodf.js-target nativeQtClientDepencencies) diff --git a/attic/programs/nativeQtClient/README b/attic/programs/nativeQtClient/README deleted file mode 100644 index 4860fa1b0..000000000 --- a/attic/programs/nativeQtClient/README +++ /dev/null @@ -1 +0,0 @@ -This is a small app that can show ODF documents using mainly javascript. Some functions that are not fast in browers are provided in C++ and it gives the ability to open files on the file system. diff --git a/attic/programs/nativeQtClient/application.qrc b/attic/programs/nativeQtClient/application.qrc deleted file mode 100644 index 5010cdb8a..000000000 --- a/attic/programs/nativeQtClient/application.qrc +++ /dev/null @@ -1,18 +0,0 @@ - - - www/app/app.js - www/app/controller/Files.js - www/app/model/FileSystem.js - www/app/store/FileStore.js - www/app/views/FileDetail.js - www/app/views/FilesList.js - www/app/views/OdfView.js - www/app/views/Viewport.js - www/index.html - www/scripts.js - www/sencha-touch.css - www/sencha-touch.js - www/webodf.css - www/webodf.js - - diff --git a/attic/programs/nativeQtClient/main.cpp b/attic/programs/nativeQtClient/main.cpp deleted file mode 100644 index 62e5fa18b..000000000 --- a/attic/programs/nativeQtClient/main.cpp +++ /dev/null @@ -1,10 +0,0 @@ -#include "odfview.h" -#include -#include - -int main(int argc, char *argv[]) { - QApplication a(argc, argv); - OdfView view; - view.show(); - return a.exec(); -} diff --git a/attic/programs/nativeQtClient/mainwindow.cpp b/attic/programs/nativeQtClient/mainwindow.cpp deleted file mode 100644 index 861014399..000000000 --- a/attic/programs/nativeQtClient/mainwindow.cpp +++ /dev/null @@ -1,173 +0,0 @@ -#include "mainwindow.h" -#include "ui_mainwindow.h" -#include "odfview.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include - -MainWindow::MainWindow(QWidget *parent) : - QMainWindow(parent), - ui(new Ui::MainWindow) -{ - ui->setupUi(this); - - createActions(); - createToolBars(); - - QCoreApplication::setOrganizationName("KO"); - //QCoreApplication::setOrganizationDomain("example.com"); - QCoreApplication::setApplicationName("Odf Viewer"); - - QSettings settings; - - setWindowTitle(tr("Odf Viewer")); - setUnifiedTitleAndToolBarOnMac(true); - - QStringList odfNameFilter; - odfNameFilter << "*.odt" << "*.ods" << "*.odp"; - dirmodel = new QFileSystemModel(this); - dirmodel->setNameFilters(odfNameFilter); - dirmodel->setFilter(QDir::AllDirs|QDir::AllEntries|QDir::NoDotAndDotDot); - dirview = new QTreeView(this); - dirview->setModel(dirmodel); - dirview->setHeaderHidden(true); - dirview->setAnimated(true); - for (int i = 1; i < dirmodel->columnCount(); i++) { - dirview->setColumnHidden(i, true); - } - QString rootpath = settings.value("rootpath", QDir::homePath()).toString(); - dirmodel->setRootPath(rootpath); - const QModelIndex rootindex = dirmodel->index(rootpath); - dirview->setRootIndex(rootindex); - QLineEdit *dirPath = new QLineEdit(rootpath, this); - dirdock = new QDockWidget(this); - QWidget *w = new QWidget(dirdock); - QVBoxLayout *layout = new QVBoxLayout(w); - dirdock->setWidget(w); - layout->addWidget(dirPath); - layout->addWidget(dirview); - addDockWidget(Qt::LeftDockWidgetArea, dirdock); - - connect(dirview, SIGNAL(clicked(QModelIndex)), this, SLOT(loadOdf(QModelIndex))); - connect(dirPath, SIGNAL(textChanged(QString)), this, SLOT(setPath(QString))); -} - -MainWindow::~MainWindow() -{ - delete ui; -} - -void -MainWindow::openFile(const QString& path) -{ - QMdiSubWindow* w = findMdiChild(path); - OdfView* v = (w) ?dynamic_cast(w->widget()) :0; - if (v == 0) { - w = ui->mdiArea->activeSubWindow(); - v = (w) ?dynamic_cast(w->widget()) :0; - } - if (v == 0) { - v = new OdfView(this); - v->showMaximized(); - w = ui->mdiArea->addSubWindow(v); - w->showMaximized(); - } - ui->mdiArea->setActiveSubWindow(w); - v->loadFile(path); -} - -void MainWindow::changeEvent(QEvent *e) -{ - QMainWindow::changeEvent(e); - switch (e->type()) { - case QEvent::LanguageChange: - ui->retranslateUi(this); - break; - default: - break; - } -} -void MainWindow::open() -{ - QString fileName = QFileDialog::getOpenFileName(this, QString(), QString(), - tr("Office Files (*.odt *.odp *.ods)")); - if (!fileName.isEmpty()) { - QMdiSubWindow *existing = findMdiChild(fileName); - if (existing) { - ui->mdiArea->setActiveSubWindow(existing); - return; - } - - OdfView *child = createOdfView(); - if (child->loadFile(fileName)) { - statusBar()->showMessage(tr("File loaded"), 2000); - child->showMaximized(); - } else { - child->close(); - } - } -} -void MainWindow::createActions() -{ - //openAct = new QAction(QIcon(":/images/open.png"), tr("&Open..."), this); - openAct = new QAction(tr("&Open..."), this); - openAct->setShortcuts(QKeySequence::Open); - openAct->setStatusTip(tr("Open an existing file")); - connect(openAct, SIGNAL(triggered()), this, SLOT(open())); -} -void MainWindow::createToolBars() -{ - fileToolBar = addToolBar(tr("File")); - fileToolBar->addAction(openAct); -} -QMdiSubWindow *MainWindow::findMdiChild(const QString &fileName) -{ - QString canonicalFilePath = QFileInfo(fileName).canonicalFilePath(); - - foreach (QMdiSubWindow *window, ui->mdiArea->subWindowList()) { - OdfView *odfView = qobject_cast(window->widget()); - if (odfView->currentFile() == canonicalFilePath) - return window; - } - return 0; -} - -OdfView *MainWindow::createOdfView() -{ - OdfView *view = new OdfView(this); - ui->mdiArea->addSubWindow(view); - return view; -} - -void -MainWindow::loadOdf(const QModelIndex& index) { - if (dirmodel->isDir(index)) { - if (dirview->isExpanded(index)) { - dirview->collapse(index); - } else { - dirview->expand(index); - } - return; - } - QString path = dirmodel->filePath(index); - path = QFileInfo(path).canonicalFilePath(); - openFile(path); -} - -void MainWindow::setPath(const QString &path) -{ - dirmodel->setRootPath(path); - const QModelIndex rootindex = dirmodel->index(path); - dirview->setRootIndex(rootindex); - QSettings settings; - settings.setValue("rootpath", path); -} - - - diff --git a/attic/programs/nativeQtClient/mainwindow.h b/attic/programs/nativeQtClient/mainwindow.h deleted file mode 100644 index d0bc822d0..000000000 --- a/attic/programs/nativeQtClient/mainwindow.h +++ /dev/null @@ -1,46 +0,0 @@ -#ifndef MAINWINDOW_H -#define MAINWINDOW_H - -#include -#include - -namespace Ui { - class MainWindow; -} - -class OdfView; -class QFileSystemModel; -class QTreeView; -class QDockWidget; -class QModelIndex; - -class MainWindow : public QMainWindow { - Q_OBJECT -public: - MainWindow(QWidget *parent = 0); - ~MainWindow(); - void openFile(const QString& path); - -private slots: - void open(); - OdfView *createOdfView(); - void loadOdf(const QModelIndex& index); - void setPath(const QString &path); - -private: - QMdiSubWindow *findMdiChild(const QString &fileName); - void createActions(); - void createToolBars(); - QToolBar *fileToolBar; - QAction *openAct; -protected: - void changeEvent(QEvent *e); - -private: - Ui::MainWindow *ui; - QFileSystemModel* dirmodel; - QTreeView* dirview; - QDockWidget* dirdock; -}; - -#endif // MAINWINDOW_H diff --git a/attic/programs/nativeQtClient/mainwindow.ui b/attic/programs/nativeQtClient/mainwindow.ui deleted file mode 100644 index 38682c701..000000000 --- a/attic/programs/nativeQtClient/mainwindow.ui +++ /dev/null @@ -1,46 +0,0 @@ - - - MainWindow - - - - 0 - 0 - 800 - 600 - - - - MainWindow - - - - - - - - - - - - 0 - 0 - 800 - 23 - - - - - - TopToolBarArea - - - false - - - - - - - - diff --git a/attic/programs/nativeQtClient/odfpage.cpp b/attic/programs/nativeQtClient/odfpage.cpp deleted file mode 100644 index d87a8baa2..000000000 --- a/attic/programs/nativeQtClient/odfpage.cpp +++ /dev/null @@ -1 +0,0 @@ -#include "odfpage.h" diff --git a/attic/programs/nativeQtClient/odfpage.h b/attic/programs/nativeQtClient/odfpage.h deleted file mode 100644 index 487d16991..000000000 --- a/attic/programs/nativeQtClient/odfpage.h +++ /dev/null @@ -1,15 +0,0 @@ -#ifndef ODFPAGE_H -#define ODFPAGE_H - -#include -#include - -class OdfPage : public QWebPage { -public: - OdfPage(QObject* parent) :QWebPage(parent) {} - void javaScriptConsoleMessage(const QString& message, int lineNumber, const QString & sourceID) { - qDebug() << sourceID << ":" << lineNumber << ":" << message; - } -}; - -#endif // ODFPAGE_H diff --git a/attic/programs/nativeQtClient/odfview.cpp b/attic/programs/nativeQtClient/odfview.cpp deleted file mode 100644 index 60da5c9ef..000000000 --- a/attic/programs/nativeQtClient/odfview.cpp +++ /dev/null @@ -1,80 +0,0 @@ -#include "odfview.h" - -#include "../qtjsruntime/nativeio.h" -#include "../qtjsruntime/nam.h" - -#include "odfpage.h" - -#include -#include -#include -#include -#include -#include -#include - -OdfView::OdfView(QWidget* parent) :QWebView(parent) -{ - QString prefix = "../android/assets/"; // set this to the right value when debugging - QString htmlfile = QDir(prefix).absoluteFilePath("www/index.html"); - if (!QFileInfo(htmlfile).exists()) { - prefix = "qrc:/"; - htmlfile = "qrc:/www/index.html"; - } - setPage(new OdfPage(this)); - nativeio = new NativeIO(this, QDir(prefix), QDir::current()); - connect(page(), SIGNAL(loadFinished(bool)), this, SLOT(slotLoadFinished(bool))); - page()->settings()->setAttribute(QWebSettings::DeveloperExtrasEnabled, true); - - connect(page()->mainFrame(), SIGNAL(javaScriptWindowObjectCleared()), - this, SLOT(slotInitWindowObjects())); - - // use our own networkaccessmanager that gives limited access to the local - // file system - networkaccessmanager = new NAM(this); - page()->setNetworkAccessManager(networkaccessmanager); - setUrl(QUrl(htmlfile)); - loaded = false; -} - -OdfView::~OdfView() { -} - -void -OdfView::slotInitWindowObjects() -{ - QWebFrame *frame = page()->mainFrame(); - frame->addToJavaScriptWindowObject("nativeio", nativeio); -} - -bool -OdfView::loadFile(const QString &fileName) { - curFile = fileName; - // odf->addFile(identifier, fileName); - // networkaccessmanager->setCurrentFile(odf->getOpenContainer(identifier)); - if (loaded) { - slotLoadFinished(true); - } - return true; -} -void -OdfView::slotLoadFinished(bool ok) { - if (!ok) return; - loaded = true; - QWebFrame *frame = page()->mainFrame(); - QString js = - "var originalReadFileSync = runtime.readFileSync;" - "runtime.readFileSync = function (path, encoding) {" - " if (path.substr(path.length - 3) === '.js') {" - " return originalReadFileSync.apply(runtime," - " [path, encoding]);" - " }" - " return nativeio.readFileSync(path, encoding);" - "};" - "runtime.read = function (path, offset, length, callback) {" - " var data = nativeio.read(path, offset, length);" - " data = runtime.byteArrayFromString(data, 'binary');" - " callback(nativeio.error()||null, data);" - "};"; - frame->evaluateJavaScript(js); -} diff --git a/attic/programs/nativeQtClient/odfview.h b/attic/programs/nativeQtClient/odfview.h deleted file mode 100644 index f61ba3c92..000000000 --- a/attic/programs/nativeQtClient/odfview.h +++ /dev/null @@ -1,29 +0,0 @@ -#ifndef ODFVIEW_H -#define ODFVIEW_H - -#include -#include -class NativeIO; - -class OdfView : public QWebView { -Q_OBJECT -public: - OdfView(QWidget* parent = 0); - ~OdfView(); - QString currentFile() { return curFile; } - -public slots: - bool loadFile(const QString &fileName); - -private slots: - void slotLoadFinished(bool ok); - void slotInitWindowObjects(); - -private: - bool loaded; - QString curFile; - QNetworkAccessManager* networkaccessmanager; - NativeIO* nativeio; -}; - -#endif // ODFVIEW_H diff --git a/attic/programs/nativeQtClient/scripts.js b/attic/programs/nativeQtClient/scripts.js deleted file mode 100644 index 120a641d9..000000000 --- a/attic/programs/nativeQtClient/scripts.js +++ /dev/null @@ -1,129 +0,0 @@ -/** - * Copyright (C) 2012 KO GmbH - - * @licstart - * The JavaScript code in this page is free software: you can redistribute it - * and/or modify it under the terms of the GNU Affero General Public License - * (GNU AGPL) as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. The code is distributed - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU AGPL for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this code. If not, see . - * - * As additional permission under GNU AGPL version 3 section 7, you - * may distribute non-source (e.g., minimized or compacted) forms of - * that code without the copy of the GNU GPL normally required by - * section 4, provided you include this license notice and a URL - * through which recipients can access the Corresponding Source. - * - * As a special exception to the AGPL, any HTML file which merely makes function - * calls to this code, and for that purpose includes it by reference shall be - * deemed a separate work for copyright law purposes. In addition, the copyright - * holders of this code give you permission to combine this code with free - * software libraries that are released under the GNU LGPL. You may copy and - * distribute such a system following the terms of the GNU AGPL for this code - * and the LGPL for the libraries. If you modify this code, you may extend this - * exception to your version of the code, but you are not obligated to do so. - * If you do not wish to do so, delete this exception statement from your - * version. - * - * This license applies to this entire compilation. - * @licend - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ - */ -/*global alert, app, window, runtime*/ -var LocalFileSystem = { - PERSISTENT: 0, - TEMPORARY: 1 -}; -function FileEntry(name, fullPath) { - "use strict"; - this.isFile = true; - this.isDirectory = false; - this.name = name; - this.fullPath = fullPath; - this.file = function (onsuccess, onerror) { - function File(fullPath) { - this.name = name; - this.fullPath = fullPath; - this.type = ""; - this.size = -1; - this.lastModifiedDate = -1; - } - var file = new File(fullPath); - try { - onsuccess(file); - } catch (e) { - alert("Error on determining file properties: " + e); - onerror(e); - } - }; -} -function FileReader() { - "use strict"; - var fr = this; - this.readAsArrayBuffer = function (file) { - var path = file.fullPath.substr(7), - data = runtime.readFileSync(path, 'binary'); - data = runtime.byteArrayFromString(data, "binary"); - window.setTimeout(function () { - fr.onloadend({target: {result: data}}); - }, 1); - }; -} -var DirectoryReader; -function DirectoryEntry(name, fullPath) { - "use strict"; - this.isFile = false; - this.isDirectory = true; - this.name = name; - this.fullPath = fullPath; - this.createReader = function () { - var reader = new DirectoryReader(fullPath); - return reader; - }; -} -function DirectoryReader(fullPath) { - "use strict"; - this.readEntries = function (onsuccess, onerror) { - window.setTimeout(function () { - var entries = []; - entries[entries.length] = new FileEntry("welcome.odt", - "welcome.odt"); - entries[entries.length] = new FileEntry("Traktatenblad.odt", - "Traktatenblad.odt"); - try { - onsuccess(entries); - } catch (e) { - onerror(e); - } - }, 1); - }; -} -window.resolveLocalFileSystemURI = function (path, onsuccess, onerror) { - "use strict"; - var p = path.lastIndexOf("/"), - name = (p === -1) ? path : path.substr(p + 1); - onsuccess(new FileEntry(name, path)); -}; -window.requestFileSystem = function (filesystem, id, onsuccess, onerror) { - "use strict"; - var dirs = [], shared, subfolder, path; - try { - if (filesystem === LocalFileSystem.PERSISTENT) { - path = ""; - onsuccess({ - name: "root", - root: new DirectoryEntry("root", path) - }); - } else { - onerror("not defined"); - } - } catch (e) { - onerror(e); - } -}; -var device = {}; diff --git a/attic/programs/playbook/CMakeLists.txt b/attic/programs/playbook/CMakeLists.txt deleted file mode 100644 index e69de29bb..000000000 diff --git a/attic/programs/playbook/build_sign.bat b/attic/programs/playbook/build_sign.bat deleted file mode 100644 index 34e8deeab..000000000 --- a/attic/programs/playbook/build_sign.bat +++ /dev/null @@ -1,23 +0,0 @@ -@echo on -set WWSDK=M:\blackberrysdk\webworkssdk -set BBWP=%WWSDK%\bbwp\bbwp -set DEPLOY=%WWSDK%\bbwp\blackberry-tablet-sdk\bin\blackberry-deploy -set JAVA_HOME=%WWSDK%\jre -set PATH=%PATH%;%JAVA_HOME%\bin - -mkdir bin -mkdir signed - -zip -r webodf.zip config.xml index.html icon.png scripts.js app sencha-touch.js sencha-touch.css webodf.js webodf.css ZoomIn.png ZoomOut.png - -rem MAKE A DEBUG VERSION -del bin\webodf.bar -%BBWP% webodf.zip -d -o bin -if %errorlevel% neq 0 exit /b %errorlevel% - -%DEPLOY% -installApp -password ko -device 192.168.1.111 -package bin\webodf.bar -if %errorlevel% neq 0 exit /b %errorlevel% - -rem MAKE A SIGNED VERSION, (can be done only once for each buildId!) -rem %BBWP% webodf.zip -g U9gXpJXbGC -buildId 2 -o signed - diff --git a/attic/programs/playbook/config.xml b/attic/programs/playbook/config.xml deleted file mode 100644 index cab778831..000000000 --- a/attic/programs/playbook/config.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - KO GmbH - WebODF - - Viewer for OpenDocument files. - - - - - - - - - access_shared - - - - - - - - - diff --git a/attic/programs/playbook/ext/blackberry.custom.filereader/js/common/custom_filereader_dispatcher.js b/attic/programs/playbook/ext/blackberry.custom.filereader/js/common/custom_filereader_dispatcher.js deleted file mode 100644 index c707e32ac..000000000 --- a/attic/programs/playbook/ext/blackberry.custom.filereader/js/common/custom_filereader_dispatcher.js +++ /dev/null @@ -1,53 +0,0 @@ -/** - * Copyright (C) 2012 KO GmbH - - * @licstart - * The JavaScript code in this page is free software: you can redistribute it - * and/or modify it under the terms of the GNU Affero General Public License - * (GNU AGPL) as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. The code is distributed - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU AGPL for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this code. If not, see . - * - * As additional permission under GNU AGPL version 3 section 7, you - * may distribute non-source (e.g., minimized or compacted) forms of - * that code without the copy of the GNU GPL normally required by - * section 4, provided you include this license notice and a URL - * through which recipients can access the Corresponding Source. - * - * As a special exception to the AGPL, any HTML file which merely makes function - * calls to this code, and for that purpose includes it by reference shall be - * deemed a separate work for copyright law purposes. In addition, the copyright - * holders of this code give you permission to combine this code with free - * software libraries that are released under the GNU LGPL. You may copy and - * distribute such a system following the terms of the GNU AGPL for this code - * and the LGPL for the libraries. If you modify this code, you may extend this - * exception to your version of the code, but you are not obligated to do so. - * If you do not wish to do so, delete this exception statement from your - * version. - * - * This license applies to this entire compilation. - * @licend - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ - */ -(function () { - var CUSTOM_FILEREADER_API_URL = "blackberry/custom/filereader"; - - var ARGS_PATH = "path"; - var ARGS_DATA = "data"; - - function CustomFileReader() { - }; - - CustomFileReader.prototype.readAsDataURL = function(path) { - var remoteCall = new blackberry.transport.RemoteFunctionCall(CUSTOM_FILEREADER_API_URL + "/readAsDataURL"); - remoteCall.addParam(ARGS_PATH, path); - return remoteCall.makeSyncCall(); - }; - - blackberry.Loader.javascriptLoaded("blackberry.custom.filereader", CustomFileReader); -})(); diff --git a/attic/programs/playbook/ext/blackberry.custom.filereader/js/common/custom_filereader_ns.js b/attic/programs/playbook/ext/blackberry.custom.filereader/js/common/custom_filereader_ns.js deleted file mode 100644 index 4d677965c..000000000 --- a/attic/programs/playbook/ext/blackberry.custom.filereader/js/common/custom_filereader_ns.js +++ /dev/null @@ -1,44 +0,0 @@ -/** - * Copyright (C) 2012 KO GmbH - - * @licstart - * The JavaScript code in this page is free software: you can redistribute it - * and/or modify it under the terms of the GNU Affero General Public License - * (GNU AGPL) as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. The code is distributed - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU AGPL for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this code. If not, see . - * - * As additional permission under GNU AGPL version 3 section 7, you - * may distribute non-source (e.g., minimized or compacted) forms of - * that code without the copy of the GNU GPL normally required by - * section 4, provided you include this license notice and a URL - * through which recipients can access the Corresponding Source. - * - * As a special exception to the AGPL, any HTML file which merely makes function - * calls to this code, and for that purpose includes it by reference shall be - * deemed a separate work for copyright law purposes. In addition, the copyright - * holders of this code give you permission to combine this code with free - * software libraries that are released under the GNU LGPL. You may copy and - * distribute such a system following the terms of the GNU AGPL for this code - * and the LGPL for the libraries. If you modify this code, you may extend this - * exception to your version of the code, but you are not obligated to do so. - * If you do not wish to do so, delete this exception statement from your - * version. - * - * This license applies to this entire compilation. - * @licend - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ - */ -(function () { - - function CustomFileReader(disp) { - this.constructor.prototype.readAsDataURL = function(path) { return disp.readAsDataURL(path); }; - }; - - blackberry.Loader.javascriptLoaded("blackberry.custom.filereader", CustomFileReader); -})(); diff --git a/attic/programs/playbook/ext/blackberry.custom.filereader/library.xml b/attic/programs/playbook/ext/blackberry.custom.filereader/library.xml deleted file mode 100644 index a5a24c3d8..000000000 --- a/attic/programs/playbook/ext/blackberry.custom.filereader/library.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - blackberry.custom.filereader.CustomFileReader - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/attic/programs/playbook/ext/blackberry.custom.filereader/src/AIR/CustomFileReader/src/blackberry/custom/filereader/CustomFileReader.as b/attic/programs/playbook/ext/blackberry.custom.filereader/src/AIR/CustomFileReader/src/blackberry/custom/filereader/CustomFileReader.as deleted file mode 100644 index 3261fbcf4..000000000 --- a/attic/programs/playbook/ext/blackberry.custom.filereader/src/AIR/CustomFileReader/src/blackberry/custom/filereader/CustomFileReader.as +++ /dev/null @@ -1,34 +0,0 @@ -package blackberry.custom.filereader { - import flash.filesystem.File; - import flash.filesystem.FileMode; - import flash.filesystem.FileStream; - import flash.utils.ByteArray; - import mx.utils.Base64Encoder; - import webworks.extension.DefaultExtension; - - public class CustomFileReader extends DefaultExtension { - - public function CustomFileReader() { - super(); - } - - override public function getFeatureList():Array { - return new Array ("blackberry.custom.filereader"); - } - - public function readAsDataURL(path:String):String { - var file:File = new File(path); - if (!file.exists) { - return ""; - } - var bytes:ByteArray = new ByteArray(); - var stream:FileStream = new FileStream(); - stream.open(file, FileMode.READ); - stream.readBytes(bytes); - var btoa:Base64Encoder = new Base64Encoder(); - btoa.encodeBytes(bytes); - stream.close(); - return "data:;base64," + btoa.toString(); - } - } -} \ No newline at end of file diff --git a/attic/programs/playbook/icon.png b/attic/programs/playbook/icon.png deleted file mode 100644 index b7ccb848e..000000000 Binary files a/attic/programs/playbook/icon.png and /dev/null differ diff --git a/attic/programs/playbook/index.html b/attic/programs/playbook/index.html deleted file mode 100644 index 77d331c12..000000000 --- a/attic/programs/playbook/index.html +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - - - - - - - - - - WebODF - - - - - diff --git a/attic/programs/playbook/scripts.js b/attic/programs/playbook/scripts.js deleted file mode 100644 index b67c1b3fb..000000000 --- a/attic/programs/playbook/scripts.js +++ /dev/null @@ -1,182 +0,0 @@ -/** - * Copyright (C) 2012 KO GmbH - - * @licstart - * The JavaScript code in this page is free software: you can redistribute it - * and/or modify it under the terms of the GNU Affero General Public License - * (GNU AGPL) as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. The code is distributed - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU AGPL for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this code. If not, see . - * - * As additional permission under GNU AGPL version 3 section 7, you - * may distribute non-source (e.g., minimized or compacted) forms of - * that code without the copy of the GNU GPL normally required by - * section 4, provided you include this license notice and a URL - * through which recipients can access the Corresponding Source. - * - * As a special exception to the AGPL, any HTML file which merely makes function - * calls to this code, and for that purpose includes it by reference shall be - * deemed a separate work for copyright law purposes. In addition, the copyright - * holders of this code give you permission to combine this code with free - * software libraries that are released under the GNU LGPL. You may copy and - * distribute such a system following the terms of the GNU AGPL for this code - * and the LGPL for the libraries. If you modify this code, you may extend this - * exception to your version of the code, but you are not obligated to do so. - * If you do not wish to do so, delete this exception statement from your - * version. - * - * This license applies to this entire compilation. - * @licend - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ - */ -/*global blackberry, alert, document, window, app*/ -var LocalFileSystem = { - PERSISTENT: 0, - TEMPORARY: 1 -}; -function FileWriter(fullPath) { - "use strict"; - this.write = function (data) { - var blob; - try { - blob = blackberry.utils.stringToBlob(data, "UTF-8"); - blackberry.io.file.saveFile(fullPath, blob); - } catch (e) { - } - }; -} -function FileEntry(name, fullPath) { - "use strict"; - this.isFile = true; - this.isDirectory = false; - this.name = name; - this.fullPath = fullPath; - this.file = function (onsuccess, onerror) { - function File(fullPath) { - this.name = name; - this.fullPath = fullPath; - this.type = ""; - this.size = -1; - this.lastModifiedDate = -1; - } - var file = new File(fullPath), - properties; - try { - properties = blackberry.io.file.getFileProperties(fullPath); - file.type = properties.mimeType; - file.size = properties.size; - file.lastModifiedDate = properties.dateModified; - onsuccess(file); - } catch (e) { - alert("Error on determining file properties: " + e); - onerror(e); - } - }; - this.createWriter = function (onsuccess, onerror) { - onsuccess(new FileWriter(fullPath)); - }; -} -function FileReader() { - "use strict"; - var fr = this; - this.readAsDataURL = function (file) { - var path = file.fullPath.substr(7); - window.setTimeout(function () { - try { - var data = blackberry.custom.filereader.readAsDataURL(path); - fr.onloadend({target: {result: data}}); - } catch (e) { - alert("Error on reading file: " + e + " " + file.fullPath); - } - }, 1); - }; - this.readAsText = function (file) { - var path = file.fullPath.substr(7); - try { - blackberry.io.file.readFile(path, function (fullPath, blob) { - var str = blackberry.utils.blobToString(blob, "UTF-8"); - fr.onloadend({target: {result: str}}); - }, true); - } catch (e) { - fr.onloadend({target: {result: "[]"}}); - } - }; -} -var DirectoryReader; -function DirectoryEntry(name, fullPath) { - "use strict"; - this.isFile = false; - this.isDirectory = true; - this.name = name; - this.fullPath = fullPath; - this.createReader = function () { - var reader = new DirectoryReader(fullPath); - return reader; - }; -} -function DirectoryReader(fullPath) { - "use strict"; - this.readEntries = function (onsuccess, onerror) { - window.setTimeout(function () { - var entries = [], - dirs = blackberry.io.dir.listDirectories(fullPath), - files = blackberry.io.dir.listFiles(fullPath), - i; - try { - for (i = 0; i < dirs.length; i += 1) { - entries[entries.length] = new DirectoryEntry(dirs[i], - fullPath + "/" + dirs[i]); - } - for (i = 0; i < files.length; i += 1) { - entries[entries.length] = new FileEntry(files[i], - fullPath + "/" + files[i]); - } - onsuccess(entries); - } catch (e) { - onerror(e); - } - }, 1); - }; -} -window.resolveLocalFileSystemURI = function (path, onsuccess, onerror) { - "use strict"; - var p = path.lastIndexOf("/"), - name; - if (p === -1) { - name = path; - path = blackberry.io.dir.appDirs.shared.documents.path + "/" + path; - } else { - name = path.substr(p + 1); - } - onsuccess(new FileEntry(name, path)); -}; -window.requestFileSystem = function (filesystem, id, onsuccess, onerror) { - "use strict"; - var dirs = [], shared, subfolder; - try { - if (filesystem === LocalFileSystem.PERSISTENT) { - shared = blackberry.io.dir.appDirs.shared; - for (subfolder in shared) { - if (shared.hasOwnProperty(subfolder)) { - dirs[dirs.length] = subfolder; - } - } - onsuccess({ - name: "root", - root: new DirectoryEntry("root", shared.documents.path - //+ "/kofficetests/odf/odt" - ) - }); - } else { - onerror("not defined"); - } - } catch (e) { - onerror(e); - } -}; -var device = {}; diff --git a/node_modules/.package-lock.json b/node_modules/.package-lock.json new file mode 100644 index 000000000..b2ebc29f0 --- /dev/null +++ b/node_modules/.package-lock.json @@ -0,0 +1,2079 @@ +{ + "name": "node-webodf", + "version": "0.6.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "node_modules/@asamuzakjp/css-color": { + "version": "6.0.7", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-6.0.7.tgz", + "integrity": "sha512-vC/bk1Lz7Tn/EfU9/apOTBk80/8dyGyWMowPoV1tJ52muDGsDqt2HPT2klrFUiY60MQmQv9q8yIht15JnBgDGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@csstools/css-calc": "^3.3.0", + "@csstools/css-color-parser": "^4.1.10", + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0", + "lru-cache": "^11.5.2" + }, + "engines": { + "node": "^22.13.0 || >=24.0.0" + } + }, + "node_modules/@asamuzakjp/dom-selector": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-8.3.2.tgz", + "integrity": "sha512-93Z1N+BQNXysodoicpOIyNh2drHfz/CTf9nnT0FEx72GJcIiwgydD7tGAr78j41LsYn3hlRn+LdGPuBLn1Bl8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "bidi-js": "^1.0.3", + "css-tree": "^3.2.1", + "is-potential-custom-element-name": "^1.0.1", + "lru-cache": "^11.5.2" + }, + "engines": { + "node": "^22.13.0 || >=24.0.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.8.tgz", + "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.8" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@bramus/specificity": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz", + "integrity": "sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "css-tree": "^3.0.0" + }, + "bin": { + "specificity": "bin/cli.js" + } + }, + "node_modules/@csstools/color-helpers": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.1.1.tgz", + "integrity": "sha512-gLNsunvwf3mCi5u5o46/Z/JcJMnhbHSaZ69rkgPzNM3J4s8hWwpPUQB6/tt0EDFyCiWzxANlx+2LJwpYj4zS1w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/@csstools/css-calc": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.3.0.tgz", + "integrity": "sha512-c5ihYsPkdG6JCkU2zTMm4+k6r7RXuGxtWYhu5DHMIiF1FHzrfmHL5so11AoFpUv/tu61xfcmT4AmKoFfMPoqdQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.2.1.tgz", + "integrity": "sha512-YpAJZhaHplYQkG8ib+/Fx5Y0eF2lVWi3tIvMJA6i39TLyUNp2439cifzW8VMjhlqrBjHzK5hVGugRRm2zTKI/A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/color-helpers": "^6.1.1", + "@csstools/css-calc": "^3.3.0" + }, + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.0.tgz", + "integrity": "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-syntax-patches-for-csstree": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.9.tgz", + "integrity": "sha512-iGGw4OsAYsS6pD29MdJ2bX/nJx65a04ZZiw6x+VwWlP2DdXf6f++Zmuv/OzALpdyfVhjbduIIF2cXM7HWBIe9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "peerDependencies": { + "css-tree": "^3.2.1" + }, + "peerDependenciesMeta": { + "css-tree": { + "optional": true + } + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz", + "integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/@exodus/bytes": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.15.1.tgz", + "integrity": "sha512-S6mL0yNB/Abt9Ei4tq8gDhcczc4S3+vQ4ra7vxnAf+YHC02srtqxKKZghx2Dq6p0e66THKwR6r8N6P95wEty7Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@noble/hashes": "^1.8.0 || ^2.0.0" + }, + "peerDependenciesMeta": { + "@noble/hashes": { + "optional": true + } + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.6.tgz", + "integrity": "sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@jsdoc/salty": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@jsdoc/salty/-/salty-0.2.12.tgz", + "integrity": "sha512-TuB0x50EoAvEX/UEWITd8Mkn3WhiTjSvbTMCLj0BhsQEl5iUzjXdA0bETEVpTk+5TGTLR6QktI9H4hLviVeaAQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "lodash": "^4.18.1" + }, + "engines": { + "node": ">=v12.0.0" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/markdown-it": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.2.0.tgz", + "integrity": "sha512-NoQ2yGlLWj4wpxMs+TYmRKk3thDrQ97agr7sFqfLsAlvoS8SNQuTrlObhFqG9iugdTtgOE9jpJ6FNM4ZGsa5xQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/linkify-it": "^5", + "@types/mdurl": "^2" + } + }, + "node_modules/@types/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@xmldom/xmldom": { + "version": "0.9.12", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.9.12.tgz", + "integrity": "sha512-5AXjrcMClTryPe9LgZrygpB1lj7s0S9E0+W+AHaVKAVyHanafK86iPSvG5xHVSp/jC+VH1UXu0TAEmY279xH7A==", + "license": "MIT", + "engines": { + "node": ">=14.6" + } + }, + "node_modules/acorn": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz", + "integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ansi-regex": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.3.0.tgz", + "integrity": "sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/bidi-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", + "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "require-from-string": "^2.0.2" + } + }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", + "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/c8": { + "version": "10.1.3", + "resolved": "https://registry.npmjs.org/c8/-/c8-10.1.3.tgz", + "integrity": "sha512-LvcyrOAaOnrrlMpW22n690PUvxiq4Uf9WMhQwNJ9vgagkL/ph1+D4uvjvDA5XCbykrc0sx+ay6pVi9YZ1GnhyA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@bcoe/v8-coverage": "^1.0.1", + "@istanbuljs/schema": "^0.1.3", + "find-up": "^5.0.0", + "foreground-child": "^3.1.1", + "istanbul-lib-coverage": "^3.2.0", + "istanbul-lib-report": "^3.0.1", + "istanbul-reports": "^3.1.6", + "test-exclude": "^7.0.1", + "v8-to-istanbul": "^9.0.0", + "yargs": "^17.7.2", + "yargs-parser": "^21.1.1" + }, + "bin": { + "c8": "bin/c8.js" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "monocart-coverage-reports": "^2" + }, + "peerDependenciesMeta": { + "monocart-coverage-reports": { + "optional": true + } + } + }, + "node_modules/catharsis": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.9.0.tgz", + "integrity": "sha512-prMTQVpcns/tzFgFVkVp6ak6RykZyWb3gu8ckUpd6YkTlacOd3DXGJjIpD4Q6zJirizvaiAjSSHlOsA+6sNh2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash": "^4.17.15" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/cliui/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-tree": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", + "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdn-data": "2.27.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/data-urls": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz", + "integrity": "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/data-urls/node_modules/whatwg-url": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.1.tgz", + "integrity": "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.11.0", + "tr46": "^6.0.0", + "webidl-conversions": "^8.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "dev": true, + "license": "MIT" + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz", + "integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/html-encoding-sniffer": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz", + "integrity": "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.6.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/js2xmlparser": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.2.tgz", + "integrity": "sha512-6n4D8gLlLf1n5mNLQPRfViYzu9RATblzPEtm1SthMX1Pjao0r9YI9nw7ZIfRxQMERS87mcswrg+r/OYrPRX6jA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "xmlcreate": "^2.0.4" + } + }, + "node_modules/jsdoc": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-4.0.5.tgz", + "integrity": "sha512-P4C6MWP9yIlMiK8nwoZvxN84vb6MsnXcHuy7XzVOvQoCizWX5JFCBsWIIWKXBltpoRZXddUOVQmCTOZt9yDj9g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@babel/parser": "^7.20.15", + "@jsdoc/salty": "^0.2.1", + "@types/markdown-it": "^14.1.1", + "bluebird": "^3.7.2", + "catharsis": "^0.9.0", + "escape-string-regexp": "^2.0.0", + "js2xmlparser": "^4.0.2", + "klaw": "^3.0.0", + "markdown-it": "^14.1.0", + "markdown-it-anchor": "^8.6.7", + "marked": "^4.0.10", + "mkdirp": "^1.0.4", + "requizzle": "^0.2.3", + "strip-json-comments": "^3.1.0", + "underscore": "~1.13.2" + }, + "bin": { + "jsdoc": "jsdoc.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/jsdom": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-30.0.1.tgz", + "integrity": "sha512-52v7mUVUfNQVYYqE1lcdaymWL0njO7lTLUog6ZvW2U5KsbiLk/GnZlVJ+qx0xfNJZ6Gn+KSpPNE52vurbxZwrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/css-color": "^6.0.5", + "@asamuzakjp/dom-selector": "^8.3.0", + "@bramus/specificity": "^2.4.2", + "@csstools/css-syntax-patches-for-csstree": "^1.1.7", + "@exodus/bytes": "^1.15.1", + "css-tree": "^3.2.1", + "data-urls": "^7.0.0", + "decimal.js": "^10.6.0", + "html-encoding-sniffer": "^6.0.0", + "is-potential-custom-element-name": "^1.0.1", + "lru-cache": "^11.5.2", + "parse5": "^8.0.1", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^6.0.2", + "undici": "^8.9.0", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^8.0.1", + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^17.1.0", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + }, + "peerDependencies": { + "canvas": "^3.2.3" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/klaw": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/klaw/-/klaw-3.0.0.tgz", + "integrity": "sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.1.9" + } + }, + "node_modules/linkify-it": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.2.tgz", + "integrity": "sha512-ONTm2jCMAVZjgQa/Fy1kScXsuOoF5NPTsoFBdE1KVIZ2vAh/r9+Bqo+0jINCBYnavTPQZz38QzFTme79ENoN3Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/markdown-it" + } + ], + "license": "MIT", + "dependencies": { + "uc.micro": "^2.0.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "11.5.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", + "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/markdown-it": { + "version": "14.3.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.3.0.tgz", + "integrity": "sha512-RCEsPjR+sr0x+AuYp601tKTkgFG4YEPLCzHST3cQ/fhlJkqAkz1L2/Qbp1j9qw5SBwQHFBoW8+hoN5xssOF0Tw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/markdown-it" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1", + "entities": "^4.5.0", + "linkify-it": "^5.0.2", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" + }, + "bin": { + "markdown-it": "bin/markdown-it.mjs" + } + }, + "node_modules/markdown-it-anchor": { + "version": "8.6.7", + "resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-8.6.7.tgz", + "integrity": "sha512-FlCHFwNnutLgVTflOYHPW2pPcl2AACqVzExlkGQNsi4CJgqOHN7YTgDd4LuhgN1BFO3TS0vLAruV1Td6dwWPJA==", + "dev": true, + "license": "Unlicense", + "peerDependencies": { + "@types/markdown-it": "*", + "markdown-it": "*" + } + }, + "node_modules/marked": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", + "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", + "dev": true, + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/mdn-data": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", + "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/mdurl": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.1.0.tgz", + "integrity": "sha512-1+HBaOx0zi/dQWht8rNv9MYf9qqpqL/kxI0hXImU6Y547zM6Sni8BQibt7ifgMcYtQg41ao3Ivd6cnSM86inpg==", + "dev": true, + "license": "MIT" + }, + "node_modules/minimatch": { + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.8" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/parse5": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.1.tgz", + "integrity": "sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^8.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5/node_modules/entities": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-8.0.0.tgz", + "integrity": "sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/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==", + "dev": true, + "license": "ISC" + }, + "node_modules/playwright-core": { + "version": "1.62.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.62.1.tgz", + "integrity": "sha512-wPYSwEBJY9GHraISXqyqtx0na0LpO3XEX7jNDhntbex7tzUS7kLnZsOlFruFJB4Hi/rhDMjXGqHewDZ68nYZVw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/requizzle": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.4.tgz", + "integrity": "sha512-JRrFk1D4OQ4SqovXOgdav+K8EAhSB/LJZqCz8tbX0KObcdeM15Ss59ozWMBWmmINMagCwmqn4ZNryUGpBsl6Jw==", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash": "^4.17.21" + } + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/terser": { + "version": "5.51.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.51.0.tgz", + "integrity": "sha512-myiQ6aFnxDOjdiXdTlC8ngVccQD88uHXTx5RUQOSEnarDYgJMjDagwAQszcOusjvmf4YqWsxoD1+MY+oAJRmpw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.15.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/test-exclude": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.2.tgz", + "integrity": "sha512-u9E6A+ZDYdp7a4WnarkXPZOx8Ilz46+kby6p1yZ8zsGTz9gYa6FIS7lj2oezzNKmtdyyJNNmmXDppga5GB7kSw==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^10.4.1", + "minimatch": "^10.2.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/tldts": { + "version": "7.4.11", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.4.11.tgz", + "integrity": "sha512-aBiNayCfTQxuIJBm06M+xR14cYaYlDlSXZbgsnKzKNxDKUVq7KFwTjwBSsb7m9Y5xO8WfPnBc63WaYFMTGlvqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tldts-core": "^7.4.11" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "7.4.11", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.4.11.tgz", + "integrity": "sha512-CW3WN2rIIE/Of21mulhgnGOwoDyEFNygyIBOONSdyAuSATgMMUCpLeUlB+E8sAwA5xRV9hYPl+kyZ9citHCaKg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tough-cookie": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.2.tgz", + "integrity": "sha512-exgYmnmL/sJpR3upZfXG5PoatXQii55xAiXGXzY+sROLZ/Y+SLcp9PgJNI9Vz37HpQ74WvDcLT8eqm+kV3FzrA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^7.0.5" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tr46": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz", + "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", + "dev": true, + "license": "MIT" + }, + "node_modules/underscore": { + "version": "1.13.8", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.8.tgz", + "integrity": "sha512-DXtD3ZtEQzc7M8m4cXotyHR+FAS18C64asBYY5vqZexfYryNNnDc02W4hKg3rdQuqOYas1jkseX0+nZXjTXnvQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/undici": { + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-8.10.0.tgz", + "integrity": "sha512-HvltHd7avK13QIw/oLe4qoOLyoVSoafqJ2jYOrtMRBkbYT31eiBQ8O0ehRKZiEZCMEyLFQNIADpgCWC5fALvYQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=22.19.0" + } + }, + "node_modules/v8-to-istanbul": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/webidl-conversions": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz", + "integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=20" + } + }, + "node_modules/whatwg-mimetype": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz", + "integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/whatwg-url": { + "version": "17.1.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-17.1.0.tgz", + "integrity": "sha512-3GeworPmc2ZfEEHP7lEbUfBX/L75wdEsi0rLNhXcXxnoN5jyq0SL5gCy06SGW2cyTIZdTvWIDQNQoza++vKeaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.15.1", + "tr46": "^6.0.0", + "webidl-conversions": "^8.0.1" + }, + "engines": { + "node": "^22.14.0 || >=24.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "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==", + "dev": true, + "license": "Apache-2.0", + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/xmlcreate": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.4.tgz", + "integrity": "sha512-nquOebG4sngPmGPICTS5EnxqhKbCmz5Ox5hsszI2T6U5qdrJizBc+0ilYSEjTSzU0yZcmvppztXe/5Al5fUwdg==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "17.7.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", + "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/yargs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/node_modules/@xmldom/xmldom/CHANGELOG.md b/node_modules/@xmldom/xmldom/CHANGELOG.md new file mode 100644 index 000000000..153494a5c --- /dev/null +++ b/node_modules/@xmldom/xmldom/CHANGELOG.md @@ -0,0 +1,1132 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [0.9.12](https://github.com/xmldom/xmldom/compare/0.9.11...0.9.12) + +### Fixed + +- Security: parsing a deeply or repeatedly namespaced document no longer consumes quadratic memory; the in-scope namespace map is inherited through the prototype chain instead of being copied for every prefix-declaring element (O(N) instead of O(N²)), preventing a denial-of-service reachable from `DOMParser.parseFromString` with default options. Serialized output is byte-identical. [`GHSA-965w-775f-mr7g`](https://github.com/xmldom/xmldom/security/advisories/GHSA-965w-775f-mr7g) +- Security: attribute de-duplication during parsing is now O(M) instead of O(M²); the `NamedNodeMap` parse-time dedup path uses a null-prototype membership index, so a well-formed document with a hostile number of duplicate attributes can no longer wedge the parse. Attribute order and duplicate resolution (last value wins, first position kept) are byte-identical, preserving the XML [no-duplicate-attributes well-formedness constraint](https://www.w3.org/TR/xml/#uniqattspec). [`GHSA-8344-3jmq-59r6`](https://github.com/xmldom/xmldom/security/advisories/GHSA-8344-3jmq-59r6) +- Security: HTML raw-text parsing no longer amplifies output on a missing or case-mismatched closing tag; the closing tag is matched case-insensitively per the WHATWG HTML [RAWTEXT end-tag rule](https://html.spec.whatwg.org/multipage/parsing.html#rawtext-end-tag-name-state) and a missing closing tag is handled explicitly, preventing a denial-of-service. Output for well-formed input is unchanged. [`GHSA-6mj3-qw4j-hgrw`](https://github.com/xmldom/xmldom/security/advisories/GHSA-6mj3-qw4j-hgrw) +- Security: malformed-input recovery is now linear instead of quadratic — the malformed tag-name scan terminates at an embedded `<`, and `Node.prototype.normalize()` merges adjacent text nodes in O(K) instead of O(K²) (also reachable programmatically), per [`normalize()`](https://dom.spec.whatwg.org/#dom-node-normalize) in the WHATWG DOM spec. DOM output is unchanged; only the reported error text differs. [`GHSA-93r5-fhx6-vmg9`](https://github.com/xmldom/xmldom/security/advisories/GHSA-93r5-fhx6-vmg9) +- Security: `XMLSerializer.serializeToString()` under `{ requireWellFormed: true }` now rejects a DocType `name` that is not a valid XML [`Name`](https://www.w3.org/TR/xml/#NT-Name), throwing `InvalidStateError` — matching the sibling `publicId`/`systemId`/`internalSubset` checks and preventing XML injection via `DocumentType.name`. [`GHSA-27p8-2357-5qqv`](https://github.com/xmldom/xmldom/security/advisories/GHSA-27p8-2357-5qqv) +- Security: `XMLSerializer.serializeToString()` under `{ requireWellFormed: true }` now validates a processing-instruction target as an XML [`NCName`](https://www.w3.org/TR/xml-names/#NT-NCName) and rejects a case-insensitive `xml`, throwing `InvalidStateError` — preventing PI-target injection via `>`, `?`, or whitespace. [`GHSA-c7q8-3ch8-vqpv`](https://github.com/xmldom/xmldom/security/advisories/GHSA-c7q8-3ch8-vqpv) +- Security: `Document.createEntityReference()` now rejects an invalid XML [`Name`](https://www.w3.org/TR/xml/#NT-Name) at creation, and `XMLSerializer.serializeToString()` under `{ requireWellFormed: true }` validates an `EntityReference` `nodeName` as an XML `Name`, throwing `InvalidStateError` — preventing XML injection via an entity-reference name. [`GHSA-6gmq-8vp8-gcm6`](https://github.com/xmldom/xmldom/security/advisories/GHSA-6gmq-8vp8-gcm6) +- Security: the `requireWellFormed` serializer's element- and attribute-name validators no longer treat an interior line terminator as satisfying the name anchors, so a name containing a line terminator is rejected with `InvalidStateError` — closing a bypass of the XML [`QName`](https://www.w3.org/TR/xml-names/#NT-QName) check. [`GHSA-jxjr-3g7g-3944`](https://github.com/xmldom/xmldom/security/advisories/GHSA-jxjr-3g7g-3944) +- Security: the `requireWellFormed` serializer's DocType `publicId`/`systemId` validators no longer treat an interior line terminator as satisfying the anchor, so an identifier containing an ECMAScript line terminator is rejected with `InvalidStateError` — closing a bypass of the XML [`PubidLiteral`](https://www.w3.org/TR/xml/#NT-PubidLiteral)/`SystemLiteral` check. [`GHSA-vr34-hp96-76pp`](https://github.com/xmldom/xmldom/security/advisories/GHSA-vr34-hp96-76pp) +- Security: `createElementNS()`, `createAttributeNS()`, `createDocumentType()`, and `createAttribute()` now reject a name containing a line terminator with `InvalidCharacterError`, because name validation applies to the whole string — closing a creation-time bypass of the XML [`Name`/`QName`](https://www.w3.org/TR/xml-names/#NT-QName) production on the default serialization path. [`GHSA-3px3-54cx-rmw9`](https://github.com/xmldom/xmldom/security/advisories/GHSA-3px3-54cx-rmw9) +- Security: the parser now reports a not-well-formed end tag whose valid name is followed by trailing content (a recoverable `error` in XML, a `warning` in HTML) instead of accepting it silently, per the XML [`ETag`](https://www.w3.org/TR/xml/#NT-ETag) production; parsing recovers to the byte-identical DOM. Consumers that want strict rejection can escalate the reported `error` to fatal via the parser's `onError` handler. [`GHSA-6h8r-xr42-gp59`](https://github.com/xmldom/xmldom/security/advisories/GHSA-6h8r-xr42-gp59) +- `DOMException`s raised during parsing are now reported as a `fatalError`, and the originating error is preserved as the `cause` on the resulting `ParseError`. + +### Chore + +- updated dependencies + +Thank you, +[@ericchiang](https://github.com/ericchiang), +[@KarimTantawey](https://github.com/KarimTantawey), +[@bhaswanthc](https://github.com/bhaswanthc), +[@arpitjain099](https://github.com/arpitjain099), +[@Paranoidgrinch](https://github.com/Paranoidgrinch), +for your contributions + +## [0.8.15](https://github.com/xmldom/xmldom/compare/0.8.14...0.8.15) + +### Fixed + +- Security: parsing a deeply or repeatedly namespaced document no longer consumes quadratic memory; the in-scope namespace map is inherited through the prototype chain instead of being copied for every prefix-declaring element (O(N) instead of O(N²)), preventing a denial-of-service reachable from `DOMParser.parseFromString` with default options. Serialized output is byte-identical. [`GHSA-965w-775f-mr7g`](https://github.com/xmldom/xmldom/security/advisories/GHSA-965w-775f-mr7g) +- Security: attribute de-duplication during parsing is now O(M) instead of O(M²); the `NamedNodeMap` parse-time dedup path uses a null-prototype membership index, so a well-formed document with a hostile number of duplicate attributes can no longer wedge the parse. Attribute order and duplicate resolution (last value wins, first position kept) are byte-identical, preserving the XML [no-duplicate-attributes well-formedness constraint](https://www.w3.org/TR/xml/#uniqattspec). [`GHSA-8344-3jmq-59r6`](https://github.com/xmldom/xmldom/security/advisories/GHSA-8344-3jmq-59r6) +- Security: trimming trailing whitespace from an XML end tag ([`ETag`](https://www.w3.org/TR/xml/#NT-ETag)) is now anchored so it runs in linear time instead of backtracking quadratically on a long whitespace run, preventing a ReDoS reachable from `DOMParser.parseFromString`. Trimmed output is byte-identical. [`GHSA-x4fp-j954-r2f4`](https://github.com/xmldom/xmldom/security/advisories/GHSA-x4fp-j954-r2f4) +- Security: malformed-input recovery is now linear instead of quadratic — the malformed tag-name scan terminates at an embedded `<`, and `Node.prototype.normalize()` merges adjacent text nodes in O(K) instead of O(K²) (also reachable programmatically), per [`normalize()`](https://dom.spec.whatwg.org/#dom-node-normalize) in the WHATWG DOM spec. DOM output is unchanged; only the reported error text differs. [`GHSA-93r5-fhx6-vmg9`](https://github.com/xmldom/xmldom/security/advisories/GHSA-93r5-fhx6-vmg9) +- Security: `XMLSerializer.serializeToString()` under `{ requireWellFormed: true }` now rejects a DocType `name` that is not a valid XML [`Name`](https://www.w3.org/TR/xml/#NT-Name), throwing `InvalidStateError` — matching the sibling `publicId`/`systemId`/`internalSubset` checks and preventing XML injection via `DocumentType.name`. [`GHSA-27p8-2357-5qqv`](https://github.com/xmldom/xmldom/security/advisories/GHSA-27p8-2357-5qqv) +- Security: `XMLSerializer.serializeToString()` under `{ requireWellFormed: true }` now validates a processing-instruction target as an XML [`NCName`](https://www.w3.org/TR/xml-names/#NT-NCName) and rejects a case-insensitive `xml`, throwing `InvalidStateError` — a check `0.8.x` did not previously perform, preventing PI-target injection via `>`, `?`, or whitespace. [`GHSA-c7q8-3ch8-vqpv`](https://github.com/xmldom/xmldom/security/advisories/GHSA-c7q8-3ch8-vqpv) +- Security: `Document.createEntityReference()` now rejects an invalid XML [`Name`](https://www.w3.org/TR/xml/#NT-Name) at creation, and `XMLSerializer.serializeToString()` under `{ requireWellFormed: true }` validates an `EntityReference` `nodeName` as an XML `Name`, throwing `InvalidStateError` — preventing XML injection via an entity-reference name. [`GHSA-6gmq-8vp8-gcm6`](https://github.com/xmldom/xmldom/security/advisories/GHSA-6gmq-8vp8-gcm6) +- Security: the parser now reports a not-well-formed end tag whose valid name is followed by trailing content as a recoverable `error` instead of accepting it silently, per the XML [`ETag`](https://www.w3.org/TR/xml/#NT-ETag) production; parsing recovers to the byte-identical DOM. Consumers that want strict rejection can escalate the reported `error` to fatal via the parser's `errorHandler`. [`GHSA-6h8r-xr42-gp59`](https://github.com/xmldom/xmldom/security/advisories/GHSA-6h8r-xr42-gp59) + +Thank you, +[@ericchiang](https://github.com/ericchiang), +[@bhaswanthc](https://github.com/bhaswanthc), +[@arpitjain099](https://github.com/arpitjain099), +[@Paranoidgrinch](https://github.com/Paranoidgrinch), +for your contributions + +## [0.9.11](https://github.com/xmldom/xmldom/compare/0.9.10...0.9.11) + +### Fixed + +- Security: `XMLSerializer.serializeToString()` now also rejects invalid element and attribute names when `{ requireWellFormed: true }` is passed, throwing `InvalidStateError` for a name that is not a valid XML [`QName`](https://www.w3.org/TR/xml-names/#NT-QName) (this covers the namespace prefix, which surfaces in the element qualified name or in a synthesized `xmlns:` declaration). This prevents XML injection via `createElement()` / `setAttribute()`, extending the existing `requireWellFormed` checks to the serialized name set. [`GHSA-w2rr-34g9-rvrj`](https://github.com/xmldom/xmldom/security/advisories/GHSA-w2rr-34g9-rvrj) [`GHSA-4w3w-2rp5-g8jm`](https://github.com/xmldom/xmldom/security/advisories/GHSA-4w3w-2rp5-g8jm) +- Security: the processing-instruction grammar regex no longer backtracks quadratically on an unterminated processing instruction (``), preventing a denial-of-service (ReDoS) reachable from `DOMParser.parseFromString` with default options. [`GHSA-g53g-w8rj-fmg7`](https://github.com/xmldom/xmldom/security/advisories/GHSA-g53g-w8rj-fmg7) +- `CharacterData` `nodeValue` and `data` are now kept in sync [`#990`](https://github.com/xmldom/xmldom/pull/990) + +### Chore + +- updated dependencies + +Thank you, +[@bhaswanthc](https://github.com/bhaswanthc), +[@jmestwa-coder](https://github.com/jmestwa-coder), +[@stevenobiajulu](https://github.com/stevenobiajulu), +for your contributions + +## [0.8.14](https://github.com/xmldom/xmldom/compare/0.8.13...0.8.14) + +### Fixed + +- Security: `XMLSerializer.serializeToString()` now also rejects invalid element and attribute names when `{ requireWellFormed: true }` is passed, throwing `InvalidStateError` for a name that is not a valid XML [`QName`](https://www.w3.org/TR/xml-names/#NT-QName) (this covers the namespace prefix, which surfaces in the element qualified name or in a synthesized `xmlns:` declaration). This prevents XML injection via `createElement()` / `setAttribute()`, extending the existing `requireWellFormed` checks to the serialized name set. [`GHSA-w2rr-34g9-rvrj`](https://github.com/xmldom/xmldom/security/advisories/GHSA-w2rr-34g9-rvrj) [`GHSA-4w3w-2rp5-g8jm`](https://github.com/xmldom/xmldom/security/advisories/GHSA-4w3w-2rp5-g8jm) + +Thank you, +[@bhaswanthc](https://github.com/bhaswanthc), +[@jmestwa-coder](https://github.com/jmestwa-coder), +for your contributions + +## [0.9.10](https://github.com/xmldom/xmldom/compare/0.9.9...0.9.10) + +### Fixed + +- Security: `XMLSerializer.serializeToString()` (and `Node.toString()`, `NodeList.toString()`) now accept a `requireWellFormed` option. When `{ requireWellFormed: true }` is passed, the serializer throws `InvalidStateError` for injection-prone node content, preventing XML injection via attacker-controlled node data. [`GHSA-j759-j44w-7fr8`](https://github.com/xmldom/xmldom/security/advisories/GHSA-j759-j44w-7fr8) [`GHSA-x6wf-f3px-wcqx`](https://github.com/xmldom/xmldom/security/advisories/GHSA-x6wf-f3px-wcqx) [`GHSA-f6ww-3ggp-fr8h`](https://github.com/xmldom/xmldom/security/advisories/GHSA-f6ww-3ggp-fr8h) + - Comment: throws when `data` contains `--` anywhere, ends with `-`, or contains characters outside the XML `Char` production + - ProcessingInstruction: throws when target contains `:` or matches `xml` (case-insensitive), or `data` contains characters outside the XML `Char` production or contains `?>` + - DocumentType: throws when `publicId` fails `PubidLiteral`, `systemId` fails `SystemLiteral`, or `internalSubset` contains `]>` +- Security: DOM traversal operations (`XMLSerializer.serializeToString()`, `Node.prototype.normalize()`, `Node.prototype.cloneNode(true)`, `Document.prototype.importNode(node, true)`, `node.textContent` getter, `getElementsByTagName()` / `getElementsByTagNameNS()` / `getElementsByClassName()` / `getElementById()`, `Node.prototype.isEqualNode()`) are now iterative. Previously, deeply nested DOM trees would exhaust the JavaScript call stack and throw an unrecoverable `RangeError`. [`GHSA-2v35-w6hq-6mfw`](https://github.com/xmldom/xmldom/security/advisories/GHSA-2v35-w6hq-6mfw) +- `isEqualNode` now correctly returns `false` for CDATASection nodes with different `data` + +### Deprecated + +- The `splitCDATASections` serializer option is deprecated and will be removed in the next breaking release. The automatic splitting of `"]]>"` in `CDATASection` data was introduced as a workaround; use `requireWellFormed: true` or ensure `CDATASection` data does not contain `"]]>"` before serialization. + +### Chore + +- updated dependencies + +Thank you, +[@Jvr2022](https://github.com/Jvr2022), +[@praveen-kv](https://github.com/praveen-kv), +[@TharVid](https://github.com/TharVid), +[@decsecre583](https://github.com/decsecre583), +[@tlsbollei](https://github.com/tlsbollei), +[@KarimTantawey](https://github.com/KarimTantawey), +for your contributions + +## [0.8.13](https://github.com/xmldom/xmldom/compare/0.8.12...0.8.13) + +### Fixed + +- Security: `XMLSerializer.serializeToString()` (and `Node.toString()`, `NodeList.toString()`) now accept a `requireWellFormed` option (fourth argument, after `isHtml` and `nodeFilter`). When `{ requireWellFormed: true }` is passed, the serializer throws `InvalidStateError` for injection-prone node content, preventing XML injection via attacker-controlled node data. [`GHSA-j759-j44w-7fr8`](https://github.com/xmldom/xmldom/security/advisories/GHSA-j759-j44w-7fr8) [`GHSA-x6wf-f3px-wcqx`](https://github.com/xmldom/xmldom/security/advisories/GHSA-x6wf-f3px-wcqx) [`GHSA-f6ww-3ggp-fr8h`](https://github.com/xmldom/xmldom/security/advisories/GHSA-f6ww-3ggp-fr8h) + - Comment: throws when `data` contains `-->` + - ProcessingInstruction: throws when `data` contains `?>` + - DocumentType: throws when `publicId` fails `PubidLiteral`, `systemId` fails `SystemLiteral`, or `internalSubset` contains `]>` +- Security: DOM traversal operations (`XMLSerializer.serializeToString()`, `Node.prototype.normalize()`, `Node.prototype.cloneNode(true)`, `Document.prototype.importNode(node, true)`, `node.textContent` getter, `getElementsByTagName()` / `getElementsByTagNameNS()` / `getElementsByClassName()` / `getElementById()`) are now iterative. Previously, deeply nested DOM trees would exhaust the JavaScript call stack and throw an unrecoverable `RangeError`. [`GHSA-2v35-w6hq-6mfw`](https://github.com/xmldom/xmldom/security/advisories/GHSA-2v35-w6hq-6mfw) + +Thank you, +[@Jvr2022](https://github.com/Jvr2022), +[@praveen-kv](https://github.com/praveen-kv), +[@TharVid](https://github.com/TharVid), +[@decsecre583](https://github.com/decsecre583), +[@tlsbollei](https://github.com/tlsbollei), +[@KarimTantawey](https://github.com/KarimTantawey), +for your contributions + + +## [0.9.9](https://github.com/xmldom/xmldom/compare/0.9.8...0.9.9) + +### Added + +- implement `ParentNode.children` getter [`#960`](https://github.com/xmldom/xmldom/pull/960) / [`#410`](https://github.com/xmldom/xmldom/issues/410) + +### Fixed + +- Security: `createCDATASection` now throws `InvalidCharacterError` when `data` contains `"]]>"`, as required by the [WHATWG DOM spec](https://dom.spec.whatwg.org/#dom-document-createcdatasection). [`GHSA-wh4c-j3r5-mjhp`](https://github.com/xmldom/xmldom/security/advisories/GHSA-wh4c-j3r5-mjhp) +- Security: `XMLSerializer` now splits CDATASection nodes whose data contains `"]]>"` into adjacent CDATA sections at serialization time, preventing XML injection via mutation methods (`appendData`, `replaceData`, `.data =`, `.textContent =`). [`GHSA-wh4c-j3r5-mjhp`](https://github.com/xmldom/xmldom/security/advisories/GHSA-wh4c-j3r5-mjhp) +- correctly traverse ancestor chain in `Node.contains` [`#931`](https://github.com/xmldom/xmldom/pull/931) + +Code that passes a string containing `"]]>"` to `createCDATASection` and relied on the previously unsafe behavior will now receive `InvalidCharacterError`. Use a mutation method such as `appendData` if you intentionally need `"]]>"` in a CDATASection node's data. + +### Chore + +- updated dependencies + +Thank you, +[@stevenobiajulu](https://github.com/stevenobiajulu), +[@yoshi389111](https://github.com/yoshi389111), +[@thesmartshadow](https://github.com/thesmartshadow), +for your contributions + + +## [0.8.12](https://github.com/xmldom/xmldom/compare/0.8.11...0.8.12) + +### Fixed + +- preserve trailing whitespace in ProcessingInstruction data [`#962`](https://github.com/xmldom/xmldom/pull/962) / [`#42`](https://github.com/xmldom/xmldom/issues/42) +- Security: `createCDATASection` now throws `InvalidCharacterError` when `data` contains `"]]>"`, as required by the [WHATWG DOM spec](https://dom.spec.whatwg.org/#dom-document-createcdatasection). [`GHSA-wh4c-j3r5-mjhp`](https://github.com/xmldom/xmldom/security/advisories/GHSA-wh4c-j3r5-mjhp) +- Security: `XMLSerializer` now splits CDATASection nodes whose data contains `"]]>"` into adjacent CDATA sections at serialization time, preventing XML injection via mutation methods (`appendData`, `replaceData`, `.data =`, `.textContent =`). [`GHSA-wh4c-j3r5-mjhp`](https://github.com/xmldom/xmldom/security/advisories/GHSA-wh4c-j3r5-mjhp) + +Code that passes a string containing `"]]>"` to `createCDATASection` and relied on the previously unsafe behavior will now receive `InvalidCharacterError`. Use a mutation method such as `appendData` if you intentionally need `"]]>"` in a CDATASection node's data. + +Thank you, +[@thesmartshadow](https://github.com/thesmartshadow), +[@stevenobiajulu](https://github.com/stevenobiajulu), +for your contributions + +## [0.8.11](https://github.com/xmldom/xmldom/compare/0.8.10...0.8.11) + +### Fixed + +- update `ownerDocument` when moving nodes between documents [`#933`](https://github.com/xmldom/xmldom/pull/933) / [`#932`](https://github.com/xmldom/xmldom/issues/932) + +Thank you, [@shunkica](https://github.com/shunkica), for your contributions + +## [0.9.8](https://github.com/xmldom/xmldom/compare/0.9.7...0.9.8) + +### Fixed + +- fix: replace \u2029 as part of normalizeLineEndings [`#839`](https://github.com/xmldom/xmldom/pull/839) / [`#838`](https://github.com/xmldom/xmldom/issues/838) +- perf: speed up line detection [`#847`](https://github.com/xmldom/xmldom/pull/847) / [`#838`](https://github.com/xmldom/xmldom/issues/838) + +### Chore + +- updated dependencies +- drop jazzer and rxjs devDependencies [`#845`](https://github.com/xmldom/xmldom/pull/845) + +Thank you, +[@kboshold](https://github.com/kboshold), +[@Ponynjaa](https://github.com/Ponynjaa), +for your contributions. + + +## [0.9.7](https://github.com/xmldom/xmldom/compare/0.9.6...0.9.7) + +### Added + +- Implementation of `hasAttributes` [`#804`](https://github.com/xmldom/xmldom/pull/804) + +### Fixed + +- locator is now true even when other options are being used for the DOMParser [`#802`](https://github.com/xmldom/xmldom/issues/802) / [`#803`](https://github.com/xmldom/xmldom/pull/803) +- allow case-insensitive DOCTYPE in HTML [`#817`](https://github.com/xmldom/xmldom/issues/817) / [`#819`](https://github.com/xmldom/xmldom/pull/819) + +### Performance + +- simplify `DOM.compareDocumentPosition` [`#805`](https://github.com/xmldom/xmldom/pull/805) + +### Chore + +- updated devDependencies + +Thank you, +[@zorkow](https://github.com/zorkow), +[@Ponynjaa](https://github.com/Ponynjaa), +[@WesselKroos](https://github.com/WesselKroos), +for your contributions. + + +## [0.9.6](https://github.com/xmldom/xmldom/compare/0.9.5...0.9.6) + +### Fixed + +- lower error level for unicode replacement character [`#790`](https://github.com/xmldom/xmldom/issues/790) / [`#794`](https://github.com/xmldom/xmldom/pull/794) / [`#797`](https://github.com/xmldom/xmldom/pull/797) + +### Chore + +- updated devDependencies +- migrate renovate config [`#792`](https://github.com/xmldom/xmldom/pull/792) + +Thank you, [@eglitise](https://github.com/eglitise), for your contributions. + + +## [0.9.5](https://github.com/xmldom/xmldom/compare/0.9.4...0.9.5) + +### Fixed + +- fix: re-index childNodes on insertBefore [`#763`](https://github.com/xmldom/xmldom/issues/763) / [`#766`](https://github.com/xmldom/xmldom/pull/766) + +Thank you, +[@mureinik](https://github.com/mureinik), +for your contributions. + + +## [0.9.4](https://github.com/xmldom/xmldom/compare/0.9.3...0.9.4) + +### Fixed + +- restore performance for large amount of child nodes [`#748`](https://github.com/xmldom/xmldom/issues/748) / [`#760`](https://github.com/xmldom/xmldom/pull/760) +- types: correct error handler level to `warning` (#759) [`#754`](https://github.com/xmldom/xmldom/issues/754) / [`#759`](https://github.com/xmldom/xmldom/pull/759) + +### Docs + +- test: verify BOM handling [`#758`](https://github.com/xmldom/xmldom/pull/758) + +Thank you, +[@luffynando](https://github.com/luffynando), +[@mattiasw](https://github.com/mattiasw), +[@JoinerDev](https://github.com/JoinerDev), +for your contributions. + + +## [0.9.3](https://github.com/xmldom/xmldom/compare/0.9.2...0.9.3) + +### Fixed + +- restore more `Node` and `ProcessingInstruction` types [`#725`](https://github.com/xmldom/xmldom/issues/725) / [`#726`](https://github.com/xmldom/xmldom/pull/726) +- `getElements*` methods return `LiveNodeList<Element>` [`#731`](https://github.com/xmldom/xmldom/issues/731) / [`#734`](https://github.com/xmldom/xmldom/pull/734) +- Add more missing `Node` props [`#728`](https://github.com/xmldom/xmldom/pull/728), triggered by unclosed [`#724`](https://github.com/xmldom/xmldom/pull/724) + +### Docs + +- Update supported runtimes in readme (NodeJS >= 14.6 and other [ES5 compatible runtimes](https://compat-table.github.io/compat-table/es5/)) + +### Chore + +- updates devDependencies + +Thank you, +[@Ponynjaa](https://github.com/Ponynjaa), +[@ayZagen](https://github.com/ayZagen), +[@sserdyuk](https://github.com/sserdyuk), +[@wydengyre](https://github.com/wydengyre), +[@mykola-mokhnach](https://github.com/mykola-mokhnach), +[@benkroeger](https://github.com/benkroeger), +for your contributions. + + +## [0.9.2](https://github.com/xmldom/xmldom/compare/0.9.1...0.9.2) + +### Feature + +- add `Element.getElementsByClassName` [`#722`](https://github.com/xmldom/xmldom/pull/722) + +### Fixed + +- add missing types for `Document.documentElement` and `Element.tagName` [`#721`](https://github.com/xmldom/xmldom/pull/721) [`#720`](https://github.com/xmldom/xmldom/issues/720) + +Thank you, [@censujiang](https://github.com/censujiang), [@Mathias-S](https://github.com/Mathias-S), for your contributions + + +## [0.9.1](https://github.com/xmldom/xmldom/compare/0.9.0...0.9.1) + +### Fixed + +- DOMParser.parseFromString requires mimeType as second argument [`#713`](https://github.com/xmldom/xmldom/pull/713) +- correct spelling of `isHTMLMimeType` in type definition [`#715`](https://github.com/xmldom/xmldom/pull/715) / [`#712`](https://github.com/xmldom/xmldom/issues/712) +- sync types with exports [`#717`](https://github.com/xmldom/xmldom/pull/717) / [`#285`](https://github.com/xmldom/xmldom/issues/285) / [`#695`](https://github.com/xmldom/xmldom/issues/695) + +### Other + +- minimum tested node version is 14 [`#710`](https://github.com/xmldom/xmldom/pull/710) + +Thank you, [@krystofwoldrich](https://github.com/krystofwoldrich), [@marvinruder](https://github.com/marvinruder), [@amacneil](https://github.com/amacneil), [@defunctzombie](https://github.com/defunctzombie), +[@tjhorner](https://github.com/tjhorner), [@danon](https://github.com/danon), for your contributions + + +## [0.9.0](https://github.com/xmldom/xmldom/compare/0.9.0-beta.11...0.9.0) + +- [Discussion](https://github.com/xmldom/xmldom/discussions/435) +- [Summary on dev.to](https://dev.to/karfau/release-090-of-xmldomxmldom-4106) + +### Features + +- feat: expose all DOM level 2 element prototypes [`#637`](https://github.com/xmldom/xmldom/pull/637) / [`#40`](https://github.com/xmldom/xmldom/issues/40) +- feat: add iterator function to NodeList and NamedNodeMap [`#634`](https://github.com/xmldom/xmldom/pull/634) / [`#633`](https://github.com/xmldom/xmldom/issues/633) + +### Fixed + +- parse empty/whitspace only doctype internal subset [`#692`](https://github.com/xmldom/xmldom/pull/692) +- avoid prototype clash in namespace prefix [`#554`](https://github.com/xmldom/xmldom/pull/554) +- report fatalError when doctype is inside elements [`#550`](https://github.com/xmldom/xmldom/pull/550) + +### Other + +- test: add fuzz target and regression tests [`#556`](https://github.com/xmldom/xmldom/pull/556) +- chore: improve .gitignore and provide .envrc.template [`#697`](https://github.com/xmldom/xmldom/pull/697) +- chore: Apply security best practices [`#546`](https://github.com/xmldom/xmldom/pull/546) +- ci: check test coverage in PRs [`#524`](https://github.com/xmldom/xmldom/pull/524) +- docs: add missing commas to readme [`#566`](https://github.com/xmldom/xmldom/pull/566) +- docs: click to copy install command in readme [`#644`](https://github.com/xmldom/xmldom/pull/644) +- docs: enhance jsdoc comments [`#511`](https://github.com/xmldom/xmldom/pull/511) + +Thank you, [@kboshold](https://github.com/kboshold), [@edi9999](https://github.com/edi9999), [@apupier](https://github.com/apupier), +[@shunkica](https://github.com/shunkica), [@homer0](https://github.com/homer0), [@jhauga](https://github.com/jhauga), +[@UdayKharatmol](https://github.com/UdayKharatmol), for your contributions + + +## [0.9.0-beta.11](https://github.com/xmldom/xmldom/compare/0.9.0-beta.10...0.9.0-beta.11) + +### Fixed + +- report more non well-formed cases [`#519`](https://github.com/xmldom/xmldom/pull/519) / [`#45`](https://github.com/xmldom/xmldom/issues/45) / [`#125`](https://github.com/xmldom/xmldom/issues/125) / [`#467`](https://github.com/xmldom/xmldom/issues/467) + BREAKING-CHANGE: Reports more not well-formed documents as fatalError + and drop broken support for optional and unclosed tags in HTML. + +### Other + +- Translate/drop non English comments [`#518`](https://github.com/xmldom/xmldom/pull/518) +- use node v16 for development [`#517`](https://github.com/xmldom/xmldom/pull/517) + +Thank you, [@brodybits](https://github.com/brodybits), [@cbettinger](https://github.com/cbettinger), [@josecarlosrx](https://github.com/josecarlosrx), for your contributions + + +## [0.9.0-beta.10](https://github.com/xmldom/xmldom/compare/0.9.0-beta.9...0.9.0-beta.10) + +### Fixed + +- dom: prevent iteration over deleted items [`#514`](https://github.com/xmldom/xmldom/pull/514)/ [`#499`](https://github.com/xmldom/xmldom/issues/499) + +### Chore + +- use prettier plugin for jsdoc [`#513`](https://github.com/xmldom/xmldom/pull/513) + +Thank you, [@qtow](https://github.com/qtow), [@shunkica](https://github.com/shunkica), [@homer0](https://github.com/homer0), for your contributions + + +## [0.8.10](https://github.com/xmldom/xmldom/compare/0.8.9...0.8.10) + +### Fixed + +- dom: prevent iteration over deleted items [`#514`](https://github.com/xmldom/xmldom/pull/514)/ [`#499`](https://github.com/xmldom/xmldom/issues/499) + +Thank you, [@qtow](https://github.com/qtow), for your contributions + + +## [0.7.13](https://github.com/xmldom/xmldom/compare/0.7.12...0.7.13) + +### Fixed + +- dom: prevent iteration over deleted items [`#514`](https://github.com/xmldom/xmldom/pull/514)/ [`#499`](https://github.com/xmldom/xmldom/issues/499) + +Thank you, [@qtow](https://github.com/qtow), for your contributions + + +## [0.9.0-beta.9](https://github.com/xmldom/xmldom/compare/0.9.0-beta.8...0.9.0-beta.9) + +### Fixed + +- Set nodeName property in ProcessingInstruction [`#509`](https://github.com/xmldom/xmldom/pull/509) / [`#505`](https://github.com/xmldom/xmldom/issues/505) +- preserve DOCTYPE internal subset [`#498`](https://github.com/xmldom/xmldom/pull/498) / [`#497`](https://github.com/xmldom/xmldom/pull/497) / [`#117`](https://github.com/xmldom/xmldom/issues/117)\ + BREAKING CHANGES: Many documents that were previously accepted by xmldom, esecially non well-formed ones are no longer accepted. Some issues that were formerly reported as errors are now a fatalError. +- DOMParser: Align parseFromString errors with specs [`#454`](https://github.com/xmldom/xmldom/pull/454) + +### Chore + +- stop running mutation tests using stryker [`#496`](https://github.com/xmldom/xmldom/pull/496) +- make `toErrorSnapshot` windows compatible [`#503`](https://github.com/xmldom/xmldom/pull/503) + +Thank you, [@cjbarth](https://github.com/cjbarth), [@shunkica](https://github.com/shunkica), [@pmahend1](https://github.com/pmahend1), [@niklasl](https://github.com/niklasl), for your contributions + + +## [0.8.9](https://github.com/xmldom/xmldom/compare/0.8.8...0.8.9) + +### Fixed + +- Set nodeName property in ProcessingInstruction [`#509`](https://github.com/xmldom/xmldom/pull/509) / [`#505`](https://github.com/xmldom/xmldom/issues/505) + +Thank you, [@cjbarth](https://github.com/cjbarth), for your contributions + + +## [0.7.12](https://github.com/xmldom/xmldom/compare/0.7.11...0.7.12) + +### Fixed + +- Set nodeName property in ProcessingInstruction [`#509`](https://github.com/xmldom/xmldom/pull/509) / [`#505`](https://github.com/xmldom/xmldom/issues/505) + +Thank you, [@cjbarth](https://github.com/cjbarth), for your contributions + + +## [0.9.0-beta.8](https://github.com/xmldom/xmldom/compare/0.9.0-beta.7...0.9.0-beta.8) + +### Fixed + +- Throw DOMException when calling removeChild with invalid parameter [`#494`](https://github.com/xmldom/xmldom/pull/494) / [`#135`](https://github.com/xmldom/xmldom/issues/135) + +BREAKING CHANGE: Previously it was possible (but not documented) to call `Node.removeChild` with any node in the tree, +and with certain exceptions, it would work. This is no longer the case: calling `Node.removeChild` with an argument that is not a direct child of the node that it is called from, will throw a NotFoundError DOMException, as it is described by the specs. + +Thank you, [@noseworthy](https://github.com/noseworthy), [@davidmc24](https://github.com/davidmc24), for your contributions + + +## [0.9.0-beta.7](https://github.com/xmldom/xmldom/compare/0.9.0-beta.6...0.9.0-beta.7) + +### Feature + +- Add `compareDocumentPosition` method from level 3 spec. [`#488`](https://github.com/xmldom/xmldom/pull/488) + +### Fixed + +- `getAttribute` and `getAttributeNS` should return `null` (#477) [`#46`](https://github.com/xmldom/xmldom/issues/46) +- several issues in NamedNodeMap and Element (#482) [`#46`](https://github.com/xmldom/xmldom/issues/46) +- properly parse closing where the last attribute has no value [`#485`](https://github.com/xmldom/xmldom/pull/485) / [`#486`](https://github.com/xmldom/xmldom/issues/486) +- extend list of HTML entities [`#489`](https://github.com/xmldom/xmldom/pull/489) + +BREAKING CHANGE: Iteration over attributes now happens in the right order and non-existing attributes now return `null` instead of undefined. THe same is true for the `namepsaceURI` and `prefix` of Attr nodes. +All of the changes are fixing misalignment with the DOM specs, so if you expected it to work as specified, +nothing should break for you. + +### Chore + +- update multiple devDependencies +- Configure jest (correctly) and wallaby [`#481`](https://github.com/xmldom/xmldom/pull/481) / [`#483`](https://github.com/xmldom/xmldom/pull/483) + +Thank you, [@bulandent](https://github.com/bulandent), [@zorkow](https://github.com/zorkow), for your contributions + + +## [0.8.8](https://github.com/xmldom/xmldom/compare/0.8.7...0.8.8) + +### Fixed + +- extend list of HTML entities [`#489`](https://github.com/xmldom/xmldom/pull/489) + +Thank you, [@zorkow](https://github.com/zorkow), for your contributions + +## [0.7.11](https://github.com/xmldom/xmldom/compare/0.7.10...0.7.11) + +### Fixed + +- extend list of HTML entities [`#489`](https://github.com/xmldom/xmldom/pull/489) + +Thank you, [@zorkow](https://github.com/zorkow), for your contributions + + +## [0.8.7](https://github.com/xmldom/xmldom/compare/0.8.6...0.8.7) + +### Fixed + +- properly parse closing where the last attribute has no value [`#485`](https://github.com/xmldom/xmldom/pull/485) / [`#486`](https://github.com/xmldom/xmldom/issues/486) + +Thank you, [@bulandent](https://github.com/bulandent), for your contributions + + +## [0.7.10](https://github.com/xmldom/xmldom/compare/0.7.9...0.7.10) + +### Fixed + +- properly parse closing where the last attribute has no value [`#485`](https://github.com/xmldom/xmldom/pull/485) / [`#486`](https://github.com/xmldom/xmldom/issues/486) + +Thank you, [@bulandent](https://github.com/bulandent), for your contributions + + +## [0.8.6](https://github.com/xmldom/xmldom/compare/0.8.5...0.8.6) + +### Fixed + +- Properly check nodes before replacement [`#457`](https://github.com/xmldom/xmldom/pull/457) / [`#455`](https://github.com/xmldom/xmldom/issues/455) / [`#456`](https://github.com/xmldom/xmldom/issues/456) + +Thank you, [@edemaine](https://github.com/edemaine), [@pedro-l9](https://github.com/pedro-l9), for your contributions + + +## [0.7.9](https://github.com/xmldom/xmldom/compare/0.7.8...0.7.9) + +### Fixed + +- Properly check nodes before replacement [`#457`](https://github.com/xmldom/xmldom/pull/457) / [`#455`](https://github.com/xmldom/xmldom/issues/455) / [`#456`](https://github.com/xmldom/xmldom/issues/456) + +Thank you, [@edemaine](https://github.com/edemaine), [@pedro-l9](https://github.com/pedro-l9), for your contributions + + +## [0.9.0-beta.6](https://github.com/xmldom/xmldom/compare/0.9.0-beta.5...0.9.0-beta.6) + +### Fixed + +- Properly check nodes before replacement [`#457`](https://github.com/xmldom/xmldom/pull/457) / [`#455`](https://github.com/xmldom/xmldom/issues/455) / [`#456`](https://github.com/xmldom/xmldom/issues/456) + +Thank you, [@edemaine](https://github.com/edemaine), [@pedro-l9](https://github.com/pedro-l9), for your contributions + + +## [0.9.0-beta.5](https://github.com/xmldom/xmldom/compare/0.9.0-beta.4...0.9.0-beta.5) + +### Fixed + +- fix: Restore ES5 compatibility [`#452`](https://github.com/xmldom/xmldom/pull/452) / [`#453`](https://github.com/xmldom/xmldom/issues/453) + +Thank you, [@fengxinming](https://github.com/fengxinming), for your contributions + + +## [0.8.5](https://github.com/xmldom/xmldom/compare/0.8.4...0.8.5) + +### Fixed + +- fix: Restore ES5 compatibility [`#452`](https://github.com/xmldom/xmldom/pull/452) / [`#453`](https://github.com/xmldom/xmldom/issues/453) + +Thank you, [@fengxinming](https://github.com/fengxinming), for your contributions + + +## [0.7.8](https://github.com/xmldom/xmldom/compare/0.7.7...0.7.8) + +### Fixed + +- fix: Restore ES5 compatibility [`#452`](https://github.com/xmldom/xmldom/pull/452) / [`#453`](https://github.com/xmldom/xmldom/issues/453) + +Thank you, [@fengxinming](https://github.com/fengxinming), for your contributions + + +## [0.9.0-beta.4](https://github.com/xmldom/xmldom/compare/0.9.0-beta.3...0.9.0-beta.4) + +### Fixed + +- Security: Prevent inserting DOM nodes when they are not well-formed [`CVE-2022-39353`](https://github.com/xmldom/xmldom/security/advisories/GHSA-crh6-fp67-6883) + In case such a DOM would be created, the part that is not well-formed will be transformed into text nodes, in which xml specific characters like `<` and `>` are encoded accordingly. + In the upcoming version 0.9.0 those text nodes will no longer be added and an error will be thrown instead. + This change can break your code, if you relied on this behavior, e.g. multiple root elements in the past. We consider it more important to align with the specs that we want to be aligned with, considering the potential security issues that might derive from people not being aware of the difference in behavior. + Related Spec: + +### Chore + +- update multiple devDependencies +- Add eslint-plugin-node for `lib` [`#448`](https://github.com/xmldom/xmldom/pull/448) / [`#190`](https://github.com/xmldom/xmldom/issues/190) +- style: Apply prettier to all code [`#447`](https://github.com/xmldom/xmldom/pull/447) / [`#29`](https://github.com/xmldom/xmldom/issues/29) / [`#130`](https://github.com/xmldom/xmldom/issues/130) + +Thank you, [@XhmikosR](https://github.com/XhmikosR), [@awwright](https://github.com/awwright), [@frumioj](https://github.com/frumioj), [@cjbarth](https://github.com/cjbarth), [@markgollnick](https://github.com/markgollnick) for your contributions + + +## [0.8.4](https://github.com/xmldom/xmldom/compare/0.8.3...0.8.4) + +### Fixed + +- Security: Prevent inserting DOM nodes when they are not well-formed [`CVE-2022-39353`](https://github.com/xmldom/xmldom/security/advisories/GHSA-crh6-fp67-6883) + In case such a DOM would be created, the part that is not well-formed will be transformed into text nodes, in which xml specific characters like `<` and `>` are encoded accordingly. + In the upcoming version 0.9.0 those text nodes will no longer be added and an error will be thrown instead. + This change can break your code, if you relied on this behavior, e.g. multiple root elements in the past. We consider it more important to align with the specs that we want to be aligned with, considering the potential security issues that might derive from people not being aware of the difference in behavior. + Related Spec: + +Thank you, [@frumioj](https://github.com/frumioj), [@cjbarth](https://github.com/cjbarth), [@markgollnick](https://github.com/markgollnick) for your contributions + + +## [0.7.7](https://github.com/xmldom/xmldom/compare/0.7.6...0.7.7) + +### Fixed + +- Security: Prevent inserting DOM nodes when they are not well-formed [`CVE-2022-39353`](https://github.com/xmldom/xmldom/security/advisories/GHSA-crh6-fp67-6883) + In case such a DOM would be created, the part that is not well-formed will be transformed into text nodes, in which xml specific characters like `<` and `>` are encoded accordingly. + In the upcoming version 0.9.0 those text nodes will no longer be added and an error will be thrown instead. + This change can break your code, if you relied on this behavior, e.g. multiple root elements in the past. We consider it more important to align with the specs that we want to be aligned with, considering the potential security issues that might derive from people not being aware of the difference in behavior. + Related Spec: + +Thank you, [@frumioj](https://github.com/frumioj), [@cjbarth](https://github.com/cjbarth), [@markgollnick](https://github.com/markgollnick) for your contributions + + +## [0.9.0-beta.3](https://github.com/xmldom/xmldom/compare/0.9.0-beta.2...0.9.0-beta.3) + +### Fixed + +- fix: Stop adding tags after incomplete closing tag [`#445`](https://github.com/xmldom/xmldom/pull/445) / [`#416`](https://github.com/xmldom/xmldom/pull/416) + BREAKING CHANGE: It no longer reports an error when parsing HTML containing incomplete closing tags, to align the behavior with the one in the browser. + BREAKING CHANGE: If your code relied on not well-formed XML to be parsed and include subsequent tags, this will no longer work. +- fix: Avoid bidirectional characters in source code [`#440`](https://github.com/xmldom/xmldom/pull/440) + +### Other + +- ci: Add CodeQL scan [`#444`](https://github.com/xmldom/xmldom/pull/444) + +Thank you, [@ACN-kck](https://github.com/ACN-kck), [@mgerlach](https://github.com/mgerlach) for your contributions + + +## [0.7.6](https://github.com/xmldom/xmldom/compare/0.7.5...0.7.6) + +### Fixed +- Avoid iterating over prototype properties [`#441`](https://github.com/xmldom/xmldom/pull/441) / [`#437`](https://github.com/xmldom/xmldom/pull/437) / [`#436`](https://github.com/xmldom/xmldom/issues/436) + +Thank you, [@jftanner](https://github.com/jftanner), [@Supraja9726](https://github.com/Supraja9726) for your contributions + + +## [0.8.3](https://github.com/xmldom/xmldom/compare/0.8.3...0.8.2) + +### Fixed +- Avoid iterating over prototype properties [`#437`](https://github.com/xmldom/xmldom/pull/437) / [`#436`](https://github.com/xmldom/xmldom/issues/436) + +Thank you, [@Supraja9726](https://github.com/Supraja9726) for your contributions + + +## [0.9.0-beta.2](https://github.com/xmldom/xmldom/compare/0.9.0-beta.1...0.9.0-beta.2) + +### Fixed +- Avoid iterating over prototype properties [`#437`](https://github.com/xmldom/xmldom/pull/437) / [`#436`](https://github.com/xmldom/xmldom/issues/436) + +Thank you, [@Supraja9726](https://github.com/Supraja9726) for your contributions + + +## [0.9.0-beta.1](https://github.com/xmldom/xmldom/compare/0.8.2...0.9.0-beta.1) + +### Fixed + +**Only use HTML rules if mimeType matches** [`#338`](https://github.com/xmldom/xmldom/pull/338), fixes [`#203`](https://github.com/xmldom/xmldom/issues/203) + +In the living specs for parsing XML and HTML, that this library is trying to implement, +there is a distinction between the different types of documents being parsed: +There are quite some rules that are different for parsing, constructing and serializing XML vs HTML documents. + +So far xmldom was always "detecting" whether "the HTML rules should be applied" by looking at the current namespace. So from the first time an the HTML default namespace (`http://www.w3.org/1999/xhtml`) was found, every node was treated as being part of an HTML document. This misconception is the root cause for quite some reported bugs. + +BREAKING CHANGE: HTML rules are no longer applied just because of the namespace, but require the `mimeType` argument passed to `DOMParser.parseFromString(source, mimeType)` to match `'text/html'`. Doing so implies all rules for handling casing for tag and attribute names when parsing, creation of nodes and searching nodes. + +BREAKING CHANGE: Correct the return type of `DOMParser.parseFromString` to `Document | undefined`. In case of parsing errors it was always possible that "the returned `Document`" has not been created. In case you are using Typescript you now need to handle those cases. + +BREAKING CHANGE: The instance property `DOMParser.options` is no longer available, instead use the individual `readonly` property per option (`assign`, `domHandler`, `errorHandler`, `normalizeLineEndings`, `locator`, `xmlns`). Those also provides the default value if the option was not passed. The 'locator' option is now just a boolean (default remains `true`). + +BREAKING CHANGE: The following methods no longer allow a (non spec compliant) boolean argument to toggle "HTML rules": +- `XMLSerializer.serializeToString` +- `Node.toString` +- `Document.toString` + +The following interfaces have been implemented: +`DOMImplementation` now implements all methods defined in the DOM spec, but not all of the behavior is implemented (see docstring): +- `createDocument` creates an "XML Document" (prototype: `Document`, property `type` is `'xml'`) +- `createHTMLDocument` creates an "HTML Document" (type/prototype: `Document`, property `type` is `'html'`). + - when no argument is passed or the first argument is a string, the basic nodes for an HTML structure are created, as specified + - when the first argument is `false` no child nodes are created + +`Document` now has two new readonly properties as specified in the DOM spec: +- `contentType` which is the mime-type that was used to create the document +- `type` which is either the string literal `'xml'` or `'html'` + +`MIME_TYPE` (`/lib/conventions.js`): +- `hasDefaultHTMLNamespace` test if the provided string is one of the miem types that implies the default HTML namespace: `text/html` or `application/xhtml+xml` + +Thank you [@weiwu-zhang](https://github.com/weiwu-zhang) for your contributions + +### Chore + +- update multiple devDependencies + + +## [0.8.2](https://github.com/xmldom/xmldom/compare/0.8.1...0.8.2) + +### Fixed +- fix(dom): Serialize `>` as specified (#395) [`#58`](https://github.com/xmldom/xmldom/issues/58) + +### Other +- docs: Add `nodeType` values to public interface description [`#396`](https://github.com/xmldom/xmldom/pull/396) +- test: Add executable examples for node and typescript [`#317`](https://github.com/xmldom/xmldom/pull/317) +- fix(dom): Serialize `>` as specified [`#395`](https://github.com/xmldom/xmldom/pull/395) +- chore: Add minimal `Object.assign` ponyfill [`#379`](https://github.com/xmldom/xmldom/pull/379) +- docs: Refine release documentation [`#378`](https://github.com/xmldom/xmldom/pull/378) +- chore: update various dev dependencies + +Thank you [@niklasl](https://github.com/niklasl), [@cburatto](https://github.com/cburatto), [@SheetJSDev](https://github.com/SheetJSDev), [@pyrsmk](https://github.com/pyrsmk) for your contributions + +## [0.8.1](https://github.com/xmldom/xmldom/compare/0.8.0...0.8.1) + +### Fixes +- Only use own properties in entityMap [`#374`](https://github.com/xmldom/xmldom/pull/374) + +### Docs +- Add security policy [`#365`](https://github.com/xmldom/xmldom/pull/365) +- changelog: Correct contributor name and link [`#366`](https://github.com/xmldom/xmldom/pull/366) +- Describe release/publish steps [`#358`](https://github.com/xmldom/xmldom/pull/358), [`#376`](https://github.com/xmldom/xmldom/pull/376) +- Add snyk package health badge [`#360`](https://github.com/xmldom/xmldom/pull/360) + + +## [0.8.0](https://github.com/xmldom/xmldom/compare/0.7.5...0.8.0) + +### Fixed +- Normalize all line endings according to XML specs [1.0](https://w3.org/TR/xml/#sec-line-ends) and [1.1](https://www.w3.org/TR/xml11/#sec-line-ends) \ + BREAKING CHANGE: Certain combination of line break characters are normalized to a single `\n` before parsing takes place and will no longer be preserved. + - [`#303`](https://github.com/xmldom/xmldom/issues/303) / [`#307`](https://github.com/xmldom/xmldom/pull/307) + - [`#49`](https://github.com/xmldom/xmldom/issues/49), [`#97`](https://github.com/xmldom/xmldom/issues/97), [`#324`](https://github.com/xmldom/xmldom/issues/324) / [`#314`](https://github.com/xmldom/xmldom/pull/314) +- XMLSerializer: Preserve whitespace character references [`#284`](https://github.com/xmldom/xmldom/issues/284) / [`#310`](https://github.com/xmldom/xmldom/pull/310) \ + BREAKING CHANGE: If you relied on the not spec compliant preservation of literal `\t`, `\n` or `\r` in **attribute values**. + To preserve those you will have to create XML that instead contains the correct numerical (or hexadecimal) equivalent (e.g. ` `, ` `, ` `). +- Drop deprecated exports `DOMImplementation` and `XMLSerializer` from `lib/dom-parser.js` [#53](https://github.com/xmldom/xmldom/issues/53) / [`#309`](https://github.com/xmldom/xmldom/pull/309) + BREAKING CHANGE: Use the one provided by the main package export. +- dom: Remove all links as part of `removeChild` [`#343`](https://github.com/xmldom/xmldom/issues/343) / [`#355`](https://github.com/xmldom/xmldom/pull/355) + +### Chore +- ci: Restore latest tested node version to 16.x [`#325`](https://github.com/xmldom/xmldom/pull/325) +- ci: Split test and lint steps into jobs [`#111`](https://github.com/xmldom/xmldom/issues/111) / [`#304`](https://github.com/xmldom/xmldom/pull/304) +- Pinned and updated devDependencies + +Thank you [@marrus-sh](https://github.com/marrus-sh), [@victorandree](https://github.com/victorandree), [@mdierolf](https://github.com/mdierolf), [@tsabbay](https://github.com/tsabbay), [@fatihpense](https://github.com/fatihpense) for your contributions + +## [0.7.5](https://github.com/xmldom/xmldom/compare/0.7.4...0.7.5) + +### Fixes: + +- Preserve default namespace when serializing [`#319`](https://github.com/xmldom/xmldom/issues/319) / [`#321`](https://github.com/xmldom/xmldom/pull/321) + Thank you, [@lupestro](https://github.com/lupestro) + +## [0.7.4](https://github.com/xmldom/xmldom/compare/0.7.3...0.7.4) + +### Fixes: + +- Restore ability to parse `__prototype__` attributes [`#315`](https://github.com/xmldom/xmldom/pull/315) + Thank you, [@dsimpsonOMF](https://github.com/dsimpsonOMF) + +## [0.7.3](https://github.com/xmldom/xmldom/compare/0.7.2...0.7.3) + +### Fixes: + +- Add doctype when parsing from string [`#277`](https://github.com/xmldom/xmldom/issues/277) / [`#301`](https://github.com/xmldom/xmldom/pull/301) +- Correct typo in error message [`#294`](https://github.com/xmldom/xmldom/pull/294) + Thank you, [@rrthomas](https://github.com/rrthomas) + +### Refactor: + +- Improve exports & require statements, new main package entry [`#233`](https://github.com/xmldom/xmldom/pull/233) + +### Docs: + +- Fix Stryker badge [`#298`](https://github.com/xmldom/xmldom/pull/298) +- Fix link to help-wanted issues [`#299`](https://github.com/xmldom/xmldom/pull/299) + +### Chore: + +- Execute stryker:dry-run on branches [`#302`](https://github.com/xmldom/xmldom/pull/302) +- Fix stryker config [`#300`](https://github.com/xmldom/xmldom/pull/300) +- Split test and lint scripts [`#297`](https://github.com/xmldom/xmldom/pull/297) +- Switch to stryker dashboard owned by org [`#292`](https://github.com/xmldom/xmldom/pull/292) + +## [0.7.2](https://github.com/xmldom/xmldom/compare/0.7.1...0.7.2) + +### Fixes: + +- Types: Add index.d.ts to packaged files [`#288`](https://github.com/xmldom/xmldom/pull/288) + Thank you, [@forty](https://github.com/forty) + +## [0.7.1](https://github.com/xmldom/xmldom/compare/0.7.0...0.7.1) + +### Fixes: + +- Types: Copy types from DefinitelyTyped [`#283`](https://github.com/xmldom/xmldom/pull/283) + Thank you, [@kachkaev](https://github.com/kachkaev) + +### Chore: +- package.json: remove author, maintainers, etc. [`#279`](https://github.com/xmldom/xmldom/pull/279) + +## [0.7.0](https://github.com/xmldom/xmldom/compare/0.6.0...0.7.0) + +Due to [`#271`](https://github.com/xmldom/xmldom/issue/271) this version was published as +- unscoped `xmldom` package to **github** (git tags [`0.7.0`](https://github.com/xmldom/xmldom/tree/0.7.0) and [`0.7.0+unscoped`](https://github.com/xmldom/xmldom/tree/0.7.0%2Bunscoped)) +- scoped `@xmldom/xmldom` package to npm (git tag `0.7.0+scoped`) +For more details look at [`#278`](https://github.com/xmldom/xmldom/pull/278#issuecomment-902172483) + +### Fixes: + +- Security: Misinterpretation of malicious XML input [`CVE-2021-32796`](https://github.com/xmldom/xmldom/security/advisories/GHSA-5fg8-2547-mr8q) +- Implement `Document.getElementsByClassName` as specified [`#213`](https://github.com/xmldom/xmldom/pull/213), thank you, [@ChALkeR](https://github.com/ChALkeR) +- Inherit namespace prefix from parent when required [`#268`](https://github.com/xmldom/xmldom/pull/268) +- Handle whitespace in closing tags [`#267`](https://github.com/xmldom/xmldom/pull/267) +- Update `DOMImplementation` according to recent specs [`#210`](https://github.com/xmldom/xmldom/pull/210) + BREAKING CHANGE: Only if you "passed features to be marked as available as a constructor arguments" and expected it to "magically work". +- No longer serializes any namespaces with an empty URI [`#244`](https://github.com/xmldom/xmldom/pull/244) + (related to [`#168`](https://github.com/xmldom/xmldom/pull/168) released in 0.6.0) + BREAKING CHANGE: Only if you rely on ["unsetting" a namespace prefix](https://github.com/xmldom/xmldom/pull/168#issuecomment-886984994) by setting it to an empty string +- Set `localName` as part of `Document.createElement` [`#229`](https://github.com/xmldom/xmldom/pull/229), thank you, [@rrthomas](https://github.com/rrthomas) + +### CI + +- We are now additionally running tests against node v16 +- Stryker tests on the master branch now run against node v14 + +### Docs + +- Describe relations with and between specs: [`#211`](https://github.com/xmldom/xmldom/pull/211), [`#247`](https://github.com/xmldom/xmldom/pull/247) + +## [0.6.0](https://github.com/xmldom/xmldom/compare/0.5.0...0.6.0) + +Published to npm: 2021-04-17 16:41 UTC by @karfau as `xmldom` + +### Fixes + +- Stop serializing empty namespace values like `xmlns:ds=""` [`#168`](https://github.com/xmldom/xmldom/pull/168) + BREAKING CHANGE: If your code expected empty namespaces attributes to be serialized. + Thank you, [@pdecat](https://github.com/pdecat) and [@FranckDepoortere](https://github.com/FranckDepoortere) +- Escape `<` to `<` when serializing attribute values [`#198`](https://github.com/xmldom/xmldom/issues/198) / [`#199`](https://github.com/xmldom/xmldom/pull/199) + +## [0.5.0](https://github.com/xmldom/xmldom/compare/0.4.0...0.5.0) + +Published to npm: 2021-03-09 03:59 UTC by @brodybits as `xmldom` + +### Fixes +- Avoid misinterpretation of malicious XML input - [`GHSA-h6q6-9hqw-rwfv`](https://github.com/xmldom/xmldom/security/advisories/GHSA-h6q6-9hqw-rwfv) (CVE-2021-21366) + - Improve error reporting; throw on duplicate attribute\ + BREAKING CHANGE: It is currently not clear how to consistently deal with duplicate attributes, so it's also safer for our users to fail when detecting them. + It's possible to configure the `DOMParser.errorHandler` before parsing, to handle those errors differently. + + To accomplish this and also be able to verify it in tests I needed to + - create a new `Error` type `ParseError` and export it + - Throw `ParseError` from `errorHandler.fatalError` and prevent those from being caught in `XMLReader`. + - export `DOMHandler` constructor as `__DOMHandler` + - Preserve quotes in DOCTYPE declaration + Since the only purpose of parsing the DOCTYPE is to be able to restore it when serializing, we decided that it would be best to leave the parsed `publicId` and `systemId` as is, including any quotes. + BREAKING CHANGE: If somebody relies on the actual unquoted values of those ids, they will need to take care of either single or double quotes and the right escaping. + (Without this change this would not have been possible because the SAX parser already dropped the information about the quotes that have been used in the source.) + + https://www.w3.org/TR/2006/REC-xml11-20060816/#dtd + https://www.w3.org/TR/2006/REC-xml11-20060816/#IDAX1KS (External Entity Declaration) + +- Fix breaking preprocessors' directives when parsing attributes [`#171`](https://github.com/xmldom/xmldom/pull/171) +- fix(dom): Escape `]]>` when serializing CharData [`#181`](https://github.com/xmldom/xmldom/pull/181) +- Switch to (only) MIT license (drop problematic LGPL license option) [`#178`](https://github.com/xmldom/xmldom/pull/178) +- Export DOMException; remove custom assertions; etc. [`#174`](https://github.com/xmldom/xmldom/pull/174) + +### Docs +- Update MDN links in `readme.md` [`#188`](https://github.com/xmldom/xmldom/pull/188) + +## [0.4.0](https://github.com/xmldom/xmldom/compare/0.3.0...0.4.0) + +Published to npm: 2020-10-27 00:44 UTC by @brodybits as `xmldom` + +### Fixes +- **BREAKING** Restore ` ` behavior from v0.1.27 [`#67`](https://github.com/xmldom/xmldom/pull/67) +- **BREAKING** Typecheck source param before parsing [`#113`](https://github.com/xmldom/xmldom/pull/113) +- Include documents in package files list [`#156`](https://github.com/xmldom/xmldom/pull/156) +- Preserve doctype with sysid [`#144`](https://github.com/xmldom/xmldom/pull/144) +- Remove ES6 syntax from getElementsByClassName [`#91`](https://github.com/xmldom/xmldom/pull/91) +- Revert "Add lowercase of åäö in entityMap" due to duplicate entries [`#84`](https://github.com/xmldom/xmldom/pull/84) +- fix: Convert all line separators to LF [`#66`](https://github.com/xmldom/xmldom/pull/66) + +### Docs +- Update CHANGELOG.md through version 0.3.0 [`#63`](https://github.com/xmldom/xmldom/pull/63) +- Update badges [`#78`](https://github.com/xmldom/xmldom/pull/78) +- Add .editorconfig file [`#104`](https://github.com/xmldom/xmldom/pull/104) +- Add note about import [`#79`](https://github.com/xmldom/xmldom/pull/79) +- Modernize & improve the example in readme.md [`#81`](https://github.com/xmldom/xmldom/pull/81) + +### CI +- Add Stryker Mutator [`#70`](https://github.com/xmldom/xmldom/pull/70) +- Add Stryker action to update dashboard [`#77`](https://github.com/xmldom/xmldom/pull/77) +- Add Node GitHub action workflow [`#64`](https://github.com/xmldom/xmldom/pull/64) +- add & enable eslint [`#106`](https://github.com/xmldom/xmldom/pull/106) +- Use eslint-plugin-es5 to enforce ES5 syntax [`#107`](https://github.com/xmldom/xmldom/pull/107) +- Recover `vows` tests, drop `proof` tests [`#59`](https://github.com/xmldom/xmldom/pull/59) +- Add jest tessuite and first tests [`#114`](https://github.com/xmldom/xmldom/pull/114) +- Add jest testsuite with `xmltest` cases [`#112`](https://github.com/xmldom/xmldom/pull/112) +- Configure Renovate [`#108`](https://github.com/xmldom/xmldom/pull/108) +- Test European HTML entities [`#86`](https://github.com/xmldom/xmldom/pull/86) +- Updated devDependencies + +### Other +- Remove files that are not of any use [`#131`](https://github.com/xmldom/xmldom/pull/131), [`#65`](https://github.com/xmldom/xmldom/pull/65), [`#33`](https://github.com/xmldom/xmldom/pull/33) + +## [0.3.0](https://github.com/xmldom/xmldom/compare/0.2.1...0.3.0) + +Published to npm: 2020-03-04 16:32 UTC by @kethinov as `xmldom` + +- **BREAKING** Node >=10.x is now required. +- **BREAKING** Remove `component.json` (deprecated package manager https://github.com/componentjs/guide) +- **BREAKING** Move existing sources into `lib` subdirectory. +- **POSSIBLY BREAKING** Introduce `files` entry in `package.json` and remove use of `.npmignore`. +- [Add `Document.getElementsByClassName`](https://github.com/xmldom/xmldom/issues/24). +- [Add `Node` to the list of exports](https://github.com/xmldom/xmldom/pull/27) +- [Add lowercase of åäö in `entityMap`](https://github.com/xmldom/xmldom/pull/23). +- Move CHANGELOG to markdown file. +- Move LICENSE to markdown file. + +## [0.2.1](https://github.com/xmldom/xmldom/compare/0.2.0...0.2.1) + +Published to npm: 2019-12-20 00:40 UTC by @brodybits as `xmldom` + +- Correct `homepage`, `repository` and `bugs` URLs in `package.json`. + +## [0.2.0](https://github.com/xmldom/xmldom/compare/v0.1.27...0.2.0) + +Published to npm: 2019-12-20 00:19 UTC by @brodybits as `xmldom` + +- Includes all **BREAKING** changes introduced in [`xmldom-alpha@v0.1.28`](#0128) by the original authors. +- **POSSIBLY BREAKING** [remove the `Object.create` check from the `_extends` method of `dom.js` that added a `__proto__` property](https://github.com/xmldom/xmldom/commit/0be2ae910a8a22c9ec2cac042e04de4c04317d2a#diff-7d1c5d97786fdf9af5446a241d0b6d56L19-L22) (). +- **POSSIBLY BREAKING** [remove code that added a `__proto__` property](https://github.com/xmldom/xmldom/commit/366159a76a181ce9a0d83f5dc48205686cfaf9cc) +- formatting/corrections in `package.json` + +## [0.1.31](https://github.com/xmldom/xmldom/compare/0.1.30...0.1.31) + +Published to npm: 2019-12-19 22:34 UTC by @brodybits as `xmldom` + +## [0.1.30](https://github.com/xmldom/xmldom/compare/0.1.29...0.1.30) + +Published to npm: 2019-12-19 22:30 UTC by @brodybits as `xmldom` + +## [0.1.29](https://github.com/xmldom/xmldom/compare/0.1.27...0.1.29) + +Published to npm: 2019-12-19 22:26 UTC by @brodybits as `xmldom` + +The patch versions (`0.1.29` - `0.1.31`) that have been released on the [v0.1.x branch](https://github.com/xmldom/xmldom/tree/0.1.x), to reflect the changed maintainers, **are branched off from [`0.1.27`](#0127) so they don't include the breaking changes introduced in [`xmldom-alpha@v0.1.28`](#0128)**: + +## Maintainer changes + +After the last commit to the original repository on the 9th of May 2017, the first commit to is from the 19th of December 2019. [The fork has been announced in the original repository on the 2nd of March 2020.](https://github.com/jindw/xmldom/issues/259) + +The versions listed below have been published to one or both of the following packages: +- +- + +It is currently not planned to continue publishing the `xmldom-alpha` package. + +The new maintainers did not invest time to understand changes that led to the last `xmldom` version [`0.1.27`](#0127) published by the original maintainer, but consider it the basis for their work. +A timeline of all the changes that happened from that version until `0.3.0` is available in . Any related questions should be asked there. + +## [0.1.28](https://github.com/xmldom/xmldom/compare/v0.1.27...xmldom-alpha@v0.1.28) + +Published to npm: 2017-05-08 02:51 UTC by @jindw as `xmldom-alpha` + +- **BREAKING** includes [regression regarding ` ` (issue #57)](https://github.com/xmldom/xmldom/issues/57) +- [Fix `license` field in `package.json`](https://github.com/jindw/xmldom/pull/178) +- [Conditional converting of HTML entities](https://github.com/jindw/xmldom/pull/80) +- Fix `dom.js` serialization issue for missing document element ([example that failed on `toString()` before this change](https://github.com/xmldom/xmldom/blob/a58dcf7a265522e80ce520fe3be0cddb1b976f6f/test/parse/unclosedcomment.js#L10-L11)) +- Add new module `entities.js` + +## [0.1.27](https://github.com/xmldom/xmldom/compare/0.1.26...0.1.27) + +Published to npm: 2016-11-28 03:56 UTC by @jindw as `xmldom` and `xmldom-alpha` + +Tags `0.1.24`, `0.1.25`, `0.1.26`, and `0.1.27` all point to the same git commit (`b53aa82`). These four versions were published within 24 hours in November 2016 with version bumps applied only to the working directory, not committed to git. + +- Various bug fixes. + +## [0.1.26](https://github.com/xmldom/xmldom/compare/0.1.25...0.1.26) + +Published to npm: 2016-11-28 03:47 UTC by @jindw as `xmldom` + +Tags `0.1.24`, `0.1.25`, `0.1.26`, and `0.1.27` all point to the same git commit (`b53aa82`). These four versions were published within 24 hours in November 2016 with version bumps applied only to the working directory, not committed to git. + +- Details unknown + +## [0.1.25](https://github.com/xmldom/xmldom/compare/0.1.24...0.1.25) + +Published to npm: 2016-11-28 03:35 UTC by @jindw as `xmldom` + +Tags `0.1.24`, `0.1.25`, `0.1.26`, and `0.1.27` all point to the same git commit (`b53aa82`). These four versions were published within 24 hours in November 2016 with version bumps applied only to the working directory, not committed to git. + +- Details unknown + +## [0.1.24](https://github.com/xmldom/xmldom/compare/0.1.22...0.1.24) + +Published to npm: 2016-11-27 14:00 UTC by @jindw as `xmldom` and `xmldom-alpha` + +Tags `0.1.24`, `0.1.25`, `0.1.26`, and `0.1.27` all point to the same git commit (`b53aa82`). These four versions were published within 24 hours in November 2016 with version bumps applied only to the working directory, not committed to git. + +- Added node filter. + +## [0.1.23](https://github.com/xmldom/xmldom/compare/0.1.22...0.1.23) + +Published to npm: 2016-05-20 04:24 UTC by @jindw as `xmldom-alpha` + +- Add namespace support for nest node serialize. +- Various other bug fixes. + +## [0.1.22](https://github.com/xmldom/xmldom/compare/0.1.21...0.1.22) + +Published to npm: 2016-01-30 11:50 UTC by @jindw as `xmldom` + +- Merge XMLNS serialization. +- Remove \r from source string. +- Print namespaces for child elements. +- Switch references to nodeType to use named constants. +- Add nodelist toString support. + +## [0.1.21](https://github.com/xmldom/xmldom/compare/0.1.20...0.1.21) + +Published to npm: 2016-01-13 11:41 UTC by @jindw as `xmldom` + +- Fix serialize bug. + +## [0.1.20](https://github.com/xmldom/xmldom/compare/0.1.19...0.1.20) + +Published to npm: 2016-01-10 08:23 UTC by @jindw as `xmldom` + +- Optimize invalid XML support. +- Add toString sorter for attributes output. +- Add html self closed node button. +- Add `*` NS support for getElementsByTagNameNS. +- Convert attribute's value to string in setAttributeNS. +- Add support for HTML entities for HTML docs only. +- Fix TypeError when Document is created with DocumentType. + +## [0.1.19](https://github.com/xmldom/xmldom/compare/0.1.18...0.1.19) + +Published to npm: 2014-01-28 15:15 UTC by @jindw as `xmldom` + +- Fix [infinite loop on unclosed comment (jindw/xmldom#68)](https://github.com/jindw/xmldom/issues/68) +- Add error report for unclosed tag. +- Various other fixes. + +## [0.1.18](https://github.com/xmldom/xmldom/compare/0.1.17...0.1.18) + +Published to npm: 2014-01-17 06:59 UTC by @bigeasy as `xmldom` + +- Add default `ns` support. +- parseFromString now renders entirely plain text documents as textNode. +- Enable option to ignore white space on parsing. + +## [0.1.17](https://github.com/xmldom/xmldom/compare/0.1.16...0.1.17) + +Published to npm: 2013-12-16 03:07 UTC by @jindw as `xmldom` + +No version bump commit for 0.1.17 was found in git history. The tag `0.1.17` points to the commit immediately preceding the 0.1.18 version bump (`87f63e6`, parent of `9ddac14`), which is the last committed state before the next version. + +## [0.1.16](https://github.com/xmldom/xmldom/compare/0.1.15...0.1.16) + +Published to npm: 2013-05-04 15:00 UTC by @bigeasy as `xmldom` + +- Correctly handle multibyte Unicode greater than two byts. #57. #56. +- Initial unit testing and test coverage. #53. #46. #19. +- Create Bower `component.json` #52. + +## [0.1.15](https://github.com/xmldom/xmldom/compare/0.1.14...0.1.15) + +Published to npm: 2013-04-03 02:41 UTC by @bigeasy as `xmldom` + +## [0.1.14](https://github.com/xmldom/xmldom/compare/0.1.13...0.1.14) + +Published to npm: 2013-03-31 15:31 UTC by @bigeasy as `xmldom` + +## [0.1.13](https://github.com/xmldom/xmldom/compare/0.1.12...0.1.13) + +Published to npm: 2012-10-02 11:35 UTC by @jindw as `xmldom` + +## [0.1.12](https://github.com/xmldom/xmldom/compare/0.1.11...0.1.12) + +Published to npm: 2012-09-03 15:02 UTC as `xmldom` + +No version bump commit for 0.1.12 was found in git history. The tag `0.1.12` points to the commit immediately preceding the 0.1.13 version bump (`47fa9b8`, parent of `eb17b3a`), which is the last committed state before the next version. + +## [0.1.11](https://github.com/xmldom/xmldom/compare/0.1.10...0.1.11) + +Published to npm: 2012-06-18 10:45 UTC by @jindw as `xmldom` + +## [0.1.10](https://github.com/xmldom/xmldom/compare/0.1.9...0.1.10) + +Published to npm: 2012-06-14 02:54 UTC by @jindw as `xmldom` + +## [0.1.9](https://github.com/xmldom/xmldom/compare/0.1.8...0.1.9) + +Published to npm: 2012-06-08 06:18 UTC by @jindw as `xmldom` + +## [0.1.8](https://github.com/xmldom/xmldom/compare/0.1.7...0.1.8) + +Published to npm: 2012-05-29 13:30 UTC by @jindw as `xmldom` + +- Add: some test case from node-o3-xml(excludes xpath support) +- Fix: remove existed attribute before setting (bug introduced in v0.1.5) +- Fix: index direct access for childNodes and any NodeList collection(not w3c standard) +- Fix: remove last child bug + +## [0.1.7](https://github.com/xmldom/xmldom/compare/0.1.6...0.1.7) + +Published to npm: 2012-05-29 03:05 UTC by @jindw as `xmldom` + +## [0.1.6](https://github.com/xmldom/xmldom/compare/0.1.5...0.1.6) + +Published to npm: 2012-05-28 12:49 UTC by @jindw as `xmldom` + +## [0.1.5](https://github.com/xmldom/xmldom/compare/0.1.4...0.1.5) + +Published to npm: 2012-05-25 17:43 UTC by @jindw as `xmldom` + +## [0.1.4](https://github.com/xmldom/xmldom/compare/0.1.3...0.1.4) + +Published to npm: 2012-05-22 16:41 UTC by @jindw as `xmldom` + +## [0.1.3](https://github.com/xmldom/xmldom/compare/0.1.2...0.1.3) + +Published to npm: 2012-05-22 16:39 UTC by @jindw as `xmldom` + +## [0.1.2](https://github.com/xmldom/xmldom/compare/0.1.1...0.1.2) + +Published to npm: 2012-02-03 10:49 UTC by @jindw as `xmldom` + +## [0.1.1](https://github.com/xmldom/xmldom/compare/0.1.0...0.1.1) + +Published to npm: 2012-01-10 07:18 UTC by @jindw as `xmldom` + +## [0.1.0](https://github.com/xmldom/xmldom/commit/5d770d4) + +Published to npm: 2012-01-06 09:49 UTC by @jindw as `xmldom` diff --git a/node_modules/@xmldom/xmldom/LICENSE b/node_modules/@xmldom/xmldom/LICENSE new file mode 100644 index 000000000..b95f5698c --- /dev/null +++ b/node_modules/@xmldom/xmldom/LICENSE @@ -0,0 +1,8 @@ +Copyright 2019 - present Christopher J. Brody and other contributors, as listed in: https://github.com/xmldom/xmldom/graphs/contributors +Copyright 2012 - 2017 @jindw and other contributors, as listed in: https://github.com/jindw/xmldom/graphs/contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/node_modules/@xmldom/xmldom/SECURITY.md b/node_modules/@xmldom/xmldom/SECURITY.md new file mode 100644 index 000000000..3d58efc7b --- /dev/null +++ b/node_modules/@xmldom/xmldom/SECURITY.md @@ -0,0 +1,50 @@ +# Security Policy + +The most up-to-date version of this document can be found at . + +## Supported Versions + +This repository contains the code for the libraries `xmldom` and `@xmldom/xmldom` on npm. + +As long as we didn't publish v1, we aim to maintain the last two minor versions with security fixes. If it is possible we provide security fixes as patch versions. +If you think there is a good reason to also patch an earlier version, let us know in a GitHub issue or the release discussion once the fix has been provided. +The maintainers will consider it, and if we agree and have/find the required resources, a patch for that version will be provided. + +Please notice that [we are no longer able to publish the (unscoped) `xmldom` package](https://github.com/xmldom/xmldom/issues/271), +and that all existing versions of `xmldom` are affected by at least one security vulnerability and should be considered deprecated. +You can still report issues regarding `xmldom` as described below. + +If you need help with migrating from `xmldom` to `@xmldom/xmldom`, file a GitHub issue or PR in the affected repository and mention @karfau. + +## Reporting vulnerabilities + +Please email reports about any security related issues you find to `security@xmldom.org`, which will forward it to the list of maintainers. +The maintainers will try to respond within 7 calendar days. (If nobody replies after 7 days, please us send a reminder!) +As part of you communication please make sure to always hit "Reply all", so all maintainers are kept in the loop. + +In addition, please include the following information along with your report: + +- Your name and affiliation (if any). +- A description of the technical details of the vulnerabilities. It is very important to let us know how we can reproduce your findings. +- An explanation who can exploit this vulnerability, and what they gain when doing so -- write an attack scenario. This will help us evaluate your report quickly, especially if the issue is complex. +- Whether this vulnerability public or known to third parties. If it is, please provide details. + +If you believe that an existing (public) issue is security-related, please email `security@xmldom.org`. +The email should include the issue URL and a short description of why it should be handled according to this security policy. + +Once an issue is reported, the maintainers use the following disclosure process: + +- When a report is received, we confirm the issue, determine its severity and the affected versions. +- If we know of specific third-party services or software based on xmldom that require mitigation before publication, those projects will be notified. +- A [GitHub security advisory](https://docs.github.com/en/code-security/security-advisories/about-github-security-advisories) is [created](https://docs.github.com/en/code-security/security-advisories/creating-a-security-advisory) (but not published) which details the problem and steps for mitigation. +- If the reporter provides a GitHub account and agrees to it, we [add that GitHub account as a collaborator on the advisory](https://docs.github.com/en/code-security/security-advisories/adding-a-collaborator-to-a-security-advisory). +- The vulnerability is fixed in a [private fork](https://docs.github.com/en/code-security/security-advisories/collaborating-in-a-temporary-private-fork-to-resolve-a-security-vulnerability) and potential workarounds are identified. +- The maintainers audit the existing code to find any potential similar problems. +- The release for the current minor version and the [security advisory are published](https://docs.github.com/en/code-security/security-advisories/publishing-a-security-advisory). +- The release(s) for previous minor version(s) are published. + +We credit reporters for identifying security issues, if they confirm that they want to. + +## Known vulnerabilities + +See https://github.com/xmldom/xmldom/security/advisories?state=published diff --git a/node_modules/@xmldom/xmldom/index.d.ts b/node_modules/@xmldom/xmldom/index.d.ts new file mode 100644 index 000000000..60c0b228f --- /dev/null +++ b/node_modules/@xmldom/xmldom/index.d.ts @@ -0,0 +1,1843 @@ +declare module '@xmldom/xmldom' { + // START ./lib/conventions.js + /** + * Since xmldom can not rely on `Object.assign`, + * it uses/provides a simplified version that is sufficient for its needs. + * + * @throws {TypeError} + * If target is not an object. + * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign + * @see https://tc39.es/ecma262/multipage/fundamental-objects.html#sec-object.assign + */ + function assign(target: T, source: S): T & S; + + /** + * For both the `text/html` and the `application/xhtml+xml` namespace the spec defines that + * the HTML namespace is provided as the default. + * + * @param {string} mimeType + * @returns {boolean} + * @see https://dom.spec.whatwg.org/#dom-document-createelement + * @see https://dom.spec.whatwg.org/#dom-domimplementation-createdocument + * @see https://dom.spec.whatwg.org/#dom-domimplementation-createhtmldocument + */ + function hasDefaultHTMLNamespace( + mimeType: string + ): mimeType is typeof MIME_TYPE.HTML | typeof MIME_TYPE.XML_XHTML_APPLICATION; + + /** + * Only returns true if `value` matches MIME_TYPE.HTML, which indicates an HTML document. + * + * @see https://www.iana.org/assignments/media-types/text/html + * @see https://en.wikipedia.org/wiki/HTML + * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMParser/parseFromString + * @see https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#dom-domparser-parsefromstring + */ + function isHTMLMimeType(mimeType: string): mimeType is typeof MIME_TYPE.HTML; + + /** + * Only returns true if `mimeType` is one of the allowed values for `DOMParser.parseFromString`. + */ + function isValidMimeType(mimeType: string): mimeType is MIME_TYPE; + + /** + * All mime types that are allowed as input to `DOMParser.parseFromString` + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMParser/parseFromString#Argument02 + * MDN + * @see https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#domparsersupportedtype + * WHATWG HTML Spec + * @see {@link DOMParser.prototype.parseFromString} + */ + type MIME_TYPE = (typeof MIME_TYPE)[keyof typeof MIME_TYPE]; + /** + * All mime types that are allowed as input to `DOMParser.parseFromString` + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMParser/parseFromString#Argument02 + * MDN + * @see https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#domparsersupportedtype + * WHATWG HTML Spec + * @see {@link DOMParser.prototype.parseFromString} + */ + var MIME_TYPE: { + /** + * `text/html`, the only mime type that triggers treating an XML document as HTML. + * + * @see https://www.iana.org/assignments/media-types/text/html IANA MimeType registration + * @see https://en.wikipedia.org/wiki/HTML Wikipedia + * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMParser/parseFromString MDN + * @see https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#dom-domparser-parsefromstring + * WHATWG HTML Spec + */ + readonly HTML: 'text/html'; + /** + * `application/xml`, the standard mime type for XML documents. + * + * @see https://www.iana.org/assignments/media-types/application/xml IANA MimeType + * registration + * @see https://tools.ietf.org/html/rfc7303#section-9.1 RFC 7303 + * @see https://en.wikipedia.org/wiki/XML_and_MIME Wikipedia + */ + readonly XML_APPLICATION: 'application/xml'; + /** + * `text/html`, an alias for `application/xml`. + * + * @see https://tools.ietf.org/html/rfc7303#section-9.2 RFC 7303 + * @see https://www.iana.org/assignments/media-types/text/xml IANA MimeType registration + * @see https://en.wikipedia.org/wiki/XML_and_MIME Wikipedia + */ + readonly XML_TEXT: 'text/xml'; + /** + * `application/xhtml+xml`, indicates an XML document that has the default HTML namespace, + * but is parsed as an XML document. + * + * @see https://www.iana.org/assignments/media-types/application/xhtml+xml IANA MimeType + * registration + * @see https://dom.spec.whatwg.org/#dom-domimplementation-createdocument WHATWG DOM Spec + * @see https://en.wikipedia.org/wiki/XHTML Wikipedia + */ + readonly XML_XHTML_APPLICATION: 'application/xhtml+xml'; + /** + * `image/svg+xml`, + * + * @see https://www.iana.org/assignments/media-types/image/svg+xml IANA MimeType registration + * @see https://www.w3.org/TR/SVG11/ W3C SVG 1.1 + * @see https://en.wikipedia.org/wiki/Scalable_Vector_Graphics Wikipedia + */ + readonly XML_SVG_IMAGE: 'image/svg+xml'; + }; + /** + * Namespaces that are used in xmldom. + * + * @see http://www.w3.org/TR/REC-xml-names + */ + type NAMESPACE = (typeof NAMESPACE)[keyof typeof NAMESPACE]; + /** + * Namespaces that are used in xmldom. + * + * @see http://www.w3.org/TR/REC-xml-names + */ + var NAMESPACE: { + /** + * The XHTML namespace. + * + * @see http://www.w3.org/1999/xhtml + */ + readonly HTML: 'http://www.w3.org/1999/xhtml'; + /** + * The SVG namespace. + * + * @see http://www.w3.org/2000/svg + */ + readonly SVG: 'http://www.w3.org/2000/svg'; + /** + * The `xml:` namespace. + * + * @see http://www.w3.org/XML/1998/namespace + */ + readonly XML: 'http://www.w3.org/XML/1998/namespace'; + + /** + * The `xmlns:` namespace. + * + * @see https://www.w3.org/2000/xmlns/ + */ + readonly XMLNS: 'http://www.w3.org/2000/xmlns/'; + }; + + // END ./lib/conventions.js + + // START ./lib/errors.js + type DOMExceptionName = + (typeof DOMExceptionName)[keyof typeof DOMExceptionName]; + var DOMExceptionName: { + /** + * the default value as defined by the spec + */ + readonly Error: 'Error'; + /** + * @deprecated + * Use RangeError instead. + */ + readonly IndexSizeError: 'IndexSizeError'; + /** + * @deprecated + * Just to match the related static code, not part of the spec. + */ + readonly DomstringSizeError: 'DomstringSizeError'; + readonly HierarchyRequestError: 'HierarchyRequestError'; + readonly WrongDocumentError: 'WrongDocumentError'; + readonly InvalidCharacterError: 'InvalidCharacterError'; + /** + * @deprecated + * Just to match the related static code, not part of the spec. + */ + readonly NoDataAllowedError: 'NoDataAllowedError'; + readonly NoModificationAllowedError: 'NoModificationAllowedError'; + readonly NotFoundError: 'NotFoundError'; + readonly NotSupportedError: 'NotSupportedError'; + readonly InUseAttributeError: 'InUseAttributeError'; + readonly InvalidStateError: 'InvalidStateError'; + readonly SyntaxError: 'SyntaxError'; + readonly InvalidModificationError: 'InvalidModificationError'; + readonly NamespaceError: 'NamespaceError'; + /** + * @deprecated + * Use TypeError for invalid arguments, + * "NotSupportedError" DOMException for unsupported operations, + * and "NotAllowedError" DOMException for denied requests instead. + */ + readonly InvalidAccessError: 'InvalidAccessError'; + /** + * @deprecated + * Just to match the related static code, not part of the spec. + */ + readonly ValidationError: 'ValidationError'; + /** + * @deprecated + * Use TypeError instead. + */ + readonly TypeMismatchError: 'TypeMismatchError'; + readonly SecurityError: 'SecurityError'; + readonly NetworkError: 'NetworkError'; + readonly AbortError: 'AbortError'; + /** + * @deprecated + * Just to match the related static code, not part of the spec. + */ + readonly URLMismatchError: 'URLMismatchError'; + readonly QuotaExceededError: 'QuotaExceededError'; + readonly TimeoutError: 'TimeoutError'; + readonly InvalidNodeTypeError: 'InvalidNodeTypeError'; + readonly DataCloneError: 'DataCloneError'; + readonly EncodingError: 'EncodingError'; + readonly NotReadableError: 'NotReadableError'; + readonly UnknownError: 'UnknownError'; + readonly ConstraintError: 'ConstraintError'; + readonly DataError: 'DataError'; + readonly TransactionInactiveError: 'TransactionInactiveError'; + readonly ReadOnlyError: 'ReadOnlyError'; + readonly VersionError: 'VersionError'; + readonly OperationError: 'OperationError'; + readonly NotAllowedError: 'NotAllowedError'; + readonly OptOutError: 'OptOutError'; + }; + type ExceptionCode = (typeof ExceptionCode)[keyof typeof ExceptionCode]; + + var ExceptionCode: { + readonly INDEX_SIZE_ERR: 1; + readonly DOMSTRING_SIZE_ERR: 2; + readonly HIERARCHY_REQUEST_ERR: 3; + readonly WRONG_DOCUMENT_ERR: 4; + readonly INVALID_CHARACTER_ERR: 5; + readonly NO_DATA_ALLOWED_ERR: 6; + readonly NO_MODIFICATION_ALLOWED_ERR: 7; + readonly NOT_FOUND_ERR: 8; + readonly NOT_SUPPORTED_ERR: 9; + readonly INUSE_ATTRIBUTE_ERR: 10; + readonly INVALID_STATE_ERR: 11; + readonly SYNTAX_ERR: 12; + readonly INVALID_MODIFICATION_ERR: 13; + readonly NAMESPACE_ERR: 14; + readonly INVALID_ACCESS_ERR: 15; + readonly VALIDATION_ERR: 16; + readonly TYPE_MISMATCH_ERR: 17; + readonly SECURITY_ERR: 18; + readonly NETWORK_ERR: 19; + readonly ABORT_ERR: 20; + readonly URL_MISMATCH_ERR: 21; + readonly QUOTA_EXCEEDED_ERR: 22; + readonly TIMEOUT_ERR: 23; + readonly INVALID_NODE_TYPE_ERR: 24; + readonly DATA_CLONE_ERR: 25; + }; + + /** + * DOM operations only raise exceptions in "exceptional" circumstances, i.e., when an + * operation is impossible to perform (either for logical reasons, because data is lost, or + * because the implementation has become unstable). In general, DOM methods return specific + * error values in ordinary processing situations, such as out-of-bound errors when using + * NodeList. + * + * Implementations should raise other exceptions under other circumstances. For example, + * implementations should raise an implementation-dependent exception if a null argument is + * passed when null was not expected. + * + * This implementation supports the following usages: + * 1. according to the living standard (both arguments are optional): + * ``` + * new DOMException("message (can be empty)", DOMExceptionNames.HierarchyRequestError) + * ``` + * 2. according to previous xmldom implementation (only the first argument is required): + * ``` + * new DOMException(DOMException.HIERARCHY_REQUEST_ERR, "optional message") + * ``` + * both result in the proper name being set. + * + * @see https://webidl.spec.whatwg.org/#idl-DOMException + * @see https://webidl.spec.whatwg.org/#dfn-error-names-table + * @see https://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-17189187 + * @see http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/ecma-script-binding.html + * @see http://www.w3.org/TR/REC-DOM-Level-1/ecma-script-language-binding.html + */ + class DOMException extends Error { + constructor(message?: string, name?: DOMExceptionName | string); + constructor(code?: ExceptionCode, message?: string); + + readonly name: DOMExceptionName; + readonly code: ExceptionCode | 0; + static readonly INDEX_SIZE_ERR: 1; + static readonly DOMSTRING_SIZE_ERR: 2; + static readonly HIERARCHY_REQUEST_ERR: 3; + static readonly WRONG_DOCUMENT_ERR: 4; + static readonly INVALID_CHARACTER_ERR: 5; + static readonly NO_DATA_ALLOWED_ERR: 6; + static readonly NO_MODIFICATION_ALLOWED_ERR: 7; + static readonly NOT_FOUND_ERR: 8; + static readonly NOT_SUPPORTED_ERR: 9; + static readonly INUSE_ATTRIBUTE_ERR: 10; + static readonly INVALID_STATE_ERR: 11; + static readonly SYNTAX_ERR: 12; + static readonly INVALID_MODIFICATION_ERR: 13; + static readonly NAMESPACE_ERR: 14; + static readonly INVALID_ACCESS_ERR: 15; + static readonly VALIDATION_ERR: 16; + static readonly TYPE_MISMATCH_ERR: 17; + static readonly SECURITY_ERR: 18; + static readonly NETWORK_ERR: 19; + static readonly ABORT_ERR: 20; + static readonly URL_MISMATCH_ERR: 21; + static readonly QUOTA_EXCEEDED_ERR: 22; + static readonly TIMEOUT_ERR: 23; + static readonly INVALID_NODE_TYPE_ERR: 24; + static readonly DATA_CLONE_ERR: 25; + } + + /** + * Creates an error that will not be caught by XMLReader aka the SAX parser. + */ + class ParseError extends Error { + constructor(message: string, locator?: any, cause?: Error); + + readonly message: string; + readonly locator?: any; + readonly cause?: Error; + } + + // END ./lib/errors.js + + // START ./lib/dom.js + + /** + * Exported for `instanceof` checks only — these types cannot be constructed directly. + */ + type InstanceOf = { + // instanceof pre ts 5.3 + (val: unknown): val is T; + // instanceof post ts 5.3 + [Symbol.hasInstance](val: unknown): val is T; + }; + + type GetRootNodeOptions = { + composed?: boolean; + }; + + /** + * The DOM Node interface is an abstract base class upon which many other DOM API objects are + * based, thus letting those object types to be used similarly and often interchangeably. As an + * abstract class, there is no such thing as a plain Node object. All objects that implement + * Node functionality are based on one of its subclasses. Most notable are Document, Element, + * and DocumentFragment. + * + * In addition, every kind of DOM node is represented by an interface based on Node. These + * include Attr, CharacterData (which Text, Comment, CDATASection and ProcessingInstruction are + * all based on), and DocumentType. + * + * In some cases, a particular feature of the base Node interface may not apply to one of its + * child interfaces; in that case, the inheriting node may return null or throw an exception, + * depending on circumstances. For example, attempting to add children to a node type that + * cannot have children will throw an exception. + * + * **This behavior is slightly different from the in the specs**: + * - unimplemented interfaces: EventTarget + * + * @see http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-1950641247 + * @see https://dom.spec.whatwg.org/#node + * @prettierignore + */ + interface Node { + /** + * Returns the children. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Node/childNodes) + */ + readonly childNodes: NodeList; + /** + * Returns the first child. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Node/firstChild) + */ + readonly firstChild: Node | null; + /** + * Returns the last child. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Node/lastChild) + */ + readonly lastChild: Node | null; + /** + * The local part of the qualified name of this node. + */ + localName: string | null; + /** + * Always returns `about:blank` currently. + * + * [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/API/Node/baseURI) + */ + readonly baseURI: 'about:blank'; + /** + * Returns true if this node is inside of a document or is the document node itself. + * + * [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/API/Node/isConnected) + */ + readonly isConnected: boolean; + /** + * The namespace URI of this node. + */ + readonly namespaceURI: string | null; + /** + * Returns the next sibling. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Node/nextSibling) + */ + readonly nextSibling: Node | null; + /** + * Returns a string appropriate for the type of node. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Node/nodeName) + */ + readonly nodeName: string; + /** + * Returns the type of node. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Node/nodeType) + */ + readonly nodeType: number; + /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Node/nodeValue) */ + nodeValue: string | null; + /** + * Returns the node document. Returns null for documents. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Node/ownerDocument) + */ + readonly ownerDocument: Document | null; + /** + * Returns the parent. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Node/parentNode) + */ + readonly parentNode: Node | null; + /** + * Returns the parent `Node` if it is of type `Element`, otherwise `null`. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Node/parentElement) + */ + readonly parentElement: Element | null; + /** + * The prefix of the namespace for this node. + */ + prefix: string | null; + /** + * Returns the previous sibling. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Node/previousSibling) + */ + readonly previousSibling: Node | null; + /** + * The text content of this node and its descendants. + * + * For {@link Element} and {@link DocumentFragment} nodes, returns the concatenation of the + * `nodeValue` of every descendant text node, excluding processing instruction and comment + * nodes. For all other node types, returns `nodeValue`. + * + * Setting `textContent` on an element or document fragment replaces all child nodes with a + * single text node; on other nodes it sets `data`, `value`, and `nodeValue` directly. + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Node/textContent) + * + * @see {@link https://dom.spec.whatwg.org/#dom-node-textcontent} + */ + textContent: string | null; + + /** + * Zero based line position inside the parsed source, + * if the `locator` was not disabled. + */ + lineNumber?: number; + /** + * One based column position inside the parsed source, + * if the `locator` was not disabled. + */ + columnNumber?: number; + + /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Node/appendChild) */ + appendChild(node: Node): Node; + + /** + * Checks whether `other` is an inclusive descendant of this node. + * + * [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/API/Node/contains) + */ + contains(other: Node | null | undefined): boolean; + /** + * Searches for the root node of this node. + * + * **This behavior is slightly different from the one in the specs**: + * - ignores `options.composed`, since `ShadowRoot`s are unsupported, therefore always + * returning root. + * + * [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/API/Node/getRootNode) + * + * @see https://dom.spec.whatwg.org/#dom-node-getrootnode + * @see https://dom.spec.whatwg.org/#concept-shadow-including-root + */ + getRootNode(options: GetRootNodeOptions): Node; + + /** + * Checks whether the given node is equal to this node. + * + * Two nodes are equal when they have the same type, defining characteristics (for the type), + * and the same `childNodes`. The comparison is iterative to avoid stack overflows on deeply + * nested trees. `Attribute` nodes of each `Element` pair are also compared iteratively. + * + * @see {@link https://dom.spec.whatwg.org/#concept-node-equals} + * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Node/isEqualNode} + */ + isEqualNode(other: Node): boolean; + + /** + * Checks whether the given node is this node. + * + * [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/API/Node/isSameNode) + */ + isSameNode(other: Node): boolean; + + /** + * Returns a copy of node. If deep is true, the copy also includes the node's descendants. + * + * @throws {DOMException} + * May throw a DOMException if operations within {@link Element#setAttributeNode} or + * {@link Node#appendChild} (which are potentially invoked in this method) do not meet their + * specific constraints. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Node/cloneNode) + */ + cloneNode(deep?: boolean): Node; + + /** + * Returns a bitmask indicating the position of other relative to node. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Node/compareDocumentPosition) + */ + compareDocumentPosition(other: Node): number; + + /** + * Returns whether node has children. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Node/hasChildNodes) + */ + hasChildNodes(): boolean; + + /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Node/insertBefore) */ + insertBefore(node: Node, child: Node | null): Node; + + /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Node/isDefaultNamespace) */ + isDefaultNamespace(namespace: string | null): boolean; + + /** + * Checks whether the DOM implementation implements a specific feature and its version. + * + * @deprecated + * Since `DOMImplementation.hasFeature` is deprecated and always returns true. + * @param feature + * The package name of the feature to test. This is the same name that can be passed to the + * method `hasFeature` on `DOMImplementation`. + * @param version + * This is the version number of the package name to test. + * @since Introduced in DOM Level 2 + * @see {@link DOMImplementation.hasFeature} + */ + isSupported(feature: string, version: string): true; + + /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Node/lookupNamespaceURI) */ + lookupNamespaceURI(prefix: string | null): string | null; + + /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Node/lookupPrefix) */ + lookupPrefix(namespace: string | null): string | null; + + /** + * Removes empty exclusive Text nodes and concatenates the data of remaining contiguous + * exclusive Text nodes into the first of their nodes. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Node/normalize) + */ + normalize(): void; + + /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Node/removeChild) */ + removeChild(child: Node): Node; + + /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Node/replaceChild) */ + replaceChild(node: Node, child: Node): Node; + + /** node is an element. */ + readonly ELEMENT_NODE: 1; + readonly ATTRIBUTE_NODE: 2; + /** node is a Text node. */ + readonly TEXT_NODE: 3; + /** node is a CDATASection node. */ + readonly CDATA_SECTION_NODE: 4; + readonly ENTITY_REFERENCE_NODE: 5; + readonly ENTITY_NODE: 6; + /** node is a ProcessingInstruction node. */ + readonly PROCESSING_INSTRUCTION_NODE: 7; + /** node is a Comment node. */ + readonly COMMENT_NODE: 8; + /** node is a document. */ + readonly DOCUMENT_NODE: 9; + /** node is a doctype. */ + readonly DOCUMENT_TYPE_NODE: 10; + /** node is a DocumentFragment node. */ + readonly DOCUMENT_FRAGMENT_NODE: 11; + readonly NOTATION_NODE: 12; + /** Set when node and other are not in the same tree. */ + readonly DOCUMENT_POSITION_DISCONNECTED: 0x01; + /** Set when other is preceding node. */ + readonly DOCUMENT_POSITION_PRECEDING: 0x02; + /** Set when other is following node. */ + readonly DOCUMENT_POSITION_FOLLOWING: 0x04; + /** Set when other is an ancestor of node. */ + readonly DOCUMENT_POSITION_CONTAINS: 0x08; + /** Set when other is a descendant of node. */ + readonly DOCUMENT_POSITION_CONTAINED_BY: 0x10; + readonly DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC: 0x20; + } + + var Node: InstanceOf & { + /** node is an element. */ + readonly ELEMENT_NODE: 1; + readonly ATTRIBUTE_NODE: 2; + /** node is a Text node. */ + readonly TEXT_NODE: 3; + /** node is a CDATASection node. */ + readonly CDATA_SECTION_NODE: 4; + readonly ENTITY_REFERENCE_NODE: 5; + readonly ENTITY_NODE: 6; + /** node is a ProcessingInstruction node. */ + readonly PROCESSING_INSTRUCTION_NODE: 7; + /** node is a Comment node. */ + readonly COMMENT_NODE: 8; + /** node is a document. */ + readonly DOCUMENT_NODE: 9; + /** node is a doctype. */ + readonly DOCUMENT_TYPE_NODE: 10; + /** node is a DocumentFragment node. */ + readonly DOCUMENT_FRAGMENT_NODE: 11; + readonly NOTATION_NODE: 12; + /** Set when node and other are not in the same tree. */ + readonly DOCUMENT_POSITION_DISCONNECTED: 0x01; + /** Set when other is preceding node. */ + readonly DOCUMENT_POSITION_PRECEDING: 0x02; + /** Set when other is following node. */ + readonly DOCUMENT_POSITION_FOLLOWING: 0x04; + /** Set when other is an ancestor of node. */ + readonly DOCUMENT_POSITION_CONTAINS: 0x08; + /** Set when other is a descendant of node. */ + readonly DOCUMENT_POSITION_CONTAINED_BY: 0x10; + readonly DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC: 0x20; + }; + + /** + * A DOM element's attribute as an object. In most DOM methods, you will probably directly + * retrieve the attribute as a string (e.g., Element.getAttribute(), but certain functions (e.g., + * Element.getAttributeNode()) or means of iterating give Attr types. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Attr) + */ + interface Attr extends Node { + readonly nodeType: typeof Node.ATTRIBUTE_NODE; + /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Attr/name) */ + readonly name: string; + /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Attr/namespaceURI) */ + readonly namespaceURI: string | null; + readonly ownerDocument: Document; + /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Attr/ownerElement) */ + readonly ownerElement: Element | null; + /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Attr/prefix) */ + readonly prefix: string | null; + /** + * @deprecated + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Attr/specified) + */ + readonly specified: true; + /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Attr/value) */ + value: string; + } + /** + * A DOM element's attribute as an object. In most DOM methods, you will probably directly + * retrieve the attribute as a string (e.g., Element.getAttribute(), but certain functions (e.g., + * Element.getAttributeNode()) or means of iterating give Attr types. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Attr) + */ + var Attr: InstanceOf; + + /** + * Objects implementing the NamedNodeMap interface are used to represent collections of nodes + * that can be accessed by name. + * Note that NamedNodeMap does not inherit from NodeList; + * NamedNodeMaps are not maintained in any particular order. + * Objects contained in an object implementing NamedNodeMap may also be accessed by an ordinal + * index, + * but this is simply to allow convenient enumeration of the contents of a NamedNodeMap, + * and does not imply that the DOM specifies an order to these Nodes. + * NamedNodeMap objects in the DOM are live. + * used for attributes or DocumentType entities + * + * This implementation only supports property indices, but does not support named properties, + * as specified in the living standard. + * + * @see https://dom.spec.whatwg.org/#interface-namednodemap + * @see https://webidl.spec.whatwg.org/#dfn-supported-property-names + */ + class NamedNodeMap implements Iterable { + /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/NamedNodeMap/length) */ + readonly length: number; + /** + * Get an attribute by name. Note: Name is in lower case in case of HTML namespace and + * document. + * + * @see https://dom.spec.whatwg.org/#concept-element-attributes-get-by-name + */ + getNamedItem(qualifiedName: string): Attr | null; + /** + * Get an attribute by namespace and local name. + * + * @see https://dom.spec.whatwg.org/#concept-element-attributes-get-by-namespace + */ + getNamedItemNS(namespace: string | null, localName: string): Attr | null; + /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/NamedNodeMap/item) */ + item(index: number): Attr | null; + + /** + * Removes an attribute specified by the local name. + * + * @throws {DOMException} + * With code: + * - {@link DOMException.NOT_FOUND_ERR} if no attribute with the given name is found. + * @see https://dom.spec.whatwg.org/#dom-namednodemap-removenameditem + * @see https://dom.spec.whatwg.org/#concept-element-attributes-remove-by-name + */ + removeNamedItem(qualifiedName: string): Attr; + /** + * Removes an attribute specified by the namespace and local name. + * + * @throws {DOMException} + * With code: + * - {@link DOMException.NOT_FOUND_ERR} if no attribute with the given namespace URI and + * local name is found. + * @see https://dom.spec.whatwg.org/#dom-namednodemap-removenameditemns + * @see https://dom.spec.whatwg.org/#concept-element-attributes-remove-by-namespace + */ + removeNamedItemNS(namespace: string | null, localName: string): Attr; + /** + * Set an attribute. + * + * @throws {DOMException} + * With code: + * - {@link INUSE_ATTRIBUTE_ERR} - If the attribute is already an attribute of another + * element. + * @see https://dom.spec.whatwg.org/#concept-element-attributes-set + */ + setNamedItem(attr: Attr): Attr | null; + /** + * Set an attribute, replacing an existing attribute with the same local name and namespace + * URI if one exists. + * + * @throws {DOMException} + * Throws a DOMException with the name "InUseAttributeError" if the attribute is already an + * attribute of another element. + * @see https://dom.spec.whatwg.org/#concept-element-attributes-set + */ + setNamedItemNS(attr: Attr): Attr | null; + [index: number]: Attr; + [Symbol.iterator](): Iterator; + } + + /** + * NodeList objects are collections of nodes, usually returned by properties such as + * Node.childNodes and methods such as document.querySelectorAll(). + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/NodeList) + */ + class NodeList implements Iterable { + /** + * Returns the number of nodes in the collection. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/NodeList/length) + */ + readonly length: number; + /** + * Returns the node with index index from the collection. The nodes are sorted in tree order. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/NodeList/item) + */ + item(index: number): T | null; + /** + * Returns a string representation of the NodeList. + * Accepts the same options as `XMLSerializer.prototype.serializeToString`. + */ + toString( + options?: XMLSerializerOptions | ((node: T) => T | undefined) + ): string; + /** + * Filters the NodeList based on a predicate. + * + * @private + */ + filter(predicate: (node: T) => boolean): T[]; + /** + * Returns the first index at which a given node can be found in the NodeList, or -1 if it is + * not present. + * + * @private + */ + indexOf(node: T): number; + + /** + * Index based access returns `undefined`, when accessing indexes >= `length`. + * But it would break a lot of code (like `Array.from` usages), + * if it would be typed as `T | undefined`. + */ + [index: number]: T; + + [Symbol.iterator](): Iterator; + } + + /** + * Represents a live collection of nodes that is automatically updated when its associated + * document changes. + */ + interface LiveNodeList extends NodeList {} + /** + * Represents a live collection of nodes that is automatically updated when its associated + * document changes. + */ + var LiveNodeList: InstanceOf; + + /** + * Element is the most general base class from which all objects in a Document inherit. It only + * has methods and properties common to all kinds of elements. More specific classes inherit from + * Element. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Element) + */ + interface Element extends Node { + readonly nodeType: typeof Node.ELEMENT_NODE; + + /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/attributes) */ + readonly attributes: NamedNodeMap; + /** + * Returns the HTML-uppercased qualified name. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/tagName) + */ + readonly tagName: string; + + /** + * Returns a live collection of the direct child elements of this element. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/children) + * + * @see https://dom.spec.whatwg.org/#dom-parentnode-children + */ + readonly children: LiveNodeList; + + /** + * Returns element's first attribute whose qualified name is qualifiedName, and null if there + * is no such attribute otherwise. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/getAttribute) + */ + getAttribute(qualifiedName: string): string | null; + /** + * Returns element's attribute whose namespace is namespace and local name is localName, and + * null if there is no such attribute otherwise. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/getAttributeNS) + */ + getAttributeNS(namespace: string | null, localName: string): string | null; + /** + * Returns the qualified names of all element's attributes. Can contain duplicates. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/getAttributeNames) + */ + getAttributeNames(): string[]; + /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/getAttributeNode) */ + getAttributeNode(qualifiedName: string): Attr | null; + /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/getAttributeNodeNS) */ + getAttributeNodeNS( + namespace: string | null, + localName: string + ): Attr | null; + /** + * Returns a LiveNodeList of all child elements which have **all** of the given class + * name(s). + * + * Returns an empty list if `classNames` is an empty string or only contains HTML white space + * characters. + * + * Warning: This returns a live LiveNodeList. + * Changes in the DOM will reflect in the array as the changes occur. + * If an element selected by this array no longer qualifies for the selector, + * it will automatically be removed. Be aware of this for iteration purposes. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/Element/getElementsByClassName + * @see https://dom.spec.whatwg.org/#concept-getelementsbyclassname + */ + getElementsByClassName(classNames: string): LiveNodeList; + + /** + * Returns a LiveNodeList of elements with the given qualifiedName. + * Searching for all descendants can be done by passing `*` as `qualifiedName`. + * + * All descendants of the specified element are searched, but not the element itself. + * The returned list is live, which means it updates itself with the DOM tree automatically. + * Therefore, there is no need to call `Element.getElementsByTagName()` + * with the same element and arguments repeatedly if the DOM changes in between calls. + * + * When called on an HTML element in an HTML document, + * `getElementsByTagName` lower-cases the argument before searching for it. + * This is undesirable when trying to match camel-cased SVG elements (such as + * ``) in an HTML document. + * Instead, use `Element.getElementsByTagNameNS()`, + * which preserves the capitalization of the tag name. + * + * `Element.getElementsByTagName` is similar to `Document.getElementsByTagName()`, + * except that it only searches for elements that are descendants of the specified element. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/Element/getElementsByTagName + * @see https://dom.spec.whatwg.org/#concept-getelementsbytagname + */ + getElementsByTagName(qualifiedName: string): LiveNodeList; + + /** + * Returns a `LiveNodeList` of elements with the given tag name belonging to the given + * namespace. It is similar to `Document.getElementsByTagNameNS`, except that its search is + * restricted to descendants of the specified element. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/getElementsByTagNameNS) + * */ + getElementsByTagNameNS( + namespaceURI: string | null, + localName: string + ): LiveNodeList; + + getQualifiedName(): string; + /** + * Returns true if element has an attribute whose qualified name is qualifiedName, and false + * otherwise. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/hasAttribute) + */ + hasAttribute(qualifiedName: string): boolean; + /** + * Returns true if element has an attribute whose namespace is namespace and local name is + * localName. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/hasAttributeNS) + */ + hasAttributeNS(namespace: string | null, localName: string): boolean; + /** + * Returns true if element has attributes, and false otherwise. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/hasAttributes) + */ + hasAttributes(): boolean; + /** + * Removes element's first attribute whose qualified name is qualifiedName. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/removeAttribute) + */ + removeAttribute(qualifiedName: string): void; + /** + * Removes element's attribute whose namespace is namespace and local name is localName. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/removeAttributeNS) + */ + removeAttributeNS(namespace: string | null, localName: string): void; + /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/removeAttributeNode) */ + removeAttributeNode(attr: Attr): Attr; + /** + * Sets the value of element's first attribute whose qualified name is qualifiedName to value. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/setAttribute) + */ + setAttribute(qualifiedName: string, value: string): void; + /** + * Sets the value of element's attribute whose namespace is namespace and local name is + * localName to value. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/setAttributeNS) + */ + setAttributeNS( + namespace: string | null, + qualifiedName: string, + value: string + ): void; + /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/setAttributeNode) */ + setAttributeNode(attr: Attr): Attr | null; + /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/setAttributeNodeNS) */ + setAttributeNodeNS(attr: Attr): Attr | null; + } + /** + * Element is the most general base class from which all objects in a Document inherit. It only + * has methods and properties common to all kinds of elements. More specific classes inherit from + * Element. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Element) + */ + var Element: InstanceOf; + + /** + * The CharacterData abstract interface represents a Node object that contains characters. This + * is an abstract interface, meaning there aren't any object of type CharacterData: it is + * implemented by other interfaces, like Text, Comment, or ProcessingInstruction which aren't + * abstract. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CharacterData) + */ + interface CharacterData extends Node { + /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/CharacterData/data) */ + data: string; + /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/CharacterData/length) */ + readonly length: number; + readonly ownerDocument: Document; + /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/CharacterData/appendData) */ + appendData(data: string): void; + /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/CharacterData/deleteData) */ + deleteData(offset: number, count: number): void; + /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/CharacterData/insertData) */ + insertData(offset: number, data: string): void; + /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/CharacterData/replaceData) */ + replaceData(offset: number, count: number, data: string): void; + /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/CharacterData/substringData) */ + substringData(offset: number, count: number): string; + } + /** + * The CharacterData abstract interface represents a Node object that contains characters. This + * is an abstract interface, meaning there aren't any object of type CharacterData: it is + * implemented by other interfaces, like Text, Comment, or ProcessingInstruction which aren't + * abstract. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CharacterData) + */ + var CharacterData: InstanceOf; + + /** + * The textual content of Element or Attr. If an element has no markup within its content, it has + * a single child implementing Text that contains the element's text. However, if the element + * contains markup, it is parsed into information items and Text nodes that form its children. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Text) + */ + interface Text extends CharacterData { + nodeName: '#text' | '#cdata-section'; + nodeType: typeof Node.TEXT_NODE | typeof Node.CDATA_SECTION_NODE; + /** + * Splits data at the given offset and returns the remainder as Text node. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Text/splitText) + */ + splitText(offset: number): Text; + } + + /** + * The textual content of Element or Attr. If an element has no markup within its content, it has + * a single child implementing Text that contains the element's text. However, if the element + * contains markup, it is parsed into information items and Text nodes that form its children. + * + * __This implementation differs from the specification:__ not constructable, + * use `document.createTextNode(data)` instead. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Text) + */ + var Text: InstanceOf; + + /** + * The Comment interface represents textual notations within markup; although it is generally not + * visually shown, such comments are available to be read in the source view. Comments are + * represented in HTML and XML as content between ''. In XML, like inside SVG or + * MathML markup, the character sequence '--' cannot be used within a comment. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Comment) + */ + interface Comment extends CharacterData { + nodeName: '#comment'; + nodeType: typeof Node.COMMENT_NODE; + } + /** + * The Comment interface represents textual notations within markup; although it is generally not + * visually shown, such comments are available to be read in the source view. Comments are + * represented in HTML and XML as content between ''. In XML, like inside SVG or + * MathML markup, the character sequence '--' cannot be used within a comment. + * + * __This implementation differs from the specification:__ not constructable, + * use `document.createComment(data)` instead. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Comment) + */ + var Comment: InstanceOf; + + /** + * A CDATA section that can be used within XML to include extended portions of unescaped text. + * The symbols < and & don’t need escaping as they normally do when inside a CDATA section. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CDATASection) + */ + interface CDATASection extends Text { + nodeName: '#cdata-section'; + nodeType: typeof Node.CDATA_SECTION_NODE; + } + /** + * A CDATA section that can be used within XML to include extended portions of unescaped text. + * The symbols < and & don’t need escaping as they normally do when inside a CDATA section. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CDATASection) + */ + var CDATASection: InstanceOf; + + /** + * The DocumentFragment interface represents a minimal document object that has no parent. + * It is used as a lightweight version of Document that stores a segment of a document structure + * comprised of nodes just like a standard document. + * The key difference is due to the fact that the document fragment isn't part + * of the active document tree structure. + * Changes made to the fragment don't affect the document. + */ + interface DocumentFragment extends Node { + readonly ownerDocument: Document; + + /** + * Returns a live collection of the direct child elements of this document fragment. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/DocumentFragment/children) + * + * @see https://dom.spec.whatwg.org/#dom-parentnode-children + */ + readonly children: LiveNodeList; + + getElementById(elementId: string): Element | null; + } + /** + * __This implementation differs from the specification:__ not constructable, + * use `document.createDocumentFragment()` instead. + */ + var DocumentFragment: InstanceOf; + + interface Entity extends Node { + nodeType: typeof Node.ENTITY_NODE; + } + var Entity: InstanceOf; + + /** + * Represents an EntityReference node, serialized as `&nodeName;`. + * + * `nodeName` is the referenced entity's name, stored verbatim. When serialized with + * `requireWellFormed: true`, the serializer validates `nodeName` against the XML `Name` + * production and throws `InvalidStateError` if it does not match; without that option the name + * is emitted verbatim between `&` and `;`. + * + * xmldom does not expand entities — the parser resolves entity references inline and never + * constructs `EntityReference` nodes, so the only producer is `Document.createEntityReference`. + */ + interface EntityReference extends Node { + nodeType: typeof Node.ENTITY_REFERENCE_NODE; + } + var EntityReference: InstanceOf; + + interface Notation extends Node { + nodeType: typeof Node.NOTATION_NODE; + } + var Notation: InstanceOf; + + interface ProcessingInstruction extends CharacterData { + nodeType: typeof Node.PROCESSING_INSTRUCTION_NODE; + /** + * A string representing the textual data contained in this object. + * For `ProcessingInstruction`, that means everything that goes after the `target`, excluding + * `?>`. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CharacterData/data) + */ + data: string; + /** + * A string containing the name of the application. + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ProcessingInstruction/target) */ + readonly target: string; + } + var ProcessingInstruction: InstanceOf; + + interface Document extends Node { + /** + * The mime type of the document is determined at creation time and can not be modified. + * + * @see https://dom.spec.whatwg.org/#concept-document-content-type + * @see {@link DOMImplementation} + * @see {@link MIME_TYPE} + */ + readonly contentType: MIME_TYPE; + /** + * @see https://dom.spec.whatwg.org/#concept-document-type + * @see {@link DOMImplementation} + */ + readonly type: 'html' | 'xml'; + /** + * The implementation that created this document. + * + * @readonly + */ + readonly implementation: DOMImplementation; + readonly ownerDocument: Document; + readonly nodeName: '#document'; + readonly nodeType: typeof Node.DOCUMENT_NODE; + readonly doctype: DocumentType | null; + /** + * Gets a reference to the root node of the document. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Document/documentElement) + */ + readonly documentElement: Element | null; + + /** + * Returns a live collection of the direct child elements of this document. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Document/children) + * + * @see https://dom.spec.whatwg.org/#dom-parentnode-children + */ + readonly children: LiveNodeList; + + /** + * Creates an attribute object with a specified name. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Document/createAttribute) + */ + createAttribute(localName: string): Attr; + + /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Document/createAttributeNS) */ + createAttributeNS(namespace: string | null, qualifiedName: string): Attr; + + /** + * Returns a new CDATASection node whose data is `data`. + * + * __This implementation differs from the specification:__ - calling this method on an HTML + * document does not throw `NotSupportedError`. + * + * @throws {DOMException} + * With code `INVALID_CHARACTER_ERR` if `data` contains `"]]>"`. + * @see https://developer.mozilla.org/en-US/docs/Web/API/Document/createCDATASection + * @see https://dom.spec.whatwg.org/#dom-document-createcdatasection + */ + createCDATASection(data: string): CDATASection; + + /** + * Creates a comment object with the specified data. + * + * No validation is performed at creation time. When the resulting document is serialized + * with `requireWellFormed: true`, the serializer throws `InvalidStateError` if the comment + * data contains `--` anywhere, ends with `-`, or contains characters outside the XML Char + * production (W3C DOM Parsing §3.2.1.3). Without that option the data is emitted verbatim. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Document/createComment) + */ + createComment(data: string): Comment; + + /** + * Creates a new document. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Document/createDocumentFragment) + */ + createDocumentFragment(): DocumentFragment; + + createElement(tagName: string): Element; + + /** + * Returns an element with namespace namespace. Its namespace prefix will be everything before + * ":" (U+003E) in qualifiedName or null. Its local name will be everything after ":" (U+003E) + * in qualifiedName or qualifiedName. + * + * If localName does not match the Name production an "InvalidCharacterError" DOMException will + * be thrown. + * + * If one of the following conditions is true a "NamespaceError" DOMException will be thrown: + * + * localName does not match the QName production. + * Namespace prefix is not null and namespace is the empty string. + * Namespace prefix is "xml" and namespace is not the XML namespace. + * qualifiedName or namespace prefix is "xmlns" and namespace is not the XMLNS namespace. + * namespace is the XMLNS namespace and neither qualifiedName nor namespace prefix is "xmlns". + * + * When supplied, options's is can be used to create a customized built-in element. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Document/createElementNS) + */ + createElementNS(namespace: string | null, qualifiedName: string): Element; + /** + * Creates an EntityReference object. + * The current implementation does not fill the `childNodes` with those of the corresponding + * `Entity` + * + * The name of the entity to reference. No namespace well-formedness checks are performed. + * + * The `name` is validated against the XML `Name` production at creation time; an invalid + * name throws `InvalidCharacterError`. When the resulting node is serialized with + * `requireWellFormed: true`, the serializer re-validates `nodeName` against the XML `Name` + * production and throws `InvalidStateError` if a later `nodeName` mutation made it invalid; + * without that option the name is emitted verbatim. + * + * __This implementation differs from the specification:__ xmldom does not expand entities — + * the parser resolves entity references inline and never constructs `EntityReference` nodes, + * so this method is the only producer. + * + * @deprecated + * In DOM Level 4. + * @returns {EntityReference} + * @throws {DOMException} + * With code `INVALID_CHARACTER_ERR` when `name` is not a valid XML `Name`. + * @throws {DOMException} + * with code `NOT_SUPPORTED_ERR` when the document is of type `html` + * @see https://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-392B75AE + */ + createEntityReference(name: string): EntityReference; + + /** + * Returns a ProcessingInstruction node whose target is target and data is data. + * + * __This behavior is slightly different from the in the specs__: + * - it does not do any input validation on the arguments and doesn't throw + * "InvalidCharacterError". + * + * Note: When the resulting document is serialized with `requireWellFormed: true`, the + * serializer throws `InvalidStateError` if `.target` is not a valid XML `NCName` (a `Name` + * with no colon) or is an ASCII case-insensitive match for `"xml"`, or if `.data` contains + * `?>` or characters outside the XML Char production (W3C DOM Parsing §3.2.1.7). Without + * that option the target and data are emitted verbatim. + * + * @see https://developer.mozilla.org/docs/Web/API/Document/createProcessingInstruction + * @see https://dom.spec.whatwg.org/#dom-document-createprocessinginstruction + * @see https://www.w3.org/TR/DOM-Parsing/#dfn-concept-serialize-xml §3.2.1.7 + */ + createProcessingInstruction( + target: string, + data: string + ): ProcessingInstruction; + + /** + * Creates a text string from the specified value. + * + * @param data + * String that specifies the nodeValue property of the text node. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Document/createTextNode) + */ + createTextNode(data: string): Text; + + /** + * Returns a reference to the first object with the specified value of the ID attribute. + */ + getElementById(elementId: string): Element | null; + + /** + * Returns a LiveNodeList of all child elements which have **all** of the given class + * name(s). + * + * Returns an empty list if `classNames` is an empty string or only contains HTML white space + * characters. + * + * Warning: This returns a live LiveNodeList. + * Changes in the DOM will reflect in the array as the changes occur. + * If an element selected by this array no longer qualifies for the selector, + * it will automatically be removed. Be aware of this for iteration purposes. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/Document/getElementsByClassName + * @see https://dom.spec.whatwg.org/#concept-getelementsbyclassname + */ + getElementsByClassName(classNames: string): LiveNodeList; + + /** + * Returns a LiveNodeList of elements with the given qualifiedName. + * Searching for all descendants can be done by passing `*` as `qualifiedName`. + * + * The complete document is searched, including the root node. + * The returned list is live, which means it updates itself with the DOM tree automatically. + * Therefore, there is no need to call `Element.getElementsByTagName()` + * with the same element and arguments repeatedly if the DOM changes in between calls. + * + * When called on an HTML element in an HTML document, + * `getElementsByTagName` lower-cases the argument before searching for it. + * This is undesirable when trying to match camel-cased SVG elements (such as + * ``) in an HTML document. + * Instead, use `Element.getElementsByTagNameNS()`, + * which preserves the capitalization of the tag name. + * + * `Element.getElementsByTagName` is similar to `Document.getElementsByTagName()`, + * except that it only searches for elements that are descendants of the specified element. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/Element/getElementsByTagName + * @see https://dom.spec.whatwg.org/#concept-getelementsbytagname + */ + getElementsByTagName(qualifiedName: string): LiveNodeList; + + /** + * Returns a `LiveNodeList` of elements with the given tag name belonging to the given + * namespace. The complete document is searched, including the root node. + * + * The returned list is live, which means it updates itself with the DOM tree automatically. + * Therefore, there is no need to call `Element.getElementsByTagName()` + * with the same element and arguments repeatedly if the DOM changes in between calls. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/getElementsByTagNameNS) + * */ + getElementsByTagNameNS( + namespaceURI: string | null, + localName: string + ): LiveNodeList; + /** + * Imports a node from another document into this document, creating a new copy owned by this + * document. If `deep` is true, the copy also includes the node's descendants. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Document/importNode) + * + * @see {@link https://dom.spec.whatwg.org/#dom-document-importnode} + */ + importNode(node: T, deep?: boolean): T; + } + + var Document: InstanceOf; + + /** + * A Node containing a doctype. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/DocumentType) + */ + interface DocumentType extends Node { + /** + * The doctype name, stored verbatim. + * Declared `readonly` by the WHATWG DOM spec; xmldom does not enforce this — direct + * property writes succeed and the written value is serialized verbatim. + * When serialized with `requireWellFormed: true`, throws `InvalidStateError` if the value + * is not a valid XML `Name` production (XML 1.0 [5]). + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/DocumentType/name) + */ + readonly name: string; + /** + * The internal subset string (the raw content between `[` and `]`), or an empty string. + * Declared `readonly` by the WHATWG DOM spec; xmldom does not enforce this — direct + * property writes succeed and the written value is serialized verbatim. + * When serialized with `requireWellFormed: true`, throws `InvalidStateError` if the value + * contains `"]>"`. + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/DocumentType/internalSubset) + */ + readonly internalSubset: string; + /** + * The external subset public identifier, stored verbatim including surrounding quotes. + * Declared `readonly` by the WHATWG DOM spec; xmldom does not enforce this — direct + * property writes succeed and the written value is serialized verbatim. + * When serialized with `requireWellFormed: true`, throws `InvalidStateError` if the value + * is non-empty and does not match the XML `PubidLiteral` production (XML 1.0 [12]). + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/DocumentType/publicId) + */ + readonly publicId: string; + /** + * The external subset system identifier, stored verbatim including surrounding quotes. + * Declared `readonly` by the WHATWG DOM spec; xmldom does not enforce this — direct + * property writes succeed and the written value is serialized verbatim. + * When serialized with `requireWellFormed: true`, throws `InvalidStateError` if the value + * is non-empty and does not match the XML `SystemLiteral` production (XML 1.0 [11]). + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/DocumentType/systemId) + */ + readonly systemId: string; + } + + var DocumentType: InstanceOf; + + class DOMImplementation { + /** + * The DOMImplementation interface represents an object providing methods which are not + * dependent on any particular document. + * Such an object is returned by the `Document.implementation` property. + * + * __The individual methods describe the differences compared to the specs.__. + * + * @class + * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMImplementation MDN + * @see https://www.w3.org/TR/REC-DOM-Level-1/level-one-core.html#ID-102161490 DOM Level 1 + * Core (Initial) + * @see https://www.w3.org/TR/DOM-Level-2-Core/core.html#ID-102161490 DOM Level 2 Core + * @see https://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-102161490 DOM Level 3 Core + * @see https://dom.spec.whatwg.org/#domimplementation DOM Living Standard + */ + constructor(); + + /** + * Creates an XML Document object of the specified type with its document element. + * + * __It behaves slightly different from the description in the living standard__: + * - There is no interface/class `XMLDocument`, it returns a `Document` instance (with it's + * `type` set to `'xml'`). + * - `encoding`, `mode`, `origin`, `url` fields are currently not declared. + * + * @returns {Document} + * The XML document. + * @see {@link DOMImplementation.createHTMLDocument} + * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMImplementation/createDocument MDN + * @see https://www.w3.org/TR/DOM-Level-2-Core/core.html#Level-2-Core-DOM-createDocument DOM + * Level 2 Core (initial) + * @see https://dom.spec.whatwg.org/#dom-domimplementation-createdocument DOM Level 2 Core + */ + createDocument( + namespaceURI: NAMESPACE | string | null, + qualifiedName: string, + doctype?: DocumentType | null + ): Document; + + /** + * Returns a doctype, with the given `qualifiedName`, `publicId`, and `systemId`. + * + * __This behavior is slightly different from the in the specs__: + * - `encoding`, `mode`, `origin`, `url` fields are currently not declared. + * + * @returns {DocumentType} + * which can either be used with `DOMImplementation.createDocument` + * upon document creation or can be put into the document via methods like + * `Node.insertBefore()` or `Node.replaceChild()` + * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMImplementation/createDocumentType + * MDN + * @see https://www.w3.org/TR/DOM-Level-2-Core/core.html#Level-2-Core-DOM-createDocType DOM + * Level 2 Core + * @see https://dom.spec.whatwg.org/#dom-domimplementation-createdocumenttype DOM Living + * Standard + */ + createDocumentType( + qualifiedName: string, + /** + * External subset public identifier. Stored verbatim including surrounding quotes. + * No creation-time validation — deferred to a future breaking release. + */ + publicId?: string, + /** + * External subset system identifier. Stored verbatim including surrounding quotes. + * No creation-time validation — deferred to a future breaking release. + */ + systemId?: string, + /** + * Internal subset string (content between `[` and `]`). Stored verbatim. + * No creation-time validation — deferred to a future breaking release. + */ + internalSubset?: string + ): DocumentType; + + /** + * Returns an HTML document, that might already have a basic DOM structure. + * + * __It behaves slightly different from the description in the living standard__: + * - If the first argument is `false` no initial nodes are added (steps 3-7 in the specs are + * omitted) + * - several properties and methods are missing - Nothing related to events is implemented. + * + * @see {@link DOMImplementation.createDocument} + * @see https://dom.spec.whatwg.org/#dom-domimplementation-createhtmldocument + * @see https://dom.spec.whatwg.org/#html-document + */ + createHTMLDocument(title?: string | false): Document; + + /** + * The DOMImplementation.hasFeature() method returns a Boolean flag indicating if a given + * feature is supported. The different implementations fairly diverged in what kind of + * features were reported. The latest version of the spec settled to force this method to + * always return true, where the functionality was accurate and in use. + * + * @deprecated + * It is deprecated and modern browsers return true in all cases. + * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMImplementation/hasFeature MDN + * @see https://www.w3.org/TR/REC-DOM-Level-1/level-one-core.html#ID-5CED94D7 DOM Level 1 + * Core + * @see https://dom.spec.whatwg.org/#dom-domimplementation-hasfeature DOM Living Standard + */ + hasFeature(feature: string, version?: string): true; + } + + /** Options accepted by `XMLSerializer.prototype.serializeToString` and `node.toString`. */ + interface XMLSerializerOptions { + /** + * When `true`, the serializer throws `InvalidStateError` for content that would produce + * ill-formed XML: CDATASection data containing `"]]>"`; Text data with characters outside + * the XML Char production; a Comment node whose data contains `--` anywhere or ends with + * `-`; or a Document with no `documentElement`. + * + * @default false + */ + requireWellFormed?: boolean; + /** + * When `true` (the default), `"]]>"` sequences in CDATASection data are split across + * concatenated CDATA sections. **Deprecated** — this option and the underlying split + * mechanics will be removed in the next breaking release. Callers should migrate to `{ + * requireWellFormed: true }`, which throws `InvalidStateError` instead of transforming. + * + * @default true + */ + splitCDATASections?: boolean; + /** A filter function applied to each node before serialization. */ + nodeFilter?: (node: Node) => Node | null | undefined; + } + + class XMLSerializer { + /** + * Returns the result of serializing `node` to XML. + * + * When `options.requireWellFormed` is `true`, throws `InvalidStateError` for content that + * would produce ill-formed XML. When `options.splitCDATASections` is `false`, + * CDATASection data is emitted verbatim. Passing a function as `options` is treated as a + * legacy `nodeFilter` for backward compatibility. + * + * __This implementation differs from the specification:__ - CDATASection serialization is + * not specified by W3C DOM Parsing or WHATWG DOM Parsing (see + * {@link https://github.com/w3c/DOM-Parsing/issues/38 w3c/DOM-Parsing#38}). + * When `splitCDATASections` is `true` (the default), `"]]>"` sequences are split across + * concatenated CDATA sections — **deprecated**, will be removed in the next breaking + * release. + * - W3C DOM Parsing §3.2.1.1 requires well-formedness checks on Element `localName`s, + * prefixes, and attribute serialization when `requireWellFormed` is `true`. Element and + * attribute qualified names (which cover the namespace prefix) are validated against the XML + * `QName` production; the remaining §3.2.1.1 checks (duplicate attributes, + * namespace-declaration consistency) and creation-time name validation are **not + * implemented** in this release — see the tracking issue filed against the next breaking + * milestone. + * + * @throws {DOMException} + * `InvalidStateError` when `requireWellFormed` is `true` and any of the following conditions + * hold: + * - an Element's qualified name (including any namespace prefix) is not a valid XML QName + * - an attribute's qualified name (including a synthesized `xmlns:` namespace declaration) is + * not a valid XML QName + * - CDATASection data contains `"]]>"` + * - Text data contains characters outside the XML Char production + * - a Comment node's data contains `--` anywhere or ends with `-` + * - a ProcessingInstruction's target is not a valid XML `NCName` (a `Name` with no colon) or + * is an ASCII case-insensitive match for `"xml"`, or its data contains `?>` or characters + * outside the XML Char production + * - a DocumentType's `name` is not a valid XML `Name` (XML 1.0 production [5]) + * - a DocumentType's `publicId` is non-empty and does not match the XML `PubidLiteral` + * production (W3C DOM Parsing §3.2.1.3; XML 1.0 production [12]) + * - a DocumentType's `systemId` is non-empty and does not match the XML `SystemLiteral` + * production (W3C DOM Parsing §3.2.1.3; XML 1.0 production [11]) + * - a DocumentType's `internalSubset` contains `"]>"` + * - an EntityReference's `nodeName` is not a valid XML `Name` (XML 1.0 production [5]) + * - the Document has no `documentElement` + * @see https://developer.mozilla.org/docs/Web/API/XMLSerializer/serializeToString + * @see https://html.spec.whatwg.org/#dom-xmlserializer-serializetostring + * @see https://github.com/w3c/DOM-Parsing/issues/84 + * @prettierignore + */ + serializeToString( + node: Node, + options?: XMLSerializerOptions | ((node: Node) => Node | null | undefined) + ): string; + } + // END ./lib/dom.js + + // START ./lib/dom-parser.js + /** + * The DOMParser interface provides the ability to parse XML or HTML source code from a string + * into a DOM `Document`. + * + * _xmldom is different from the spec in that it allows an `options` parameter, + * to control the behavior._. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMParser + * @see https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#dom-parsing-and-serialization + */ + class DOMParser { + /** + * The DOMParser interface provides the ability to parse XML or HTML source code from a + * string into a DOM `Document`. + * + * _xmldom is different from the spec in that it allows an `options` parameter, + * to control the behavior._. + * + * @class + * @param {DOMParserOptions} [options] + * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMParser + * @see https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#dom-parsing-and-serialization + */ + constructor(options?: DOMParserOptions); + + /** + * Parses `source` using the options in the way configured by the `DOMParserOptions` of + * `this` + * `DOMParser`. If `mimeType` is `text/html` an HTML `Document` is created, otherwise an XML + * `Document` is created. + * + * __It behaves different from the description in the living standard__: + * - Uses the `options` passed to the `DOMParser` constructor to modify the behavior. + * - Any unexpected input is reported to `onError` with either a `warning`, `error` or + * `fatalError` level. + * - Any `fatalError` throws a `ParseError` which prevents further processing. + * - Any error thrown by `onError` is converted to a `ParseError` which prevents further + * processing - If no `Document` was created during parsing it is reported as a `fatalError`. + * - A `DOMException` raised while building the DOM (e.g. an unbound namespace prefix) is + * reported as a `fatalError` and rethrown as a `ParseError` with the `DOMException` as its + * `cause`. + * + * @returns + * The `Document` node. + * @throws {ParseError} + * for any `fatalError` or anything that is thrown by `onError` + * @throws {TypeError} + * for any invalid `mimeType` + * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMParser/parseFromString + * @see https://html.spec.whatwg.org/#dom-domparser-parsefromstring-dev + */ + parseFromString(source: string, mimeType: MIME_TYPE | string): Document; + } + + interface DOMParserOptions { + /** + * The method to use instead of `Object.assign` (defaults to `conventions.assign`), + * which is used to copy values from the options before they are used for parsing. + * + * @private + * @see {@link assign} + */ + readonly assign?: typeof Object.assign; + /** + * For internal testing: The class for creating an instance for handling events from the SAX + * parser. + * *****Warning: By configuring a faulty implementation, + * the specified behavior can completely be broken*****. + * + * @private + */ + readonly domHandler?: unknown; + + /** + * DEPRECATED: Use `onError` instead! + * + * For backwards compatibility: + * If it is a function, it will be used as a value for `onError`, + * but it receives different argument types than before 0.9.0. + * + * @deprecated + * @throws {TypeError} + * If it is an object. + */ + readonly errorHandler?: ErrorHandlerFunction; + + /** + * Configures if the nodes created during parsing + * will have a `lineNumber` and a `columnNumber` attribute + * describing their location in the XML string. + * Default is true. + */ + readonly locator?: boolean; + + /** + * used to replace line endings before parsing, defaults to exported `normalizeLineEndings`, + * which normalizes line endings according to , + * including some Unicode "newline" characters. + * + * @see {@link normalizeLineEndings} + */ + readonly normalizeLineEndings?: (source: string) => string; + /** + * A function invoked for every error that occurs during parsing. + * + * If it is not provided, all errors are reported to `console.error` + * and only `fatalError`s are thrown as a `ParseError`, + * which prevents any further processing. + * If the provided method throws, a `ParserError` is thrown, + * which prevents any further processing. + * + * Be aware that many `warning`s are considered an error that prevents further processing in + * most implementations. + * + * @param level + * The error level as reported by the SAXParser. + * @param message + * The error message. + * @param context + * The DOMHandler instance used for parsing. + * @see {@link onErrorStopParsing} + * @see {@link onWarningStopParsing} + */ + readonly onError?: ErrorHandlerFunction; + + /** + * The XML namespaces that should be assumed when parsing. + * The default namespace can be provided by the key that is the empty string. + * When the `mimeType` for HTML, XHTML or SVG are passed to `parseFromString`, + * the default namespace that will be used, + * will be overridden according to the specification. + */ + readonly xmlns?: Readonly>; + } + + interface ErrorHandlerFunction { + ( + level: 'warning' | 'error' | 'fatalError', + msg: string, + context: any + ): void; + } + + /** + * Normalizes line ending according to , + * including some Unicode "newline" characters: + * + * > XML parsed entities are often stored in computer files which, + * > for editing convenience, are organized into lines. + * > These lines are typically separated by some combination + * > of the characters CARRIAGE RETURN (#xD) and LINE FEED (#xA). + * > + * > To simplify the tasks of applications, the XML processor must behave + * > as if it normalized all line breaks in external parsed entities (including the document entity) + * > on input, before parsing, by translating the following to a single #xA character: + * > + * > 1. the two-character sequence #xD #xA, + * > 2. the two-character sequence #xD #x85, + * > 3. the single character #x85, + * > 4. the single character #x2028, + * > 5. the single character #x2029, + * > 6. any #xD character that is not immediately followed by #xA or #x85. + * + * @prettierignore + */ + function normalizeLineEndings(input: string): string; + /** + * A method that prevents any further parsing when an `error` + * with level `error` is reported during parsing. + * + * @see {@link DOMParserOptions.onError} + * @see {@link onWarningStopParsing} + */ + function onErrorStopParsing(): void | never; + + /** + * A method that prevents any further parsing when an `error` + * with any level is reported during parsing. + * + * @see {@link DOMParserOptions.onError} + * @see {@link onErrorStopParsing} + */ + function onWarningStopParsing(): never; + + // END ./lib/dom-parser.js +} diff --git a/node_modules/@xmldom/xmldom/lib/.eslintrc.yml b/node_modules/@xmldom/xmldom/lib/.eslintrc.yml new file mode 100644 index 000000000..a7314af01 --- /dev/null +++ b/node_modules/@xmldom/xmldom/lib/.eslintrc.yml @@ -0,0 +1,3 @@ +extends: + - 'plugin:es5/no-es2015' + - 'plugin:n/recommended' diff --git a/node_modules/@xmldom/xmldom/lib/conventions.js b/node_modules/@xmldom/xmldom/lib/conventions.js new file mode 100644 index 000000000..c0642011d --- /dev/null +++ b/node_modules/@xmldom/xmldom/lib/conventions.js @@ -0,0 +1,429 @@ +'use strict'; + +/** + * Ponyfill for `Array.prototype.find` which is only available in ES6 runtimes. + * + * Works with anything that has a `length` property and index access properties, + * including NodeList. + * + * @param {T[] | { length: number; [number]: T }} list + * @param {function (item: T, index: number, list:T[]):boolean} predicate + * @param {Partial>?} ac + * Allows injecting a custom implementation in tests (`Array.prototype` by default). + * @returns {T | undefined} + * @template {unknown} T + * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find + * @see https://tc39.es/ecma262/multipage/indexed-collections.html#sec-array.prototype.find + */ +function find(list, predicate, ac) { + if (ac === undefined) { + ac = Array.prototype; + } + if (list && typeof ac.find === 'function') { + return ac.find.call(list, predicate); + } + for (var i = 0; i < list.length; i++) { + if (hasOwn(list, i)) { + var item = list[i]; + if (predicate.call(undefined, item, i, list)) { + return item; + } + } + } +} + +/** + * "Shallow freezes" an object to render it immutable. + * Uses `Object.freeze` if available, + * otherwise the immutability is only in the type. + * + * Is used to create "enum like" objects. + * + * If `Object.getOwnPropertyDescriptors` is available, + * a new object with all properties of object but without any prototype is created and returned + * after freezing it. + * + * @param {T} object + * The object to freeze. + * @param {Pick} [oc=Object] + * `Object` by default, + * allows to inject custom object constructor for tests. + * @returns {Readonly} + * @template {Object} T + * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze + * @prettierignore + */ +function freeze(object, oc) { + if (oc === undefined) { + oc = Object; + } + if (oc && typeof oc.getOwnPropertyDescriptors === 'function') { + object = oc.create(null, oc.getOwnPropertyDescriptors(object)); + } + return oc && typeof oc.freeze === 'function' ? oc.freeze(object) : object; +} + +/** + * Implementation for `Object.hasOwn` but ES5 compatible. + * + * @param {any} object + * @param {string | number} key + * @returns {boolean} + */ +function hasOwn(object, key) { + return Object.prototype.hasOwnProperty.call(object, key); +} + +/** + * Since xmldom can not rely on `Object.assign`, + * it uses/provides a simplified version that is sufficient for its needs. + * + * @param {Object} target + * @param {Object | null | undefined} source + * @returns {Object} + * The target with the merged/overridden properties. + * @throws {TypeError} + * If target is not an object. + * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign + * @see https://tc39.es/ecma262/multipage/fundamental-objects.html#sec-object.assign + */ +function assign(target, source) { + if (target === null || typeof target !== 'object') { + throw new TypeError('target is not an object'); + } + for (var key in source) { + if (hasOwn(source, key)) { + target[key] = source[key]; + } + } + return target; +} + +/** + * A number of attributes are boolean attributes. + * The presence of a boolean attribute on an element represents the `true` value, + * and the absence of the attribute represents the `false` value. + * + * If the attribute is present, its value must either be the empty string, or a value that is + * an ASCII case-insensitive match for the attribute's canonical name, + * with no leading or trailing whitespace. + * + * Note: The values `"true"` and `"false"` are not allowed on boolean attributes. + * To represent a `false` value, the attribute has to be omitted altogether. + * + * @see https://html.spec.whatwg.org/#boolean-attributes + * @see https://html.spec.whatwg.org/#attributes-3 + */ +var HTML_BOOLEAN_ATTRIBUTES = freeze({ + allowfullscreen: true, + async: true, + autofocus: true, + autoplay: true, + checked: true, + controls: true, + default: true, + defer: true, + disabled: true, + formnovalidate: true, + hidden: true, + ismap: true, + itemscope: true, + loop: true, + multiple: true, + muted: true, + nomodule: true, + novalidate: true, + open: true, + playsinline: true, + readonly: true, + required: true, + reversed: true, + selected: true, +}); + +/** + * Check if `name` is matching one of the HTML boolean attribute names. + * This method doesn't check if such attributes are allowed in the context of the current + * document/parsing. + * + * @param {string} name + * @returns {boolean} + * @see {@link HTML_BOOLEAN_ATTRIBUTES} + * @see https://html.spec.whatwg.org/#boolean-attributes + * @see https://html.spec.whatwg.org/#attributes-3 + */ +function isHTMLBooleanAttribute(name) { + return hasOwn(HTML_BOOLEAN_ATTRIBUTES, name.toLowerCase()); +} + +/** + * Void elements only have a start tag; end tags must not be specified for void elements. + * These elements should be written as self-closing like this: ``. + * This should not be confused with optional tags that HTML allows to omit the end tag for + * (like `li`, `tr` and others), which can have content after them, + * so they can not be written as self-closing. + * xmldom does not have any logic for optional end tags cases, + * and will report them as a warning. + * Content that would go into the unopened element, + * will instead be added as a sibling text node. + * + * @type {Readonly<{ + * area: boolean; + * col: boolean; + * img: boolean; + * wbr: boolean; + * link: boolean; + * hr: boolean; + * source: boolean; + * br: boolean; + * input: boolean; + * param: boolean; + * meta: boolean; + * embed: boolean; + * track: boolean; + * base: boolean; + * }>} + * @see https://html.spec.whatwg.org/#void-elements + * @see https://html.spec.whatwg.org/#optional-tags + */ +var HTML_VOID_ELEMENTS = freeze({ + area: true, + base: true, + br: true, + col: true, + embed: true, + hr: true, + img: true, + input: true, + link: true, + meta: true, + param: true, + source: true, + track: true, + wbr: true, +}); + +/** + * Check if `tagName` is matching one of the HTML void element names. + * This method doesn't check if such tags are allowed in the context of the current + * document/parsing. + * + * @param {string} tagName + * @returns {boolean} + * @see {@link HTML_VOID_ELEMENTS} + * @see https://html.spec.whatwg.org/#void-elements + */ +function isHTMLVoidElement(tagName) { + return hasOwn(HTML_VOID_ELEMENTS, tagName.toLowerCase()); +} + +/** + * Tag names that are raw text elements according to HTML spec. + * The value denotes whether they are escapable or not. + * + * @see {@link isHTMLEscapableRawTextElement} + * @see {@link isHTMLRawTextElement} + * @see https://html.spec.whatwg.org/#raw-text-elements + * @see https://html.spec.whatwg.org/#escapable-raw-text-elements + */ +var HTML_RAW_TEXT_ELEMENTS = freeze({ + script: false, + style: false, + textarea: true, + title: true, +}); + +/** + * Check if `tagName` is matching one of the HTML raw text element names. + * This method doesn't check if such tags are allowed in the context of the current + * document/parsing. + * + * @param {string} tagName + * @returns {boolean} + * @see {@link isHTMLEscapableRawTextElement} + * @see {@link HTML_RAW_TEXT_ELEMENTS} + * @see https://html.spec.whatwg.org/#raw-text-elements + * @see https://html.spec.whatwg.org/#escapable-raw-text-elements + */ +function isHTMLRawTextElement(tagName) { + var key = tagName.toLowerCase(); + return hasOwn(HTML_RAW_TEXT_ELEMENTS, key) && !HTML_RAW_TEXT_ELEMENTS[key]; +} +/** + * Check if `tagName` is matching one of the HTML escapable raw text element names. + * This method doesn't check if such tags are allowed in the context of the current + * document/parsing. + * + * @param {string} tagName + * @returns {boolean} + * @see {@link isHTMLRawTextElement} + * @see {@link HTML_RAW_TEXT_ELEMENTS} + * @see https://html.spec.whatwg.org/#raw-text-elements + * @see https://html.spec.whatwg.org/#escapable-raw-text-elements + */ +function isHTMLEscapableRawTextElement(tagName) { + var key = tagName.toLowerCase(); + return hasOwn(HTML_RAW_TEXT_ELEMENTS, key) && HTML_RAW_TEXT_ELEMENTS[key]; +} +/** + * Only returns true if `value` matches MIME_TYPE.HTML, which indicates an HTML document. + * + * @param {string} mimeType + * @returns {mimeType is 'text/html'} + * @see https://www.iana.org/assignments/media-types/text/html + * @see https://en.wikipedia.org/wiki/HTML + * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMParser/parseFromString + * @see https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#dom-domparser-parsefromstring + */ +function isHTMLMimeType(mimeType) { + return mimeType === MIME_TYPE.HTML; +} +/** + * For both the `text/html` and the `application/xhtml+xml` namespace the spec defines that the + * HTML namespace is provided as the default. + * + * @param {string} mimeType + * @returns {boolean} + * @see https://dom.spec.whatwg.org/#dom-document-createelement + * @see https://dom.spec.whatwg.org/#dom-domimplementation-createdocument + * @see https://dom.spec.whatwg.org/#dom-domimplementation-createhtmldocument + */ +function hasDefaultHTMLNamespace(mimeType) { + return isHTMLMimeType(mimeType) || mimeType === MIME_TYPE.XML_XHTML_APPLICATION; +} + +/** + * All mime types that are allowed as input to `DOMParser.parseFromString` + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMParser/parseFromString#Argument02 + * MDN + * @see https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#domparsersupportedtype + * WHATWG HTML Spec + * @see {@link DOMParser.prototype.parseFromString} + */ +var MIME_TYPE = freeze({ + /** + * `text/html`, the only mime type that triggers treating an XML document as HTML. + * + * @see https://www.iana.org/assignments/media-types/text/html IANA MimeType registration + * @see https://en.wikipedia.org/wiki/HTML Wikipedia + * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMParser/parseFromString MDN + * @see https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#dom-domparser-parsefromstring + * WHATWG HTML Spec + */ + HTML: 'text/html', + + /** + * `application/xml`, the standard mime type for XML documents. + * + * @see https://www.iana.org/assignments/media-types/application/xml IANA MimeType + * registration + * @see https://tools.ietf.org/html/rfc7303#section-9.1 RFC 7303 + * @see https://en.wikipedia.org/wiki/XML_and_MIME Wikipedia + */ + XML_APPLICATION: 'application/xml', + + /** + * `text/xml`, an alias for `application/xml`. + * + * @see https://tools.ietf.org/html/rfc7303#section-9.2 RFC 7303 + * @see https://www.iana.org/assignments/media-types/text/xml IANA MimeType registration + * @see https://en.wikipedia.org/wiki/XML_and_MIME Wikipedia + */ + XML_TEXT: 'text/xml', + + /** + * `application/xhtml+xml`, indicates an XML document that has the default HTML namespace, + * but is parsed as an XML document. + * + * @see https://www.iana.org/assignments/media-types/application/xhtml+xml IANA MimeType + * registration + * @see https://dom.spec.whatwg.org/#dom-domimplementation-createdocument WHATWG DOM Spec + * @see https://en.wikipedia.org/wiki/XHTML Wikipedia + */ + XML_XHTML_APPLICATION: 'application/xhtml+xml', + + /** + * `image/svg+xml`, + * + * @see https://www.iana.org/assignments/media-types/image/svg+xml IANA MimeType registration + * @see https://www.w3.org/TR/SVG11/ W3C SVG 1.1 + * @see https://en.wikipedia.org/wiki/Scalable_Vector_Graphics Wikipedia + */ + XML_SVG_IMAGE: 'image/svg+xml', +}); +/** + * @typedef {'application/xhtml+xml' | 'application/xml' | 'image/svg+xml' | 'text/html' | 'text/xml'} + * MimeType + */ +/** + * @type {MimeType[]} + * @private + * Basically `Object.values`, which is not available in ES5. + */ +var _MIME_TYPES = Object.keys(MIME_TYPE).map(function (key) { + return MIME_TYPE[key]; +}); + +/** + * Only returns true if `mimeType` is one of the allowed values for + * `DOMParser.parseFromString`. + * + * @param {string} mimeType + * @returns {mimeType is 'application/xhtml+xml' | 'application/xml' | 'image/svg+xml' | 'text/html' | 'text/xml'} + * + */ +function isValidMimeType(mimeType) { + return _MIME_TYPES.indexOf(mimeType) > -1; +} +/** + * Namespaces that are used in this code base. + * + * @see http://www.w3.org/TR/REC-xml-names + */ +var NAMESPACE = freeze({ + /** + * The XHTML namespace. + * + * @see http://www.w3.org/1999/xhtml + */ + HTML: 'http://www.w3.org/1999/xhtml', + + /** + * The SVG namespace. + * + * @see http://www.w3.org/2000/svg + */ + SVG: 'http://www.w3.org/2000/svg', + + /** + * The `xml:` namespace. + * + * @see http://www.w3.org/XML/1998/namespace + */ + XML: 'http://www.w3.org/XML/1998/namespace', + + /** + * The `xmlns:` namespace. + * + * @see https://www.w3.org/2000/xmlns/ + */ + XMLNS: 'http://www.w3.org/2000/xmlns/', +}); + +exports.assign = assign; +exports.find = find; +exports.freeze = freeze; +exports.HTML_BOOLEAN_ATTRIBUTES = HTML_BOOLEAN_ATTRIBUTES; +exports.HTML_RAW_TEXT_ELEMENTS = HTML_RAW_TEXT_ELEMENTS; +exports.HTML_VOID_ELEMENTS = HTML_VOID_ELEMENTS; +exports.hasDefaultHTMLNamespace = hasDefaultHTMLNamespace; +exports.hasOwn = hasOwn; +exports.isHTMLBooleanAttribute = isHTMLBooleanAttribute; +exports.isHTMLRawTextElement = isHTMLRawTextElement; +exports.isHTMLEscapableRawTextElement = isHTMLEscapableRawTextElement; +exports.isHTMLMimeType = isHTMLMimeType; +exports.isHTMLVoidElement = isHTMLVoidElement; +exports.isValidMimeType = isValidMimeType; +exports.MIME_TYPE = MIME_TYPE; +exports.NAMESPACE = NAMESPACE; diff --git a/node_modules/@xmldom/xmldom/lib/dom-parser.js b/node_modules/@xmldom/xmldom/lib/dom-parser.js new file mode 100644 index 000000000..d74ba8dcb --- /dev/null +++ b/node_modules/@xmldom/xmldom/lib/dom-parser.js @@ -0,0 +1,591 @@ +'use strict'; + +var conventions = require('./conventions'); +var dom = require('./dom'); +var errors = require('./errors'); +var entities = require('./entities'); +var sax = require('./sax'); + +var DOMImplementation = dom.DOMImplementation; + +var hasDefaultHTMLNamespace = conventions.hasDefaultHTMLNamespace; +var isHTMLMimeType = conventions.isHTMLMimeType; +var isValidMimeType = conventions.isValidMimeType; +var MIME_TYPE = conventions.MIME_TYPE; +var NAMESPACE = conventions.NAMESPACE; +var ParseError = errors.ParseError; + +var XMLReader = sax.XMLReader; + +/** + * Normalizes line ending according to , + * including some Unicode "newline" characters: + * + * > XML parsed entities are often stored in computer files which, + * > for editing convenience, are organized into lines. + * > These lines are typically separated by some combination + * > of the characters CARRIAGE RETURN (#xD) and LINE FEED (#xA). + * > + * > To simplify the tasks of applications, the XML processor must behave + * > as if it normalized all line breaks in external parsed entities (including the document entity) + * > on input, before parsing, by translating the following to a single #xA character: + * > + * > 1. the two-character sequence #xD #xA, + * > 2. the two-character sequence #xD #x85, + * > 3. the single character #x85, + * > 4. the single character #x2028, + * > 5. the single character #x2029, + * > 6. any #xD character that is not immediately followed by #xA or #x85. + * + * @param {string} input + * @returns {string} + * @prettierignore + */ +function normalizeLineEndings(input) { + return input.replace(/\r[\n\u0085]/g, '\n').replace(/[\r\u0085\u2028\u2029]/g, '\n'); +} + +/** + * @typedef Locator + * @property {number} [columnNumber] + * @property {number} [lineNumber] + */ + +/** + * @typedef DOMParserOptions + * @property {typeof assign} [assign] + * The method to use instead of `conventions.assign`, which is used to copy values from + * `options` before they are used for parsing. + * @property {typeof DOMHandler} [domHandler] + * For internal testing: The class for creating an instance for handling events from the SAX + * parser. + * *****Warning: By configuring a faulty implementation, the specified behavior can completely + * be broken.*****. + * @property {Function} [errorHandler] + * DEPRECATED! use `onError` instead. + * @property {function(level:ErrorLevel, message:string, context: DOMHandler):void} + * [onError] + * A function invoked for every error that occurs during parsing. + * + * If it is not provided, all errors are reported to `console.error` + * and only `fatalError`s are thrown as a `ParseError`, + * which prevents any further processing. + * If the provided method throws, a `ParserError` is thrown, + * which prevents any further processing. + * + * Be aware that many `warning`s are considered an error that prevents further processing in + * most implementations. + * @property {boolean} [locator=true] + * Configures if the nodes created during parsing will have a `lineNumber` and a `columnNumber` + * attribute describing their location in the XML string. + * Default is true. + * @property {(string) => string} [normalizeLineEndings] + * used to replace line endings before parsing, defaults to exported `normalizeLineEndings`, + * which normalizes line endings according to , + * including some Unicode "newline" characters. + * @property {Object} [xmlns] + * The XML namespaces that should be assumed when parsing. + * The default namespace can be provided by the key that is the empty string. + * When the `mimeType` for HTML, XHTML or SVG are passed to `parseFromString`, + * the default namespace that will be used, + * will be overridden according to the specification. + * @see {@link normalizeLineEndings} + */ + +/** + * The DOMParser interface provides the ability to parse XML or HTML source code from a string + * into a DOM `Document`. + * + * ***xmldom is different from the spec in that it allows an `options` parameter, + * to control the behavior***. + * + * @class + * @param {DOMParserOptions} [options] + * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMParser + * @see https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#dom-parsing-and-serialization + */ +function DOMParser(options) { + options = options || {}; + if (options.locator === undefined) { + options.locator = true; + } + + /** + * The method to use instead of `conventions.assign`, which is used to copy values from + * `options` + * before they are used for parsing. + * + * @type {conventions.assign} + * @private + * @see {@link conventions.assign} + * @readonly + */ + this.assign = options.assign || conventions.assign; + + /** + * For internal testing: The class for creating an instance for handling events from the SAX + * parser. + * *****Warning: By configuring a faulty implementation, the specified behavior can completely + * be broken*****. + * + * @type {typeof DOMHandler} + * @private + * @readonly + */ + this.domHandler = options.domHandler || DOMHandler; + + /** + * A function that is invoked for every error that occurs during parsing. + * + * If it is not provided, all errors are reported to `console.error` + * and only `fatalError`s are thrown as a `ParseError`, + * which prevents any further processing. + * If the provided method throws, a `ParserError` is thrown, + * which prevents any further processing. + * + * Be aware that many `warning`s are considered an error that prevents further processing in + * most implementations. + * + * @type {function(level:ErrorLevel, message:string, context: DOMHandler):void} + * @see {@link onErrorStopParsing} + * @see {@link onWarningStopParsing} + */ + this.onError = options.onError || options.errorHandler; + if (options.errorHandler && typeof options.errorHandler !== 'function') { + throw new TypeError('errorHandler object is no longer supported, switch to onError!'); + } else if (options.errorHandler) { + options.errorHandler('warning', 'The `errorHandler` option has been deprecated, use `onError` instead!', this); + } + + /** + * used to replace line endings before parsing, defaults to `normalizeLineEndings` + * + * @type {(string) => string} + * @readonly + */ + this.normalizeLineEndings = options.normalizeLineEndings || normalizeLineEndings; + + /** + * Configures if the nodes created during parsing will have a `lineNumber` and a + * `columnNumber` + * attribute describing their location in the XML string. + * Default is true. + * + * @type {boolean} + * @readonly + */ + this.locator = !!options.locator; + + /** + * The default namespace can be provided by the key that is the empty string. + * When the `mimeType` for HTML, XHTML or SVG are passed to `parseFromString`, + * the default namespace that will be used, + * will be overridden according to the specification. + * + * @type {Readonly} + * @readonly + */ + this.xmlns = this.assign(Object.create(null), options.xmlns); +} + +/** + * Parses `source` using the options in the way configured by the `DOMParserOptions` of `this` + * `DOMParser`. If `mimeType` is `text/html` an HTML `Document` is created, + * otherwise an XML `Document` is created. + * + * __It behaves different from the description in the living standard__: + * - Uses the `options` passed to the `DOMParser` constructor to modify the behavior. + * - Any unexpected input is reported to `onError` with either a `warning`, + * `error` or `fatalError` level. + * - Any `fatalError` throws a `ParseError` which prevents further processing. + * - Any error thrown by `onError` is converted to a `ParseError` which prevents further + * processing - If no `Document` was created during parsing it is reported as a `fatalError`. + * - A `DOMException` raised while building the DOM (e.g. an unbound namespace prefix) is + * reported as a `fatalError` and rethrown as a `ParseError` with the `DOMException` as its + * `cause`. + * *****Warning: By configuring a faulty DOMHandler implementation, + * the specified behavior can completely be broken*****. + * + * @param {string} source + * The XML mime type only allows string input! + * @param {string} [mimeType='application/xml'] + * the mimeType or contentType of the document to be created determines the `type` of document + * created (XML or HTML) + * @returns {Document} + * The `Document` node. + * @throws {ParseError} + * for any `fatalError` or anything that is thrown by `onError` + * @throws {TypeError} + * for any invalid `mimeType` + * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMParser/parseFromString + * @see https://html.spec.whatwg.org/#dom-domparser-parsefromstring-dev + */ +DOMParser.prototype.parseFromString = function (source, mimeType) { + if (!isValidMimeType(mimeType)) { + throw new TypeError('DOMParser.parseFromString: the provided mimeType "' + mimeType + '" is not valid.'); + } + var defaultNSMap = this.assign(Object.create(null), this.xmlns); + var entityMap = entities.XML_ENTITIES; + var defaultNamespace = defaultNSMap[''] || null; + if (hasDefaultHTMLNamespace(mimeType)) { + entityMap = entities.HTML_ENTITIES; + defaultNamespace = NAMESPACE.HTML; + } else if (mimeType === MIME_TYPE.XML_SVG_IMAGE) { + defaultNamespace = NAMESPACE.SVG; + } + defaultNSMap[''] = defaultNamespace; + defaultNSMap.xml = defaultNSMap.xml || NAMESPACE.XML; + + var domBuilder = new this.domHandler({ + mimeType: mimeType, + defaultNamespace: defaultNamespace, + onError: this.onError, + }); + var locator = this.locator ? {} : undefined; + if (this.locator) { + domBuilder.setDocumentLocator(locator); + } + + var sax = new XMLReader(); + sax.errorHandler = domBuilder; + sax.domBuilder = domBuilder; + var isXml = !conventions.isHTMLMimeType(mimeType); + if (isXml && typeof source !== 'string') { + sax.errorHandler.fatalError('source is not a string'); + } + sax.parse(this.normalizeLineEndings(String(source)), defaultNSMap, entityMap); + if (!domBuilder.doc.documentElement) { + sax.errorHandler.fatalError('missing root element'); + } + return domBuilder.doc; +}; + +/** + * @typedef DOMHandlerOptions + * @property {string} [mimeType=MIME_TYPE.XML_APPLICATION] + * @property {string | null} [defaultNamespace=null] + */ +/** + * The class that is used to handle events from the SAX parser to create the related DOM + * elements. + * + * Some methods are only implemented as an empty function, + * since they are (at least currently) not relevant for xmldom. + * + * @class + * @param {DOMHandlerOptions} [options] + * @see http://www.saxproject.org/apidoc/org/xml/sax/ext/DefaultHandler2.html + */ +function DOMHandler(options) { + var opt = options || {}; + /** + * The mime type is used to determine if the DOM handler will create an XML or HTML document. + * Only if it is set to `text/html` it will create an HTML document. + * It defaults to MIME_TYPE.XML_APPLICATION. + * + * @type {string} + * @see {@link MIME_TYPE} + * @readonly + */ + this.mimeType = opt.mimeType || MIME_TYPE.XML_APPLICATION; + + /** + * The namespace to use to create an XML document. + * For the following reasons this is required: + * - The SAX API for `startDocument` doesn't offer any way to pass a namespace, + * since at that point there is no way for the parser to know what the default namespace from + * the document will be. + * - When creating using `DOMImplementation.createDocument` it is required to pass a + * namespace, + * to determine the correct `Document.contentType`, which should match `this.mimeType`. + * - When parsing an XML document with the `application/xhtml+xml` mimeType, + * the HTML namespace needs to be the default namespace. + * + * @type {string | null} + * @private + * @readonly + */ + this.defaultNamespace = opt.defaultNamespace || null; + + /** + * @type {boolean} + * @private + */ + this.cdata = false; + + /** + * The last `Element` that was created by `startElement`. + * `endElement` sets it to the `currentElement.parentNode`. + * + * Note: The sax parser currently sets it to white space text nodes between tags. + * + * @type {Element | Node | undefined} + * @private + */ + this.currentElement = undefined; + + /** + * The Document that is created as part of `startDocument`, + * and returned by `DOMParser.parseFromString`. + * + * @type {Document | undefined} + * @readonly + */ + this.doc = undefined; + + /** + * The locator is stored as part of setDocumentLocator. + * It is controlled and mutated by the SAX parser to store the current parsing position. + * It is used by DOMHandler to set `columnNumber` and `lineNumber` + * on the DOM nodes. + * + * @type {Readonly | undefined} + * @private + * @readonly (the + * sax parser currently sometimes set's it) + */ + this.locator = undefined; + /** + * @type {function (level:ErrorLevel ,message:string, context:DOMHandler):void} + * @readonly + */ + this.onError = opt.onError; +} + +function position(locator, node) { + node.lineNumber = locator.lineNumber; + node.columnNumber = locator.columnNumber; +} + +DOMHandler.prototype = { + /** + * Either creates an XML or an HTML document and stores it under `this.doc`. + * If it is an XML document, `this.defaultNamespace` is used to create it, + * and it will not contain any `childNodes`. + * If it is an HTML document, it will be created without any `childNodes`. + * + * @see http://www.saxproject.org/apidoc/org/xml/sax/ContentHandler.html + */ + startDocument: function () { + var impl = new DOMImplementation(); + this.doc = isHTMLMimeType(this.mimeType) ? impl.createHTMLDocument(false) : impl.createDocument(this.defaultNamespace, ''); + }, + startElement: function (namespaceURI, localName, qName, attrs) { + var doc = this.doc; + var el = doc.createElementNS(namespaceURI, qName || localName); + var len = attrs.length; + appendElement(this, el); + this.currentElement = el; + + this.locator && position(this.locator, el); + for (var i = 0; i < len; i++) { + var namespaceURI = attrs.getURI(i); + var value = attrs.getValue(i); + var qName = attrs.getQName(i); + var attr = doc.createAttributeNS(namespaceURI, qName); + this.locator && position(attrs.getLocator(i), attr); + attr.value = attr.nodeValue = value; + el.setAttributeNode(attr); + } + }, + endElement: function (namespaceURI, localName, qName) { + this.currentElement = this.currentElement.parentNode; + }, + startPrefixMapping: function (prefix, uri) {}, + endPrefixMapping: function (prefix) {}, + processingInstruction: function (target, data) { + var ins = this.doc.createProcessingInstruction(target, data); + this.locator && position(this.locator, ins); + appendElement(this, ins); + }, + ignorableWhitespace: function (ch, start, length) {}, + characters: function (chars, start, length) { + chars = _toString.apply(this, arguments); + //console.log(chars) + if (chars) { + if (this.cdata) { + var charNode = this.doc.createCDATASection(chars); + } else { + var charNode = this.doc.createTextNode(chars); + } + if (this.currentElement) { + this.currentElement.appendChild(charNode); + } else if (/^\s*$/.test(chars)) { + this.doc.appendChild(charNode); + //process xml + } + this.locator && position(this.locator, charNode); + } + }, + skippedEntity: function (name) {}, + endDocument: function () { + this.doc.normalize(); + }, + /** + * Stores the locator to be able to set the `columnNumber` and `lineNumber` + * on the created DOM nodes. + * + * @param {Locator} locator + */ + setDocumentLocator: function (locator) { + if (locator) { + locator.lineNumber = 0; + } + this.locator = locator; + }, + //LexicalHandler + comment: function (chars, start, length) { + chars = _toString.apply(this, arguments); + var comm = this.doc.createComment(chars); + this.locator && position(this.locator, comm); + appendElement(this, comm); + }, + + startCDATA: function () { + //used in characters() methods + this.cdata = true; + }, + endCDATA: function () { + this.cdata = false; + }, + + startDTD: function (name, publicId, systemId, internalSubset) { + var impl = this.doc.implementation; + if (impl && impl.createDocumentType) { + var dt = impl.createDocumentType(name, publicId, systemId, internalSubset); + this.locator && position(this.locator, dt); + appendElement(this, dt); + this.doc.doctype = dt; + } + }, + reportError: function (level, message) { + if (typeof this.onError === 'function') { + try { + this.onError(level, message, this); + } catch (e) { + throw new ParseError('Reporting ' + level + ' "' + message + '" caused ' + e, this.locator); + } + } else { + console.error('[xmldom ' + level + ']\t' + message, _locator(this.locator)); + } + }, + /** + * @see http://www.saxproject.org/apidoc/org/xml/sax/ErrorHandler.html + */ + warning: function (message) { + this.reportError('warning', message); + }, + error: function (message) { + this.reportError('error', message); + }, + /** + * This function reports a fatal error and throws a ParseError. + * + * @param {string} message + * - The message to be used for reporting and throwing the error. + * @param {Error} [cause] + * The error that caused this fatal error, preserved as the thrown `ParseError`'s `cause`. + * @returns {never} + * This function always throws an error and never returns a value. + * @throws {ParseError} + * Always throws a ParseError with the provided message. + */ + fatalError: function (message, cause) { + this.reportError('fatalError', message); + throw new ParseError(message, this.locator, cause); + }, +}; + +function _locator(l) { + if (l) { + return '\n@#[line:' + l.lineNumber + ',col:' + l.columnNumber + ']'; + } +} + +function _toString(chars, start, length) { + if (typeof chars == 'string') { + return chars.substr(start, length); + } else { + //java sax connect width xmldom on rhino(what about: "? && !(chars instanceof String)") + if (chars.length >= start + length || start) { + return new java.lang.String(chars, start, length) + ''; + } + return chars; + } +} + +/* + * @link http://www.saxproject.org/apidoc/org/xml/sax/ext/LexicalHandler.html + * used method of org.xml.sax.ext.LexicalHandler: + * #comment(chars, start, length) + * #startCDATA() + * #endCDATA() + * #startDTD(name, publicId, systemId) + * + * + * IGNORED method of org.xml.sax.ext.LexicalHandler: + * #endDTD() + * #startEntity(name) + * #endEntity(name) + * + * + * @link http://www.saxproject.org/apidoc/org/xml/sax/ext/DeclHandler.html + * IGNORED method of org.xml.sax.ext.DeclHandler + * #attributeDecl(eName, aName, type, mode, value) + * #elementDecl(name, model) + * #externalEntityDecl(name, publicId, systemId) + * #internalEntityDecl(name, value) + * @link http://www.saxproject.org/apidoc/org/xml/sax/ext/EntityResolver2.html + * IGNORED method of org.xml.sax.EntityResolver2 + * #resolveEntity(String name,String publicId,String baseURI,String systemId) + * #resolveEntity(publicId, systemId) + * #getExternalSubset(name, baseURI) + * @link http://www.saxproject.org/apidoc/org/xml/sax/DTDHandler.html + * IGNORED method of org.xml.sax.DTDHandler + * #notationDecl(name, publicId, systemId) {}; + * #unparsedEntityDecl(name, publicId, systemId, notationName) {}; + */ +'endDTD,startEntity,endEntity,attributeDecl,elementDecl,externalEntityDecl,internalEntityDecl,resolveEntity,getExternalSubset,notationDecl,unparsedEntityDecl'.replace( + /\w+/g, + function (key) { + DOMHandler.prototype[key] = function () { + return null; + }; + } +); + +/* Private static helpers treated below as private instance methods, so don't need to add these to the public API; we might use a Relator to also get rid of non-standard public properties */ +function appendElement(handler, node) { + if (!handler.currentElement) { + handler.doc.appendChild(node); + } else { + handler.currentElement.appendChild(node); + } +} + +/** + * A method that prevents any further parsing when an `error` + * with level `error` is reported during parsing. + * + * @see {@link DOMParserOptions.onError} + * @see {@link onWarningStopParsing} + */ +function onErrorStopParsing(level) { + if (level === 'error') throw 'onErrorStopParsing'; +} + +/** + * A method that prevents any further parsing when any `error` is reported during parsing. + * + * @see {@link DOMParserOptions.onError} + * @see {@link onErrorStopParsing} + */ +function onWarningStopParsing() { + throw 'onWarningStopParsing'; +} + +exports.__DOMHandler = DOMHandler; +exports.DOMParser = DOMParser; +exports.normalizeLineEndings = normalizeLineEndings; +exports.onErrorStopParsing = onErrorStopParsing; +exports.onWarningStopParsing = onWarningStopParsing; diff --git a/node_modules/@xmldom/xmldom/lib/dom.js b/node_modules/@xmldom/xmldom/lib/dom.js new file mode 100644 index 000000000..5731bed13 --- /dev/null +++ b/node_modules/@xmldom/xmldom/lib/dom.js @@ -0,0 +1,3759 @@ +'use strict'; + +var conventions = require('./conventions'); +var find = conventions.find; +var hasDefaultHTMLNamespace = conventions.hasDefaultHTMLNamespace; +var hasOwn = conventions.hasOwn; +var isHTMLMimeType = conventions.isHTMLMimeType; +var isHTMLRawTextElement = conventions.isHTMLRawTextElement; +var isHTMLVoidElement = conventions.isHTMLVoidElement; +var MIME_TYPE = conventions.MIME_TYPE; +var NAMESPACE = conventions.NAMESPACE; + +/** + * Private DOM Constructor symbol + * + * Internal symbol used for construction of all classes whose constructors should be private. + * Currently used for checks in `Node`, `Document`, `Element`, `Attr`, `CharacterData`, `Text`, `Comment`, + * `CDATASection`, `DocumentType`, `Notation`, `Entity`, `EntityReference`, `DocumentFragment`, `ProcessingInstruction` + * so the constructor can't be used from outside the module. + */ +var PDC = Symbol(); + +var errors = require('./errors'); +var DOMException = errors.DOMException; +var DOMExceptionName = errors.DOMExceptionName; + +var g = require('./grammar'); + +/** + * Checks if the given symbol equals the Private DOM Constructor symbol (PDC) + * and throws an Illegal constructor exception when the symbols don't match. + * This ensures that the constructor remains private and can't be used outside this module. + */ +function checkSymbol(symbol) { + if (symbol !== PDC) { + throw new TypeError('Illegal constructor'); + } +} + +/** + * A prerequisite for `[].filter`, to drop elements that are empty. + * + * @param {string} input + * The string to be checked. + * @returns {boolean} + * Returns `true` if the input string is not empty, `false` otherwise. + */ +function notEmptyString(input) { + return input !== ''; +} +/** + * Splits a string on ASCII whitespace characters (U+0009 TAB, U+000A LF, U+000C FF, U+000D CR, + * U+0020 SPACE). + * It follows the definition from the infra specification from WHATWG. + * + * @param {string} input + * The string to be split. + * @returns {string[]} + * An array of the split strings. The array can be empty if the input string is empty or only + * contains whitespace characters. + * @see {@link https://infra.spec.whatwg.org/#split-on-ascii-whitespace} + * @see {@link https://infra.spec.whatwg.org/#ascii-whitespace} + */ +function splitOnASCIIWhitespace(input) { + // U+0009 TAB, U+000A LF, U+000C FF, U+000D CR, U+0020 SPACE + return input ? input.split(/[\t\n\f\r ]+/).filter(notEmptyString) : []; +} + +/** + * Adds element as a key to current if it is not already present. + * + * @param {Record} current + * The current record object to which the element will be added as a key. + * The object's keys are string types and values are either boolean or undefined. + * @param {string} element + * The string to be added as a key to the current record. + * @returns {Record} + * The updated record object after the addition of the new element. + */ +function orderedSetReducer(current, element) { + if (!hasOwn(current, element)) { + current[element] = true; + } + return current; +} + +/** + * Converts a string into an ordered set by splitting the input on ASCII whitespace and + * ensuring uniqueness of elements. + * This follows the definition of an ordered set from the infra specification by WHATWG. + * + * @param {string} input + * The input string to be transformed into an ordered set. + * @returns {string[]} + * An array of unique strings obtained from the input, preserving the original order. + * The array can be empty if the input string is empty or only contains whitespace characters. + * @see {@link https://infra.spec.whatwg.org/#ordered-set} + */ +function toOrderedSet(input) { + if (!input) return []; + var list = splitOnASCIIWhitespace(input); + return Object.keys(list.reduce(orderedSetReducer, {})); +} + +/** + * Uses `list.indexOf` to implement a function that behaves like `Array.prototype.includes`. + * This function is used in environments where `Array.prototype.includes` may not be available. + * + * @param {any[]} list + * The array in which to search for the element. + * @returns {function(any): boolean} + * A function that accepts an element and returns a boolean indicating whether the element is + * included in the provided list. + */ +function arrayIncludes(list) { + return function (element) { + return list && list.indexOf(element) !== -1; + }; +} + +/** + * Validates a qualified name based on the criteria provided in the DOM specification by + * WHATWG. + * + * @param {string} qualifiedName + * The qualified name to be validated. + * @throws {DOMException} + * With code {@link DOMException.INVALID_CHARACTER_ERR} if the qualified name contains an + * invalid character. + * @see {@link https://dom.spec.whatwg.org/#validate} + */ +function validateQualifiedName(qualifiedName) { + if (!g.QName_exact.test(qualifiedName)) { + throw new DOMException(DOMException.INVALID_CHARACTER_ERR, 'invalid character in qualified name "' + qualifiedName + '"'); + } +} + +/** + * Validates a qualified name and the namespace associated with it, + * based on the criteria provided in the DOM specification by WHATWG. + * + * @param {string | null} namespace + * The namespace to be validated. It can be a string or null. + * @param {string} qualifiedName + * The qualified name to be validated. + * @returns {[namespace: string | null, prefix: string | null, localName: string]} + * Returns a tuple with the namespace, + * prefix and local name of the qualified name. + * @throws {DOMException} + * Throws a DOMException if the qualified name or the namespace is not valid. + * @see {@link https://dom.spec.whatwg.org/#validate-and-extract} + */ +function validateAndExtract(namespace, qualifiedName) { + validateQualifiedName(qualifiedName); + namespace = namespace || null; + /** + * @type {string | null} + */ + var prefix = null; + var localName = qualifiedName; + if (qualifiedName.indexOf(':') >= 0) { + var splitResult = qualifiedName.split(':'); + prefix = splitResult[0]; + localName = splitResult[1]; + } + if (prefix !== null && namespace === null) { + throw new DOMException(DOMException.NAMESPACE_ERR, 'prefix is non-null and namespace is null'); + } + if (prefix === 'xml' && namespace !== conventions.NAMESPACE.XML) { + throw new DOMException(DOMException.NAMESPACE_ERR, 'prefix is "xml" and namespace is not the XML namespace'); + } + if ((prefix === 'xmlns' || qualifiedName === 'xmlns') && namespace !== conventions.NAMESPACE.XMLNS) { + throw new DOMException( + DOMException.NAMESPACE_ERR, + 'either qualifiedName or prefix is "xmlns" and namespace is not the XMLNS namespace' + ); + } + if (namespace === conventions.NAMESPACE.XMLNS && prefix !== 'xmlns' && qualifiedName !== 'xmlns') { + throw new DOMException( + DOMException.NAMESPACE_ERR, + 'namespace is the XMLNS namespace and neither qualifiedName nor prefix is "xmlns"' + ); + } + return [namespace, prefix, localName]; +} + +/** + * Copies properties from one object to another. + * It only copies the object's own (not inherited) properties. + * + * @param {Object} src + * The source object from which properties are copied. + * @param {Object} dest + * The destination object to which properties are copied. + */ +function copy(src, dest) { + for (var p in src) { + if (hasOwn(src, p)) { + dest[p] = src[p]; + } + } +} + +/** + * Extends a class with the properties and methods of a super class. + * It uses a form of prototypal inheritance, and establishes the `constructor` property + * correctly(?). + * + * It is not clear to the current maintainers if this implementation is making sense, + * since it creates an intermediate prototype function, + * which all properties of `Super` are copied onto using `_copy`. + * + * @param {Object} Class + * The class that is to be extended. + * @param {Object} Super + * The super class from which properties and methods are inherited. + * @private + */ +function _extends(Class, Super) { + var pt = Class.prototype; + if (!(pt instanceof Super)) { + function t() {} + t.prototype = Super.prototype; + t = new t(); + copy(pt, t); + Class.prototype = pt = t; + } + if (pt.constructor != Class) { + if (typeof Class != 'function') { + console.error('unknown Class:' + Class); + } + pt.constructor = Class; + } +} + +var NodeType = {}; +var ELEMENT_NODE = (NodeType.ELEMENT_NODE = 1); +var ATTRIBUTE_NODE = (NodeType.ATTRIBUTE_NODE = 2); +var TEXT_NODE = (NodeType.TEXT_NODE = 3); +var CDATA_SECTION_NODE = (NodeType.CDATA_SECTION_NODE = 4); +var ENTITY_REFERENCE_NODE = (NodeType.ENTITY_REFERENCE_NODE = 5); +var ENTITY_NODE = (NodeType.ENTITY_NODE = 6); +var PROCESSING_INSTRUCTION_NODE = (NodeType.PROCESSING_INSTRUCTION_NODE = 7); +var COMMENT_NODE = (NodeType.COMMENT_NODE = 8); +var DOCUMENT_NODE = (NodeType.DOCUMENT_NODE = 9); +var DOCUMENT_TYPE_NODE = (NodeType.DOCUMENT_TYPE_NODE = 10); +var DOCUMENT_FRAGMENT_NODE = (NodeType.DOCUMENT_FRAGMENT_NODE = 11); +var NOTATION_NODE = (NodeType.NOTATION_NODE = 12); + +var DocumentPosition = conventions.freeze({ + DOCUMENT_POSITION_DISCONNECTED: 1, + DOCUMENT_POSITION_PRECEDING: 2, + DOCUMENT_POSITION_FOLLOWING: 4, + DOCUMENT_POSITION_CONTAINS: 8, + DOCUMENT_POSITION_CONTAINED_BY: 16, + DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC: 32, +}); + +//helper functions for compareDocumentPosition +/** + * Finds the common ancestor in two parent chains. + * + * @param {Node[]} a + * The first parent chain. + * @param {Node[]} b + * The second parent chain. + * @returns {Node} + * The common ancestor node if it exists. If there is no common ancestor, the function will + * return `null`. + */ +function commonAncestor(a, b) { + if (b.length < a.length) return commonAncestor(b, a); + var c = null; + for (var n in a) { + if (a[n] !== b[n]) return c; + c = a[n]; + } + return c; +} + +/** + * Assigns a unique identifier to a document to ensure consistency while comparing unrelated + * nodes. + * + * @param {Document} doc + * The document to which a unique identifier is to be assigned. + * @returns {string} + * The unique identifier of the document. If the document already had a unique identifier, the + * function will return the existing one. + */ +function docGUID(doc) { + if (!doc.guid) doc.guid = Math.random(); + return doc.guid; +} +//-- end of helper functions + +/** + * The NodeList interface provides the abstraction of an ordered collection of nodes, + * without defining or constraining how this collection is implemented. + * NodeList objects in the DOM are live. + * The items in the NodeList are accessible via an integral index, starting from 0. + * You can also access the items of the NodeList with a `for...of` loop. + * + * @class NodeList + * @see http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-536297177 + * @constructs NodeList + */ +function NodeList() {} +NodeList.prototype = { + /** + * The number of nodes in the list. The range of valid child node indices is 0 to length-1 + * inclusive. + * + * @type {number} + */ + length: 0, + /** + * Returns the item at `index`. If index is greater than or equal to the number of nodes in + * the list, this returns null. + * + * @param index + * Unsigned long Index into the collection. + * @returns {Node | null} + * The node at position `index` in the NodeList, + * or null if that is not a valid index. + */ + item: function (index) { + return index >= 0 && index < this.length ? this[index] : null; + }, + /** + * Returns a string representation of the NodeList. + * + * Accepts the same `options` object as `XMLSerializer.prototype.serializeToString` + * (`requireWellFormed`, `splitCDATASections`, `nodeFilter`). Passing a function is treated as + * a legacy `nodeFilter` for backward compatibility. + * + * @param {Object | function} [options] + * @param {boolean} [options.requireWellFormed=false] + * @param {boolean} [options.splitCDATASections=true] + * @param {function} [options.nodeFilter] + * @returns {string} + */ + toString: function (options) { + var opts; + if (typeof options === 'function') { + opts = { requireWellFormed: false, splitCDATASections: true, nodeFilter: options }; + } else if (!!options) { + opts = { + requireWellFormed: !!options.requireWellFormed, + splitCDATASections: options.splitCDATASections !== false, + nodeFilter: options.nodeFilter || null, + }; + } else { + opts = { requireWellFormed: false, splitCDATASections: true, nodeFilter: null }; + } + for (var buf = [], i = 0; i < this.length; i++) { + serializeToString(this[i], buf, null, opts); + } + return buf.join(''); + }, + /** + * Filters the NodeList based on a predicate. + * + * @param {function(Node): boolean} predicate + * - A predicate function to filter the NodeList. + * @returns {Node[]} + * An array of nodes that satisfy the predicate. + * @private + */ + filter: function (predicate) { + return Array.prototype.filter.call(this, predicate); + }, + /** + * Returns the first index at which a given node can be found in the NodeList, or -1 if it is + * not present. + * + * @param {Node} item + * - The Node item to locate in the NodeList. + * @returns {number} + * The first index of the node in the NodeList; -1 if not found. + * @private + */ + indexOf: function (item) { + return Array.prototype.indexOf.call(this, item); + }, +}; +NodeList.prototype[Symbol.iterator] = function () { + var me = this; + var index = 0; + + return { + next: function () { + if (index < me.length) { + return { + value: me[index++], + done: false, + }; + } else { + return { + done: true, + }; + } + }, + return: function () { + return { + done: true, + }; + }, + }; +}; + +/** + * Represents a live collection of nodes that is automatically updated when its associated + * document changes. + * + * @class LiveNodeList + * @param {Node} node + * The associated node. + * @param {function} refresh + * The function to refresh the live node list. + * @augments NodeList + * @constructs LiveNodeList + */ +function LiveNodeList(node, refresh) { + this._node = node; + this._refresh = refresh; + _updateLiveList(this); +} +/** + * Updates the live node list. + * + * @param {LiveNodeList} list + * The live node list to update. + * @private + */ +function _updateLiveList(list) { + var inc = list._node._inc || list._node.ownerDocument._inc; + if (list._inc !== inc) { + var ls = list._refresh(list._node); + __set__(list, 'length', ls.length); + if (!list.$$length || ls.length < list.$$length) { + for (var i = ls.length; i in list; i++) { + if (hasOwn(list, i)) { + delete list[i]; + } + } + } + copy(ls, list); + list._inc = inc; + } +} +/** + * Returns the node at position `index` in the LiveNodeList, or null if that is not a valid + * index. + * + * @param {number} i + * Index into the collection. + * @returns {Node | null} + * The node at position `index` in the LiveNodeList, or null if that is not a valid index. + */ +LiveNodeList.prototype.item = function (i) { + _updateLiveList(this); + return this[i] || null; +}; + +_extends(LiveNodeList, NodeList); + +/** + * Objects implementing the NamedNodeMap interface are used to represent collections of nodes + * that can be accessed by name. + * Note that NamedNodeMap does not inherit from NodeList; + * NamedNodeMaps are not maintained in any particular order. + * Objects contained in an object implementing NamedNodeMap may also be accessed by an ordinal + * index, + * but this is simply to allow convenient enumeration of the contents of a NamedNodeMap, + * and does not imply that the DOM specifies an order to these Nodes. + * NamedNodeMap objects in the DOM are live. + * used for attributes or DocumentType entities + * + * This implementation only supports property indices, but does not support named properties, + * as specified in the living standard. + * + * @class NamedNodeMap + * @see https://dom.spec.whatwg.org/#interface-namednodemap + * @see https://webidl.spec.whatwg.org/#dfn-supported-property-names + * @constructs NamedNodeMap + */ +/** + * A live collection of an element's attributes, keyed by name. + * + * The numbered entries and `length` are the sole authority for attribute order. + * A separate two-level null-prototype membership index (`namespaceURI` -> + * `localName` -> `Attr`, with the null/empty namespace held in its own bucket) + * lets the parse-time de-duplication in `setNamedItem` resolve an existing + * attribute in O(1) instead of scanning the list, so building an element with M + * attributes costs O(M) rather than O(M^2). The index never reorders attributes. + */ +function NamedNodeMap() { + // namespaceURI (non-empty string) -> (localName -> Attr) + this._nsIndex = Object.create(null); + // localName -> Attr, for the null / empty-string namespace + this._noNsIndex = Object.create(null); +} +/** + * Returns the index of a node within the list. + * + * @param {Array} list + * The list of nodes. + * @param {Node} node + * The node to find. + * @returns {number} + * The index of the node within the list, or -1 if not found. + * @private + */ +function _findNodeIndex(list, node) { + var i = 0; + while (i < list.length) { + if (list[i] === node) { + return i; + } + i++; + } +} +/** + * Returns the second-level index bucket (`localName` -> `Attr`) for a namespace, + * replicating `getNamedItemNS`'s falsy-namespace normalization: `null`, + * `undefined` and `''` all resolve to the dedicated null-namespace bucket, kept separate from + * any real URI (so a namespace URI equal to the string `"null"` + * cannot collide with the null namespace). Both levels are null-prototype objects, so an + * attribute named `__proto__` or `constructor` is an ordinary key. + * + * @param {NamedNodeMap} map + * @param {string | null | undefined} namespaceURI + * @param {boolean} create + * Create the bucket if it does not exist yet. + * @returns {Object | undefined} + * @private + */ +function _nnmBucket(map, namespaceURI, create) { + if (!namespaceURI) { + return map._noNsIndex; + } + var bucket = map._nsIndex[namespaceURI]; + if (!bucket && create) { + bucket = map._nsIndex[namespaceURI] = Object.create(null); + } + return bucket; +} +/** + * Looks up an attribute by namespace and local name through the membership index. + * + * @param {NamedNodeMap} map + * @param {string | null | undefined} namespaceURI + * @param {string} localName + * @returns {Attr | null} + * The matching attribute, or `null` when absent. + * @private + */ +function _nnmIndexFind(map, namespaceURI, localName) { + var bucket = _nnmBucket(map, namespaceURI, false); + var found = bucket && bucket[localName]; + return found ? found : null; +} +/** + * Records `attr` in the membership index under its namespace and local name, + * replacing any previous attribute with the same key. + * + * @param {NamedNodeMap} map + * @param {Attr} attr + * @private + */ +function _nnmIndexAdd(map, attr) { + _nnmBucket(map, attr.namespaceURI, true)[attr.localName] = attr; +} +/** + * Removes `attr` from the membership index. + * + * @param {NamedNodeMap} map + * @param {Attr} attr + * @private + */ +function _nnmIndexRemove(map, attr) { + var bucket = _nnmBucket(map, attr.namespaceURI, false); + if (bucket) { + delete bucket[attr.localName]; + } +} +/** + * Adds a new attribute to the list and updates the owner element of the attribute. + * + * @param {Element} el + * The element which will become the owner of the new attribute. + * @param {NamedNodeMap} list + * The list to which the new attribute will be added. + * @param {Attr} newAttr + * The new attribute to be added. + * @param {Attr} oldAttr + * The old attribute to be replaced, or null if no attribute is to be replaced. + * @returns {void} + * @private + */ +function _addNamedNode(el, list, newAttr, oldAttr) { + if (oldAttr) { + list[_findNodeIndex(list, oldAttr)] = newAttr; + } else { + list[list.length] = newAttr; + list.length++; + } + // Keep the membership index in sync with the ordered list. On replacement + // `oldAttr` shares `newAttr`'s (namespace, localName) key, so this overwrites + // its entry; on append it adds a new one. + _nnmIndexAdd(list, newAttr); + if (el) { + newAttr.ownerElement = el; + var doc = el.ownerDocument; + if (doc) { + oldAttr && _onRemoveAttribute(doc, el, oldAttr); + _onAddAttribute(doc, el, newAttr); + } + } +} +/** + * Removes an attribute from the list and updates the owner element of the attribute. + * + * @param {Element} el + * The element which is the current owner of the attribute. + * @param {NamedNodeMap} list + * The list from which the attribute will be removed. + * @param {Attr} attr + * The attribute to be removed. + * @returns {void} + * @private + */ +function _removeNamedNode(el, list, attr) { + //console.log('remove attr:'+attr) + var i = _findNodeIndex(list, attr); + if (i >= 0) { + var lastIndex = list.length - 1; + while (i <= lastIndex) { + list[i] = list[++i]; + } + list.length = lastIndex; + _nnmIndexRemove(list, attr); + if (el) { + var doc = el.ownerDocument; + if (doc) { + _onRemoveAttribute(doc, el, attr); + } + attr.ownerElement = null; + } + } +} +NamedNodeMap.prototype = { + length: 0, + item: NodeList.prototype.item, + + /** + * Get an attribute by name. Note: Name is in lower case in case of HTML namespace and + * document. + * + * @param {string} localName + * The local name of the attribute. + * @returns {Attr | null} + * The attribute with the given local name, or null if no such attribute exists. + * @see https://dom.spec.whatwg.org/#concept-element-attributes-get-by-name + */ + getNamedItem: function (localName) { + if (this._ownerElement && this._ownerElement._isInHTMLDocumentAndNamespace()) { + localName = localName.toLowerCase(); + } + var i = 0; + while (i < this.length) { + var attr = this[i]; + if (attr.nodeName === localName) { + return attr; + } + i++; + } + return null; + }, + + /** + * Set an attribute. + * + * @param {Attr} attr + * The attribute to set. + * @returns {Attr | null} + * The old attribute with the same local name and namespace URI as the new one, or null if no + * such attribute exists. + * @throws {DOMException} + * With code: + * - {@link INUSE_ATTRIBUTE_ERR} - If the attribute is already an attribute of another + * element. + * @see https://dom.spec.whatwg.org/#concept-element-attributes-set + */ + setNamedItem: function (attr) { + var el = attr.ownerElement; + if (el && el !== this._ownerElement) { + throw new DOMException(DOMException.INUSE_ATTRIBUTE_ERR); + } + // Resolve any existing attribute with the same (namespace, localName) + // through the O(1) membership index rather than an O(M) scan — this is the + // parse-dedup hot path (`setAttributeNode` per attribute during parse). + var oldAttr = _nnmIndexFind(this, attr.namespaceURI, attr.localName); + if (oldAttr === attr) { + return attr; + } + _addNamedNode(this._ownerElement, this, attr, oldAttr); + return oldAttr; + }, + + /** + * Set an attribute, replacing an existing attribute with the same local name and namespace + * URI if one exists. + * + * @param {Attr} attr + * The attribute to set. + * @returns {Attr | null} + * The old attribute with the same local name and namespace URI as the new one, or null if no + * such attribute exists. + * @throws {DOMException} + * Throws a DOMException with the name "InUseAttributeError" if the attribute is already an + * attribute of another element. + * @see https://dom.spec.whatwg.org/#concept-element-attributes-set + */ + setNamedItemNS: function (attr) { + return this.setNamedItem(attr); + }, + + /** + * Removes an attribute specified by the local name. + * + * @param {string} localName + * The local name of the attribute to be removed. + * @returns {Attr} + * The attribute node that was removed. + * @throws {DOMException} + * With code: + * - {@link DOMException.NOT_FOUND_ERR} if no attribute with the given name is found. + * @see https://dom.spec.whatwg.org/#dom-namednodemap-removenameditem + * @see https://dom.spec.whatwg.org/#concept-element-attributes-remove-by-name + */ + removeNamedItem: function (localName) { + var attr = this.getNamedItem(localName); + if (!attr) { + throw new DOMException(DOMException.NOT_FOUND_ERR, localName); + } + _removeNamedNode(this._ownerElement, this, attr); + return attr; + }, + + /** + * Removes an attribute specified by the namespace and local name. + * + * @param {string | null} namespaceURI + * The namespace URI of the attribute to be removed. + * @param {string} localName + * The local name of the attribute to be removed. + * @returns {Attr} + * The attribute node that was removed. + * @throws {DOMException} + * With code: + * - {@link DOMException.NOT_FOUND_ERR} if no attribute with the given namespace URI and local + * name is found. + * @see https://dom.spec.whatwg.org/#dom-namednodemap-removenameditemns + * @see https://dom.spec.whatwg.org/#concept-element-attributes-remove-by-namespace + */ + removeNamedItemNS: function (namespaceURI, localName) { + var attr = this.getNamedItemNS(namespaceURI, localName); + if (!attr) { + throw new DOMException(DOMException.NOT_FOUND_ERR, namespaceURI ? namespaceURI + ' : ' + localName : localName); + } + _removeNamedNode(this._ownerElement, this, attr); + return attr; + }, + + /** + * Get an attribute by namespace and local name. + * + * @param {string | null} namespaceURI + * The namespace URI of the attribute. + * @param {string} localName + * The local name of the attribute. + * @returns {Attr | null} + * The attribute with the given namespace URI and local name, or null if no such attribute + * exists. + * @see https://dom.spec.whatwg.org/#concept-element-attributes-get-by-namespace + */ + getNamedItemNS: function (namespaceURI, localName) { + if (!namespaceURI) { + namespaceURI = null; + } + var i = 0; + while (i < this.length) { + var node = this[i]; + if (node.localName === localName && node.namespaceURI === namespaceURI) { + return node; + } + i++; + } + return null; + }, +}; +NamedNodeMap.prototype[Symbol.iterator] = function () { + var me = this; + var index = 0; + + return { + next: function () { + if (index < me.length) { + return { + value: me[index++], + done: false, + }; + } else { + return { + done: true, + }; + } + }, + return: function () { + return { + done: true, + }; + }, + }; +}; + +/** + * The DOMImplementation interface provides a number of methods for performing operations that + * are independent of any particular instance of the document object model. + * + * The DOMImplementation interface represents an object providing methods which are not + * dependent on any particular document. + * Such an object is returned by the `Document.implementation` property. + * + * **The individual methods describe the differences compared to the specs**. + * + * @class DOMImplementation + * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMImplementation MDN + * @see https://www.w3.org/TR/REC-DOM-Level-1/level-one-core.html#ID-102161490 DOM Level 1 Core + * (Initial) + * @see https://www.w3.org/TR/DOM-Level-2-Core/core.html#ID-102161490 DOM Level 2 Core + * @see https://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-102161490 DOM Level 3 Core + * @see https://dom.spec.whatwg.org/#domimplementation DOM Living Standard + * @constructs DOMImplementation + */ +function DOMImplementation() {} + +DOMImplementation.prototype = { + /** + * Test if the DOM implementation implements a specific feature and version, as specified in + * {@link https://www.w3.org/TR/DOM-Level-3-Core/core.html#DOMFeatures DOM Features}. + * + * The DOMImplementation.hasFeature() method returns a Boolean flag indicating if a given + * feature is supported. The different implementations fairly diverged in what kind of + * features were reported. The latest version of the spec settled to force this method to + * always return true, where the functionality was accurate and in use. + * + * @deprecated + * It is deprecated and modern browsers return true in all cases. + * @function DOMImplementation#hasFeature + * @param {string} feature + * The name of the feature to test. + * @param {string} [version] + * This is the version number of the feature to test. + * @returns {boolean} + * Always returns true. + * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMImplementation/hasFeature MDN + * @see https://www.w3.org/TR/REC-DOM-Level-1/level-one-core.html#ID-5CED94D7 DOM Level 1 Core + * @see https://dom.spec.whatwg.org/#dom-domimplementation-hasfeature DOM Living Standard + * @see https://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-5CED94D7 DOM Level 3 Core + */ + hasFeature: function (feature, version) { + return true; + }, + /** + * Creates a DOM Document object of the specified type with its document element. Note that + * based on the {@link DocumentType} + * given to create the document, the implementation may instantiate specialized + * {@link Document} objects that support additional features than the "Core", such as "HTML" + * {@link https://www.w3.org/TR/DOM-Level-3-Core/references.html#DOM2HTML DOM Level 2 HTML}. + * On the other hand, setting the {@link DocumentType} after the document was created makes + * this very unlikely to happen. Alternatively, specialized {@link Document} creation methods, + * such as createHTMLDocument + * {@link https://www.w3.org/TR/DOM-Level-3-Core/references.html#DOM2HTML DOM Level 2 HTML}, + * can be used to obtain specific types of {@link Document} objects. + * + * __It behaves slightly different from the description in the living standard__: + * - There is no interface/class `XMLDocument`, it returns a `Document` + * instance (with it's `type` set to `'xml'`). + * - `encoding`, `mode`, `origin`, `url` fields are currently not declared. + * + * @function DOMImplementation.createDocument + * @param {string | null} namespaceURI + * The + * {@link https://www.w3.org/TR/DOM-Level-3-Core/glossary.html#dt-namespaceURI namespace URI} + * of the document element to create or null. + * @param {string | null} qualifiedName + * The + * {@link https://www.w3.org/TR/DOM-Level-3-Core/glossary.html#dt-qualifiedname qualified name} + * of the document element to be created or null. + * @param {DocumentType | null} [doctype=null] + * The type of document to be created or null. When doctype is not null, its + * {@link Node#ownerDocument} attribute is set to the document being created. Default is + * `null` + * @returns {Document} + * A new {@link Document} object with its document element. If the NamespaceURI, + * qualifiedName, and doctype are null, the returned {@link Document} is empty with no + * document element. + * @throws {DOMException} + * With code: + * + * - `INVALID_CHARACTER_ERR`: Raised if the specified qualified name is not an XML name + * according to {@link https://www.w3.org/TR/DOM-Level-3-Core/references.html#XML XML 1.0}. + * - `NAMESPACE_ERR`: Raised if the qualifiedName is malformed, if the qualifiedName has a + * prefix and the namespaceURI is null, or if the qualifiedName is null and the namespaceURI + * is different from null, or if the qualifiedName has a prefix that is "xml" and the + * namespaceURI is different from "{@link http://www.w3.org/XML/1998/namespace}" + * {@link https://www.w3.org/TR/DOM-Level-3-Core/references.html#Namespaces XML Namespaces}, + * or if the DOM implementation does not support the "XML" feature but a non-null namespace + * URI was provided, since namespaces were defined by XML. + * - `WRONG_DOCUMENT_ERR`: Raised if doctype has already been used with a different document + * or was created from a different implementation. + * - `NOT_SUPPORTED_ERR`: May be raised if the implementation does not support the feature + * "XML" and the language exposed through the Document does not support XML Namespaces (such + * as {@link https://www.w3.org/TR/DOM-Level-3-Core/references.html#HTML40 HTML 4.01}). + * @since DOM Level 2. + * @see {@link #createHTMLDocument} + * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMImplementation/createDocument MDN + * @see https://dom.spec.whatwg.org/#dom-domimplementation-createdocument DOM Living Standard + * @see https://www.w3.org/TR/DOM-Level-3-Core/core.html#Level-2-Core-DOM-createDocument DOM + * Level 3 Core + * @see https://www.w3.org/TR/DOM-Level-2-Core/core.html#Level-2-Core-DOM-createDocument DOM + * Level 2 Core (initial) + */ + createDocument: function (namespaceURI, qualifiedName, doctype) { + var contentType = MIME_TYPE.XML_APPLICATION; + if (namespaceURI === NAMESPACE.HTML) { + contentType = MIME_TYPE.XML_XHTML_APPLICATION; + } else if (namespaceURI === NAMESPACE.SVG) { + contentType = MIME_TYPE.XML_SVG_IMAGE; + } + var doc = new Document(PDC, { contentType: contentType }); + doc.implementation = this; + doc.childNodes = new NodeList(); + doc.doctype = doctype || null; + if (doctype) { + doc.appendChild(doctype); + } + if (qualifiedName) { + var root = doc.createElementNS(namespaceURI, qualifiedName); + doc.appendChild(root); + } + return doc; + }, + /** + * Creates an empty DocumentType node. Entity declarations and notations are not made + * available. Entity reference expansions and default attribute additions do not occur. + * + * **This behavior is slightly different from the one in the specs**: + * - `encoding`, `mode`, `origin`, `url` fields are currently not declared. + * - `publicId` and `systemId` contain the raw data including any possible quotes, + * so they can always be serialized back to the original value + * - `internalSubset` contains the raw string between `[` and `]` if present, + * but is not parsed or validated in any form. + * + * @function DOMImplementation#createDocumentType + * @param {string} qualifiedName + * The {@link https://www.w3.org/TR/DOM-Level-3-Core/glossary.html#dt-qualifiedname qualified + * name} of the document type to be created. + * @param {string} [publicId] + * The external subset public identifier. Stored verbatim including surrounding quotes. + * When serialized with `requireWellFormed: true`, the serializer throws `InvalidStateError` + * if the value is non-empty and does not match the XML `PubidLiteral` production + * (W3C DOM Parsing §3.2.1.3; XML 1.0 production [12]). Creation-time validation is not + * enforced — deferred to a future breaking release. + * @param {string} [systemId] + * The external subset system identifier. Stored verbatim including surrounding quotes. + * When serialized with `requireWellFormed: true`, the serializer throws `InvalidStateError` + * if the value is non-empty and does not match the XML `SystemLiteral` production + * (W3C DOM Parsing §3.2.1.3; XML 1.0 production [11]). Creation-time validation is not + * enforced — deferred to a future breaking release. + * @param {string} [internalSubset] + * The internal subset or an empty string if it is not present. Stored verbatim. + * When serialized with `requireWellFormed: true`, the serializer throws `InvalidStateError` + * if the value contains `"]>"`. Creation-time validation is not enforced. + * @returns {DocumentType} + * A new {@link DocumentType} node with {@link Node#ownerDocument} set to null. + * @throws {DOMException} + * With code: + * + * - `INVALID_CHARACTER_ERR`: Raised if the specified qualified name is not an XML name + * according to {@link https://www.w3.org/TR/DOM-Level-3-Core/references.html#XML XML 1.0}. + * - `NAMESPACE_ERR`: Raised if the qualifiedName is malformed. + * - `NOT_SUPPORTED_ERR`: May be raised if the implementation does not support the feature + * "XML" and the language exposed through the Document does not support XML Namespaces (such + * as {@link https://www.w3.org/TR/DOM-Level-3-Core/references.html#HTML40 HTML 4.01}). + * @since DOM Level 2. + * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMImplementation/createDocumentType + * MDN + * @see https://dom.spec.whatwg.org/#dom-domimplementation-createdocumenttype DOM Living + * Standard + * @see https://www.w3.org/TR/DOM-Level-3-Core/core.html#Level-3-Core-DOM-createDocType DOM + * Level 3 Core + * @see https://www.w3.org/TR/DOM-Level-2-Core/core.html#Level-2-Core-DOM-createDocType DOM + * Level 2 Core + * @see https://github.com/xmldom/xmldom/blob/master/CHANGELOG.md#050 + * @see https://www.w3.org/TR/DOM-Level-2-Core/#core-ID-Core-DocType-internalSubset + * @prettierignore + */ + createDocumentType: function (qualifiedName, publicId, systemId, internalSubset) { + validateQualifiedName(qualifiedName); + var node = new DocumentType(PDC); + node.name = qualifiedName; + node.nodeName = qualifiedName; + node.publicId = publicId || ''; + node.systemId = systemId || ''; + node.internalSubset = internalSubset || ''; + node.childNodes = new NodeList(); + + return node; + }, + /** + * Returns an HTML document, that might already have a basic DOM structure. + * + * __It behaves slightly different from the description in the living standard__: + * - If the first argument is `false` no initial nodes are added (steps 3-7 in the specs are + * omitted) + * - `encoding`, `mode`, `origin`, `url` fields are currently not declared. + * + * @param {string | false} [title] + * A string containing the title to give the new HTML document. + * @returns {Document} + * The HTML document. + * @since WHATWG Living Standard. + * @see {@link #createDocument} + * @see https://dom.spec.whatwg.org/#dom-domimplementation-createhtmldocument + * @see https://dom.spec.whatwg.org/#html-document + */ + createHTMLDocument: function (title) { + var doc = new Document(PDC, { contentType: MIME_TYPE.HTML }); + doc.implementation = this; + doc.childNodes = new NodeList(); + if (title !== false) { + doc.doctype = this.createDocumentType('html'); + doc.doctype.ownerDocument = doc; + doc.appendChild(doc.doctype); + var htmlNode = doc.createElement('html'); + doc.appendChild(htmlNode); + var headNode = doc.createElement('head'); + htmlNode.appendChild(headNode); + if (typeof title === 'string') { + var titleNode = doc.createElement('title'); + titleNode.appendChild(doc.createTextNode(title)); + headNode.appendChild(titleNode); + } + htmlNode.appendChild(doc.createElement('body')); + } + return doc; + }, +}; + +/** + * The DOM Node interface is an abstract base class upon which many other DOM API objects are + * based, thus letting those object types to be used similarly and often interchangeably. As an + * abstract class, there is no such thing as a plain Node object. All objects that implement + * Node functionality are based on one of its subclasses. Most notable are Document, Element, + * and DocumentFragment. + * + * In addition, every kind of DOM node is represented by an interface based on Node. These + * include Attr, CharacterData (which Text, Comment, CDATASection and ProcessingInstruction are + * all based on), and DocumentType. + * + * In some cases, a particular feature of the base Node interface may not apply to one of its + * child interfaces; in that case, the inheriting node may return null or throw an exception, + * depending on circumstances. For example, attempting to add children to a node type that + * cannot have children will throw an exception. + * + * **This behavior is slightly different from the in the specs**: + * - unimplemented interfaces: `EventTarget` + * + * @class + * @abstract + * @param {Symbol} symbol + * @see http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-1950641247 + * @see https://dom.spec.whatwg.org/#node + * @prettierignore + */ +function Node(symbol) { + checkSymbol(symbol); +} + +Node.prototype = { + /** + * The first child of this node. + * + * @type {Node | null} + */ + firstChild: null, + /** + * The last child of this node. + * + * @type {Node | null} + */ + lastChild: null, + /** + * The previous sibling of this node. + * + * @type {Node | null} + */ + previousSibling: null, + /** + * The next sibling of this node. + * + * @type {Node | null} + */ + nextSibling: null, + /** + * The parent node of this node. + * + * @type {Node | null} + */ + parentNode: null, + /** + * The parent element of this node. + * + * @type {Element | null} + */ + get parentElement() { + return this.parentNode && this.parentNode.nodeType === this.ELEMENT_NODE ? this.parentNode : null; + }, + /** + * The child nodes of this node. + * + * @type {NodeList} + */ + childNodes: null, + /** + * The document object associated with this node. + * + * @type {Document | null} + */ + ownerDocument: null, + /** + * The value of this node. + * + * @type {string | null} + */ + nodeValue: null, + /** + * The namespace URI of this node. + * + * @type {string | null} + */ + namespaceURI: null, + /** + * The prefix of the namespace for this node. + * + * @type {string | null} + */ + prefix: null, + /** + * The local part of the qualified name of this node. + * + * @type {string | null} + */ + localName: null, + /** + * The baseURI is currently always `about:blank`, + * since that's what happens when you create a document from scratch. + * + * @type {'about:blank'} + */ + baseURI: 'about:blank', + /** + * Is true if this node is part of a document. + * + * @type {boolean} + */ + get isConnected() { + var rootNode = this.getRootNode(); + return rootNode && rootNode.nodeType === rootNode.DOCUMENT_NODE; + }, + /** + * Checks whether `other` is an inclusive descendant of this node. + * + * @param {Node | null | undefined} other + * The node to check. + * @returns {boolean} + * True if `other` is an inclusive descendant of this node; false otherwise. + * @see https://dom.spec.whatwg.org/#dom-node-contains + */ + contains: function (other) { + if (!other) return false; + var parent = other; + do { + if (this === parent) return true; + parent = parent.parentNode; + } while (parent); + return false; + }, + /** + * @typedef GetRootNodeOptions + * @property {boolean} [composed=false] + */ + /** + * Searches for the root node of this node. + * + * **This behavior is slightly different from the in the specs**: + * - ignores `options.composed`, since `ShadowRoot`s are unsupported, always returns root. + * + * @param {GetRootNodeOptions} [options] + * @returns {Node} + * Root node. + * @see https://dom.spec.whatwg.org/#dom-node-getrootnode + * @see https://dom.spec.whatwg.org/#concept-shadow-including-root + */ + getRootNode: function (options) { + var parent = this; + do { + if (!parent.parentNode) { + return parent; + } + parent = parent.parentNode; + } while (parent); + }, + /** + * Checks whether the given node is equal to this node. + * + * Two nodes are equal when they have the same type, defining characteristics (for the type), + * and the same childNodes. The comparison is iterative to avoid stack overflows on + * deeply-nested trees. Attribute nodes of each Element pair are also pushed onto the stack + * and compared the same way. + * + * @param {Node} [otherNode] + * @returns {boolean} + * @see https://dom.spec.whatwg.org/#concept-node-equals + * @see ../docs/walk-dom.md. + */ + isEqualNode: function (otherNode) { + if (!otherNode) return false; + + // Use an explicit {node, other} pair stack to avoid call-stack overflow on deep trees. + // walkDOM cannot be used here — parallel two-tree traversal requires pairing + // corresponding nodes at each step across both trees simultaneously. + var stack = [{ node: this, other: otherNode }]; + while (stack.length > 0) { + var pair = stack.pop(); + var node = pair.node; + var other = pair.other; + + if (node.nodeType !== other.nodeType) return false; + + switch (node.nodeType) { + case node.DOCUMENT_TYPE_NODE: + if (node.name !== other.name) return false; + if (node.publicId !== other.publicId) return false; + if (node.systemId !== other.systemId) return false; + break; + case node.ELEMENT_NODE: + if (node.namespaceURI !== other.namespaceURI) return false; + if (node.prefix !== other.prefix) return false; + if (node.localName !== other.localName) return false; + if (node.attributes.length !== other.attributes.length) return false; + for (var i = 0; i < node.attributes.length; i++) { + var attr = node.attributes.item(i); + var otherAttr = other.getAttributeNodeNS(attr.namespaceURI, attr.localName); + if (!otherAttr) return false; + stack.push({ node: attr, other: otherAttr }); + } + break; + case node.ATTRIBUTE_NODE: + if (node.namespaceURI !== other.namespaceURI) return false; + if (node.localName !== other.localName) return false; + if (node.value !== other.value) return false; + break; + case node.PROCESSING_INSTRUCTION_NODE: + if (node.target !== other.target || node.data !== other.data) return false; + break; + case node.TEXT_NODE: + case node.CDATA_SECTION_NODE: + case node.COMMENT_NODE: + if (node.data !== other.data) return false; + break; + } + + if (node.childNodes.length !== other.childNodes.length) return false; + + // Push children in reverse order so index 0 is processed first (LIFO). + for (var i = node.childNodes.length - 1; i >= 0; i--) { + stack.push({ node: node.childNodes[i], other: other.childNodes[i] }); + } + } + + return true; + }, + /** + * Checks whether or not the given node is this node. + * + * @param {Node} [otherNode] + */ + isSameNode: function (otherNode) { + return this === otherNode; + }, + /** + * Inserts a node before a reference node as a child of this node. + * + * @param {Node} newChild + * The new child node to be inserted. + * @param {Node | null} refChild + * The reference node before which newChild will be inserted. + * @returns {Node} + * The new child node successfully inserted. + * @throws {DOMException} + * Throws a DOMException if inserting the node would result in a DOM tree that is not + * well-formed, or if `child` is provided but is not a child of `parent`. + * See {@link _insertBefore} for more details. + * @since Modified in DOM L2 + */ + insertBefore: function (newChild, refChild) { + return _insertBefore(this, newChild, refChild); + }, + /** + * Replaces an old child node with a new child node within this node. + * + * @param {Node} newChild + * The new node that is to replace the old node. + * If it already exists in the DOM, it is removed from its original position. + * @param {Node} oldChild + * The existing child node to be replaced. + * @returns {Node} + * Returns the replaced child node. + * @throws {DOMException} + * Throws a DOMException if replacing the node would result in a DOM tree that is not + * well-formed, or if `oldChild` is not a child of `this`. + * This can also occur if the pre-replacement validity assertion fails. + * See {@link _insertBefore}, {@link Node.removeChild}, and + * {@link assertPreReplacementValidityInDocument} for more details. + * @see https://dom.spec.whatwg.org/#concept-node-replace + */ + replaceChild: function (newChild, oldChild) { + _insertBefore(this, newChild, oldChild, assertPreReplacementValidityInDocument); + if (oldChild) { + this.removeChild(oldChild); + } + }, + /** + * Removes an existing child node from this node. + * + * @param {Node} oldChild + * The child node to be removed. + * @returns {Node} + * Returns the removed child node. + * @throws {DOMException} + * Throws a DOMException if `oldChild` is not a child of `this`. + * See {@link _removeChild} for more details. + */ + removeChild: function (oldChild) { + return _removeChild(this, oldChild); + }, + /** + * Appends a child node to this node. + * + * @param {Node} newChild + * The child node to be appended to this node. + * If it already exists in the DOM, it is removed from its original position. + * @returns {Node} + * Returns the appended child node. + * @throws {DOMException} + * Throws a DOMException if appending the node would result in a DOM tree that is not + * well-formed, or if `newChild` is not a valid Node. + * See {@link insertBefore} for more details. + */ + appendChild: function (newChild) { + return this.insertBefore(newChild, null); + }, + /** + * Determines whether this node has any child nodes. + * + * @returns {boolean} + * Returns true if this node has any child nodes, and false otherwise. + */ + hasChildNodes: function () { + return this.firstChild != null; + }, + /** + * Creates a copy of the calling node. + * + * @param {boolean} deep + * If true, the contents of the node are recursively copied. + * If false, only the node itself (and its attributes, if it is an element) are copied. + * @returns {Node} + * Returns the newly created copy of the node. + * @throws {DOMException} + * May throw a DOMException if operations within {@link Element#setAttributeNode} or + * {@link Node#appendChild} (which are potentially invoked in this method) do not meet their + * specific constraints. + * @see {@link cloneNode} + */ + cloneNode: function (deep) { + return cloneNode(this.ownerDocument || this, this, deep); + }, + /** + * Puts the specified node and all of its subtree into a "normalized" form. In a normalized + * subtree, no text nodes in the subtree are empty and there are no adjacent text nodes. + * + * Specifically, this method merges any adjacent text nodes (i.e., nodes for which `nodeType` + * is `TEXT_NODE`) into a single node with the combined data. It also removes any empty text + * nodes. + * + * This method iterativly traverses all child nodes to normalize all descendent nodes within + * the subtree. + * + * @throws {DOMException} + * May throw a DOMException if operations within removeChild or appendData (which are + * potentially invoked in this method) do not meet their specific constraints. + * @since Modified in DOM Level 2 + * @see {@link Node.removeChild} + * @see {@link CharacterData.appendData} + * @see ../docs/walk-dom.md. + */ + normalize: function () { + walkDOM(this, null, { + enter: function (node) { + // Merge adjacent text children of node before walkDOM schedules them. + // walkDOM reads lastChild/previousSibling after enter returns, so the + // surviving post-merge children are what it descends into. + var child = node.firstChild; + while (child) { + var next = child.nextSibling; + if (next !== null && next.nodeType === TEXT_NODE && child.nodeType === TEXT_NODE) { + // Merge the whole run of adjacent text nodes at once: gather the + // following text siblings' data, unlink them in a single pass, and + // re-index the child list a single time. Per-sibling `removeChild` + // (each an O(K) re-index) plus per-sibling `appendData` (each an O(K) + // string rebuild) is O(K^2) over a long run of single-character text + // nodes; this keeps it O(K). The first text node of the run survives + // and carries the concatenated data, preserving node identity and + // locator semantics. + var tail = []; + var sibling = next; + while (sibling !== null && sibling.nodeType === TEXT_NODE) { + tail.push(sibling.data); + sibling = sibling.nextSibling; + } + // `sibling` is now the first non-text node after the run, or null. + var removed = child.nextSibling; + while (removed !== sibling) { + var following = removed.nextSibling; + removed.parentNode = null; + removed.previousSibling = null; + removed.nextSibling = null; + removed = following; + } + child.nextSibling = sibling; + if (sibling !== null) { + sibling.previousSibling = child; + } else { + node.lastChild = child; + } + child.appendData(tail.join('')); // single O(K) string rebuild + _onUpdateChild(node.ownerDocument, node); // single O(K) re-index + child = sibling; + } else { + child = next; + } + } + return true; // descend into surviving children + }, + }); + }, + /** + * Checks whether the DOM implementation implements a specific feature and its version. + * + * @deprecated + * Since `DOMImplementation.hasFeature` is deprecated and always returns true. + * @param {string} feature + * The package name of the feature to test. This is the same name that can be passed to the + * method `hasFeature` on `DOMImplementation`. + * @param {string} version + * This is the version number of the package name to test. + * @returns {boolean} + * Returns true in all cases in the current implementation. + * @since Introduced in DOM Level 2 + * @see {@link DOMImplementation.hasFeature} + */ + isSupported: function (feature, version) { + return this.ownerDocument.implementation.hasFeature(feature, version); + }, + /** + * Look up the prefix associated to the given namespace URI, starting from this node. + * **The default namespace declarations are ignored by this method.** + * See Namespace Prefix Lookup for details on the algorithm used by this method. + * + * **This behavior is different from the in the specs**: + * - no node type specific handling + * - uses the internal attribute _nsMap for resolving namespaces that is updated when changing attributes + * + * @param {string | null} namespaceURI + * The namespace URI for which to find the associated prefix. + * @returns {string | null} + * The associated prefix, if found; otherwise, null. + * @see https://www.w3.org/TR/DOM-Level-3-Core/core.html#Node3-lookupNamespacePrefix + * @see https://www.w3.org/TR/DOM-Level-3-Core/namespaces-algorithms.html#lookupNamespacePrefixAlgo + * @see https://dom.spec.whatwg.org/#dom-node-lookupprefix + * @see https://github.com/xmldom/xmldom/issues/322 + * @prettierignore + */ + lookupPrefix: function (namespaceURI) { + var el = this; + while (el) { + var map = el._nsMap; + //console.dir(map) + if (map) { + for (var n in map) { + if (hasOwn(map, n) && map[n] === namespaceURI) { + return n; + } + } + } + el = el.nodeType == ATTRIBUTE_NODE ? el.ownerDocument : el.parentNode; + } + return null; + }, + /** + * This function is used to look up the namespace URI associated with the given prefix, + * starting from this node. + * + * **This behavior is different from the in the specs**: + * - no node type specific handling + * - uses the internal attribute _nsMap for resolving namespaces that is updated when changing attributes + * + * @param {string | null} prefix + * The prefix for which to find the associated namespace URI. + * @returns {string | null} + * The associated namespace URI, if found; otherwise, null. + * @since DOM Level 3 + * @see https://dom.spec.whatwg.org/#dom-node-lookupnamespaceuri + * @see https://www.w3.org/TR/DOM-Level-3-Core/core.html#Node3-lookupNamespaceURI + * @prettierignore + */ + lookupNamespaceURI: function (prefix) { + var el = this; + while (el) { + var map = el._nsMap; + //console.dir(map) + if (map) { + if (hasOwn(map, prefix)) { + return map[prefix]; + } + } + el = el.nodeType == ATTRIBUTE_NODE ? el.ownerDocument : el.parentNode; + } + return null; + }, + /** + * Determines whether the given namespace URI is the default namespace. + * + * The function works by looking up the prefix associated with the given namespace URI. If no + * prefix is found (i.e., the namespace URI is not registered in the namespace map of this + * node or any of its ancestors), it returns `true`, implying the namespace URI is considered + * the default. + * + * **This behavior is different from the in the specs**: + * - no node type specific handling + * - uses the internal attribute _nsMap for resolving namespaces that is updated when changing attributes + * + * @param {string | null} namespaceURI + * The namespace URI to be checked. + * @returns {boolean} + * Returns true if the given namespace URI is the default namespace, false otherwise. + * @since DOM Level 3 + * @see https://www.w3.org/TR/DOM-Level-3-Core/core.html#Node3-isDefaultNamespace + * @see https://dom.spec.whatwg.org/#dom-node-isdefaultnamespace + * @prettierignore + */ + isDefaultNamespace: function (namespaceURI) { + var prefix = this.lookupPrefix(namespaceURI); + return prefix == null; + }, + /** + * Compares the reference node with a node with regard to their position in the document and + * according to the document order. + * + * @param {Node} other + * The node to compare the reference node to. + * @returns {number} + * Returns how the node is positioned relatively to the reference node according to the + * bitmask. 0 if reference node and given node are the same. + * @since DOM Level 3 + * @see https://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/core.html#Node3-compare + * @see https://dom.spec.whatwg.org/#dom-node-comparedocumentposition + */ + compareDocumentPosition: function (other) { + if (this === other) return 0; + var node1 = other; + var node2 = this; + var attr1 = null; + var attr2 = null; + if (node1 instanceof Attr) { + attr1 = node1; + node1 = attr1.ownerElement; + } + if (node2 instanceof Attr) { + attr2 = node2; + node2 = attr2.ownerElement; + if (attr1 && node1 && node2 === node1) { + for (var i = 0, attr; (attr = node2.attributes[i]); i++) { + if (attr === attr1) + return DocumentPosition.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC + DocumentPosition.DOCUMENT_POSITION_PRECEDING; + if (attr === attr2) + return DocumentPosition.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC + DocumentPosition.DOCUMENT_POSITION_FOLLOWING; + } + } + } + if (!node1 || !node2 || node2.ownerDocument !== node1.ownerDocument) { + return ( + DocumentPosition.DOCUMENT_POSITION_DISCONNECTED + + DocumentPosition.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC + + (docGUID(node2.ownerDocument) > docGUID(node1.ownerDocument) + ? DocumentPosition.DOCUMENT_POSITION_FOLLOWING + : DocumentPosition.DOCUMENT_POSITION_PRECEDING) + ); + } + if (attr2 && node1 === node2) { + return DocumentPosition.DOCUMENT_POSITION_CONTAINS + DocumentPosition.DOCUMENT_POSITION_PRECEDING; + } + if (attr1 && node1 === node2) { + return DocumentPosition.DOCUMENT_POSITION_CONTAINED_BY + DocumentPosition.DOCUMENT_POSITION_FOLLOWING; + } + + var chain1 = []; + var ancestor1 = node1.parentNode; + while (ancestor1) { + if (!attr2 && ancestor1 === node2) { + return DocumentPosition.DOCUMENT_POSITION_CONTAINED_BY + DocumentPosition.DOCUMENT_POSITION_FOLLOWING; + } + chain1.push(ancestor1); + ancestor1 = ancestor1.parentNode; + } + chain1.reverse(); + + var chain2 = []; + var ancestor2 = node2.parentNode; + while (ancestor2) { + if (!attr1 && ancestor2 === node1) { + return DocumentPosition.DOCUMENT_POSITION_CONTAINS + DocumentPosition.DOCUMENT_POSITION_PRECEDING; + } + chain2.push(ancestor2); + ancestor2 = ancestor2.parentNode; + } + chain2.reverse(); + + var ca = commonAncestor(chain1, chain2); + for (var n in ca.childNodes) { + var child = ca.childNodes[n]; + if (child === node2) return DocumentPosition.DOCUMENT_POSITION_FOLLOWING; + if (child === node1) return DocumentPosition.DOCUMENT_POSITION_PRECEDING; + if (chain2.indexOf(child) >= 0) return DocumentPosition.DOCUMENT_POSITION_FOLLOWING; + if (chain1.indexOf(child) >= 0) return DocumentPosition.DOCUMENT_POSITION_PRECEDING; + } + return 0; + }, +}; + +/** + * Encodes special XML characters to their corresponding entities. + * + * @param {string} c + * The character to be encoded. + * @returns {string} + * The encoded character. + * @private + */ +function _xmlEncoder(c) { + return ( + (c == '<' && '<') || (c == '>' && '>') || (c == '&' && '&') || (c == '"' && '"') || '&#' + c.charCodeAt() + ';' + ); +} + +copy(NodeType, Node); +copy(NodeType, Node.prototype); +copy(DocumentPosition, Node); +copy(DocumentPosition, Node.prototype); + +/** + * Visits every node in the subtree rooted at `node` in depth-first pre-order. + * + * Delegates to {@link walkDOM} for traversal. The `callback` is called on each node; + * if it returns a truthy value, traversal stops immediately. + * + * @param {Node} node + * Root of the subtree to visit. + * @param {function(Node): *} callback + * Called for each node. A truthy return value stops traversal early. + */ +function _visitNode(node, callback) { + walkDOM(node, null, { + enter: function (n) { + return callback(n) ? walkDOM.STOP : true; + }, + }); +} + +/** + * Depth-first pre/post-order DOM tree walker. + * + * Visits every node in the subtree rooted at `node`. For each node: + * + * 1. Calls `callbacks.enter(node, context)` before descending into the node's children. The + * return value becomes the `context` passed to each child's `enter` call and to the matching + * `exit` call. + * 2. If `enter` returns `null` or `undefined`, the node's children are skipped; + * sibling traversal continues normally. + * 3. If `enter` returns `walkDOM.STOP`, the entire traversal is aborted immediately — no + * further `enter` or `exit` calls are made. + * 4. `lastChild` and `previousSibling` are read **after** `enter` returns, so `enter` may + * safely modify the node's own child list before the walker descends. Modifying siblings of + * the current node or any other part of the tree produces unpredictable results: nodes already + * queued on the stack are visited regardless of DOM changes, and newly inserted nodes outside + * the current child list are never visited. + * 5. Calls `callbacks.exit(node, context)` (if provided) after all of a node's children have + * been visited, passing the same `context` that `enter` + * returned for that node. + * + * This implementation uses an explicit stack and does not recurse — it is safe on arbitrarily + * deep trees. + * + * @param {Node} node + * Root of the subtree to walk. + * @param {*} context + * Initial context value passed to the root node's `enter`. + * @param {{ enter: function(Node, *): *, exit?: function(Node, *): void }} callbacks + * @returns {void | walkDOM.STOP} + * @see ../docs/walk-dom.md. + */ +function walkDOM(node, context, callbacks) { + // Each stack frame is {node, context, phase}: + // walkDOM.ENTER — call enter, then push children + // walkDOM.EXIT — call exit + var stack = [{ node: node, context: context, phase: walkDOM.ENTER }]; + while (stack.length > 0) { + var frame = stack.pop(); + if (frame.phase === walkDOM.ENTER) { + var childContext = callbacks.enter(frame.node, frame.context); + if (childContext === walkDOM.STOP) { + return walkDOM.STOP; + } + // Push exit frame before children so it fires after all children are processed (Last In First Out) + stack.push({ node: frame.node, context: childContext, phase: walkDOM.EXIT }); + if (childContext === null || childContext === undefined) { + continue; // skip children + } + // lastChild is read after enter returns, so enter may modify the child list. + var child = frame.node.lastChild; + // Traverse from lastChild backwards so that pushing onto the stack + // naturally yields firstChild on top (processed first). + while (child) { + stack.push({ node: child, context: childContext, phase: walkDOM.ENTER }); + child = child.previousSibling; + } + } else { + // frame.phase === walkDOM.EXIT + if (callbacks.exit) { + callbacks.exit(frame.node, frame.context); + } + } + } +} + +/** + * Sentinel value returned from a `walkDOM` `enter` callback to abort the entire traversal + * immediately. + * + * @type {symbol} + */ +walkDOM.STOP = Symbol('walkDOM.STOP'); +/** + * Phase constant for a stack frame that has not yet been visited. + * The `enter` callback is called and children are scheduled. + * + * @type {number} + */ +walkDOM.ENTER = 0; +/** + * Phase constant for a stack frame whose subtree has been fully visited. + * The `exit` callback is called. + * + * @type {number} + */ +walkDOM.EXIT = 1; + +/** + * @typedef DocumentOptions + * @property {string} [contentType=MIME_TYPE.XML_APPLICATION] + */ +/** + * The Document interface describes the common properties and methods for any kind of document. + * + * It should usually be created using `new DOMImplementation().createDocument(...)` + * or `new DOMImplementation().createHTMLDocument(...)`. + * + * The constructor is considered a private API and offers to initially set the `contentType` + * property via it's options parameter. + * + * @class + * @param {Symbol} symbol + * @param {DocumentOptions} [options] + * @augments Node + * @private + * @see https://developer.mozilla.org/en-US/docs/Web/API/Document + * @see https://dom.spec.whatwg.org/#interface-document + */ +function Document(symbol, options) { + checkSymbol(symbol); + + var opt = options || {}; + this.ownerDocument = this; + /** + * The mime type of the document is determined at creation time and can not be modified. + * + * @type {string} + * @see https://dom.spec.whatwg.org/#concept-document-content-type + * @see {@link DOMImplementation} + * @see {@link MIME_TYPE} + * @readonly + */ + this.contentType = opt.contentType || MIME_TYPE.XML_APPLICATION; + /** + * @type {'html' | 'xml'} + * @see https://dom.spec.whatwg.org/#concept-document-type + * @see {@link DOMImplementation} + * @readonly + */ + this.type = isHTMLMimeType(this.contentType) ? 'html' : 'xml'; +} + +/** + * Updates the namespace mapping of an element when a new attribute is added. + * + * @param {Document} doc + * The document that the element belongs to. + * @param {Element} el + * The element to which the attribute is being added. + * @param {Attr} newAttr + * The new attribute being added. + * @private + */ +function _onAddAttribute(doc, el, newAttr) { + doc && doc._inc++; + var ns = newAttr.namespaceURI; + if (ns === NAMESPACE.XMLNS) { + //update namespace + el._nsMap[newAttr.prefix ? newAttr.localName : ''] = newAttr.value; + } +} + +/** + * Updates the namespace mapping of an element when an attribute is removed. + * + * @param {Document} doc + * The document that the element belongs to. + * @param {Element} el + * The element from which the attribute is being removed. + * @param {Attr} newAttr + * The attribute being removed. + * @param {boolean} remove + * Indicates whether the attribute is to be removed. + * @private + */ +function _onRemoveAttribute(doc, el, newAttr, remove) { + doc && doc._inc++; + var ns = newAttr.namespaceURI; + if (ns === NAMESPACE.XMLNS) { + //update namespace + delete el._nsMap[newAttr.prefix ? newAttr.localName : '']; + } +} + +/** + * Updates `parent.childNodes`, adjusting the indexed items and its `length`. + * If `newChild` is provided and has no nextSibling, it will be appended. + * Otherwise, it's assumed that an item has been removed or inserted, + * and `parent.firstNode` and its `.nextSibling` to re-indexing all child nodes of `parent`. + * + * @param {Document} doc + * The parent document of `el`. + * @param {Node} parent + * The parent node whose childNodes list needs to be updated. + * @param {Node} [newChild] + * The new child node to be appended. If not provided, the function assumes a node has been + * removed. + * @private + */ +function _onUpdateChild(doc, parent, newChild) { + if (doc && doc._inc) { + doc._inc++; + var childNodes = parent.childNodes; + // assumes nextSibling and previousSibling were already configured upfront + if (newChild && !newChild.nextSibling) { + // if an item has been appended, we only need to update the last index and the length + childNodes[childNodes.length++] = newChild; + } else { + // otherwise we need to reindex all items, + // which can take a while when processing nodes with a lot of children + var child = parent.firstChild; + var i = 0; + while (child) { + childNodes[i++] = child; + child = child.nextSibling; + } + childNodes.length = i; + delete childNodes[childNodes.length]; + } + } +} + +/** + * Removes the connections between `parentNode` and `child` + * and any existing `child.previousSibling` or `child.nextSibling`. + * + * @param {Node} parentNode + * The parent node from which the child node is to be removed. + * @param {Node} child + * The child node to be removed from the parentNode. + * @returns {Node} + * Returns the child node that was removed. + * @throws {DOMException} + * With code: + * - {@link DOMException.NOT_FOUND_ERR} If the parentNode is not the parent of the child node. + * @private + * @see https://github.com/xmldom/xmldom/issues/135 + * @see https://github.com/xmldom/xmldom/issues/145 + */ +function _removeChild(parentNode, child) { + if (parentNode !== child.parentNode) { + throw new DOMException(DOMException.NOT_FOUND_ERR, "child's parent is not parent"); + } + var oldPreviousSibling = child.previousSibling; + var oldNextSibling = child.nextSibling; + if (oldPreviousSibling) { + oldPreviousSibling.nextSibling = oldNextSibling; + } else { + parentNode.firstChild = oldNextSibling; + } + if (oldNextSibling) { + oldNextSibling.previousSibling = oldPreviousSibling; + } else { + parentNode.lastChild = oldPreviousSibling; + } + _onUpdateChild(parentNode.ownerDocument, parentNode); + child.parentNode = null; + child.previousSibling = null; + child.nextSibling = null; + return child; +} + +/** + * Returns `true` if `node` can be a parent for insertion. + * + * @param {Node} node + * @returns {boolean} + */ +function hasValidParentNodeType(node) { + return ( + node && + (node.nodeType === Node.DOCUMENT_NODE || node.nodeType === Node.DOCUMENT_FRAGMENT_NODE || node.nodeType === Node.ELEMENT_NODE) + ); +} + +/** + * Returns `true` if `node` can be inserted according to it's `nodeType`. + * + * @param {Node} node + * @returns {boolean} + */ +function hasInsertableNodeType(node) { + return ( + node && + (node.nodeType === Node.CDATA_SECTION_NODE || + node.nodeType === Node.COMMENT_NODE || + node.nodeType === Node.DOCUMENT_FRAGMENT_NODE || + node.nodeType === Node.DOCUMENT_TYPE_NODE || + node.nodeType === Node.ELEMENT_NODE || + node.nodeType === Node.PROCESSING_INSTRUCTION_NODE || + node.nodeType === Node.TEXT_NODE) + ); +} + +/** + * Returns true if `node` is a DOCTYPE node. + * + * @param {Node} node + * @returns {boolean} + */ +function isDocTypeNode(node) { + return node && node.nodeType === Node.DOCUMENT_TYPE_NODE; +} + +/** + * Returns true if the node is an element. + * + * @param {Node} node + * @returns {boolean} + */ +function isElementNode(node) { + return node && node.nodeType === Node.ELEMENT_NODE; +} +/** + * Returns true if `node` is a text node. + * + * @param {Node} node + * @returns {boolean} + */ +function isTextNode(node) { + return node && node.nodeType === Node.TEXT_NODE; +} + +/** + * Check if en element node can be inserted before `child`, or at the end if child is falsy, + * according to the presence and position of a doctype node on the same level. + * + * @param {Document} doc + * The document node. + * @param {Node} child + * The node that would become the nextSibling if the element would be inserted. + * @returns {boolean} + * `true` if an element can be inserted before child. + * @private + */ +function isElementInsertionPossible(doc, child) { + var parentChildNodes = doc.childNodes || []; + if (find(parentChildNodes, isElementNode) || isDocTypeNode(child)) { + return false; + } + var docTypeNode = find(parentChildNodes, isDocTypeNode); + return !(child && docTypeNode && parentChildNodes.indexOf(docTypeNode) > parentChildNodes.indexOf(child)); +} + +/** + * Check if en element node can be inserted before `child`, or at the end if child is falsy, + * according to the presence and position of a doctype node on the same level. + * + * @param {Node} doc + * The document node. + * @param {Node} child + * The node that would become the nextSibling if the element would be inserted. + * @returns {boolean} + * `true` if an element can be inserted before child. + * @private + */ +function isElementReplacementPossible(doc, child) { + var parentChildNodes = doc.childNodes || []; + + function hasElementChildThatIsNotChild(node) { + return isElementNode(node) && node !== child; + } + + if (find(parentChildNodes, hasElementChildThatIsNotChild)) { + return false; + } + var docTypeNode = find(parentChildNodes, isDocTypeNode); + return !(child && docTypeNode && parentChildNodes.indexOf(docTypeNode) > parentChildNodes.indexOf(child)); +} + +/** + * Asserts pre-insertion validity of a node into a parent before a child. + * Throws errors for invalid node combinations that would result in an ill-formed DOM. + * + * @param {Node} parent + * The parent node to insert `node` into. + * @param {Node} node + * The node to insert. + * @param {Node | null} child + * The node that should become the `nextSibling` of `node`. If null, no sibling is considered. + * @throws {DOMException} + * With code: + * - {@link DOMException.HIERARCHY_REQUEST_ERR} If `parent` is not a Document, + * DocumentFragment, or Element node. + * - {@link DOMException.HIERARCHY_REQUEST_ERR} If `node` is a host-including inclusive + * ancestor of `parent`. (Currently not implemented) + * - {@link DOMException.NOT_FOUND_ERR} If `child` is non-null and its `parent` is not + * `parent`. + * - {@link DOMException.HIERARCHY_REQUEST_ERR} If `node` is not a DocumentFragment, + * DocumentType, Element, or CharacterData node. + * - {@link DOMException.HIERARCHY_REQUEST_ERR} If either `node` is a Text node and `parent` is + * a document, or if `node` is a doctype and `parent` is not a document. + * @private + * @see https://dom.spec.whatwg.org/#concept-node-ensure-pre-insertion-validity + * @see https://dom.spec.whatwg.org/#concept-node-replace + */ +function assertPreInsertionValidity1to5(parent, node, child) { + // 1. If `parent` is not a Document, DocumentFragment, or Element node, then throw a "HierarchyRequestError" DOMException. + if (!hasValidParentNodeType(parent)) { + throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR, 'Unexpected parent node type ' + parent.nodeType); + } + // 2. If `node` is a host-including inclusive ancestor of `parent`, then throw a "HierarchyRequestError" DOMException. + // not implemented! + // 3. If `child` is non-null and its parent is not `parent`, then throw a "NotFoundError" DOMException. + if (child && child.parentNode !== parent) { + throw new DOMException(DOMException.NOT_FOUND_ERR, 'child not in parent'); + } + if ( + // 4. If `node` is not a DocumentFragment, DocumentType, Element, or CharacterData node, then throw a "HierarchyRequestError" DOMException. + !hasInsertableNodeType(node) || + // 5. If either `node` is a Text node and `parent` is a document, + // the sax parser currently adds top level text nodes, this will be fixed in 0.9.0 + // || (node.nodeType === Node.TEXT_NODE && parent.nodeType === Node.DOCUMENT_NODE) + // or `node` is a doctype and `parent` is not a document, then throw a "HierarchyRequestError" DOMException. + (isDocTypeNode(node) && parent.nodeType !== Node.DOCUMENT_NODE) + ) { + throw new DOMException( + DOMException.HIERARCHY_REQUEST_ERR, + 'Unexpected node type ' + node.nodeType + ' for parent node type ' + parent.nodeType + ); + } +} + +/** + * Asserts pre-insertion validity of a node into a document before a child. + * Throws errors for invalid node combinations that would result in an ill-formed DOM. + * + * @param {Document} parent + * The parent node to insert `node` into. + * @param {Node} node + * The node to insert. + * @param {Node | undefined} child + * The node that should become the `nextSibling` of `node`. If undefined, no sibling is + * considered. + * @returns {Node} + * @throws {DOMException} + * With code: + * - {@link DOMException.HIERARCHY_REQUEST_ERR} If `node` is a DocumentFragment with more than + * one element child or has a Text node child. + * - {@link DOMException.HIERARCHY_REQUEST_ERR} If `node` is a DocumentFragment with one + * element child and either `parent` has an element child, `child` is a doctype, or `child` is + * non-null and a doctype is following `child`. + * - {@link DOMException.HIERARCHY_REQUEST_ERR} If `node` is an Element and `parent` has an + * element child, `child` is a doctype, or `child` is non-null and a doctype is following + * `child`. + * - {@link DOMException.HIERARCHY_REQUEST_ERR} If `node` is a DocumentType and `parent` has a + * doctype child, `child` is non-null and an element is preceding `child`, or `child` is null + * and `parent` has an element child. + * @private + * @see https://dom.spec.whatwg.org/#concept-node-ensure-pre-insertion-validity + * @see https://dom.spec.whatwg.org/#concept-node-replace + */ +function assertPreInsertionValidityInDocument(parent, node, child) { + var parentChildNodes = parent.childNodes || []; + var nodeChildNodes = node.childNodes || []; + + // DocumentFragment + if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) { + var nodeChildElements = nodeChildNodes.filter(isElementNode); + // If node has more than one element child or has a Text node child. + if (nodeChildElements.length > 1 || find(nodeChildNodes, isTextNode)) { + throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR, 'More than one element or text in fragment'); + } + // Otherwise, if `node` has one element child and either `parent` has an element child, + // `child` is a doctype, or `child` is non-null and a doctype is following `child`. + if (nodeChildElements.length === 1 && !isElementInsertionPossible(parent, child)) { + throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR, 'Element in fragment can not be inserted before doctype'); + } + } + // Element + if (isElementNode(node)) { + // `parent` has an element child, `child` is a doctype, + // or `child` is non-null and a doctype is following `child`. + if (!isElementInsertionPossible(parent, child)) { + throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR, 'Only one element can be added and only after doctype'); + } + } + // DocumentType + if (isDocTypeNode(node)) { + // `parent` has a doctype child, + if (find(parentChildNodes, isDocTypeNode)) { + throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR, 'Only one doctype is allowed'); + } + var parentElementChild = find(parentChildNodes, isElementNode); + // `child` is non-null and an element is preceding `child`, + if (child && parentChildNodes.indexOf(parentElementChild) < parentChildNodes.indexOf(child)) { + throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR, 'Doctype can only be inserted before an element'); + } + // or `child` is null and `parent` has an element child. + if (!child && parentElementChild) { + throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR, 'Doctype can not be appended since element is present'); + } + } +} + +/** + * @param {Document} parent + * The parent node to insert `node` into. + * @param {Node} node + * The node to insert. + * @param {Node | undefined} child + * the node that should become the `nextSibling` of `node` + * @returns {Node} + * @throws {DOMException} + * For several node combinations that would create a DOM that is not well-formed. + * @throws {DOMException} + * If `child` is provided but is not a child of `parent`. + * @private + * @see https://dom.spec.whatwg.org/#concept-node-ensure-pre-insertion-validity + * @see https://dom.spec.whatwg.org/#concept-node-replace + */ +function assertPreReplacementValidityInDocument(parent, node, child) { + var parentChildNodes = parent.childNodes || []; + var nodeChildNodes = node.childNodes || []; + + // DocumentFragment + if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) { + var nodeChildElements = nodeChildNodes.filter(isElementNode); + // If `node` has more than one element child or has a Text node child. + if (nodeChildElements.length > 1 || find(nodeChildNodes, isTextNode)) { + throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR, 'More than one element or text in fragment'); + } + // Otherwise, if `node` has one element child and either `parent` has an element child that is not `child` or a doctype is following `child`. + if (nodeChildElements.length === 1 && !isElementReplacementPossible(parent, child)) { + throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR, 'Element in fragment can not be inserted before doctype'); + } + } + // Element + if (isElementNode(node)) { + // `parent` has an element child that is not `child` or a doctype is following `child`. + if (!isElementReplacementPossible(parent, child)) { + throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR, 'Only one element can be added and only after doctype'); + } + } + // DocumentType + if (isDocTypeNode(node)) { + function hasDoctypeChildThatIsNotChild(node) { + return isDocTypeNode(node) && node !== child; + } + + // `parent` has a doctype child that is not `child`, + if (find(parentChildNodes, hasDoctypeChildThatIsNotChild)) { + throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR, 'Only one doctype is allowed'); + } + var parentElementChild = find(parentChildNodes, isElementNode); + // or an element is preceding `child`. + if (child && parentChildNodes.indexOf(parentElementChild) < parentChildNodes.indexOf(child)) { + throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR, 'Doctype can only be inserted before an element'); + } + } +} + +/** + * Inserts a node into a parent node before a child node. + * + * @param {Node} parent + * The parent node to insert the node into. + * @param {Node} node + * The node to insert into the parent. + * @param {Node | null} child + * The node that should become the next sibling of the node. + * If null, the function inserts the node at the end of the children of the parent node. + * @param {Function} [_inDocumentAssertion] + * An optional function to check pre-insertion validity if parent is a document node. + * Defaults to {@link assertPreInsertionValidityInDocument} + * @returns {Node} + * Returns the inserted node. + * @throws {DOMException} + * Throws a DOMException if inserting the node would result in a DOM tree that is not + * well-formed. See {@link assertPreInsertionValidity1to5}, + * {@link assertPreInsertionValidityInDocument}. + * @throws {DOMException} + * Throws a DOMException if child is provided but is not a child of the parent. See + * {@link Node.removeChild} + * @private + * @see https://dom.spec.whatwg.org/#concept-node-ensure-pre-insertion-validity + */ +function _insertBefore(parent, node, child, _inDocumentAssertion) { + // To ensure pre-insertion validity of a node into a parent before a child, run these steps: + assertPreInsertionValidity1to5(parent, node, child); + + // If parent is a document, and any of the statements below, switched on the interface node implements, + // are true, then throw a "HierarchyRequestError" DOMException. + if (parent.nodeType === Node.DOCUMENT_NODE) { + (_inDocumentAssertion || assertPreInsertionValidityInDocument)(parent, node, child); + } + + var cp = node.parentNode; + if (cp) { + cp.removeChild(node); //remove and update + } + if (node.nodeType === DOCUMENT_FRAGMENT_NODE) { + var newFirst = node.firstChild; + if (newFirst == null) { + return node; + } + var newLast = node.lastChild; + } else { + newFirst = newLast = node; + } + var pre = child ? child.previousSibling : parent.lastChild; + + newFirst.previousSibling = pre; + newLast.nextSibling = child; + + if (pre) { + pre.nextSibling = newFirst; + } else { + parent.firstChild = newFirst; + } + if (child == null) { + parent.lastChild = newLast; + } else { + child.previousSibling = newLast; + } + do { + newFirst.parentNode = parent; + } while (newFirst !== newLast && (newFirst = newFirst.nextSibling)); + _onUpdateChild(parent.ownerDocument || parent, parent, node); + if (node.nodeType == DOCUMENT_FRAGMENT_NODE) { + node.firstChild = node.lastChild = null; + } + + return node; +} + +Document.prototype = { + /** + * The implementation that created this document. + * + * @type DOMImplementation + * @readonly + */ + implementation: null, + nodeName: '#document', + nodeType: DOCUMENT_NODE, + /** + * The DocumentType node of the document. + * + * @type DocumentType + * @readonly + */ + doctype: null, + documentElement: null, + _inc: 1, + + insertBefore: function (newChild, refChild) { + //raises + if (newChild.nodeType === DOCUMENT_FRAGMENT_NODE) { + var child = newChild.firstChild; + while (child) { + var next = child.nextSibling; + this.insertBefore(child, refChild); + child = next; + } + return newChild; + } + _insertBefore(this, newChild, refChild); + newChild.ownerDocument = this; + if (this.documentElement === null && newChild.nodeType === ELEMENT_NODE) { + this.documentElement = newChild; + } + + return newChild; + }, + removeChild: function (oldChild) { + var removed = _removeChild(this, oldChild); + if (removed === this.documentElement) { + this.documentElement = null; + } + return removed; + }, + replaceChild: function (newChild, oldChild) { + //raises + _insertBefore(this, newChild, oldChild, assertPreReplacementValidityInDocument); + newChild.ownerDocument = this; + if (oldChild) { + this.removeChild(oldChild); + } + if (isElementNode(newChild)) { + this.documentElement = newChild; + } + }, + /** + * Imports a node from another document into this document, creating a new copy owned by this + * document. The source node and its subtree are not modified. + * + * @param {Node} importedNode + * The node to import. + * @param {boolean} deep + * If true, the contents of the node are recursively imported. + * If false, only the node itself (and its attributes, if it is an element) are imported. + * @returns {Node} + * Returns the newly created import of the node. + * @see {@link importNode} + * @see {@link https://dom.spec.whatwg.org/#dom-document-importnode} + */ + importNode: function (importedNode, deep) { + return importNode(this, importedNode, deep); + }, + // Introduced in DOM Level 2: + getElementById: function (id) { + var rtv = null; + _visitNode(this.documentElement, function (node) { + if (node.nodeType == ELEMENT_NODE) { + if (node.getAttribute('id') == id) { + rtv = node; + return true; + } + } + }); + return rtv; + }, + + /** + * Creates a new `Element` that is owned by this `Document`. + * In HTML Documents `localName` is the lower cased `tagName`, + * otherwise no transformation is being applied. + * When `contentType` implies the HTML namespace, it will be set as `namespaceURI`. + * + * __This implementation differs from the specification:__ - The provided name is not checked + * against the `Name` production, + * so no related error will be thrown. + * - There is no interface `HTMLElement`, it is always an `Element`. + * - There is no support for a second argument to indicate using custom elements. + * + * @param {string} tagName + * @returns {Element} + * @see https://developer.mozilla.org/en-US/docs/Web/API/Document/createElement + * @see https://dom.spec.whatwg.org/#dom-document-createelement + * @see https://dom.spec.whatwg.org/#concept-create-element + */ + createElement: function (tagName) { + var node = new Element(PDC); + node.ownerDocument = this; + if (this.type === 'html') { + tagName = tagName.toLowerCase(); + } + if (hasDefaultHTMLNamespace(this.contentType)) { + node.namespaceURI = NAMESPACE.HTML; + } + node.nodeName = tagName; + node.tagName = tagName; + node.localName = tagName; + node.childNodes = new NodeList(); + var attrs = (node.attributes = new NamedNodeMap()); + attrs._ownerElement = node; + return node; + }, + /** + * @returns {DocumentFragment} + */ + createDocumentFragment: function () { + var node = new DocumentFragment(PDC); + node.ownerDocument = this; + node.childNodes = new NodeList(); + return node; + }, + /** + * @param {string} data + * @returns {Text} + */ + createTextNode: function (data) { + var node = new Text(PDC); + node.ownerDocument = this; + node.childNodes = new NodeList(); + node.appendData(data); + return node; + }, + /** + * @param {string} data + * @returns {Comment} + * @see https://dom.spec.whatwg.org/#dom-document-createcomment + * @see https://www.w3.org/TR/xml/#NT-Comment XML 1.0 production [15] + * @see https://www.w3.org/TR/DOM-Parsing/#dfn-concept-serialize-xml §3.2.1.3 + * + * Note: no validation is performed at creation time. When the resulting document is + * serialized with `requireWellFormed: true`, the serializer throws `InvalidStateError` + * if the comment data contains `--` anywhere, ends with `-`, or contains characters + * outside the XML Char production (W3C DOM Parsing §3.2.1.3). Without that option the + * data is emitted verbatim. + */ + createComment: function (data) { + var node = new Comment(PDC); + node.ownerDocument = this; + node.childNodes = new NodeList(); + node.appendData(data); + return node; + }, + /** + * Returns a new CDATASection node whose data is `data`. + * + * __This implementation differs from the specification:__ - calling this method on an HTML + * document does not throw `NotSupportedError`. + * + * @param {string} data + * @returns {CDATASection} + * @throws {DOMException} + * With code `INVALID_CHARACTER_ERR` if `data` contains `"]]>"`. + * @see https://developer.mozilla.org/en-US/docs/Web/API/Document/createCDATASection + * @see https://dom.spec.whatwg.org/#dom-document-createcdatasection + */ + createCDATASection: function (data) { + if (data.indexOf(']]>') !== -1) { + throw new DOMException(DOMException.INVALID_CHARACTER_ERR, 'data contains "]]>"'); + } + var node = new CDATASection(PDC); + node.ownerDocument = this; + node.childNodes = new NodeList(); + node.appendData(data); + return node; + }, + /** + * Returns a ProcessingInstruction node whose target is target and data is data. + * + * __This behavior is slightly different from the in the specs__: + * - it does not do any input validation on the arguments and doesn't throw + * "InvalidCharacterError". + * + * Note: When the resulting document is serialized with `requireWellFormed: true`, the + * serializer throws `InvalidStateError` if `.target` is not a valid XML `NCName` (a `Name` + * with no colon) or is an ASCII case-insensitive match for `"xml"`, or if `.data` contains + * `?>` or characters outside the XML Char production (W3C DOM Parsing §3.2.1.7). Without that + * option the target and data are emitted verbatim. + * + * @param {string} target + * @param {string} data + * @returns {ProcessingInstruction} + * @see https://developer.mozilla.org/docs/Web/API/Document/createProcessingInstruction + * @see https://dom.spec.whatwg.org/#dom-document-createprocessinginstruction + * @see https://www.w3.org/TR/DOM-Parsing/#dfn-concept-serialize-xml §3.2.1.7 + */ + createProcessingInstruction: function (target, data) { + var node = new ProcessingInstruction(PDC); + node.ownerDocument = this; + node.childNodes = new NodeList(); + node.nodeName = node.target = target; + node.nodeValue = node.data = data; + return node; + }, + /** + * Creates an `Attr` node that is owned by this document. + * In HTML Documents `localName` is the lower cased `name`, + * otherwise no transformation is being applied. + * + * __This implementation differs from the specification:__ - The provided name is not checked + * against the `Name` production, + * so no related error will be thrown. + * + * @param {string} name + * @returns {Attr} + * @see https://developer.mozilla.org/en-US/docs/Web/API/Document/createAttribute + * @see https://dom.spec.whatwg.org/#dom-document-createattribute + */ + createAttribute: function (name) { + if (!g.QName_exact.test(name)) { + throw new DOMException(DOMException.INVALID_CHARACTER_ERR, 'invalid character in name "' + name + '"'); + } + if (this.type === 'html') { + name = name.toLowerCase(); + } + return this._createAttribute(name); + }, + _createAttribute: function (name) { + var node = new Attr(PDC); + node.ownerDocument = this; + node.childNodes = new NodeList(); + node.name = name; + node.nodeName = name; + node.localName = name; + node.specified = true; + return node; + }, + /** + * Creates an EntityReference object. + * The current implementation does not fill the `childNodes` with those of the corresponding + * `Entity` + * + * The `name` is validated against the XML `Name` production at creation time; an invalid name + * throws `InvalidCharacterError`. When the resulting node is serialized with + * `requireWellFormed: true`, the serializer re-validates `nodeName` against the XML `Name` + * production and throws `InvalidStateError` if a later `nodeName` mutation made it invalid; + * without that option the name is emitted verbatim. + * + * __This implementation differs from the specification:__ xmldom does not expand entities — + * the parser resolves entity references inline and never constructs `EntityReference` nodes, + * so this method is the only producer. + * + * @deprecated + * In DOM Level 4. + * @param {string} name + * The name of the entity to reference. No namespace well-formedness checks are performed. + * @returns {EntityReference} + * @throws {DOMException} + * With code `INVALID_CHARACTER_ERR` when `name` is not a valid XML `Name`. + * @throws {DOMException} + * with code `NOT_SUPPORTED_ERR` when the document is of type `html` + * @see https://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-392B75AE + */ + createEntityReference: function (name) { + if (!g.Name_exact.test(name)) { + throw new DOMException(DOMException.INVALID_CHARACTER_ERR, 'not a valid xml name "' + name + '"'); + } + if (this.type === 'html') { + throw new DOMException('document is an html document', DOMExceptionName.NotSupportedError); + } + + var node = new EntityReference(PDC); + node.ownerDocument = this; + node.childNodes = new NodeList(); + node.nodeName = name; + return node; + }, + // Introduced in DOM Level 2: + /** + * @param {string} namespaceURI + * @param {string} qualifiedName + * @returns {Element} + */ + createElementNS: function (namespaceURI, qualifiedName) { + var validated = validateAndExtract(namespaceURI, qualifiedName); + var node = new Element(PDC); + var attrs = (node.attributes = new NamedNodeMap()); + node.childNodes = new NodeList(); + node.ownerDocument = this; + node.nodeName = qualifiedName; + node.tagName = qualifiedName; + node.namespaceURI = validated[0]; + node.prefix = validated[1]; + node.localName = validated[2]; + attrs._ownerElement = node; + return node; + }, + // Introduced in DOM Level 2: + /** + * @param {string} namespaceURI + * @param {string} qualifiedName + * @returns {Attr} + */ + createAttributeNS: function (namespaceURI, qualifiedName) { + var validated = validateAndExtract(namespaceURI, qualifiedName); + var node = new Attr(PDC); + node.ownerDocument = this; + node.childNodes = new NodeList(); + node.nodeName = qualifiedName; + node.name = qualifiedName; + node.specified = true; + node.namespaceURI = validated[0]; + node.prefix = validated[1]; + node.localName = validated[2]; + return node; + }, +}; +_extends(Document, Node); + +function Element(symbol) { + checkSymbol(symbol); + + this._nsMap = Object.create(null); +} +Element.prototype = { + nodeType: ELEMENT_NODE, + /** + * The attributes of this element. + * + * @type {NamedNodeMap | null} + */ + attributes: null, + getQualifiedName: function () { + return this.prefix ? this.prefix + ':' + this.localName : this.localName; + }, + _isInHTMLDocumentAndNamespace: function () { + return this.ownerDocument.type === 'html' && this.namespaceURI === NAMESPACE.HTML; + }, + /** + * Implementaton of Level2 Core function hasAttributes. + * + * @returns {boolean} + * True if attribute list is not empty. + * @see https://www.w3.org/TR/DOM-Level-2-Core/#core-ID-NodeHasAttrs + */ + hasAttributes: function () { + return !!(this.attributes && this.attributes.length); + }, + hasAttribute: function (name) { + return !!this.getAttributeNode(name); + }, + /** + * Returns element’s first attribute whose qualified name is `name`, and `null` + * if there is no such attribute. + * + * @param {string} name + * @returns {string | null} + */ + getAttribute: function (name) { + var attr = this.getAttributeNode(name); + return attr ? attr.value : null; + }, + getAttributeNode: function (name) { + if (this._isInHTMLDocumentAndNamespace()) { + name = name.toLowerCase(); + } + return this.attributes.getNamedItem(name); + }, + /** + * Sets the value of element’s first attribute whose qualified name is qualifiedName to value. + * + * @param {string} name + * @param {string} value + */ + setAttribute: function (name, value) { + if (this._isInHTMLDocumentAndNamespace()) { + name = name.toLowerCase(); + } + var attr = this.getAttributeNode(name); + if (attr) { + attr.value = attr.nodeValue = '' + value; + } else { + attr = this.ownerDocument._createAttribute(name); + attr.value = attr.nodeValue = '' + value; + this.setAttributeNode(attr); + } + }, + removeAttribute: function (name) { + var attr = this.getAttributeNode(name); + attr && this.removeAttributeNode(attr); + }, + setAttributeNode: function (newAttr) { + return this.attributes.setNamedItem(newAttr); + }, + setAttributeNodeNS: function (newAttr) { + return this.attributes.setNamedItemNS(newAttr); + }, + removeAttributeNode: function (oldAttr) { + //console.log(this == oldAttr.ownerElement) + return this.attributes.removeNamedItem(oldAttr.nodeName); + }, + //get real attribute name,and remove it by removeAttributeNode + removeAttributeNS: function (namespaceURI, localName) { + var old = this.getAttributeNodeNS(namespaceURI, localName); + old && this.removeAttributeNode(old); + }, + + hasAttributeNS: function (namespaceURI, localName) { + return this.getAttributeNodeNS(namespaceURI, localName) != null; + }, + /** + * Returns element’s attribute whose namespace is `namespaceURI` and local name is + * `localName`, + * or `null` if there is no such attribute. + * + * @param {string} namespaceURI + * @param {string} localName + * @returns {string | null} + */ + getAttributeNS: function (namespaceURI, localName) { + var attr = this.getAttributeNodeNS(namespaceURI, localName); + return attr ? attr.value : null; + }, + /** + * Sets the value of element’s attribute whose namespace is `namespaceURI` and local name is + * `localName` to value. + * + * @param {string} namespaceURI + * @param {string} qualifiedName + * @param {string} value + * @see https://dom.spec.whatwg.org/#dom-element-setattributens + */ + setAttributeNS: function (namespaceURI, qualifiedName, value) { + var validated = validateAndExtract(namespaceURI, qualifiedName); + var localName = validated[2]; + var attr = this.getAttributeNodeNS(namespaceURI, localName); + if (attr) { + attr.value = attr.nodeValue = '' + value; + } else { + attr = this.ownerDocument.createAttributeNS(namespaceURI, qualifiedName); + attr.value = attr.nodeValue = '' + value; + this.setAttributeNode(attr); + } + }, + getAttributeNodeNS: function (namespaceURI, localName) { + return this.attributes.getNamedItemNS(namespaceURI, localName); + }, + + /** + * Returns a LiveNodeList of all child elements which have **all** of the given class name(s). + * + * Returns an empty list if `classNames` is an empty string or only contains HTML white space + * characters. + * + * Warning: This returns a live LiveNodeList. + * Changes in the DOM will reflect in the array as the changes occur. + * If an element selected by this array no longer qualifies for the selector, + * it will automatically be removed. Be aware of this for iteration purposes. + * + * @param {string} classNames + * Is a string representing the class name(s) to match; multiple class names are separated by + * (ASCII-)whitespace. + * @see https://developer.mozilla.org/en-US/docs/Web/API/Element/getElementsByClassName + * @see https://developer.mozilla.org/en-US/docs/Web/API/Document/getElementsByClassName + * @see https://dom.spec.whatwg.org/#concept-getelementsbyclassname + */ + getElementsByClassName: function (classNames) { + var classNamesSet = toOrderedSet(classNames); + return new LiveNodeList(this, function (base) { + var ls = []; + if (classNamesSet.length > 0) { + _visitNode(base, function (node) { + if (node !== base && node.nodeType === ELEMENT_NODE) { + var nodeClassNames = node.getAttribute('class'); + // can be null if the attribute does not exist + if (nodeClassNames) { + // before splitting and iterating just compare them for the most common case + var matches = classNames === nodeClassNames; + if (!matches) { + var nodeClassNamesSet = toOrderedSet(nodeClassNames); + matches = classNamesSet.every(arrayIncludes(nodeClassNamesSet)); + } + if (matches) { + ls.push(node); + } + } + } + }); + } + return ls; + }); + }, + + /** + * Returns a LiveNodeList of elements with the given qualifiedName. + * Searching for all descendants can be done by passing `*` as `qualifiedName`. + * + * All descendants of the specified element are searched, but not the element itself. + * The returned list is live, which means it updates itself with the DOM tree automatically. + * Therefore, there is no need to call `Element.getElementsByTagName()` + * with the same element and arguments repeatedly if the DOM changes in between calls. + * + * When called on an HTML element in an HTML document, + * `getElementsByTagName` lower-cases the argument before searching for it. + * This is undesirable when trying to match camel-cased SVG elements (such as + * ``) in an HTML document. + * Instead, use `Element.getElementsByTagNameNS()`, + * which preserves the capitalization of the tag name. + * + * `Element.getElementsByTagName` is similar to `Document.getElementsByTagName()`, + * except that it only searches for elements that are descendants of the specified element. + * + * @param {string} qualifiedName + * @returns {LiveNodeList} + * @see https://developer.mozilla.org/en-US/docs/Web/API/Element/getElementsByTagName + * @see https://dom.spec.whatwg.org/#concept-getelementsbytagname + */ + getElementsByTagName: function (qualifiedName) { + var isHTMLDocument = (this.nodeType === DOCUMENT_NODE ? this : this.ownerDocument).type === 'html'; + var lowerQualifiedName = qualifiedName.toLowerCase(); + return new LiveNodeList(this, function (base) { + var ls = []; + _visitNode(base, function (node) { + if (node === base || node.nodeType !== ELEMENT_NODE) { + return; + } + if (qualifiedName === '*') { + ls.push(node); + } else { + var nodeQualifiedName = node.getQualifiedName(); + var matchingQName = isHTMLDocument && node.namespaceURI === NAMESPACE.HTML ? lowerQualifiedName : qualifiedName; + if (nodeQualifiedName === matchingQName) { + ls.push(node); + } + } + }); + return ls; + }); + }, + getElementsByTagNameNS: function (namespaceURI, localName) { + return new LiveNodeList(this, function (base) { + var ls = []; + _visitNode(base, function (node) { + if ( + node !== base && + node.nodeType === ELEMENT_NODE && + (namespaceURI === '*' || node.namespaceURI === namespaceURI) && + (localName === '*' || node.localName == localName) + ) { + ls.push(node); + } + }); + return ls; + }); + }, +}; +Document.prototype.getElementsByClassName = Element.prototype.getElementsByClassName; +Document.prototype.getElementsByTagName = Element.prototype.getElementsByTagName; +Document.prototype.getElementsByTagNameNS = Element.prototype.getElementsByTagNameNS; + +_extends(Element, Node); +function Attr(symbol) { + checkSymbol(symbol); + + this.namespaceURI = null; + this.prefix = null; + this.ownerElement = null; +} +Attr.prototype.nodeType = ATTRIBUTE_NODE; +_extends(Attr, Node); + +function CharacterData(symbol) { + checkSymbol(symbol); +} +CharacterData.prototype = { + data: '', + substringData: function (offset, count) { + return this.data.substring(offset, offset + count); + }, + appendData: function (text) { + text = this.data + text; + this.nodeValue = this.data = text; + this.length = text.length; + }, + insertData: function (offset, text) { + this.replaceData(offset, 0, text); + }, + deleteData: function (offset, count) { + this.replaceData(offset, count, ''); + }, + replaceData: function (offset, count, text) { + var start = this.data.substring(0, offset); + var end = this.data.substring(offset + count); + text = start + text + end; + this.nodeValue = this.data = text; + this.length = text.length; + }, +}; +_extends(CharacterData, Node); +function Text(symbol) { + checkSymbol(symbol); +} +Text.prototype = { + nodeName: '#text', + nodeType: TEXT_NODE, + splitText: function (offset) { + var text = this.data; + var newText = text.substring(offset); + text = text.substring(0, offset); + this.data = this.nodeValue = text; + this.length = text.length; + var newNode = this.ownerDocument.createTextNode(newText); + if (this.parentNode) { + this.parentNode.insertBefore(newNode, this.nextSibling); + } + return newNode; + }, +}; +_extends(Text, CharacterData); +function Comment(symbol) { + checkSymbol(symbol); +} +Comment.prototype = { + nodeName: '#comment', + nodeType: COMMENT_NODE, +}; +_extends(Comment, CharacterData); + +function CDATASection(symbol) { + checkSymbol(symbol); +} +CDATASection.prototype = { + nodeName: '#cdata-section', + nodeType: CDATA_SECTION_NODE, +}; +_extends(CDATASection, Text); + +/** + * @class DocumentType + * @augments Node + * @property {string} name + * The doctype name, stored verbatim. Declared `readonly` by the WHATWG DOM spec; xmldom does + * not enforce this constraint — direct property writes succeed and the written value is + * serialized verbatim. When serialized with `requireWellFormed: true`, the serializer + * validates the value against the XML `Name` production and throws `InvalidStateError` if it + * does not match. + * @property {string} publicId + * The external subset public identifier, stored verbatim (including surrounding quotes). + * Declared `readonly` by the WHATWG DOM spec; xmldom does not enforce this constraint — + * direct property writes succeed and the written value is serialized verbatim. + * When serialized with `requireWellFormed: true`, the serializer validates the value against + * the XML `PubidLiteral` production and throws `InvalidStateError` if it does not match. + * @property {string} systemId + * The external subset system identifier, stored verbatim (including surrounding quotes). + * Declared `readonly` by the WHATWG DOM spec; xmldom does not enforce this constraint — + * direct property writes succeed and the written value is serialized verbatim. + * When serialized with `requireWellFormed: true`, the serializer validates the value against + * the XML `SystemLiteral` production and throws `InvalidStateError` if it does not match. + * @property {string} internalSubset + * The internal subset string (the raw content between `[` and `]`), or an empty string. + * Declared `readonly` by the WHATWG DOM spec; xmldom does not enforce this constraint — + * direct property writes succeed and the written value is serialized verbatim. + * When serialized with `requireWellFormed: true`, the serializer throws `InvalidStateError` + * if the value contains `"]>"`. + * @see https://developer.mozilla.org/docs/Web/API/DocumentType MDN + * @see https://dom.spec.whatwg.org/#interface-documenttype WHATWG DOM + * @prettierignore + */ +function DocumentType(symbol) { + checkSymbol(symbol); +} +DocumentType.prototype.nodeType = DOCUMENT_TYPE_NODE; +_extends(DocumentType, Node); + +function Notation(symbol) { + checkSymbol(symbol); +} +Notation.prototype.nodeType = NOTATION_NODE; +_extends(Notation, Node); + +function Entity(symbol) { + checkSymbol(symbol); +} +Entity.prototype.nodeType = ENTITY_NODE; +_extends(Entity, Node); + +/** + * Represents an EntityReference node, serialized as `&nodeName;`. + * + * `nodeName` is the referenced entity's name, stored verbatim. When serialized with + * `requireWellFormed: true`, the serializer validates `nodeName` against the XML `Name` + * production and throws `InvalidStateError` if it does not match; without that option the name + * is emitted verbatim between `&` and `;`. + * + * __This implementation differs from the specification:__ xmldom does not expand entities — + * the parser resolves entity references inline and never constructs `EntityReference` nodes, + * so the only producer is {@link Document#createEntityReference}. + * + * @class + * @see https://www.w3.org/TR/xml/#NT-Name + */ +function EntityReference(symbol) { + checkSymbol(symbol); +} +EntityReference.prototype.nodeType = ENTITY_REFERENCE_NODE; +_extends(EntityReference, Node); + +function DocumentFragment(symbol) { + checkSymbol(symbol); +} +DocumentFragment.prototype.nodeName = '#document-fragment'; +DocumentFragment.prototype.nodeType = DOCUMENT_FRAGMENT_NODE; +_extends(DocumentFragment, Node); + +function ProcessingInstruction(symbol) { + checkSymbol(symbol); +} +ProcessingInstruction.prototype.nodeType = PROCESSING_INSTRUCTION_NODE; +_extends(ProcessingInstruction, CharacterData); +function XMLSerializer() {} +/** + * Returns the result of serializing `node` to XML. + * + * When `options.requireWellFormed` is `true`, the serializer throws `InvalidStateError` for + * content that would produce ill-formed XML (e.g. CDATASection data containing `"]]>"`, Text + * data containing characters outside the XML Char production, or a Document with no + * `documentElement`). + * + * When `options.splitCDATASections` is `false`, CDATASection data is emitted verbatim even + * when it contains `"]]>"`. When `true` (the default), `"]]>"` sequences are split across + * concatenated CDATA sections — this behavior is **deprecated** and will be removed in the + * next breaking release. Callers should migrate to `{ requireWellFormed: true }`, which throws + * `InvalidStateError` instead of transforming. + * + * __This implementation differs from the specification:__ - CDATASection serialization is not + * specified by W3C DOM Parsing or WHATWG DOM Parsing (see + * {@link https://github.com/w3c/DOM-Parsing/issues/38 w3c/DOM-Parsing#38}). + * When `splitCDATASections` is `true` (the default), `"]]>"` sequences in CDATASection data + * are split across concatenated CDATA sections — this mechanism is derived from DOM Level 3 + * Core and is **deprecated**. The split mechanics will be removed in the next breaking + * release. Callers that rely on this behavior should migrate to `{ requireWellFormed: true }`. + * - W3C DOM Parsing §3.2.1.1 requires well-formedness checks on Element `localName`s, + * prefixes, + * and attribute serialization (duplicate attributes, namespace declarations, attribute value + * characters) when `requireWellFormed` is `true`. Element and attribute qualified names (which + * cover the namespace prefix) are validated against the XML `QName` production; the remaining + * §3.2.1.1 checks (duplicate attributes, namespace-declaration consistency) and creation-time + * name validation are **not implemented** in this release — see the tracking issue filed + * against the next breaking milestone. + * + * @param {Node} node + * @param {Object | function} [options] + * Options object, or a legacy nodeFilter function (backward compatible). + * @param {boolean} [options.requireWellFormed=false] + * When `true`, throws `InvalidStateError` for content that would produce ill-formed XML. + * @param {boolean} [options.splitCDATASections=true] + * When `true` (default), splits `"]]>"` sequences in CDATASection data across concatenated + * CDATA sections. **Deprecated** — will be removed in the next breaking release. + * @param {function} [options.nodeFilter] + * A filter function applied to each node before serialization. + * @returns {string} + * @throws {DOMException} + * With name `InvalidStateError` when `requireWellFormed` is `true` and any of the following + * conditions hold: + * - an Element's qualified name (including any namespace prefix) is not a valid XML QName + * - an attribute's qualified name (including a synthesized `xmlns:` namespace declaration) is + * not a valid XML QName + * - CDATASection data contains `"]]>"` + * - Text data contains characters outside the XML Char production + * - a Comment node's data contains `--` anywhere or ends with `-` + * - a ProcessingInstruction's target is not a valid XML `NCName` (a `Name` with no colon) or is + * an ASCII case-insensitive match for `"xml"`, or its data contains `?>` or characters outside + * the XML Char production + * - a DocumentType's `name` is not a valid XML `Name` (XML 1.0 production [5]) + * - a DocumentType's `publicId` is non-empty and does not match the XML `PubidLiteral` + * production (W3C DOM Parsing §3.2.1.3; XML 1.0 production [12]) + * - a DocumentType's `systemId` is non-empty and does not match the XML `SystemLiteral` + * production (W3C DOM Parsing §3.2.1.3; XML 1.0 production [11]) + * - a DocumentType's `internalSubset` contains `"]>"` + * - an EntityReference's `nodeName` is not a valid XML `Name` (XML 1.0 production [5]) + * - the Document has no `documentElement` + * @see https://developer.mozilla.org/docs/Web/API/XMLSerializer/serializeToString + * @see https://html.spec.whatwg.org/#dom-xmlserializer-serializetostring + * @see https://github.com/w3c/DOM-Parsing/issues/84 + * @prettierignore + */ +XMLSerializer.prototype.serializeToString = function (node, options) { + return nodeSerializeToString.call(node, options); +}; +Node.prototype.toString = nodeSerializeToString; +function nodeSerializeToString(options) { + // Normalize the user-supplied options into a single internal opts object so that the + // internal serializer always works with a consistent shape rather than positional flags. + var opts; + if (typeof options === 'function') { + opts = { requireWellFormed: false, splitCDATASections: true, nodeFilter: options }; + } else if (options != null) { + opts = { + requireWellFormed: !!options.requireWellFormed, + splitCDATASections: options.splitCDATASections !== false, + nodeFilter: options.nodeFilter || null, + }; + } else { + opts = { requireWellFormed: false, splitCDATASections: true, nodeFilter: null }; + } + var buf = []; + var refNode = (this.nodeType === DOCUMENT_NODE && this.documentElement) || this; + var prefix = refNode.prefix; + var uri = refNode.namespaceURI; + + if (uri && prefix == null) { + var prefix = refNode.lookupPrefix(uri); + if (prefix == null) { + var visibleNamespaces = [ + { namespace: uri, prefix: null }, + //{namespace:uri,prefix:''} + ]; + } + } + serializeToString(this, buf, visibleNamespaces, opts); + return buf.join(''); +} + +function needNamespaceDefine(node, isHTML, visibleNamespaces) { + var prefix = node.prefix || ''; + var uri = node.namespaceURI; + // According to [Namespaces in XML 1.0](https://www.w3.org/TR/REC-xml-names/#ns-using) , + // and more specifically https://www.w3.org/TR/REC-xml-names/#nsc-NoPrefixUndecl : + // > In a namespace declaration for a prefix [...], the attribute value MUST NOT be empty. + // in a similar manner [Namespaces in XML 1.1](https://www.w3.org/TR/xml-names11/#ns-using) + // and more specifically https://www.w3.org/TR/xml-names11/#nsc-NSDeclared : + // > [...] Furthermore, the attribute value [...] must not be an empty string. + // so serializing empty namespace value like xmlns:ds="" would produce an invalid XML document. + if (!uri) { + return false; + } + if ((prefix === 'xml' && uri === NAMESPACE.XML) || uri === NAMESPACE.XMLNS) { + return false; + } + + var i = visibleNamespaces.length; + while (i--) { + var ns = visibleNamespaces[i]; + // get namespace prefix + if (ns.prefix === prefix) { + return ns.namespace !== uri; + } + } + return true; +} +/** + * Literal whitespace other than space that appear in attribute values are serialized as + * their entity references, so they will be preserved. + * (In contrast to whitespace literals in the input which are normalized to spaces). + * + * Well-formed constraint: No < in Attribute Values: + * > The replacement text of any entity referred to directly or indirectly + * > in an attribute value must not contain a <. + * + * @see https://www.w3.org/TR/xml11/#CleanAttrVals + * @see https://www.w3.org/TR/xml11/#NT-AttValue + * @see https://www.w3.org/TR/xml11/#AVNormalize + * @see https://w3c.github.io/DOM-Parsing/#serializing-an-element-s-attributes + * @prettierignore + */ +function addSerializedAttribute(buf, qualifiedName, value, requireWellFormed) { + if (requireWellFormed && !g.QName_exact.test(qualifiedName)) { + throw new DOMException( + 'The attribute name "' + qualifiedName + '" is not a valid XML QName', + DOMExceptionName.InvalidStateError + ); + } + buf.push(' ', qualifiedName, '="', value.replace(/[<>&"\t\n\r]/g, _xmlEncoder), '"'); +} + +function serializeToString(node, buf, visibleNamespaces, opts) { + if (!visibleNamespaces) { + visibleNamespaces = []; + } + var nodeFilter = opts.nodeFilter; + var requireWellFormed = opts.requireWellFormed; + var splitCDATASections = opts.splitCDATASections; + var doc = node.nodeType === DOCUMENT_NODE ? node : node.ownerDocument; + var isHTML = doc.type === 'html'; + + walkDOM( + node, + { ns: visibleNamespaces }, + { + enter: function (n, ctx) { + var namespaces = ctx.ns; + + if (nodeFilter) { + n = nodeFilter(n); + if (n) { + if (typeof n == 'string') { + buf.push(n); + return null; + } + } else { + return null; + } + } + + switch (n.nodeType) { + case ELEMENT_NODE: + var attrs = n.attributes; + var len = attrs.length; + var nodeName = n.tagName; + + var prefixedNodeName = nodeName; + if (!isHTML && !n.prefix && n.namespaceURI) { + var defaultNS; + // lookup current default ns from `xmlns` attribute + for (var ai = 0; ai < attrs.length; ai++) { + if (attrs.item(ai).name === 'xmlns') { + defaultNS = attrs.item(ai).value; + break; + } + } + if (!defaultNS) { + // lookup current default ns in visibleNamespaces + for (var nsi = namespaces.length - 1; nsi >= 0; nsi--) { + var nsEntry = namespaces[nsi]; + if (nsEntry.prefix === '' && nsEntry.namespace === n.namespaceURI) { + defaultNS = nsEntry.namespace; + break; + } + } + } + if (defaultNS !== n.namespaceURI) { + for (var nsi = namespaces.length - 1; nsi >= 0; nsi--) { + var nsEntry = namespaces[nsi]; + if (nsEntry.namespace === n.namespaceURI) { + if (nsEntry.prefix) { + prefixedNodeName = nsEntry.prefix + ':' + nodeName; + } + break; + } + } + } + } + + if (requireWellFormed && !g.QName_exact.test(prefixedNodeName)) { + throw new DOMException( + 'The element name "' + prefixedNodeName + '" is not a valid XML QName', + DOMExceptionName.InvalidStateError + ); + } + + buf.push('<', prefixedNodeName); + + // Build a fresh namespace snapshot for this element's children. + // The slice prevents sibling elements from inheriting each other's declarations. + var childNamespaces = namespaces.slice(); + + for (var i = 0; i < len; i++) { + // add namespaces for attributes + var attr = attrs.item(i); + if (attr.prefix == 'xmlns') { + childNamespaces.push({ + prefix: attr.localName, + namespace: attr.value, + }); + } else if (attr.nodeName == 'xmlns') { + childNamespaces.push({ prefix: '', namespace: attr.value }); + } + } + + for (var i = 0; i < len; i++) { + var attr = attrs.item(i); + if (needNamespaceDefine(attr, isHTML, childNamespaces)) { + var attrPrefix = attr.prefix || ''; + var uri = attr.namespaceURI; + addSerializedAttribute(buf, attrPrefix ? 'xmlns:' + attrPrefix : 'xmlns', uri, requireWellFormed); + childNamespaces.push({ prefix: attrPrefix, namespace: uri }); + } + // Apply nodeFilter and serialize the attribute. + var filteredAttr = nodeFilter ? nodeFilter(attr) : attr; + if (filteredAttr) { + if (typeof filteredAttr === 'string') { + buf.push(filteredAttr); + } else { + addSerializedAttribute(buf, filteredAttr.name, filteredAttr.value, requireWellFormed); + } + } + } + + // add namespace for current node + if (nodeName === prefixedNodeName && needNamespaceDefine(n, isHTML, childNamespaces)) { + var nodePrefix = n.prefix || ''; + var uri = n.namespaceURI; + addSerializedAttribute(buf, nodePrefix ? 'xmlns:' + nodePrefix : 'xmlns', uri, requireWellFormed); + childNamespaces.push({ prefix: nodePrefix, namespace: uri }); + } + + // in XML elements can be closed when they have no children + var canCloseTag = !n.firstChild; + if (canCloseTag && (isHTML || n.namespaceURI === NAMESPACE.HTML)) { + // in HTML (doc or ns) only void elements can be closed right away + canCloseTag = isHTMLVoidElement(nodeName); + } + if (canCloseTag) { + buf.push('/>'); + // Self-closing: no children and no closing tag needed from exit. + return null; + } + + buf.push('>'); + + // HTML raw text elements: serialize children as raw data without further descent. + if (isHTML && isHTMLRawTextElement(nodeName)) { + var child = n.firstChild; + while (child) { + if (child.data) { + buf.push(child.data); + } else { + serializeToString(child, buf, childNamespaces.slice(), opts); + } + child = child.nextSibling; + } + buf.push(''); + // Children handled manually above; prevent walkDOM from also traversing them. + return null; + } + + // Return child context so walkDOM descends; exit will emit the closing tag. + return { ns: childNamespaces, tag: prefixedNodeName }; + case DOCUMENT_NODE: + case DOCUMENT_FRAGMENT_NODE: + if (requireWellFormed && n.nodeType === DOCUMENT_NODE && n.documentElement == null) { + throw new DOMException('The Document has no documentElement', DOMExceptionName.InvalidStateError); + } + // Pass namespaces through; each child element will slice independently. + return { ns: namespaces }; + case ATTRIBUTE_NODE: + addSerializedAttribute(buf, n.name, n.value, requireWellFormed); + return null; + case TEXT_NODE: + /* + * The ampersand character (&) and the left angle bracket (<) must not appear in their literal form, + * except when used as markup delimiters, or within a comment, a processing instruction, + * or a CDATA section. + * If they are needed elsewhere, they must be escaped using either numeric character + * references or the strings `&` and `<` respectively. + * The right angle bracket (>) may be represented using the string " > ", + * and must, for compatibility, be escaped using either `>`, + * or a character reference when it appears in the string `]]>` in content, + * when that string is not marking the end of a CDATA section. + * + * In the content of elements, character data is any string of characters which does not + * contain the start-delimiter of any markup and does not include the CDATA-section-close + * delimiter, `]]>`. + * + * @see https://www.w3.org/TR/xml/#NT-CharData + * @see https://w3c.github.io/DOM-Parsing/#xml-serializing-a-text-node + */ + if (requireWellFormed && g.InvalidChar.test(n.data)) { + throw new DOMException( + 'The Text node data contains characters outside the XML Char production', + DOMExceptionName.InvalidStateError + ); + } + buf.push(n.data.replace(/[<&>]/g, _xmlEncoder)); + return null; + case CDATA_SECTION_NODE: + if (requireWellFormed && n.data.indexOf(']]>') !== -1) { + throw new DOMException('The CDATASection data contains "]]>"', DOMExceptionName.InvalidStateError); + } + if (splitCDATASections) { + buf.push(g.CDATA_START, n.data.replace(/]]>/g, ']]]]>'), g.CDATA_END); + } else { + buf.push(g.CDATA_START, n.data, g.CDATA_END); + } + return null; + case COMMENT_NODE: + if (requireWellFormed) { + if (g.InvalidChar.test(n.data)) { + throw new DOMException( + 'The comment node data contains characters outside the XML Char production', + DOMExceptionName.InvalidStateError + ); + } + if (n.data.indexOf('--') !== -1 || n.data[n.data.length - 1] === '-') { + throw new DOMException( + 'The comment node data contains "--" or ends with "-"', + DOMExceptionName.InvalidStateError + ); + } + } + buf.push(g.COMMENT_START, n.data, g.COMMENT_END); + return null; + case DOCUMENT_TYPE_NODE: + var pubid = n.publicId; + var sysid = n.systemId; + if (requireWellFormed) { + if (!g.Name_exact.test(n.name)) { + throw new DOMException( + 'The doctype name "' + n.name + '" is not a valid XML Name', + DOMExceptionName.InvalidStateError + ); + } + if (pubid && !g.PubidLiteral_match.test(pubid)) { + throw new DOMException('DocumentType publicId is not a valid PubidLiteral', DOMExceptionName.InvalidStateError); + } + if (sysid && sysid !== '.' && !g.SystemLiteral_match.test(sysid)) { + throw new DOMException('DocumentType systemId is not a valid SystemLiteral', DOMExceptionName.InvalidStateError); + } + if (n.internalSubset && n.internalSubset.indexOf(']>') !== -1) { + throw new DOMException('DocumentType internalSubset contains "]>"', DOMExceptionName.InvalidStateError); + } + } + buf.push(g.DOCTYPE_DECL_START, ' ', n.name); + if (pubid) { + buf.push(' ', g.PUBLIC, ' ', pubid); + if (sysid && sysid !== '.') { + buf.push(' ', sysid); + } + } else if (sysid && sysid !== '.') { + buf.push(' ', g.SYSTEM, ' ', sysid); + } + if (n.internalSubset) { + buf.push(' [', n.internalSubset, ']'); + } + buf.push('>'); + return null; + case PROCESSING_INSTRUCTION_NODE: + if (requireWellFormed) { + if (!g.NCName_exact.test(n.target) || n.target.toLowerCase() === 'xml') { + throw new DOMException( + 'The processing instruction target "' + n.target + '" is not a valid XML NCName or is reserved', + DOMExceptionName.InvalidStateError + ); + } + if (g.InvalidChar.test(n.data)) { + throw new DOMException( + 'The ProcessingInstruction data contains characters outside the XML Char production', + DOMExceptionName.InvalidStateError + ); + } + if (n.data.indexOf('?>') !== -1) { + throw new DOMException('The ProcessingInstruction data contains "?>"', DOMExceptionName.InvalidStateError); + } + } + buf.push(''); + return null; + case ENTITY_REFERENCE_NODE: + if (requireWellFormed && !g.Name_exact.test(n.nodeName)) { + throw new DOMException( + 'The entity reference name "' + n.nodeName + '" is not a valid XML Name', + DOMExceptionName.InvalidStateError + ); + } + buf.push('&', n.nodeName, ';'); + return null; + //case ENTITY_NODE: + //case NOTATION_NODE: + default: + buf.push('??', n.nodeName); + return null; + } + }, + exit: function (n, childCtx) { + // Emit the closing tag for elements that were opened (not self-closed, not raw text). + if (childCtx && childCtx.tag) { + buf.push(''); + } + }, + } + ); +} +/** + * Imports a node from a different document into `doc`, creating a new copy. + * Delegates to {@link walkDOM} for traversal. Each node in the subtree is shallow-cloned, + * stamped with `doc` as its `ownerDocument`, and detached (`parentNode` set to `null`). + * Children are imported recursively when `deep` is `true`; for {@link Attr} nodes `deep` is + * always forced to `true` + * because an attribute's value lives in a child text node. + * + * @param {Document} doc + * The document that will own the imported node. + * @param {Node} node + * The node to import. + * @param {boolean} deep + * If `true`, descendants are imported recursively. + * @returns {Node} + * The newly imported node, now owned by `doc`. + */ +function importNode(doc, node, deep) { + var destRoot; + walkDOM(node, null, { + enter: function (srcNode, destParent) { + // Shallow-clone the node and stamp it into the target document. + var destNode = srcNode.cloneNode(false); + destNode.ownerDocument = doc; + destNode.parentNode = null; + // capture as the root of the imported subtree or attach to parent. + if (destParent === null) { + destRoot = destNode; + } else { + destParent.appendChild(destNode); + } + // ATTRIBUTE_NODE must always be imported deeply: its value lives in a child text node. + var shouldDeep = srcNode.nodeType === ATTRIBUTE_NODE || deep; + return shouldDeep ? destNode : null; + }, + }); + return destRoot; +} + +/** + * Creates a copy of a node from an existing one. + * + * @param {Document} doc + * The Document object representing the document that the new node will belong to. + * @param {Node} node + * The node to clone. + * @param {boolean} deep + * If true, the contents of the node are recursively copied. + * If false, only the node itself (and its attributes, if it is an element) are copied. + * @returns {Node} + * Returns the newly created copy of the node. + * @throws {DOMException} + * May throw a DOMException if operations within setAttributeNode or appendChild (which are + * potentially invoked in this function) do not meet their specific constraints. + */ +function cloneNode(doc, node, deep) { + var destRoot; + walkDOM(node, null, { + enter: function (srcNode, destParent) { + // 1. Create a blank node of the same type and copy all scalar own properties. + var destNode = new srcNode.constructor(PDC); + for (var n in srcNode) { + if (hasOwn(srcNode, n)) { + var v = srcNode[n]; + if (typeof v != 'object') { + if (v != destNode[n]) { + destNode[n] = v; + } + } + } + } + if (srcNode.childNodes) { + destNode.childNodes = new NodeList(); + } + destNode.ownerDocument = doc; + // 2. Handle node-type-specific setup. + // Attributes are not DOM children, so they are cloned inline here + // rather than by walkDOM descent. + // ATTRIBUTE_NODE forces deep=true so its own children are walked. + var shouldDeep = deep; + switch (destNode.nodeType) { + case ELEMENT_NODE: + var attrs = srcNode.attributes; + var attrs2 = (destNode.attributes = new NamedNodeMap()); + var len = attrs.length; + attrs2._ownerElement = destNode; + for (var i = 0; i < len; i++) { + destNode.setAttributeNode(cloneNode(doc, attrs.item(i), true)); + } + break; + case ATTRIBUTE_NODE: + shouldDeep = true; + } + // 3. Attach to parent, or capture as the root of the cloned subtree. + if (destParent !== null) { + destParent.appendChild(destNode); + } else { + destRoot = destNode; + } + // 4. Return destNode as the context for children (causes walkDOM to descend), + // or null to skip children (shallow clone). + return shouldDeep ? destNode : null; + }, + }); + return destRoot; +} + +function __set__(object, key, value) { + object[key] = value; +} + +// Returns a new array of direct Element children. +// Passed to LiveNodeList to implement ParentNode.children. +// https://dom.spec.whatwg.org/#dom-parentnode-children +function childrenRefresh(node) { + var ls = []; + var child = node.firstChild; + while (child) { + if (child.nodeType === ELEMENT_NODE) { + ls.push(child); + } + child = child.nextSibling; + } + return ls; +} + +//do dynamic +try { + if (Object.defineProperty) { + Object.defineProperty(LiveNodeList.prototype, 'length', { + get: function () { + _updateLiveList(this); + return this.$$length; + }, + }); + + /** + * The text content of this node and its descendants. + * + * For {@link Element} and {@link DocumentFragment} nodes, returns the concatenation of the + * `nodeValue` of every descendant text node, excluding processing instruction and comment + * nodes. For all other node types, returns `nodeValue`. + * + * Setting `textContent` on an element or document fragment replaces all child nodes with a + * single text node; on other nodes it sets `data`, `value`, and `nodeValue` directly. + * + * @type {string | null} + * @see {@link https://dom.spec.whatwg.org/#dom-node-textcontent} + */ + Object.defineProperty(Node.prototype, 'textContent', { + get: function () { + if (this.nodeType === ELEMENT_NODE || this.nodeType === DOCUMENT_FRAGMENT_NODE) { + var buf = []; + walkDOM(this, null, { + enter: function (n) { + if (n.nodeType === ELEMENT_NODE || n.nodeType === DOCUMENT_FRAGMENT_NODE) { + return true; // enter children + } + if (n.nodeType === PROCESSING_INSTRUCTION_NODE || n.nodeType === COMMENT_NODE) { + return null; // excluded from text content + } + buf.push(n.nodeValue); + }, + }); + return buf.join(''); + } + return this.nodeValue; + }, + + set: function (data) { + switch (this.nodeType) { + case ELEMENT_NODE: + case DOCUMENT_FRAGMENT_NODE: + while (this.firstChild) { + this.removeChild(this.firstChild); + } + if (data || String(data)) { + this.appendChild(this.ownerDocument.createTextNode(data)); + } + break; + + default: + this.data = data; + this.value = data; + this.nodeValue = data; + } + }, + }); + + Object.defineProperty(CharacterData.prototype, 'data', { + get: function () { + return this._data != null ? this._data : ''; + }, + set: function (v) { + this._data = v; + this.length = typeof v === 'string' ? v.length : 0; + }, + }); + + Object.defineProperty(CharacterData.prototype, 'nodeValue', { + get: function () { + return this.data; + }, + set: function (v) { + this.data = v; + }, + enumerable: true, + configurable: true, + }); + + Object.defineProperty(Element.prototype, 'children', { + get: function () { + return new LiveNodeList(this, childrenRefresh); + }, + }); + Object.defineProperty(Document.prototype, 'children', { + get: function () { + return new LiveNodeList(this, childrenRefresh); + }, + }); + Object.defineProperty(DocumentFragment.prototype, 'children', { + get: function () { + return new LiveNodeList(this, childrenRefresh); + }, + }); + + __set__ = function (object, key, value) { + //console.log(value) + object['$$' + key] = value; + }; + } +} catch (e) { + //ie8 +} + +exports._updateLiveList = _updateLiveList; +exports.Attr = Attr; +exports.CDATASection = CDATASection; +exports.CharacterData = CharacterData; +exports.Comment = Comment; +exports.Document = Document; +exports.DocumentFragment = DocumentFragment; +exports.DocumentType = DocumentType; +exports.DOMImplementation = DOMImplementation; +exports.Element = Element; +exports.Entity = Entity; +exports.EntityReference = EntityReference; +exports.LiveNodeList = LiveNodeList; +exports.NamedNodeMap = NamedNodeMap; +exports.Node = Node; +exports.NodeList = NodeList; +exports.Notation = Notation; +exports.Text = Text; +exports.ProcessingInstruction = ProcessingInstruction; +exports.walkDOM = walkDOM; +exports.XMLSerializer = XMLSerializer; diff --git a/node_modules/@xmldom/xmldom/lib/entities.js b/node_modules/@xmldom/xmldom/lib/entities.js new file mode 100644 index 000000000..5684a92c4 --- /dev/null +++ b/node_modules/@xmldom/xmldom/lib/entities.js @@ -0,0 +1,2171 @@ +'use strict'; + +var freeze = require('./conventions').freeze; + +/** + * The entities that are predefined in every XML document. + * + * @see https://www.w3.org/TR/2006/REC-xml11-20060816/#sec-predefined-ent W3C XML 1.1 + * @see https://www.w3.org/TR/2008/REC-xml-20081126/#sec-predefined-ent W3C XML 1.0 + * @see https://en.wikipedia.org/wiki/List_of_XML_and_HTML_character_entity_references#Predefined_entities_in_XML + * Wikipedia + */ +exports.XML_ENTITIES = freeze({ + amp: '&', + apos: "'", + gt: '>', + lt: '<', + quot: '"', +}); + +/** + * A map of all entities that are detected in an HTML document. + * They contain all entries from `XML_ENTITIES`. + * + * @see {@link XML_ENTITIES} + * @see {@link DOMParser.parseFromString} + * @see {@link DOMImplementation.prototype.createHTMLDocument} + * @see https://html.spec.whatwg.org/#named-character-references WHATWG HTML(5) + * Spec + * @see https://html.spec.whatwg.org/entities.json JSON + * @see https://www.w3.org/TR/xml-entity-names/ W3C XML Entity Names + * @see https://www.w3.org/TR/html4/sgml/entities.html W3C HTML4/SGML + * @see https://en.wikipedia.org/wiki/List_of_XML_and_HTML_character_entity_references#Character_entity_references_in_HTML + * Wikipedia (HTML) + * @see https://en.wikipedia.org/wiki/List_of_XML_and_HTML_character_entity_references#Entities_representing_special_characters_in_XHTML + * Wikpedia (XHTML) + */ +exports.HTML_ENTITIES = freeze({ + Aacute: '\u00C1', + aacute: '\u00E1', + Abreve: '\u0102', + abreve: '\u0103', + ac: '\u223E', + acd: '\u223F', + acE: '\u223E\u0333', + Acirc: '\u00C2', + acirc: '\u00E2', + acute: '\u00B4', + Acy: '\u0410', + acy: '\u0430', + AElig: '\u00C6', + aelig: '\u00E6', + af: '\u2061', + Afr: '\uD835\uDD04', + afr: '\uD835\uDD1E', + Agrave: '\u00C0', + agrave: '\u00E0', + alefsym: '\u2135', + aleph: '\u2135', + Alpha: '\u0391', + alpha: '\u03B1', + Amacr: '\u0100', + amacr: '\u0101', + amalg: '\u2A3F', + AMP: '\u0026', + amp: '\u0026', + And: '\u2A53', + and: '\u2227', + andand: '\u2A55', + andd: '\u2A5C', + andslope: '\u2A58', + andv: '\u2A5A', + ang: '\u2220', + ange: '\u29A4', + angle: '\u2220', + angmsd: '\u2221', + angmsdaa: '\u29A8', + angmsdab: '\u29A9', + angmsdac: '\u29AA', + angmsdad: '\u29AB', + angmsdae: '\u29AC', + angmsdaf: '\u29AD', + angmsdag: '\u29AE', + angmsdah: '\u29AF', + angrt: '\u221F', + angrtvb: '\u22BE', + angrtvbd: '\u299D', + angsph: '\u2222', + angst: '\u00C5', + angzarr: '\u237C', + Aogon: '\u0104', + aogon: '\u0105', + Aopf: '\uD835\uDD38', + aopf: '\uD835\uDD52', + ap: '\u2248', + apacir: '\u2A6F', + apE: '\u2A70', + ape: '\u224A', + apid: '\u224B', + apos: '\u0027', + ApplyFunction: '\u2061', + approx: '\u2248', + approxeq: '\u224A', + Aring: '\u00C5', + aring: '\u00E5', + Ascr: '\uD835\uDC9C', + ascr: '\uD835\uDCB6', + Assign: '\u2254', + ast: '\u002A', + asymp: '\u2248', + asympeq: '\u224D', + Atilde: '\u00C3', + atilde: '\u00E3', + Auml: '\u00C4', + auml: '\u00E4', + awconint: '\u2233', + awint: '\u2A11', + backcong: '\u224C', + backepsilon: '\u03F6', + backprime: '\u2035', + backsim: '\u223D', + backsimeq: '\u22CD', + Backslash: '\u2216', + Barv: '\u2AE7', + barvee: '\u22BD', + Barwed: '\u2306', + barwed: '\u2305', + barwedge: '\u2305', + bbrk: '\u23B5', + bbrktbrk: '\u23B6', + bcong: '\u224C', + Bcy: '\u0411', + bcy: '\u0431', + bdquo: '\u201E', + becaus: '\u2235', + Because: '\u2235', + because: '\u2235', + bemptyv: '\u29B0', + bepsi: '\u03F6', + bernou: '\u212C', + Bernoullis: '\u212C', + Beta: '\u0392', + beta: '\u03B2', + beth: '\u2136', + between: '\u226C', + Bfr: '\uD835\uDD05', + bfr: '\uD835\uDD1F', + bigcap: '\u22C2', + bigcirc: '\u25EF', + bigcup: '\u22C3', + bigodot: '\u2A00', + bigoplus: '\u2A01', + bigotimes: '\u2A02', + bigsqcup: '\u2A06', + bigstar: '\u2605', + bigtriangledown: '\u25BD', + bigtriangleup: '\u25B3', + biguplus: '\u2A04', + bigvee: '\u22C1', + bigwedge: '\u22C0', + bkarow: '\u290D', + blacklozenge: '\u29EB', + blacksquare: '\u25AA', + blacktriangle: '\u25B4', + blacktriangledown: '\u25BE', + blacktriangleleft: '\u25C2', + blacktriangleright: '\u25B8', + blank: '\u2423', + blk12: '\u2592', + blk14: '\u2591', + blk34: '\u2593', + block: '\u2588', + bne: '\u003D\u20E5', + bnequiv: '\u2261\u20E5', + bNot: '\u2AED', + bnot: '\u2310', + Bopf: '\uD835\uDD39', + bopf: '\uD835\uDD53', + bot: '\u22A5', + bottom: '\u22A5', + bowtie: '\u22C8', + boxbox: '\u29C9', + boxDL: '\u2557', + boxDl: '\u2556', + boxdL: '\u2555', + boxdl: '\u2510', + boxDR: '\u2554', + boxDr: '\u2553', + boxdR: '\u2552', + boxdr: '\u250C', + boxH: '\u2550', + boxh: '\u2500', + boxHD: '\u2566', + boxHd: '\u2564', + boxhD: '\u2565', + boxhd: '\u252C', + boxHU: '\u2569', + boxHu: '\u2567', + boxhU: '\u2568', + boxhu: '\u2534', + boxminus: '\u229F', + boxplus: '\u229E', + boxtimes: '\u22A0', + boxUL: '\u255D', + boxUl: '\u255C', + boxuL: '\u255B', + boxul: '\u2518', + boxUR: '\u255A', + boxUr: '\u2559', + boxuR: '\u2558', + boxur: '\u2514', + boxV: '\u2551', + boxv: '\u2502', + boxVH: '\u256C', + boxVh: '\u256B', + boxvH: '\u256A', + boxvh: '\u253C', + boxVL: '\u2563', + boxVl: '\u2562', + boxvL: '\u2561', + boxvl: '\u2524', + boxVR: '\u2560', + boxVr: '\u255F', + boxvR: '\u255E', + boxvr: '\u251C', + bprime: '\u2035', + Breve: '\u02D8', + breve: '\u02D8', + brvbar: '\u00A6', + Bscr: '\u212C', + bscr: '\uD835\uDCB7', + bsemi: '\u204F', + bsim: '\u223D', + bsime: '\u22CD', + bsol: '\u005C', + bsolb: '\u29C5', + bsolhsub: '\u27C8', + bull: '\u2022', + bullet: '\u2022', + bump: '\u224E', + bumpE: '\u2AAE', + bumpe: '\u224F', + Bumpeq: '\u224E', + bumpeq: '\u224F', + Cacute: '\u0106', + cacute: '\u0107', + Cap: '\u22D2', + cap: '\u2229', + capand: '\u2A44', + capbrcup: '\u2A49', + capcap: '\u2A4B', + capcup: '\u2A47', + capdot: '\u2A40', + CapitalDifferentialD: '\u2145', + caps: '\u2229\uFE00', + caret: '\u2041', + caron: '\u02C7', + Cayleys: '\u212D', + ccaps: '\u2A4D', + Ccaron: '\u010C', + ccaron: '\u010D', + Ccedil: '\u00C7', + ccedil: '\u00E7', + Ccirc: '\u0108', + ccirc: '\u0109', + Cconint: '\u2230', + ccups: '\u2A4C', + ccupssm: '\u2A50', + Cdot: '\u010A', + cdot: '\u010B', + cedil: '\u00B8', + Cedilla: '\u00B8', + cemptyv: '\u29B2', + cent: '\u00A2', + CenterDot: '\u00B7', + centerdot: '\u00B7', + Cfr: '\u212D', + cfr: '\uD835\uDD20', + CHcy: '\u0427', + chcy: '\u0447', + check: '\u2713', + checkmark: '\u2713', + Chi: '\u03A7', + chi: '\u03C7', + cir: '\u25CB', + circ: '\u02C6', + circeq: '\u2257', + circlearrowleft: '\u21BA', + circlearrowright: '\u21BB', + circledast: '\u229B', + circledcirc: '\u229A', + circleddash: '\u229D', + CircleDot: '\u2299', + circledR: '\u00AE', + circledS: '\u24C8', + CircleMinus: '\u2296', + CirclePlus: '\u2295', + CircleTimes: '\u2297', + cirE: '\u29C3', + cire: '\u2257', + cirfnint: '\u2A10', + cirmid: '\u2AEF', + cirscir: '\u29C2', + ClockwiseContourIntegral: '\u2232', + CloseCurlyDoubleQuote: '\u201D', + CloseCurlyQuote: '\u2019', + clubs: '\u2663', + clubsuit: '\u2663', + Colon: '\u2237', + colon: '\u003A', + Colone: '\u2A74', + colone: '\u2254', + coloneq: '\u2254', + comma: '\u002C', + commat: '\u0040', + comp: '\u2201', + compfn: '\u2218', + complement: '\u2201', + complexes: '\u2102', + cong: '\u2245', + congdot: '\u2A6D', + Congruent: '\u2261', + Conint: '\u222F', + conint: '\u222E', + ContourIntegral: '\u222E', + Copf: '\u2102', + copf: '\uD835\uDD54', + coprod: '\u2210', + Coproduct: '\u2210', + COPY: '\u00A9', + copy: '\u00A9', + copysr: '\u2117', + CounterClockwiseContourIntegral: '\u2233', + crarr: '\u21B5', + Cross: '\u2A2F', + cross: '\u2717', + Cscr: '\uD835\uDC9E', + cscr: '\uD835\uDCB8', + csub: '\u2ACF', + csube: '\u2AD1', + csup: '\u2AD0', + csupe: '\u2AD2', + ctdot: '\u22EF', + cudarrl: '\u2938', + cudarrr: '\u2935', + cuepr: '\u22DE', + cuesc: '\u22DF', + cularr: '\u21B6', + cularrp: '\u293D', + Cup: '\u22D3', + cup: '\u222A', + cupbrcap: '\u2A48', + CupCap: '\u224D', + cupcap: '\u2A46', + cupcup: '\u2A4A', + cupdot: '\u228D', + cupor: '\u2A45', + cups: '\u222A\uFE00', + curarr: '\u21B7', + curarrm: '\u293C', + curlyeqprec: '\u22DE', + curlyeqsucc: '\u22DF', + curlyvee: '\u22CE', + curlywedge: '\u22CF', + curren: '\u00A4', + curvearrowleft: '\u21B6', + curvearrowright: '\u21B7', + cuvee: '\u22CE', + cuwed: '\u22CF', + cwconint: '\u2232', + cwint: '\u2231', + cylcty: '\u232D', + Dagger: '\u2021', + dagger: '\u2020', + daleth: '\u2138', + Darr: '\u21A1', + dArr: '\u21D3', + darr: '\u2193', + dash: '\u2010', + Dashv: '\u2AE4', + dashv: '\u22A3', + dbkarow: '\u290F', + dblac: '\u02DD', + Dcaron: '\u010E', + dcaron: '\u010F', + Dcy: '\u0414', + dcy: '\u0434', + DD: '\u2145', + dd: '\u2146', + ddagger: '\u2021', + ddarr: '\u21CA', + DDotrahd: '\u2911', + ddotseq: '\u2A77', + deg: '\u00B0', + Del: '\u2207', + Delta: '\u0394', + delta: '\u03B4', + demptyv: '\u29B1', + dfisht: '\u297F', + Dfr: '\uD835\uDD07', + dfr: '\uD835\uDD21', + dHar: '\u2965', + dharl: '\u21C3', + dharr: '\u21C2', + DiacriticalAcute: '\u00B4', + DiacriticalDot: '\u02D9', + DiacriticalDoubleAcute: '\u02DD', + DiacriticalGrave: '\u0060', + DiacriticalTilde: '\u02DC', + diam: '\u22C4', + Diamond: '\u22C4', + diamond: '\u22C4', + diamondsuit: '\u2666', + diams: '\u2666', + die: '\u00A8', + DifferentialD: '\u2146', + digamma: '\u03DD', + disin: '\u22F2', + div: '\u00F7', + divide: '\u00F7', + divideontimes: '\u22C7', + divonx: '\u22C7', + DJcy: '\u0402', + djcy: '\u0452', + dlcorn: '\u231E', + dlcrop: '\u230D', + dollar: '\u0024', + Dopf: '\uD835\uDD3B', + dopf: '\uD835\uDD55', + Dot: '\u00A8', + dot: '\u02D9', + DotDot: '\u20DC', + doteq: '\u2250', + doteqdot: '\u2251', + DotEqual: '\u2250', + dotminus: '\u2238', + dotplus: '\u2214', + dotsquare: '\u22A1', + doublebarwedge: '\u2306', + DoubleContourIntegral: '\u222F', + DoubleDot: '\u00A8', + DoubleDownArrow: '\u21D3', + DoubleLeftArrow: '\u21D0', + DoubleLeftRightArrow: '\u21D4', + DoubleLeftTee: '\u2AE4', + DoubleLongLeftArrow: '\u27F8', + DoubleLongLeftRightArrow: '\u27FA', + DoubleLongRightArrow: '\u27F9', + DoubleRightArrow: '\u21D2', + DoubleRightTee: '\u22A8', + DoubleUpArrow: '\u21D1', + DoubleUpDownArrow: '\u21D5', + DoubleVerticalBar: '\u2225', + DownArrow: '\u2193', + Downarrow: '\u21D3', + downarrow: '\u2193', + DownArrowBar: '\u2913', + DownArrowUpArrow: '\u21F5', + DownBreve: '\u0311', + downdownarrows: '\u21CA', + downharpoonleft: '\u21C3', + downharpoonright: '\u21C2', + DownLeftRightVector: '\u2950', + DownLeftTeeVector: '\u295E', + DownLeftVector: '\u21BD', + DownLeftVectorBar: '\u2956', + DownRightTeeVector: '\u295F', + DownRightVector: '\u21C1', + DownRightVectorBar: '\u2957', + DownTee: '\u22A4', + DownTeeArrow: '\u21A7', + drbkarow: '\u2910', + drcorn: '\u231F', + drcrop: '\u230C', + Dscr: '\uD835\uDC9F', + dscr: '\uD835\uDCB9', + DScy: '\u0405', + dscy: '\u0455', + dsol: '\u29F6', + Dstrok: '\u0110', + dstrok: '\u0111', + dtdot: '\u22F1', + dtri: '\u25BF', + dtrif: '\u25BE', + duarr: '\u21F5', + duhar: '\u296F', + dwangle: '\u29A6', + DZcy: '\u040F', + dzcy: '\u045F', + dzigrarr: '\u27FF', + Eacute: '\u00C9', + eacute: '\u00E9', + easter: '\u2A6E', + Ecaron: '\u011A', + ecaron: '\u011B', + ecir: '\u2256', + Ecirc: '\u00CA', + ecirc: '\u00EA', + ecolon: '\u2255', + Ecy: '\u042D', + ecy: '\u044D', + eDDot: '\u2A77', + Edot: '\u0116', + eDot: '\u2251', + edot: '\u0117', + ee: '\u2147', + efDot: '\u2252', + Efr: '\uD835\uDD08', + efr: '\uD835\uDD22', + eg: '\u2A9A', + Egrave: '\u00C8', + egrave: '\u00E8', + egs: '\u2A96', + egsdot: '\u2A98', + el: '\u2A99', + Element: '\u2208', + elinters: '\u23E7', + ell: '\u2113', + els: '\u2A95', + elsdot: '\u2A97', + Emacr: '\u0112', + emacr: '\u0113', + empty: '\u2205', + emptyset: '\u2205', + EmptySmallSquare: '\u25FB', + emptyv: '\u2205', + EmptyVerySmallSquare: '\u25AB', + emsp: '\u2003', + emsp13: '\u2004', + emsp14: '\u2005', + ENG: '\u014A', + eng: '\u014B', + ensp: '\u2002', + Eogon: '\u0118', + eogon: '\u0119', + Eopf: '\uD835\uDD3C', + eopf: '\uD835\uDD56', + epar: '\u22D5', + eparsl: '\u29E3', + eplus: '\u2A71', + epsi: '\u03B5', + Epsilon: '\u0395', + epsilon: '\u03B5', + epsiv: '\u03F5', + eqcirc: '\u2256', + eqcolon: '\u2255', + eqsim: '\u2242', + eqslantgtr: '\u2A96', + eqslantless: '\u2A95', + Equal: '\u2A75', + equals: '\u003D', + EqualTilde: '\u2242', + equest: '\u225F', + Equilibrium: '\u21CC', + equiv: '\u2261', + equivDD: '\u2A78', + eqvparsl: '\u29E5', + erarr: '\u2971', + erDot: '\u2253', + Escr: '\u2130', + escr: '\u212F', + esdot: '\u2250', + Esim: '\u2A73', + esim: '\u2242', + Eta: '\u0397', + eta: '\u03B7', + ETH: '\u00D0', + eth: '\u00F0', + Euml: '\u00CB', + euml: '\u00EB', + euro: '\u20AC', + excl: '\u0021', + exist: '\u2203', + Exists: '\u2203', + expectation: '\u2130', + ExponentialE: '\u2147', + exponentiale: '\u2147', + fallingdotseq: '\u2252', + Fcy: '\u0424', + fcy: '\u0444', + female: '\u2640', + ffilig: '\uFB03', + fflig: '\uFB00', + ffllig: '\uFB04', + Ffr: '\uD835\uDD09', + ffr: '\uD835\uDD23', + filig: '\uFB01', + FilledSmallSquare: '\u25FC', + FilledVerySmallSquare: '\u25AA', + fjlig: '\u0066\u006A', + flat: '\u266D', + fllig: '\uFB02', + fltns: '\u25B1', + fnof: '\u0192', + Fopf: '\uD835\uDD3D', + fopf: '\uD835\uDD57', + ForAll: '\u2200', + forall: '\u2200', + fork: '\u22D4', + forkv: '\u2AD9', + Fouriertrf: '\u2131', + fpartint: '\u2A0D', + frac12: '\u00BD', + frac13: '\u2153', + frac14: '\u00BC', + frac15: '\u2155', + frac16: '\u2159', + frac18: '\u215B', + frac23: '\u2154', + frac25: '\u2156', + frac34: '\u00BE', + frac35: '\u2157', + frac38: '\u215C', + frac45: '\u2158', + frac56: '\u215A', + frac58: '\u215D', + frac78: '\u215E', + frasl: '\u2044', + frown: '\u2322', + Fscr: '\u2131', + fscr: '\uD835\uDCBB', + gacute: '\u01F5', + Gamma: '\u0393', + gamma: '\u03B3', + Gammad: '\u03DC', + gammad: '\u03DD', + gap: '\u2A86', + Gbreve: '\u011E', + gbreve: '\u011F', + Gcedil: '\u0122', + Gcirc: '\u011C', + gcirc: '\u011D', + Gcy: '\u0413', + gcy: '\u0433', + Gdot: '\u0120', + gdot: '\u0121', + gE: '\u2267', + ge: '\u2265', + gEl: '\u2A8C', + gel: '\u22DB', + geq: '\u2265', + geqq: '\u2267', + geqslant: '\u2A7E', + ges: '\u2A7E', + gescc: '\u2AA9', + gesdot: '\u2A80', + gesdoto: '\u2A82', + gesdotol: '\u2A84', + gesl: '\u22DB\uFE00', + gesles: '\u2A94', + Gfr: '\uD835\uDD0A', + gfr: '\uD835\uDD24', + Gg: '\u22D9', + gg: '\u226B', + ggg: '\u22D9', + gimel: '\u2137', + GJcy: '\u0403', + gjcy: '\u0453', + gl: '\u2277', + gla: '\u2AA5', + glE: '\u2A92', + glj: '\u2AA4', + gnap: '\u2A8A', + gnapprox: '\u2A8A', + gnE: '\u2269', + gne: '\u2A88', + gneq: '\u2A88', + gneqq: '\u2269', + gnsim: '\u22E7', + Gopf: '\uD835\uDD3E', + gopf: '\uD835\uDD58', + grave: '\u0060', + GreaterEqual: '\u2265', + GreaterEqualLess: '\u22DB', + GreaterFullEqual: '\u2267', + GreaterGreater: '\u2AA2', + GreaterLess: '\u2277', + GreaterSlantEqual: '\u2A7E', + GreaterTilde: '\u2273', + Gscr: '\uD835\uDCA2', + gscr: '\u210A', + gsim: '\u2273', + gsime: '\u2A8E', + gsiml: '\u2A90', + Gt: '\u226B', + GT: '\u003E', + gt: '\u003E', + gtcc: '\u2AA7', + gtcir: '\u2A7A', + gtdot: '\u22D7', + gtlPar: '\u2995', + gtquest: '\u2A7C', + gtrapprox: '\u2A86', + gtrarr: '\u2978', + gtrdot: '\u22D7', + gtreqless: '\u22DB', + gtreqqless: '\u2A8C', + gtrless: '\u2277', + gtrsim: '\u2273', + gvertneqq: '\u2269\uFE00', + gvnE: '\u2269\uFE00', + Hacek: '\u02C7', + hairsp: '\u200A', + half: '\u00BD', + hamilt: '\u210B', + HARDcy: '\u042A', + hardcy: '\u044A', + hArr: '\u21D4', + harr: '\u2194', + harrcir: '\u2948', + harrw: '\u21AD', + Hat: '\u005E', + hbar: '\u210F', + Hcirc: '\u0124', + hcirc: '\u0125', + hearts: '\u2665', + heartsuit: '\u2665', + hellip: '\u2026', + hercon: '\u22B9', + Hfr: '\u210C', + hfr: '\uD835\uDD25', + HilbertSpace: '\u210B', + hksearow: '\u2925', + hkswarow: '\u2926', + hoarr: '\u21FF', + homtht: '\u223B', + hookleftarrow: '\u21A9', + hookrightarrow: '\u21AA', + Hopf: '\u210D', + hopf: '\uD835\uDD59', + horbar: '\u2015', + HorizontalLine: '\u2500', + Hscr: '\u210B', + hscr: '\uD835\uDCBD', + hslash: '\u210F', + Hstrok: '\u0126', + hstrok: '\u0127', + HumpDownHump: '\u224E', + HumpEqual: '\u224F', + hybull: '\u2043', + hyphen: '\u2010', + Iacute: '\u00CD', + iacute: '\u00ED', + ic: '\u2063', + Icirc: '\u00CE', + icirc: '\u00EE', + Icy: '\u0418', + icy: '\u0438', + Idot: '\u0130', + IEcy: '\u0415', + iecy: '\u0435', + iexcl: '\u00A1', + iff: '\u21D4', + Ifr: '\u2111', + ifr: '\uD835\uDD26', + Igrave: '\u00CC', + igrave: '\u00EC', + ii: '\u2148', + iiiint: '\u2A0C', + iiint: '\u222D', + iinfin: '\u29DC', + iiota: '\u2129', + IJlig: '\u0132', + ijlig: '\u0133', + Im: '\u2111', + Imacr: '\u012A', + imacr: '\u012B', + image: '\u2111', + ImaginaryI: '\u2148', + imagline: '\u2110', + imagpart: '\u2111', + imath: '\u0131', + imof: '\u22B7', + imped: '\u01B5', + Implies: '\u21D2', + in: '\u2208', + incare: '\u2105', + infin: '\u221E', + infintie: '\u29DD', + inodot: '\u0131', + Int: '\u222C', + int: '\u222B', + intcal: '\u22BA', + integers: '\u2124', + Integral: '\u222B', + intercal: '\u22BA', + Intersection: '\u22C2', + intlarhk: '\u2A17', + intprod: '\u2A3C', + InvisibleComma: '\u2063', + InvisibleTimes: '\u2062', + IOcy: '\u0401', + iocy: '\u0451', + Iogon: '\u012E', + iogon: '\u012F', + Iopf: '\uD835\uDD40', + iopf: '\uD835\uDD5A', + Iota: '\u0399', + iota: '\u03B9', + iprod: '\u2A3C', + iquest: '\u00BF', + Iscr: '\u2110', + iscr: '\uD835\uDCBE', + isin: '\u2208', + isindot: '\u22F5', + isinE: '\u22F9', + isins: '\u22F4', + isinsv: '\u22F3', + isinv: '\u2208', + it: '\u2062', + Itilde: '\u0128', + itilde: '\u0129', + Iukcy: '\u0406', + iukcy: '\u0456', + Iuml: '\u00CF', + iuml: '\u00EF', + Jcirc: '\u0134', + jcirc: '\u0135', + Jcy: '\u0419', + jcy: '\u0439', + Jfr: '\uD835\uDD0D', + jfr: '\uD835\uDD27', + jmath: '\u0237', + Jopf: '\uD835\uDD41', + jopf: '\uD835\uDD5B', + Jscr: '\uD835\uDCA5', + jscr: '\uD835\uDCBF', + Jsercy: '\u0408', + jsercy: '\u0458', + Jukcy: '\u0404', + jukcy: '\u0454', + Kappa: '\u039A', + kappa: '\u03BA', + kappav: '\u03F0', + Kcedil: '\u0136', + kcedil: '\u0137', + Kcy: '\u041A', + kcy: '\u043A', + Kfr: '\uD835\uDD0E', + kfr: '\uD835\uDD28', + kgreen: '\u0138', + KHcy: '\u0425', + khcy: '\u0445', + KJcy: '\u040C', + kjcy: '\u045C', + Kopf: '\uD835\uDD42', + kopf: '\uD835\uDD5C', + Kscr: '\uD835\uDCA6', + kscr: '\uD835\uDCC0', + lAarr: '\u21DA', + Lacute: '\u0139', + lacute: '\u013A', + laemptyv: '\u29B4', + lagran: '\u2112', + Lambda: '\u039B', + lambda: '\u03BB', + Lang: '\u27EA', + lang: '\u27E8', + langd: '\u2991', + langle: '\u27E8', + lap: '\u2A85', + Laplacetrf: '\u2112', + laquo: '\u00AB', + Larr: '\u219E', + lArr: '\u21D0', + larr: '\u2190', + larrb: '\u21E4', + larrbfs: '\u291F', + larrfs: '\u291D', + larrhk: '\u21A9', + larrlp: '\u21AB', + larrpl: '\u2939', + larrsim: '\u2973', + larrtl: '\u21A2', + lat: '\u2AAB', + lAtail: '\u291B', + latail: '\u2919', + late: '\u2AAD', + lates: '\u2AAD\uFE00', + lBarr: '\u290E', + lbarr: '\u290C', + lbbrk: '\u2772', + lbrace: '\u007B', + lbrack: '\u005B', + lbrke: '\u298B', + lbrksld: '\u298F', + lbrkslu: '\u298D', + Lcaron: '\u013D', + lcaron: '\u013E', + Lcedil: '\u013B', + lcedil: '\u013C', + lceil: '\u2308', + lcub: '\u007B', + Lcy: '\u041B', + lcy: '\u043B', + ldca: '\u2936', + ldquo: '\u201C', + ldquor: '\u201E', + ldrdhar: '\u2967', + ldrushar: '\u294B', + ldsh: '\u21B2', + lE: '\u2266', + le: '\u2264', + LeftAngleBracket: '\u27E8', + LeftArrow: '\u2190', + Leftarrow: '\u21D0', + leftarrow: '\u2190', + LeftArrowBar: '\u21E4', + LeftArrowRightArrow: '\u21C6', + leftarrowtail: '\u21A2', + LeftCeiling: '\u2308', + LeftDoubleBracket: '\u27E6', + LeftDownTeeVector: '\u2961', + LeftDownVector: '\u21C3', + LeftDownVectorBar: '\u2959', + LeftFloor: '\u230A', + leftharpoondown: '\u21BD', + leftharpoonup: '\u21BC', + leftleftarrows: '\u21C7', + LeftRightArrow: '\u2194', + Leftrightarrow: '\u21D4', + leftrightarrow: '\u2194', + leftrightarrows: '\u21C6', + leftrightharpoons: '\u21CB', + leftrightsquigarrow: '\u21AD', + LeftRightVector: '\u294E', + LeftTee: '\u22A3', + LeftTeeArrow: '\u21A4', + LeftTeeVector: '\u295A', + leftthreetimes: '\u22CB', + LeftTriangle: '\u22B2', + LeftTriangleBar: '\u29CF', + LeftTriangleEqual: '\u22B4', + LeftUpDownVector: '\u2951', + LeftUpTeeVector: '\u2960', + LeftUpVector: '\u21BF', + LeftUpVectorBar: '\u2958', + LeftVector: '\u21BC', + LeftVectorBar: '\u2952', + lEg: '\u2A8B', + leg: '\u22DA', + leq: '\u2264', + leqq: '\u2266', + leqslant: '\u2A7D', + les: '\u2A7D', + lescc: '\u2AA8', + lesdot: '\u2A7F', + lesdoto: '\u2A81', + lesdotor: '\u2A83', + lesg: '\u22DA\uFE00', + lesges: '\u2A93', + lessapprox: '\u2A85', + lessdot: '\u22D6', + lesseqgtr: '\u22DA', + lesseqqgtr: '\u2A8B', + LessEqualGreater: '\u22DA', + LessFullEqual: '\u2266', + LessGreater: '\u2276', + lessgtr: '\u2276', + LessLess: '\u2AA1', + lesssim: '\u2272', + LessSlantEqual: '\u2A7D', + LessTilde: '\u2272', + lfisht: '\u297C', + lfloor: '\u230A', + Lfr: '\uD835\uDD0F', + lfr: '\uD835\uDD29', + lg: '\u2276', + lgE: '\u2A91', + lHar: '\u2962', + lhard: '\u21BD', + lharu: '\u21BC', + lharul: '\u296A', + lhblk: '\u2584', + LJcy: '\u0409', + ljcy: '\u0459', + Ll: '\u22D8', + ll: '\u226A', + llarr: '\u21C7', + llcorner: '\u231E', + Lleftarrow: '\u21DA', + llhard: '\u296B', + lltri: '\u25FA', + Lmidot: '\u013F', + lmidot: '\u0140', + lmoust: '\u23B0', + lmoustache: '\u23B0', + lnap: '\u2A89', + lnapprox: '\u2A89', + lnE: '\u2268', + lne: '\u2A87', + lneq: '\u2A87', + lneqq: '\u2268', + lnsim: '\u22E6', + loang: '\u27EC', + loarr: '\u21FD', + lobrk: '\u27E6', + LongLeftArrow: '\u27F5', + Longleftarrow: '\u27F8', + longleftarrow: '\u27F5', + LongLeftRightArrow: '\u27F7', + Longleftrightarrow: '\u27FA', + longleftrightarrow: '\u27F7', + longmapsto: '\u27FC', + LongRightArrow: '\u27F6', + Longrightarrow: '\u27F9', + longrightarrow: '\u27F6', + looparrowleft: '\u21AB', + looparrowright: '\u21AC', + lopar: '\u2985', + Lopf: '\uD835\uDD43', + lopf: '\uD835\uDD5D', + loplus: '\u2A2D', + lotimes: '\u2A34', + lowast: '\u2217', + lowbar: '\u005F', + LowerLeftArrow: '\u2199', + LowerRightArrow: '\u2198', + loz: '\u25CA', + lozenge: '\u25CA', + lozf: '\u29EB', + lpar: '\u0028', + lparlt: '\u2993', + lrarr: '\u21C6', + lrcorner: '\u231F', + lrhar: '\u21CB', + lrhard: '\u296D', + lrm: '\u200E', + lrtri: '\u22BF', + lsaquo: '\u2039', + Lscr: '\u2112', + lscr: '\uD835\uDCC1', + Lsh: '\u21B0', + lsh: '\u21B0', + lsim: '\u2272', + lsime: '\u2A8D', + lsimg: '\u2A8F', + lsqb: '\u005B', + lsquo: '\u2018', + lsquor: '\u201A', + Lstrok: '\u0141', + lstrok: '\u0142', + Lt: '\u226A', + LT: '\u003C', + lt: '\u003C', + ltcc: '\u2AA6', + ltcir: '\u2A79', + ltdot: '\u22D6', + lthree: '\u22CB', + ltimes: '\u22C9', + ltlarr: '\u2976', + ltquest: '\u2A7B', + ltri: '\u25C3', + ltrie: '\u22B4', + ltrif: '\u25C2', + ltrPar: '\u2996', + lurdshar: '\u294A', + luruhar: '\u2966', + lvertneqq: '\u2268\uFE00', + lvnE: '\u2268\uFE00', + macr: '\u00AF', + male: '\u2642', + malt: '\u2720', + maltese: '\u2720', + Map: '\u2905', + map: '\u21A6', + mapsto: '\u21A6', + mapstodown: '\u21A7', + mapstoleft: '\u21A4', + mapstoup: '\u21A5', + marker: '\u25AE', + mcomma: '\u2A29', + Mcy: '\u041C', + mcy: '\u043C', + mdash: '\u2014', + mDDot: '\u223A', + measuredangle: '\u2221', + MediumSpace: '\u205F', + Mellintrf: '\u2133', + Mfr: '\uD835\uDD10', + mfr: '\uD835\uDD2A', + mho: '\u2127', + micro: '\u00B5', + mid: '\u2223', + midast: '\u002A', + midcir: '\u2AF0', + middot: '\u00B7', + minus: '\u2212', + minusb: '\u229F', + minusd: '\u2238', + minusdu: '\u2A2A', + MinusPlus: '\u2213', + mlcp: '\u2ADB', + mldr: '\u2026', + mnplus: '\u2213', + models: '\u22A7', + Mopf: '\uD835\uDD44', + mopf: '\uD835\uDD5E', + mp: '\u2213', + Mscr: '\u2133', + mscr: '\uD835\uDCC2', + mstpos: '\u223E', + Mu: '\u039C', + mu: '\u03BC', + multimap: '\u22B8', + mumap: '\u22B8', + nabla: '\u2207', + Nacute: '\u0143', + nacute: '\u0144', + nang: '\u2220\u20D2', + nap: '\u2249', + napE: '\u2A70\u0338', + napid: '\u224B\u0338', + napos: '\u0149', + napprox: '\u2249', + natur: '\u266E', + natural: '\u266E', + naturals: '\u2115', + nbsp: '\u00A0', + nbump: '\u224E\u0338', + nbumpe: '\u224F\u0338', + ncap: '\u2A43', + Ncaron: '\u0147', + ncaron: '\u0148', + Ncedil: '\u0145', + ncedil: '\u0146', + ncong: '\u2247', + ncongdot: '\u2A6D\u0338', + ncup: '\u2A42', + Ncy: '\u041D', + ncy: '\u043D', + ndash: '\u2013', + ne: '\u2260', + nearhk: '\u2924', + neArr: '\u21D7', + nearr: '\u2197', + nearrow: '\u2197', + nedot: '\u2250\u0338', + NegativeMediumSpace: '\u200B', + NegativeThickSpace: '\u200B', + NegativeThinSpace: '\u200B', + NegativeVeryThinSpace: '\u200B', + nequiv: '\u2262', + nesear: '\u2928', + nesim: '\u2242\u0338', + NestedGreaterGreater: '\u226B', + NestedLessLess: '\u226A', + NewLine: '\u000A', + nexist: '\u2204', + nexists: '\u2204', + Nfr: '\uD835\uDD11', + nfr: '\uD835\uDD2B', + ngE: '\u2267\u0338', + nge: '\u2271', + ngeq: '\u2271', + ngeqq: '\u2267\u0338', + ngeqslant: '\u2A7E\u0338', + nges: '\u2A7E\u0338', + nGg: '\u22D9\u0338', + ngsim: '\u2275', + nGt: '\u226B\u20D2', + ngt: '\u226F', + ngtr: '\u226F', + nGtv: '\u226B\u0338', + nhArr: '\u21CE', + nharr: '\u21AE', + nhpar: '\u2AF2', + ni: '\u220B', + nis: '\u22FC', + nisd: '\u22FA', + niv: '\u220B', + NJcy: '\u040A', + njcy: '\u045A', + nlArr: '\u21CD', + nlarr: '\u219A', + nldr: '\u2025', + nlE: '\u2266\u0338', + nle: '\u2270', + nLeftarrow: '\u21CD', + nleftarrow: '\u219A', + nLeftrightarrow: '\u21CE', + nleftrightarrow: '\u21AE', + nleq: '\u2270', + nleqq: '\u2266\u0338', + nleqslant: '\u2A7D\u0338', + nles: '\u2A7D\u0338', + nless: '\u226E', + nLl: '\u22D8\u0338', + nlsim: '\u2274', + nLt: '\u226A\u20D2', + nlt: '\u226E', + nltri: '\u22EA', + nltrie: '\u22EC', + nLtv: '\u226A\u0338', + nmid: '\u2224', + NoBreak: '\u2060', + NonBreakingSpace: '\u00A0', + Nopf: '\u2115', + nopf: '\uD835\uDD5F', + Not: '\u2AEC', + not: '\u00AC', + NotCongruent: '\u2262', + NotCupCap: '\u226D', + NotDoubleVerticalBar: '\u2226', + NotElement: '\u2209', + NotEqual: '\u2260', + NotEqualTilde: '\u2242\u0338', + NotExists: '\u2204', + NotGreater: '\u226F', + NotGreaterEqual: '\u2271', + NotGreaterFullEqual: '\u2267\u0338', + NotGreaterGreater: '\u226B\u0338', + NotGreaterLess: '\u2279', + NotGreaterSlantEqual: '\u2A7E\u0338', + NotGreaterTilde: '\u2275', + NotHumpDownHump: '\u224E\u0338', + NotHumpEqual: '\u224F\u0338', + notin: '\u2209', + notindot: '\u22F5\u0338', + notinE: '\u22F9\u0338', + notinva: '\u2209', + notinvb: '\u22F7', + notinvc: '\u22F6', + NotLeftTriangle: '\u22EA', + NotLeftTriangleBar: '\u29CF\u0338', + NotLeftTriangleEqual: '\u22EC', + NotLess: '\u226E', + NotLessEqual: '\u2270', + NotLessGreater: '\u2278', + NotLessLess: '\u226A\u0338', + NotLessSlantEqual: '\u2A7D\u0338', + NotLessTilde: '\u2274', + NotNestedGreaterGreater: '\u2AA2\u0338', + NotNestedLessLess: '\u2AA1\u0338', + notni: '\u220C', + notniva: '\u220C', + notnivb: '\u22FE', + notnivc: '\u22FD', + NotPrecedes: '\u2280', + NotPrecedesEqual: '\u2AAF\u0338', + NotPrecedesSlantEqual: '\u22E0', + NotReverseElement: '\u220C', + NotRightTriangle: '\u22EB', + NotRightTriangleBar: '\u29D0\u0338', + NotRightTriangleEqual: '\u22ED', + NotSquareSubset: '\u228F\u0338', + NotSquareSubsetEqual: '\u22E2', + NotSquareSuperset: '\u2290\u0338', + NotSquareSupersetEqual: '\u22E3', + NotSubset: '\u2282\u20D2', + NotSubsetEqual: '\u2288', + NotSucceeds: '\u2281', + NotSucceedsEqual: '\u2AB0\u0338', + NotSucceedsSlantEqual: '\u22E1', + NotSucceedsTilde: '\u227F\u0338', + NotSuperset: '\u2283\u20D2', + NotSupersetEqual: '\u2289', + NotTilde: '\u2241', + NotTildeEqual: '\u2244', + NotTildeFullEqual: '\u2247', + NotTildeTilde: '\u2249', + NotVerticalBar: '\u2224', + npar: '\u2226', + nparallel: '\u2226', + nparsl: '\u2AFD\u20E5', + npart: '\u2202\u0338', + npolint: '\u2A14', + npr: '\u2280', + nprcue: '\u22E0', + npre: '\u2AAF\u0338', + nprec: '\u2280', + npreceq: '\u2AAF\u0338', + nrArr: '\u21CF', + nrarr: '\u219B', + nrarrc: '\u2933\u0338', + nrarrw: '\u219D\u0338', + nRightarrow: '\u21CF', + nrightarrow: '\u219B', + nrtri: '\u22EB', + nrtrie: '\u22ED', + nsc: '\u2281', + nsccue: '\u22E1', + nsce: '\u2AB0\u0338', + Nscr: '\uD835\uDCA9', + nscr: '\uD835\uDCC3', + nshortmid: '\u2224', + nshortparallel: '\u2226', + nsim: '\u2241', + nsime: '\u2244', + nsimeq: '\u2244', + nsmid: '\u2224', + nspar: '\u2226', + nsqsube: '\u22E2', + nsqsupe: '\u22E3', + nsub: '\u2284', + nsubE: '\u2AC5\u0338', + nsube: '\u2288', + nsubset: '\u2282\u20D2', + nsubseteq: '\u2288', + nsubseteqq: '\u2AC5\u0338', + nsucc: '\u2281', + nsucceq: '\u2AB0\u0338', + nsup: '\u2285', + nsupE: '\u2AC6\u0338', + nsupe: '\u2289', + nsupset: '\u2283\u20D2', + nsupseteq: '\u2289', + nsupseteqq: '\u2AC6\u0338', + ntgl: '\u2279', + Ntilde: '\u00D1', + ntilde: '\u00F1', + ntlg: '\u2278', + ntriangleleft: '\u22EA', + ntrianglelefteq: '\u22EC', + ntriangleright: '\u22EB', + ntrianglerighteq: '\u22ED', + Nu: '\u039D', + nu: '\u03BD', + num: '\u0023', + numero: '\u2116', + numsp: '\u2007', + nvap: '\u224D\u20D2', + nVDash: '\u22AF', + nVdash: '\u22AE', + nvDash: '\u22AD', + nvdash: '\u22AC', + nvge: '\u2265\u20D2', + nvgt: '\u003E\u20D2', + nvHarr: '\u2904', + nvinfin: '\u29DE', + nvlArr: '\u2902', + nvle: '\u2264\u20D2', + nvlt: '\u003C\u20D2', + nvltrie: '\u22B4\u20D2', + nvrArr: '\u2903', + nvrtrie: '\u22B5\u20D2', + nvsim: '\u223C\u20D2', + nwarhk: '\u2923', + nwArr: '\u21D6', + nwarr: '\u2196', + nwarrow: '\u2196', + nwnear: '\u2927', + Oacute: '\u00D3', + oacute: '\u00F3', + oast: '\u229B', + ocir: '\u229A', + Ocirc: '\u00D4', + ocirc: '\u00F4', + Ocy: '\u041E', + ocy: '\u043E', + odash: '\u229D', + Odblac: '\u0150', + odblac: '\u0151', + odiv: '\u2A38', + odot: '\u2299', + odsold: '\u29BC', + OElig: '\u0152', + oelig: '\u0153', + ofcir: '\u29BF', + Ofr: '\uD835\uDD12', + ofr: '\uD835\uDD2C', + ogon: '\u02DB', + Ograve: '\u00D2', + ograve: '\u00F2', + ogt: '\u29C1', + ohbar: '\u29B5', + ohm: '\u03A9', + oint: '\u222E', + olarr: '\u21BA', + olcir: '\u29BE', + olcross: '\u29BB', + oline: '\u203E', + olt: '\u29C0', + Omacr: '\u014C', + omacr: '\u014D', + Omega: '\u03A9', + omega: '\u03C9', + Omicron: '\u039F', + omicron: '\u03BF', + omid: '\u29B6', + ominus: '\u2296', + Oopf: '\uD835\uDD46', + oopf: '\uD835\uDD60', + opar: '\u29B7', + OpenCurlyDoubleQuote: '\u201C', + OpenCurlyQuote: '\u2018', + operp: '\u29B9', + oplus: '\u2295', + Or: '\u2A54', + or: '\u2228', + orarr: '\u21BB', + ord: '\u2A5D', + order: '\u2134', + orderof: '\u2134', + ordf: '\u00AA', + ordm: '\u00BA', + origof: '\u22B6', + oror: '\u2A56', + orslope: '\u2A57', + orv: '\u2A5B', + oS: '\u24C8', + Oscr: '\uD835\uDCAA', + oscr: '\u2134', + Oslash: '\u00D8', + oslash: '\u00F8', + osol: '\u2298', + Otilde: '\u00D5', + otilde: '\u00F5', + Otimes: '\u2A37', + otimes: '\u2297', + otimesas: '\u2A36', + Ouml: '\u00D6', + ouml: '\u00F6', + ovbar: '\u233D', + OverBar: '\u203E', + OverBrace: '\u23DE', + OverBracket: '\u23B4', + OverParenthesis: '\u23DC', + par: '\u2225', + para: '\u00B6', + parallel: '\u2225', + parsim: '\u2AF3', + parsl: '\u2AFD', + part: '\u2202', + PartialD: '\u2202', + Pcy: '\u041F', + pcy: '\u043F', + percnt: '\u0025', + period: '\u002E', + permil: '\u2030', + perp: '\u22A5', + pertenk: '\u2031', + Pfr: '\uD835\uDD13', + pfr: '\uD835\uDD2D', + Phi: '\u03A6', + phi: '\u03C6', + phiv: '\u03D5', + phmmat: '\u2133', + phone: '\u260E', + Pi: '\u03A0', + pi: '\u03C0', + pitchfork: '\u22D4', + piv: '\u03D6', + planck: '\u210F', + planckh: '\u210E', + plankv: '\u210F', + plus: '\u002B', + plusacir: '\u2A23', + plusb: '\u229E', + pluscir: '\u2A22', + plusdo: '\u2214', + plusdu: '\u2A25', + pluse: '\u2A72', + PlusMinus: '\u00B1', + plusmn: '\u00B1', + plussim: '\u2A26', + plustwo: '\u2A27', + pm: '\u00B1', + Poincareplane: '\u210C', + pointint: '\u2A15', + Popf: '\u2119', + popf: '\uD835\uDD61', + pound: '\u00A3', + Pr: '\u2ABB', + pr: '\u227A', + prap: '\u2AB7', + prcue: '\u227C', + prE: '\u2AB3', + pre: '\u2AAF', + prec: '\u227A', + precapprox: '\u2AB7', + preccurlyeq: '\u227C', + Precedes: '\u227A', + PrecedesEqual: '\u2AAF', + PrecedesSlantEqual: '\u227C', + PrecedesTilde: '\u227E', + preceq: '\u2AAF', + precnapprox: '\u2AB9', + precneqq: '\u2AB5', + precnsim: '\u22E8', + precsim: '\u227E', + Prime: '\u2033', + prime: '\u2032', + primes: '\u2119', + prnap: '\u2AB9', + prnE: '\u2AB5', + prnsim: '\u22E8', + prod: '\u220F', + Product: '\u220F', + profalar: '\u232E', + profline: '\u2312', + profsurf: '\u2313', + prop: '\u221D', + Proportion: '\u2237', + Proportional: '\u221D', + propto: '\u221D', + prsim: '\u227E', + prurel: '\u22B0', + Pscr: '\uD835\uDCAB', + pscr: '\uD835\uDCC5', + Psi: '\u03A8', + psi: '\u03C8', + puncsp: '\u2008', + Qfr: '\uD835\uDD14', + qfr: '\uD835\uDD2E', + qint: '\u2A0C', + Qopf: '\u211A', + qopf: '\uD835\uDD62', + qprime: '\u2057', + Qscr: '\uD835\uDCAC', + qscr: '\uD835\uDCC6', + quaternions: '\u210D', + quatint: '\u2A16', + quest: '\u003F', + questeq: '\u225F', + QUOT: '\u0022', + quot: '\u0022', + rAarr: '\u21DB', + race: '\u223D\u0331', + Racute: '\u0154', + racute: '\u0155', + radic: '\u221A', + raemptyv: '\u29B3', + Rang: '\u27EB', + rang: '\u27E9', + rangd: '\u2992', + range: '\u29A5', + rangle: '\u27E9', + raquo: '\u00BB', + Rarr: '\u21A0', + rArr: '\u21D2', + rarr: '\u2192', + rarrap: '\u2975', + rarrb: '\u21E5', + rarrbfs: '\u2920', + rarrc: '\u2933', + rarrfs: '\u291E', + rarrhk: '\u21AA', + rarrlp: '\u21AC', + rarrpl: '\u2945', + rarrsim: '\u2974', + Rarrtl: '\u2916', + rarrtl: '\u21A3', + rarrw: '\u219D', + rAtail: '\u291C', + ratail: '\u291A', + ratio: '\u2236', + rationals: '\u211A', + RBarr: '\u2910', + rBarr: '\u290F', + rbarr: '\u290D', + rbbrk: '\u2773', + rbrace: '\u007D', + rbrack: '\u005D', + rbrke: '\u298C', + rbrksld: '\u298E', + rbrkslu: '\u2990', + Rcaron: '\u0158', + rcaron: '\u0159', + Rcedil: '\u0156', + rcedil: '\u0157', + rceil: '\u2309', + rcub: '\u007D', + Rcy: '\u0420', + rcy: '\u0440', + rdca: '\u2937', + rdldhar: '\u2969', + rdquo: '\u201D', + rdquor: '\u201D', + rdsh: '\u21B3', + Re: '\u211C', + real: '\u211C', + realine: '\u211B', + realpart: '\u211C', + reals: '\u211D', + rect: '\u25AD', + REG: '\u00AE', + reg: '\u00AE', + ReverseElement: '\u220B', + ReverseEquilibrium: '\u21CB', + ReverseUpEquilibrium: '\u296F', + rfisht: '\u297D', + rfloor: '\u230B', + Rfr: '\u211C', + rfr: '\uD835\uDD2F', + rHar: '\u2964', + rhard: '\u21C1', + rharu: '\u21C0', + rharul: '\u296C', + Rho: '\u03A1', + rho: '\u03C1', + rhov: '\u03F1', + RightAngleBracket: '\u27E9', + RightArrow: '\u2192', + Rightarrow: '\u21D2', + rightarrow: '\u2192', + RightArrowBar: '\u21E5', + RightArrowLeftArrow: '\u21C4', + rightarrowtail: '\u21A3', + RightCeiling: '\u2309', + RightDoubleBracket: '\u27E7', + RightDownTeeVector: '\u295D', + RightDownVector: '\u21C2', + RightDownVectorBar: '\u2955', + RightFloor: '\u230B', + rightharpoondown: '\u21C1', + rightharpoonup: '\u21C0', + rightleftarrows: '\u21C4', + rightleftharpoons: '\u21CC', + rightrightarrows: '\u21C9', + rightsquigarrow: '\u219D', + RightTee: '\u22A2', + RightTeeArrow: '\u21A6', + RightTeeVector: '\u295B', + rightthreetimes: '\u22CC', + RightTriangle: '\u22B3', + RightTriangleBar: '\u29D0', + RightTriangleEqual: '\u22B5', + RightUpDownVector: '\u294F', + RightUpTeeVector: '\u295C', + RightUpVector: '\u21BE', + RightUpVectorBar: '\u2954', + RightVector: '\u21C0', + RightVectorBar: '\u2953', + ring: '\u02DA', + risingdotseq: '\u2253', + rlarr: '\u21C4', + rlhar: '\u21CC', + rlm: '\u200F', + rmoust: '\u23B1', + rmoustache: '\u23B1', + rnmid: '\u2AEE', + roang: '\u27ED', + roarr: '\u21FE', + robrk: '\u27E7', + ropar: '\u2986', + Ropf: '\u211D', + ropf: '\uD835\uDD63', + roplus: '\u2A2E', + rotimes: '\u2A35', + RoundImplies: '\u2970', + rpar: '\u0029', + rpargt: '\u2994', + rppolint: '\u2A12', + rrarr: '\u21C9', + Rrightarrow: '\u21DB', + rsaquo: '\u203A', + Rscr: '\u211B', + rscr: '\uD835\uDCC7', + Rsh: '\u21B1', + rsh: '\u21B1', + rsqb: '\u005D', + rsquo: '\u2019', + rsquor: '\u2019', + rthree: '\u22CC', + rtimes: '\u22CA', + rtri: '\u25B9', + rtrie: '\u22B5', + rtrif: '\u25B8', + rtriltri: '\u29CE', + RuleDelayed: '\u29F4', + ruluhar: '\u2968', + rx: '\u211E', + Sacute: '\u015A', + sacute: '\u015B', + sbquo: '\u201A', + Sc: '\u2ABC', + sc: '\u227B', + scap: '\u2AB8', + Scaron: '\u0160', + scaron: '\u0161', + sccue: '\u227D', + scE: '\u2AB4', + sce: '\u2AB0', + Scedil: '\u015E', + scedil: '\u015F', + Scirc: '\u015C', + scirc: '\u015D', + scnap: '\u2ABA', + scnE: '\u2AB6', + scnsim: '\u22E9', + scpolint: '\u2A13', + scsim: '\u227F', + Scy: '\u0421', + scy: '\u0441', + sdot: '\u22C5', + sdotb: '\u22A1', + sdote: '\u2A66', + searhk: '\u2925', + seArr: '\u21D8', + searr: '\u2198', + searrow: '\u2198', + sect: '\u00A7', + semi: '\u003B', + seswar: '\u2929', + setminus: '\u2216', + setmn: '\u2216', + sext: '\u2736', + Sfr: '\uD835\uDD16', + sfr: '\uD835\uDD30', + sfrown: '\u2322', + sharp: '\u266F', + SHCHcy: '\u0429', + shchcy: '\u0449', + SHcy: '\u0428', + shcy: '\u0448', + ShortDownArrow: '\u2193', + ShortLeftArrow: '\u2190', + shortmid: '\u2223', + shortparallel: '\u2225', + ShortRightArrow: '\u2192', + ShortUpArrow: '\u2191', + shy: '\u00AD', + Sigma: '\u03A3', + sigma: '\u03C3', + sigmaf: '\u03C2', + sigmav: '\u03C2', + sim: '\u223C', + simdot: '\u2A6A', + sime: '\u2243', + simeq: '\u2243', + simg: '\u2A9E', + simgE: '\u2AA0', + siml: '\u2A9D', + simlE: '\u2A9F', + simne: '\u2246', + simplus: '\u2A24', + simrarr: '\u2972', + slarr: '\u2190', + SmallCircle: '\u2218', + smallsetminus: '\u2216', + smashp: '\u2A33', + smeparsl: '\u29E4', + smid: '\u2223', + smile: '\u2323', + smt: '\u2AAA', + smte: '\u2AAC', + smtes: '\u2AAC\uFE00', + SOFTcy: '\u042C', + softcy: '\u044C', + sol: '\u002F', + solb: '\u29C4', + solbar: '\u233F', + Sopf: '\uD835\uDD4A', + sopf: '\uD835\uDD64', + spades: '\u2660', + spadesuit: '\u2660', + spar: '\u2225', + sqcap: '\u2293', + sqcaps: '\u2293\uFE00', + sqcup: '\u2294', + sqcups: '\u2294\uFE00', + Sqrt: '\u221A', + sqsub: '\u228F', + sqsube: '\u2291', + sqsubset: '\u228F', + sqsubseteq: '\u2291', + sqsup: '\u2290', + sqsupe: '\u2292', + sqsupset: '\u2290', + sqsupseteq: '\u2292', + squ: '\u25A1', + Square: '\u25A1', + square: '\u25A1', + SquareIntersection: '\u2293', + SquareSubset: '\u228F', + SquareSubsetEqual: '\u2291', + SquareSuperset: '\u2290', + SquareSupersetEqual: '\u2292', + SquareUnion: '\u2294', + squarf: '\u25AA', + squf: '\u25AA', + srarr: '\u2192', + Sscr: '\uD835\uDCAE', + sscr: '\uD835\uDCC8', + ssetmn: '\u2216', + ssmile: '\u2323', + sstarf: '\u22C6', + Star: '\u22C6', + star: '\u2606', + starf: '\u2605', + straightepsilon: '\u03F5', + straightphi: '\u03D5', + strns: '\u00AF', + Sub: '\u22D0', + sub: '\u2282', + subdot: '\u2ABD', + subE: '\u2AC5', + sube: '\u2286', + subedot: '\u2AC3', + submult: '\u2AC1', + subnE: '\u2ACB', + subne: '\u228A', + subplus: '\u2ABF', + subrarr: '\u2979', + Subset: '\u22D0', + subset: '\u2282', + subseteq: '\u2286', + subseteqq: '\u2AC5', + SubsetEqual: '\u2286', + subsetneq: '\u228A', + subsetneqq: '\u2ACB', + subsim: '\u2AC7', + subsub: '\u2AD5', + subsup: '\u2AD3', + succ: '\u227B', + succapprox: '\u2AB8', + succcurlyeq: '\u227D', + Succeeds: '\u227B', + SucceedsEqual: '\u2AB0', + SucceedsSlantEqual: '\u227D', + SucceedsTilde: '\u227F', + succeq: '\u2AB0', + succnapprox: '\u2ABA', + succneqq: '\u2AB6', + succnsim: '\u22E9', + succsim: '\u227F', + SuchThat: '\u220B', + Sum: '\u2211', + sum: '\u2211', + sung: '\u266A', + Sup: '\u22D1', + sup: '\u2283', + sup1: '\u00B9', + sup2: '\u00B2', + sup3: '\u00B3', + supdot: '\u2ABE', + supdsub: '\u2AD8', + supE: '\u2AC6', + supe: '\u2287', + supedot: '\u2AC4', + Superset: '\u2283', + SupersetEqual: '\u2287', + suphsol: '\u27C9', + suphsub: '\u2AD7', + suplarr: '\u297B', + supmult: '\u2AC2', + supnE: '\u2ACC', + supne: '\u228B', + supplus: '\u2AC0', + Supset: '\u22D1', + supset: '\u2283', + supseteq: '\u2287', + supseteqq: '\u2AC6', + supsetneq: '\u228B', + supsetneqq: '\u2ACC', + supsim: '\u2AC8', + supsub: '\u2AD4', + supsup: '\u2AD6', + swarhk: '\u2926', + swArr: '\u21D9', + swarr: '\u2199', + swarrow: '\u2199', + swnwar: '\u292A', + szlig: '\u00DF', + Tab: '\u0009', + target: '\u2316', + Tau: '\u03A4', + tau: '\u03C4', + tbrk: '\u23B4', + Tcaron: '\u0164', + tcaron: '\u0165', + Tcedil: '\u0162', + tcedil: '\u0163', + Tcy: '\u0422', + tcy: '\u0442', + tdot: '\u20DB', + telrec: '\u2315', + Tfr: '\uD835\uDD17', + tfr: '\uD835\uDD31', + there4: '\u2234', + Therefore: '\u2234', + therefore: '\u2234', + Theta: '\u0398', + theta: '\u03B8', + thetasym: '\u03D1', + thetav: '\u03D1', + thickapprox: '\u2248', + thicksim: '\u223C', + ThickSpace: '\u205F\u200A', + thinsp: '\u2009', + ThinSpace: '\u2009', + thkap: '\u2248', + thksim: '\u223C', + THORN: '\u00DE', + thorn: '\u00FE', + Tilde: '\u223C', + tilde: '\u02DC', + TildeEqual: '\u2243', + TildeFullEqual: '\u2245', + TildeTilde: '\u2248', + times: '\u00D7', + timesb: '\u22A0', + timesbar: '\u2A31', + timesd: '\u2A30', + tint: '\u222D', + toea: '\u2928', + top: '\u22A4', + topbot: '\u2336', + topcir: '\u2AF1', + Topf: '\uD835\uDD4B', + topf: '\uD835\uDD65', + topfork: '\u2ADA', + tosa: '\u2929', + tprime: '\u2034', + TRADE: '\u2122', + trade: '\u2122', + triangle: '\u25B5', + triangledown: '\u25BF', + triangleleft: '\u25C3', + trianglelefteq: '\u22B4', + triangleq: '\u225C', + triangleright: '\u25B9', + trianglerighteq: '\u22B5', + tridot: '\u25EC', + trie: '\u225C', + triminus: '\u2A3A', + TripleDot: '\u20DB', + triplus: '\u2A39', + trisb: '\u29CD', + tritime: '\u2A3B', + trpezium: '\u23E2', + Tscr: '\uD835\uDCAF', + tscr: '\uD835\uDCC9', + TScy: '\u0426', + tscy: '\u0446', + TSHcy: '\u040B', + tshcy: '\u045B', + Tstrok: '\u0166', + tstrok: '\u0167', + twixt: '\u226C', + twoheadleftarrow: '\u219E', + twoheadrightarrow: '\u21A0', + Uacute: '\u00DA', + uacute: '\u00FA', + Uarr: '\u219F', + uArr: '\u21D1', + uarr: '\u2191', + Uarrocir: '\u2949', + Ubrcy: '\u040E', + ubrcy: '\u045E', + Ubreve: '\u016C', + ubreve: '\u016D', + Ucirc: '\u00DB', + ucirc: '\u00FB', + Ucy: '\u0423', + ucy: '\u0443', + udarr: '\u21C5', + Udblac: '\u0170', + udblac: '\u0171', + udhar: '\u296E', + ufisht: '\u297E', + Ufr: '\uD835\uDD18', + ufr: '\uD835\uDD32', + Ugrave: '\u00D9', + ugrave: '\u00F9', + uHar: '\u2963', + uharl: '\u21BF', + uharr: '\u21BE', + uhblk: '\u2580', + ulcorn: '\u231C', + ulcorner: '\u231C', + ulcrop: '\u230F', + ultri: '\u25F8', + Umacr: '\u016A', + umacr: '\u016B', + uml: '\u00A8', + UnderBar: '\u005F', + UnderBrace: '\u23DF', + UnderBracket: '\u23B5', + UnderParenthesis: '\u23DD', + Union: '\u22C3', + UnionPlus: '\u228E', + Uogon: '\u0172', + uogon: '\u0173', + Uopf: '\uD835\uDD4C', + uopf: '\uD835\uDD66', + UpArrow: '\u2191', + Uparrow: '\u21D1', + uparrow: '\u2191', + UpArrowBar: '\u2912', + UpArrowDownArrow: '\u21C5', + UpDownArrow: '\u2195', + Updownarrow: '\u21D5', + updownarrow: '\u2195', + UpEquilibrium: '\u296E', + upharpoonleft: '\u21BF', + upharpoonright: '\u21BE', + uplus: '\u228E', + UpperLeftArrow: '\u2196', + UpperRightArrow: '\u2197', + Upsi: '\u03D2', + upsi: '\u03C5', + upsih: '\u03D2', + Upsilon: '\u03A5', + upsilon: '\u03C5', + UpTee: '\u22A5', + UpTeeArrow: '\u21A5', + upuparrows: '\u21C8', + urcorn: '\u231D', + urcorner: '\u231D', + urcrop: '\u230E', + Uring: '\u016E', + uring: '\u016F', + urtri: '\u25F9', + Uscr: '\uD835\uDCB0', + uscr: '\uD835\uDCCA', + utdot: '\u22F0', + Utilde: '\u0168', + utilde: '\u0169', + utri: '\u25B5', + utrif: '\u25B4', + uuarr: '\u21C8', + Uuml: '\u00DC', + uuml: '\u00FC', + uwangle: '\u29A7', + vangrt: '\u299C', + varepsilon: '\u03F5', + varkappa: '\u03F0', + varnothing: '\u2205', + varphi: '\u03D5', + varpi: '\u03D6', + varpropto: '\u221D', + vArr: '\u21D5', + varr: '\u2195', + varrho: '\u03F1', + varsigma: '\u03C2', + varsubsetneq: '\u228A\uFE00', + varsubsetneqq: '\u2ACB\uFE00', + varsupsetneq: '\u228B\uFE00', + varsupsetneqq: '\u2ACC\uFE00', + vartheta: '\u03D1', + vartriangleleft: '\u22B2', + vartriangleright: '\u22B3', + Vbar: '\u2AEB', + vBar: '\u2AE8', + vBarv: '\u2AE9', + Vcy: '\u0412', + vcy: '\u0432', + VDash: '\u22AB', + Vdash: '\u22A9', + vDash: '\u22A8', + vdash: '\u22A2', + Vdashl: '\u2AE6', + Vee: '\u22C1', + vee: '\u2228', + veebar: '\u22BB', + veeeq: '\u225A', + vellip: '\u22EE', + Verbar: '\u2016', + verbar: '\u007C', + Vert: '\u2016', + vert: '\u007C', + VerticalBar: '\u2223', + VerticalLine: '\u007C', + VerticalSeparator: '\u2758', + VerticalTilde: '\u2240', + VeryThinSpace: '\u200A', + Vfr: '\uD835\uDD19', + vfr: '\uD835\uDD33', + vltri: '\u22B2', + vnsub: '\u2282\u20D2', + vnsup: '\u2283\u20D2', + Vopf: '\uD835\uDD4D', + vopf: '\uD835\uDD67', + vprop: '\u221D', + vrtri: '\u22B3', + Vscr: '\uD835\uDCB1', + vscr: '\uD835\uDCCB', + vsubnE: '\u2ACB\uFE00', + vsubne: '\u228A\uFE00', + vsupnE: '\u2ACC\uFE00', + vsupne: '\u228B\uFE00', + Vvdash: '\u22AA', + vzigzag: '\u299A', + Wcirc: '\u0174', + wcirc: '\u0175', + wedbar: '\u2A5F', + Wedge: '\u22C0', + wedge: '\u2227', + wedgeq: '\u2259', + weierp: '\u2118', + Wfr: '\uD835\uDD1A', + wfr: '\uD835\uDD34', + Wopf: '\uD835\uDD4E', + wopf: '\uD835\uDD68', + wp: '\u2118', + wr: '\u2240', + wreath: '\u2240', + Wscr: '\uD835\uDCB2', + wscr: '\uD835\uDCCC', + xcap: '\u22C2', + xcirc: '\u25EF', + xcup: '\u22C3', + xdtri: '\u25BD', + Xfr: '\uD835\uDD1B', + xfr: '\uD835\uDD35', + xhArr: '\u27FA', + xharr: '\u27F7', + Xi: '\u039E', + xi: '\u03BE', + xlArr: '\u27F8', + xlarr: '\u27F5', + xmap: '\u27FC', + xnis: '\u22FB', + xodot: '\u2A00', + Xopf: '\uD835\uDD4F', + xopf: '\uD835\uDD69', + xoplus: '\u2A01', + xotime: '\u2A02', + xrArr: '\u27F9', + xrarr: '\u27F6', + Xscr: '\uD835\uDCB3', + xscr: '\uD835\uDCCD', + xsqcup: '\u2A06', + xuplus: '\u2A04', + xutri: '\u25B3', + xvee: '\u22C1', + xwedge: '\u22C0', + Yacute: '\u00DD', + yacute: '\u00FD', + YAcy: '\u042F', + yacy: '\u044F', + Ycirc: '\u0176', + ycirc: '\u0177', + Ycy: '\u042B', + ycy: '\u044B', + yen: '\u00A5', + Yfr: '\uD835\uDD1C', + yfr: '\uD835\uDD36', + YIcy: '\u0407', + yicy: '\u0457', + Yopf: '\uD835\uDD50', + yopf: '\uD835\uDD6A', + Yscr: '\uD835\uDCB4', + yscr: '\uD835\uDCCE', + YUcy: '\u042E', + yucy: '\u044E', + Yuml: '\u0178', + yuml: '\u00FF', + Zacute: '\u0179', + zacute: '\u017A', + Zcaron: '\u017D', + zcaron: '\u017E', + Zcy: '\u0417', + zcy: '\u0437', + Zdot: '\u017B', + zdot: '\u017C', + zeetrf: '\u2128', + ZeroWidthSpace: '\u200B', + Zeta: '\u0396', + zeta: '\u03B6', + Zfr: '\u2128', + zfr: '\uD835\uDD37', + ZHcy: '\u0416', + zhcy: '\u0436', + zigrarr: '\u21DD', + Zopf: '\u2124', + zopf: '\uD835\uDD6B', + Zscr: '\uD835\uDCB5', + zscr: '\uD835\uDCCF', + zwj: '\u200D', + zwnj: '\u200C', +}); + +/** + * @deprecated + * Use `HTML_ENTITIES` instead. + * @see {@link HTML_ENTITIES} + */ +exports.entityMap = exports.HTML_ENTITIES; diff --git a/node_modules/@xmldom/xmldom/lib/errors.js b/node_modules/@xmldom/xmldom/lib/errors.js new file mode 100644 index 000000000..996cd3665 --- /dev/null +++ b/node_modules/@xmldom/xmldom/lib/errors.js @@ -0,0 +1,205 @@ +'use strict'; + +var conventions = require('./conventions'); + +function extendError(constructor, writableName) { + constructor.prototype = Object.create(Error.prototype, { + constructor: { value: constructor }, + name: { value: constructor.name, enumerable: true, writable: writableName }, + }); +} + +var DOMExceptionName = conventions.freeze({ + /** + * the default value as defined by the spec + */ + Error: 'Error', + /** + * @deprecated + * Use RangeError instead. + */ + IndexSizeError: 'IndexSizeError', + /** + * @deprecated + * Just to match the related static code, not part of the spec. + */ + DomstringSizeError: 'DomstringSizeError', + HierarchyRequestError: 'HierarchyRequestError', + WrongDocumentError: 'WrongDocumentError', + InvalidCharacterError: 'InvalidCharacterError', + /** + * @deprecated + * Just to match the related static code, not part of the spec. + */ + NoDataAllowedError: 'NoDataAllowedError', + NoModificationAllowedError: 'NoModificationAllowedError', + NotFoundError: 'NotFoundError', + NotSupportedError: 'NotSupportedError', + InUseAttributeError: 'InUseAttributeError', + InvalidStateError: 'InvalidStateError', + SyntaxError: 'SyntaxError', + InvalidModificationError: 'InvalidModificationError', + NamespaceError: 'NamespaceError', + /** + * @deprecated + * Use TypeError for invalid arguments, + * "NotSupportedError" DOMException for unsupported operations, + * and "NotAllowedError" DOMException for denied requests instead. + */ + InvalidAccessError: 'InvalidAccessError', + /** + * @deprecated + * Just to match the related static code, not part of the spec. + */ + ValidationError: 'ValidationError', + /** + * @deprecated + * Use TypeError instead. + */ + TypeMismatchError: 'TypeMismatchError', + SecurityError: 'SecurityError', + NetworkError: 'NetworkError', + AbortError: 'AbortError', + /** + * @deprecated + * Just to match the related static code, not part of the spec. + */ + URLMismatchError: 'URLMismatchError', + QuotaExceededError: 'QuotaExceededError', + TimeoutError: 'TimeoutError', + InvalidNodeTypeError: 'InvalidNodeTypeError', + DataCloneError: 'DataCloneError', + EncodingError: 'EncodingError', + NotReadableError: 'NotReadableError', + UnknownError: 'UnknownError', + ConstraintError: 'ConstraintError', + DataError: 'DataError', + TransactionInactiveError: 'TransactionInactiveError', + ReadOnlyError: 'ReadOnlyError', + VersionError: 'VersionError', + OperationError: 'OperationError', + NotAllowedError: 'NotAllowedError', + OptOutError: 'OptOutError', +}); +var DOMExceptionNames = Object.keys(DOMExceptionName); + +function isValidDomExceptionCode(value) { + return typeof value === 'number' && value >= 1 && value <= 25; +} +function endsWithError(value) { + return typeof value === 'string' && value.substring(value.length - DOMExceptionName.Error.length) === DOMExceptionName.Error; +} +/** + * DOM operations only raise exceptions in "exceptional" circumstances, i.e., when an operation + * is impossible to perform (either for logical reasons, because data is lost, or because the + * implementation has become unstable). In general, DOM methods return specific error values in + * ordinary processing situations, such as out-of-bound errors when using NodeList. + * + * Implementations should raise other exceptions under other circumstances. For example, + * implementations should raise an implementation-dependent exception if a null argument is + * passed when null was not expected. + * + * This implementation supports the following usages: + * 1. according to the living standard (both arguments are optional): + * ``` + * new DOMException("message (can be empty)", DOMExceptionNames.HierarchyRequestError) + * ``` + * 2. according to previous xmldom implementation (only the first argument is required): + * ``` + * new DOMException(DOMException.HIERARCHY_REQUEST_ERR, "optional message") + * ``` + * both result in the proper name being set. + * + * @class DOMException + * @param {number | string} messageOrCode + * The reason why an operation is not acceptable. + * If it is a number, it is used to determine the `name`, see + * {@link https://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-258A00AF ExceptionCode} + * @param {string | keyof typeof DOMExceptionName | Error} [nameOrMessage] + * The `name` to use for the error. + * If `messageOrCode` is a number, this arguments is used as the `message` instead. + * @augments Error + * @see https://webidl.spec.whatwg.org/#idl-DOMException + * @see https://webidl.spec.whatwg.org/#dfn-error-names-table + * @see https://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-17189187 + * @see http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/ecma-script-binding.html + * @see http://www.w3.org/TR/REC-DOM-Level-1/ecma-script-language-binding.html + */ +function DOMException(messageOrCode, nameOrMessage) { + // support old way of passing arguments: first argument is a valid number + if (isValidDomExceptionCode(messageOrCode)) { + this.name = DOMExceptionNames[messageOrCode]; + this.message = nameOrMessage || ''; + } else { + this.message = messageOrCode; + this.name = endsWithError(nameOrMessage) ? nameOrMessage : DOMExceptionName.Error; + } + if (Error.captureStackTrace) Error.captureStackTrace(this, DOMException); +} +extendError(DOMException, true); +Object.defineProperties(DOMException.prototype, { + code: { + enumerable: true, + get: function () { + var code = DOMExceptionNames.indexOf(this.name); + if (isValidDomExceptionCode(code)) return code; + return 0; + }, + }, +}); + +var ExceptionCode = { + INDEX_SIZE_ERR: 1, + DOMSTRING_SIZE_ERR: 2, + HIERARCHY_REQUEST_ERR: 3, + WRONG_DOCUMENT_ERR: 4, + INVALID_CHARACTER_ERR: 5, + NO_DATA_ALLOWED_ERR: 6, + NO_MODIFICATION_ALLOWED_ERR: 7, + NOT_FOUND_ERR: 8, + NOT_SUPPORTED_ERR: 9, + INUSE_ATTRIBUTE_ERR: 10, + INVALID_STATE_ERR: 11, + SYNTAX_ERR: 12, + INVALID_MODIFICATION_ERR: 13, + NAMESPACE_ERR: 14, + INVALID_ACCESS_ERR: 15, + VALIDATION_ERR: 16, + TYPE_MISMATCH_ERR: 17, + SECURITY_ERR: 18, + NETWORK_ERR: 19, + ABORT_ERR: 20, + URL_MISMATCH_ERR: 21, + QUOTA_EXCEEDED_ERR: 22, + TIMEOUT_ERR: 23, + INVALID_NODE_TYPE_ERR: 24, + DATA_CLONE_ERR: 25, +}; + +var entries = Object.entries(ExceptionCode); +for (var i = 0; i < entries.length; i++) { + var key = entries[i][0]; + DOMException[key] = entries[i][1]; +} + +/** + * Creates an error that will not be caught by XMLReader aka the SAX parser. + * + * @class + * @param {string} message + * @param {any} [locator] + * @param {Error} [cause] + * The error that caused this one, e.g. a `DOMException` thrown while building the DOM. + */ +function ParseError(message, locator, cause) { + this.message = message; + this.locator = locator; + this.cause = cause; + if (Error.captureStackTrace) Error.captureStackTrace(this, ParseError); +} +extendError(ParseError); + +exports.DOMException = DOMException; +exports.DOMExceptionName = DOMExceptionName; +exports.ExceptionCode = ExceptionCode; +exports.ParseError = ParseError; diff --git a/node_modules/@xmldom/xmldom/lib/grammar.js b/node_modules/@xmldom/xmldom/lib/grammar.js new file mode 100644 index 000000000..01bf0a34d --- /dev/null +++ b/node_modules/@xmldom/xmldom/lib/grammar.js @@ -0,0 +1,559 @@ +'use strict'; + +/** + * Detects relevant unicode support for regular expressions in the runtime. + * Should the runtime not accepts the flag `u` or unicode ranges, + * character classes without unicode handling will be used. + * + * @param {typeof RegExp} [RegExpImpl=RegExp] + * For testing: the RegExp class. + * @returns {boolean} + * @see https://node.green/#ES2015-syntax-RegExp--y--and--u--flags + */ +function detectUnicodeSupport(RegExpImpl) { + try { + if (typeof RegExpImpl !== 'function') { + RegExpImpl = RegExp; + } + // eslint-disable-next-line es5/no-unicode-regex,es5/no-unicode-code-point-escape + var match = new RegExpImpl('\u{1d306}', 'u').exec('𝌆'); + return !!match && match[0].length === 2; + } catch (error) {} + return false; +} +var UNICODE_SUPPORT = detectUnicodeSupport(); + +/** + * Removes `[`, `]` and any trailing quantifiers from the source of a RegExp. + * + * @param {RegExp} regexp + */ +function chars(regexp) { + if (regexp.source[0] !== '[') { + throw new Error(regexp + ' can not be used with chars'); + } + return regexp.source.slice(1, regexp.source.lastIndexOf(']')); +} + +/** + * Creates a new character list regular expression, + * by removing `search` from the source of `regexp`. + * + * @param {RegExp} regexp + * @param {string} search + * The character(s) to remove. + * @returns {RegExp} + */ +function chars_without(regexp, search) { + if (regexp.source[0] !== '[') { + throw new Error('/' + regexp.source + '/ can not be used with chars_without'); + } + if (!search || typeof search !== 'string') { + throw new Error(JSON.stringify(search) + ' is not a valid search'); + } + if (regexp.source.indexOf(search) === -1) { + throw new Error('"' + search + '" is not is /' + regexp.source + '/'); + } + if (search === '-' && regexp.source.indexOf(search) !== 1) { + throw new Error('"' + search + '" is not at the first postion of /' + regexp.source + '/'); + } + return new RegExp(regexp.source.replace(search, ''), UNICODE_SUPPORT ? 'u' : ''); +} + +/** + * Combines and Regular expressions correctly by using `RegExp.source`. + * + * @param {...(RegExp | string)[]} args + * @returns {RegExp} + */ +function reg(args) { + var self = this; + return new RegExp( + Array.prototype.slice + .call(arguments) + .map(function (part) { + var isStr = typeof part === 'string'; + if (isStr && self === undefined && part === '|') { + throw new Error('use regg instead of reg to wrap expressions with `|`!'); + } + return isStr ? part : part.source; + }) + .join(''), + UNICODE_SUPPORT ? 'u' : '' + ); +} + +/** + * Like `reg` but wraps the expression in `(?:`,`)` to create a non tracking group. + * + * @param {...(RegExp | string)[]} args + * @returns {RegExp} + */ +function regg(args) { + if (arguments.length === 0) { + throw new Error('no parameters provided'); + } + return reg.apply(regg, ['(?:'].concat(Array.prototype.slice.call(arguments), [')'])); +} + +// /** +// * Append ^ to the beginning of the expression. +// * @param {...(RegExp | string)[]} args +// * @returns {RegExp} +// */ +// function reg_start(args) { +// if (arguments.length === 0) { +// throw new Error('no parameters provided'); +// } +// return reg.apply(reg_start, ['^'].concat(Array.prototype.slice.call(arguments))); +// } + +// https://www.w3.org/TR/xml/#document +// `[1] document ::= prolog element Misc*` +// https://www.w3.org/TR/xml11/#NT-document +// `[1] document ::= ( prolog element Misc* ) - ( Char* RestrictedChar Char* )` + +/** + * A character usually appearing in wrongly converted strings. + * + * @type {string} + * @see https://en.wikipedia.org/wiki/Specials_(Unicode_block)#Replacement_character + * @see https://nodejs.dev/en/api/v18/buffer/#buffers-and-character-encodings + * @see https://www.unicode.org/faq/utf_bom.html#BOM + * @readonly + */ +var UNICODE_REPLACEMENT_CHARACTER = '\uFFFD'; +// https://www.w3.org/TR/xml/#NT-Char +// any Unicode character, excluding the surrogate blocks, FFFE, and FFFF. +// `[2] Char ::= #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]` +// https://www.w3.org/TR/xml11/#NT-Char +// `[2] Char ::= [#x1-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]` +// https://www.w3.org/TR/xml11/#NT-RestrictedChar +// `[2a] RestrictedChar ::= [#x1-#x8] | [#xB-#xC] | [#xE-#x1F] | [#x7F-#x84] | [#x86-#x9F]` +// https://www.w3.org/TR/xml11/#charsets +var Char = /[-\x09\x0A\x0D\x20-\x2C\x2E-\uD7FF\uE000-\uFFFD]/; // without \u10000-\uEFFFF +if (UNICODE_SUPPORT) { + // eslint-disable-next-line es5/no-unicode-code-point-escape + Char = reg('[', chars(Char), '\\u{10000}-\\u{10FFFF}', ']'); +} +// Negation of Char: matches any character that is NOT a valid XML 1.0 Char. +// Derived directly from the Char character class above (after the unicode-support extension). +// XML 1.0 Char production [2]: #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF] +// @see https://www.w3.org/TR/xml/#NT-Char +var InvalidChar = new RegExp('[^' + chars(Char) + ']', UNICODE_SUPPORT ? 'u' : ''); + +var _SChar = /[\x20\x09\x0D\x0A]/; +var SChar_s = chars(_SChar); +// https://www.w3.org/TR/xml11/#NT-S +// `[3] S ::= (#x20 | #x9 | #xD | #xA)+` +var S = reg(_SChar, '+'); +// optional whitespace described as `S?` in the grammar, +// simplified to 0-n occurrences of the character class +// instead of 0-1 occurrences of a non-capturing group around S +var S_OPT = reg(_SChar, '*'); + +// https://www.w3.org/TR/xml11/#NT-NameStartChar +// `[4] NameStartChar ::= ":" | [A-Z] | "_" | [a-z] | [#xC0-#xD6] | [#xD8-#xF6] | [#xF8-#x2FF] | [#x370-#x37D] | [#x37F-#x1FFF] | [#x200C-#x200D] | [#x2070-#x218F] | [#x2C00-#x2FEF] | [#x3001-#xD7FF] | [#xF900-#xFDCF] | [#xFDF0-#xFFFD] | [#x10000-#xEFFFF]` +var NameStartChar = + /[:_a-zA-Z\xC0-\xD6\xD8-\xF6\xF8-\u02FF\u0370-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD]/; // without \u10000-\uEFFFF +if (UNICODE_SUPPORT) { + // eslint-disable-next-line es5/no-unicode-code-point-escape + NameStartChar = reg('[', chars(NameStartChar), '\\u{10000}-\\u{10FFFF}', ']'); +} +var NameStartChar_s = chars(NameStartChar); + +// https://www.w3.org/TR/xml11/#NT-NameChar +// `[4a] NameChar ::= NameStartChar | "-" | "." | [0-9] | #xB7 | [#x0300-#x036F] | [#x203F-#x2040]` +var NameChar = reg('[', NameStartChar_s, chars(/[-.0-9\xB7]/), chars(/[\u0300-\u036F\u203F-\u2040]/), ']'); +// https://www.w3.org/TR/xml11/#NT-Name +// `[5] Name ::= NameStartChar (NameChar)*` +var Name = reg(NameStartChar, NameChar, '*'); +// Full-string anchored matcher for requireWellFormed serializer checks +// https://w3c.github.io/DOM-Parsing/#xml-serializing-a-document-node +var Name_exact = reg('^', Name, '$'); +/* +https://www.w3.org/TR/xml11/#NT-Names +`[6] Names ::= Name (#x20 Name)*` +*/ + +// https://www.w3.org/TR/xml11/#NT-Nmtoken +// `[7] Nmtoken ::= (NameChar)+` +var Nmtoken = reg(NameChar, '+'); +/* +https://www.w3.org/TR/xml11/#NT-Nmtokens +`[8] Nmtokens ::= Nmtoken (#x20 Nmtoken)*` +var Nmtokens = reg(Nmtoken, regg(/\x20/, Nmtoken), '*'); +*/ + +// https://www.w3.org/TR/xml11/#NT-EntityRef +// `[68] EntityRef ::= '&' Name ';'` [WFC: Entity Declared] [VC: Entity Declared] [WFC: Parsed Entity] [WFC: No Recursion] +var EntityRef = reg('&', Name, ';'); +// https://www.w3.org/TR/xml11/#NT-CharRef +// `[66] CharRef ::= '&#' [0-9]+ ';' | '&#x' [0-9a-fA-F]+ ';'` [WFC: Legal Character] +var CharRef = regg(/&#[0-9]+;|&#x[0-9a-fA-F]+;/); + +/* +https://www.w3.org/TR/xml11/#NT-Reference +- `[67] Reference ::= EntityRef | CharRef` +- `[66] CharRef ::= '&#' [0-9]+ ';' | '&#x' [0-9a-fA-F]+ ';'` [WFC: Legal Character] +- `[68] EntityRef ::= '&' Name ';'` [WFC: Entity Declared] [VC: Entity Declared] [WFC: Parsed Entity] [WFC: No Recursion] +*/ +var Reference = regg(EntityRef, '|', CharRef); + +// https://www.w3.org/TR/xml11/#NT-PEReference +// `[69] PEReference ::= '%' Name ';'` +// [VC: Entity Declared] [WFC: No Recursion] [WFC: In DTD] +var PEReference = reg('%', Name, ';'); + +// https://www.w3.org/TR/xml11/#NT-EntityValue +// `[9] EntityValue ::= '"' ([^%&"] | PEReference | Reference)* '"' | "'" ([^%&'] | PEReference | Reference)* "'"` +var EntityValue = regg( + reg('"', regg(/[^%&"]/, '|', PEReference, '|', Reference), '*', '"'), + '|', + reg("'", regg(/[^%&']/, '|', PEReference, '|', Reference), '*', "'") +); + +// https://www.w3.org/TR/xml11/#NT-AttValue +// `[10] AttValue ::= '"' ([^<&"] | Reference)* '"' | "'" ([^<&'] | Reference)* "'"` +var AttValue = regg('"', regg(/[^<&"]/, '|', Reference), '*', '"', '|', "'", regg(/[^<&']/, '|', Reference), '*', "'"); + +// https://www.w3.org/TR/xml-names/#ns-decl +// https://www.w3.org/TR/xml-names/#ns-qualnames +// NameStartChar without ":" +var NCNameStartChar = chars_without(NameStartChar, ':'); +// https://www.w3.org/TR/xml-names/#orphans +// `[5] NCNameChar ::= NameChar - ':'` +// An XML NameChar, minus the ":" +var NCNameChar = chars_without(NameChar, ':'); +// https://www.w3.org/TR/xml-names/#NT-NCName +// `[4] NCName ::= Name - (Char* ':' Char*)` +// An XML Name, minus the ":" +var NCName = reg(NCNameStartChar, NCNameChar, '*'); +// Full-string anchored matcher for requireWellFormed serializer checks +// https://w3c.github.io/DOM-Parsing/#xml-serializing-a-document-node +var NCName_exact = reg('^', NCName, '$'); + +/** +https://www.w3.org/TR/xml-names/#ns-qualnames + +``` +[7] QName ::= PrefixedName | UnprefixedName + === (NCName ':' NCName) | NCName + === NCName (':' NCName)? +[8] PrefixedName ::= Prefix ':' LocalPart + === NCName ':' NCName +[9] UnprefixedName ::= LocalPart + === NCName +[10] Prefix ::= NCName +[11] LocalPart ::= NCName +``` +*/ +var QName = reg(NCName, regg(':', NCName), '?'); +var QName_exact = reg('^', QName, '$'); +var QName_group = reg('(', QName, ')'); + +// https://www.w3.org/TR/xml11/#NT-SystemLiteral +// `[11] SystemLiteral ::= ('"' [^"]* '"') | ("'" [^']* "'")` +var SystemLiteral = regg(/"[^"]*"|'[^']*'/); + +/* + https://www.w3.org/TR/xml11/#NT-PI + ``` + [17] PITarget ::= Name - (('X' | 'x') ('M' | 'm') ('L' | 'l')) + [16] PI ::= '' Char*)))? '?>' + ``` + target /xml/i is not excluded! +*/ +// The `(?!S)` after the leading `S+` asserts the data starts with a non-whitespace +// character (greedy `S+` already consumes all separating whitespace), pruning the +// `S+`/`Char*?` whitespace overlap that otherwise makes an unterminated PI (no `?>`) +// backtrack quadratically. The lookahead is non-capturing, so the data stays group 2. +var PI = reg(/^<\?/, '(', Name, ')', regg(S, '(?!', _SChar, ')(', Char, '*?)'), '?', /\?>/); + +// https://www.w3.org/TR/xml11/#NT-PubidChar +// `[13] PubidChar ::= #x20 | #xD | #xA | [a-zA-Z0-9] | [-'()+,./:=?;!*#@$_%]` +var PubidChar = /[\x20\x0D\x0Aa-zA-Z0-9-'()+,./:=?;!*#@$_%]/; + +// https://www.w3.org/TR/xml11/#NT-PubidLiteral +// `[12] PubidLiteral ::= '"' PubidChar* '"' | "'" (PubidChar - "'")* "'"` +var PubidLiteral = regg('"', PubidChar, '*"', '|', "'", chars_without(PubidChar, "'"), "*'"); + +// https://www.w3.org/TR/xml11/#NT-CharData +// `[14] CharData ::= [^<&]* - ([^<&]* ']]>' [^<&]*)` + +var COMMENT_START = ''; +// https://www.w3.org/TR/xml11/#NT-Comment +// `[15] Comment ::= ''` +var Comment = reg(COMMENT_START, regg(chars_without(Char, '-'), '|', reg('-', chars_without(Char, '-'))), '*', COMMENT_END); + +var PCDATA = '#PCDATA'; +// https://www.w3.org/TR/xml11/#NT-Mixed +// `[51] Mixed ::= '(' S? '#PCDATA' (S? '|' S? Name)* S? ')*' | '(' S? '#PCDATA' S? ')'` +// https://www.w3.org/TR/xml-names/#NT-Mixed +// `[51] Mixed ::= '(' S? '#PCDATA' (S? '|' S? QName)* S? ')*' | '(' S? '#PCDATA' S? ')'` +// [VC: Proper Group/PE Nesting] [VC: No Duplicate Types] +var Mixed = regg( + reg(/\(/, S_OPT, PCDATA, regg(S_OPT, /\|/, S_OPT, QName), '*', S_OPT, /\)\*/), + '|', + reg(/\(/, S_OPT, PCDATA, S_OPT, /\)/) +); + +var _children_quantity = /[?*+]?/; +/* + `[49] choice ::= '(' S? cp ( S? '|' S? cp )+ S? ')'` [VC: Proper Group/PE Nesting] + `[50] seq ::= '(' S? cp ( S? ',' S? cp )* S? ')'` [VC: Proper Group/PE Nesting] + simplification to solve circular referencing, but doesn't check validity constraint "Proper Group/PE Nesting" + var _choice_or_seq = reg('[', NameChar_s, SChar_s, chars(_children_quantity), '()|,]*'); + ``` + [48] cp ::= (Name | choice | seq) ('?' | '*' | '+')? + === (Name | '(' S? cp ( S? '|' S? cp )+ S? ')' | '(' S? cp ( S? ',' S? cp )* S? ')') ('?' | '*' | '+')? + !== (Name | [_choice_or_seq]*) ('?' | '*' | '+')? + ``` + simplification to solve circular referencing, but doesn't check validity constraint "Proper Group/PE Nesting" + var cp = reg(regg(Name, '|', _choice_or_seq), _children_quantity); +*/ +/* +Inefficient regular expression (High) +This part of the regular expression may cause exponential backtracking on strings starting with '(|' and containing many repetitions of '|'. +https://github.com/xmldom/xmldom/security/code-scanning/91 +var choice = regg(/\(/, S_OPT, cp, regg(S_OPT, /\|/, S_OPT, cp), '+', S_OPT, /\)/); +*/ +/* +Inefficient regular expression (High) +This part of the regular expression may cause exponential backtracking on strings starting with '(,' and containing many repetitions of ','. +https://github.com/xmldom/xmldom/security/code-scanning/92 +var seq = regg(/\(/, S_OPT, cp, regg(S_OPT, /,/, S_OPT, cp), '*', S_OPT, /\)/); +*/ + +// `[47] children ::= (choice | seq) ('?' | '*' | '+')?` +// simplification to solve circular referencing, but doesn't check validity constraint "Proper Group/PE Nesting" +var children = reg(/\([^>]+\)/, _children_quantity /*regg(choice, '|', seq), _children_quantity*/); + +// https://www.w3.org/TR/xml11/#NT-contentspec +// `[46] contentspec ::= 'EMPTY' | 'ANY' | Mixed | children` +var contentspec = regg('EMPTY', '|', 'ANY', '|', Mixed, '|', children); + +var ELEMENTDECL_START = ''` +// https://www.w3.org/TR/xml-names/#NT-elementdecl +// `[17] elementdecl ::= ''` +// because of https://www.w3.org/TR/xml11/#NT-PEReference +// since xmldom is not supporting replacements of PEReferences in the DTD +// this also supports PEReference in the possible places +var elementdecl = reg(ELEMENTDECL_START, S, regg(QName, '|', PEReference), S, regg(contentspec, '|', PEReference), S_OPT, '>'); + +// https://www.w3.org/TR/xml11/#NT-NotationType +// `[58] NotationType ::= 'NOTATION' S '(' S? Name (S? '|' S? Name)* S? ')'` +// [VC: Notation Attributes] [VC: One Notation Per Element Type] [VC: No Notation on Empty Element] [VC: No Duplicate Tokens] +var NotationType = reg('NOTATION', S, /\(/, S_OPT, Name, regg(S_OPT, /\|/, S_OPT, Name), '*', S_OPT, /\)/); +// https://www.w3.org/TR/xml11/#NT-Enumeration +// `[59] Enumeration ::= '(' S? Nmtoken (S? '|' S? Nmtoken)* S? ')'` +// [VC: Enumeration] [VC: No Duplicate Tokens] +var Enumeration = reg(/\(/, S_OPT, Nmtoken, regg(S_OPT, /\|/, S_OPT, Nmtoken), '*', S_OPT, /\)/); + +// https://www.w3.org/TR/xml11/#NT-EnumeratedType +// `[57] EnumeratedType ::= NotationType | Enumeration` +var EnumeratedType = regg(NotationType, '|', Enumeration); + +/* +``` +[55] StringType ::= 'CDATA' +[56] TokenizedType ::= 'ID' [VC: ID] [VC: One ID per Element Type] [VC: ID Attribute Default] + | 'IDREF' [VC: IDREF] + | 'IDREFS' [VC: IDREF] + | 'ENTITY' [VC: Entity Name] + | 'ENTITIES' [VC: Entity Name] + | 'NMTOKEN' [VC: Name Token] + | 'NMTOKENS' [VC: Name Token] + [54] AttType ::= StringType | TokenizedType | EnumeratedType +```*/ +var AttType = regg(/CDATA|ID|IDREF|IDREFS|ENTITY|ENTITIES|NMTOKEN|NMTOKENS/, '|', EnumeratedType); + +// `[60] DefaultDecl ::= '#REQUIRED' | '#IMPLIED' | (('#FIXED' S)? AttValue)` +// [WFC: No < in Attribute Values] [WFC: No External Entity References] +// [VC: Fixed Attribute Default] [VC: Required Attribute] [VC: Attribute Default Value Syntactically Correct] +var DefaultDecl = regg(/#REQUIRED|#IMPLIED/, '|', regg(regg('#FIXED', S), '?', AttValue)); + +// https://www.w3.org/TR/xml11/#NT-AttDef +// [53] AttDef ::= S Name S AttType S DefaultDecl +// https://www.w3.org/TR/xml-names/#NT-AttDef +// [1] NSAttName ::= PrefixedAttName | DefaultAttName +// [2] PrefixedAttName ::= 'xmlns:' NCName [NSC: Reserved Prefixes and Namespace Names] +// [3] DefaultAttName ::= 'xmlns' +// [21] AttDef ::= S (QName | NSAttName) S AttType S DefaultDecl +// === S Name S AttType S DefaultDecl +// xmldom is not distinguishing between QName and NSAttName on this level +// to support XML without namespaces in DTD we can not restrict it to QName +var AttDef = regg(S, Name, S, AttType, S, DefaultDecl); + +var ATTLIST_DECL_START = ''` +// https://www.w3.org/TR/xml-names/#NT-AttlistDecl +// `[20] AttlistDecl ::= ''` +// to support XML without namespaces in DTD we can not restrict it to QName +var AttlistDecl = reg(ATTLIST_DECL_START, S, Name, AttDef, '*', S_OPT, '>'); + +// https://html.spec.whatwg.org/multipage/urls-and-fetching.html#about:legacy-compat +var ABOUT_LEGACY_COMPAT = 'about:legacy-compat'; +var ABOUT_LEGACY_COMPAT_SystemLiteral = regg('"' + ABOUT_LEGACY_COMPAT + '"', '|', "'" + ABOUT_LEGACY_COMPAT + "'"); +var SYSTEM = 'SYSTEM'; +var PUBLIC = 'PUBLIC'; +// https://www.w3.org/TR/xml11/#NT-ExternalID +// `[75] ExternalID ::= 'SYSTEM' S SystemLiteral | 'PUBLIC' S PubidLiteral S SystemLiteral` +var ExternalID = regg(regg(SYSTEM, S, SystemLiteral), '|', regg(PUBLIC, S, PubidLiteral, S, SystemLiteral)); +var ExternalID_match = reg( + '^', + regg( + regg(SYSTEM, S, '(?', SystemLiteral, ')'), + '|', + regg(PUBLIC, S, '(?', PubidLiteral, ')', S, '(?', SystemLiteral, ')') + ) +); +// Full-string anchored matcher for requireWellFormed serializer checks +// https://w3c.github.io/DOM-Parsing/#xml-serializing-a-document-node +var PubidLiteral_match = reg('^', PubidLiteral, '$'); +// Full-string anchored matcher for requireWellFormed serializer checks +// https://w3c.github.io/DOM-Parsing/#xml-serializing-a-document-node +var SystemLiteral_match = reg('^', SystemLiteral, '$'); + +// https://www.w3.org/TR/xml11/#NT-NDataDecl +// `[76] NDataDecl ::= S 'NDATA' S Name` [VC: Notation Declared] +var NDataDecl = regg(S, 'NDATA', S, Name); + +// https://www.w3.org/TR/xml11/#NT-EntityDef +// `[73] EntityDef ::= EntityValue | (ExternalID NDataDecl?)` +var EntityDef = regg(EntityValue, '|', regg(ExternalID, NDataDecl, '?')); + +var ENTITY_DECL_START = ''` +var GEDecl = reg(ENTITY_DECL_START, S, Name, S, EntityDef, S_OPT, '>'); +// https://www.w3.org/TR/xml11/#NT-PEDef +// `[74] PEDef ::= EntityValue | ExternalID` +var PEDef = regg(EntityValue, '|', ExternalID); +// https://www.w3.org/TR/xml11/#NT-PEDecl +// `[72] PEDecl ::= ''` +var PEDecl = reg(ENTITY_DECL_START, S, '%', S, Name, S, PEDef, S_OPT, '>'); +// https://www.w3.org/TR/xml11/#NT-EntityDecl +// `[70] EntityDecl ::= GEDecl | PEDecl` +var EntityDecl = regg(GEDecl, '|', PEDecl); + +// https://www.w3.org/TR/xml11/#NT-PublicID +// `[83] PublicID ::= 'PUBLIC' S PubidLiteral` +var PublicID = reg(PUBLIC, S, PubidLiteral); +// https://www.w3.org/TR/xml11/#NT-NotationDecl +// `[82] NotationDecl ::= ''` [VC: Unique Notation Name] +var NotationDecl = reg(''); + +// https://www.w3.org/TR/xml11/#NT-Eq +// `[25] Eq ::= S? '=' S?` +var Eq = reg(S_OPT, '=', S_OPT); +// https://www.w3.org/TR/xml/#NT-VersionNum +// `[26] VersionNum ::= '1.' [0-9]+` +// https://www.w3.org/TR/xml11/#NT-VersionNum +// `[26] VersionNum ::= '1.1'` +var VersionNum = /1[.]\d+/; +// https://www.w3.org/TR/xml11/#NT-VersionInfo +// `[24] VersionInfo ::= S 'version' Eq ("'" VersionNum "'" | '"' VersionNum '"')` +var VersionInfo = reg(S, 'version', Eq, regg("'", VersionNum, "'", '|', '"', VersionNum, '"')); +// https://www.w3.org/TR/xml11/#NT-EncName +// `[81] EncName ::= [A-Za-z] ([A-Za-z0-9._] | '-')*` +var EncName = /[A-Za-z][-A-Za-z0-9._]*/; +// https://www.w3.org/TR/xml11/#NT-EncDecl +// `[80] EncodingDecl ::= S 'encoding' Eq ('"' EncName '"' | "'" EncName "'" )` +var EncodingDecl = regg(S, 'encoding', Eq, regg('"', EncName, '"', '|', "'", EncName, "'")); +// https://www.w3.org/TR/xml11/#NT-SDDecl +// `[32] SDDecl ::= S 'standalone' Eq (("'" ('yes' | 'no') "'") | ('"' ('yes' | 'no') '"'))` +var SDDecl = regg(S, 'standalone', Eq, regg("'", regg('yes', '|', 'no'), "'", '|', '"', regg('yes', '|', 'no'), '"')); +// https://www.w3.org/TR/xml11/#NT-XMLDecl +// [23] XMLDecl ::= '' +var XMLDecl = reg(/^<\?xml/, VersionInfo, EncodingDecl, '?', SDDecl, '?', S_OPT, /\?>/); + +/* + https://www.w3.org/TR/xml/#NT-markupdecl + https://www.w3.org/TR/xml11/#NT-markupdecl + `[29] markupdecl ::= elementdecl | AttlistDecl | EntityDecl | NotationDecl | PI | Comment` + var markupdecl = regg(elementdecl, '|', AttlistDecl, '|', EntityDecl, '|', NotationDecl, '|', PI_unsafe, '|', Comment); +*/ +/* + https://www.w3.org/TR/xml-names/#NT-doctypedecl +`[28a] DeclSep ::= PEReference | S` + https://www.w3.org/TR/xml11/#NT-intSubset +``` + [28b] intSubset ::= (markupdecl | DeclSep)* + === (markupdecl | PEReference | S)* +``` + [WFC: PE Between Declarations] + var intSubset = reg(regg(markupdecl, '|', PEReference, '|', S), '*'); +*/ +var DOCTYPE_DECL_START = ''` + https://www.afterwardsw3.org/TR/xml-names/#NT-doctypedecl + `[16] doctypedecl ::= ''` + var doctypedecl = reg(''); +*/ + +var CDATA_START = ''; +var CDStart = //; +var CData = reg(Char, '*?', CDEnd); +/* + https://www.w3.org/TR/xml/#dt-cdsection + `[18] CDSect ::= CDStart CData CDEnd` + `[19] CDStart ::= '' Char*))` + `[21] CDEnd ::= ']]>'` +*/ +var CDSect = reg(CDStart, CData); + +// unit tested +exports.chars = chars; +exports.chars_without = chars_without; +exports.detectUnicodeSupport = detectUnicodeSupport; +exports.reg = reg; +exports.regg = regg; +exports.ABOUT_LEGACY_COMPAT = ABOUT_LEGACY_COMPAT; +exports.ABOUT_LEGACY_COMPAT_SystemLiteral = ABOUT_LEGACY_COMPAT_SystemLiteral; +exports.AttlistDecl = AttlistDecl; +exports.CDATA_START = CDATA_START; +exports.CDATA_END = CDATA_END; +exports.CDSect = CDSect; +exports.Char = Char; +exports.Comment = Comment; +exports.COMMENT_START = COMMENT_START; +exports.COMMENT_END = COMMENT_END; +exports.DOCTYPE_DECL_START = DOCTYPE_DECL_START; +exports.elementdecl = elementdecl; +exports.EntityDecl = EntityDecl; +exports.EntityValue = EntityValue; +exports.ExternalID = ExternalID; +exports.ExternalID_match = ExternalID_match; +exports.Name = Name; +exports.Name_exact = Name_exact; +exports.NCName_exact = NCName_exact; +exports.NotationDecl = NotationDecl; +exports.Reference = Reference; +exports.PEReference = PEReference; +exports.PI = PI; +exports.PUBLIC = PUBLIC; +exports.PubidLiteral = PubidLiteral; +exports.PubidLiteral_match = PubidLiteral_match; +exports.QName = QName; +exports.QName_exact = QName_exact; +exports.QName_group = QName_group; +exports.S = S; +exports.SChar_s = SChar_s; +exports.S_OPT = S_OPT; +exports.SYSTEM = SYSTEM; +exports.SystemLiteral = SystemLiteral; +exports.SystemLiteral_match = SystemLiteral_match; +exports.InvalidChar = InvalidChar; +exports.UNICODE_REPLACEMENT_CHARACTER = UNICODE_REPLACEMENT_CHARACTER; +exports.UNICODE_SUPPORT = UNICODE_SUPPORT; +exports.XMLDecl = XMLDecl; diff --git a/node_modules/@xmldom/xmldom/lib/index.js b/node_modules/@xmldom/xmldom/lib/index.js new file mode 100644 index 000000000..6e873df9a --- /dev/null +++ b/node_modules/@xmldom/xmldom/lib/index.js @@ -0,0 +1,41 @@ +'use strict'; +var conventions = require('./conventions'); +exports.assign = conventions.assign; +exports.hasDefaultHTMLNamespace = conventions.hasDefaultHTMLNamespace; +exports.isHTMLMimeType = conventions.isHTMLMimeType; +exports.isValidMimeType = conventions.isValidMimeType; +exports.MIME_TYPE = conventions.MIME_TYPE; +exports.NAMESPACE = conventions.NAMESPACE; + +var errors = require('./errors'); +exports.DOMException = errors.DOMException; +exports.DOMExceptionName = errors.DOMExceptionName; +exports.ExceptionCode = errors.ExceptionCode; +exports.ParseError = errors.ParseError; + +var dom = require('./dom'); +exports.Attr = dom.Attr; +exports.CDATASection = dom.CDATASection; +exports.CharacterData = dom.CharacterData; +exports.Comment = dom.Comment; +exports.Document = dom.Document; +exports.DocumentFragment = dom.DocumentFragment; +exports.DocumentType = dom.DocumentType; +exports.DOMImplementation = dom.DOMImplementation; +exports.Element = dom.Element; +exports.Entity = dom.Entity; +exports.EntityReference = dom.EntityReference; +exports.LiveNodeList = dom.LiveNodeList; +exports.NamedNodeMap = dom.NamedNodeMap; +exports.Node = dom.Node; +exports.NodeList = dom.NodeList; +exports.Notation = dom.Notation; +exports.ProcessingInstruction = dom.ProcessingInstruction; +exports.Text = dom.Text; +exports.XMLSerializer = dom.XMLSerializer; + +var domParser = require('./dom-parser'); +exports.DOMParser = domParser.DOMParser; +exports.normalizeLineEndings = domParser.normalizeLineEndings; +exports.onErrorStopParsing = domParser.onErrorStopParsing; +exports.onWarningStopParsing = domParser.onWarningStopParsing; diff --git a/node_modules/@xmldom/xmldom/lib/sax.js b/node_modules/@xmldom/xmldom/lib/sax.js new file mode 100644 index 000000000..5b73d93a1 --- /dev/null +++ b/node_modules/@xmldom/xmldom/lib/sax.js @@ -0,0 +1,983 @@ +'use strict'; + +var conventions = require('./conventions'); +var g = require('./grammar'); +var errors = require('./errors'); + +var isHTMLEscapableRawTextElement = conventions.isHTMLEscapableRawTextElement; +var isHTMLMimeType = conventions.isHTMLMimeType; +var isHTMLRawTextElement = conventions.isHTMLRawTextElement; +var hasOwn = conventions.hasOwn; +var NAMESPACE = conventions.NAMESPACE; +var ParseError = errors.ParseError; +var DOMException = errors.DOMException; + +//var handlers = 'resolveEntity,getExternalSubset,characters,endDocument,endElement,endPrefixMapping,ignorableWhitespace,processingInstruction,setDocumentLocator,skippedEntity,startDocument,startElement,startPrefixMapping,notationDecl,unparsedEntityDecl,error,fatalError,warning,attributeDecl,elementDecl,externalEntityDecl,internalEntityDecl,comment,endCDATA,endDTD,endEntity,startCDATA,startDTD,startEntity'.split(',') + +//S_TAG, S_ATTR, S_EQ, S_ATTR_NOQUOT_VALUE +//S_ATTR_SPACE, S_ATTR_END, S_TAG_SPACE, S_TAG_CLOSE +var S_TAG = 0; //tag name offerring +var S_ATTR = 1; //attr name offerring +var S_ATTR_SPACE = 2; //attr name end and space offer +var S_EQ = 3; //=space? +var S_ATTR_NOQUOT_VALUE = 4; //attr value(no quot value only) +var S_ATTR_END = 5; //attr value end and no space(quot end) +var S_TAG_SPACE = 6; //(attr value end || tag end ) && (space offer) +var S_TAG_CLOSE = 7; //closed el + +function XMLReader() {} + +XMLReader.prototype = { + parse: function (source, defaultNSMap, entityMap) { + var domBuilder = this.domBuilder; + domBuilder.startDocument(); + _copy(defaultNSMap, (defaultNSMap = Object.create(null))); + parse(source, defaultNSMap, entityMap, domBuilder, this.errorHandler); + domBuilder.endDocument(); + }, +}; + +/** + * Detecting everything that might be a reference, + * including those without ending `;`, since those are allowed in HTML. + * The entityReplacer takes care of verifying and transforming each occurrence, + * and reports to the errorHandler on those that are not OK, + * depending on the context. + */ +var ENTITY_REG = /&#?\w+;?/g; + +function parse(source, defaultNSMapCopy, entityMap, domBuilder, errorHandler) { + var isHTML = isHTMLMimeType(domBuilder.mimeType); + if (source.indexOf(g.UNICODE_REPLACEMENT_CHARACTER) >= 0) { + errorHandler.warning('Unicode replacement character detected, source encoding issues?'); + } + + function fixedFromCharCode(code) { + // String.prototype.fromCharCode does not supports + // > 2 bytes unicode chars directly + if (code > 0xffff) { + code -= 0x10000; + var surrogate1 = 0xd800 + (code >> 10), + surrogate2 = 0xdc00 + (code & 0x3ff); + + return String.fromCharCode(surrogate1, surrogate2); + } else { + return String.fromCharCode(code); + } + } + + function entityReplacer(a) { + var complete = a[a.length - 1] === ';' ? a : a + ';'; + if (!isHTML && complete !== a) { + errorHandler.error('EntityRef: expecting ;'); + return a; + } + var match = g.Reference.exec(complete); + if (!match || match[0].length !== complete.length) { + errorHandler.error('entity not matching Reference production: ' + a); + return a; + } + var k = complete.slice(1, -1); + if (hasOwn(entityMap, k)) { + return entityMap[k]; + } else if (k.charAt(0) === '#') { + return fixedFromCharCode(parseInt(k.substring(1).replace('x', '0x'))); + } else { + errorHandler.error('entity not found:' + a); + return a; + } + } + + function appendText(end) { + //has some bugs + if (end > start) { + var xt = source.substring(start, end).replace(ENTITY_REG, entityReplacer); + locator && position(start); + domBuilder.characters(xt, 0, end - start); + start = end; + } + } + + var lineStart = 0; + var lineEnd = 0; + var linePattern = /\r\n?|\n|$/g; + var locator = domBuilder.locator; + + function position(p, m) { + while (p >= lineEnd && (m = linePattern.exec(source))) { + lineStart = lineEnd; + lineEnd = m.index + m[0].length; + locator.lineNumber++; + } + locator.columnNumber = p - lineStart + 1; + } + + var parseStack = [{ currentNSMap: defaultNSMapCopy }]; + var unclosedTags = []; + var start = 0; + while (true) { + try { + var tagStart = source.indexOf('<', start); + if (tagStart < 0) { + if (!isHTML && unclosedTags.length > 0) { + return errorHandler.fatalError('unclosed xml tag(s): ' + unclosedTags.join(', ')); + } + if (!source.substring(start).match(/^\s*$/)) { + var doc = domBuilder.doc; + var text = doc.createTextNode(source.substring(start)); + if (doc.documentElement) { + // `return errorHandler.error` is not a common pattern, + // it is usually only used with `.fatalError`s. + // In this case it is intentional, because it allows to stop parsing + // and returning doc after reporting the extra content that will not be part of the document. + return errorHandler.error('Extra content at the end of the document'); + } + doc.appendChild(text); + domBuilder.currentElement = text; + } + return; + } + if (tagStart > start) { + var fromSource = source.substring(start, tagStart); + if (!isHTML && unclosedTags.length === 0) { + fromSource = fromSource.replace(new RegExp(g.S_OPT.source, 'g'), ''); + fromSource && errorHandler.error("Unexpected content outside root element: '" + fromSource + "'"); + } + appendText(tagStart); + } + switch (source.charAt(tagStart + 1)) { + case '/': + var end = source.indexOf('>', tagStart + 2); + var tagNameRaw = source.substring(tagStart + 2, end > 0 ? end : undefined); + if (!tagNameRaw) { + return errorHandler.fatalError('end tag name missing'); + } + var endTagNameStrict = g.reg('^', g.QName_group, g.S_OPT, '$'); + var tagNameMatch = end > 0 && endTagNameStrict.exec(tagNameRaw); + if (!tagNameMatch) { + var leadingTagNameMatch = end > 0 && g.reg('^', g.QName_group).exec(tagNameRaw); + if (isHTML && leadingTagNameMatch) { + errorHandler.warning('end tag name contains invalid trailing characters: "' + tagNameRaw + '"'); + tagNameMatch = leadingTagNameMatch; + } else if ( + // Backward compatibility, remove this whole `else if` arm in the next breaking release + // (XML then falls through to the `fatalError` below, for a clean mode split: XML fatal, + // HTML warning). A valid end-tag name followed by a line break and trailing content was + // silently accepted while `reg` still used the `m` flag; re-adding `m` here matches exactly + // those inputs, kept recoverable and reported. + leadingTagNameMatch && + new RegExp(endTagNameStrict.source, endTagNameStrict.flags + 'm').test(tagNameRaw) + ) { + errorHandler.error('end tag name is followed by a line break and trailing content: "' + tagNameRaw + '"'); + tagNameMatch = leadingTagNameMatch; + } else { + return errorHandler.fatalError('end tag name contains invalid characters: "' + tagNameRaw + '"'); + } + } + if (!domBuilder.currentElement && !domBuilder.doc.documentElement) { + // not enough information to provide a helpful error message, + // but parsing will throw since there is no root element + return; + } + var currentTagName = + unclosedTags[unclosedTags.length - 1] || + domBuilder.currentElement.tagName || + domBuilder.doc.documentElement.tagName || + ''; + if (currentTagName !== tagNameMatch[1]) { + var tagNameLower = tagNameMatch[1].toLowerCase(); + if (!isHTML || currentTagName.toLowerCase() !== tagNameLower) { + return errorHandler.fatalError('Opening and ending tag mismatch: "' + currentTagName + '" != "' + tagNameRaw + '"'); + } + } + var config = parseStack.pop(); + unclosedTags.pop(); + var localNSMap = config.localNSMap; + domBuilder.endElement(config.uri, config.localName, currentTagName); + if (localNSMap) { + for (var prefix in localNSMap) { + if (hasOwn(localNSMap, prefix)) { + domBuilder.endPrefixMapping(prefix); + } + } + } + + end++; + break; + // end element + case '?': // + locator && position(tagStart); + end = parseProcessingInstruction(source, tagStart, domBuilder, errorHandler); + break; + case '!': // start) { + start = end; + } else { + //Possible sax fallback here, risk of positional error + appendText(Math.max(tagStart, start) + 1); + } + } +} + +function copyLocator(f, t) { + t.lineNumber = f.lineNumber; + t.columnNumber = f.columnNumber; + return t; +} + +/** + * @returns + * end of the elementStartPart(end of elementEndPart for selfClosed el) + * @see {@link #appendElement} + */ +function parseElementStartPart(source, start, el, currentNSMap, entityReplacer, errorHandler, isHTML) { + /** + * @param {string} qname + * @param {string} value + * @param {number} startIndex + */ + function addAttribute(qname, value, startIndex) { + if (hasOwn(el.attributeNames, qname)) { + return errorHandler.fatalError('Attribute ' + qname + ' redefined'); + } + if (!isHTML && value.indexOf('<') >= 0) { + return errorHandler.fatalError("Unescaped '<' not allowed in attributes values"); + } + el.addValue( + qname, + // @see https://www.w3.org/TR/xml/#AVNormalize + // since the xmldom sax parser does not "interpret" DTD the following is not implemented: + // - recursive replacement of (DTD) entity references + // - trimming and collapsing multiple spaces into a single one for attributes that are not of type CDATA + value.replace(/[\t\n\r]/g, ' ').replace(ENTITY_REG, entityReplacer), + startIndex + ); + } + + var attrName; + var value; + var p = ++start; + var s = S_TAG; //status + while (true) { + var c = source.charAt(p); + if (s === S_TAG && c === '<') { + // A `<` can never occur inside a tag name. Without this guard the scan runs + // on to the next `>` (or EOF) before `setTagName` rejects the whole slice, so + // a document with many `<` inside a malformed tag makes each one-character + // recovery step re-scan to the distant `>` — O(n^2). Stopping at the `<` keeps + // each recovery step bounded. The candidate scanned so far is reported raw, + // consistent with the sibling invalid-tag-name throw below. + throw new Error('unexpected < in tag name: ' + source.slice(start, p)); + } + switch (c) { + case '=': + if (s === S_ATTR) { + //attrName + attrName = source.slice(start, p); + s = S_EQ; + } else if (s === S_ATTR_SPACE) { + s = S_EQ; + } else { + //fatalError: equal must after attrName or space after attrName + throw new Error('attribute equal must after attrName'); + } + break; + case "'": + case '"': + if ( + s === S_EQ || + s === S_ATTR //|| s == S_ATTR_SPACE + ) { + //equal + if (s === S_ATTR) { + errorHandler.warning('attribute value must after "="'); + attrName = source.slice(start, p); + } + start = p + 1; + p = source.indexOf(c, start); + if (p > 0) { + value = source.slice(start, p); + addAttribute(attrName, value, start - 1); + s = S_ATTR_END; + } else { + //fatalError: no end quot match + throw new Error("attribute value no end '" + c + "' match"); + } + } else if (s == S_ATTR_NOQUOT_VALUE) { + value = source.slice(start, p); + addAttribute(attrName, value, start); + errorHandler.warning('attribute "' + attrName + '" missed start quot(' + c + ')!!'); + start = p + 1; + s = S_ATTR_END; + } else { + //fatalError: no equal before + throw new Error('attribute value must after "="'); + } + break; + case '/': + switch (s) { + case S_TAG: + el.setTagName(source.slice(start, p)); + case S_ATTR_END: + case S_TAG_SPACE: + case S_TAG_CLOSE: + s = S_TAG_CLOSE; + el.closed = true; + case S_ATTR_NOQUOT_VALUE: + case S_ATTR: + break; + case S_ATTR_SPACE: + el.closed = true; + break; + //case S_EQ: + default: + throw new Error("attribute invalid close char('/')"); + } + break; + case '': //end document + errorHandler.error('unexpected end of input'); + if (s == S_TAG) { + el.setTagName(source.slice(start, p)); + } + return p; + case '>': + switch (s) { + case S_TAG: + el.setTagName(source.slice(start, p)); + case S_ATTR_END: + case S_TAG_SPACE: + case S_TAG_CLOSE: + break; //normal + case S_ATTR_NOQUOT_VALUE: //Compatible state + case S_ATTR: + value = source.slice(start, p); + if (value.slice(-1) === '/') { + el.closed = true; + value = value.slice(0, -1); + } + case S_ATTR_SPACE: + if (s === S_ATTR_SPACE) { + value = attrName; + } + if (s == S_ATTR_NOQUOT_VALUE) { + errorHandler.warning('attribute "' + value + '" missed quot(")!'); + addAttribute(attrName, value, start); + } else { + if (!isHTML) { + errorHandler.warning('attribute "' + value + '" missed value!! "' + value + '" instead!!'); + } + addAttribute(value, value, start); + } + break; + case S_EQ: + if (!isHTML) { + return errorHandler.fatalError('AttValue: \' or " expected'); + } + } + return p; + /*xml space '\x20' | #x9 | #xD | #xA; */ + case '\u0080': + c = ' '; + default: + if (c <= ' ') { + //space + switch (s) { + case S_TAG: + el.setTagName(source.slice(start, p)); //tagName + s = S_TAG_SPACE; + break; + case S_ATTR: + attrName = source.slice(start, p); + s = S_ATTR_SPACE; + break; + case S_ATTR_NOQUOT_VALUE: + var value = source.slice(start, p); + errorHandler.warning('attribute "' + value + '" missed quot(")!!'); + addAttribute(attrName, value, start); + case S_ATTR_END: + s = S_TAG_SPACE; + break; + //case S_TAG_SPACE: + //case S_EQ: + //case S_ATTR_SPACE: + // void();break; + //case S_TAG_CLOSE: + //ignore warning + } + } else { + //not space + //S_TAG, S_ATTR, S_EQ, S_ATTR_NOQUOT_VALUE + //S_ATTR_SPACE, S_ATTR_END, S_TAG_SPACE, S_TAG_CLOSE + switch (s) { + //case S_TAG:void();break; + //case S_ATTR:void();break; + //case S_ATTR_NOQUOT_VALUE:void();break; + case S_ATTR_SPACE: + if (!isHTML) { + errorHandler.warning('attribute "' + attrName + '" missed value!! "' + attrName + '" instead2!!'); + } + addAttribute(attrName, attrName, start); + start = p; + s = S_ATTR; + break; + case S_ATTR_END: + errorHandler.warning('attribute space is required"' + attrName + '"!!'); + case S_TAG_SPACE: + s = S_ATTR; + start = p; + break; + case S_EQ: + s = S_ATTR_NOQUOT_VALUE; + start = p; + break; + case S_TAG_CLOSE: + throw new Error("elements closed character '/' and '>' must be connected to"); + } + } + } //end outer switch + p++; + } +} + +/** + * @returns + * `true` if a new namespace has been defined. + */ +function appendElement(el, domBuilder, currentNSMap) { + var tagName = el.tagName; + var localNSMap = null; + var i = el.length; + while (i--) { + var a = el[i]; + var qName = a.qName; + var value = a.value; + var nsp = qName.indexOf(':'); + if (nsp > 0) { + var prefix = (a.prefix = qName.slice(0, nsp)); + var localName = qName.slice(nsp + 1); + var nsPrefix = prefix === 'xmlns' && localName; + } else { + localName = qName; + prefix = null; + nsPrefix = qName === 'xmlns' && ''; + } + //can not set prefix,because prefix !== '' + a.localName = localName; + //prefix == null for no ns prefix attribute + if (nsPrefix !== false) { + //hack!! + if (localNSMap == null) { + localNSMap = Object.create(null); + // Derive the child scope's namespace map by prototype-chain inheritance + // instead of a flat copy: lookups inherit ancestor prefixes transparently, + // so a document nesting N scopes retains O(N) map entries rather than + // sum(1..N) = O(N^2). `localNSMap` stays a flat own-only record of the + // prefixes declared at THIS element, so own-property enumeration + // (endPrefixMapping below) still reports only local declarations. + currentNSMap = Object.create(currentNSMap); + } + currentNSMap[nsPrefix] = localNSMap[nsPrefix] = value; + a.uri = NAMESPACE.XMLNS; + domBuilder.startPrefixMapping(nsPrefix, value); + } + } + var i = el.length; + while (i--) { + a = el[i]; + if (a.prefix) { + //no prefix attribute has no namespace + if (a.prefix === 'xml') { + a.uri = NAMESPACE.XML; + } + if (a.prefix !== 'xmlns') { + a.uri = currentNSMap[a.prefix]; + } + } + } + var nsp = tagName.indexOf(':'); + if (nsp > 0) { + prefix = el.prefix = tagName.slice(0, nsp); + localName = el.localName = tagName.slice(nsp + 1); + } else { + prefix = null; //important!! + localName = el.localName = tagName; + } + //no prefix element has default namespace + var ns = (el.uri = currentNSMap[prefix || '']); + domBuilder.startElement(ns, localName, tagName, el); + //endPrefixMapping and startPrefixMapping have not any help for dom builder + //localNSMap = null + if (el.closed) { + domBuilder.endElement(ns, localName, tagName); + if (localNSMap) { + for (prefix in localNSMap) { + if (hasOwn(localNSMap, prefix)) { + domBuilder.endPrefixMapping(prefix); + } + } + } + } else { + el.currentNSMap = currentNSMap; + el.localNSMap = localNSMap; + //parseStack.push(el); + return true; + } +} + +function parseHtmlSpecialContent(source, elStartEnd, tagName, entityReplacer, domBuilder) { + // https://html.spec.whatwg.org/#raw-text-elements + // https://html.spec.whatwg.org/#escapable-raw-text-elements + // https://html.spec.whatwg.org/#cdata-rcdata-restrictions:raw-text-elements + // TODO: https://html.spec.whatwg.org/#cdata-rcdata-restrictions + var isEscapableRaw = isHTMLEscapableRawTextElement(tagName); + if (isEscapableRaw || isHTMLRawTextElement(tagName)) { + // The closing tag of a raw-text element matches case-insensitively + // (WHATWG HTML §13.2.5.14 RAWTEXT end tag name state). A case-sensitive + // search that missed the closing tag would return -1 and the `substring` + // below would treat -1 as a backward slice from position 0, re-emitting all + // prior source and amplifying output quadratically across repeated elements. + // The regex is anchored to `elStartEnd` via `lastIndex` so it scans forward + // only (O(distance), not the whole source). + var closeTag = new RegExp('', 'ig'); + closeTag.lastIndex = elStartEnd; + var match = closeTag.exec(source); + var elEndStart = match ? match.index : -1; + if (elEndStart < 0) { + // No closing tag: never slice with a -1 end index. Leave the element to + // the parse loop's normal recovery instead of back-capturing the source. + return elStartEnd + 1; + } + var text = source.substring(elStartEnd + 1, elEndStart); + + if (isEscapableRaw) { + text = text.replace(ENTITY_REG, entityReplacer); + } + domBuilder.characters(text, 0, text.length); + return elEndStart; + } + return elStartEnd + 1; +} + +function _copy(source, target) { + for (var n in source) { + if (hasOwn(source, n)) { + target[n] = source[n]; + } + } +} + +/** + * @typedef ParseUtils + * @property {function(relativeIndex: number?): string | undefined} char + * Provides look ahead access to a singe character relative to the current index. + * @property {function(): number} getIndex + * Provides read-only access to the current index. + * @property {function(reg: RegExp): string | null} getMatch + * Applies the provided regular expression enforcing that it starts at the current index and + * returns the complete matching string, + * and moves the current index by the length of the matching string. + * @property {function(): string} getSource + * Provides read-only access to the complete source. + * @property {function(places: number?): void} skip + * moves the current index by places (defaults to 1) + * @property {function(): number} skipBlanks + * Moves the current index by the amount of white space that directly follows the current index + * and returns the amount of whitespace chars skipped (0..n), + * or -1 if the end of the source was reached. + * @property {function(): string} substringFromIndex + * creates a substring from the current index to the end of `source` + * @property {function(compareWith: string): boolean} substringStartsWith + * Checks if `source` contains `compareWith`, starting from the current index. + * @property {function(compareWith: string): boolean} substringStartsWithCaseInsensitive + * Checks if `source` contains `compareWith`, starting from the current index, + * comparing the upper case of both sides. + * @see {@link parseUtils} + */ + +/** + * A temporary scope for parsing and look ahead operations in `source`, + * starting from index `start`. + * + * Some operations move the current index by a number of positions, + * after which `getIndex` returns the new index. + * + * @param {string} source + * @param {number} start + * @returns {ParseUtils} + */ +function parseUtils(source, start) { + var index = start; + + function char(n) { + n = n || 0; + return source.charAt(index + n); + } + + function skip(n) { + n = n || 1; + index += n; + } + + function skipBlanks() { + var blanks = 0; + while (index < source.length) { + var c = char(); + if (c !== ' ' && c !== '\n' && c !== '\t' && c !== '\r') { + return blanks; + } + blanks++; + skip(); + } + return -1; + } + function substringFromIndex() { + return source.substring(index); + } + function substringStartsWith(text) { + return source.substring(index, index + text.length) === text; + } + function substringStartsWithCaseInsensitive(text) { + return source.substring(index, index + text.length).toUpperCase() === text.toUpperCase(); + } + + function getMatch(args) { + var expr = g.reg('^', args); + var match = expr.exec(substringFromIndex()); + if (match) { + skip(match[0].length); + return match[0]; + } + return null; + } + return { + char: char, + getIndex: function () { + return index; + }, + getMatch: getMatch, + getSource: function () { + return source; + }, + skip: skip, + skipBlanks: skipBlanks, + substringFromIndex: substringFromIndex, + substringStartsWith: substringStartsWith, + substringStartsWithCaseInsensitive: substringStartsWithCaseInsensitive, + }; +} + +/** + * @param {ParseUtils} p + * @param {DOMHandler} errorHandler + * @returns {string} + */ +function parseDoctypeInternalSubset(p, errorHandler) { + /** + * @param {ParseUtils} p + * @param {DOMHandler} errorHandler + * @returns {string} + */ + function parsePI(p, errorHandler) { + var match = g.PI.exec(p.substringFromIndex()); + if (!match) { + return errorHandler.fatalError('processing instruction is not well-formed at position ' + p.getIndex()); + } + if (match[1].toLowerCase() === 'xml') { + return errorHandler.fatalError( + 'xml declaration is only allowed at the start of the document, but found at position ' + p.getIndex() + ); + } + p.skip(match[0].length); + return match[0]; + } + // Parse internal subset + var source = p.getSource(); + if (p.char() === '[') { + p.skip(1); + var intSubsetStart = p.getIndex(); + while (p.getIndex() < source.length) { + p.skipBlanks(); + if (p.char() === ']') { + var internalSubset = source.substring(intSubsetStart, p.getIndex()); + p.skip(1); + return internalSubset; + } + var current = null; + // Only in external subset + // if (char() === '<' && char(1) === '!' && char(2) === '[') { + // parseConditionalSections(p, errorHandler); + // } else + if (p.char() === '<' && p.char(1) === '!') { + switch (p.char(2)) { + case 'E': // ELEMENT | ENTITY + if (p.char(3) === 'L') { + current = p.getMatch(g.elementdecl); + } else if (p.char(3) === 'N') { + current = p.getMatch(g.EntityDecl); + } + break; + case 'A': // ATTRIBUTE + current = p.getMatch(g.AttlistDecl); + break; + case 'N': // NOTATION + current = p.getMatch(g.NotationDecl); + break; + case '-': // COMMENT + current = p.getMatch(g.Comment); + break; + } + } else if (p.char() === '<' && p.char(1) === '?') { + current = parsePI(p, errorHandler); + } else if (p.char() === '%') { + current = p.getMatch(g.PEReference); + } else { + return errorHandler.fatalError('Error detected in Markup declaration'); + } + if (!current) { + return errorHandler.fatalError('Error in internal subset at position ' + p.getIndex()); + } + } + return errorHandler.fatalError('doctype internal subset is not well-formed, missing ]'); + } +} + +/** + * Called when the parser encounters an element starting with '') { + return errorHandler.fatalError('doctype not terminated with > at position ' + p.getIndex()); + } + p.skip(1); + domBuilder.startDTD(doctype.name, doctype.publicId, doctype.systemId, doctype.internalSubset); + domBuilder.endDTD(); + return p.getIndex(); + } + default: + return errorHandler.fatalError('Not well-formed XML starting with " 0) { + return errorHandler.fatalError( + 'processing instruction at position ' + start + ' is an xml declaration which is only at the start of the document' + ); + } + if (!g.XMLDecl.test(source.substring(start))) { + return errorHandler.fatalError('xml declaration is not well-formed'); + } + } + domBuilder.processingInstruction(match[1], match[2]); + return start + match[0].length; +} + +function ElementAttributes() { + this.attributeNames = Object.create(null); +} + +ElementAttributes.prototype = { + setTagName: function (tagName) { + if (!g.QName_exact.test(tagName)) { + throw new Error('invalid tagName:' + tagName); + } + this.tagName = tagName; + }, + addValue: function (qName, value, offset) { + if (!g.QName_exact.test(qName)) { + throw new Error('invalid attribute:' + qName); + } + this.attributeNames[qName] = this.length; + this[this.length++] = { qName: qName, value: value, offset: offset }; + }, + length: 0, + getLocalName: function (i) { + return this[i].localName; + }, + getLocator: function (i) { + return this[i].locator; + }, + getQName: function (i) { + return this[i].qName; + }, + getURI: function (i) { + return this[i].uri; + }, + getValue: function (i) { + return this[i].value; + }, + // ,getIndex:function(uri, localName)){ + // if(localName){ + // + // }else{ + // var qName = uri + // } + // }, + // getValue:function(){return this.getValue(this.getIndex.apply(this,arguments))}, + // getType:function(uri,localName){} + // getType:function(i){}, +}; + +exports.XMLReader = XMLReader; +exports.parseUtils = parseUtils; +exports.parseDoctypeCommentOrCData = parseDoctypeCommentOrCData; diff --git a/node_modules/@xmldom/xmldom/package.json b/node_modules/@xmldom/xmldom/package.json new file mode 100644 index 000000000..c8a8a0b17 --- /dev/null +++ b/node_modules/@xmldom/xmldom/package.json @@ -0,0 +1,77 @@ +{ + "name": "@xmldom/xmldom", + "version": "0.9.12", + "description": "A pure JavaScript W3C standard-based (XML DOM Level 2 Core) DOMParser and XMLSerializer module.", + "keywords": [ + "w3c", + "dom", + "xml", + "parser", + "javascript", + "DOMParser", + "XMLSerializer", + "ponyfill" + ], + "homepage": "https://github.com/xmldom/xmldom", + "repository": { + "type": "git", + "url": "git://github.com/xmldom/xmldom.git" + }, + "main": "lib/index.js", + "types": "index.d.ts", + "files": [ + "CHANGELOG.md", + "LICENSE", + "readme.md", + "SECURITY.md", + "index.d.ts", + "lib" + ], + "config": { + "test_stack_size": 256 + }, + "scripts": { + "lint": "eslint examples lib test", + "format": "prettier --write examples lib test index.d.ts", + "format:check": "prettier --check examples lib test index.d.ts", + "changelog": "auto-changelog --unreleased-only", + "start": "nodemon --watch package.json --watch lib --watch test --exec 'npm --silent run test && npm --silent run lint'", + "test": "node --stack-size=$npm_package_config_test_stack_size ./node_modules/.bin/jest", + "fuzz": "jest --config=./jest.fuzz.config.js", + "test:types": "cd examples/typescript-node-es6 && ./pretest.sh 3 && ./pretest.sh 4 && ./pretest.sh 5 && node dist/index.js", + "testrelease": "npm test && eslint lib", + "version": "./changelog-has-version.sh", + "release": "np --no-yarn --test-script testrelease" + }, + "engines": { + "node": ">=14.6" + }, + "devDependencies": { + "@homer0/prettier-plugin-jsdoc": "10.0.1", + "auto-changelog": "2.5.1", + "eslint": "8.57.1", + "eslint-config-prettier": "10.1.8", + "eslint-plugin-anti-trojan-source": "1.1.7", + "eslint-plugin-es5": "1.5.0", + "eslint-plugin-n": "17.24.0", + "eslint-plugin-prettier": "5.5.6", + "get-stream": "6.0.1", + "jest": "29.7.0", + "nodemon": "3.1.14", + "np": "9.2.0", + "prettier": "3.8.3", + "xmltest": "2.0.3", + "yauzl": "3.4.0" + }, + "bugs": { + "url": "https://github.com/xmldom/xmldom/issues" + }, + "license": "MIT", + "auto-changelog": { + "prepend": true, + "remote": "origin", + "tagPrefix": "", + "template": "./auto-changelog.hbs" + }, + "packageManager": "npm@11.19.0+sha512.48377f8478372aa1c4e47b763475b135836da82436a5700f2e5e8eb5084fc840f93c7b117eb3ad3b5f7d3194c81b6710a10d59448f6ddbcb21ac3fb672bdc003" +} diff --git a/node_modules/@xmldom/xmldom/readme.md b/node_modules/@xmldom/xmldom/readme.md new file mode 100644 index 000000000..15c287271 --- /dev/null +++ b/node_modules/@xmldom/xmldom/readme.md @@ -0,0 +1,366 @@ +# @xmldom/xmldom + +***Since version 0.7.0 this package is published to npm as [`@xmldom/xmldom`](https://www.npmjs.com/package/@xmldom/xmldom) and no longer as [`xmldom`](https://www.npmjs.com/package/xmldom), because [we are no longer able to publish `xmldom`](https://github.com/xmldom/xmldom/issues/271).*** +*For better readability in the docs, we will continue to talk about this library as "xmldom".* + +[![license(MIT)](https://img.shields.io/npm/l/@xmldom/xmldom?color=blue&style=flat-square)](https://github.com/xmldom/xmldom/blob/master/LICENSE) +[![no dependencies](https://img.shields.io/badge/dependencies-0-lightgreen)](https://socket.dev/npm/package/@xmldom/xmldom) +[![codecov](https://codecov.io/gh/xmldom/xmldom/branch/master/graph/badge.svg?token=NisDcchEOV)](https://codecov.io/gh/xmldom/xmldom) +[![install size](https://packagephobia.com/badge?p=@xmldom/xmldom)](https://packagephobia.com/result?p=@xmldom/xmldom) + +[![OpenSSF Best Practices](https://www.bestpractices.dev/projects/7879/badge)](https://www.bestpractices.dev/projects/7879) +[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/xmldom/xmldom/badge)](https://securityscorecards.dev/viewer/?uri=github.com/xmldom/xmldom) +[![Socket Badge](https://socket.dev/api/badge/npm/package/@xmldom/xmldom)](https://socket.dev/npm/package/@xmldom/xmldom) + +[![npm:latest](https://img.shields.io/npm/v/@xmldom/xmldom/latest?style=flat-square)](https://www.npmjs.com/package/@xmldom/xmldom) +[![npm:next](https://img.shields.io/npm/v/@xmldom/xmldom/next?style=flat-square)](https://www.npmjs.com/package/@xmldom/xmldom?activeTab=versions) +[![npm:lts](https://img.shields.io/npm/v/@xmldom/xmldom/lts?style=flat-square)](https://www.npmjs.com/package/@xmldom/xmldom?activeTab=versions) + +[![bug issues](https://img.shields.io/github/issues/xmldom/xmldom/bug?color=red&style=flat-square)](https://github.com/xmldom/xmldom/issues?q=is%3Aissue+is%3Aopen+label%3Abug) +[![help-wanted issues](https://img.shields.io/github/issues/xmldom/xmldom/help-wanted?color=darkgreen&style=flat-square)](https://github.com/xmldom/xmldom/issues?q=is%3Aissue+is%3Aopen+label%3Ahelp-wanted) + +xmldom is a javascript [ponyfill](https://ponyfill.com/) to provide the following APIs [that are present in modern browsers](https://caniuse.com/xml-serializer) to other runtimes: +- convert an XML string into a DOM tree + ``` + new DOMParser().parseFromString(xml, mimeType) => Document + ``` +- create, access and modify a DOM tree + ``` + new DOMImplementation().createDocument(...) => Document + ``` +- serialize a DOM tree back into an XML string + ``` + new XMLSerializer().serializeToString(node) => string + ``` + +The target runtimes `xmldom` supports are currently Node >= v14.6 (and very likely any other [ES5 compatible runtime](https://compat-table.github.io/compat-table/es5/)). + +When deciding how to fix bugs or implement features, `xmldom` tries to stay as close as possible to the various [related specifications/standards](#specs). +As indicated by the version starting with `0.`, this implementation is not feature complete and some implemented features differ from what the specifications describe. +**Issues and PRs for such differences are always welcome, even when they only provide a failing test case.** + +This project was forked from it's [original source](https://github.com/jindw/xmldom) in 2019, more details about that transition can be found in the [CHANGELOG](CHANGELOG.md#maintainer-changes). + +## Usage + +### Install: + +``` +npm install @xmldom/xmldom +``` + +### Example: + +[In NodeJS](examples/nodejs/src/index.js) +```javascript +const { DOMParser, XMLSerializer } = require('@xmldom/xmldom') + +const source = ` + test + +` + +const doc = new DOMParser().parseFromString(source, 'text/xml') + +const serialized = new XMLSerializer().serializeToString(doc) +``` + +Note: in Typescript ~~and ES6~~ (see [#316](https://github.com/xmldom/xmldom/issues/316)) you can use the `import` approach, as follows: + +```typescript +import { DOMParser } from '@xmldom/xmldom' +``` + +## API Reference + +* [DOMParser](https://developer.mozilla.org/en-US/docs/Web/API/DOMParser): + + ```javascript + parseFromString(xmlsource, mimeType) + ``` + * **options extension** _by xmldom_ (not DOM standard!!) + + ```javascript + // the options argument can be used to modify behavior + // for more details check the documentation on the code or type definition + new DOMParser(options) + ``` + + * [XMLSerializer](https://developer.mozilla.org/en-US/docs/Web/API/XMLSerializer) + + ```javascript + serializeToString(node) + ``` +### DOM level2 method and attribute: + +All DOM node types listed below are exported for use with `instanceof` (e.g. `node instanceof Text`). +They cannot be constructed directly — use the `Document` factory methods (`createTextNode`, `createComment`, `createDocumentFragment`, …) instead. + +* [Node](http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-1950641247) + + readonly class properties (aka `NodeType`), + these can be accessed from any `Node` instance `node`: + `if (node.nodeType === node.ELEMENT_NODE) {...` + + 1. `ELEMENT_NODE` (`1`) + 2. `ATTRIBUTE_NODE` (`2`) + 3. `TEXT_NODE` (`3`) + 4. `CDATA_SECTION_NODE` (`4`) + 5. `ENTITY_REFERENCE_NODE` (`5`) + 6. `ENTITY_NODE` (`6`) + 7. `PROCESSING_INSTRUCTION_NODE` (`7`) + 8. `COMMENT_NODE` (`8`) + 9. `DOCUMENT_NODE` (`9`) + 10. `DOCUMENT_TYPE_NODE` (`10`) + 11. `DOCUMENT_FRAGMENT_NODE` (`11`) + 12. `NOTATION_NODE` (`12`) + + attribute: + - `nodeValue` | `prefix` | `textContent` + + readonly attribute: + - `nodeName` | `nodeType` | `parentNode` | `parentElement` | `childNodes` | `firstChild` | `lastChild` | `previousSibling` | `nextSibling` | `attributes` | `ownerDocument` | `namespaceURI` | `localName` | `isConnected` | `baseURI` + + method: + * `insertBefore(newChild, refChild)` + * `replaceChild(newChild, oldChild)` + * `removeChild(oldChild)` + * `appendChild(newChild)` + * `hasChildNodes()` + * `cloneNode(deep)` + * `normalize()` + * `contains(otherNode)` + * `getRootNode()` + * `isEqualNode(otherNode)` + * `isSameNode(otherNode)` + * `isSupported(feature, version)` + * `hasAttributes()` +* [DOMException](http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/ecma-script-binding.html) + + extends the Error type thrown as part of DOM API. + + readonly class properties: + - `INDEX_SIZE_ERR` (`1`) + - `DOMSTRING_SIZE_ERR` (`2`) + - `HIERARCHY_REQUEST_ERR` (`3`) + - `WRONG_DOCUMENT_ERR` (`4`) + - `INVALID_CHARACTER_ERR` (`5`) + - `NO_DATA_ALLOWED_ERR` (`6`) + - `NO_MODIFICATION_ALLOWED_ERR` (`7`) + - `NOT_FOUND_ERR` (`8`) + - `NOT_SUPPORTED_ERR` (`9`) + - `INUSE_ATTRIBUTE_ERR` (`10`) + - `INVALID_STATE_ERR` (`11`) + - `SYNTAX_ERR` (`12`) + - `INVALID_MODIFICATION_ERR` (`13`) + - `NAMESPACE_ERR` (`14`) + - `INVALID_ACCESS_ERR` (`15`) + + attributes: + - `code` with a value matching one of the above constants. + +* [DOMImplementation](http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-102161490) + + method: + - `hasFeature(feature, version)` (deprecated) + - `createDocumentType(qualifiedName, publicId, systemId)` + - `createDocument(namespaceURI, qualifiedName, doctype)` + +* [Document](http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#i-Document) : Node + + readonly attribute: + - `doctype` | `implementation` | `documentElement` + + method: + - `createElement(tagName)` + - `createDocumentFragment()` + - `createTextNode(data)` + - `createComment(data)` + - `createCDATASection(data)` + - `createProcessingInstruction(target, data)` + - `createAttribute(name)` + - `createEntityReference(name)` + - `getElementsByTagName(tagname)` + - `importNode(importedNode, deep)` + - `createElementNS(namespaceURI, qualifiedName)` + - `createAttributeNS(namespaceURI, qualifiedName)` + - `getElementsByTagNameNS(namespaceURI, localName)` + - `getElementById(elementId)` + +* [DocumentFragment](http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-B63ED1A3) : Node +* [Element](http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-745549614) : Node + + readonly attribute: + - `tagName` + + method: + - `getAttribute(name)` + - `setAttribute(name, value)` + - `removeAttribute(name)` + - `getAttributeNode(name)` + - `setAttributeNode(newAttr)` + - `removeAttributeNode(oldAttr)` + - `getElementsByTagName(name)` + - `getAttributeNS(namespaceURI, localName)` + - `setAttributeNS(namespaceURI, qualifiedName, value)` + - `removeAttributeNS(namespaceURI, localName)` + - `getAttributeNodeNS(namespaceURI, localName)` + - `setAttributeNodeNS(newAttr)` + - `getElementsByTagNameNS(namespaceURI, localName)` + - `hasAttribute(name)` + - `hasAttributeNS(namespaceURI, localName)` + +* [Attr](http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-637646024) : Node + + attribute: + - `value` + + readonly attribute: + - `name` | `specified` | `ownerElement` + +* [NodeList](http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-536297177) + + readonly attribute: + - `length` + + method: + - `item(index)` + +* [NamedNodeMap](http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-1780488922) + + readonly attribute: + - `length` + + method: + - `getNamedItem(name)` + - `setNamedItem(arg)` + - `removeNamedItem(name)` + - `item(index)` + - `getNamedItemNS(namespaceURI, localName)` + - `setNamedItemNS(arg)` + - `removeNamedItemNS(namespaceURI, localName)` + +* [CharacterData](http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-FF21A306) : Node + + method: + - `substringData(offset, count)` + - `appendData(arg)` + - `insertData(offset, arg)` + - `deleteData(offset, count)` + - `replaceData(offset, count, arg)` + +* [Text](http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-1312295772) : CharacterData + + method: + - `splitText(offset)` + +* [CDATASection](http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-667469212) +* [Comment](http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-1728279322) : CharacterData + +* [DocumentType](http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-412266927) + + readonly attribute: + - `name` | `entities` | `notations` | `publicId` | `systemId` | `internalSubset` + +* Notation : Node + + readonly attribute: + - `publicId` | `systemId` + +* Entity : Node + + readonly attribute: + - `publicId` | `systemId` | `notationName` + +* EntityReference : Node +* ProcessingInstruction : Node + + attribute: + - `data` + readonly attribute: + - `target` + +### DOM level 3 support: + +* [Node](http://www.w3.org/TR/DOM-Level-3-Core/core.html#Node3-textContent) + + attribute: + - `textContent` + + method: + - `isDefaultNamespace(namespaceURI)` + - `lookupNamespaceURI(prefix)` + +### DOM Living Standard support: + +* [ParentNode](https://dom.spec.whatwg.org/#interface-parentnode) mixin (on `Document`, `DocumentFragment`, `Element`) + + readonly attribute: + - `children` + +### DOM extension by xmldom + +* [Node] Source position extension; + + attribute: + - `lineNumber` //number starting from `1` + - `columnNumber` //number starting from `1` + +## Specs + +The implementation is based on several specifications: + + +![Overview of related specifications and their relations](docs/specs.svg) + +### DOM Parsing and Serialization + +From the [W3C DOM Parsing and Serialization (WD 2016)](https://www.w3.org/TR/2016/WD-DOM-Parsing-20160517/) `xmldom` provides an implementation for the interfaces: +- `DOMParser` +- `XMLSerializer` + +Note that there are some known deviations between this implementation and the W3 specifications. + +Note: [The latest version of this spec](https://w3c.github.io/DOM-Parsing/) has the status "Editors Draft", since it is under active development. One major change is that [the definition of the `DOMParser` interface has been moved to the HTML spec](https://w3c.github.io/DOM-Parsing/#the-domparser-interface) + + +### DOM + +The original author claims that xmldom implements [DOM Level 2] in a "fully compatible" way and some parts of [DOM Level 3], but there are not enough tests to prove this. Both Specifications are now superseded by the [DOM Level 4 aka Living standard] wich has a much broader scope than xmldom. +In the past, there have been multiple (even breaking) changes to align xmldom with the living standard, +so if you find a difference that is not documented, any contribution to resolve the difference is very welcome (even just reporting it as an issue). + +xmldom implements the following interfaces: +- `Attr` +- `CDATASection` +- `CharacterData` +- `Comment` +- `Document` +- `DocumentFragment` +- `DocumentType` +- `DOMException` +- `DOMImplementation` +- `Element` +- `Entity` +- `EntityReference` +- `LiveNodeList` +- `NamedNodeMap` +- `Node` +- `NodeList` +- `Notation` +- `ProcessingInstruction` +- `Text` + +more details are available in the (incomplete) [API Reference](#api-reference) section. + +### HTML + +xmldom does not have any goal of supporting the full spec, but it has some capability to parse, report and serialize things differently when it is told to parse HTML (by passing the HTML namespace). + +### SAX, XML, XMLNS + +xmldom has an own SAX parser implementation to do the actual parsing, which implements some interfaces in alignment with the Java interfaces SAX defines: +- `XMLReader` +- `DOMHandler` + +There is an idea/proposal to make it possible to replace it with something else in diff --git a/node_modules/README b/node_modules/README deleted file mode 100644 index 4666d00ba..000000000 --- a/node_modules/README +++ /dev/null @@ -1,2 +0,0 @@ -This includes third party modules. -Respective licenses apply. diff --git a/node_modules/karma-webodf/lib/adapter.js b/node_modules/karma-webodf/lib/adapter.js deleted file mode 100644 index 57f58cc73..000000000 --- a/node_modules/karma-webodf/lib/adapter.js +++ /dev/null @@ -1,48 +0,0 @@ -/*global window, tester, tests, runNextTest, runtime*/ -/*jslint nomen: true, emptyblock: true, unparam: true*/ - -(function (win) { - "use strict"; - // signal to UnitTester that the tests should not start automatically. - win.use_karma = true; - /** - * Returned start function is invoked by Karma runner when Karma is - * ready (connected with a browser and loaded all the required files) - * - * @param {Object} karma Karma runner instance - * @return {Function} start function - */ - function createStartFn(karma) { - return function () { - var testCount = 0; - tester.resourcePrefix = "tests/"; - tester.reporter = function (r) { - testCount += 1; - karma.info({total: testCount}); - karma.result(r); - }; - // tell karma how many tests there are - karma.info({total: testCount}); - runNextTest(tests, tester, function () { - // tell karma to end the run - karma.complete({ - coverage: window.__coverage__ - }); - }); - }; - } - /** - * Returned function is used for logging by Karma - */ - function createDumpFn(karma, serialize) { - // inside you could use a custom `serialize` function - // to modify or attach messages or hook into logging - return function () { - karma.info({ dump: [].slice.call(arguments) }); - }; - } - win.__karma__.start = createStartFn(window.__karma__); - win.dump = createDumpFn(win.__karma__, function (value) { - return value; - }); -}(window)); diff --git a/node_modules/karma-webodf/lib/index.js b/node_modules/karma-webodf/lib/index.js deleted file mode 100644 index 898ead36e..000000000 --- a/node_modules/karma-webodf/lib/index.js +++ /dev/null @@ -1,93 +0,0 @@ -/*jslint nomen: true*/ -/*global __dirname, module, require */ -function createPattern(path) { - "use strict"; - return {pattern: path, included: true, served: true, watched: false}; -} - -function get(path, response) { - "use strict"; - var fs = require('fs'); - fs.stat(path, function (err, stats) { - var s; - if (err) { - response.writeHead(404); - response.end(); - } else { - response.writeHead(200, { 'content-length': stats.size }); - s = fs.createReadStream(path); - s.pipe(response); - } - }); -} - -function put(path, request, response) { - "use strict"; - var fs = require('fs'), - s = fs.createWriteStream(path); - request.pipe(s); - request.on('end', function () { - response.writeHead(200); - response.end(); - }); -} - -function head(path, response) { - "use strict"; - var fs = require('fs'); -console.log("HRMM " + path); - fs.stat(path, function (err, stats) { -console.log("HEAD " + err); - if (err) { - response.writeHead(404); - } else { - response.writeHead(200, { 'content-length': stats.size }); - } - response.end(); - }); -} - -function unlink(path, response) { - "use strict"; - var fs = require('fs'); - fs.unlink(path, function (err) { - if (err) { - response.writeHead(500); - } else { - response.writeHead(200); - } - response.end(); - }); -} - -function handleRequest(request, response) { - "use strict"; - var path = request.url.slice(1); - console.log(request.method + " " + path); - if (request.method === 'GET') { - get(path, response); - } else if (request.method === 'PUT') { - put(path, request, response); - } else if (request.method === 'DELETE') { - unlink(path, response); - } else if (request.method === 'HEAD') { - head(path, response); - } else { - response.end(500); - } -} - -function initWebODF(files) { - "use strict"; - files.unshift(createPattern(__dirname + '/adapter.js')); - var http = require('http'), - server = http.createServer(handleRequest); - server.listen(8642); -} - - -initWebODF.$inject = ['config.files']; - -module.exports = { - 'framework:webodf': ['factory', initWebODF] -}; diff --git a/node_modules/karma-webodf/package.json b/node_modules/karma-webodf/package.json deleted file mode 100644 index 3a21f1da0..000000000 --- a/node_modules/karma-webodf/package.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "name": "karma-webodf", - "version": "0.1.0", - "description": "A Karma plugin - adapter for WebODF testing framework.", - "main": "lib/index.js", - "license": "AGPLv3" -} diff --git a/node_modules/xmldom/.gitattributes b/node_modules/xmldom/.gitattributes deleted file mode 100644 index b15706354..000000000 --- a/node_modules/xmldom/.gitattributes +++ /dev/null @@ -1,4 +0,0 @@ -# Unset behaviour for some text-like files, as this is just a copy of original files, -# with obviously DOS line endings - -* binary diff --git a/node_modules/xmldom/.project b/node_modules/xmldom/.project deleted file mode 100644 index 49691ced8..000000000 --- a/node_modules/xmldom/.project +++ /dev/null @@ -1,11 +0,0 @@ - - - xmldom - - - - - - - - diff --git a/node_modules/xmldom/__package__.js b/node_modules/xmldom/__package__.js deleted file mode 100644 index 93af3495a..000000000 --- a/node_modules/xmldom/__package__.js +++ /dev/null @@ -1,4 +0,0 @@ -this.addScript('dom.js',['DOMImplementation','XMLSerializer']); -this.addScript('dom-parser.js',['DOMHandler','DOMParser'], - ['DOMImplementation','XMLReader']); -this.addScript('sax.js','XMLReader'); \ No newline at end of file diff --git a/node_modules/xmldom/changelog b/node_modules/xmldom/changelog deleted file mode 100644 index e8fcc6f1b..000000000 --- a/node_modules/xmldom/changelog +++ /dev/null @@ -1,5 +0,0 @@ -0.1.8 - * Add: some test case from node-o3-xml(excludes xpath support) - * Fix: remove existed attribute before setting (bug introduced in v0.1.5) - * Fix: index direct access for childNodes and any NodeList collection(not w3c standard) - * Fix: remove last child bug \ No newline at end of file diff --git a/node_modules/xmldom/dom-parser.js b/node_modules/xmldom/dom-parser.js deleted file mode 100644 index 0a755525d..000000000 --- a/node_modules/xmldom/dom-parser.js +++ /dev/null @@ -1,253 +0,0 @@ -function DOMParser(options){ - this.options = - options != true && //To the version (0.1.12) compatible - options ||{locator:{}}; - -} -DOMParser.prototype.parseFromString = function(source,mimeType){ - var sax = new XMLReader(); - var options = this.options; - var domBuilder = options.domBuilder || new DOMHandler();//contentHandler and LexicalHandler - var errorHandler = options.errorHandler; - var locator = options.locator; - var defaultNSMap = {}; - var entityMap = {'lt':'<','gt':'>','amp':'&','quot':'"','apos':"'"} - if(locator){ - domBuilder.setDocumentLocator(locator) - } - - sax.errorHandler = buildErrorHandler(errorHandler,domBuilder,locator); - sax.domBuilder = options.domBuilder || domBuilder; - if(/\/x?html?$/.test(mimeType)){ - entityMap.nbsp = '\xa0'; - entityMap.copy = '\xa9'; - defaultNSMap['']= 'http://www.w3.org/1999/xhtml'; - } - sax.parse(source,defaultNSMap,entityMap); - return domBuilder.document; -} -function buildErrorHandler(errorImpl,domBuilder,locator){ - if(!errorImpl){ - if(domBuilder instanceof DOMHandler){ - return domBuilder; - } - errorImpl = domBuilder ; - } - var errorHandler = {} - var isCallback = errorImpl instanceof Function; - locator = locator||{} - function build(key){ - var fn = errorImpl[key]; - if(!fn){ - if(isCallback){ - fn = errorImpl.length == 2?function(msg){errorImpl(key,msg)}:errorImpl; - }else{ - var i=arguments.length; - while(--i){ - if(fn = errorImpl[arguments[i]]){ - break; - } - } - } - } - errorHandler[key] = fn && function(msg){ - fn(msg+_locator(locator)); - }||function(){}; - } - build('warning','warn'); - build('error','warn','warning'); - build('fatalError','warn','warning','error'); - return errorHandler; -} -/** - * +ContentHandler+ErrorHandler - * +LexicalHandler+EntityResolver2 - * -DeclHandler-DTDHandler - * - * DefaultHandler:EntityResolver, DTDHandler, ContentHandler, ErrorHandler - * DefaultHandler2:DefaultHandler,LexicalHandler, DeclHandler, EntityResolver2 - * @link http://www.saxproject.org/apidoc/org/xml/sax/helpers/DefaultHandler.html - */ -function DOMHandler() { - this.cdata = false; -} -function position(locator,node){ - node.lineNumber = locator.lineNumber; - node.columnNumber = locator.columnNumber; -} -/** - * @see org.xml.sax.ContentHandler#startDocument - * @link http://www.saxproject.org/apidoc/org/xml/sax/ContentHandler.html - */ -DOMHandler.prototype = { - startDocument : function() { - this.document = new DOMImplementation().createDocument(null, null, null); - if (this.locator) { - this.document.documentURI = this.locator.systemId; - } - }, - startElement:function(namespaceURI, localName, qName, attrs) { - var doc = this.document; - var el = doc.createElementNS(namespaceURI, qName||localName); - var len = attrs.length; - appendElement(this, el); - this.currentElement = el; - - this.locator && position(this.locator,el) - for (var i = 0 ; i < len; i++) { - var namespaceURI = attrs.getURI(i); - var value = attrs.getValue(i); - var qName = attrs.getQName(i); - var attr = doc.createAttributeNS(namespaceURI, qName); - if( attr.getOffset){ - position(attr.getOffset(1),attr) - } - attr.value = attr.nodeValue = value; - el.setAttributeNode(attr) - } - }, - endElement:function(namespaceURI, localName, qName) { - var current = this.currentElement - var tagName = current.tagName; - this.currentElement = current.parentNode; - }, - startPrefixMapping:function(prefix, uri) { - }, - endPrefixMapping:function(prefix) { - }, - processingInstruction:function(target, data) { - var ins = this.document.createProcessingInstruction(target, data); - this.locator && position(this.locator,ins) - appendElement(this, ins); - }, - ignorableWhitespace:function(ch, start, length) { - }, - characters:function(chars, start, length) { - chars = _toString.apply(this,arguments) - //console.log(chars) - if(this.currentElement && chars){ - if (this.cdata) { - var charNode = this.document.createCDATASection(chars); - this.currentElement.appendChild(charNode); - } else { - var charNode = this.document.createTextNode(chars); - this.currentElement.appendChild(charNode); - } - this.locator && position(this.locator,charNode) - } - }, - skippedEntity:function(name) { - }, - endDocument:function() { - this.document.normalize(); - }, - setDocumentLocator:function (locator) { - if(this.locator = locator){// && !('lineNumber' in locator)){ - locator.lineNumber = 0; - } - }, - //LexicalHandler - comment:function(chars, start, length) { - chars = _toString.apply(this,arguments) - var comm = this.document.createComment(chars); - this.locator && position(this.locator,comm) - appendElement(this, comm); - }, - - startCDATA:function() { - //used in characters() methods - this.cdata = true; - }, - endCDATA:function() { - this.cdata = false; - }, - - startDTD:function(name, publicId, systemId) { - var impl = this.document.implementation; - if (impl && impl.createDocumentType) { - var dt = impl.createDocumentType(name, publicId, systemId); - this.locator && position(this.locator,dt) - appendElement(this, dt); - } - }, - /** - * @see org.xml.sax.ErrorHandler - * @link http://www.saxproject.org/apidoc/org/xml/sax/ErrorHandler.html - */ - warning:function(error) { - console.warn(error,_locator(this.locator)); - }, - error:function(error) { - console.error(error,_locator(this.locator)); - }, - fatalError:function(error) { - console.error(error,_locator(this.locator)); - throw error; - } -} -function _locator(l){ - if(l){ - return '\n@'+(l.systemId ||'')+'#[line:'+l.lineNumber+',col:'+l.columnNumber+']' - } -} -function _toString(chars,start,length){ - if(typeof chars == 'string'){ - return chars.substr(start,length) - }else{//java sax connect width xmldom on rhino(what about: "? && !(chars instanceof String)") - if(chars.length >= start+length || start){ - return new java.lang.String(chars,start,length)+''; - } - return chars; - } -} - -/* - * @link http://www.saxproject.org/apidoc/org/xml/sax/ext/LexicalHandler.html - * used method of org.xml.sax.ext.LexicalHandler: - * #comment(chars, start, length) - * #startCDATA() - * #endCDATA() - * #startDTD(name, publicId, systemId) - * - * - * IGNORED method of org.xml.sax.ext.LexicalHandler: - * #endDTD() - * #startEntity(name) - * #endEntity(name) - * - * - * @link http://www.saxproject.org/apidoc/org/xml/sax/ext/DeclHandler.html - * IGNORED method of org.xml.sax.ext.DeclHandler - * #attributeDecl(eName, aName, type, mode, value) - * #elementDecl(name, model) - * #externalEntityDecl(name, publicId, systemId) - * #internalEntityDecl(name, value) - * @link http://www.saxproject.org/apidoc/org/xml/sax/ext/EntityResolver2.html - * IGNORED method of org.xml.sax.EntityResolver2 - * #resolveEntity(String name,String publicId,String baseURI,String systemId) - * #resolveEntity(publicId, systemId) - * #getExternalSubset(name, baseURI) - * @link http://www.saxproject.org/apidoc/org/xml/sax/DTDHandler.html - * IGNORED method of org.xml.sax.DTDHandler - * #notationDecl(name, publicId, systemId) {}; - * #unparsedEntityDecl(name, publicId, systemId, notationName) {}; - */ -"endDTD,startEntity,endEntity,attributeDecl,elementDecl,externalEntityDecl,internalEntityDecl,resolveEntity,getExternalSubset,notationDecl,unparsedEntityDecl".replace(/\w+/g,function(key){ - DOMHandler.prototype[key] = function(){return null} -}) - -/* Private static helpers treated below as private instance methods, so don't need to add these to the public API; we might use a Relator to also get rid of non-standard public properties */ -function appendElement (hander,node) { - if (!hander.currentElement) { - hander.document.appendChild(node); - } else { - hander.currentElement.appendChild(node); - } -}//appendChild and setAttributeNS are preformance key - -if(typeof require == 'function'){ - var XMLReader = require('./sax').XMLReader; - var DOMImplementation = require('./dom').DOMImplementation; - exports.XMLSerializer = require('./dom').XMLSerializer ; - exports.DOMParser = DOMParser; -} diff --git a/node_modules/xmldom/dom.js b/node_modules/xmldom/dom.js deleted file mode 100644 index 7cb012dd6..000000000 --- a/node_modules/xmldom/dom.js +++ /dev/null @@ -1,1135 +0,0 @@ -/* - * DOM Level 2 - * Object DOMException - * @see http://www.w3.org/TR/REC-DOM-Level-1/ecma-script-language-binding.html - * @see http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/ecma-script-binding.html - */ - -function copy(src,dest){ - for(var p in src){ - dest[p] = src[p]; - } -} -/** -^\w+\.prototype\.([_\w]+)\s*=\s*((?:.*\{\s*?[\r\n][\s\S]*?^})|\S.*?(?=[;\r\n]));? -^\w+\.prototype\.([_\w]+)\s*=\s*(\S.*?(?=[;\r\n]));? - */ -function _extends(Class,Super){ - var pt = Class.prototype; - if(Object.create){ - var ppt = Object.create(Super.prototype) - pt.__proto__ = ppt; - } - if(!(pt instanceof Super)){ - function t(){}; - t.prototype = Super.prototype; - t = new t(); - copy(pt,t); - Class.prototype = pt = t; - } - if(pt.constructor != Class){ - if(typeof Class != 'function'){ - console.error("unknow Class:"+Class) - } - pt.constructor = Class - } -} -var htmlns = 'http://www.w3.org/1999/xhtml' ; -// Node Types -var NodeType = {} -var ELEMENT_NODE = NodeType.ELEMENT_NODE = 1; -var ATTRIBUTE_NODE = NodeType.ATTRIBUTE_NODE = 2; -var TEXT_NODE = NodeType.TEXT_NODE = 3; -var CDATA_SECTION_NODE = NodeType.CDATA_SECTION_NODE = 4; -var ENTITY_REFERENCE_NODE = NodeType.ENTITY_REFERENCE_NODE = 5; -var ENTITY_NODE = NodeType.ENTITY_NODE = 6; -var PROCESSING_INSTRUCTION_NODE = NodeType.PROCESSING_INSTRUCTION_NODE = 7; -var COMMENT_NODE = NodeType.COMMENT_NODE = 8; -var DOCUMENT_NODE = NodeType.DOCUMENT_NODE = 9; -var DOCUMENT_TYPE_NODE = NodeType.DOCUMENT_TYPE_NODE = 10; -var DOCUMENT_FRAGMENT_NODE = NodeType.DOCUMENT_FRAGMENT_NODE = 11; -var NOTATION_NODE = NodeType.NOTATION_NODE = 12; - -// ExceptionCode -var ExceptionCode = {} -var ExceptionMessage = {}; -var INDEX_SIZE_ERR = ExceptionCode.INDEX_SIZE_ERR = ((ExceptionMessage[1]="Index size error"),1); -var DOMSTRING_SIZE_ERR = ExceptionCode.DOMSTRING_SIZE_ERR = ((ExceptionMessage[2]="DOMString size error"),2); -var HIERARCHY_REQUEST_ERR = ExceptionCode.HIERARCHY_REQUEST_ERR = ((ExceptionMessage[3]="Hierarchy request error"),3); -var WRONG_DOCUMENT_ERR = ExceptionCode.WRONG_DOCUMENT_ERR = ((ExceptionMessage[4]="Wrong document"),4); -var INVALID_CHARACTER_ERR = ExceptionCode.INVALID_CHARACTER_ERR = ((ExceptionMessage[5]="Invalid character"),5); -var NO_DATA_ALLOWED_ERR = ExceptionCode.NO_DATA_ALLOWED_ERR = ((ExceptionMessage[6]="No data allowed"),6); -var NO_MODIFICATION_ALLOWED_ERR = ExceptionCode.NO_MODIFICATION_ALLOWED_ERR = ((ExceptionMessage[7]="No modification allowed"),7); -var NOT_FOUND_ERR = ExceptionCode.NOT_FOUND_ERR = ((ExceptionMessage[8]="Not found"),8); -var NOT_SUPPORTED_ERR = ExceptionCode.NOT_SUPPORTED_ERR = ((ExceptionMessage[9]="Not supported"),9); -var INUSE_ATTRIBUTE_ERR = ExceptionCode.INUSE_ATTRIBUTE_ERR = ((ExceptionMessage[10]="Attribute in use"),10); -//level2 -var INVALID_STATE_ERR = ExceptionCode.INVALID_STATE_ERR = ((ExceptionMessage[11]="Invalid state"),11); -var SYNTAX_ERR = ExceptionCode.SYNTAX_ERR = ((ExceptionMessage[12]="Syntax error"),12); -var INVALID_MODIFICATION_ERR = ExceptionCode.INVALID_MODIFICATION_ERR = ((ExceptionMessage[13]="Invalid modification"),13); -var NAMESPACE_ERR = ExceptionCode.NAMESPACE_ERR = ((ExceptionMessage[14]="Invalid namespace"),14); -var INVALID_ACCESS_ERR = ExceptionCode.INVALID_ACCESS_ERR = ((ExceptionMessage[15]="Invalid access"),15); - - -function DOMException(code, message) { - if(message instanceof Error){ - var error = message; - }else{ - error = this; - Error.call(this, ExceptionMessage[code]); - this.message = ExceptionMessage[code]; - if(Error.captureStackTrace) Error.captureStackTrace(this, DOMException); - } - error.code = code; - if(message) this.message = this.message + ": " + message; - return error; -}; -DOMException.prototype = Error.prototype; -copy(ExceptionCode,DOMException) -/** - * @see http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-536297177 - * The NodeList interface provides the abstraction of an ordered collection of nodes, without defining or constraining how this collection is implemented. NodeList objects in the DOM are live. - * The items in the NodeList are accessible via an integral index, starting from 0. - */ -function NodeList() { -}; -NodeList.prototype = { - /** - * The number of nodes in the list. The range of valid child node indices is 0 to length-1 inclusive. - * @standard level1 - */ - length:0, - /** - * Returns the indexth item in the collection. If index is greater than or equal to the number of nodes in the list, this returns null. - * @standard level1 - * @param index unsigned long - * Index into the collection. - * @return Node - * The node at the indexth position in the NodeList, or null if that is not a valid index. - */ - item: function(index) { - return this[index] || null; - } -}; -function LiveNodeList(node,refresh){ - this._node = node; - this._refresh = refresh - _updateLiveList(this); -} -function _updateLiveList(list){ - var inc = list._node._inc || list._node.ownerDocument._inc; - if(list._inc != inc){ - var ls = list._refresh(list._node); - //console.log(ls.length) - __set__(list,'length',ls.length); - copy(ls,list); - list._inc = inc; - } -} -LiveNodeList.prototype.item = function(i){ - _updateLiveList(this); - return this[i]; -} - -_extends(LiveNodeList,NodeList); -/** - * - * Objects implementing the NamedNodeMap interface are used to represent collections of nodes that can be accessed by name. Note that NamedNodeMap does not inherit from NodeList; NamedNodeMaps are not maintained in any particular order. Objects contained in an object implementing NamedNodeMap may also be accessed by an ordinal index, but this is simply to allow convenient enumeration of the contents of a NamedNodeMap, and does not imply that the DOM specifies an order to these Nodes. - * NamedNodeMap objects in the DOM are live. - * used for attributes or DocumentType entities - */ -function NamedNodeMap() { -}; - -function _findNodeIndex(list,node){ - var i = list.length; - while(i--){ - if(list[i] === node){return i} - } -} - -function _addNamedNode(el,list,newAttr,oldAttr){ - if(oldAttr){ - list[_findNodeIndex(list,oldAttr)] = newAttr; - }else{ - list[list.length++] = newAttr; - } - if(el){ - newAttr.ownerElement = el; - var doc = el.ownerDocument; - if(doc){ - oldAttr && _onRemoveAttribute(doc,el,oldAttr); - _onAddAttribute(doc,el,newAttr); - } - } -} -function _removeNamedNode(el,list,attr){ - var i = _findNodeIndex(list,attr); - if(i>=0){ - var lastIndex = list.length-1 - while(i0 || key == 'xmlns'){ -// return null; -// } - var i = this.length; - while(i--){ - var attr = this[i]; - if(attr.nodeName == key){ - return attr; - } - } - }, - setNamedItem: function(attr) { - var el = attr.ownerElement; - if(el && el!=this._ownerElement){ - throw new DOMException(INUSE_ATTRIBUTE_ERR); - } - var oldAttr = this.getNamedItem(attr.nodeName); - _addNamedNode(this._ownerElement,this,attr,oldAttr); - return oldAttr; - }, - /* returns Node */ - setNamedItemNS: function(attr) {// raises: WRONG_DOCUMENT_ERR,NO_MODIFICATION_ALLOWED_ERR,INUSE_ATTRIBUTE_ERR - var el = attr.ownerElement, oldAttr; - if(el && el!=this._ownerElement){ - throw new DOMException(INUSE_ATTRIBUTE_ERR); - } - oldAttr = this.getNamedItemNS(attr.namespaceURI,attr.localName); - _addNamedNode(this._ownerElement,this,attr,oldAttr); - return oldAttr; - }, - - /* returns Node */ - removeNamedItem: function(key) { - var attr = this.getNamedItem(key); - _removeNamedNode(this._ownerElement,this,attr); - return attr; - - - },// raises: NOT_FOUND_ERR,NO_MODIFICATION_ALLOWED_ERR - - //for level2 - removeNamedItemNS:function(namespaceURI,localName){ - var attr = this.getNamedItemNS(namespaceURI,localName); - _removeNamedNode(this._ownerElement,this,attr); - return attr; - }, - getNamedItemNS: function(namespaceURI, localName) { - var i = this.length; - while(i--){ - var node = this[i]; - if(node.localName == localName && node.namespaceURI == namespaceURI){ - return node; - } - } - return null; - } -}; -/** - * @see http://www.w3.org/TR/REC-DOM-Level-1/level-one-core.html#ID-102161490 - */ -function DOMImplementation(/* Object */ features) { - this._features = {}; - if (features) { - for (var feature in features) { - this._features = features[feature]; - } - } -}; - -DOMImplementation.prototype = { - hasFeature: function(/* string */ feature, /* string */ version) { - var versions = this._features[feature.toLowerCase()]; - if (versions && (!version || version in versions)) { - return true; - } else { - return false; - } - }, - // Introduced in DOM Level 2: - createDocument:function(namespaceURI, qualifiedName, doctype){// raises:INVALID_CHARACTER_ERR,NAMESPACE_ERR,WRONG_DOCUMENT_ERR - var doc = new Document(); - doc.doctype = doctype; - if(doctype){ - doc.appendChild(doctype); - } - doc.implementation = this; - doc.childNodes = new NodeList(); - if(qualifiedName){ - var root = doc.createElementNS(namespaceURI,qualifiedName); - doc.appendChild(root); - } - return doc; - }, - // Introduced in DOM Level 2: - createDocumentType:function(qualifiedName, publicId, systemId){// raises:INVALID_CHARACTER_ERR,NAMESPACE_ERR - var node = new DocumentType(); - node.name = qualifiedName; - node.nodeName = qualifiedName; - node.publicId = publicId; - node.systemId = systemId; - // Introduced in DOM Level 2: - //readonly attribute DOMString internalSubset; - - //TODO:.. - // readonly attribute NamedNodeMap entities; - // readonly attribute NamedNodeMap notations; - return node; - } -}; - - -/** - * @see http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-1950641247 - */ - -function Node() { -}; - -Node.prototype = { - firstChild : null, - lastChild : null, - previousSibling : null, - nextSibling : null, - attributes : null, - parentNode : null, - childNodes : null, - ownerDocument : null, - nodeValue : null, - namespaceURI : null, - prefix : null, - localName : null, - // Modified in DOM Level 2: - insertBefore:function(newChild, refChild){//raises - return _insertBefore(this,newChild,refChild); - }, - replaceChild:function(newChild, oldChild){//raises - this.insertBefore(newChild,oldChild); - if(oldChild){ - this.removeChild(oldChild); - } - }, - removeChild:function(oldChild){ - return _removeChild(this,oldChild); - }, - appendChild:function(newChild){ - return this.insertBefore(newChild,null); - }, - hasChildNodes:function(){ - return this.firstChild != null; - }, - cloneNode:function(deep){ - return cloneNode(this.ownerDocument||this,this,deep); - }, - // Modified in DOM Level 2: - normalize:function(){ - var child = this.firstChild; - while(child){ - var next = child.nextSibling; - if(next && next.nodeType == TEXT_NODE && child.nodeType == TEXT_NODE){ - this.removeChild(next); - child.appendData(next.data); - }else{ - child.normalize(); - child = next; - } - } - }, - // Introduced in DOM Level 2: - isSupported:function(feature, version){ - return this.ownerDocument.implementation.hasFeature(feature,version); - }, - // Introduced in DOM Level 2: - hasAttributes:function(){ - return this.attributes.length>0; - }, - lookupPrefix:function(namespaceURI){ - var el = this; - while(el){ - var map = el._nsMap; - //console.dir(map) - if(map){ - for(var n in map){ - if(map[n] == namespaceURI){ - return n; - } - } - } - el = el.nodeType == 2?el.ownerDocument : el.parentNode; - } - return null; - }, - // Introduced in DOM Level 3: - lookupNamespaceURI:function(prefix){ - var el = this; - while(el){ - var map = el._nsMap; - //console.dir(map) - if(map){ - if(prefix in map){ - return map[prefix] ; - } - } - el = el.nodeType == 2?el.ownerDocument : el.parentNode; - } - return null; - }, - // Introduced in DOM Level 3: - isDefaultNamespace:function(namespaceURI){ - var prefix = this.lookupPrefix(namespaceURI); - return prefix == null; - } -}; - - -function _xmlEncoder(c){ - return c == '<' && '<' || - c == '>' && '>' || - c == '&' && '&' || - c == '"' && '"' || - '&#'+c.charCodeAt()+';' -} - - -copy(NodeType,Node); -copy(NodeType,Node.prototype); - -/** - * @param callback return true for continue,false for break - * @return boolean true: break visit; - */ -function _visitNode(node,callback){ - if(callback(node)){ - return true; - } - if(node = node.firstChild){ - do{ - if(_visitNode(node,callback)){return true} - }while(node=node.nextSibling) - } -} - - - -function Document(){ -} -function _onAddAttribute(doc,el,newAttr){ - doc && doc._inc++; - var ns = newAttr.namespaceURI ; - if(ns == 'http://www.w3.org/2000/xmlns/'){ - //update namespace - el._nsMap[newAttr.prefix?newAttr.localName:''] = newAttr.value - } -} -function _onRemoveAttribute(doc,el,newAttr,remove){ - doc && doc._inc++; - var ns = newAttr.namespaceURI ; - if(ns == 'http://www.w3.org/2000/xmlns/'){ - //update namespace - delete el._nsMap[newAttr.prefix?newAttr.localName:''] - } -} -function _onUpdateChild(doc,el,newChild){ - if(doc && doc._inc){ - doc._inc++; - //update childNodes - var cs = el.childNodes; - if(newChild){ - cs[cs.length++] = newChild; - }else{ - //console.log(1) - var child = el.firstChild; - var i = 0; - while(child){ - cs[i++] = child; - child =child.nextSibling; - } - cs.length = i; - } - } -} - -/** - * attributes; - * children; - * - * writeable properties: - * nodeValue,Attr:value,CharacterData:data - * prefix - */ -function _removeChild(parentNode,child){ - var previous = child.previousSibling; - var next = child.nextSibling; - if(previous){ - previous.nextSibling = next; - }else{ - parentNode.firstChild = next - } - if(next){ - next.previousSibling = previous; - }else{ - parentNode.lastChild = previous; - } - _onUpdateChild(parentNode.ownerDocument,parentNode); - return child; -} -/** - * preformance key(refChild == null) - */ -function _insertBefore(parentNode,newChild,nextChild){ - var cp = newChild.parentNode; - if(cp){ - cp.removeChild(newChild);//remove and update - } - if(newChild.nodeType === DOCUMENT_FRAGMENT_NODE){ - var newFirst = newChild.firstChild; - var newLast = newChild.lastChild; - }else{ - newFirst = newLast = newChild; - } - var pre = nextChild ? nextChild.previousSibling : parentNode.lastChild; - - newFirst.previousSibling = pre; - newLast.nextSibling = nextChild; - - - if(pre){ - pre.nextSibling = newFirst; - }else{ - parentNode.firstChild = newFirst; - } - if(nextChild == null){ - parentNode.lastChild = newLast; - }else{ - nextChild.previousSibling = newLast; - } - do{ - newFirst.parentNode = parentNode; - }while(newFirst !== newLast && (newFirst= newFirst.nextSibling)) - _onUpdateChild(parentNode.ownerDocument||parentNode,parentNode); - //console.log(parentNode.lastChild.nextSibling == null) - if (newChild.nodeType == DOCUMENT_FRAGMENT_NODE) { - newChild.firstChild = newChild.lastChild = null; - } - return newChild; -} -function _appendSingleChild(parentNode,newChild){ - var cp = newChild.parentNode; - if(cp){ - var pre = parentNode.lastChild; - cp.removeChild(newChild);//remove and update - var pre = parentNode.lastChild; - } - var pre = parentNode.lastChild; - newChild.parentNode = parentNode; - newChild.previousSibling = pre; - newChild.nextSibling = null; - if(pre){ - pre.nextSibling = newChild; - }else{ - parentNode.firstChild = newChild; - } - parentNode.lastChild = newChild; - _onUpdateChild(parentNode.ownerDocument,parentNode,newChild); - return newChild; - //console.log("__aa",parentNode.lastChild.nextSibling == null) -} -Document.prototype = { - //implementation : null, - nodeName : '#document', - nodeType : DOCUMENT_NODE, - doctype : null, - documentElement : null, - _inc : 1, - - insertBefore : function(newChild, refChild){//raises - if(newChild.nodeType == DOCUMENT_FRAGMENT_NODE){ - var child = newChild.firstChild; - while(child){ - var next = child.nextSibling; - this.insertBefore(child,refChild); - child = next; - } - return newChild; - } - if(this.documentElement == null && newChild.nodeType == 1){ - this.documentElement = newChild; - } - - return _insertBefore(this,newChild,refChild),(newChild.ownerDocument = this),newChild; - }, - removeChild : function(oldChild){ - if(this.documentElement == oldChild){ - this.documentElement = null; - } - return _removeChild(this,oldChild); - }, - // Introduced in DOM Level 2: - importNode : function(importedNode,deep){ - return importNode(this,importedNode,deep); - }, - // Introduced in DOM Level 2: - getElementById : function(id){ - var rtv = null; - _visitNode(this.documentElement,function(node){ - if(node.nodeType == 1){ - if(node.getAttribute('id') == id){ - rtv = node; - return true; - } - } - }) - return rtv; - }, - - //document factory method: - createElement : function(tagName){ - var node = new Element(); - node.ownerDocument = this; - node.nodeName = tagName; - node.tagName = tagName; - node.childNodes = new NodeList(); - var attrs = node.attributes = new NamedNodeMap(); - attrs._ownerElement = node; - return node; - }, - createDocumentFragment : function(){ - var node = new DocumentFragment(); - node.ownerDocument = this; - node.childNodes = new NodeList(); - return node; - }, - createTextNode : function(data){ - var node = new Text(); - node.ownerDocument = this; - node.appendData(data) - return node; - }, - createComment : function(data){ - var node = new Comment(); - node.ownerDocument = this; - node.appendData(data) - return node; - }, - createCDATASection : function(data){ - var node = new CDATASection(); - node.ownerDocument = this; - node.appendData(data) - return node; - }, - createProcessingInstruction : function(target,data){ - var node = new ProcessingInstruction(); - node.ownerDocument = this; - node.tagName = node.target = target; - node.nodeValue= node.data = data; - return node; - }, - createAttribute : function(name){ - var node = new Attr(); - node.ownerDocument = this; - node.name = name; - node.nodeName = name; - node.localName = name; - node.specified = true; - return node; - }, - createEntityReference : function(name){ - var node = new EntityReference(); - node.ownerDocument = this; - node.nodeName = name; - return node; - }, - // Introduced in DOM Level 2: - createElementNS : function(namespaceURI,qualifiedName){ - var node = new Element(); - var pl = qualifiedName.split(':'); - var attrs = node.attributes = new NamedNodeMap(); - node.childNodes = new NodeList(); - node.ownerDocument = this; - node.nodeName = qualifiedName; - node.tagName = qualifiedName; - node.namespaceURI = namespaceURI; - if(pl.length == 2){ - node.prefix = pl[0]; - node.localName = pl[1]; - }else{ - //el.prefix = null; - node.localName = qualifiedName; - } - attrs._ownerElement = node; - return node; - }, - // Introduced in DOM Level 2: - createAttributeNS : function(namespaceURI,qualifiedName){ - var node = new Attr(); - var pl = qualifiedName.split(':'); - node.ownerDocument = this; - node.nodeName = qualifiedName; - node.name = qualifiedName; - node.namespaceURI = namespaceURI; - node.specified = true; - if(pl.length == 2){ - node.prefix = pl[0]; - node.localName = pl[1]; - }else{ - //el.prefix = null; - node.localName = qualifiedName; - } - return node; - } -}; -_extends(Document,Node); - - -function Element() { - this._nsMap = {}; -}; -Element.prototype = { - nodeType : ELEMENT_NODE, - hasAttribute : function(name){ - return this.getAttributeNode(name)!=null; - }, - getAttribute : function(name){ - var attr = this.getAttributeNode(name); - return attr && attr.value || ''; - }, - getAttributeNode : function(name){ - return this.attributes.getNamedItem(name); - }, - setAttribute : function(name, value){ - var attr = this.ownerDocument.createAttribute(name); - attr.value = attr.nodeValue = "" + value; - this.setAttributeNode(attr) - }, - removeAttribute : function(name){ - var attr = this.getAttributeNode(name) - attr && this.removeAttributeNode(attr); - }, - - //four real opeartion method - appendChild:function(newChild){ - if(newChild.nodeType === DOCUMENT_FRAGMENT_NODE){ - return this.insertBefore(newChild,null); - }else{ - return _appendSingleChild(this,newChild); - } - }, - setAttributeNode : function(newAttr){ - return this.attributes.setNamedItem(newAttr); - }, - setAttributeNodeNS : function(newAttr){ - return this.attributes.setNamedItemNS(newAttr); - }, - removeAttributeNode : function(oldAttr){ - return this.attributes.removeNamedItem(oldAttr.nodeName); - }, - //get real attribute name,and remove it by removeAttributeNode - removeAttributeNS : function(namespaceURI, localName){ - var old = this.getAttributeNodeNS(namespaceURI, localName); - old && this.removeAttributeNode(old); - }, - - hasAttributeNS : function(namespaceURI, localName){ - return this.getAttributeNodeNS(namespaceURI, localName)!=null; - }, - getAttributeNS : function(namespaceURI, localName){ - var attr = this.getAttributeNodeNS(namespaceURI, localName); - return attr && attr.value || ''; - }, - setAttributeNS : function(namespaceURI, qualifiedName, value){ - var attr = this.ownerDocument.createAttributeNS(namespaceURI, qualifiedName); - attr.value = attr.nodeValue = value; - this.setAttributeNode(attr) - }, - getAttributeNodeNS : function(namespaceURI, localName){ - return this.attributes.getNamedItemNS(namespaceURI, localName); - }, - - getElementsByTagName : function(tagName){ - return new LiveNodeList(this,function(base){ - var ls = []; - _visitNode(base,function(node){ - if(node !== base && node.nodeType == ELEMENT_NODE && (tagName === '*' || node.tagName == tagName)){ - ls.push(node); - } - }); - return ls; - }); - }, - getElementsByTagNameNS : function(namespaceURI, localName){ - return new LiveNodeList(this,function(base){ - var ls = []; - _visitNode(base,function(node){ - if(node !== base && node.nodeType === ELEMENT_NODE && node.namespaceURI === namespaceURI && (localName === '*' || node.localName == localName)){ - ls.push(node); - } - }); - return ls; - }); - } -}; -Document.prototype.getElementsByTagName = Element.prototype.getElementsByTagName; -Document.prototype.getElementsByTagNameNS = Element.prototype.getElementsByTagNameNS; - - -_extends(Element,Node); -function Attr() { -}; -Attr.prototype.nodeType = ATTRIBUTE_NODE; -_extends(Attr,Node); - - -function CharacterData() { -}; -CharacterData.prototype = { - data : '', - substringData : function(offset, count) { - return this.data.substring(offset, offset+count); - }, - appendData: function(text) { - text = this.data+text; - this.nodeValue = this.data = text; - this.length = text.length; - }, - insertData: function(offset,text) { - this.replaceData(offset,0,text); - - }, - appendChild:function(newChild){ - //if(!(newChild instanceof CharacterData)){ - throw new Error(ExceptionMessage[3]) - //} - return Node.prototype.appendChild.apply(this,arguments) - }, - deleteData: function(offset, count) { - this.replaceData(offset,count,""); - }, - replaceData: function(offset, count, text) { - var start = this.data.substring(0,offset); - var end = this.data.substring(offset+count); - text = start + text + end; - this.nodeValue = this.data = text; - this.length = text.length; - } -} -_extends(CharacterData,Node); -function Text() { -}; -Text.prototype = { - nodeName : "#text", - nodeType : TEXT_NODE, - splitText : function(offset) { - var text = this.data; - var newText = text.substring(offset); - text = text.substring(0, offset); - this.data = this.nodeValue = text; - this.length = text.length; - var newNode = this.ownerDocument.createTextNode(newText); - if(this.parentNode){ - this.parentNode.insertBefore(newNode, this.nextSibling); - } - return newNode; - } -} -_extends(Text,CharacterData); -function Comment() { -}; -Comment.prototype = { - nodeName : "#comment", - nodeType : COMMENT_NODE -} -_extends(Comment,CharacterData); - -function CDATASection() { -}; -CDATASection.prototype = { - nodeName : "#cdata-section", - nodeType : CDATA_SECTION_NODE -} -_extends(CDATASection,CharacterData); - - -function DocumentType() { -}; -DocumentType.prototype.nodeType = DOCUMENT_TYPE_NODE; -_extends(DocumentType,Node); - -function Notation() { -}; -Notation.prototype.nodeType = NOTATION_NODE; -_extends(Notation,Node); - -function Entity() { -}; -Entity.prototype.nodeType = ENTITY_NODE; -_extends(Entity,Node); - -function EntityReference() { -}; -EntityReference.prototype.nodeType = ENTITY_REFERENCE_NODE; -_extends(EntityReference,Node); - -function DocumentFragment() { -}; -DocumentFragment.prototype.nodeName = "#document-fragment"; -DocumentFragment.prototype.nodeType = DOCUMENT_FRAGMENT_NODE; -_extends(DocumentFragment,Node); - - -function ProcessingInstruction() { -} -ProcessingInstruction.prototype.nodeType = PROCESSING_INSTRUCTION_NODE; -_extends(ProcessingInstruction,Node); -function XMLSerializer(){} -XMLSerializer.prototype.serializeToString = function(node){ - var buf = []; - serializeToString(node,buf); - return buf.join(''); -} -Node.prototype.toString =function(){ - return XMLSerializer.prototype.serializeToString(this); -} -function serializeToString(node,buf){ - switch(node.nodeType){ - case ELEMENT_NODE: - var attrs = node.attributes; - var len = attrs.length; - var child = node.firstChild; - var nodeName = node.tagName; - var isHTML = htmlns === node.namespaceURI - buf.push('<',nodeName); - for(var i=0;i'); - //if is cdata child node - if(isHTML && /^script$/i.test(nodeName)){ - if(child){ - buf.push(child.data); - } - }else{ - while(child){ - serializeToString(child,buf); - child = child.nextSibling; - } - } - buf.push(''); - }else{ - buf.push('/>'); - } - return; - case DOCUMENT_NODE: - case DOCUMENT_FRAGMENT_NODE: - var child = node.firstChild; - while(child){ - serializeToString(child,buf); - child = child.nextSibling; - } - return; - case ATTRIBUTE_NODE: - return buf.push(' ',node.name,'="',node.value.replace(/[<&"]/g,_xmlEncoder),'"'); - case TEXT_NODE: - return buf.push(node.data.replace(/[<&]/g,_xmlEncoder)); - case CDATA_SECTION_NODE: - return buf.push( ''); - case COMMENT_NODE: - return buf.push( ""); - case DOCUMENT_TYPE_NODE: - var pubid = node.publicId; - var sysid = node.systemId; - buf.push(''); - }else if(sysid && sysid!='.'){ - buf.push(' SYSTEM "',sysid,'">'); - }else{ - var sub = node.internalSubset; - if(sub){ - buf.push(" [",sub,"]"); - } - buf.push(">"); - } - return; - case PROCESSING_INSTRUCTION_NODE: - return buf.push( ""); - case ENTITY_REFERENCE_NODE: - return buf.push( '&',node.nodeName,';'); - //case ENTITY_NODE: - //case NOTATION_NODE: - default: - buf.push('??',node.nodeName); - } -} -function importNode(doc,node,deep){ - var node2; - switch (node.nodeType) { - case ELEMENT_NODE: - node2 = node.cloneNode(false); - node2.ownerDocument = doc; - var attrs = node2.attributes; - var len = attrs.length; - for(var i=0;i=0.1" - }, - "dependencies": {}, - "devDependencies": {}, - "maintainers": [ - { - "name": "jindw", - "email": "jindw@xidea.org", - "url": "http://www.xidea.org" - } - ], - "contributors": [ - { - "name": "Yaron Naveh", - "email": "yaronn01@gmail.com", - "url": "http://webservices20.blogspot.com/" - }, - { - "name": "Harutyun Amirjanyan", - "email": "amirjanyan@gmail.com", - "url": "https://github.com/nightwing" - }, - { - "name": "bigeasy", - "email": "alan@prettyrobots.com", - "url": "http://www.prettyrobots.com/" - } - ], - "bugs": { - "email": "jindw@xidea.org", - "url": "http://github.com/jindw/xmldom/issues" - }, - "licenses": [ - { - "type": "LGPL", - "url": "http://www.gnu.org/licenses/lgpl.html" - } - ], - "readme": "Introduction\n-------\nAnother xml parser for nodejs/browser/rhino for java.\nFully compatible with `W3C DOM level2`; and some compatible with `level3`.\nsupport `DOMParser` and `XMLSerializer` interface such as in browser.\n\nInstall:\n-------\n>npm install xmldom\n\nExample:\n====\n```javascript\nvar DOMParser = require('xmldom').DOMParser;\nvar doc = new DOMParser().parseFromString(\n '\\n'+\n '\\ttest\\n'+\n '\\t\\n'+\n '\\t\\n'+\n ''\n ,'text/xml');\ndoc.documentElement.setAttribute('x','y');\ndoc.documentElement.setAttributeNS('./lite','c:x','y2');\nvar nsAttr = doc.documentElement.getAttributeNS('./lite','x')\nconsole.info(nsAttr)\nconsole.info(doc)\n```\nAPI Reference\n=====\n\n * [DOMParser](https://developer.mozilla.org/en/DOMParser):\n\n\t```javascript\n\tparseFromString(xmlsource,mimeType)\n\t```\n\t* **options extension** _by xmldom_(not BOM standard!!)\n\n\t```javascript\n\t//added the options argument\n\tnew DOMParser(options)\n\t\n\t//errorHandler is supported\n\tnew DOMParser({\n\t\t/**\n\t\t * youcan override the errorHandler for xml parser\n\t\t * @link http://www.saxproject.org/apidoc/org/xml/sax/ErrorHandler.html\n\t\t */\n\t\terrorHandler:{warning:callback,error:callback,fatalError:callback}\n\t})\n\t\t\n\t```\n\n * [XMLSerializer](https://developer.mozilla.org/en/XMLSerializer)\n \n\t```javascript\n\tserializeToString(node)\n\t```\nDOM level2 method and attribute:\n------\n\n * [Node](http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-1950641247)\n\t\n\t\tattribute:\n\t\t\tnodeValue|prefix\n\t\treadonly attribute:\n\t\t\tnodeName|nodeType|parentNode|childNodes|firstChild|lastChild|previousSibling|nextSibling|attributes|ownerDocument|namespaceURI|localName\n\t\tmethod:\t\n\t\t\tinsertBefore(newChild, refChild)\n\t\t\treplaceChild(newChild, oldChild)\n\t\t\tremoveChild(oldChild)\n\t\t\tappendChild(newChild)\n\t\t\thasChildNodes()\n\t\t\tcloneNode(deep)\n\t\t\tnormalize()\n\t\t\tisSupported(feature, version)\n\t\t\thasAttributes()\n\n * [DOMImplementation](http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-102161490)\n\t\t\n\t\tmethod:\n\t\t\thasFeature(feature, version)\n\t\t\tcreateDocumentType(qualifiedName, publicId, systemId)\n\t\t\tcreateDocument(namespaceURI, qualifiedName, doctype)\n\n * [Document](http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#i-Document) : Node\n\t\t\n\t\treadonly attribute:\n\t\t\tdoctype|implementation|documentElement\n\t\tmethod:\n\t\t\tcreateElement(tagName)\n\t\t\tcreateDocumentFragment()\n\t\t\tcreateTextNode(data)\n\t\t\tcreateComment(data)\n\t\t\tcreateCDATASection(data)\n\t\t\tcreateProcessingInstruction(target, data)\n\t\t\tcreateAttribute(name)\n\t\t\tcreateEntityReference(name)\n\t\t\tgetElementsByTagName(tagname)\n\t\t\timportNode(importedNode, deep)\n\t\t\tcreateElementNS(namespaceURI, qualifiedName)\n\t\t\tcreateAttributeNS(namespaceURI, qualifiedName)\n\t\t\tgetElementsByTagNameNS(namespaceURI, localName)\n\t\t\tgetElementById(elementId)\n\n * [DocumentFragment](http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-B63ED1A3) : Node\n * [Element](http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-745549614) : Node\n\t\t\n\t\treadonly attribute:\n\t\t\ttagName\n\t\tmethod:\n\t\t\tgetAttribute(name)\n\t\t\tsetAttribute(name, value)\n\t\t\tremoveAttribute(name)\n\t\t\tgetAttributeNode(name)\n\t\t\tsetAttributeNode(newAttr)\n\t\t\tremoveAttributeNode(oldAttr)\n\t\t\tgetElementsByTagName(name)\n\t\t\tgetAttributeNS(namespaceURI, localName)\n\t\t\tsetAttributeNS(namespaceURI, qualifiedName, value)\n\t\t\tremoveAttributeNS(namespaceURI, localName)\n\t\t\tgetAttributeNodeNS(namespaceURI, localName)\n\t\t\tsetAttributeNodeNS(newAttr)\n\t\t\tgetElementsByTagNameNS(namespaceURI, localName)\n\t\t\thasAttribute(name)\n\t\t\thasAttributeNS(namespaceURI, localName)\n\n * [Attr](http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-637646024) : Node\n\t\n\t\tattribute:\n\t\t\tvalue\n\t\treadonly attribute:\n\t\t\tname|specified|ownerElement\n\n * [NodeList](http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-536297177)\n\t\t\n\t\treadonly attribute:\n\t\t\tlength\n\t\tmethod:\n\t\t\titem(index)\n\t\n * [NamedNodeMap](http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-1780488922)\n\n\t\treadonly attribute:\n\t\t\tlength\n\t\tmethod:\n\t\t\tgetNamedItem(name)\n\t\t\tsetNamedItem(arg)\n\t\t\tremoveNamedItem(name)\n\t\t\titem(index)\n\t\t\tgetNamedItemNS(namespaceURI, localName)\n\t\t\tsetNamedItemNS(arg)\n\t\t\tremoveNamedItemNS(namespaceURI, localName)\n\t\t\n * [CharacterData](http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-FF21A306) : Node\n\t\n\t\tmethod:\n\t\t\tsubstringData(offset, count)\n\t\t\tappendData(arg)\n\t\t\tinsertData(offset, arg)\n\t\t\tdeleteData(offset, count)\n\t\t\treplaceData(offset, count, arg)\n\t\t\n * [Text](http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-1312295772) : CharacterData\n\t\n\t\tmethod:\n\t\t\tsplitText(offset)\n\t\t\t\n * [CDATASection](http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-667469212)\n * [Comment](http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-1728279322) : CharacterData\n\t\n * [DocumentType](http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-412266927)\n\t\n\t\treadonly attribute:\n\t\t\tname|entities|notations|publicId|systemId|internalSubset\n\t\t\t\n * Notation : Node\n\t\n\t\treadonly attribute:\n\t\t\tpublicId|systemId\n\t\t\t\n * Entity : Node\n\t\n\t\treadonly attribute:\n\t\t\tpublicId|systemId|notationName\n\t\t\t\n * EntityReference : Node \n * ProcessingInstruction : Node \n\t\n\t\tattribute:\n\t\t\tdata\n\t\treadonly attribute:\n\t\t\ttarget\n\t\t\nDOM level 3 support:\n-----\n\n * [Node](http://www.w3.org/TR/DOM-Level-3-Core/core.html#Node3-textContent)\n\t\t\n\t\tattribute:\n\t\t\ttextContent\n\t\tmethod:\n\t\t\tisDefaultNamespace(namespaceURI){\n\t\t\tlookupNamespaceURI(prefix)\n\nDOM extension by xmldom\n---\n * [Node]\n\tSource position extension; \n\t\tattribute:\n\t\t\t//Numbered starting from '1'\n\t\t\tlineNumber\n\t\t\t//Numbered starting from '1'\n\t\t\tcolumnNumber\n", - "_id": "xmldom@0.1.13", - "dist": { - "shasum": "54a7bcad1fc60da141d25a1db785286ca06ee7a4" - }, - "_from": "/home/th/Downloads/xmldom-0.1.13.tgz" -} diff --git a/node_modules/xmldom/readme.md b/node_modules/xmldom/readme.md deleted file mode 100644 index b1deb8028..000000000 --- a/node_modules/xmldom/readme.md +++ /dev/null @@ -1,213 +0,0 @@ -Introduction -------- -Another xml parser for nodejs/browser/rhino for java. -Fully compatible with `W3C DOM level2`; and some compatible with `level3`. -support `DOMParser` and `XMLSerializer` interface such as in browser. - -Install: -------- ->npm install xmldom - -Example: -==== -```javascript -var DOMParser = require('xmldom').DOMParser; -var doc = new DOMParser().parseFromString( - '\n'+ - '\ttest\n'+ - '\t\n'+ - '\t\n'+ - '' - ,'text/xml'); -doc.documentElement.setAttribute('x','y'); -doc.documentElement.setAttributeNS('./lite','c:x','y2'); -var nsAttr = doc.documentElement.getAttributeNS('./lite','x') -console.info(nsAttr) -console.info(doc) -``` -API Reference -===== - - * [DOMParser](https://developer.mozilla.org/en/DOMParser): - - ```javascript - parseFromString(xmlsource,mimeType) - ``` - * **options extension** _by xmldom_(not BOM standard!!) - - ```javascript - //added the options argument - new DOMParser(options) - - //errorHandler is supported - new DOMParser({ - /** - * youcan override the errorHandler for xml parser - * @link http://www.saxproject.org/apidoc/org/xml/sax/ErrorHandler.html - */ - errorHandler:{warning:callback,error:callback,fatalError:callback} - }) - - ``` - - * [XMLSerializer](https://developer.mozilla.org/en/XMLSerializer) - - ```javascript - serializeToString(node) - ``` -DOM level2 method and attribute: ------- - - * [Node](http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-1950641247) - - attribute: - nodeValue|prefix - readonly attribute: - nodeName|nodeType|parentNode|childNodes|firstChild|lastChild|previousSibling|nextSibling|attributes|ownerDocument|namespaceURI|localName - method: - insertBefore(newChild, refChild) - replaceChild(newChild, oldChild) - removeChild(oldChild) - appendChild(newChild) - hasChildNodes() - cloneNode(deep) - normalize() - isSupported(feature, version) - hasAttributes() - - * [DOMImplementation](http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-102161490) - - method: - hasFeature(feature, version) - createDocumentType(qualifiedName, publicId, systemId) - createDocument(namespaceURI, qualifiedName, doctype) - - * [Document](http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#i-Document) : Node - - readonly attribute: - doctype|implementation|documentElement - method: - createElement(tagName) - createDocumentFragment() - createTextNode(data) - createComment(data) - createCDATASection(data) - createProcessingInstruction(target, data) - createAttribute(name) - createEntityReference(name) - getElementsByTagName(tagname) - importNode(importedNode, deep) - createElementNS(namespaceURI, qualifiedName) - createAttributeNS(namespaceURI, qualifiedName) - getElementsByTagNameNS(namespaceURI, localName) - getElementById(elementId) - - * [DocumentFragment](http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-B63ED1A3) : Node - * [Element](http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-745549614) : Node - - readonly attribute: - tagName - method: - getAttribute(name) - setAttribute(name, value) - removeAttribute(name) - getAttributeNode(name) - setAttributeNode(newAttr) - removeAttributeNode(oldAttr) - getElementsByTagName(name) - getAttributeNS(namespaceURI, localName) - setAttributeNS(namespaceURI, qualifiedName, value) - removeAttributeNS(namespaceURI, localName) - getAttributeNodeNS(namespaceURI, localName) - setAttributeNodeNS(newAttr) - getElementsByTagNameNS(namespaceURI, localName) - hasAttribute(name) - hasAttributeNS(namespaceURI, localName) - - * [Attr](http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-637646024) : Node - - attribute: - value - readonly attribute: - name|specified|ownerElement - - * [NodeList](http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-536297177) - - readonly attribute: - length - method: - item(index) - - * [NamedNodeMap](http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-1780488922) - - readonly attribute: - length - method: - getNamedItem(name) - setNamedItem(arg) - removeNamedItem(name) - item(index) - getNamedItemNS(namespaceURI, localName) - setNamedItemNS(arg) - removeNamedItemNS(namespaceURI, localName) - - * [CharacterData](http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-FF21A306) : Node - - method: - substringData(offset, count) - appendData(arg) - insertData(offset, arg) - deleteData(offset, count) - replaceData(offset, count, arg) - - * [Text](http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-1312295772) : CharacterData - - method: - splitText(offset) - - * [CDATASection](http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-667469212) - * [Comment](http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-1728279322) : CharacterData - - * [DocumentType](http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-412266927) - - readonly attribute: - name|entities|notations|publicId|systemId|internalSubset - - * Notation : Node - - readonly attribute: - publicId|systemId - - * Entity : Node - - readonly attribute: - publicId|systemId|notationName - - * EntityReference : Node - * ProcessingInstruction : Node - - attribute: - data - readonly attribute: - target - -DOM level 3 support: ------ - - * [Node](http://www.w3.org/TR/DOM-Level-3-Core/core.html#Node3-textContent) - - attribute: - textContent - method: - isDefaultNamespace(namespaceURI){ - lookupNamespaceURI(prefix) - -DOM extension by xmldom ---- - * [Node] - Source position extension; - attribute: - //Numbered starting from '1' - lineNumber - //Numbered starting from '1' - columnNumber diff --git a/node_modules/xmldom/sax.js b/node_modules/xmldom/sax.js deleted file mode 100644 index 901dfe01c..000000000 --- a/node_modules/xmldom/sax.js +++ /dev/null @@ -1,551 +0,0 @@ -//[4] NameStartChar ::= ":" | [A-Z] | "_" | [a-z] | [#xC0-#xD6] | [#xD8-#xF6] | [#xF8-#x2FF] | [#x370-#x37D] | [#x37F-#x1FFF] | [#x200C-#x200D] | [#x2070-#x218F] | [#x2C00-#x2FEF] | [#x3001-#xD7FF] | [#xF900-#xFDCF] | [#xFDF0-#xFFFD] | [#x10000-#xEFFFF] -//[4a] NameChar ::= NameStartChar | "-" | "." | [0-9] | #xB7 | [#x0300-#x036F] | [#x203F-#x2040] -//[5] Name ::= NameStartChar (NameChar)* -var nameStartChar = /[A-Z_a-z\xC0-\xD6\xD8-\xF6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD]///\u10000-\uEFFFF -var nameChar = new RegExp("[\\-\\.0-9"+nameStartChar.source.slice(1,-1)+"\u00B7\u0300-\u036F\\ux203F-\u2040]"); -var tagNamePattern = new RegExp('^'+nameStartChar.source+nameChar.source+'*(?:\:'+nameStartChar.source+nameChar.source+'*)?$'); -//var tagNamePattern = /^[a-zA-Z_][\w\-\.]*(?:\:[a-zA-Z_][\w\-\.]*)?$/ -//var handlers = 'resolveEntity,getExternalSubset,characters,endDocument,endElement,endPrefixMapping,ignorableWhitespace,processingInstruction,setDocumentLocator,skippedEntity,startDocument,startElement,startPrefixMapping,notationDecl,unparsedEntityDecl,error,fatalError,warning,attributeDecl,elementDecl,externalEntityDecl,internalEntityDecl,comment,endCDATA,endDTD,endEntity,startCDATA,startDTD,startEntity'.split(',') - -//S_TAG, S_ATTR, S_EQ, S_V -//S_ATTR_S, S_E, S_S, S_C -var S_TAG = 0;//tag name offerring -var S_ATTR = 1;//attr name offerring -var S_ATTR_S=2;//attr name end and space offer -var S_EQ = 3;//=space? -var S_V = 4;//attr value(no quot value only) -var S_E = 5;//attr value end and no space(quot end) -var S_S = 6;//(attr value end || tag end ) && (space offer) -var S_C = 7;//closed el - -function XMLReader(){ -} - -XMLReader.prototype = { - parse:function(source,defaultNSMap,entityMap){ - var domBuilder = this.domBuilder; - domBuilder.startDocument(); - _copy(defaultNSMap ,defaultNSMap = {}) - parse(source,defaultNSMap,entityMap, - domBuilder,this.errorHandler); - domBuilder.endDocument(); - } -} -function parse(source,defaultNSMapCopy,entityMap,domBuilder,errorHandler){ - function entityReplacer(a){ - var k = a.slice(1,-1); - if(k in entityMap){ - return entityMap[k]; - }else if(k.charAt(0) === '#'){ - return String.fromCharCode(parseInt(k.substr(1).replace('x','0x'))) - }else{ - errorHandler.error('entity not found:'+a); - return a; - } - } - function appendText(end){//has some bugs - var xt = source.substring(start,end).replace(/&#?\w+;/g,entityReplacer); - locator&&position(start); - domBuilder.characters(xt,0,end-start); - start = end - } - function position(start,m){ - while(start>=endPos && (m = linePattern.exec(source))){ - startPos = m.index; - endPos = startPos + m[0].length; - locator.lineNumber++; - //console.log('line++:',locator,startPos,endPos) - } - locator.columnNumber = start-startPos+1; - } - var startPos = 0; - var endPos = 0; - var linePattern = /.+(?:\r\n?|\n)|.*$/g - var locator = domBuilder.locator; - - var parseStack = [{currentNSMap:defaultNSMapCopy}] - var closeMap = {}; - var start = 0; - while(true){ - var i = source.indexOf('<',start); - if(i>start){ - appendText(i); - } - switch(source.charAt(i+1)){ - case '/': - var end = source.indexOf('>',i+3); - var tagName = source.substring(i+2,end); - var config = parseStack.pop(); - var localNSMap = config.localNSMap; - - if(config.tagName != tagName){ - errorHandler.fatalError("end tag name: "+tagName+' is not match the current start tagName:'+config.tagName ); - } - domBuilder.endElement(config.uri,config.localName,tagName); - if(localNSMap){ - for(var prefix in localNSMap){ - domBuilder.endPrefixMapping(prefix) ; - } - } - end++; - break; - // end elment - case '?':// - locator&&position(i); - end = parseInstruction(source,i,domBuilder); - break; - case '!':// 0){ - value = source.slice(start,p).replace(/&#?\w+;/g,entityReplacer); - el.add(attrName,value,start-1); - s = S_E; - }else{ - //fatalError: no end quot match - throw new Error('attribute value no end \''+c+'\' match'); - } - }else if(s == S_V){ - value = source.slice(start,p).replace(/&#?\w+;/g,entityReplacer); - //console.log(attrName,value,start,p) - el.add(attrName,value,start); - //console.dir(el) - errorHandler.warning('attribute "'+attrName+'" missed start quot('+c+')!!'); - start = p+1; - s = S_E - }else{ - //fatalError: no equal before - throw new Error('attribute value must after "="'); - } - break; - case '/': - switch(s){ - case S_TAG: - el.setTagName(source.slice(start,p)); - case S_E: - case S_S: - case S_C: - s = S_C; - el.closed = true; - case S_V: - case S_ATTR: - case S_ATTR_S: - break; - //case S_EQ: - default: - throw new Error("attribute invalid close char('/')") - } - break; - case '>': - switch(s){ - case S_TAG: - el.setTagName(source.slice(start,p)); - case S_E: - case S_S: - case S_C: - break;//normal - case S_V://Compatible state - case S_ATTR: - value = source.slice(start,p); - if(value.slice(-1) === '/'){ - el.closed = true; - value = value.slice(0,-1) - } - case S_ATTR_S: - if(s === S_ATTR_S){ - value = attrName; - } - if(s == S_V){ - errorHandler.warning('attribute "'+value+'" missed quot(")!!'); - el.add(attrName,value.replace(/&#?\w+;/g,entityReplacer),start) - }else{ - errorHandler.warning('attribute "'+value+'" missed value!! "'+value+'" instead!!') - el.add(value,value,start) - } - break; - case S_EQ: - throw new Error('attribute value missed!!'); - } -// console.log(tagName,tagNamePattern,tagNamePattern.test(tagName)) - return p; - /*xml space '\x20' | #x9 | #xD | #xA; */ - case '\u0080': - c = ' '; - default: - if(c<= ' '){//space - switch(s){ - case S_TAG: - el.setTagName(source.slice(start,p));//tagName - s = S_S; - break; - case S_ATTR: - attrName = source.slice(start,p) - s = S_ATTR_S; - break; - case S_V: - var value = source.slice(start,p).replace(/&#?\w+;/g,entityReplacer); - errorHandler.warning('attribute "'+value+'" missed quot(")!!'); - el.add(attrName,value,start) - case S_E: - s = S_S; - break; - //case S_S: - //case S_EQ: - //case S_ATTR_S: - // void();break; - //case S_C: - //ignore warning - } - }else{//not space -//S_TAG, S_ATTR, S_EQ, S_V -//S_ATTR_S, S_E, S_S, S_C - switch(s){ - //case S_TAG:void();break; - //case S_ATTR:void();break; - //case S_V:void();break; - case S_ATTR_S: - errorHandler.warning('attribute "'+attrName+'" missed value!! "'+attrName+'" instead!!') - el.add(attrName,attrName,start); - start = p; - s = S_ATTR; - break; - case S_E: - errorHandler.warning('attribute space is required"'+attrName+'"!!') - case S_S: - s = S_ATTR; - start = p; - break; - case S_EQ: - s = S_V; - start = p; - break; - case S_C: - throw new Error("elements closed character '/' and '>' must be connected to"); - } - } - } - p++; - } -} -/** - * @return end of the elementStartPart(end of elementEndPart for selfClosed el) - */ -function appendElement(el,domBuilder,parseStack){ - var tagName = el.tagName; - var localNSMap = null; - var currentNSMap = parseStack[parseStack.length-1].currentNSMap; - var i = el.length; - while(i--){ - var a = el[i]; - var qName = a.qName; - var value = a.value; - var nsp = qName.indexOf(':'); - if(nsp>0){ - var prefix = a.prefix = qName.slice(0,nsp); - var localName = qName.slice(nsp+1); - var nsPrefix = prefix === 'xmlns' && localName - }else{ - localName = qName; - prefix = null - nsPrefix = qName === 'xmlns' && '' - } - //can not set prefix,because prefix !== '' - a.localName = localName ; - //prefix == null for no ns prefix attribute - if(nsPrefix !== false){//hack!! - if(localNSMap == null){ - localNSMap = {} - _copy(currentNSMap,currentNSMap={}) - } - currentNSMap[nsPrefix] = localNSMap[nsPrefix] = value; - a.uri = 'http://www.w3.org/2000/xmlns/' - domBuilder.startPrefixMapping(nsPrefix, value) - } - } - var i = el.length; - while(i--){ - a = el[i]; - var prefix = a.prefix; - if(prefix){//no prefix attribute has no namespace - if(prefix === 'xml'){ - a.uri = 'http://www.w3.org/XML/1998/namespace'; - }if(prefix !== 'xmlns'){ - a.uri = currentNSMap[prefix] - } - } - } - var nsp = tagName.indexOf(':'); - if(nsp>0){ - prefix = el.prefix = tagName.slice(0,nsp); - localName = el.localName = tagName.slice(nsp+1); - }else{ - prefix = null;//important!! - localName = el.localName = tagName; - } - //no prefix element has default namespace - var ns = el.uri = currentNSMap[prefix || '']; - domBuilder.startElement(ns,localName,tagName,el); - //endPrefixMapping and startPrefixMapping have not any help for dom builder - //localNSMap = null - if(el.closed){ - domBuilder.endElement(ns,localName,tagName); - if(localNSMap){ - for(prefix in localNSMap){ - domBuilder.endPrefixMapping(prefix) - } - } - }else{ - el.currentNSMap = currentNSMap; - el.localNSMap = localNSMap; - parseStack.push(el); - } -} -function parseHtmlSpecialContent(source,elStartEnd,tagName,entityReplacer,domBuilder){ - if(/^(?:script|textarea)$/i.test(tagName)){ - var elEndStart = source.indexOf('',elStartEnd); - var text = source.substring(elStartEnd+1,elEndStart); - if(/[&<]/.test(text)){ - if(/^script$/i.test(tagName)){ - //if(!/\]\]>/.test(text)){ - //lexHandler.startCDATA(); - domBuilder.characters(text,0,text.length); - //lexHandler.endCDATA(); - return elEndStart; - //} - }//}else{//text area - text = text.replace(/&#?\w+;/g,entityReplacer); - domBuilder.characters(text,0,text.length); - return elEndStart; - //} - - } - } - return elStartEnd+1; -} -function fixSelfClosed(source,elStartEnd,tagName,closeMap){ - //if(tagName in closeMap){ - var pos = closeMap[tagName]; - if(pos == null){ - //console.log(tagName) - pos = closeMap[tagName] = source.lastIndexOf('') - } - return pos',start+4); - //append comment source.substring(4,end)//\n\n something\nx', 'text/xml'); - var test = doc.documentElement; - var a = test.firstChild.nextSibling; - assertPosition(doc.firstChild, 1, 1); - assertPosition(doc.firstChild.nextSibling, 1, 1+''.length); - assertPosition(test, 2, 1); - //assertPosition(test.firstChild, 1, 7); - assertPosition(a, 3, 3); - assertPosition(a.firstChild, 3, 19); - assertPosition(a.firstChild.nextSibling, 3, 19+''.length); - assertPosition(test.lastChild, 4, 5); - }, - 'error positions':function(){ - var error = [] - var parser = new DOMParser({ - locator:{systemId:'c:/test/1.xml'}, - errorHandler:function(msg){ - error.push(msg); - } - }); - var doc = parser.parseFromString('<;test', 'text/html'); - console.assert(/\n@c\:\/test\/1\.xml#\[line\:\d+,col\:\d+\]/.test(error.join(' ')),'line,col must record:'+error) - }, - 'error positions p':function(){ - var error = [] - var parser = new DOMParser({ - locator:{}, - errorHandler:function(msg){ - error.push(msg); - } - }); - var doc = parser.parseFromString('\n\t', 'text/html'); - var root = doc.documentElement; - var textNode = root.firstChild; - console.log(root+'/'+textNode) - console.assert(/\n@#\[line\:2,col\:2\]/.test(error.join(' ')),'line,col must record:'+error); - console.log(textNode.lineNumber+'/'+textNode.columnNumber) - } -}).run(); \ No newline at end of file diff --git a/node_modules/xmldom/test/namespace.js b/node_modules/xmldom/test/namespace.js deleted file mode 100644 index a06248c5b..000000000 --- a/node_modules/xmldom/test/namespace.js +++ /dev/null @@ -1,32 +0,0 @@ -var wows = require('vows'); -var DOMParser = require('xmldom').DOMParser; - -// Create a Test Suite -wows.describe('XML Namespace Parse').addBatch({ - 'default namespace': function () { - var dom = new DOMParser().parseFromString('','text/xml'); - var root = dom.documentElement; - console.assert(root.namespaceURI=='http://test.com') - console.assert(root.lookupNamespaceURI('') == 'http://test.com') - console.assert(root.firstChild.namespaceURI=='http://test.com') - console.assert(root.firstChild.lookupNamespaceURI('') == 'http://test.com') - console.assert(root.firstChild.getAttributeNode('attr').namespaceURI==null) - }, - 'prefix namespace': function () { - var dom = new DOMParser().parseFromString('','text/xml'); - var root = dom.documentElement; - console.assert(root.firstChild.namespaceURI == 'http://p1.com') - console.assert(root.lookupNamespaceURI('p1') == 'http://p1.com') - console.assert(root.firstChild.getAttributeNode('attr') == null) - console.assert(root.firstChild.getAttributeNode('p1:attr').namespaceURI == 'http://p1.com') - console.assert(root.firstChild.nextSibling.namespaceURI == 'http://p2.com') - console.assert(root.firstChild.nextSibling.lookupNamespaceURI('p2') == 'http://p2.com') - }, - 'after prefix namespace': function () { - var dom = new DOMParser().parseFromString('','text/xml'); - var root = dom.documentElement; - console.assert(root.firstChild.namespaceURI=='http://p.com') - console.assert(root.lastChild.namespaceURI=='http://test.com') - console.assert(root.firstChild.nextSibling.lookupNamespaceURI('p') == 'http://test.com') - } -}).run(); // Run it \ No newline at end of file diff --git a/node_modules/xmldom/test/node.js b/node_modules/xmldom/test/node.js deleted file mode 100644 index 6527eee8e..000000000 --- a/node_modules/xmldom/test/node.js +++ /dev/null @@ -1,102 +0,0 @@ -var wows = require('vows'); -var assert = require('assert'); -var DOMParser = require('xmldom').DOMParser; -var XMLSerializer = require('xmldom').XMLSerializer; -var parser = new DOMParser(); -// Create a Test Suite -wows.describe('XML Node Parse').addBatch({ - 'element': function () { - var dom = new DOMParser().parseFromString(''); - console.assert (dom.childNodes.length== 1,dom.childNodes.length, 1); - console.assert (dom.documentElement.childNodes.length== 1); - console.assert (dom.documentElement.tagName== 'xml'); - console.assert (dom.documentElement.firstChild.tagName== 'child'); - }, - 'text':function(){ - var dom = new DOMParser().parseFromString('start center end'); - var root = dom.documentElement; - console.assert( root.firstChild.data =='start center end'); - console.assert( root.firstChild.nextSibling ==null); - }, - 'cdata': function () { - var dom = new DOMParser().parseFromString('start ]]> end'); - var root = dom.documentElement; - console.assert ( root.firstChild.data =='start '); - console.assert ( root.firstChild.nextSibling.data ==''); - console.assert ( root.firstChild.nextSibling.nextSibling.nextSibling.data =='[[[[[[[[]]]]]]]]'); - }, - 'cdata empty': function () { - var dom = new DOMParser().parseFromString('start end'); - var root = dom.documentElement; - console.assert ( root.textContent =='start end'); - }, - 'comment': function(){ - var dom = new DOMParser().parseFromString(''); - var root = dom.documentElement; - console.assert ( root.firstChild.nodeValue ==' comment&>< '); - }, - 'cdata comment': function(){ - var dom = new DOMParser().parseFromString('start ]]> end'); - var root = dom.documentElement; - console.assert ( root.firstChild.nodeValue =='start '); - console.assert ( root.firstChild.nextSibling.nodeValue ==''); - console.assert ( root.firstChild.nextSibling.nextSibling.nextSibling.nodeValue ==' comment '); - console.assert ( root.firstChild.nextSibling.nextSibling.nextSibling.nextSibling.nodeValue =='end'); - }, - 'append node': function () { - var dom = new DOMParser().parseFromString(''); - var child = dom.createElement("child"); - console.assert ( child == dom.documentElement.appendChild(child)); - console.assert ( child == dom.documentElement.firstChild); - var fragment = new dom.createDocumentFragment(); - console.assert ( child == fragment.appendChild(child)); - }, - 'insert node': function () { - var dom = new DOMParser().parseFromString(''); - var node = dom.createElement("sibling"); - var child = dom.documentElement.firstChild; - child.parentNode.insertBefore(node, child); - console.assert ( node == child.previousSibling); - console.assert ( node.nextSibling == child); - console.assert ( node.parentNode == child.parentNode); - }, - 'insert fragment': function () { - var dom = new DOMParser().parseFromString(''); - var fragment = dom.createDocumentFragment(); - assert(fragment.nodeType === 11); - var first = fragment.appendChild(dom.createElement("first")); - var last = fragment.appendChild(dom.createElement("last")); - console.assert ( fragment.firstChild == first); - console.assert ( fragment.lastChild == last); - console.assert ( last.previousSibling == first); - console.assert ( first.nextSibling == last); - var child = dom.documentElement.firstChild; - child.parentNode.insertBefore(fragment, child); - console.assert ( last.previousSibling == first); - console.assert ( first.nextSibling == last); - console.assert ( child.parentNode.firstChild == first); - console.assert ( last == child.previousSibling); - console.assert ( last.nextSibling == child); - console.assert ( first.parentNode == child.parentNode); - console.assert ( last.parentNode == child.parentNode); - } -}).addBatch({ - "instruction":function(){ - var source = '&'; - var doc = new DOMParser().parseFromString(source,"text/xml"); - var source2 = new XMLSerializer().serializeToString(doc); - console.assert(source == source2,source2); - } -}).run(); // Run it -//var ELEMENT_NODE = NodeType.ELEMENT_NODE = 1; -//var ATTRIBUTE_NODE = NodeType.ATTRIBUTE_NODE = 2; -//var TEXT_NODE = NodeType.TEXT_NODE = 3; -//var CDATA_SECTION_NODE = NodeType.CDATA_SECTION_NODE = 4; -//var ENTITY_REFERENCE_NODE = NodeType.ENTITY_REFERENCE_NODE = 5; -//var ENTITY_NODE = NodeType.ENTITY_NODE = 6; -//var PROCESSING_INSTRUCTION_NODE = NodeType.PROCESSING_INSTRUCTION_NODE = 7; -//var COMMENT_NODE = NodeType.COMMENT_NODE = 8; -//var DOCUMENT_NODE = NodeType.DOCUMENT_NODE = 9; -//var DOCUMENT_TYPE_NODE = NodeType.DOCUMENT_TYPE_NODE = 10; -//var DOCUMENT_FRAGMENT_NODE = NodeType.DOCUMENT_FRAGMENT_NODE = 11; -//var NOTATION_NODE = NodeType.NOTATION_NODE = 12; diff --git a/node_modules/xmldom/test/parse-element.js b/node_modules/xmldom/test/parse-element.js deleted file mode 100644 index 53b839561..000000000 --- a/node_modules/xmldom/test/parse-element.js +++ /dev/null @@ -1,31 +0,0 @@ -var wows = require('vows'); -var assert = require('assert'); -var DOMParser = require('xmldom').DOMParser; -var XMLSerializer = require('xmldom').XMLSerializer; -var parser = new DOMParser(); -// Create a Test Suite -wows.describe('XML Node Parse').addBatch({ - 'noAttribute': function () { - var dom = new DOMParser().parseFromString('','text/xml'); - var dom = new DOMParser().parseFromString('','text/xml'); - var dom = new DOMParser().parseFromString('','text/xml'); - var dom = new DOMParser().parseFromString('','text/xml'); - var dom = new DOMParser().parseFromString('','text/xml'); - }, - 'simpleAttribute': function () { - var dom = new DOMParser().parseFromString('','text/xml'); - var dom = new DOMParser().parseFromString('','text/xml'); - var dom = new DOMParser().parseFromString('','text/xml'); - var dom = new DOMParser().parseFromString('','text/xml'); - var dom = new DOMParser().parseFromString('','text/xml'); - var dom = new DOMParser().parseFromString('','text/xml'); - var dom = new DOMParser().parseFromString('','text/xml'); - var dom = new DOMParser().parseFromString('','text/xml'); - }, - 'nsAttribute': function () { - var dom = new DOMParser().parseFromString('','text/xml'); - var dom = new DOMParser().parseFromString('','text/xml'); - var dom = new DOMParser().parseFromString('','text/xml'); - var dom = new DOMParser().parseFromString('','text/xml'); - } -}).run(); \ No newline at end of file diff --git a/node_modules/xmldom/test/simple.js b/node_modules/xmldom/test/simple.js deleted file mode 100644 index 4a398beab..000000000 --- a/node_modules/xmldom/test/simple.js +++ /dev/null @@ -1,11 +0,0 @@ -var wows = require('vows'); -var DOMParser = require('xmldom').DOMParser; - - -wows.describe('errorHandle').addBatch({ - 'simple': function() { - var parser = new DOMParser(); - var doc = parser.parseFromString('', 'text/html'); - console.log(doc+''); - } -}).run(); \ No newline at end of file diff --git a/node_modules/xmldom/test/test.js b/node_modules/xmldom/test/test.js deleted file mode 100644 index a8aa97f3d..000000000 --- a/node_modules/xmldom/test/test.js +++ /dev/null @@ -1,24 +0,0 @@ -var wows = require('vows'); -var assert = require('assert'); -var DOMParser = require('xmldom').DOMParser; -var XMLSerializer = require('xmldom').XMLSerializer; - - -var doc = new DOMParser().parseFromString('' + - '' + - '','text/xml'); - -var doc1 = doc; -var str1=new XMLSerializer().serializeToString(doc); -var doc2 = doc1.cloneNode(true); -var doc3 = doc1.cloneNode(true); -var doc4 = doc1.cloneNode(true); - -doc3.documentElement.appendChild(doc3.documentElement.lastChild); -//doc4.documentElement.appendChild(doc4.documentElement.firstChild); - -var str2=new XMLSerializer().serializeToString(doc2); -var str3=new XMLSerializer().serializeToString(doc3); -var str4=new XMLSerializer().serializeToString(doc4); -console.assert(str1 == str3,str3,str1); -//console.assert(str3 != str4 && str3.length == str4.length,str3); diff --git a/node_modules/xmldom/test/test.xml b/node_modules/xmldom/test/test.xml deleted file mode 100644 index bd2c504a7..000000000 --- a/node_modules/xmldom/test/test.xml +++ /dev/null @@ -1,19719 +0,0 @@ - - - - 8.0 - 05/21/12 12:18:42 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ]]> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ]]> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ]]> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ]]> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ]]> - - - - - ]]> - - - - - ]]> - - - - - ]]> - - - - - ]]> - - - - - ]]> - - - - - ]]> - - - - - ]]> - - - - - ]]> - - - - - ]]> - - - - - ]]> - - - - - ]]> - - - - - ]]> - - - - - ]]> - - - - - ]]> - - - - - ]]> - - - - - ]]> - - - - - ]]> - - - - - ]]> - - - - - ]]> - - - - - ]]> - - - - - ]]> - - - - - ]]> - - - - - ]]> - - - - - ]]> - - - - - ]]> - - - - - ]]> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
-
-
-
-
\ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 000000000..4bd91fe65 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2097 @@ +{ + "name": "node-webodf", + "version": "0.6.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "node-webodf", + "version": "0.6.0", + "license": "AGPL-3.0", + "dependencies": { + "@xmldom/xmldom": "^0.9.10" + }, + "devDependencies": { + "c8": "^10.1.3", + "jsdoc": "^4.0.5", + "jsdom": "^30.0.1", + "playwright-core": "^1.62.1", + "terser": "^5.50.0" + }, + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/@asamuzakjp/css-color": { + "version": "6.0.7", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-6.0.7.tgz", + "integrity": "sha512-vC/bk1Lz7Tn/EfU9/apOTBk80/8dyGyWMowPoV1tJ52muDGsDqt2HPT2klrFUiY60MQmQv9q8yIht15JnBgDGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@csstools/css-calc": "^3.3.0", + "@csstools/css-color-parser": "^4.1.10", + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0", + "lru-cache": "^11.5.2" + }, + "engines": { + "node": "^22.13.0 || >=24.0.0" + } + }, + "node_modules/@asamuzakjp/dom-selector": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-8.3.2.tgz", + "integrity": "sha512-93Z1N+BQNXysodoicpOIyNh2drHfz/CTf9nnT0FEx72GJcIiwgydD7tGAr78j41LsYn3hlRn+LdGPuBLn1Bl8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "bidi-js": "^1.0.3", + "css-tree": "^3.2.1", + "is-potential-custom-element-name": "^1.0.1", + "lru-cache": "^11.5.2" + }, + "engines": { + "node": "^22.13.0 || >=24.0.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.8.tgz", + "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.8" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@bramus/specificity": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz", + "integrity": "sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "css-tree": "^3.0.0" + }, + "bin": { + "specificity": "bin/cli.js" + } + }, + "node_modules/@csstools/color-helpers": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.1.1.tgz", + "integrity": "sha512-gLNsunvwf3mCi5u5o46/Z/JcJMnhbHSaZ69rkgPzNM3J4s8hWwpPUQB6/tt0EDFyCiWzxANlx+2LJwpYj4zS1w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/@csstools/css-calc": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.3.0.tgz", + "integrity": "sha512-c5ihYsPkdG6JCkU2zTMm4+k6r7RXuGxtWYhu5DHMIiF1FHzrfmHL5so11AoFpUv/tu61xfcmT4AmKoFfMPoqdQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.2.1.tgz", + "integrity": "sha512-YpAJZhaHplYQkG8ib+/Fx5Y0eF2lVWi3tIvMJA6i39TLyUNp2439cifzW8VMjhlqrBjHzK5hVGugRRm2zTKI/A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/color-helpers": "^6.1.1", + "@csstools/css-calc": "^3.3.0" + }, + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.0.tgz", + "integrity": "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-syntax-patches-for-csstree": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.9.tgz", + "integrity": "sha512-iGGw4OsAYsS6pD29MdJ2bX/nJx65a04ZZiw6x+VwWlP2DdXf6f++Zmuv/OzALpdyfVhjbduIIF2cXM7HWBIe9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "peerDependencies": { + "css-tree": "^3.2.1" + }, + "peerDependenciesMeta": { + "css-tree": { + "optional": true + } + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz", + "integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/@exodus/bytes": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.15.1.tgz", + "integrity": "sha512-S6mL0yNB/Abt9Ei4tq8gDhcczc4S3+vQ4ra7vxnAf+YHC02srtqxKKZghx2Dq6p0e66THKwR6r8N6P95wEty7Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@noble/hashes": "^1.8.0 || ^2.0.0" + }, + "peerDependenciesMeta": { + "@noble/hashes": { + "optional": true + } + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.6.tgz", + "integrity": "sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@jsdoc/salty": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@jsdoc/salty/-/salty-0.2.12.tgz", + "integrity": "sha512-TuB0x50EoAvEX/UEWITd8Mkn3WhiTjSvbTMCLj0BhsQEl5iUzjXdA0bETEVpTk+5TGTLR6QktI9H4hLviVeaAQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "lodash": "^4.18.1" + }, + "engines": { + "node": ">=v12.0.0" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/markdown-it": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.2.0.tgz", + "integrity": "sha512-NoQ2yGlLWj4wpxMs+TYmRKk3thDrQ97agr7sFqfLsAlvoS8SNQuTrlObhFqG9iugdTtgOE9jpJ6FNM4ZGsa5xQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/linkify-it": "^5", + "@types/mdurl": "^2" + } + }, + "node_modules/@types/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@xmldom/xmldom": { + "version": "0.9.12", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.9.12.tgz", + "integrity": "sha512-5AXjrcMClTryPe9LgZrygpB1lj7s0S9E0+W+AHaVKAVyHanafK86iPSvG5xHVSp/jC+VH1UXu0TAEmY279xH7A==", + "license": "MIT", + "engines": { + "node": ">=14.6" + } + }, + "node_modules/acorn": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz", + "integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ansi-regex": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.3.0.tgz", + "integrity": "sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/bidi-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", + "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "require-from-string": "^2.0.2" + } + }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", + "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/c8": { + "version": "10.1.3", + "resolved": "https://registry.npmjs.org/c8/-/c8-10.1.3.tgz", + "integrity": "sha512-LvcyrOAaOnrrlMpW22n690PUvxiq4Uf9WMhQwNJ9vgagkL/ph1+D4uvjvDA5XCbykrc0sx+ay6pVi9YZ1GnhyA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@bcoe/v8-coverage": "^1.0.1", + "@istanbuljs/schema": "^0.1.3", + "find-up": "^5.0.0", + "foreground-child": "^3.1.1", + "istanbul-lib-coverage": "^3.2.0", + "istanbul-lib-report": "^3.0.1", + "istanbul-reports": "^3.1.6", + "test-exclude": "^7.0.1", + "v8-to-istanbul": "^9.0.0", + "yargs": "^17.7.2", + "yargs-parser": "^21.1.1" + }, + "bin": { + "c8": "bin/c8.js" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "monocart-coverage-reports": "^2" + }, + "peerDependenciesMeta": { + "monocart-coverage-reports": { + "optional": true + } + } + }, + "node_modules/catharsis": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.9.0.tgz", + "integrity": "sha512-prMTQVpcns/tzFgFVkVp6ak6RykZyWb3gu8ckUpd6YkTlacOd3DXGJjIpD4Q6zJirizvaiAjSSHlOsA+6sNh2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash": "^4.17.15" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/cliui/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-tree": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", + "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdn-data": "2.27.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/data-urls": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz", + "integrity": "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/data-urls/node_modules/whatwg-url": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.1.tgz", + "integrity": "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.11.0", + "tr46": "^6.0.0", + "webidl-conversions": "^8.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "dev": true, + "license": "MIT" + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz", + "integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/html-encoding-sniffer": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz", + "integrity": "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.6.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/js2xmlparser": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.2.tgz", + "integrity": "sha512-6n4D8gLlLf1n5mNLQPRfViYzu9RATblzPEtm1SthMX1Pjao0r9YI9nw7ZIfRxQMERS87mcswrg+r/OYrPRX6jA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "xmlcreate": "^2.0.4" + } + }, + "node_modules/jsdoc": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-4.0.5.tgz", + "integrity": "sha512-P4C6MWP9yIlMiK8nwoZvxN84vb6MsnXcHuy7XzVOvQoCizWX5JFCBsWIIWKXBltpoRZXddUOVQmCTOZt9yDj9g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@babel/parser": "^7.20.15", + "@jsdoc/salty": "^0.2.1", + "@types/markdown-it": "^14.1.1", + "bluebird": "^3.7.2", + "catharsis": "^0.9.0", + "escape-string-regexp": "^2.0.0", + "js2xmlparser": "^4.0.2", + "klaw": "^3.0.0", + "markdown-it": "^14.1.0", + "markdown-it-anchor": "^8.6.7", + "marked": "^4.0.10", + "mkdirp": "^1.0.4", + "requizzle": "^0.2.3", + "strip-json-comments": "^3.1.0", + "underscore": "~1.13.2" + }, + "bin": { + "jsdoc": "jsdoc.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/jsdom": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-30.0.1.tgz", + "integrity": "sha512-52v7mUVUfNQVYYqE1lcdaymWL0njO7lTLUog6ZvW2U5KsbiLk/GnZlVJ+qx0xfNJZ6Gn+KSpPNE52vurbxZwrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/css-color": "^6.0.5", + "@asamuzakjp/dom-selector": "^8.3.0", + "@bramus/specificity": "^2.4.2", + "@csstools/css-syntax-patches-for-csstree": "^1.1.7", + "@exodus/bytes": "^1.15.1", + "css-tree": "^3.2.1", + "data-urls": "^7.0.0", + "decimal.js": "^10.6.0", + "html-encoding-sniffer": "^6.0.0", + "is-potential-custom-element-name": "^1.0.1", + "lru-cache": "^11.5.2", + "parse5": "^8.0.1", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^6.0.2", + "undici": "^8.9.0", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^8.0.1", + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^17.1.0", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + }, + "peerDependencies": { + "canvas": "^3.2.3" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/klaw": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/klaw/-/klaw-3.0.0.tgz", + "integrity": "sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.1.9" + } + }, + "node_modules/linkify-it": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.2.tgz", + "integrity": "sha512-ONTm2jCMAVZjgQa/Fy1kScXsuOoF5NPTsoFBdE1KVIZ2vAh/r9+Bqo+0jINCBYnavTPQZz38QzFTme79ENoN3Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/markdown-it" + } + ], + "license": "MIT", + "dependencies": { + "uc.micro": "^2.0.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "11.5.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", + "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/markdown-it": { + "version": "14.3.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.3.0.tgz", + "integrity": "sha512-RCEsPjR+sr0x+AuYp601tKTkgFG4YEPLCzHST3cQ/fhlJkqAkz1L2/Qbp1j9qw5SBwQHFBoW8+hoN5xssOF0Tw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/markdown-it" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1", + "entities": "^4.5.0", + "linkify-it": "^5.0.2", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" + }, + "bin": { + "markdown-it": "bin/markdown-it.mjs" + } + }, + "node_modules/markdown-it-anchor": { + "version": "8.6.7", + "resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-8.6.7.tgz", + "integrity": "sha512-FlCHFwNnutLgVTflOYHPW2pPcl2AACqVzExlkGQNsi4CJgqOHN7YTgDd4LuhgN1BFO3TS0vLAruV1Td6dwWPJA==", + "dev": true, + "license": "Unlicense", + "peerDependencies": { + "@types/markdown-it": "*", + "markdown-it": "*" + } + }, + "node_modules/marked": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", + "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", + "dev": true, + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/mdn-data": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", + "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/mdurl": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.1.0.tgz", + "integrity": "sha512-1+HBaOx0zi/dQWht8rNv9MYf9qqpqL/kxI0hXImU6Y547zM6Sni8BQibt7ifgMcYtQg41ao3Ivd6cnSM86inpg==", + "dev": true, + "license": "MIT" + }, + "node_modules/minimatch": { + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.8" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/parse5": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.1.tgz", + "integrity": "sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^8.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5/node_modules/entities": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-8.0.0.tgz", + "integrity": "sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/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==", + "dev": true, + "license": "ISC" + }, + "node_modules/playwright-core": { + "version": "1.62.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.62.1.tgz", + "integrity": "sha512-wPYSwEBJY9GHraISXqyqtx0na0LpO3XEX7jNDhntbex7tzUS7kLnZsOlFruFJB4Hi/rhDMjXGqHewDZ68nYZVw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/requizzle": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.4.tgz", + "integrity": "sha512-JRrFk1D4OQ4SqovXOgdav+K8EAhSB/LJZqCz8tbX0KObcdeM15Ss59ozWMBWmmINMagCwmqn4ZNryUGpBsl6Jw==", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash": "^4.17.21" + } + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/terser": { + "version": "5.51.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.51.0.tgz", + "integrity": "sha512-myiQ6aFnxDOjdiXdTlC8ngVccQD88uHXTx5RUQOSEnarDYgJMjDagwAQszcOusjvmf4YqWsxoD1+MY+oAJRmpw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.15.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/test-exclude": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.2.tgz", + "integrity": "sha512-u9E6A+ZDYdp7a4WnarkXPZOx8Ilz46+kby6p1yZ8zsGTz9gYa6FIS7lj2oezzNKmtdyyJNNmmXDppga5GB7kSw==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^10.4.1", + "minimatch": "^10.2.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/tldts": { + "version": "7.4.11", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.4.11.tgz", + "integrity": "sha512-aBiNayCfTQxuIJBm06M+xR14cYaYlDlSXZbgsnKzKNxDKUVq7KFwTjwBSsb7m9Y5xO8WfPnBc63WaYFMTGlvqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tldts-core": "^7.4.11" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "7.4.11", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.4.11.tgz", + "integrity": "sha512-CW3WN2rIIE/Of21mulhgnGOwoDyEFNygyIBOONSdyAuSATgMMUCpLeUlB+E8sAwA5xRV9hYPl+kyZ9citHCaKg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tough-cookie": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.2.tgz", + "integrity": "sha512-exgYmnmL/sJpR3upZfXG5PoatXQii55xAiXGXzY+sROLZ/Y+SLcp9PgJNI9Vz37HpQ74WvDcLT8eqm+kV3FzrA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^7.0.5" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tr46": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz", + "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", + "dev": true, + "license": "MIT" + }, + "node_modules/underscore": { + "version": "1.13.8", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.8.tgz", + "integrity": "sha512-DXtD3ZtEQzc7M8m4cXotyHR+FAS18C64asBYY5vqZexfYryNNnDc02W4hKg3rdQuqOYas1jkseX0+nZXjTXnvQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/undici": { + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-8.10.0.tgz", + "integrity": "sha512-HvltHd7avK13QIw/oLe4qoOLyoVSoafqJ2jYOrtMRBkbYT31eiBQ8O0ehRKZiEZCMEyLFQNIADpgCWC5fALvYQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=22.19.0" + } + }, + "node_modules/v8-to-istanbul": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/webidl-conversions": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz", + "integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=20" + } + }, + "node_modules/whatwg-mimetype": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz", + "integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/whatwg-url": { + "version": "17.1.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-17.1.0.tgz", + "integrity": "sha512-3GeworPmc2ZfEEHP7lEbUfBX/L75wdEsi0rLNhXcXxnoN5jyq0SL5gCy06SGW2cyTIZdTvWIDQNQoza++vKeaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.15.1", + "tr46": "^6.0.0", + "webidl-conversions": "^8.0.1" + }, + "engines": { + "node": "^22.14.0 || >=24.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "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==", + "dev": true, + "license": "Apache-2.0", + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/xmlcreate": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.4.tgz", + "integrity": "sha512-nquOebG4sngPmGPICTS5EnxqhKbCmz5Ox5hsszI2T6U5qdrJizBc+0ilYSEjTSzU0yZcmvppztXe/5Al5fUwdg==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "17.7.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", + "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/yargs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 000000000..b56ca78e1 --- /dev/null +++ b/package.json @@ -0,0 +1,72 @@ +{ + "name": "node-webodf", + "author": "KO GmbH ", + "description": "WebODF - JavaScript Document Engine https://webodf.org/", + "version": "0.6.0", + "contributors": [ + { + "name": "KO GmbH", + "site": "https://webodf.org/" + }, + { + "name": "Jeffrey van Norden", + "email": "jeffrey@grexx.net" + } + ], + "license": "AGPL-3.0", + "keywords": [ + "OpenDocument", + "ODF", + "PDF", + "Text", + "Document", + "Slides", + "Presentation", + "ODT", + "OTT", + "FODT", + "ODP", + "OTP", + "FODP", + "Viewer" + ], + "repository": { + "type": "git", + "url": "git+https://github.com/webodf/WebODF.git" + }, + "dependencies": { + "@xmldom/xmldom": "^0.9.10" + }, + "devDependencies": { + "c8": "^10.1.3", + "jsdoc": "^4.0.5", + "jsdom": "^30.0.1", + "playwright-core": "^1.62.1", + "terser": "^5.50.0" + }, + "scripts": { + "prepare": "node scripts/build.js", + "build": "node scripts/build.js", + "check": "node scripts/check.js", + "check:tests": "node scripts/check.js --tests", + "doc": "node scripts/doc.js", + "test": "node scripts/test.js", + "test:node": "node scripts/test.js", + "test:rhino": "node scripts/test-rhino.js", + "coverage": "node scripts/coverage.js", + "all": "npm run check && npm run check:tests && npm test && npm run build && npm run doc", + "test:browser": "node scripts/test-browser.js", + "test:extension": "node scripts/test-extension.js" + }, + "files": [ + "dist", + "webodf", + "scripts", + "README.md", + "README-Building.md" + ], + "main": "dist/webodf.js", + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } +} diff --git a/programs/CMakeLists.txt b/programs/CMakeLists.txt index 79ecd7744..61e95eb94 100644 --- a/programs/CMakeLists.txt +++ b/programs/CMakeLists.txt @@ -1,13 +1,19 @@ -add_subdirectory(touchui) - if(BUILD_QTJSRUNTIME) add_subdirectory(qtjsruntime) endif(BUILD_QTJSRUNTIME) add_subdirectory(docnosis) -add_subdirectory(firefoxextension) +add_subdirectory(opendocumentviewer-webext) + +if(BUILD_DESKTOP) + add_subdirectory(opendocumentviewer-desktop) +endif(BUILD_DESKTOP) add_subdirectory(benchmark) add_subdirectory(editor) add_subdirectory(cordova) + +add_subdirectory(opendocumentviewer-android) + +add_subdirectory(opendocumentviewer-ios) diff --git a/programs/benchmark/CMakeLists.txt b/programs/benchmark/CMakeLists.txt index b87976aa9..8f19d64df 100644 --- a/programs/benchmark/CMakeLists.txt +++ b/programs/benchmark/CMakeLists.txt @@ -9,30 +9,31 @@ COPY_FILES(BENCHMARK_HTML ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR FILE(GLOB BENCHMARK_JS "${CMAKE_CURRENT_SOURCE_DIR}/js/*.js") -FOREACH(JSFILE ${BENCHMARK_JS}) - set(BENCHMARK_CLOSURE_ARGS ${BENCHMARK_CLOSURE_ARGS} --js ${JSFILE}) -ENDFOREACH(JSFILE) - +# The modules of the benchmark are written for AMD, which the closure compiler +# carried until it dropped it. They are gathered by a script of node instead, +# with the few lines that resolve them: a benchmark measures the library, not +# itself, so nothing here is compiled. add_custom_command( OUTPUT benchmark.js-target # from webodf.js target (DEPENDS webodf.js) COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/webodf/webodf.js ${CMAKE_CURRENT_BINARY_DIR} - COMMAND ${Java_JAVA_EXECUTABLE} - ARGS -jar ${CLOSURE_JAR} - ${BENCHMARK_CLOSURE_ARGS} - --transform_amd_modules - --process_common_js_modules - --common_js_entry_module HTMLBenchmark.js - --common_js_module_path_prefix ${CMAKE_CURRENT_SOURCE_DIR}/js/ - --compilation_level SIMPLE_OPTIMIZATIONS - --formatting PRETTY_PRINT - --js_output_file "benchmark.js-" + COMMAND ${NODE} ARGS ${CMAKE_CURRENT_SOURCE_DIR}/libexec/bundle.js + ${CMAKE_CURRENT_SOURCE_DIR}/js + ${CMAKE_CURRENT_BINARY_DIR}/benchmark.js + + DEPENDS ${NODE} webodf.js-target ${BENCHMARK_JS} + ${CMAKE_CURRENT_SOURCE_DIR}/libexec/bundle.js +) - COMMAND ${CMAKE_COMMAND} ARGS -E rename "benchmark.js-" "benchmark.js" +# The benchmark leaves out the actions that take a minute or more on the large +# documents, see "README-Building.md". The whole of it is another target, and +# another page, that asks for them. +add_custom_target(benchmark-html DEPENDS benchmark.js-target ${BENCHMARK_HTML}) - DEPENDS ClosureCompiler webodf.js-target +COPY_FILES(BENCHMARK_ALL_HTML ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} + all.html ) -add_custom_target(html-benchmark DEPENDS benchmark.js-target ${BENCHMARK_HTML}) +add_custom_target(benchmark-html-all DEPENDS benchmark-html ${BENCHMARK_ALL_HTML}) diff --git a/programs/benchmark/all.html b/programs/benchmark/all.html new file mode 100644 index 000000000..7d2f0bf95 --- /dev/null +++ b/programs/benchmark/all.html @@ -0,0 +1,18 @@ + + + + WebODF performance and benchmarking tool + + + + + + +

Running the whole of the benchmark…

+

index.html?fileUrl=1page.odt,10pages.odt,100pages.odt,1000pages.odt&layout=matrix

+ + diff --git a/programs/benchmark/index.html b/programs/benchmark/index.html index 456cdef4a..3452f3745 100644 --- a/programs/benchmark/index.html +++ b/programs/benchmark/index.html @@ -30,7 +30,7 @@

Benchmark [unknown]

diff --git a/programs/benchmark/js/AlignCurrentSelectionJustified.js b/programs/benchmark/js/AlignCurrentSelectionJustified.js index 9491b690e..3d3f672f3 100644 --- a/programs/benchmark/js/AlignCurrentSelectionJustified.js +++ b/programs/benchmark/js/AlignCurrentSelectionJustified.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global define*/ diff --git a/programs/benchmark/js/Benchmark.js b/programs/benchmark/js/Benchmark.js index 2f2e7c3fd..47421f54a 100644 --- a/programs/benchmark/js/Benchmark.js +++ b/programs/benchmark/js/Benchmark.js @@ -18,17 +18,17 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ -/*global define, runtime, core, console, odf */ +/*global define, runtime, webodfcore, console, odf */ define(["OdfBenchmarkContext"], function (OdfBenchmarkContext) { "use strict"; - runtime.loadClass("core.EventNotifier"); - runtime.loadClass("core.Task"); + runtime.loadClass("webodfcore.EventNotifier"); + runtime.loadClass("webodfcore.Task"); runtime.loadClass("odf.OdfCanvas"); /** @@ -38,7 +38,7 @@ define(["OdfBenchmarkContext"], function (OdfBenchmarkContext) { function Benchmark(canvasElement) { var self = this, context = new OdfBenchmarkContext(), - events = new core.EventNotifier(["start", "complete"]), + events = new webodfcore.EventNotifier(["start", "complete"]), currentActionIndex, lastProfileAction; @@ -69,7 +69,7 @@ define(["OdfBenchmarkContext"], function (OdfBenchmarkContext) { }); events.emit("start", {}); - core.Task.SUPPRESS_MANUAL_PROCESSING = true; + webodfcore.Task.SUPPRESS_MANUAL_PROCESSING = true; context.odfCanvas = new odf.OdfCanvas(canvasElement); currentActionIndex = -1; executeNextAction(); diff --git a/programs/benchmark/js/BenchmarkAction.js b/programs/benchmark/js/BenchmarkAction.js index ea7727378..1dfd97409 100644 --- a/programs/benchmark/js/BenchmarkAction.js +++ b/programs/benchmark/js/BenchmarkAction.js @@ -18,23 +18,23 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ -/*global define, runtime, core*/ +/*global define, runtime, webodfcore*/ define(function () { "use strict"; - runtime.loadClass("core.EventNotifier"); + runtime.loadClass("webodfcore.EventNotifier"); /** * @constructor */ function BenchmarkAction(state) { var startedAt, - events = new core.EventNotifier(["start", "complete"]); + events = new webodfcore.EventNotifier(["start", "complete"]); this.subscribe = events.subscribe; diff --git a/programs/benchmark/js/BoldCurrentSelection.js b/programs/benchmark/js/BoldCurrentSelection.js index 554c4bef2..6d11cacd7 100644 --- a/programs/benchmark/js/BoldCurrentSelection.js +++ b/programs/benchmark/js/BoldCurrentSelection.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global define, runtime, gui, ops*/ diff --git a/programs/benchmark/js/EnterEditMode.js b/programs/benchmark/js/EnterEditMode.js index 1ca178001..29c47ee23 100644 --- a/programs/benchmark/js/EnterEditMode.js +++ b/programs/benchmark/js/EnterEditMode.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global define, runtime, gui, ops*/ diff --git a/programs/benchmark/js/HTMLActionResult.js b/programs/benchmark/js/HTMLActionResult.js index c04c6f2c0..dca4240e6 100644 --- a/programs/benchmark/js/HTMLActionResult.js +++ b/programs/benchmark/js/HTMLActionResult.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global define, document*/ diff --git a/programs/benchmark/js/HTMLBenchmark.js b/programs/benchmark/js/HTMLBenchmark.js index b92373337..2c95adaf3 100644 --- a/programs/benchmark/js/HTMLBenchmark.js +++ b/programs/benchmark/js/HTMLBenchmark.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global define, document, window, webodf*/ @@ -27,6 +27,7 @@ define([ "Benchmark", "HTMLResultsRenderer", + "HTMLMatrixRenderer", "OpenDocument", "EnterEditMode", "MoveCursorToEndDirect", @@ -41,7 +42,7 @@ define([ "MoveCursorToEnd", "MoveCursorToStart", "SaveDocument" -], function (Benchmark, HTMLResultsRenderer, +], function (Benchmark, HTMLResultsRenderer, HTMLMatrixRenderer, OpenDocument, EnterEditMode, MoveCursorToEndDirect,InsertLetterA, RemovePositions, MoveCursorLeft, SelectEntireDocument, RemoveCurrentSelection, PreloadDocument, BoldCurrentSelection, AlignCurrentSelectionJustified, MoveCursorToEnd, MoveCursorToStart, SaveDocument) { @@ -73,17 +74,33 @@ define([ /** * Extract supported benchmark options from the url query parameters - * @return {!{fileUrl: !string, includeSlow: !boolean, colour: (string|undefined)}} + * @return {!{fileUrls: !Array., includeSlow: !boolean, + * colour: (string|undefined), matrix: !boolean}} */ function getConfiguration() { var params = getQueryParams(); return { - /** Test document to load. Relative or absolute urls are supported */ - fileUrl: params.fileUrl || "100pages.odt", - /** Include known slow actions in the benchmark. These can take 10 or more minutes each on large docs */ - includeSlow: params.includeSlow === "false" ? false : true, + /** + * Test documents to load, one after the other, separated by + * commas. Relative or absolute urls are supported. One document + * is measured unless others are named, as the four of them take + * minutes: "all.html" names them, see "README-Building.md". + */ + fileUrls: (params.fileUrl || "100pages.odt").split(","), + /** + * Include the known slow actions in the benchmark. They take a + * minute on a hundred pages and far more on a thousand, as one + * operation is made for each paragraph of the selection, and they + * are left out by "includeSlow=false" when the wait is too long. + */ + includeSlow: params.includeSlow !== "false", /** Background colour of the benchmark results. Useful for distinguishing different benchmark versions */ - colour: params.colour + colour: params.colour, + /** + * One table of an action to a line and a document to a column, + * rather than the actions of a document one under the other. + */ + matrix: params.layout === "matrix" }; } @@ -95,40 +112,95 @@ define([ var loadingScreenElement = document.getElementById('loadingScreen'), canvasElement = document.getElementById("canvas"), benchmarkResultsElement = document.getElementById("benchmarkResults").getElementsByTagName("tbody")[0], + benchmarkHeadElement = document.getElementById("benchmarkResults").getElementsByTagName("thead")[0], versionElement = document.getElementById("version"), config = getConfiguration(), - benchmark = new Benchmark(canvasElement), - renderer = new HTMLResultsRenderer(benchmark, benchmarkResultsElement); + documents = config.fileUrls.slice(), + matrix = null, + column = -1, + self = this; versionElement.textContent = webodf.Version; - renderer.setBackgroundColour(config.colour); - loadingScreenElement.style.display = "none"; - benchmark.actions.push(new PreloadDocument(config.fileUrl)); - benchmark.actions.push(new OpenDocument(config.fileUrl)); - benchmark.actions.push(new EnterEditMode()); - benchmark.actions.push(new MoveCursorToEnd()); - benchmark.actions.push(new MoveCursorToStart()); - benchmark.actions.push(new InsertLetterA(100)); - benchmark.actions.push(new RemovePositions(100, true)); - benchmark.actions.push(new MoveCursorToEndDirect()); - benchmark.actions.push(new InsertLetterA(1)); - benchmark.actions.push(new InsertLetterA(100)); - benchmark.actions.push(new RemovePositions(1, true)); - benchmark.actions.push(new MoveCursorLeft(1)); - benchmark.actions.push(new MoveCursorLeft(100)); - benchmark.actions.push(new RemovePositions(1, false)); - benchmark.actions.push(new RemovePositions(100, true)); - benchmark.actions.push(new SelectEntireDocument()); - benchmark.actions.push(new BoldCurrentSelection()); - benchmark.actions.push(new AlignCurrentSelectionJustified()); - benchmark.actions.push(new SaveDocument()); - if (config.includeSlow) { - benchmark.actions.push(new RemoveCurrentSelection()); + /** + * A line that says which document the rows under it belong to. + * @param {!string} fileUrl + * @return {undefined} + */ + function writeHeading(fileUrl) { + var row = document.createElement("tr"), + cell = document.createElement("th"); + cell.colSpan = 5; + cell.textContent = fileUrl; + cell.style.textAlign = "left"; + cell.style.paddingTop = "1em"; + row.appendChild(cell); + benchmarkResultsElement.appendChild(row); + } + + /** + * @param {!string} fileUrl + * @param {!function():undefined} callback + * @return {undefined} + */ + function runOne(fileUrl, callback) { + var benchmark = new Benchmark(canvasElement), + renderer; + + if (config.matrix) { + column += 1; + } else { + renderer = new HTMLResultsRenderer(benchmark, benchmarkResultsElement); + renderer.setBackgroundColour(config.colour); + writeHeading(fileUrl); + } + + benchmark.actions.push(new PreloadDocument(fileUrl)); + benchmark.actions.push(new OpenDocument(fileUrl)); + benchmark.actions.push(new EnterEditMode()); + benchmark.actions.push(new MoveCursorToEnd()); + benchmark.actions.push(new MoveCursorToStart()); + benchmark.actions.push(new InsertLetterA(100)); + benchmark.actions.push(new RemovePositions(100, true)); + benchmark.actions.push(new MoveCursorToEndDirect()); + benchmark.actions.push(new InsertLetterA(1)); + benchmark.actions.push(new InsertLetterA(100)); + benchmark.actions.push(new RemovePositions(1, true)); + benchmark.actions.push(new MoveCursorLeft(1)); + benchmark.actions.push(new MoveCursorLeft(100)); + benchmark.actions.push(new RemovePositions(1, false)); + benchmark.actions.push(new RemovePositions(100, true)); + benchmark.actions.push(new SelectEntireDocument()); + benchmark.actions.push(new BoldCurrentSelection()); + benchmark.actions.push(new AlignCurrentSelectionJustified()); + benchmark.actions.push(new SaveDocument()); + if (config.includeSlow) { + benchmark.actions.push(new RemoveCurrentSelection()); + } + + if (config.matrix) { + matrix.follow(benchmark, column); + } + benchmark.subscribe("complete", callback); + benchmark.start(); + } + + // The documents are measured one after the other, in one table: a run + // is read as a whole, and two runs are compared line by line. + function runNext() { + var fileUrl = documents.shift(); + if (fileUrl) { + runOne(fileUrl, runNext); + } + } + + if (config.matrix) { + matrix = new HTMLMatrixRenderer(benchmarkResultsElement, + benchmarkHeadElement, config.fileUrls); } - this.start = benchmark.start; + self.start = runNext; } return HTMLBenchmark; diff --git a/programs/benchmark/js/HTMLMatrixRenderer.js b/programs/benchmark/js/HTMLMatrixRenderer.js new file mode 100644 index 000000000..eaf0bd7ca --- /dev/null +++ b/programs/benchmark/js/HTMLMatrixRenderer.js @@ -0,0 +1,106 @@ +/** + * Copyright (C) 2026 Daniel Berthereau + * + * @licstart + * This file is part of WebODF. + * + * WebODF is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License (GNU AGPL) + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * WebODF is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with WebODF. If not, see . + * @licend + * + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ + */ + +/*global define, document*/ + +define(function () { + "use strict"; + + /** + * The four documents in one table: an action to a line, a document to a + * column, and the time it took in the cell. A run is then read across, from + * a page to a thousand, which is what a benchmark is for. + * @constructor + * @param {!HTMLTableElement} outputTable + * @param {!HTMLElement} outputHead + * @param {!Array.} fileUrls + */ + function HTMLMatrixRenderer(outputTable, outputHead, fileUrls) { + var rows = {}; + + function writeHead() { + var row = document.createElement("tr"), + cell = document.createElement("td"); + cell.className = "action"; + cell.textContent = "Action"; + row.appendChild(cell); + fileUrls.forEach(function (fileUrl) { + var column = document.createElement("td"); + column.className = "elapsed"; + column.textContent = fileUrl.replace(/\.odt$/, ""); + row.appendChild(column); + }); + outputHead.textContent = ""; + outputHead.appendChild(row); + } + + /** + * The line of an action is made when the first document reaches it, and + * the ones after it fill their own cell of that same line. + * @param {!number} index + * @param {!string} description + * @return {!HTMLTableRowElement} + */ + function rowOf(index, description) { + var row, cell, i; + if (!rows.hasOwnProperty(index)) { + row = document.createElement("tr"); + cell = document.createElement("td"); + // The name of the document is in the head of its column, so + // it is taken out of the line, that holds for all of them. + cell.textContent = (index + 1) + ". " + + description.replace(/\s+\S+\.odt$/, ""); + row.appendChild(cell); + for (i = 0; i < fileUrls.length; i += 1) { + row.appendChild(document.createElement("td")); + } + outputTable.appendChild(row); + rows[index] = row; + } + return rows[index]; + } + + /** + * @param {!Benchmark} benchmark + * @param {!number} column + * @return {undefined} + */ + this.follow = function (benchmark, column) { + benchmark.actions.forEach(function (action, index) { + var row = rowOf(index, action.state.description), + cell = row.cells[column + 1]; + cell.textContent = "…"; + action.subscribe("complete", function (state) { + cell.textContent = state.status + ? state.elapsedTime + : "failed"; + }); + }); + }; + + writeHead(); + } + + return HTMLMatrixRenderer; +}); diff --git a/programs/benchmark/js/HTMLResultsRenderer.js b/programs/benchmark/js/HTMLResultsRenderer.js index 8490d5df4..26c19d919 100644 --- a/programs/benchmark/js/HTMLResultsRenderer.js +++ b/programs/benchmark/js/HTMLResultsRenderer.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global define, document*/ diff --git a/programs/benchmark/js/InsertLetterA.js b/programs/benchmark/js/InsertLetterA.js index 0413560f1..e9b64ab25 100644 --- a/programs/benchmark/js/InsertLetterA.js +++ b/programs/benchmark/js/InsertLetterA.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global define*/ diff --git a/programs/benchmark/js/MoveCursorLeft.js b/programs/benchmark/js/MoveCursorLeft.js index 6b7d6f8da..51654c69e 100644 --- a/programs/benchmark/js/MoveCursorLeft.js +++ b/programs/benchmark/js/MoveCursorLeft.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global define*/ diff --git a/programs/benchmark/js/MoveCursorToEnd.js b/programs/benchmark/js/MoveCursorToEnd.js index 982c3db76..010cb7528 100644 --- a/programs/benchmark/js/MoveCursorToEnd.js +++ b/programs/benchmark/js/MoveCursorToEnd.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global define*/ diff --git a/programs/benchmark/js/MoveCursorToEndDirect.js b/programs/benchmark/js/MoveCursorToEndDirect.js index a0207797c..e79ca254b 100644 --- a/programs/benchmark/js/MoveCursorToEndDirect.js +++ b/programs/benchmark/js/MoveCursorToEndDirect.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global define*/ diff --git a/programs/benchmark/js/MoveCursorToStart.js b/programs/benchmark/js/MoveCursorToStart.js index d8b813f87..34471ff7d 100644 --- a/programs/benchmark/js/MoveCursorToStart.js +++ b/programs/benchmark/js/MoveCursorToStart.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global define*/ diff --git a/programs/benchmark/js/OdfBenchmarkContext.js b/programs/benchmark/js/OdfBenchmarkContext.js index 4e6a61722..27fcaf44f 100644 --- a/programs/benchmark/js/OdfBenchmarkContext.js +++ b/programs/benchmark/js/OdfBenchmarkContext.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global define, runtime*/ diff --git a/programs/benchmark/js/OpenDocument.js b/programs/benchmark/js/OpenDocument.js index eced8336f..9f90ebfea 100644 --- a/programs/benchmark/js/OpenDocument.js +++ b/programs/benchmark/js/OpenDocument.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global define*/ diff --git a/programs/benchmark/js/PreloadDocument.js b/programs/benchmark/js/PreloadDocument.js index 6ebdc247f..195eae39d 100644 --- a/programs/benchmark/js/PreloadDocument.js +++ b/programs/benchmark/js/PreloadDocument.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global define, runtime*/ diff --git a/programs/benchmark/js/RangeCalculator.js b/programs/benchmark/js/RangeCalculator.js index 669f63b69..749120417 100644 --- a/programs/benchmark/js/RangeCalculator.js +++ b/programs/benchmark/js/RangeCalculator.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global define, runtime, odf, document, Node*/ diff --git a/programs/benchmark/js/RemoveCurrentSelection.js b/programs/benchmark/js/RemoveCurrentSelection.js index e014d3fda..2cd4e86a9 100644 --- a/programs/benchmark/js/RemoveCurrentSelection.js +++ b/programs/benchmark/js/RemoveCurrentSelection.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global define*/ diff --git a/programs/benchmark/js/RemovePositions.js b/programs/benchmark/js/RemovePositions.js index 26598a48c..9c75ff1ec 100644 --- a/programs/benchmark/js/RemovePositions.js +++ b/programs/benchmark/js/RemovePositions.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global define*/ diff --git a/programs/benchmark/js/SaveDocument.js b/programs/benchmark/js/SaveDocument.js index e1282cf02..406532521 100644 --- a/programs/benchmark/js/SaveDocument.js +++ b/programs/benchmark/js/SaveDocument.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global define*/ diff --git a/programs/benchmark/js/SelectEntireDocument.js b/programs/benchmark/js/SelectEntireDocument.js index a8013b516..ae909de02 100644 --- a/programs/benchmark/js/SelectEntireDocument.js +++ b/programs/benchmark/js/SelectEntireDocument.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global define*/ diff --git a/programs/benchmark/libexec/bundle.js b/programs/benchmark/libexec/bundle.js new file mode 100644 index 000000000..2c17566eb --- /dev/null +++ b/programs/benchmark/libexec/bundle.js @@ -0,0 +1,92 @@ +/** + * Copyright (C) 2026 Daniel Berthereau + * + * @licstart + * This file is part of WebODF. + * + * WebODF is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License (GNU AGPL) + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * WebODF is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with WebODF. If not, see . + * @licend + * + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ + */ + +/*global require, process, console*/ + +// The modules of the benchmark are written for AMD, which the closure compiler +// carried until it dropped it: the option "--transform_amd_modules" is not one +// it knows any more. They are gathered here instead, each under the name of its +// file, with the few lines that resolve them at the end. Nothing is compiled: a +// benchmark measures the library, not itself. +// +// Usage: node libexec/bundle.js js benchmark.js +var fs = require("fs"), + path = require("path"); + +/** + * @param {!string} dir + * @param {!string} output + * @return {undefined} + */ +function bundle(dir, output) { + "use strict"; + var names = fs.readdirSync(dir).filter(function (name) { + return name.slice(-3) === ".js"; + }).sort(), + parts = [`(function () { + "use strict"; + var factories = {}, modules = {}; + function define(name, deps, factory) { + if (factory === undefined) { + factory = deps; + deps = []; + } + factories[name] = {deps: deps, factory: factory}; + } + function need(name) { + var module = factories[name]; + if (!modules.hasOwnProperty(name)) { + modules[name] = module.factory.apply(null, module.deps.map(need)); + } + return modules[name]; + } +`] + + names.forEach(function (name) { + var module = name.slice(0, -3), + source = fs.readFileSync(path.join(dir, name), "utf8"), + at = source.indexOf("define("); + if (at === -1) { + return; + } + // The call of the module is named after its file, as a loader of AMD + // names it after the address it read it from. + parts.push(`${source.slice(0, at)}define(${JSON.stringify(module)}, ` + + source.slice(at + "define(".length)); + }); + + // The page starts the benchmark itself, so the module of the entry is + // handed to it. It used to be named "module$HTMLBenchmark", the name the + // closure compiler gave a module of AMD. + parts.push(` window.HTMLBenchmark = need("HTMLBenchmark"); +}()); +`); + fs.writeFileSync(output, parts.join("")); +} + +if (process.argv.length !== 4) { + console.log("Usage: node libexec/bundle.js "); + process.exit(1); +} +bundle(process.argv[2], process.argv[3]); diff --git a/programs/benchmark/runner.css b/programs/benchmark/runner.css index aba3b0f93..1f845aa46 100644 --- a/programs/benchmark/runner.css +++ b/programs/benchmark/runner.css @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ html, body { diff --git a/programs/benchmark/webodf.js b/programs/benchmark/webodf.js index 2517ac451..adfc914cc 100644 --- a/programs/benchmark/webodf.js +++ b/programs/benchmark/webodf.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global XMLHttpRequest, runtime*/ diff --git a/programs/cordova/CMakeLists.txt b/programs/cordova/CMakeLists.txt index 2e8e3cfce..da7b1afb4 100644 --- a/programs/cordova/CMakeLists.txt +++ b/programs/cordova/CMakeLists.txt @@ -1,4 +1,18 @@ +# Two products are packed here, and neither builds any more, see +# "README-Products.md". They are kept until they are either brought up to date +# or dropped: cordova only wraps "webodf.js" in a web view, so what has to be +# written again is the packaging, not anything of WebODF. +# +# product-opendocumentviewer-firefoxos packs the viewer for Firefox OS, that Mozilla +# abandoned in 2016, but that lives on through its forks, B2G OS, KaiOS and +# Capyloon. It does not build any more: cordova 3.5, of 2014, is asked for +# below. +# +# The apk of android was packed here as well. It is built by programs/opendocumentviewer-android +# now, without cordova, so the platform of android is not added here any more +# and the files it needed are gone. + configure_file(config.xml.in config.xml) configure_file(manifest.webapp.in manifest.webapp) @@ -28,13 +42,6 @@ add_custom_command( DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/WebODF/hooks/README.md WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/WebODF ) -# add the android platforms to the WebODF cordova folder -add_custom_command( - OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/WebODF/plugins/android.json - COMMAND ${CORDOVA} platform add android - DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/WebODF/plugins/org.apache.cordova.file/plugin.xml - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/WebODF -) # add the firefoxos platforms to the WebODF cordova folder add_custom_command( OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/WebODF/plugins/firefoxos.json @@ -57,24 +64,6 @@ add_custom_command( WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/WebODF ) -if(BUILD_APK) - # build the android apk - add_custom_command( - OUTPUT WebODF/platforms/android/ant-build/WebODF-debug.apk - COMMAND ${CMAKE_COMMAND} ARGS -E copy_if_different - ${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/AndroidManifest.xml - ${CMAKE_CURRENT_BINARY_DIR}/WebODF/platforms/android/AndroidManifest.xml - COMMAND ${CMAKE_COMMAND} ARGS -E remove_directory ${CMAKE_CURRENT_BINARY_DIR}/WebODF/platforms/android/res/ - COMMAND ${CMAKE_COMMAND} ARGS -E copy_directory ${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/res/ ${CMAKE_CURRENT_BINARY_DIR}/WebODF/platforms/android/res/ - COMMAND ${CMAKE_COMMAND} ARGS -E remove_directory ${CMAKE_CURRENT_BINARY_DIR}/WebODF/platforms/android/src/org/webodf/ - COMMAND ${CMAKE_COMMAND} ARGS -E copy_directory ${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/src/org/webodf/ ${CMAKE_CURRENT_BINARY_DIR}/WebODF/platforms/android/src/org/webodf/ - COMMAND ${CORDOVA} build android - DEPENDS WebODF/merges/config.xml - ${CMAKE_CURRENT_BINARY_DIR}/WebODF/plugins/android.json - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/WebODF - ) - add_custom_target(product-androidviewer DEPENDS WebODF/platforms/android/ant-build/WebODF-debug.apk) -endif() # build the firefoxos package add_custom_command( @@ -89,4 +78,4 @@ add_custom_command( WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/WebODF ) -add_custom_target(product-firefoxosviewer DEPENDS WebODF/platforms/firefoxos/build/package.zip) +add_custom_target(product-opendocumentviewer-firefoxos DEPENDS WebODF/platforms/firefoxos/build/package.zip) diff --git a/programs/cordova/config.xml.in b/programs/cordova/config.xml.in index 39af309b8..723c15a5f 100644 --- a/programs/cordova/config.xml.in +++ b/programs/cordova/config.xml.in @@ -4,8 +4,8 @@ A viewer for OpenDocument Format files. - - Jos van den Oever + + Daniel Berthereau diff --git a/programs/cordova/platforms/android/AndroidManifest.xml b/programs/cordova/platforms/android/AndroidManifest.xml deleted file mode 100644 index 10b6b4f48..000000000 --- a/programs/cordova/platforms/android/AndroidManifest.xml +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/programs/cordova/platforms/android/res/drawable-hdpi/icon.png b/programs/cordova/platforms/android/res/drawable-hdpi/icon.png deleted file mode 100644 index 72dddb396..000000000 Binary files a/programs/cordova/platforms/android/res/drawable-hdpi/icon.png and /dev/null differ diff --git a/programs/cordova/platforms/android/res/drawable-land-hdpi/screen.png b/programs/cordova/platforms/android/res/drawable-land-hdpi/screen.png deleted file mode 100644 index e7b7d82b1..000000000 Binary files a/programs/cordova/platforms/android/res/drawable-land-hdpi/screen.png and /dev/null differ diff --git a/programs/cordova/platforms/android/res/drawable-land-ldpi/screen.png b/programs/cordova/platforms/android/res/drawable-land-ldpi/screen.png deleted file mode 100644 index 45c0f69f2..000000000 Binary files a/programs/cordova/platforms/android/res/drawable-land-ldpi/screen.png and /dev/null differ diff --git a/programs/cordova/platforms/android/res/drawable-land-mdpi/screen.png b/programs/cordova/platforms/android/res/drawable-land-mdpi/screen.png deleted file mode 100644 index 3e24b9b41..000000000 Binary files a/programs/cordova/platforms/android/res/drawable-land-mdpi/screen.png and /dev/null differ diff --git a/programs/cordova/platforms/android/res/drawable-land-xhdpi/screen.png b/programs/cordova/platforms/android/res/drawable-land-xhdpi/screen.png deleted file mode 100644 index c71baaeda..000000000 Binary files a/programs/cordova/platforms/android/res/drawable-land-xhdpi/screen.png and /dev/null differ diff --git a/programs/cordova/platforms/android/res/drawable-ldpi/icon.png b/programs/cordova/platforms/android/res/drawable-ldpi/icon.png deleted file mode 100644 index 705ba70d3..000000000 Binary files a/programs/cordova/platforms/android/res/drawable-ldpi/icon.png and /dev/null differ diff --git a/programs/cordova/platforms/android/res/drawable-mdpi/icon.png b/programs/cordova/platforms/android/res/drawable-mdpi/icon.png deleted file mode 100644 index 3fec4c6d8..000000000 Binary files a/programs/cordova/platforms/android/res/drawable-mdpi/icon.png and /dev/null differ diff --git a/programs/cordova/platforms/android/res/drawable-port-hdpi/screen.png b/programs/cordova/platforms/android/res/drawable-port-hdpi/screen.png deleted file mode 100644 index 0ee23834e..000000000 Binary files a/programs/cordova/platforms/android/res/drawable-port-hdpi/screen.png and /dev/null differ diff --git a/programs/cordova/platforms/android/res/drawable-port-ldpi/screen.png b/programs/cordova/platforms/android/res/drawable-port-ldpi/screen.png deleted file mode 100644 index 4458802ee..000000000 Binary files a/programs/cordova/platforms/android/res/drawable-port-ldpi/screen.png and /dev/null differ diff --git a/programs/cordova/platforms/android/res/drawable-port-mdpi/screen.png b/programs/cordova/platforms/android/res/drawable-port-mdpi/screen.png deleted file mode 100644 index 1c74ae096..000000000 Binary files a/programs/cordova/platforms/android/res/drawable-port-mdpi/screen.png and /dev/null differ diff --git a/programs/cordova/platforms/android/res/drawable-port-xhdpi/screen.png b/programs/cordova/platforms/android/res/drawable-port-xhdpi/screen.png deleted file mode 100644 index 720646fe3..000000000 Binary files a/programs/cordova/platforms/android/res/drawable-port-xhdpi/screen.png and /dev/null differ diff --git a/programs/cordova/platforms/android/res/drawable-xhdpi/icon.png b/programs/cordova/platforms/android/res/drawable-xhdpi/icon.png deleted file mode 100644 index c570a3c4d..000000000 Binary files a/programs/cordova/platforms/android/res/drawable-xhdpi/icon.png and /dev/null differ diff --git a/programs/cordova/platforms/android/res/drawable/icon.png b/programs/cordova/platforms/android/res/drawable/icon.png deleted file mode 100644 index c570a3c4d..000000000 Binary files a/programs/cordova/platforms/android/res/drawable/icon.png and /dev/null differ diff --git a/programs/cordova/platforms/android/res/menu/main.xml b/programs/cordova/platforms/android/res/menu/main.xml deleted file mode 100644 index fe033b85a..000000000 --- a/programs/cordova/platforms/android/res/menu/main.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/programs/cordova/platforms/android/res/values/strings.xml b/programs/cordova/platforms/android/res/values/strings.xml deleted file mode 100644 index b27d0e252..000000000 --- a/programs/cordova/platforms/android/res/values/strings.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - WebODF - Report issue - Visit website - About - diff --git a/programs/cordova/platforms/android/res/xml/config.xml b/programs/cordova/platforms/android/res/xml/config.xml deleted file mode 100644 index a073b88ef..000000000 --- a/programs/cordova/platforms/android/res/xml/config.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - - - - WebODF - - A viewer for OpenDocument Format files. - - - Jos van den Oever - - - - diff --git a/programs/cordova/platforms/android/src/org/webodf/Base64OutputStream.java b/programs/cordova/platforms/android/src/org/webodf/Base64OutputStream.java deleted file mode 100644 index 73a16550f..000000000 --- a/programs/cordova/platforms/android/src/org/webodf/Base64OutputStream.java +++ /dev/null @@ -1,169 +0,0 @@ -/* - * - * $Id: BASE64OutputStream.java,v 1.1 2005/05/03 06:51:35 hamada Exp $ - * - * Copyright (c) 2001 Sun Microsystems, Inc. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * 3. The end-user documentation included with the redistribution, - * if any, must include the following acknowledgment: - * "This product includes software developed by the - * Sun Microsystems, Inc. for Project JXTA." - * Alternately, this acknowledgment may appear in the software itself, - * if and wherever such third-party acknowledgments normally appear. - * - * 4. The names "Sun", "Sun Microsystems, Inc.", "JXTA" and "Project JXTA" - * must not be used to endorse or promote products derived from this - * software without prior written permission. For written - * permission, please contact Project JXTA at http://www.jxta.org. - * - * 5. Products derived from this software may not be called "JXTA", - * nor may "JXTA" appear in their name, without prior written - * permission of Sun. - * - * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL SUN MICROSYSTEMS OR - * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF - * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT - * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - * - * ==================================================================== - * - * This software consists of voluntary contributions made by many - * individuals on behalf of Project JXTA. For more - * information on Project JXTA, please see - * . - * - * This license is based on the BSD license adopted by the Apache Foundation. - */ - -package org.webodf; - -import java.io.IOException; -import java.io.OutputStream; -import java.io.Writer; - -/** - * An OutputStream implementation which encodes the written bytes into BASE64 - * encoded character data and writes the output to an associated text Writer. - * - * - * - * This implementation is not thread safe. * * @see - * net.jxta.impl.util.BASE64InputStream * @see IETF RFC 2045 MIME : Format of - * Internet Message BodiesW - */ -public class Base64OutputStream extends OutputStream { - /** * If true then the output stream has been closed. */ - private boolean closed = false; - /** * The output writer. */ - private Writer sendTo = null; - /** * Buffer for incomplete characters. */ - private byte[] buffer = new byte[] { 0, 0, 0 }; - /** * The number of characters currently in the buffer. */ - private byte inBuffer = 0; - - /** - * * Construct a BASE64 Output Stream. * * @param sendTo The text Writer to - * which the BASE64 output will be * written. - */ - public Base64OutputStream(Writer sendTo) { - this.sendTo = sendTo; - } - - /** * {@inheritDoc} */ - public void write(int b) throws IOException { - if (closed) { - throw new IOException("OutputStream closed."); - } - buffer[inBuffer++] = (byte) b; - if (buffer.length == inBuffer) { - writeBuffer(); - } - } - - /** - * * {@inheritDoc} * * - * - * The output writer is NOT closed. - */ - public void close() throws IOException { - flush(); - closed = true; - sendTo = null; - } - - /** * {@inheritDoc} */ - public void flush() throws IOException { - writeBuffer(); - } - - /** * Write a full or partial buffer to the output writer. */ - private void writeBuffer() throws IOException { - if (0 == inBuffer) { - return; - } - int val = ((buffer[0] & 0x00FF) << 16) + ((buffer[1] & 0x00FF) << 8) - + (buffer[2] & 0x00FF); - int c0 = (val >> 18) & 0x003F; - int c1 = (val >> 12) & 0x003F; - int c2 = (val >> 6) & 0x003F; - int c3 = val & 0x003F; - sendTo.write(encodeSixBits(c0)); - sendTo.write(encodeSixBits(c1)); - if (inBuffer > 1) { - sendTo.write(encodeSixBits(c2)); - } else { - sendTo.write('='); - } - if (inBuffer > 2) { - sendTo.write(encodeSixBits(c3)); - } else { - sendTo.write('='); - } - buffer[0] = 0; - buffer[1] = 0; - buffer[2] = 0; - inBuffer = 0; - } - - /** * BASE64 Encoding Table */ - static final char encode[] = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', - 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', - 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', - 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', - 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', - '9', '+', '/' }; - - /** - * * Encode six bits into a character using the standard BASE64 table. * * @param - * b the bits to encode. b must be >=0 and <= 63 * @return the appropriate - * character for the input value. - */ - private static char encodeSixBits(int b) { - char c; - if ((b < 0) || (b > 63)) - throw new IllegalArgumentException("bad encode value"); - else - c = encode[b]; - return c; - } -} \ No newline at end of file diff --git a/programs/cordova/platforms/android/src/org/webodf/WebODF.java b/programs/cordova/platforms/android/src/org/webodf/WebODF.java deleted file mode 100644 index 599acc518..000000000 --- a/programs/cordova/platforms/android/src/org/webodf/WebODF.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - Licensed to the Apache Software Foundation (ASF) under one - or more contributor license agreements. See the NOTICE file - distributed with this work for additional information - regarding copyright ownership. The ASF licenses this file - to you under the Apache License, Version 2.0 (the - "License"); you may not use this file except in compliance - with the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, - software distributed under the License is distributed on an - "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - KIND, either express or implied. See the License for the - specific language governing permissions and limitations - under the License. - */ - -package org.webodf; - -import android.os.Bundle; -import org.apache.cordova.*; - -public class WebODF extends CordovaActivity -{ - @Override - public void onCreate(Bundle savedInstanceState) - { - super.onCreate(savedInstanceState); - super.init(); - // Set by in config.xml - super.loadUrl(Config.getStartUrl()); - //super.loadUrl("file:///android_asset/www/index.html"); - } -} - diff --git a/programs/cordova/platforms/android/src/org/webodf/WebODFActivity.java b/programs/cordova/platforms/android/src/org/webodf/WebODFActivity.java deleted file mode 100644 index 008ab04ec..000000000 --- a/programs/cordova/platforms/android/src/org/webodf/WebODFActivity.java +++ /dev/null @@ -1,198 +0,0 @@ -package org.webodf; - -import java.io.BufferedInputStream; -import java.io.BufferedOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.util.zip.ZipEntry; -import java.util.zip.ZipOutputStream; - -import org.apache.cordova.CordovaActivity; -import org.apache.cordova.Config; - -import android.content.Context; -import android.content.Intent; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Picture; -import android.net.Uri; -import android.os.Bundle; -import android.util.Log; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; - -public class WebODFActivity extends CordovaActivity { - - private String path; - private ZipReader zipreader; - - /** Called when the activity is first created. */ - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - super.init(); - path = null; - if (getIntent() != null && getIntent().getData() != null) { - path = getIntent().getData().getPath(); -Log.d("TEST",path); - } - // Set by in config.xml - super.loadUrl(Config.getStartUrl()); - } - - private void openOdfFile() { - if (path == null) { - return; - } - String escapedPath = "file://" + path.replace("'", "\\'"); - String cmd = "invokeString = '" + escapedPath - + "';application.openUrl(invokeString);"; - sendJavascript(cmd); -Log.d("TEST", cmd); - } - - @Override - protected void onResume() { - super.onResume(); -Log.d("TEST", "ONRESUME"); - zipreader = new ZipReader(appView); - appView.addJavascriptInterface(zipreader, "zipreader"); - // the app may be resume by an intent to open a file - openOdfFile(); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - MenuInflater inflater = getMenuInflater(); - inflater.inflate(R.menu.main, menu); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - // Handle item selection - switch (item.getItemId()) { - case R.id.report_issue: - reportIssue(); - return true; - case R.id.about: - about(); - return true; - case R.id.visit_website: - visitWebODFWebsite(); - return true; - default: - return super.onOptionsItemSelected(item); - } - } - - private void visitWebODFWebsite() { - String url = "http://webodf.org/"; - Intent i = new Intent(Intent.ACTION_VIEW); - i.setData(Uri.parse(url)); - startActivity(i); - } - - private void about() { - String url = "http://webodf.org/about/"; - Intent i = new Intent(Intent.ACTION_VIEW); - i.setData(Uri.parse(url)); - startActivity(i); - } - - private File takeScreenShot() { - Picture screenshot = appView.capturePicture(); - Bitmap bitmap = Bitmap.createBitmap(screenshot.getWidth(), - screenshot.getHeight(), Bitmap.Config.ARGB_8888); - Canvas canvas = new Canvas(bitmap); - screenshot.draw(canvas); - screenshot = null; - String filename = "screenshot.png"; - try { - FileOutputStream out = openFileOutput(filename, - Context.MODE_WORLD_READABLE); - bitmap.compress(Bitmap.CompressFormat.PNG, 90, out); - out.flush(); - out.close(); - } catch (IOException e) { - return null; - } - return new File(getFilesDir(), filename); - } - - Uri getApplicationFileUri(String filename) { - // start path from /mnt/sdcard to make gmail mail client work - return Uri.fromFile(new File("/mnt/sdcard/../.." + getFilesDir() + "/" - + filename)); - } - - File createZip(File files[]) { - final int buffersize = 2048; - String zipfile = "issuereport.zip"; - try { - BufferedInputStream origin = null; - FileOutputStream dest = openFileOutput(zipfile, - Context.MODE_WORLD_READABLE); - ZipOutputStream out = new ZipOutputStream(new BufferedOutputStream( - dest)); - byte data[] = new byte[buffersize]; - for (int i = 0; i < files.length; i++) { - FileInputStream fi = new FileInputStream(files[i]); - origin = new BufferedInputStream(fi, buffersize); - ZipEntry entry = new ZipEntry(files[i].getName()); - out.putNextEntry(entry); - int count; - while ((count = origin.read(data, 0, buffersize)) != -1) { - out.write(data, 0, count); - } - origin.close(); - } - out.close(); - dest.close(); - } catch (Exception e) { - e.printStackTrace(); - return null; - } - return new File(getFilesDir(), zipfile); - } - - private void reportIssue() { - File screenshotfile = takeScreenShot(); - File odffile = (path == null) ? null : new File(path); - File attachmentfile = null; - String type = "text/plain"; - if (screenshotfile != null) { - if (odffile != null) { - type = "application/zip"; - attachmentfile = createZip(new File[] { screenshotfile, odffile }); - } else { - type = "image/png"; - attachmentfile = screenshotfile; - } - } else if (odffile != null) { - attachmentfile = odffile; - type = "application/vnd.oasis.opendocument.text"; - } - - Intent emailIntent = new Intent(android.content.Intent.ACTION_SEND); - String aEmailList[] = { "WebODF ", - "jos@vandenoever.info" }; - emailIntent.putExtra(android.content.Intent.EXTRA_EMAIL, aEmailList); - emailIntent.putExtra(android.content.Intent.EXTRA_SUBJECT, - "Issue report"); - - if (attachmentfile != null) { - // start path from /mnt/sdcard to make gmail mail client work - Uri uri = Uri.fromFile(new File("/mnt/sdcard/../.." - + attachmentfile.getAbsolutePath())); - emailIntent.putExtra(Intent.EXTRA_STREAM, uri); - } - emailIntent.setType(type); - emailIntent.putExtra(Intent.EXTRA_TEXT, "My message body."); - startActivity(emailIntent); - } - -} diff --git a/programs/cordova/platforms/android/src/org/webodf/ZipReader.java b/programs/cordova/platforms/android/src/org/webodf/ZipReader.java deleted file mode 100644 index 9e5c97361..000000000 --- a/programs/cordova/platforms/android/src/org/webodf/ZipReader.java +++ /dev/null @@ -1,88 +0,0 @@ -package org.webodf; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.StringWriter; -import java.util.zip.ZipFile; - -import android.webkit.WebView; - -public class ZipReader { - - private WebView view; - private ZipFile zip; - private String url; - - ZipReader(WebView view) { - this.view = view; - } - - private InputStream openZipEntry(String url, String filename) - throws IOException, NullPointerException { - if (zip == null || this.url != url) { - zip = new ZipFile(new File(url.substring(7)), ZipFile.OPEN_READ); - this.url = url; - } - return zip.getInputStream(zip.getEntry(filename)); - } - - public void loadAsString(String url, String filename, String callbackname) { - StringWriter sw = new StringWriter(); - String err = null; - try { - InputStreamReader reader = new InputStreamReader(openZipEntry(url, - filename)); - int c; - while ((c = reader.read()) != -1) { - if (c == '"') { - sw.append("\\\""); - } else if (c != '\r' && c != '\n') { - sw.append((char) c); - } - } - reader.close(); - } catch (Exception e) { - // TODO: escape the filename - err = "Could not read file " + filename + " from " + url; - } - call(callbackname, err, sw.toString()); - } - - public void loadAsDataURL(String url, String filename, String mimetype, - String callbackname) { - String err = null; - StringWriter sw = new StringWriter(); - sw.write("data:"); - if (mimetype != null) { - sw.write(mimetype); - } - sw.write(";base64,"); - Base64OutputStream base64 = new Base64OutputStream(sw); - InputStream fi; - try { - fi = openZipEntry(url, filename); - int c; - while ((c = fi.read()) != -1) { - base64.write(c); - } - fi.close(); - base64.close(); - sw.close(); - } catch (Exception e) { - err = "Could not read file " + filename + " from " + url; - } - call(callbackname, err, sw.toString()); - } - - private void call(String callbackname, String err, String data) { - if (err != null) { - err = "\"" + err + "\""; - } - String cmd = "javascript:(function() {" + callbackname + "(" + err - + ",\"" + data + "\");})();"; - view.loadUrl(cmd); - } - -} diff --git a/programs/cordova/platforms/android/src/org/webodf/starturl/StartURL.java b/programs/cordova/platforms/android/src/org/webodf/starturl/StartURL.java deleted file mode 100644 index 46d198502..000000000 --- a/programs/cordova/platforms/android/src/org/webodf/starturl/StartURL.java +++ /dev/null @@ -1,32 +0,0 @@ -package org.webodf.starturl; - -import android.content.Intent; -import org.apache.cordova.CallbackContext; -import org.apache.cordova.CordovaInterface; -import org.apache.cordova.CordovaPlugin; -import org.apache.cordova.CordovaWebView; -import org.json.JSONArray; -import org.json.JSONException; - -public class StartURL extends CordovaPlugin { - @Override - public void initialize(CordovaInterface cordova, CordovaWebView webView) { - super.initialize(cordova, webView); - } - @Override - public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException { - if ("startUrl".equals(action)) { - callbackContext.success(getStartURL()); - return true; - } - return false; - } - private String getStartURL() { - String url = null; - Intent intent = cordova.getActivity().getIntent(); - if (intent != null && intent.getData() != null) { - url = "file://" + intent.getData().getPath(); - } - return url; - } -} diff --git a/programs/cordova/plugins/org.webodf.starturl/android/StartURL.java b/programs/cordova/plugins/org.webodf.starturl/android/StartURL.java deleted file mode 100644 index 46d198502..000000000 --- a/programs/cordova/plugins/org.webodf.starturl/android/StartURL.java +++ /dev/null @@ -1,32 +0,0 @@ -package org.webodf.starturl; - -import android.content.Intent; -import org.apache.cordova.CallbackContext; -import org.apache.cordova.CordovaInterface; -import org.apache.cordova.CordovaPlugin; -import org.apache.cordova.CordovaWebView; -import org.json.JSONArray; -import org.json.JSONException; - -public class StartURL extends CordovaPlugin { - @Override - public void initialize(CordovaInterface cordova, CordovaWebView webView) { - super.initialize(cordova, webView); - } - @Override - public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException { - if ("startUrl".equals(action)) { - callbackContext.success(getStartURL()); - return true; - } - return false; - } - private String getStartURL() { - String url = null; - Intent intent = cordova.getActivity().getIntent(); - if (intent != null && intent.getData() != null) { - url = "file://" + intent.getData().getPath(); - } - return url; - } -} diff --git a/programs/cordova/plugins/org.webodf.starturl/plugin.xml b/programs/cordova/plugins/org.webodf.starturl/plugin.xml index 38aa64524..78a39f896 100644 --- a/programs/cordova/plugins/org.webodf.starturl/plugin.xml +++ b/programs/cordova/plugins/org.webodf.starturl/plugin.xml @@ -1,6 +1,5 @@ Start URL @@ -9,15 +8,4 @@ xmlns:android="http://schemas.android.com/apk/res/android" - - - - - - - - - - - diff --git a/programs/cordova/plugins/org.webodf.starturl/www/starturl.js b/programs/cordova/plugins/org.webodf.starturl/www/starturl.js index cd19b99c0..3300596e3 100644 --- a/programs/cordova/plugins/org.webodf.starturl/www/starturl.js +++ b/programs/cordova/plugins/org.webodf.starturl/www/starturl.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global window, console, require*/ var exec = require('cordova/exec'); diff --git a/programs/cordova/www/FileScanner.js b/programs/cordova/www/FileScanner.js index 747275d5f..a6f01eaf7 100644 --- a/programs/cordova/www/FileScanner.js +++ b/programs/cordova/www/FileScanner.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global window, console, navigator*/ diff --git a/programs/cordova/www/OdfViewer.js b/programs/cordova/www/OdfViewer.js index 9f4addf62..217cf3738 100644 --- a/programs/cordova/www/OdfViewer.js +++ b/programs/cordova/www/OdfViewer.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global FileScanner, window, DirectoryEntry, FileScannerEntry, FileScannerFileEntry, Blob, XMLHttpRequest, odf, console, WebKitBlobBuilder, navigator, runtime, FileReader, Uint8Array, endsWith*/ diff --git a/programs/cordova/www/index.js b/programs/cordova/www/index.js index f5edc64c7..ecf88f90e 100644 --- a/programs/cordova/www/index.js +++ b/programs/cordova/www/index.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global alert, document, window, FileScanner, OdfViewer*/ diff --git a/programs/docnosis/CMakeLists.txt b/programs/docnosis/CMakeLists.txt index e69de29bb..80fbadabe 100644 --- a/programs/docnosis/CMakeLists.txt +++ b/programs/docnosis/CMakeLists.txt @@ -0,0 +1,42 @@ +# Docnosis tells whether a document keeps to the OpenDocument standard, by +# reading it against the schemas of Relax NG that OASIS publishes, which are +# beside it. It is a page and nothing else: the document is read in the browser +# of the one who asks, and it goes nowhere. +# +# It reads the library from its sources rather than from "webodf.js": the +# validator of Relax NG is left out of the file that ships, as no viewer uses +# it, see "scripts/lib/sources.js". + +set(DOCNOSIS_DIR ${CMAKE_CURRENT_BINARY_DIR}/docnosis) + +FILE(GLOB DOCNOSIS_SCHEMAS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/*.rng) +FILE(GLOB DOCNOSIS_ICONS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/*.png) + +COPY_FILES(DOCNOSIS_FILES ${CMAKE_CURRENT_SOURCE_DIR} ${DOCNOSIS_DIR} + index.html docnosis.js docnosis.css + ${DOCNOSIS_SCHEMAS} ${DOCNOSIS_ICONS}) + +# The sources of the library, under "lib", where the page looks for them. They +# are gathered here rather than taken from LIBJSFILES, that is only known in +# the directory of the library and in the ones below it. +FILE(GLOB_RECURSE DOCNOSIS_LIBFILES RELATIVE ${CMAKE_SOURCE_DIR}/webodf + ${CMAKE_SOURCE_DIR}/webodf/lib/*.js) +COPY_FILES(DOCNOSIS_FILES ${CMAKE_SOURCE_DIR}/webodf ${DOCNOSIS_DIR} + ${DOCNOSIS_LIBFILES} lib/manifest.json) + +add_custom_target(build-docnosis ALL DEPENDS manifest.json-target + ${DOCNOSIS_FILES}) + +add_custom_command( + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/docnosis-${WEBODF_VERSION}.zip + COMMAND ${NODE} ARGS ${RUNTIMEJS} ${TOOLS_DIR}/zipdir.js + ${DOCNOSIS_DIR} + ${CMAKE_CURRENT_BINARY_DIR}/docnosis-${WEBODF_VERSION}.zip + DEPENDS ${NODE} ${TOOLS_DIR}/zipdir.js ${DOCNOSIS_FILES} +) +add_custom_target(product-docnosis + DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/docnosis-${WEBODF_VERSION}.zip) +WEBODF_PRODUCT(product-docnosis + ${CMAKE_CURRENT_BINARY_DIR}/docnosis-${WEBODF_VERSION}.zip) diff --git a/programs/docnosis/OpenDocument-v1.2-cos01-manifest-schema.rng b/programs/docnosis/OpenDocument-v1.2-os-manifest-schema.rng similarity index 98% rename from programs/docnosis/OpenDocument-v1.2-cos01-manifest-schema.rng rename to programs/docnosis/OpenDocument-v1.2-os-manifest-schema.rng index 8902970ee..af13a26c7 100644 --- a/programs/docnosis/OpenDocument-v1.2-cos01-manifest-schema.rng +++ b/programs/docnosis/OpenDocument-v1.2-os-manifest-schema.rng @@ -1,9 +1,9 @@ + + + + + + + + + + + + + + + + + + + Blowfish CFB + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SHA1/1K + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + edit + presentation-slide-show + read-only + + + + + + + + + + + + + + + + + + + + + PGP + + + + + PBKDF2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1.3 + + + + + [^:]+:[^:]+ + + + + + + + + + + + + + + + + SHA1 + + + + + + + + + + + + + + \ No newline at end of file diff --git a/programs/docnosis/OpenDocument-v1.3-schema.rng b/programs/docnosis/OpenDocument-v1.3-schema.rng new file mode 100644 index 000000000..dc77c4fc5 --- /dev/null +++ b/programs/docnosis/OpenDocument-v1.3-schema.rng @@ -0,0 +1,18299 @@ + + + + + + + + + + + + + + + + + (([\i-[:]][\c-[:]]*)?:)?.+ + 1 + + + + + + + + + + + + + + + + + + + + + + + + \[(([\i-[:]][\c-[:]]*)?:)?.+\] + 3 + + + + + + + + + + + + + + + + + rgb + hsl + + + + + + + clockwise + counter-clockwise + + + + + + + + + + + + + + + + + + + + + discrete + linear + paced + spline + + + + + + + + + translate + scale + rotate + skewX + skewY + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + forward + reverse + + + + + + + + + + + + in + out + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + An IRI-reference as defined in [RFC3987]. See ODF 1.3 Part 3 section 18.3. + + + + + + + + + + true + false + + + + + + + + + + + + + + + + + + + + + + + + + + ($?([^\. ']+|'([^']|'')+'))?\.$?[A-Z]+$?[0-9]+ + + + + + + ($?([^\. ']+|'([^']|'')+'))?\.$?[A-Z]+$?[0-9]+(:($?([^\. ']+|'([^']|'')+'))?\.$?[A-Z]+$?[0-9]+)? + + + ($?([^\. ']+|'([^']|'')+'))?\.$?[0-9]+:($?([^\. ']+|'([^']|'')+'))?\.$?[0-9]+ + + + ($?([^\. ']+|'([^']|'')+'))?\.$?[A-Z]+:($?([^\. ']+|'([^']|'')+'))?\.$?[A-Z]+ + + + + + + Value is a space separated list of "cellRangeAddress" patterns + + + + + + + + + + + + + + + + + + + + + + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + simple + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + x + y + z + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + major + minor + + + + + + + + + + + + + + + + + + + + + + + + + start + end + top + bottom + + + + + + start + center + end + + + + + + + top-start + bottom-start + top-end + bottom-end + + + + + + + + + wide + high + balanced + + + + + custom + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + none + row + column + both + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + rect\([ ]*((-?([0-9]+(\.[0-9]*)?|\.[0-9]+)((cm)|(mm)|(in)|(pt)|(pc)))|(auto))([ ]*,[ ]*((-?([0-9]+(\.[0-9]*)?|\.[0-9]+)((cm)|(mm)|(in)|(pt)|(pc))))|(auto)){3}[ ]*\) + + + + + #[0-9a-fA-F]{6} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + none + sum + + + + + + + replace + sum + + + + + + + + + + + default + on-click + with-previous + after-previous + timing-root + main-sequence + interactive-sequence + + + + + + + + + + + + + + + + + custom + entrance + exit + emphasis + motion-path + ole-action + media-call + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + discrete + linear + paced + spline + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + transparent + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + auto + column + page + + + + + + + auto + column + page + + + + + + + + + gregorian + gengou + ROC + hanja_yoil + hanja + hijri + jewish + buddhist + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + short + medium + long + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + simple + + + + + + + + + + + + + new + replace + + + + + + + + + + + + nohref + + + + + + + + + + + + + + + + + full + section + cut + arc + + + + + + + + + + + + + + + + + + + + + + + + + + + simple + + + + + + + embed + + + + + onLoad + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + scale + scale-min + + + + + + + + scale + scale-min + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + first + last + all + media + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + table + query + command + + + + + + + + + + + + + + + + + value + formula + + + + + + + + + value + formula + none + + + + + + + + + value + none + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + remove + freeze + hold + transition + auto + inherit + + + + + + + + + remove + freeze + hold + auto + default + transition + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + center + + + + + + start + end + top + bottom + + + + + + start + center + end + + + + + + + + + + + flat + 3d + + + + + + + + + fixed + language + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + auto + always + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + i + I + + + + + + + + a + A + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + auto + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + page + chapter + direction + text + + + + + + + + + + + + + + + + + + + + indefinite + + + + + + + + + never + always + whenNotActive + inherit + + + + + + + + + never + always + whenNotActive + default + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ltr + ttb + + + + + + + + + + + + + + simple + + + + + + + onRequest + + + + + + + + objectBoundingBox + + + + + + + + + + + pad + reflect + repeat + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + accepted + rejected + pending + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + start + end + left + right + center + justify + + + + + + + + + + page + frame + paragraph + char + as-char + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + float + + + + + + + + percentage + + + + + + + + currency + + + + + + + + + + + + + date + + + + + + + + time + + + + + + + + boolean + + + + + + + + string + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + top + middle + bottom + from-top + below + + + + + + + + + + + + + + page + page-content + frame + frame-content + paragraph + paragraph-content + char + line + baseline + text + + + + + + + + + lr-tb + rl-tb + tb-rl + tb-lr + lr + rl + tb + page + + + + + + + + + + + + + + + + + + boolean + short + int + long + double + string + datetime + base64Binary + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + [A-Za-z0-9]{1,8} + + + + + + + + + + + + + + + + + + + non-primitive + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + equal-integer + is-boolean + equal-boolean + equal-use-only-zero + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + no-nulls + nullable + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + simple + + + + + + + none + + + + + onRequest + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + simple + + + + + + + none + + + + + onRequest + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + boolean + short + int + long + double + string + + + + + + + + + + + + + + + + + + + + + + + + + bit + boolean + tinyint + smallint + integer + bigint + float + real + double + numeric + decimal + char + varchar + longvarchar + date + time + timestmp + binary + varbinary + longvarbinary + sqlnull + other + object + distinct + struct + array + blob + clob + ref + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + simple + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + primary + unique + foreign + + + + + + + + + + + cascade + restrict + set-null + no-action + set-default + + + + + + + cascade + restrict + set-null + no-action + set-default + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + parallel + perspective + + + + + + + + + + + + + + + + + + + + + + flat + phong + gouraud + draft + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + simple + + + + + + + onRequest + + + + + + + + + + + new + replace + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + standard + lines + line + curve + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + flat + phong + gouraud + draft + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + parallel + perspective + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + none + segments + rectangle + + + + + + + + + + + + + + + + + normal + path + shape + + + + + + + path + shape + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + top-left + top + top-right + left + center + right + bottom-left + bottom-right + + + + + + auto + left + right + up + down + horizontal + vertical + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + single + double + triple + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + always + screen + printer + none + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + false + + + + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + rect + round + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + -0.5 + 0.5 + + + + + roman + swiss + modern + decorative + script + system + + + + + fixed + variable + + + + + normal + italic + oblique + + + + + normal + small-caps + + + + + normal + bold + 100 + 200 + 300 + 400 + 500 + 600 + 700 + 800 + 900 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + simple + + + + + + + onRequest + + + + + + + + + + + + get + post + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + table + query + command + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + float + + + + + + + + + + + + percentage + + + + + + + + + + + + currency + + + + + + + + + + + + + + + + + date + + + + + + + + + + + + time + + + + + + + + + + + + boolean + + + + + + + + + + + + string + + + + + + + + + + + void + + + + + + + + void + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + horizontal + vertical + + + + + + + + linear + axial + radial + ellipsoid + square + rectangular + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + left + center + right + + + + + horizontal + horizontal-on-odd + horizontal-on-even + + + + + + + + + + + + + + + + + + + + + + + + + + + + avoid-overlap + center + top + top-right + right + bottom-right + bottom + bottom-left + left + top-left + inside + outside + near-origin + + + + + + + + [A-Za-z]{1,8} + + + + + -?([0-9]+(\.[0-9]*)?|\.[0-9]+)((cm)|(mm)|(in)|(pt)|(pc)|(px)) + + + + + continuous + skip-white-space + + + + + none + solid + dotted + dash + long-dash + dot-dash + dot-dot-dash + wave + + + + + none + single + double + + + + + auto + normal + bold + thin + medium + thick + + + + + + + + + + selection + selection-indices + + + + + + + + + + + + + + + + table + query + sql + sql-pass-through + value-list + table-fields + + + + + + + + + + + To avoid inclusion of the complete MathML schema, anything is allowed within a math:math top-level element + + + + + + + + + + + + + + + + + + + + + [^:]+:[^:]+ + + + + + none + current + parent + + + + + 0.0 + + + + + + + + ([0-9]+(\.[0-9]*)?|\.[0-9]+)((cm)|(mm)|(in)|(pt)|(pc)|(px)) + + + + + ([0-9]+(\.[0-9]*)?|\.[0-9]+)(px) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + short + long + + + + + + + + + + + + + + + + short + long + + + + + + + + + + + + + + + + + + + + + + + + + + + short + long + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + short + long + + + + + + + + + + + + + + + short + long + + + + + + + + + + + + + + + + + + + + + + + + + + + short + long + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + short + long + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + short + long + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + short + long + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + into-default-style-data-style + into-english-number + keep-text + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1.3 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + simple + + + + + + + onRequest + + + + + + + + + + + + + + + + + simple + + + + + + + replace + + + + + onLoad + + + + + + + + + + + + + + + + + + + new + replace + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + float + + + + + + date + + + + + + time + + + + + + boolean + + + + + + string + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text:page-count + text:paragraph-count + text:word-count + text:character-count + text:table-count + text:image-count + text:object-count + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text:reference-ref + text:bookmark-ref + + + + + + + + + + + + + + + + + + + + + + + + simple + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + value + unit + gap + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text:identifier + text:address + text:annote + text:author + text:booktitle + text:chapter + text:edition + text:editor + text:howpublished + text:institution + text:journal + text:month + text:note + text:number + text:organizations + text:pages + text:publisher + text:school + text:series + text:title + text:report-type + text:volume + text:year + text:url + text:custom1 + text:custom2 + text:custom3 + text:custom4 + text:custom5 + text:isbn + text:issn + + + + + + + + + + + + + + + + + + + + + + + + + + + + + -?([0-9]+(\.[0-9]*)?|\.[0-9]+)% + + + + + \([ ]*-?([0-9]+(\.[0-9]*)?|\.[0-9]+)((cm)|(mm)|(in)|(pt)|(pc))([ ]+-?([0-9]+(\.[0-9]*)?|\.[0-9]+)((cm)|(mm)|(in)|(pt)|(pc))){2}[ ]*\) + + + + + -?[0-9]+,-?[0-9]+([ ]+-?[0-9]+,-?[0-9]+)* + + + + + + + + ([0-9]*[1-9][0-9]*(\.[0-9]*)?|0+\.[0-9]*[1-9][0-9]*|\.[0-9]*[1-9][0-9]*)((cm)|(mm)|(in)|(pt)|(pc)|(px)) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + title + outline + subtitle + text + graphic + object + chart + table + orgchart + page + notes + handout + header + footer + date-time + page-number + + + + + + + + + + fixed + current-date + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + none + previous-page + next-page + first-page + last-page + hide + stop + execute + show + verb + fade-out + sound + last-visited-page + + + + + + + + + + + + + + + + + + + + + + + + + simple + + + + + + + embed + + + + + onRequest + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + enabled + disabled + + + + + + + enabled + disabled + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + simple + + + + + + + onRequest + + + + + + new + replace + + + + + + + + + + + + + + + + + + + + + none + from-left + from-top + from-right + from-bottom + from-center + from-upper-left + from-upper-right + from-lower-left + from-lower-right + to-left + to-top + to-right + to-bottom + to-upper-left + to-upper-right + to-lower-right + to-lower-left + path + spiral-inward-left + spiral-inward-right + spiral-outward-left + spiral-outward-right + vertical + horizontal + to-center + clockwise + counter-clockwise + + + + + none + fade + move + stripes + open + close + dissolve + wavyline + random + lines + laser + appear + hide + move-short + checkerboard + rotate + stretch + + + + + slow + medium + fast + + + + + + + + + + [0-9]+\* + + + + + row + column + + + + + + + + + + + + + + + + + + + + + + + simple + + + + + + + onRequest + + + + + + + + + [A-Za-z0-9]{1,8} + + + + + + + + + + + + none + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + -?([0-9]?[0-9](\.[0-9]*)?|100(\.0*)?|\.[0-9]+)% + + + + + + + + + + + + unchecked + checked + unknown + + + + + + + + + + + + + + + + + + + + + + + no-repeat + repeat + stretch + + + + + + + left + center + right + top + bottom + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + none + + + automatic + + + + named-symbol + + + + square + diamond + arrow-down + arrow-up + arrow-right + arrow-left + bow-tie + hourglass + circle + star + x + plus + asterisk + horizontal-bar + vertical-bar + + + + + + image + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + none + cubic-spline + b-spline + step-start + step-end + step-center-x + step-center-y + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + cuboid + cylinder + cone + pyramid + + + + + + + + + + + + + + + + + use-zero + leave-gap + ignore + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + side-by-side + stagger-even + stagger-odd + + + + + + + + + none + value + percentage + value-and-percentage + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + none + variance + standard-deviation + percentage + error-margin + constant + standard-error + cell-range + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + columns + rows + + + + + + + none + linear + logarithmic + moving-average + exponential + power + polynomial + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + prior + central + averaged-abscissa + + + + + + + + start + end + + + + + + + + near-axis + near-axis-other-side + outside-start + outside-end + + + + + + + at-labels + at-axis + at-labels-and-axis + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + none + solid + dotted + dashed + dot-dashed + + + + + + + + + + + + + + + top + middle + bottom + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + manual + automatic + semi-automatic + + + + + + + none + fade-from-left + fade-from-top + fade-from-right + fade-from-bottom + fade-from-upperleft + fade-from-upperright + fade-from-lowerleft + fade-from-lowerright + move-from-left + move-from-top + move-from-right + move-from-bottom + move-from-upperleft + move-from-upperright + move-from-lowerleft + move-from-lowerright + uncover-to-left + uncover-to-top + uncover-to-right + uncover-to-bottom + uncover-to-upperleft + uncover-to-upperright + uncover-to-lowerleft + uncover-to-lowerright + fade-to-center + fade-from-center + vertical-stripes + horizontal-stripes + clockwise + counterclockwise + open-vertical + open-horizontal + close-vertical + close-horizontal + wavyline-from-left + wavyline-from-top + wavyline-from-right + wavyline-from-bottom + spiralin-left + spiralin-right + spiralout-left + spiralout-right + roll-from-top + roll-from-left + roll-from-right + roll-from-bottom + stretch-from-left + stretch-from-top + stretch-from-right + stretch-from-bottom + vertical-lines + horizontal-lines + dissolve + random + vertical-checkerboard + horizontal-checkerboard + interlocking-horizontal-left + interlocking-horizontal-right + interlocking-vertical-top + interlocking-vertical-bottom + fly-away + open + close + melt + + + + + + + + + + + + + + + + + + + + + + forward + reverse + + + + + + + + + + + + + + + + + visible + hidden + + + + + + + full + border + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + word + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + normal + ultra-condensed + extra-condensed + condensed + semi-condensed + semi-expanded + expanded + extra-expanded + ultra-expanded + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + left + center + right + + + + + + + + + + + + + + + + + + + + none + solid + bitmap + gradient + hatch + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + no-repeat + repeat + stretch + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + top-left + top + top-right + left + center + right + bottom-left + bottom + bottom-right + + + + + + + + + horizontal + vertical + + + + + + + + + + + + + + + + + + nonzero + evenodd + + + + + + + + + + + + + + + + none + dash + solid + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + 1 + + + + + + + + + miter + round + bevel + middle + none + + + + + + + butt + square + round + + + + + + + + + + + + none + scroll + alternate + slide + + + + + + + left + right + up + down + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + top + middle + bottom + justify + + + + + + + left + center + right + justify + + + + + + + no-wrap + wrap + + + + + + + + + + + + greyscale + mono + watermark + standard + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + visible + hidden + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + below + above + + + + + + + + + + + + automatic + left-outside + inside + right-outside + + + + + + + automatic + above + below + center + + + + + + + automatic + mm + cm + m + km + pt + pc + inch + ft + mi + + + + + + + + + + + + + + + + + straight-line + angled-line + angled-connector-line + + + + + + + fixed + free + + + + + + + + + + + + + + + + + horizontal + vertical + auto + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + correct + attractive + + + + + + + + + + + + + + + + + enabled + disabled + + + + + + + + + + + + + + + + + + + + + + standard + double-sided + + + + + + + object + flat + sphere + + + + + + + normal + inverse + + + + + + + object + parallel + sphere + + + + + + + object + parallel + sphere + + + + + + + luminance + intensity + color + + + + + + + enabled + disabled + + + + + + + replace + modulate + blend + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + visible + hidden + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + none + + + + content + position + size + + + + + + + + + + left + center + right + from-left + inside + outside + from-inside + + + + + + + + + + + + page + page-content + page-start-margin + page-end-margin + frame + frame-content + frame-start-margin + frame-end-margin + paragraph + paragraph-content + paragraph-start-margin + paragraph-end-margin + char + + + + + + + + + + + + + + + + + none + left + right + parallel + dynamic + run-through + biggest + + + + + + + + + + + + no-limit + + + + + + + + + + + + + full + outside + + + + + + + foreground + background + + + + + + + + + + + + clip + auto-create-new-frame + + + + + + + none + vertical + + + vertical + + + + + vertical + + + + + + + + auto + + + + + + + + iterative + once-concurrent + once-successive + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + content + thumbnail + icon + print-view + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + listtab + space + nothing + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + label-width-and-position + label-alignment + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + all + left + right + mirrored + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + default + + + + + + + + portrait + landscape + + + + + + + + + + + + + + + + + + + + + + headers + grid + annotations + objects + charts + drawings + formulas + zero-values + + + + + + + + + ttb + ltr + + + + + + + + continue + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + horizontal + vertical + both + none + + + + + + + + + + + + + none + line + both + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + normal + + + + + + + + + + + + + + + + + + + + + + + + + start + center + justify + + + + + + + + + + + + auto + always + + + + + + + + + + + + + + + + + + + + + + auto + page + + + + + + + no-limit + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + none + ideograph-alpha + + + + + + + simple + hanging + + + + + + + normal + strict + + + + + + + top + middle + bottom + auto + baseline + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + above + below + + + + + + + left + center + right + distribute-letter + distribute-space + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text + + + + + + + + paragraph + + + + + + + + + + + section + + + + + + + + ruby + + + + + + + + table + + + + + + + + table-column + + + + + + + + table-row + + + + + + + + table-cell + + + + + + + + + + + + + + + graphic + presentation + + + + + + + + + + + + + + + drawing-page + + + + + + + + chart + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + left + center + right + + + + + + char + + + + + + + + + + + + + + + + + + + + + + + font-color + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + top + middle + bottom + automatic + + + + + + + fix + value-type + + + + + + + + auto + 0 + 0deg + 0rad + 0grad + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + no-wrap + wrap + + + + + + + + none + bottom + top + center + + + + + + + none + hidden-and-protected + + + + protected + formula-hidden + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + left + center + right + margins + + + + + + + + + + + + + + + + + + + + collapsing + separating + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + auto + always + + + + + + + + + + + + + + + + + + + + + + + + + + + + none + lowercase + uppercase + capitalize + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + font-color + + + + + + + + + + + + + + + + + + + + super + sub + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + latin + asian + complex + ignore + + + + + + + + normal + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + none + embossed + engraved + + + + + + + + + + + + + + + + + + + + + + + + + + + font-color + + + + + + + + + + + + + + + + + + + + + + + font-color + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + none + letters + lines + + + + + + + + + + + + + + + + + none + + + none + accent + dot + circle + disc + + + above + below + + + + + + + + + + + + + + + + + + + fixed + line-height + + + + + + + + + + + + + + + + + + + + + true + + + none + + + + condition + + + none + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + records + current + page + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + average + count + countnums + max + min + product + stdev + stdevp + sum + var + varp + + + + + + + + + + + + + none + row + column + both + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + from-top + from-bottom + + + + + + + + + + + + + + + + + + + + + + + + + + + row + column + data + hidden + + + + + page + + + + + + + + + + + + + + + auto + average + count + countnums + max + min + product + stdev + stdevp + sum + var + varp + + + + + + + + + + + + + + + + + + + + + + + + + named + + + + + + + + previous + next + + + + + + none + member-difference + member-percentage + member-percentage-difference + running-total + row-percentage + column-percentage + total-percentage + index + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + auto + + + + + + auto + + + + + + + + + + auto + + + + + + auto + + + + + + + + + + + + + seconds + minutes + hours + days + months + quarters + years + + + + + + + + + + + + + + + + + tabular-layout + outline-subtotals-top + outline-subtotals-bottom + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + data + + + + + + + + none + manual + name + + + + + + ascending + descending + + + + + + + + + + + + + + auto + average + count + countnums + max + min + product + stdev + stdevp + sum + var + varp + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + none + row + column + both + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + column + row + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + row + column + table + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + stop + warning + information + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + self + cell-range + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text + number + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + from-another-table + to-another-table + from-same-table + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + row + column + table + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + enable + disable + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + column + row + + + + + + + + + + + + + + + + + + + + + + + + + + + simple + + + + + + + onRequest + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + none + + + + print-range + filter + repeat-row + repeat-column + + + + + + + + + + + + date + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + trace-dependents + remove-dependents + trace-precedents + remove-precedents + trace-errors + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + alpha-numeric + integer + double + + + + + + + + + + + + + + + + + + + + text + number + automatic + + + + + + + + ascending + descending + + + + + + + + + + + + + + + + + text + number + automatic + + + + + + + + ascending + descending + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + average + count + countnums + max + min + product + stdev + stdevp + sum + var + varp + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + copy-all + copy-results-only + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + none + unsorted + sort-ascending + + + + + + + + visible + collapse + filter + + + + + + + + + + + + + + + + + + + _self + _blank + _parent + _top + + + + + + + + + + + + + + + + + + + + + + + + + + + + simple + + + + + + + onRequest + + + + + + + + + + + new + replace + + + + + + + + + + + + + + + + + + + + + + + + + + simple + + + + + + + + + + + + + + + + + + + + + + + + + 1 + 2 + 3 + separator + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + article + book + booklet + conference + custom1 + custom2 + custom3 + custom4 + custom5 + email + inbook + incollection + inproceedings + journal + manual + mastersthesis + misc + phdthesis + proceedings + techreport + unpublished + www + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + number-no-superior + number-all-superior + number + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + name + number + number-and-name + plain-number-and-name + plain-number + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + full + path + name + name-and-extension + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text + category-and-value + caption + + + + + + + + + + + + + + + + + + + + + + + + + + + address + annote + author + bibliography-type + booktitle + chapter + custom1 + custom2 + custom3 + custom4 + custom5 + edition + editor + howpublished + identifier + institution + isbn + issn + journal + month + note + number + organizations + pages + publisher + report-type + school + series + title + url + volume + year + + + + + + + + + + + + + + + + + + + + name + number + number-and-name + plain-number + plain-number-and-name + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + right + + + + left + + + + + + + + + + + + + + + + + + + + + + + + + + + document + chapter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + left + right + inner + outer + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + footnote + endnote + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + document + chapter + page + + + + + + + text + page + section + document + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + previous + next + + + + + + + + + + + + + + + + + + + + + + + + previous + current + next + + + + + + + + + + + + + + + + + text + table + text-box + image + object + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + none + + + + + condition + + + + + + + + + + + + + + + + + + + simple + + + + + + + embed + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + category-and-value + caption + value + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + address + annote + author + bibliography-type + booktitle + chapter + custom1 + custom2 + custom3 + custom4 + custom5 + edition + editor + howpublished + identifier + institution + isbn + issn + journal + month + note + number + organizations + pages + publisher + report-type + school + series + title + url + volume + year + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + document + chapter + + + + + + + + + + + + + + + full + path + name + name-and-extension + area + title + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + [A-Za-z][A-Za-z0-9._\-]* + + + + + + + + + + + + + + submit + reset + push + url + + + + + float + time + date + percentage + currency + boolean + string + + + + + + + + \([ ]*-?([0-9]+(\.[0-9]*)?|\.[0-9]+)([ ]+-?([0-9]+(\.[0-9]*)?|\.[0-9]+)){2}[ ]*\) + + + + + top + center + bottom + + + + + + + + + + + + + + + + + + + + + + ([0-9]?[0-9](\.[0-9]*)?|100(\.0*)?|\.[0-9]+)% + + + + + 0 + 1 + + + diff --git a/programs/docnosis/OpenDocument-v1.4-manifest-schema.rng b/programs/docnosis/OpenDocument-v1.4-manifest-schema.rng new file mode 100644 index 000000000..2bc6ab856 --- /dev/null +++ b/programs/docnosis/OpenDocument-v1.4-manifest-schema.rng @@ -0,0 +1,237 @@ + + + + + + + + + + + + + + + + + + + + + Blowfish CFB + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SHA1/1K + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + edit + presentation-slide-show + read-only + + + + + + + + + + + + + + + + + + + + + PGP + + + + + PBKDF2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1.4 + + + + + [^:]+:[^:]+ + + + + + + + + + + + + + + + + SHA1 + + + + + + + + + + + + + + diff --git a/programs/docnosis/OpenDocument-v1.4-schema.rng b/programs/docnosis/OpenDocument-v1.4-schema.rng new file mode 100644 index 000000000..a69428283 --- /dev/null +++ b/programs/docnosis/OpenDocument-v1.4-schema.rng @@ -0,0 +1,18572 @@ + + + + + + + + + + + + + + + + + + (([\i-[:]][\c-[:]]*)?:)?.+ + 1 + + + + + + + + + + + + + + + + + + + + + + + + \[(([\i-[:]][\c-[:]]*)?:)?.+\] + 3 + + + + + + + + + + + + + + + + + rgb + hsl + + + + + + + clockwise + counter-clockwise + + + + + + + + + + + + + + + + + + + + + discrete + linear + paced + spline + + + + + + + + + translate + scale + rotate + skewX + skewY + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + forward + reverse + + + + + + + + + + + + in + out + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + An IRI-reference as defined in [RFC3987]. See ODF 1.4 Part 3 section 18.3. + + + + + + + + + + true + false + + + + + + + + + + + + + + + + + + + + + + + + + + ($?([^\. ']+|'([^']|'')+'))?\.$?[A-Z]+$?[0-9]+ + + + + + + ($?([^\. ']+|'([^']|'')+'))?\.$?[A-Z]+$?[0-9]+(:($?([^\. ']+|'([^']|'')+'))?\.$?[A-Z]+$?[0-9]+)? + + + ($?([^\. ']+|'([^']|'')+'))?\.$?[0-9]+:($?([^\. ']+|'([^']|'')+'))?\.$?[0-9]+ + + + ($?([^\. ']+|'([^']|'')+'))?\.$?[A-Z]+:($?([^\. ']+|'([^']|'')+'))?\.$?[A-Z]+ + + + + + + Value is a space separated list of "cellRangeAddress" patterns + + + + + + + + + + + + + + + + + + + + + + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + simple + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + x + y + z + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + major + minor + + + + + + + + + + + + + + + + + + + + + + + + + start + end + top + bottom + + + + + + start + center + end + + + + + + + top-start + bottom-start + top-end + bottom-end + + + + + + + + + wide + high + balanced + + + + + custom + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + none + row + column + both + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + rect\([ ]*((-?([0-9]+(\.[0-9]*)?|\.[0-9]+)((cm)|(mm)|(in)|(pt)|(pc)))|(auto))([ ]*,[ ]*((-?([0-9]+(\.[0-9]*)?|\.[0-9]+)((cm)|(mm)|(in)|(pt)|(pc))))|(auto)){3}[ ]*\) + + + + + #[0-9a-fA-F]{6} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + none + sum + + + + + + + replace + sum + + + + + + + + + + + default + on-click + with-previous + after-previous + timing-root + main-sequence + interactive-sequence + + + + + + + + + + + + + + + + + custom + entrance + exit + emphasis + motion-path + ole-action + media-call + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + discrete + linear + paced + spline + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + transparent + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + auto + column + page + + even-page + odd-page + + + + + + + auto + column + page + + even-page + odd-page + + + + + + + + + gregorian + gengou + ROC + hanja_yoil + hanja + hijri + jewish + buddhist + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + short + medium + long + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + simple + + + + + + + + + + + + + new + replace + + + + + + + + + + + + nohref + + + + + + + + + + + + + + + + + full + section + cut + arc + + + + + + + + + + + + + + + + + + + + + + + + + + + simple + + + + + + + embed + + + + + onLoad + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + scale + scale-min + + + + + + + + scale + scale-min + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + first + last + all + media + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + table + query + command + + + + + + + + + + + + + + + + + value + formula + + + + + + + + + value + formula + none + + + + + + + + + value + none + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + remove + freeze + hold + transition + auto + inherit + + + + + + + + + remove + freeze + hold + auto + default + transition + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + center + + + + + + start + end + top + bottom + + + + + + start + center + end + + + + + + + + + + + flat + 3d + + + + + + + + + fixed + language + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + auto + always + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + i + I + + + + + + + + a + A + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + auto + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + indefinite + + + + + + + + + never + always + whenNotActive + inherit + + + + + + + + + never + always + whenNotActive + default + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ltr + ttb + + + + + + + + + + + + + + simple + + + + + + + onRequest + + + + + + + + objectBoundingBox + + + + + + + + + + + pad + reflect + repeat + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + accepted + rejected + pending + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + start + end + left + right + center + justify + + + + + + + + + + page + frame + paragraph + char + as-char + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + float + + + + + + + + percentage + + + + + + + + currency + + + + + + + + + + + + + date + + + + + + + + time + + + + + + + + boolean + + + + + + + + string + + + + + + + + + + + error + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + top + middle + bottom + from-top + below + + + + + + + + + + + + + + page + page-content + + page-content-bottom + + page-content-top + frame + frame-content + paragraph + paragraph-content + char + line + baseline + text + + + + + + + + + lr-tb + lr + rl-tb + rl + tb-rl + tb + tb-lr + + sideways-rl + sideways-lr + page + + + + + + + + + + + + + + + + + + boolean + short + int + long + double + string + datetime + base64Binary + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + [A-Za-z0-9]{1,8} + + + + + + + + + + + + + + + + + + + non-primitive + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + equal-integer + is-boolean + equal-boolean + equal-use-only-zero + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + no-nulls + nullable + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + simple + + + + + + + none + + + + + onRequest + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + simple + + + + + + + none + + + + + onRequest + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + boolean + short + int + long + double + string + + + + + + + + + + + + + + + + + + + + + + + + + bit + boolean + tinyint + smallint + integer + bigint + float + real + double + numeric + decimal + char + varchar + longvarchar + date + time + timestmp + binary + varbinary + longvarbinary + sqlnull + other + object + distinct + struct + array + blob + clob + ref + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + simple + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + primary + unique + foreign + + + + + + + + + + + cascade + restrict + set-null + no-action + set-default + + + + + + + cascade + restrict + set-null + no-action + set-default + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + parallel + perspective + + + + + + + + + + + + + + + + + + + + + + flat + phong + gouraud + draft + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + simple + + + + + + + onRequest + + + + + + + + + + + new + replace + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + standard + lines + line + curve + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + flat + phong + gouraud + draft + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + parallel + perspective + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + none + segments + rectangle + + + + + + + + + + + + + + + + + normal + path + shape + + + + + + + path + shape + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + top-left + top + top-right + left + center + right + bottom-left + bottom-right + + + + + + auto + left + right + up + down + horizontal + vertical + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + single + double + triple + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + always + screen + printer + none + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + false + + + + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + rect + round + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + -0.5 + 0.5 + + + + + roman + swiss + modern + decorative + script + system + + + + + fixed + variable + + + + + normal + italic + oblique + + + + + normal + small-caps + + + + + normal + bold + 100 + 200 + 300 + 400 + 500 + 600 + 700 + 800 + 900 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + simple + + + + + + + onRequest + + + + + + + + + + + + get + post + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + table + query + command + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + float + + + + + + + + + + + + percentage + + + + + + + + + + + + currency + + + + + + + + + + + + + + + + + date + + + + + + + + + + + + time + + + + + + + + + + + + boolean + + + + + + + + + + + + string + + + + + + + + + + + void + + + + + + + + void + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + horizontal + vertical + + + + + + + + linear + axial + radial + ellipsoid + square + rectangular + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + left + center + right + + + + + horizontal + horizontal-on-odd + horizontal-on-even + + + + + + + + + + + + + + + + + + + + + + + + + + + + avoid-overlap + center + top + top-right + right + bottom-right + bottom + bottom-left + left + top-left + inside + outside + near-origin + + + + + + + + [A-Za-z]{1,8} + + + + + -?([0-9]+(\.[0-9]*)?|\.[0-9]+)((cm)|(mm)|(in)|(pt)|(pc)|(px)) + + + + + continuous + skip-white-space + + + + + none + solid + dotted + dash + long-dash + dot-dash + dot-dot-dash + wave + + + + + none + single + double + + + + + auto + normal + bold + thin + medium + thick + + + + + + + + + + selection + selection-indices + + + + + + + + + + + + + + + + table + query + sql + sql-pass-through + value-list + table-fields + + + + + + + + + + + To avoid inclusion of the complete MathML schema, anything is allowed within a math:math top-level element + + + + + + + + + + + + + + + + + + + + + [^:]+:[^:]+ + + + + + none + current + parent + + + + + 0.0 + + + + + + + + ([0-9]+(\.[0-9]*)?|\.[0-9]+)((cm)|(mm)|(in)|(pt)|(pc)|(px)) + + + + + + ([0-9]+(\.[0-9]*)?|\.[0-9]+)% + + + + + ([0-9]+(\.[0-9]*)?|\.[0-9]+)(px) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + short + long + + + + + + + + + + + + + + + + short + long + + + + + + + + + + + + + + + + + + + + + + + + + + + short + long + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + short + long + + + + + + + + + + + + + + + short + long + + + + + + + + + + + + + + + + + + + + + + + + + + + short + long + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + short + long + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + short + long + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + short + long + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + into-default-style-data-style + into-english-number + keep-text + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1.4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + simple + + + + + + + onRequest + + + + + + + + + + + + + + + + + simple + + + + + + + replace + + + + + onLoad + + + + + + + + + + + + + + + + + + + new + replace + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + float + + + + + + date + + + + + + time + + + + + + boolean + + + + + + string + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text:page-count + text:paragraph-count + text:word-count + text:character-count + text:table-count + text:image-count + text:object-count + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text:reference-ref + text:bookmark-ref + + + + + + + + + + + + + + + + + + + + + + + + simple + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + value + unit + gap + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text:identifier + text:address + text:annote + text:author + text:booktitle + text:chapter + text:edition + text:editor + text:howpublished + text:institution + text:journal + text:month + text:note + text:number + text:organizations + text:pages + text:publisher + text:school + text:series + text:title + text:report-type + text:volume + text:year + text:url + text:custom1 + text:custom2 + text:custom3 + text:custom4 + text:custom5 + text:isbn + text:issn + + + + + + + + + + + + + + + + + + + + + + + + + + + + + -?([0-9]+(\.[0-9]*)?|\.[0-9]+)% + + + + + \([ ]*-?([0-9]+(\.[0-9]*)?|\.[0-9]+)((cm)|(mm)|(in)|(pt)|(pc))([ ]+-?([0-9]+(\.[0-9]*)?|\.[0-9]+)((cm)|(mm)|(in)|(pt)|(pc))){2}[ ]*\) + + + + + -?[0-9]+,-?[0-9]+([ ]+-?[0-9]+,-?[0-9]+)* + + + + + + + + ([0-9]*[1-9][0-9]*(\.[0-9]*)?|0+\.[0-9]*[1-9][0-9]*|\.[0-9]*[1-9][0-9]*)((cm)|(mm)|(in)|(pt)|(pc)|(px)) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + title + outline + subtitle + text + graphic + object + chart + table + orgchart + page + notes + handout + header + footer + date-time + page-number + + + + + + + + + + fixed + current-date + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + none + previous-page + next-page + first-page + last-page + hide + stop + execute + show + verb + fade-out + sound + last-visited-page + + + + + + + + + + + + + + + + + + + + + + + + + simple + + + + + + + embed + + + + + onRequest + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + enabled + disabled + + + + + + + enabled + disabled + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + simple + + + + + + + onRequest + + + + + + new + replace + + + + + + + + + + + + + + + + + + + + + none + from-left + from-top + from-right + from-bottom + from-center + from-upper-left + from-upper-right + from-lower-left + from-lower-right + to-left + to-top + to-right + to-bottom + to-upper-left + to-upper-right + to-lower-right + to-lower-left + path + spiral-inward-left + spiral-inward-right + spiral-outward-left + spiral-outward-right + vertical + horizontal + to-center + clockwise + counter-clockwise + + + + + none + fade + move + stripes + open + close + dissolve + wavyline + random + lines + laser + appear + hide + move-short + checkerboard + rotate + stretch + + + + + slow + medium + fast + + + + + + + + + + [0-9]+\* + + + + + row + column + + + + + + + + + + + + + + + + + + + + + + + simple + + + + + + + onRequest + + + + + + + + + [A-Za-z0-9]{1,8} + + + + + + + + + + + + none + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + -?([0-9]?[0-9](\.[0-9]*)?|100(\.0*)?|\.[0-9]+)% + + + + + + + + + + + + unchecked + checked + unknown + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + no-repeat + repeat + stretch + + + + + + + left + center + right + top + bottom + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + none + + + automatic + + + + named-symbol + + + + square + diamond + arrow-down + arrow-up + arrow-right + arrow-left + bow-tie + hourglass + circle + star + x + plus + asterisk + horizontal-bar + vertical-bar + + + + + + image + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + none + cubic-spline + b-spline + step-start + step-end + step-center-x + step-center-y + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + cuboid + cylinder + cone + pyramid + + + + + + + + + + + + + + + + + use-zero + leave-gap + ignore + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + side-by-side + stagger-even + stagger-odd + + + + + + + + + none + value + percentage + value-and-percentage + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + none + variance + standard-deviation + percentage + error-margin + constant + standard-error + cell-range + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + columns + rows + + + + + + + none + linear + logarithmic + moving-average + exponential + power + polynomial + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + prior + central + averaged-abscissa + + + + + + + + start + end + + + + + + + + near-axis + near-axis-other-side + outside-start + outside-end + + + + + + + at-labels + at-axis + at-labels-and-axis + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + none + solid + dotted + dashed + dot-dashed + + + + + + + + + + + + + + + top + middle + bottom + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + manual + automatic + semi-automatic + + + + + + + none + fade-from-left + fade-from-top + fade-from-right + fade-from-bottom + fade-from-upperleft + fade-from-upperright + fade-from-lowerleft + fade-from-lowerright + move-from-left + move-from-top + move-from-right + move-from-bottom + move-from-upperleft + move-from-upperright + move-from-lowerleft + move-from-lowerright + uncover-to-left + uncover-to-top + uncover-to-right + uncover-to-bottom + uncover-to-upperleft + uncover-to-upperright + uncover-to-lowerleft + uncover-to-lowerright + fade-to-center + fade-from-center + vertical-stripes + horizontal-stripes + clockwise + counterclockwise + open-vertical + open-horizontal + close-vertical + close-horizontal + wavyline-from-left + wavyline-from-top + wavyline-from-right + wavyline-from-bottom + spiralin-left + spiralin-right + spiralout-left + spiralout-right + roll-from-top + roll-from-left + roll-from-right + roll-from-bottom + stretch-from-left + stretch-from-top + stretch-from-right + stretch-from-bottom + vertical-lines + horizontal-lines + dissolve + random + vertical-checkerboard + horizontal-checkerboard + interlocking-horizontal-left + interlocking-horizontal-right + interlocking-vertical-top + interlocking-vertical-bottom + fly-away + open + close + melt + + + + + + + + + + + + + + + + + + + + + + forward + reverse + + + + + + + + + + + + + + + + + visible + hidden + + + + + + + full + border + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + word + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + normal + ultra-condensed + extra-condensed + condensed + semi-condensed + semi-expanded + expanded + extra-expanded + ultra-expanded + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + left + center + right + + + + + + + + + + + + + + + + + + + + + + + + + + + none + solid + bitmap + gradient + hatch + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + no-repeat + repeat + stretch + + scale + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + top-left + top + top-right + left + center + right + bottom-left + bottom + bottom-right + + + + + + + + + horizontal + vertical + + + + + + + + + + + + + + + + + + nonzero + evenodd + + + + + + + + + + + + + + + + + + + + + + + + + + + + none + dash + solid + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + 1 + + + + + + + + + miter + round + bevel + middle + none + + + + + + + butt + square + round + + + + + + + + + + + + none + scroll + alternate + slide + + + + + + + left + right + up + down + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + top + middle + bottom + justify + + + + + + + left + center + right + justify + + + + + + + no-wrap + wrap + + + + + + + + + + + + greyscale + mono + watermark + standard + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + visible + hidden + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + below + above + + + + + + + + + + + + automatic + left-outside + inside + right-outside + + + + + + + automatic + above + below + center + + + + + + + automatic + mm + cm + m + km + pt + pc + inch + ft + mi + + + + + + + + + + + + + + + + + straight-line + angled-line + angled-connector-line + + + + + + + fixed + free + + + + + + + + + + + + + + + + + horizontal + vertical + auto + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + correct + attractive + + + + + + + + + + + + + + + + + enabled + disabled + + + + + + + + + + + + + + + + + + + + + + standard + double-sided + + + + + + + object + flat + sphere + + + + + + + normal + inverse + + + + + + + object + parallel + sphere + + + + + + + object + parallel + sphere + + + + + + + luminance + intensity + color + + + + + + + enabled + disabled + + + + + + + replace + modulate + blend + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + visible + hidden + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + none + + + + content + position + size + + + + + + + + + + left + center + right + from-left + inside + outside + from-inside + + + + + + + + + + + + page + page-content + page-start-margin + page-end-margin + frame + frame-content + frame-start-margin + frame-end-margin + paragraph + paragraph-content + paragraph-start-margin + paragraph-end-margin + char + + + + + + + + + + + + + + + + + none + left + right + parallel + dynamic + run-through + biggest + + + + + + + + + + + + no-limit + + + + + + + + + + + + + full + outside + + + + + + + foreground + background + + + + + + + + + + + + clip + auto-create-new-frame + + + + + + + none + vertical + + + vertical + + + + + vertical + + + + + + + + auto + + + + + + + + iterative + once-concurrent + once-successive + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + content + thumbnail + icon + print-view + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + listtab + space + nothing + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + label-width-and-position + label-alignment + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + all + left + right + mirrored + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + default + + + + + + + + portrait + landscape + + + + + + + + + + + + + + + + + + + + + + + + + + + + headers + grid + annotations + objects + charts + drawings + formulas + zero-values + + + + + + + + + ttb + ltr + + + + + + + + + continue + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + horizontal + vertical + both + none + + + + + + + + + + + + + none + line + both + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + normal + + + + + + + + + + + + + + + + + + + + + + + + + start + center + justify + + + + + + + + + + + + auto + always + + + + + + + + + + + + + + + + + + + + + + + + + + + + auto + page + + + + + + + no-limit + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + none + ideograph-alpha + + + + + + + simple + hanging + + + + + + + normal + strict + + + + + + + top + middle + bottom + auto + baseline + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + above + below + + + + + + + left + center + right + distribute-letter + distribute-space + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text + + + + + + + + paragraph + + + + + + + + + + + section + + + + + + + + ruby + + + + + + + + table + + + + + + + + table-column + + + + + + + + table-row + + + + + + + + table-cell + + + + + + + + + + + + + + + graphic + presentation + + + + + + + + + + + + + + + drawing-page + + + + + + + + chart + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + left + center + right + + + + + + char + + + + + + + + + + + + + + + + + + + + + + + font-color + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + top + middle + bottom + automatic + + + + + + + fix + value-type + + + + + + + + auto + 0 + 0deg + 0rad + 0grad + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + no-wrap + wrap + + + + + + + + none + bottom + top + center + + + + + + + none + hidden-and-protected + + + + protected + formula-hidden + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + left + center + right + margins + + + + + + + + + + + + + + + + + + + + collapsing + separating + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + auto + always + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + none + lowercase + uppercase + capitalize + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + font-color + + + + + + + + + + + + + + + + + + + + super + sub + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + latin + asian + complex + ignore + + + + + + + + normal + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + none + embossed + engraved + + + + + + + + + + + + + + + + + + + + + + + + + + + font-color + + + + + + + + + + + + + + + + + + + + + + + font-color + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + none + letters + lines + + + + + + + + + + + + + + + + + none + + + none + accent + dot + circle + disc + + + above + below + + + + + + + + + + + + + + + + + + + fixed + line-height + + + + + + + + + + + + + + + + + + + + + true + + + none + + + + condition + + + none + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + records + current + page + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + average + count + countnums + max + min + product + stdev + stdevp + sum + var + varp + + + + + + + + + + + + + none + row + column + both + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + from-top + from-bottom + + + + + + + + + + + + + + + + + + + + + + + + + + + row + column + data + hidden + + + + + page + + + + + + + + + + + + + + + auto + average + count + countnums + max + min + product + stdev + stdevp + sum + var + varp + + + + + + + + + + + + + + + + + + + + + + + + + named + + + + + + + + previous + next + + + + + + none + member-difference + member-percentage + member-percentage-difference + running-total + row-percentage + column-percentage + total-percentage + index + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + auto + + + + + + auto + + + + + + + + + + auto + + + + + + auto + + + + + + + + + + + + + seconds + minutes + hours + days + months + quarters + years + + + + + + + + + + + + + + + + + tabular-layout + outline-subtotals-top + outline-subtotals-bottom + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + data + + + + + + + + none + manual + name + + + + + + ascending + descending + + + + + + + + + + + + + + auto + average + count + countnums + max + min + product + stdev + stdevp + sum + var + varp + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + none + row + column + both + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + column + row + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + row + column + table + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + stop + warning + information + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + self + cell-range + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text + number + + + + + + + + + + + + + + text-color + data-style-color + + + + + + window-font-color + + + + + + + background-color + + + + + + transparent + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + from-another-table + to-another-table + from-same-table + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + row + column + table + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + enable + disable + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + column + row + + + + + + + + + + + + + + + + + + + + + + + + + + + simple + + + + + + + onRequest + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + none + + + + print-range + filter + repeat-row + repeat-column + + + + + + + + + + + + date + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + trace-dependents + remove-dependents + trace-precedents + remove-precedents + trace-errors + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + alpha-numeric + integer + double + + + + + + + + + + + + + + + + + + + + text + number + automatic + + + + + + + + ascending + descending + + + + + + + + + + + + + + + + + text + number + automatic + + + + + + + + ascending + descending + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + average + count + countnums + max + min + product + stdev + stdevp + sum + var + varp + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + copy-all + copy-results-only + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + none + unsorted + sort-ascending + + + + + + + + visible + collapse + filter + + + + + + + + + + + + + + + + + + + _self + _blank + _parent + _top + + + + + + + + + + + + + + + + + + + + + + + + + + + + simple + + + + + + + onRequest + + + + + + + + + + + new + replace + + + + + + + + + + + + + + + + + + + + + + + + + + simple + + + + + + + + + + + + + + + + + + + + + + + + + 1 + 2 + 3 + separator + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + article + book + booklet + conference + custom1 + custom2 + custom3 + custom4 + custom5 + email + inbook + incollection + inproceedings + journal + manual + mastersthesis + misc + phdthesis + proceedings + techreport + unpublished + www + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + page + chapter + direction + text + number-no-superior + number-all-superior + number + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + name + number + number-and-name + plain-number-and-name + plain-number + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + full + path + name + name-and-extension + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text + category-and-value + caption + + + + + + + + + + + + + + + + + + + + + + + + + + + address + annote + author + bibliography-type + booktitle + chapter + custom1 + custom2 + custom3 + custom4 + custom5 + edition + editor + howpublished + identifier + institution + isbn + issn + journal + month + note + number + organizations + pages + publisher + report-type + school + series + title + url + volume + year + + + + + + + + + + + + + + + + + + + + name + number + number-and-name + plain-number + plain-number-and-name + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + right + + + + left + + + + + + + + + + + + + + + + + + + + + + + + + + + document + chapter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + left + right + inner + outer + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + footnote + endnote + + + + + + + + + + page + chapter + direction + text + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + document + chapter + page + + + + + + + text + page + section + document + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + previous + next + + + + + + + + + + + + + + + + + + + + + + + + previous + current + next + + + + + + + + + + + + + + + + + text + table + text-box + image + object + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + none + + + + + condition + + + + + + + + + + + + + + + + + + + simple + + + + + + + embed + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + page + chapter + direction + text + category-and-value + caption + value + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + address + annote + author + bibliography-type + booktitle + chapter + custom1 + custom2 + custom3 + custom4 + custom5 + edition + editor + howpublished + identifier + institution + isbn + issn + journal + month + note + number + organizations + pages + publisher + report-type + school + series + title + url + volume + year + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + document + chapter + + + + + + + + + + + + + + + full + path + name + name-and-extension + area + title + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + [A-Za-z][A-Za-z0-9._\-]* + + + + + + + + + + + + + + submit + reset + push + url + + + + + float + time + date + percentage + currency + boolean + string + + + + + + + + \([ ]*-?([0-9]+(\.[0-9]*)?|\.[0-9]+)([ ]+-?([0-9]+(\.[0-9]*)?|\.[0-9]+)){2}[ ]*\) + + + + + top + center + bottom + + + + + + + + + + + + + + + + + + + + + + ([0-9]?[0-9](\.[0-9]*)?|100(\.0*)?|\.[0-9]+)% + + + + + 0 + 1 + + + diff --git a/programs/docnosis/docnosis.css b/programs/docnosis/docnosis.css new file mode 100644 index 000000000..e149b0757 --- /dev/null +++ b/programs/docnosis/docnosis.css @@ -0,0 +1,44 @@ +/* The page follows the theme of the system, as the viewers do. */ +html, body { + margin: 0; + padding: 0; + background: #ffffff; + color: #15141a; + font-family: sans-serif; + line-height: 1.5; +} +h1, p#lead, div#docnosis { + max-width: 50em; + margin: 0 auto; + padding: 0 1.5em; +} +h1 { + padding-top: 1em; + font-size: 1.6em; +} +p#lead { + padding-bottom: 1em; +} +fieldset { + border: 1px dashed rgba(0, 0, 0, 0.3); + border-radius: 4px; + padding: 1em; +} +/* What a diagnosis says: a heading by file, then what each test found. */ +div#docnosis h1 { + padding: 1.5em 0 0; + font-size: 1.2em; +} +div#docnosis img { + vertical-align: middle; + padding-right: 0.5em; +} +@media (prefers-color-scheme: dark) { + html, body { + background: #1c1b22; + color: #fbfbfe; + } + fieldset { + border-color: rgba(255, 255, 255, 0.3); + } +} diff --git a/programs/docnosis/docnosis.js b/programs/docnosis/docnosis.js index c074d9b9f..1b2db629e 100644 --- a/programs/docnosis/docnosis.js +++ b/programs/docnosis/docnosis.js @@ -18,13 +18,13 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ -/*global runtime, Node, window, DOMParser, core, xmldom, NodeFilter, alert, +/*global runtime, Node, window, DOMParser, webodfcore, xmldom, NodeFilter, alert, FileReader*/ -runtime.loadClass("core.Zip"); -runtime.loadClass("core.Base64"); +runtime.loadClass("webodfcore.Zip"); +runtime.loadClass("webodfcore.Base64"); runtime.loadClass("xmldom.RelaxNG"); runtime.loadClass("xmldom.RelaxNGParser"); @@ -175,6 +175,13 @@ function UnpackJob() { return callback(); } var e = input.file.entries[position]; + // A name that ends with a slash is a directory of the zip, that holds + // no bytes: asking for its content answered "not found", which said + // that a package was broken when it was not. + if (e.filename.charAt(e.filename.length - 1) === "/") { + e.directory = true; + return loadZipEntries(input, zip, position + 1, callback); + } zip.load(e.filename, function (err, data) { if (err) { input.errors.unpackErrors.push(err); @@ -187,7 +194,7 @@ function UnpackJob() { }); } function loadZip(input, callback) { - var zip = new core.Zip(input.file.path, function (err, zip) { + var zip = new webodfcore.Zip(input.file.path, function (err, zip) { if (err) { input.errors.unpackErrors.push(err); callback(); @@ -361,13 +368,18 @@ function VersionTestJob() { log.push("content.xml has no version number."); } } + if (vinfo.version === undefined) { + log.push("No version of the standard is declared. One is required" + + " from 1.2 on, so this document is of 1.1 or older, and it is" + + " read against the schema of 1.1."); + } input.version = vinfo.version; callback(); }; } function GetThumbnailJob() { "use strict"; - var base64 = new core.Base64(); + var base64 = new webodfcore.Base64(); this.inputpattern = { file: { entries: [] }, errors: {}, mimetype: "" }; this.outputpattern = { thumbnail: "", errors: { thumbnailErrors: [] } }; this.run = function (input, callback) { @@ -392,31 +404,119 @@ function GetThumbnailJob() { callback(); }; } +/** + * Names the namespaces of a document that the standard does not define, and + * tells how many elements and attributes are written in each. + * + * A document is allowed to hold them: the standard says that a program writes + * its additions under a name of its own, LibreOffice under "loext", and that a + * reader that does not know them ignores them and reads on. Such a document is + * of the standard, and it is what LibreOffice writes by default, but it does + * not fit the schema, which describes what the standard defines and nothing + * else. Saying so apart from the errors is the whole point: without it, a + * document that is perfectly readable everywhere is reported as broken. + */ +function ExtensionsJob() { + "use strict"; + this.inputpattern = { file: { entries: [] } }; + this.outputpattern = { extensions: [], errors: { extensionErrors: [] } }; + /** + * A namespace is of the standard when OASIS defines it, and the standard + * borrows a few from the w3c and from Dublin Core. Anything else is an + * addition of a program, "urn:org:documentfoundation:..." for LibreOffice + * and "urn:openoffice:names:experimental:..." for OpenOffice. + */ + function isOfTheStandard(ns) { + return ns === null + || ns.indexOf("urn:oasis:names:tc:opendocument:") === 0 + || ns.indexOf("http://docs.oasis-open.org/ns/office/") === 0 + || ns.indexOf("http://www.w3.org/") === 0 + || ns.indexOf("http://purl.org/dc/") === 0; + } + function count(node, found) { + var i, attributes; + if (node.nodeType === 1) { + if (!isOfTheStandard(node.namespaceURI)) { + found[node.namespaceURI] = (found[node.namespaceURI] || 0) + 1; + } + attributes = node.attributes; + for (i = 0; i < attributes.length; i += 1) { + if (!isOfTheStandard(attributes[i].namespaceURI) + && attributes[i].namespaceURI !== null) { + found[attributes[i].namespaceURI] = + (found[attributes[i].namespaceURI] || 0) + 1; + } + } + } + for (i = 0; i < node.childNodes.length; i += 1) { + count(node.childNodes[i], found); + } + } + this.run = function (input, callback) { + var found = {}, named = [], ns, i, entries; + input.errors = input.errors || {}; + input.errors.extensionErrors = []; + input.extensions = []; + // A document is read as one file when it is written as one xml, and as + // the entries of its package otherwise. + if (input.file.dom) { + count(input.file.dom.documentElement, found); + } else { + entries = input.file.entries || []; + for (i = 0; i < entries.length; i += 1) { + if (entries[i].dom) { + count(entries[i].dom.documentElement, found); + } + } + } + for (ns in found) { + if (found.hasOwnProperty(ns)) { + named.push(ns + ": " + found[ns] + " elements or attributes"); + } + } + named.sort(); + input.extensions = named; + if (named.length) { + input.errors.extensionErrors.push("This document holds additions" + + " that the standard does not define, which it allows: a" + + " reader that does not know them ignores them. They are why" + + " a document of LibreOffice does not fit the schema. " + + named.join("; ") + "."); + } + callback(input); + }; +} + function RelaxNGJob() { "use strict"; + // The schemas of every version of the standard, as published by OASIS: a + // document names the one it was written to, and it is read against that + // one. The versions of 1.2 and after are the approved ones, the "os", + // where the two that were here before were of a draft, "cos01". var parser = new xmldom.RelaxNGParser(), - validators = {}; + validators = {}, + schemas = { + "urn:oasis:names:tc:opendocument:xmlns:manifest:1.0": { + "1.0": "OpenDocument-manifest-schema-v1.0-os.rng", + "1.1": "OpenDocument-manifest-schema-v1.1.rng", + "1.2": "OpenDocument-v1.2-os-manifest-schema.rng", + "1.3": "OpenDocument-v1.3-manifest-schema.rng", + "1.4": "OpenDocument-v1.4-manifest-schema.rng" + }, + "urn:oasis:names:tc:opendocument:xmlns:office:1.0": { + "1.0": "OpenDocument-schema-v1.0-os.rng", + "1.1": "OpenDocument-schema-v1.1.rng", + "1.2": "OpenDocument-v1.2-os-schema.rng", + "1.3": "OpenDocument-v1.3-schema.rng", + "1.4": "OpenDocument-v1.4-schema.rng" + } + }; this.inputpattern = { file: {dom: null}, version: null }; this.outputpattern = { errors: { relaxngErrors: [] } }; function loadValidator(ns, version, callback) { - var rng; - if (ns === "urn:oasis:names:tc:opendocument:xmlns:manifest:1.0") { - if (version === "1.2") { - rng = "OpenDocument-v1.2-cos01-manifest-schema.rng"; - } else if (version === "1.1") { - rng = "OpenDocument-manifest-schema-v1.1.rng"; - } else if (version === "1.0") { - rng = "OpenDocument-manifest-schema-v1.0-os.rng"; - } - } else if (ns === "urn:oasis:names:tc:opendocument:xmlns:office:1.0") { - if (version === "1.2") { - rng = "OpenDocument-v1.2-cos01-schema.rng"; - } else if (version === "1.1") { - rng = "OpenDocument-schema-v1.1.rng"; - } else if (version === "1.0") { - rng = "OpenDocument-schema-v1.0-os.rng"; - } - } + var rng = schemas.hasOwnProperty(ns) + ? schemas[ns][version] + : undefined; if (rng) { runtime.loadXML(rng, function (err, dom) { var relaxng; @@ -439,6 +539,20 @@ function RelaxNGJob() { callback(null); } } + /** + * The schema a document is read against, by the version it names. A + * document that names none is read against 1.1, as one was only required + * from 1.2 on; the report says so, as the answer depends on it. + */ + function schemaOf(ns, version) { + if (!version && (ns === "urn:oasis:names:tc:opendocument:xmlns:office:1.0" + || ns === "urn:oasis:names:tc:opendocument:xmlns:manifest:1.0")) { + version = "1.1"; + } + return schemas.hasOwnProperty(ns) + ? schemas[ns][version] + : undefined; + } function getValidator(ns, version, callback) { if (ns === "urn:oasis:names:tc:opendocument:xmlns:office:1.0" || ns === "urn:oasis:names:tc:opendocument:xmlns:manifest:1.0") { @@ -452,9 +566,14 @@ function RelaxNGJob() { loadValidator(ns, version, callback); } function validate(log, dom, filename, version, callback) { - var ns = dom.documentElement.namespaceURI; + var ns = dom.documentElement.namespaceURI, + schema = schemaOf(ns, version); getValidator(ns, version, function (relaxng) { if (!relaxng) { + // Nothing to read it against: say so rather than pass in + // silence, which read as "nothing to report". + log.push(filename + ": no schema is here for this version of" + + " the standard, so it was not read against one."); return callback(); } var walker = dom.createTreeWalker(dom.firstChild, 0xFFFFFFFF, { @@ -467,7 +586,8 @@ function RelaxNGJob() { var i; if (err) { for (i = 0; i < err.length; i += 1) { - log.push(filename + ": " + err[i]); + log.push(filename + ", read against " + schema + ": " + + err[i]); } } callback(); @@ -496,7 +616,6 @@ function RelaxNGJob() { this.run = function (input, callback) { input.errors = input.errors || {}; input.errors.relaxngErrors = []; - runtime.log(input.version); if (input.file.dom) { validate(input.errors.relaxngErrors, input.file.dom, input.file.path, input.version, callback); @@ -601,6 +720,7 @@ function JobRunner(datarenderer) { jobtypes.push(new GetThumbnailJob()); jobtypes.push(new VersionTestJob()); jobtypes.push(new ParseXMLJob()); + jobtypes.push(new ExtensionsJob()); jobtypes.push(new RelaxNGJob()); function run() { @@ -781,6 +901,27 @@ function Docnosis(element) { }; } + /** + * Read a document by its address rather than from the one who reads the + * page: "index.html?file=document.odt". A page that is given a document + * this way is one a link may lead to, and it is how the tests run it. + */ + this.diagnose = function (path) { + var div = doc.createElement("div"); + diagnoses.appendChild(div); + runtime.readFile(path, "binary", function (error, data) { + if (error) { + runtime.log(String(error)); + return; + } + jobrunnerdata.push({file: { + path: path, + data: data + }}); + jobrunner.setData(jobrunnerdata); + }); + }; + form = createForm(); element.appendChild(form); element.appendChild(diagnoses); diff --git a/programs/docnosis/index.html b/programs/docnosis/index.html index 21629b0f3..fae225bf9 100644 --- a/programs/docnosis/index.html +++ b/programs/docnosis/index.html @@ -1,24 +1,35 @@ - - + + - + docnosis - - - - - - - + + + + + + + + +

docnosis

+

Tells whether a document keeps to the OpenDocument standard: it + is read against the schema of the version it names, from 1.0 to 1.4, as + published by OASIS. The document is read in this page and goes nowhere: + nothing is uploaded, and no server sees it.

- + diff --git a/programs/editor/CMakeLists.txt b/programs/editor/CMakeLists.txt index 2ac508d23..769f5908d 100644 --- a/programs/editor/CMakeLists.txt +++ b/programs/editor/CMakeLists.txt @@ -53,11 +53,19 @@ SET(SPLIT_SCREEN_EDITOR_ONLY_FILES vcs-locally-modified.png ) -# creates a copy of the compiled webodf.js in the editor build dir +# creates a copy of the compiled webodf.js in the editor build dir. The copy +# is a file of the build and not only a target, so that a build made after the +# library was compiled again carries the library that was compiled and not the +# one of the build before it. +add_custom_command( + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/webodf.js + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/webodf/webodf.js + ${CMAKE_CURRENT_BINARY_DIR}/webodf.js + DEPENDS ${CMAKE_BINARY_DIR}/webodf/webodf.js webodf.js-target +) add_custom_target(editor-webodf.js-target - COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/webodf/webodf.js . + DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/webodf.js ) -add_dependencies(editor-webodf.js-target webodf.js-target) #################### @@ -72,11 +80,11 @@ add_custom_command( # from our sources COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_SOURCE_DIR}/dojo-deps/ dojo-deps/ - # from dojo download (DEPENDS Dojo) - COMMAND ${CMAKE_COMMAND} -E copy_directory ${DOJO}/dojo/ dojo-deps/src/dojo/ - COMMAND ${CMAKE_COMMAND} -E copy_directory ${DOJO}/dijit/ dojo-deps/src/dijit/ - COMMAND ${CMAKE_COMMAND} -E copy_directory ${DOJO}/dojox/ dojo-deps/src/dojox/ - COMMAND ${CMAKE_COMMAND} -E copy_directory ${DOJO}/util/ dojo-deps/src/util/ + # from dojo download (DEPENDS Dojo), one package for each directory + COMMAND ${CMAKE_COMMAND} -E copy_directory ${DOJO_CORE}/ dojo-deps/src/dojo/ + COMMAND ${CMAKE_COMMAND} -E copy_directory ${DOJO_DIJIT}/ dojo-deps/src/dijit/ + COMMAND ${CMAKE_COMMAND} -E copy_directory ${DOJO_DOJOX}/ dojo-deps/src/dojox/ + COMMAND ${CMAKE_COMMAND} -E copy_directory ${DOJO_UTIL}/ dojo-deps/src/util/ # now do the dojo magic... COMMAND ${NODE} ${DOJO_DEPS}/src/dojo/dojo.js load=build @@ -89,6 +97,17 @@ add_custom_command( COMMAND ${CMAKE_COMMAND} -E copy dojo-deps/dist/dojo/dojo.js dojo/ COMMAND ${CMAKE_COMMAND} -E copy_directory dojo-deps/dist/dojo/resources/ dojo/resources/ + # The words of the languages that dojo gathers into one file by language, + # "dojo/nls/dojo_ROOT.js" and its like: the loader asks a server for them + # by their own way, that no cache of the layer answers. + COMMAND ${CMAKE_COMMAND} -E copy_directory dojo-deps/dist/dojo/nls/ dojo/nls/ + + # The way dojo looks an element up, "dojo/selector/lite", that it asks a + # server for the first time a query is made: it is not in the layer, and + # without it the widgets that are made from a query are never made, the + # picker of the style of a paragraph among them. + COMMAND ${CMAKE_COMMAND} -E copy_directory dojo-deps/dist/dojo/selector/ dojo/selector/ + COMMAND ${CMAKE_COMMAND} -E make_directory app/resources/ COMMAND ${CMAKE_COMMAND} -E copy dojo-deps/dist/app/resources/app.css app/resources/ @@ -106,7 +125,14 @@ add_custom_command( COMMAND ${NODE} ${CMAKE_SOURCE_DIR}/webodf/tools/dojoNlsCompile.js dojo-deps/dist de fr nl it eu > dojobundle.js - COMMAND ${NODE} ${CMAKE_CURRENT_SOURCE_DIR}/libexec/mergeIntoDojo.js dojobuild=${DOJO_DEPS}/dist/dojo/dojo.js dojobundle.js > dojo-amalgamation.js + COMMAND ${NODE} ${CMAKE_CURRENT_SOURCE_DIR}/libexec/mergeIntoDojo.js dojobuild=${DOJO_DEPS}/dist/dojo/dojo.js dojobundle.js > dojo-amalgamation-full.js + + # The layer of dojo is built without a minifier, see the profile beside + # this file, and the whole of it is minified here instead, by terser, the + # one the library is minified with: it leaves the modules of dojo alone, + # where the closure compiler of today writes half of them wrong. + COMMAND ${NODE} ${CMAKE_SOURCE_DIR}/node_modules/terser/bin/terser + dojo-amalgamation-full.js -c -m -o dojo-amalgamation.js # cleanup # COMMAND ${CMAKE_COMMAND} -E remove_directory dojo-deps/dist @@ -128,40 +154,47 @@ add_custom_target(more-common-editorfiles-target ## Wodo.TextEditor ################## -set(WODOTEXTEDITORDOC_BUILDDIR ${WODOTEXTEDITORBUILDDIR}/docs) -set(WODOTEXTEDITORCODE_BUILDDIR ${WODOTEXTEDITORBUILDDIR}/wodotexteditor) +set(OPENDOCUMENTTEXTEDITORDOC_BUILDDIR ${OPENDOCUMENTTEXTEDITORBUILDDIR}/docs) +set(OPENDOCUMENTTEXTEDITORCODE_BUILDDIR ${OPENDOCUMENTTEXTEDITORBUILDDIR}/wodotexteditor) +# The output is a file and not the directory itself: msbuild follows files +# alone, and a directory named as an output is one it never sees written, +# which it warns of at each build (MSB8064, MSB8065). add_custom_command( - OUTPUT ${WODOTEXTEDITORBUILDDIR} + OUTPUT ${OPENDOCUMENTTEXTEDITORBUILDDIR}.stamp # clean up output dir - COMMAND ${CMAKE_COMMAND} -E remove_directory ${WODOTEXTEDITORBUILDDIR} - COMMAND ${CMAKE_COMMAND} -E make_directory ${WODOTEXTEDITORBUILDDIR} - COMMAND ${CMAKE_COMMAND} -E make_directory ${WODOTEXTEDITORCODE_BUILDDIR} + COMMAND ${CMAKE_COMMAND} -E remove_directory ${OPENDOCUMENTTEXTEDITORBUILDDIR} + COMMAND ${CMAKE_COMMAND} -E make_directory ${OPENDOCUMENTTEXTEDITORBUILDDIR} + COMMAND ${CMAKE_COMMAND} -E make_directory ${OPENDOCUMENTTEXTEDITORCODE_BUILDDIR} - COMMAND ${CMAKE_COMMAND} -E copy webodf.js ${WODOTEXTEDITORCODE_BUILDDIR} + COMMAND ${CMAKE_COMMAND} -E copy webodf.js ${OPENDOCUMENTTEXTEDITORCODE_BUILDDIR} - COMMAND ${CMAKE_COMMAND} -E copy_directory resources/ ${WODOTEXTEDITORCODE_BUILDDIR}/resources/ - COMMAND ${CMAKE_COMMAND} -E copy_directory translations/ ${WODOTEXTEDITORCODE_BUILDDIR}/translations/ - COMMAND ${CMAKE_COMMAND} -E copy_directory widgets/ ${WODOTEXTEDITORCODE_BUILDDIR}/widgets/ + COMMAND ${CMAKE_COMMAND} -E copy_directory resources/ ${OPENDOCUMENTTEXTEDITORCODE_BUILDDIR}/resources/ + COMMAND ${CMAKE_COMMAND} -E copy_directory translations/ ${OPENDOCUMENTTEXTEDITORCODE_BUILDDIR}/translations/ + COMMAND ${CMAKE_COMMAND} -E copy_directory widgets/ ${OPENDOCUMENTTEXTEDITORCODE_BUILDDIR}/widgets/ # dojo compiled - COMMAND ${CMAKE_COMMAND} -E copy dojo-amalgamation.js ${WODOTEXTEDITORCODE_BUILDDIR} + COMMAND ${CMAKE_COMMAND} -E copy dojo-amalgamation.js ${OPENDOCUMENTTEXTEDITORCODE_BUILDDIR} # more dojo (data) files - COMMAND ${CMAKE_COMMAND} -E make_directory ${WODOTEXTEDITORCODE_BUILDDIR}/dojo - COMMAND ${CMAKE_COMMAND} -E copy_directory dojo/resources ${WODOTEXTEDITORCODE_BUILDDIR}/dojo/resources - COMMAND ${CMAKE_COMMAND} -E copy_directory app/ ${WODOTEXTEDITORCODE_BUILDDIR}/app/ - COMMAND ${CMAKE_COMMAND} -E copy_directory dijit/ ${WODOTEXTEDITORCODE_BUILDDIR}/dijit/ - COMMAND ${CMAKE_COMMAND} -E copy_directory dojox/ ${WODOTEXTEDITORCODE_BUILDDIR}/dojox/ - - COMMAND ${CMAKE_COMMAND} -E copy ${LIBJSLICENSEFILE} ${WODOTEXTEDITORBUILDDIR} - COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/HOWTO-wodotexteditor.md ${WODOTEXTEDITORBUILDDIR}/HOWTO.md - - DEPENDS dojo-amalgamation.js editor-webodf.js-target more-common-editorfiles-target + COMMAND ${CMAKE_COMMAND} -E make_directory ${OPENDOCUMENTTEXTEDITORCODE_BUILDDIR}/dojo + COMMAND ${CMAKE_COMMAND} -E copy_directory dojo/resources ${OPENDOCUMENTTEXTEDITORCODE_BUILDDIR}/dojo/resources + COMMAND ${CMAKE_COMMAND} -E copy_directory dojo/nls ${OPENDOCUMENTTEXTEDITORCODE_BUILDDIR}/dojo/nls + COMMAND ${CMAKE_COMMAND} -E copy_directory dojo/selector ${OPENDOCUMENTTEXTEDITORCODE_BUILDDIR}/dojo/selector + COMMAND ${CMAKE_COMMAND} -E copy_directory app/ ${OPENDOCUMENTTEXTEDITORCODE_BUILDDIR}/app/ + COMMAND ${CMAKE_COMMAND} -E copy_directory dijit/ ${OPENDOCUMENTTEXTEDITORCODE_BUILDDIR}/dijit/ + COMMAND ${CMAKE_COMMAND} -E copy_directory dojox/ ${OPENDOCUMENTTEXTEDITORCODE_BUILDDIR}/dojox/ + + COMMAND ${CMAKE_COMMAND} -E copy ${LIBJSLICENSEFILE} ${OPENDOCUMENTTEXTEDITORBUILDDIR} + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/HOWTO-wodotexteditor.md ${OPENDOCUMENTTEXTEDITORBUILDDIR}/HOWTO.md + COMMAND ${TOUCHFILE} ${OPENDOCUMENTTEXTEDITORBUILDDIR}.stamp + + DEPENDS dojo-amalgamation.js ${CMAKE_CURRENT_BINARY_DIR}/webodf.js + editor-webodf.js-target more-common-editorfiles-target ) foreach(AE_FILE ${ALONE_EDITOR_FILES}) - set(AE_BUILDFILE ${WODOTEXTEDITORCODE_BUILDDIR}/${AE_FILE}) + set(AE_BUILDFILE ${OPENDOCUMENTTEXTEDITORCODE_BUILDDIR}/${AE_FILE}) set(AE_FILE ${CMAKE_CURRENT_SOURCE_DIR}/${AE_FILE}) add_custom_command( OUTPUT ${AE_BUILDFILE} @@ -169,13 +202,13 @@ foreach(AE_FILE ${ALONE_EDITOR_FILES}) ${AE_FILE} ${AE_BUILDFILE} DEPENDS - ${AE_FILE} ${WODOTEXTEDITORBUILDDIR} + ${AE_FILE} ${OPENDOCUMENTTEXTEDITORBUILDDIR}.stamp ) - list(APPEND WODOTEXTEDITOR_BUILDFILES ${AE_BUILDFILE}) + list(APPEND OPENDOCUMENTTEXTEDITOR_BUILDFILES ${AE_BUILDFILE}) endforeach(AE_FILE) foreach(AE_FILE ${ALONE_EDITOR_EXAMPLE_FILES}) - set(AE_BUILDFILE ${WODOTEXTEDITORBUILDDIR}/${AE_FILE}) + set(AE_BUILDFILE ${OPENDOCUMENTTEXTEDITORBUILDDIR}/${AE_FILE}) set(AE_FILE ${CMAKE_CURRENT_SOURCE_DIR}/${AE_FILE}) add_custom_command( OUTPUT ${AE_BUILDFILE} @@ -183,45 +216,48 @@ foreach(AE_FILE ${ALONE_EDITOR_EXAMPLE_FILES}) ${AE_FILE} ${AE_BUILDFILE} DEPENDS - ${AE_FILE} ${WODOTEXTEDITORBUILDDIR} + ${AE_FILE} ${OPENDOCUMENTTEXTEDITORBUILDDIR}.stamp ) - list(APPEND WODOTEXTEDITOR_BUILDFILES ${AE_BUILDFILE}) + list(APPEND OPENDOCUMENTTEXTEDITOR_BUILDFILES ${AE_BUILDFILE}) endforeach(AE_FILE) -add_custom_target(wodotexteditorbuilddir-target DEPENDS ${WODOTEXTEDITORBUILDDIR} ${WODOTEXTEDITOR_BUILDFILES}) +add_custom_target(opendocumenttexteditorbuilddir-target DEPENDS ${OPENDOCUMENTTEXTEDITORBUILDDIR}.stamp ${OPENDOCUMENTTEXTEDITOR_BUILDFILES}) add_custom_command( - OUTPUT ${WODOTEXTEDITORDOC_BUILDDIR}/index.html - COMMAND ${Java_JAVA_EXECUTABLE} ARGS -jar ${JSDOCDIR}/jsrun.jar - ${JSDOCDIR}/app/run.js - -d=${WODOTEXTEDITORDOC_BUILDDIR} - -t=${JSDOCDIR}/templates/jsdoc - wodotexteditor.js - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} - DEPENDS wodotexteditorbuilddir-target JsDoc + OUTPUT ${OPENDOCUMENTTEXTEDITORDOC_BUILDDIR}/index.html + COMMAND ${NODE} ARGS ${CMAKE_SOURCE_DIR}/scripts/doc.js + --output ${OPENDOCUMENTTEXTEDITORDOC_BUILDDIR} + --source ${CMAKE_CURRENT_SOURCE_DIR}/wodotexteditor.js + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + DEPENDS ${NODE} opendocumenttexteditorbuilddir-target + ${CMAKE_SOURCE_DIR}/scripts/doc.js ) -add_custom_target(wodotexteditor-doc DEPENDS ${WODOTEXTEDITORDOC_BUILDDIR}/index.html) +add_custom_target(opendocumenttexteditor-doc DEPENDS ${OPENDOCUMENTTEXTEDITORDOC_BUILDDIR}/index.html) -# package wodotexteditor -set(WODOTEXTEDITOR_ZIP wodotexteditor-${WEBODF_VERSION}.zip) +# package opendocumenttexteditor +set(OPENDOCUMENTTEXTEDITOR_ZIP opendocumenttexteditor-${WEBODF_VERSION}.zip) add_custom_command( - OUTPUT ${WODOTEXTEDITOR_ZIP} + OUTPUT ${OPENDOCUMENTTEXTEDITOR_ZIP} # zip using javascript code running in node.js COMMAND ${NODE} ARGS ${RUNTIMEJS} ${TOOLS_DIR}/zipdir.js - ${WODOTEXTEDITORBUILDDIR} - ${CMAKE_BINARY_DIR}/${WODOTEXTEDITOR_ZIP} + ${OPENDOCUMENTTEXTEDITORBUILDDIR} + ${CMAKE_BINARY_DIR}/${OPENDOCUMENTTEXTEDITOR_ZIP} notopdir - wodotexteditor-${WEBODF_VERSION} + opendocumenttexteditor-${WEBODF_VERSION} DEPENDS - wodotexteditorbuilddir-target - wodotexteditor-doc + opendocumenttexteditorbuilddir-target + opendocumenttexteditor-doc WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} ) -add_custom_target(build-wodotexteditor DEPENDS ${WODOTEXTEDITOR_ZIP}) +add_custom_target(build-opendocumenttexteditor DEPENDS ${OPENDOCUMENTTEXTEDITOR_ZIP}) -add_custom_target(test-wodotexteditor DEPENDS webodf.js-tests) +add_custom_target(test-opendocumenttexteditor DEPENDS webodf.js-tests) -add_custom_target(product-wodotexteditor DEPENDS build-wodotexteditor test-wodotexteditor) +add_custom_target(product-opendocumenttexteditor DEPENDS build-opendocumenttexteditor test-opendocumenttexteditor) +# The zip is named without a path, so it is written where the build was +# started from, which is the root of the build and not this directory. +WEBODF_PRODUCT(product-opendocumenttexteditor + ${CMAKE_BINARY_DIR}/${OPENDOCUMENTTEXTEDITOR_ZIP}) ########################### @@ -229,38 +265,43 @@ add_custom_target(product-wodotexteditor DEPENDS build-wodotexteditor test-wodot ########################### # Needs more thinking still, interface not yet done -set(WODOCOLLABTEXTEDITORCODE_BUILDDIR ${WODOCOLLABTEXTEDITORBUILDDIR}/wodo) +set(OPENDOCUMENTTEXTCOLLABCODE_BUILDDIR ${OPENDOCUMENTTEXTCOLLABBUILDDIR}/wodo) +# The output is a file, as for the editor above. add_custom_command( - OUTPUT ${WODOCOLLABTEXTEDITORBUILDDIR} + OUTPUT ${OPENDOCUMENTTEXTCOLLABBUILDDIR}.stamp # clean up output dir - COMMAND ${CMAKE_COMMAND} -E remove_directory ${WODOCOLLABTEXTEDITORBUILDDIR} - COMMAND ${CMAKE_COMMAND} -E make_directory ${WODOCOLLABTEXTEDITORBUILDDIR} - COMMAND ${CMAKE_COMMAND} -E make_directory ${WODOCOLLABTEXTEDITORCODE_BUILDDIR} + COMMAND ${CMAKE_COMMAND} -E remove_directory ${OPENDOCUMENTTEXTCOLLABBUILDDIR} + COMMAND ${CMAKE_COMMAND} -E make_directory ${OPENDOCUMENTTEXTCOLLABBUILDDIR} + COMMAND ${CMAKE_COMMAND} -E make_directory ${OPENDOCUMENTTEXTCOLLABCODE_BUILDDIR} - COMMAND ${CMAKE_COMMAND} -E copy webodf.js ${WODOCOLLABTEXTEDITORCODE_BUILDDIR} + COMMAND ${CMAKE_COMMAND} -E copy webodf.js ${OPENDOCUMENTTEXTCOLLABCODE_BUILDDIR} - COMMAND ${CMAKE_COMMAND} -E copy_directory resources/ ${WODOCOLLABTEXTEDITORCODE_BUILDDIR}/resources/ - COMMAND ${CMAKE_COMMAND} -E copy_directory translations/ ${WODOCOLLABTEXTEDITORCODE_BUILDDIR}/translations/ - COMMAND ${CMAKE_COMMAND} -E copy_directory widgets/ ${WODOCOLLABTEXTEDITORCODE_BUILDDIR}/widgets/ + COMMAND ${CMAKE_COMMAND} -E copy_directory resources/ ${OPENDOCUMENTTEXTCOLLABCODE_BUILDDIR}/resources/ + COMMAND ${CMAKE_COMMAND} -E copy_directory translations/ ${OPENDOCUMENTTEXTCOLLABCODE_BUILDDIR}/translations/ + COMMAND ${CMAKE_COMMAND} -E copy_directory widgets/ ${OPENDOCUMENTTEXTCOLLABCODE_BUILDDIR}/widgets/ # dojo compiled - COMMAND ${CMAKE_COMMAND} -E copy dojo-amalgamation.js ${WODOCOLLABTEXTEDITORCODE_BUILDDIR} + COMMAND ${CMAKE_COMMAND} -E copy dojo-amalgamation.js ${OPENDOCUMENTTEXTCOLLABCODE_BUILDDIR} # more dojo (data) files - COMMAND ${CMAKE_COMMAND} -E make_directory ${WODOCOLLABTEXTEDITORCODE_BUILDDIR}/dojo - COMMAND ${CMAKE_COMMAND} -E copy_directory dojo/resources ${WODOCOLLABTEXTEDITORCODE_BUILDDIR}/dojo/resources - COMMAND ${CMAKE_COMMAND} -E copy_directory app/ ${WODOCOLLABTEXTEDITORCODE_BUILDDIR}/app/ - COMMAND ${CMAKE_COMMAND} -E copy_directory dijit/ ${WODOCOLLABTEXTEDITORCODE_BUILDDIR}/dijit/ - COMMAND ${CMAKE_COMMAND} -E copy_directory dojox/ ${WODOCOLLABTEXTEDITORCODE_BUILDDIR}/dojox/ - - COMMAND ${CMAKE_COMMAND} -E copy ${LIBJSLICENSEFILE} ${WODOCOLLABTEXTEDITORBUILDDIR} - - DEPENDS dojo-amalgamation.js editor-webodf.js-target more-common-editorfiles-target + COMMAND ${CMAKE_COMMAND} -E make_directory ${OPENDOCUMENTTEXTCOLLABCODE_BUILDDIR}/dojo + COMMAND ${CMAKE_COMMAND} -E copy_directory dojo/resources ${OPENDOCUMENTTEXTCOLLABCODE_BUILDDIR}/dojo/resources + COMMAND ${CMAKE_COMMAND} -E copy_directory dojo/nls ${OPENDOCUMENTTEXTCOLLABCODE_BUILDDIR}/dojo/nls + COMMAND ${CMAKE_COMMAND} -E copy_directory dojo/selector ${OPENDOCUMENTTEXTCOLLABCODE_BUILDDIR}/dojo/selector + COMMAND ${CMAKE_COMMAND} -E copy_directory app/ ${OPENDOCUMENTTEXTCOLLABCODE_BUILDDIR}/app/ + COMMAND ${CMAKE_COMMAND} -E copy_directory dijit/ ${OPENDOCUMENTTEXTCOLLABCODE_BUILDDIR}/dijit/ + COMMAND ${CMAKE_COMMAND} -E copy_directory dojox/ ${OPENDOCUMENTTEXTCOLLABCODE_BUILDDIR}/dojox/ + + COMMAND ${CMAKE_COMMAND} -E copy ${LIBJSLICENSEFILE} ${OPENDOCUMENTTEXTCOLLABBUILDDIR} + COMMAND ${TOUCHFILE} ${OPENDOCUMENTTEXTCOLLABBUILDDIR}.stamp + + DEPENDS dojo-amalgamation.js ${CMAKE_CURRENT_BINARY_DIR}/webodf.js + editor-webodf.js-target more-common-editorfiles-target ) foreach(CE_FILE ${COMMON_COLLAB_FILES} ${COMMON_EDITOR_FILES}) - set(CE_BUILDFILE ${WODOCOLLABTEXTEDITORCODE_BUILDDIR}/${CE_FILE}) + set(CE_BUILDFILE ${OPENDOCUMENTTEXTCOLLABCODE_BUILDDIR}/${CE_FILE}) set(CE_FILE ${CMAKE_CURRENT_SOURCE_DIR}/${CE_FILE}) add_custom_command( OUTPUT ${CE_BUILDFILE} @@ -268,31 +309,33 @@ foreach(CE_FILE ${COMMON_COLLAB_FILES} ${COMMON_EDITOR_FILES}) ${CE_FILE} ${CE_BUILDFILE} DEPENDS - ${CE_FILE} ${WODOCOLLABTEXTEDITORBUILDDIR} + ${CE_FILE} ${OPENDOCUMENTTEXTCOLLABBUILDDIR}.stamp ) - list(APPEND WODOCOLLABTEXTEDITOR_BUILDFILES ${CE_BUILDFILE}) + list(APPEND OPENDOCUMENTTEXTCOLLAB_BUILDFILES ${CE_BUILDFILE}) endforeach(CE_FILE) -add_custom_target(wodocollabtexteditorbuilddir-target DEPENDS ${WODOCOLLABTEXTEDITORBUILDDIR} ${WODOCOLLABTEXTEDITOR_BUILDFILES}) +add_custom_target(opendocumenttextcollabbuilddir-target DEPENDS ${OPENDOCUMENTTEXTCOLLABBUILDDIR}.stamp ${OPENDOCUMENTTEXTCOLLAB_BUILDFILES}) -# package wodotexteditor +# package opendocumenttexteditor add_custom_command( - OUTPUT ${WODOCOLLABTEXTEDITOR_ZIP} + OUTPUT ${OPENDOCUMENTTEXTCOLLAB_ZIP} # zip using javascript code running in node.js COMMAND ${NODE} ARGS ${RUNTIMEJS} ${TOOLS_DIR}/zipdir.js - ${WODOCOLLABTEXTEDITORBUILDDIR} - ${WODOCOLLABTEXTEDITOR_ZIP} + ${OPENDOCUMENTTEXTCOLLABBUILDDIR} + ${OPENDOCUMENTTEXTCOLLAB_ZIP} notopdir - wodocollabtexteditor-${WEBODF_VERSION} + opendocumenttextcollab-${WEBODF_VERSION} DEPENDS - wodocollabtexteditorbuilddir-target + opendocumenttextcollabbuilddir-target WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} ) -add_custom_target(build-wodocollabtexteditor DEPENDS ${WODOCOLLABTEXTEDITOR_ZIP}) +add_custom_target(build-opendocumenttextcollab DEPENDS ${OPENDOCUMENTTEXTCOLLAB_ZIP}) -add_custom_target(test-wodocollabtexteditor DEPENDS webodf.js-tests) +add_custom_target(test-opendocumenttextcollab DEPENDS webodf.js-tests) -add_custom_target(product-wodocollabtexteditor DEPENDS build-wodocollabtexteditor test-wodocollabtexteditor) +add_custom_target(product-opendocumenttextcollab DEPENDS build-opendocumenttextcollab test-opendocumenttextcollab) +# This one is named with the path of the root of the build already. +WEBODF_PRODUCT(product-opendocumenttextcollab ${OPENDOCUMENTTEXTCOLLAB_ZIP}) ################ diff --git a/programs/editor/EditorSession.js b/programs/editor/EditorSession.js index f2e91b695..97eff89ba 100644 --- a/programs/editor/EditorSession.js +++ b/programs/editor/EditorSession.js @@ -18,19 +18,19 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ -/*global runtime, define, document, core, odf, gui, ops*/ +/*global runtime, define, document, webodfcore, odf, gui, ops*/ define("webodf/editor/EditorSession", [ "dojo/text!resources/fonts/fonts.css" ], function (fontsCSS) { // fontsCSS is retrieved as a string, using dojo's text retrieval AMD plugin "use strict"; - runtime.loadClass("core.Async"); - runtime.loadClass("core.DomUtils"); + runtime.loadClass("webodfcore.Async"); + runtime.loadClass("webodfcore.DomUtils"); runtime.loadClass("odf.OdfUtils"); runtime.loadClass("ops.OdtDocument"); runtime.loadClass("ops.OdtStepsTranslator"); @@ -47,14 +47,14 @@ define("webodf/editor/EditorSession", [ runtime.loadClass("gui.TrivialUndoManager"); runtime.loadClass("gui.SvgSelectionView"); runtime.loadClass("gui.SelectionViewManager"); - runtime.loadClass("core.EventNotifier"); + runtime.loadClass("webodfcore.EventNotifier"); runtime.loadClass("gui.ShadowCursor"); runtime.loadClass("gui.CommonConstraints"); /** * Instantiate a new editor session attached to an existing operation session * @constructor - * @implements {core.EventSource} + * @implements {webodfcore.EventSource} * @param {!ops.Session} session * @param {!string} localMemberId * @param {{viewOptions:gui.SessionViewOptions,directParagraphStylingEnabled:boolean,annotationsEnabled:boolean}} config @@ -71,10 +71,10 @@ define("webodf/editor/EditorSession", [ textns = odf.Namespaces.textns, fontStyles = document.createElement('style'), formatting = odtDocument.getFormatting(), - domUtils = core.DomUtils, + domUtils = webodfcore.DomUtils, odfUtils = odf.OdfUtils, odfFieldView, - eventNotifier = new core.EventNotifier([ + eventNotifier = new webodfcore.EventNotifier([ EditorSession.signalMemberAdded, EditorSession.signalMemberUpdated, EditorSession.signalMemberRemoved, @@ -89,7 +89,7 @@ define("webodf/editor/EditorSession", [ shadowCursor = new gui.ShadowCursor(odtDocument), sessionConstraints, /**@const*/ - NEXT = core.StepDirection.NEXT; + NEXT = webodfcore.StepDirection.NEXT; /** * @return {Array.} @@ -584,7 +584,7 @@ define("webodf/editor/EditorSession", [ destroy ]; - core.Async.destroyAll(cleanup, callback); + webodfcore.Async.destroyAll(cleanup, callback); }; function init() { diff --git a/programs/editor/FullWindowZoomHelper.js b/programs/editor/FullWindowZoomHelper.js index b3d88b928..c84777b54 100644 --- a/programs/editor/FullWindowZoomHelper.js +++ b/programs/editor/FullWindowZoomHelper.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global define, document, window */ diff --git a/programs/editor/MemberListView.js b/programs/editor/MemberListView.js index c188e2af9..82e3009e7 100644 --- a/programs/editor/MemberListView.js +++ b/programs/editor/MemberListView.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global define,runtime */ diff --git a/programs/editor/SessionBackend.js b/programs/editor/SessionBackend.js index 15e194299..8ed123462 100644 --- a/programs/editor/SessionBackend.js +++ b/programs/editor/SessionBackend.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global ops*/ diff --git a/programs/editor/SessionList.js b/programs/editor/SessionList.js index 1d9f0e2b9..823cd9dac 100644 --- a/programs/editor/SessionList.js +++ b/programs/editor/SessionList.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global ops, runtime */ diff --git a/programs/editor/SessionListView.js b/programs/editor/SessionListView.js index ff32f029e..891a8b890 100644 --- a/programs/editor/SessionListView.js +++ b/programs/editor/SessionListView.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global Node, define, runtime */ diff --git a/programs/editor/Tools.js b/programs/editor/Tools.js index e5e2e9bab..310446c33 100644 --- a/programs/editor/Tools.js +++ b/programs/editor/Tools.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global window, define, require, document, dijit, dojo, runtime, ops*/ @@ -80,14 +80,22 @@ define("webodf/editor/Tools", [ var tool = null; if (enabled) { - if (config) { - tool = new Tool(config, placeAndStartUpWidget); - } else { - tool = new Tool(placeAndStartUpWidget); + // A tool that fails is a tool that is missing, and not a + // toolbar that is missing: the ones that follow it are + // built all the same. + try { + if (config) { + tool = new Tool(config, placeAndStartUpWidget); + } else { + tool = new Tool(placeAndStartUpWidget); + } + sessionSubscribers.push(tool); + tool.onToolDone = onToolDone; + tool.setEditorSession(editorSession); + } catch (e) { + runtime.log("Could not create a tool: " + e); + tool = null; } - sessionSubscribers.push(tool); - tool.onToolDone = onToolDone; - tool.setEditorSession(editorSession); } return tool; @@ -232,16 +240,22 @@ define("webodf/editor/Tools", [ }); formatDropDownMenu.addChild(paragraphStylesMenuItem); - paragraphStylesDialog = new ParagraphStylesDialog(function (dialog) { - paragraphStylesMenuItem.onClick = function () { - if (editorSession) { - dialog.startup(); - dialog.show(); - } - }; - }); - sessionSubscribers.push(paragraphStylesDialog); - paragraphStylesDialog.onToolDone = onToolDone; + // As for a tool, a dialog that fails takes itself away + // and leaves the rest of the toolbar standing. + try { + paragraphStylesDialog = new ParagraphStylesDialog(function (dialog) { + paragraphStylesMenuItem.onClick = function () { + if (editorSession) { + dialog.startup(); + dialog.show(); + } + }; + }); + sessionSubscribers.push(paragraphStylesDialog); + paragraphStylesDialog.onToolDone = onToolDone; + } catch (e) { + runtime.log("Could not create the dialog of the paragraph styles: " + e); + } formatMenuButton = new DropDownButton({ dropDown: formatDropDownMenu, diff --git a/programs/editor/Translator.js b/programs/editor/Translator.js index 434fb7b92..864ffc2fd 100644 --- a/programs/editor/Translator.js +++ b/programs/editor/Translator.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global define, runtime, XMLHttpRequest */ diff --git a/programs/editor/backend/Server.js b/programs/editor/backend/Server.js index 4a95684bc..cdeeb7f79 100644 --- a/programs/editor/backend/Server.js +++ b/programs/editor/backend/Server.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global ops*/ diff --git a/programs/editor/backend/ServerFactory.js b/programs/editor/backend/ServerFactory.js index 67340de8d..bb4204e83 100644 --- a/programs/editor/backend/ServerFactory.js +++ b/programs/editor/backend/ServerFactory.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global ops, SessionList*/ diff --git a/programs/editor/backend/jsglobal/OperationRouter.js b/programs/editor/backend/jsglobal/OperationRouter.js index 7ee2bb9eb..4b270f59b 100644 --- a/programs/editor/backend/jsglobal/OperationRouter.js +++ b/programs/editor/backend/jsglobal/OperationRouter.js @@ -18,11 +18,11 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ -/*global define, runtime, core, ops*/ +/*global define, runtime, webodfcore, ops*/ define("webodf/editor/backend/jsglobal/OperationRouter", [], function () { "use strict"; @@ -37,8 +37,8 @@ define("webodf/editor/backend/jsglobal/OperationRouter", [], function () { EVENT_HASSESSIONHOSTCONNECTIONCHANGED = "hasSessionHostConnectionChanged"; runtime.loadClass("ops.OperationTransformer"); - runtime.loadClass("core.EventNotifier"); - runtime.loadClass("core.Task"); + runtime.loadClass("webodfcore.EventNotifier"); + runtime.loadClass("webodfcore.Task"); /** * @constructor @@ -46,7 +46,7 @@ define("webodf/editor/backend/jsglobal/OperationRouter", [], function () { */ return function JsGlobalOperationRouter(sessionId, memberId, server, errorCallback) { - var events = new core.EventNotifier([ + var events = new webodfcore.EventNotifier([ ops.OperationRouter.signalProcessingBatchStart, ops.OperationRouter.signalProcessingBatchEnd, EVENT_HASLOCALUNSYNCEDOPERATIONSCHANGED, @@ -339,7 +339,7 @@ define("webodf/editor/backend/jsglobal/OperationRouter", [], function () { }; function init() { - syncTask = core.Task.createTimeoutTask(synchronizeWithServer, 100); + syncTask = webodfcore.Task.createTimeoutTask(synchronizeWithServer, 100); } init(); }; diff --git a/programs/editor/backend/jsglobal/Server.js b/programs/editor/backend/jsglobal/Server.js index c9c51bb23..a57fe8eb4 100644 --- a/programs/editor/backend/jsglobal/Server.js +++ b/programs/editor/backend/jsglobal/Server.js @@ -18,11 +18,11 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ -/*global define, window, runtime, core*/ +/*global define, window, runtime, webodfcore*/ define("webodf/editor/backend/jsglobal/Server", [], function () { "use strict"; diff --git a/programs/editor/backend/jsglobal/ServerFactory.js b/programs/editor/backend/jsglobal/ServerFactory.js index f200f36f5..f88e8ca5b 100644 --- a/programs/editor/backend/jsglobal/ServerFactory.js +++ b/programs/editor/backend/jsglobal/ServerFactory.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global define, document, require, runtime, ops */ diff --git a/programs/editor/backend/jsglobal/SessionBackend.js b/programs/editor/backend/jsglobal/SessionBackend.js index 6e93aedcb..dc86bad42 100644 --- a/programs/editor/backend/jsglobal/SessionBackend.js +++ b/programs/editor/backend/jsglobal/SessionBackend.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global define, ops*/ diff --git a/programs/editor/backend/jsglobal/SessionList.js b/programs/editor/backend/jsglobal/SessionList.js index 4aca7e196..db52741fd 100644 --- a/programs/editor/backend/jsglobal/SessionList.js +++ b/programs/editor/backend/jsglobal/SessionList.js @@ -18,11 +18,11 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ -/*global define, ops, runtime, core */ +/*global define, ops, runtime, webodfcore */ define("webodf/editor/backend/jsglobal/SessionList", [], function () { "use strict"; @@ -36,10 +36,10 @@ define("webodf/editor/backend/jsglobal/SessionList", [], function () { /**@const @type {!number}*/ POLL_FREQUENCY_MS = 500; - runtime.loadClass("core.EventNotifier"); + runtime.loadClass("webodfcore.EventNotifier"); return function JsGlobalSessionList(server) { - var events = new core.EventNotifier([ + var events = new webodfcore.EventNotifier([ EVENT_SESSION_CREATED, EVENT_SESSION_MODIFIED, EVENT_SESSION_REMOVED @@ -116,7 +116,7 @@ define("webodf/editor/backend/jsglobal/SessionList", [], function () { }; function init() { - pullUpdateTask = core.Task.createTimeoutTask(function() { + pullUpdateTask = webodfcore.Task.createTimeoutTask(function() { fetchAndProcessSessionList(); pullUpdateTask.trigger(); }, POLL_FREQUENCY_MS); diff --git a/programs/editor/backend/pullbox/OperationRouter.js b/programs/editor/backend/pullbox/OperationRouter.js index 145368d8c..e14d85dba 100644 --- a/programs/editor/backend/pullbox/OperationRouter.js +++ b/programs/editor/backend/pullbox/OperationRouter.js @@ -18,11 +18,11 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ -/*global define, runtime, core, ops*/ +/*global define, runtime, webodfcore, ops*/ define("webodf/editor/backend/pullbox/OperationRouter", [], function () { "use strict"; @@ -38,7 +38,7 @@ define("webodf/editor/backend/pullbox/OperationRouter", [], function () { EVENT_HASSESSIONHOSTCONNECTIONCHANGED = "hasSessionHostConnectionChanged"; runtime.loadClass("ops.OperationTransformer"); - runtime.loadClass("core.EventNotifier"); + runtime.loadClass("webodfcore.EventNotifier"); /** * route operations in a networked collaborative manner. @@ -84,7 +84,7 @@ define("webodf/editor/backend/pullbox/OperationRouter", [], function () { hasLocalUnsyncedOps = false, /**@type{!boolean}*/ hasSessionHostConnection = true, - eventNotifier = new core.EventNotifier([ + eventNotifier = new webodfcore.EventNotifier([ EVENT_BEFORESAVETOFILE, EVENT_SAVEDTOFILE, EVENT_HASLOCALUNSYNCEDOPERATIONSCHANGED, diff --git a/programs/editor/backend/pullbox/Server.js b/programs/editor/backend/pullbox/Server.js index 8a01a91e5..73c77bee8 100644 --- a/programs/editor/backend/pullbox/Server.js +++ b/programs/editor/backend/pullbox/Server.js @@ -18,16 +18,16 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ -/*global define, runtime, core, XMLHttpRequest*/ +/*global define, runtime, webodfcore, XMLHttpRequest*/ define("webodf/editor/backend/pullbox/Server", [], function () { "use strict"; - runtime.loadClass("core.Base64"); + runtime.loadClass("webodfcore.Base64"); /** * @constructor @@ -39,7 +39,7 @@ define("webodf/editor/backend/pullbox/Server", [], function () { var self = this, token, /**@const*/serverCallTimeout = 10000, - base64 = new core.Base64(); + base64 = new webodfcore.Base64(); args = args || {}; args.url = args.url || "/WSER"; diff --git a/programs/editor/backend/pullbox/ServerFactory.js b/programs/editor/backend/pullbox/ServerFactory.js index 0ceb3a152..9af0bfd4d 100644 --- a/programs/editor/backend/pullbox/ServerFactory.js +++ b/programs/editor/backend/pullbox/ServerFactory.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global define, document, require, runtime, ops */ diff --git a/programs/editor/backend/pullbox/SessionBackend.js b/programs/editor/backend/pullbox/SessionBackend.js index 1ea866f24..d16e3ab76 100644 --- a/programs/editor/backend/pullbox/SessionBackend.js +++ b/programs/editor/backend/pullbox/SessionBackend.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global define, ops*/ diff --git a/programs/editor/backend/pullbox/SessionList.js b/programs/editor/backend/pullbox/SessionList.js index fa8ccd097..e699f3cc4 100644 --- a/programs/editor/backend/pullbox/SessionList.js +++ b/programs/editor/backend/pullbox/SessionList.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global define, ops, runtime */ diff --git a/programs/editor/bellaplugin.js b/programs/editor/bellaplugin.js index 7fc4d0f4e..e520e3bfa 100644 --- a/programs/editor/bellaplugin.js +++ b/programs/editor/bellaplugin.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global window*/ diff --git a/programs/editor/dojo-deps/profiles/app.profile.js b/programs/editor/dojo-deps/profiles/app.profile.js index 210674e9e..4615fa495 100644 --- a/programs/editor/dojo-deps/profiles/app.profile.js +++ b/programs/editor/dojo-deps/profiles/app.profile.js @@ -31,14 +31,16 @@ var profile = { // Excludes tests, demos, and original template files from being included in the built version. mini: true, - // Uses Closure Compiler as the JavaScript minifier. This can also be set to "shrinksafe" to use ShrinkSafe, - // though ShrinkSafe is deprecated and not recommended. - // This option defaults to "" (no compression) if not provided. - optimize: 'closure', + // Nothing is minified here: the closure compiler of today writes the + // modules of dojo wrong, as "define("a b c", f)" where the names of the + // dependencies belong in a list, and half of the modules of a build came + // out that way. The layer is minified by terser once it is whole, see + // "CMakeLists.txt" beside this file. + optimize: '', // We're building layers, so we need to set the minifier to use for those, too. // This defaults to "shrinksafe" if not provided. - layerOptimize: 'closure', + layerOptimize: '', // Strips all calls to console functions within the code. You can also set this to "warn" to strip everything // but console.error, and any other truthy value to strip everything but console.warn and console.error. diff --git a/programs/editor/dojo-deps/src/app/package.json b/programs/editor/dojo-deps/src/app/package.json index c090977fb..3ccd11042 100644 --- a/programs/editor/dojo-deps/src/app/package.json +++ b/programs/editor/dojo-deps/src/app/package.json @@ -3,10 +3,10 @@ "version": "1.0", "main": "main", "dependencies": { - "dojo": "1.8.0", - "dijit": "1.8.0", - "dojox": "1.8.0", - "util": "1.8.0" + "dojo": "1.17.3", + "dijit": "1.17.3", + "dojox": "1.17.3", + "util": "1.17.3" }, "description": "Dojo Boilerplate Example Application. A quick-start application that demonstrates basic usage of the Dojo Toolkit. This package.json file is a CommonJS Packages/1.0 manifest, with a special `dojoBuild` property to point to the build file for this package.", "licenses": [ @@ -23,4 +23,4 @@ "keywords": ["JavaScript", "Dojo", "Boilerplate", "Example", "Howto"], "homepage": "https://github.com/csnover/dojo-boilerplate", "dojoBuild": "package.js" -} \ No newline at end of file +} diff --git a/programs/editor/libexec/mergeIntoDojo.js b/programs/editor/libexec/mergeIntoDojo.js index 42404b1b8..41efafe82 100644 --- a/programs/editor/libexec/mergeIntoDojo.js +++ b/programs/editor/libexec/mergeIntoDojo.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /* @@ -98,8 +98,29 @@ return 1; } - tail = dojo_build.slice(dojo_build.length-1000).toString(); - idx = dojo_build.length - 1000 + tail.lastIndexOf("\n"); + // The modules are put before the line that boots the loader, so that + // nothing is asked for on demand once the page runs. That line was looked + // for as the last line of the file, which held while dojo was minified + // into one line by its own build; it is looked for by what it does + // instead, so that a build that is not minified is read as well. + // The modules are put before the block that boots the loader, which is + // the block that takes what the layer holds in its cache and hands it to + // the loader: a module written after it is a module the loader never + // finds there, and asks a server for. The block was looked for as the + // last line of the file, which held only while dojo minified its layer + // into one line; it is looked for by what it does instead. + tail = dojo_build.toString(); + idx = tail.lastIndexOf("require({cache:{}})"); + if (idx !== -1) { + idx = tail.lastIndexOf("(function(", idx); + } + if (idx === -1) { + idx = tail.lastIndexOf("\n"); + } + if (idx === -1) { + log("the boot of the loader was not found in the build of dojo."); + return 1; + } process.stdout.write(dojo_build.slice(0, idx)); diff --git a/programs/editor/localfileeditor.js b/programs/editor/localfileeditor.js index ed8e4e061..5e83e6667 100644 --- a/programs/editor/localfileeditor.js +++ b/programs/editor/localfileeditor.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global document, window, runtime, FileReader, alert, Uint8Array, Blob, saveAs, Wodo*/ diff --git a/programs/editor/plugins/bella/Actions.js b/programs/editor/plugins/bella/Actions.js index 414cf79ac..26d3cf79d 100644 --- a/programs/editor/plugins/bella/Actions.js +++ b/programs/editor/plugins/bella/Actions.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global define, odf*/ diff --git a/programs/editor/plugins/bella/Bella.js b/programs/editor/plugins/bella/Bella.js index bd142e058..e446bb163 100644 --- a/programs/editor/plugins/bella/Bella.js +++ b/programs/editor/plugins/bella/Bella.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global define, window, ops*/ @@ -39,7 +39,7 @@ define("webodf/editor/plugins/bella/Bella", [ /** * @constructor - * @implements {core.Destroyable} + * @implements {webodfcore.Destroyable} * @param {!Object=} config Configuration options to pass to fuzzer * automatically generated */ @@ -75,7 +75,7 @@ define("webodf/editor/plugins/bella/Bella", [ * Workaround a TrivialUndoManager bug with cursor positions not being * correctly restored. It's done by adding OpMoveCursors for all cursors * with the current cursor state as the very last operations. - * (see also https://github.com/kogmbh/WebODF/issues/903) + * (see also https://github.com/webodf/WebODF/issues/903) * @return {undefined} */ function saveCurrentCursorPositions() { @@ -163,7 +163,7 @@ define("webodf/editor/plugins/bella/Bella", [ } /*jslint emptyblock: false*/ if (scheduledTask) { - webodf.core.Async.destroyAll([scheduledTask.destroy], callback || noop); + webodf.webodfcore.Async.destroyAll([scheduledTask.destroy], callback || noop); if (reporter) { reporter.destroy(); reporter = undefined; @@ -188,7 +188,7 @@ define("webodf/editor/plugins/bella/Bella", [ stop(); state.startedAt = new Date(); reporter = new SimpleStatusReporter(self, config.statusContainer); - scheduledTask = webodf.core.Task.createTimeoutTask(pounceOnComputer, newInterval); + scheduledTask = webodf.webodfcore.Task.createTimeoutTask(pounceOnComputer, newInterval); scheduledTask.triggerImmediate(); }; diff --git a/programs/editor/plugins/bella/BellaControl.js b/programs/editor/plugins/bella/BellaControl.js index 84e6ed58d..90bc860c3 100644 --- a/programs/editor/plugins/bella/BellaControl.js +++ b/programs/editor/plugins/bella/BellaControl.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global define, runtime*/ diff --git a/programs/editor/plugins/bella/CatHerder.js b/programs/editor/plugins/bella/CatHerder.js index d93dc00ed..c65acd488 100644 --- a/programs/editor/plugins/bella/CatHerder.js +++ b/programs/editor/plugins/bella/CatHerder.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global define, window*/ diff --git a/programs/editor/plugins/bella/DocumentValidator.js b/programs/editor/plugins/bella/DocumentValidator.js index 38794a2b8..bf1d735d1 100644 --- a/programs/editor/plugins/bella/DocumentValidator.js +++ b/programs/editor/plugins/bella/DocumentValidator.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global define, window, NodeFilter, ops, runtime*/ diff --git a/programs/editor/plugins/bella/Random.js b/programs/editor/plugins/bella/Random.js index 6cbcff361..74f71412c 100644 --- a/programs/editor/plugins/bella/Random.js +++ b/programs/editor/plugins/bella/Random.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global define*/ diff --git a/programs/editor/plugins/bella/SimpleStatusReporter.js b/programs/editor/plugins/bella/SimpleStatusReporter.js index dba6e6494..f5aed14fb 100644 --- a/programs/editor/plugins/bella/SimpleStatusReporter.js +++ b/programs/editor/plugins/bella/SimpleStatusReporter.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global define, window*/ diff --git a/programs/editor/plugins/bella/WrappedSessionController.js b/programs/editor/plugins/bella/WrappedSessionController.js index 2b0d502c1..7a67b6c80 100644 --- a/programs/editor/plugins/bella/WrappedSessionController.js +++ b/programs/editor/plugins/bella/WrappedSessionController.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global define, window*/ diff --git a/programs/editor/pullboxeditor.js b/programs/editor/pullboxeditor.js index 51978ad45..79eaef211 100644 --- a/programs/editor/pullboxeditor.js +++ b/programs/editor/pullboxeditor.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global require, document, window, history, runtime, Wodo, alert*/ diff --git a/programs/editor/revieweditor.js b/programs/editor/revieweditor.js index f159701ab..01846fbfb 100644 --- a/programs/editor/revieweditor.js +++ b/programs/editor/revieweditor.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global document, window, runtime, FileReader, alert, Uint8Array, Blob, saveAs, Wodo*/ diff --git a/programs/editor/splitscreeneditor.js b/programs/editor/splitscreeneditor.js index 74c9c0e21..3e0e77bb2 100644 --- a/programs/editor/splitscreeneditor.js +++ b/programs/editor/splitscreeneditor.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global require, define, document, window, runtime, alert, Wodo*/ diff --git a/programs/editor/webodf.js b/programs/editor/webodf.js index e8773cb3d..d4d3798cb 100644 --- a/programs/editor/webodf.js +++ b/programs/editor/webodf.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global XMLHttpRequest, runtime, WodoFromSource: true*/ diff --git a/programs/editor/widgets/aboutDialog.js b/programs/editor/widgets/aboutDialog.js index a42463d2a..1a2f07838 100644 --- a/programs/editor/widgets/aboutDialog.js +++ b/programs/editor/widgets/aboutDialog.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global define, dojo, runtime, webodf */ diff --git a/programs/editor/widgets/annotation.js b/programs/editor/widgets/annotation.js index 9f0e4319e..db12f5d2e 100644 --- a/programs/editor/widgets/annotation.js +++ b/programs/editor/widgets/annotation.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global define, require, runtime, gui */ diff --git a/programs/editor/widgets/dialogWidgets/alignmentPane.js b/programs/editor/widgets/dialogWidgets/alignmentPane.js index 80494296c..8cc87d62d 100644 --- a/programs/editor/widgets/dialogWidgets/alignmentPane.js +++ b/programs/editor/widgets/dialogWidgets/alignmentPane.js @@ -18,18 +18,18 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ -/*global runtime,core,define,require,dijit */ +/*global runtime,webodfcore,define,require,dijit */ define("webodf/editor/widgets/dialogWidgets/alignmentPane", [ "webodf/editor/widgets/dialogWidgets/idMangler"], function (IdMangler) { "use strict"; - runtime.loadClass("core.CSSUnits"); + runtime.loadClass("webodfcore.CSSUnits"); var AlignmentPane = function (callback) { var self = this, @@ -48,7 +48,7 @@ function (IdMangler) { this.setStyle = function (styleName) { var style = editorSession.getParagraphStyleAttributes(styleName)['style:paragraph-properties'], - cssUnits = new core.CSSUnits(), + cssUnits = new webodfcore.CSSUnits(), s_topMargin, s_bottomMargin, s_leftMargin, diff --git a/programs/editor/widgets/dialogWidgets/editHyperlinkPane.js b/programs/editor/widgets/dialogWidgets/editHyperlinkPane.js index dbdd5fe25..ac48c75ec 100644 --- a/programs/editor/widgets/dialogWidgets/editHyperlinkPane.js +++ b/programs/editor/widgets/dialogWidgets/editHyperlinkPane.js @@ -18,11 +18,11 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ -/*global runtime,core,define,require,document,dijit */ +/*global runtime,webodfcore,define,require,document,dijit */ define("webodf/editor/widgets/dialogWidgets/editHyperlinkPane", [ "dojo", @@ -32,7 +32,7 @@ define("webodf/editor/widgets/dialogWidgets/editHyperlinkPane", [ function (dojo, ContentPane, IdMangler) { "use strict"; - runtime.loadClass("core.CSSUnits"); + runtime.loadClass("webodfcore.CSSUnits"); var EditHyperlinkPane = function () { var self = this, diff --git a/programs/editor/widgets/dialogWidgets/fontEffectsPane.js b/programs/editor/widgets/dialogWidgets/fontEffectsPane.js index eb049463d..2f2a5e42a 100644 --- a/programs/editor/widgets/dialogWidgets/fontEffectsPane.js +++ b/programs/editor/widgets/dialogWidgets/fontEffectsPane.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global runtime,define,require,document,dijit */ diff --git a/programs/editor/widgets/dialogWidgets/idMangler.js b/programs/editor/widgets/dialogWidgets/idMangler.js index 4118b1468..17d225eb3 100644 --- a/programs/editor/widgets/dialogWidgets/idMangler.js +++ b/programs/editor/widgets/dialogWidgets/idMangler.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global define, document, dojo, dijit */ diff --git a/programs/editor/widgets/editHyperlinks.js b/programs/editor/widgets/editHyperlinks.js index 6622ebae9..aed5328cd 100644 --- a/programs/editor/widgets/editHyperlinks.js +++ b/programs/editor/widgets/editHyperlinks.js @@ -18,11 +18,11 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ -/*global define, require, document, odf, runtime, core, gui */ +/*global define, require, document, odf, runtime, webodfcore, gui */ define("webodf/editor/widgets/editHyperlinks", [ "webodf/editor/EditorSession", @@ -36,7 +36,7 @@ define("webodf/editor/widgets/editHyperlinks", [ runtime.loadClass("odf.OdfUtils"); runtime.loadClass("odf.TextSerializer"); - runtime.loadClass("core.EventSubscriptions"); + runtime.loadClass("webodfcore.EventSubscriptions"); var EditHyperlinks = function (callback) { var self = this, @@ -48,7 +48,7 @@ define("webodf/editor/widgets/editHyperlinks", [ removeHyperlinkButton, odfUtils = odf.OdfUtils, textSerializer = new odf.TextSerializer(), - eventSubscriptions = new core.EventSubscriptions(), + eventSubscriptions = new webodfcore.EventSubscriptions(), dialog; function updateLinkEditorContent() { diff --git a/programs/editor/widgets/fontPicker.js b/programs/editor/widgets/fontPicker.js index d243c6869..2b581a9b5 100644 --- a/programs/editor/widgets/fontPicker.js +++ b/programs/editor/widgets/fontPicker.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global define,require,document */ define("webodf/editor/widgets/fontPicker", [ diff --git a/programs/editor/widgets/imageInserter.js b/programs/editor/widgets/imageInserter.js index ddac52156..43cb44664 100644 --- a/programs/editor/widgets/imageInserter.js +++ b/programs/editor/widgets/imageInserter.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global define, require, document, Image, FileReader, window, runtime, ops, gui */ diff --git a/programs/editor/widgets/paragraphAlignment.js b/programs/editor/widgets/paragraphAlignment.js index d0f35ad9f..4226adde8 100644 --- a/programs/editor/widgets/paragraphAlignment.js +++ b/programs/editor/widgets/paragraphAlignment.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global define, require, ops, gui, runtime */ diff --git a/programs/editor/widgets/paragraphStyles.js b/programs/editor/widgets/paragraphStyles.js index c17b59fd4..305323041 100644 --- a/programs/editor/widgets/paragraphStyles.js +++ b/programs/editor/widgets/paragraphStyles.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global define, require, runtime, ops */ diff --git a/programs/editor/widgets/paragraphStylesDialog.js b/programs/editor/widgets/paragraphStylesDialog.js index 1629a89bf..1319c75e0 100644 --- a/programs/editor/widgets/paragraphStylesDialog.js +++ b/programs/editor/widgets/paragraphStylesDialog.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global define, require, dojo, dijit, runtime */ @@ -239,7 +239,12 @@ function (IdMangler) { dropDown: cloneTooltip, style: "float: right; margin-bottom: 5px;" }); - topBar.addChild(cloneDropDown, 1); + // The widgets are added in the order they are read, without + // a place of their own: dojo asks a container for the child + // that stands at a place, and a container that holds none + // has no such child, which threw and left the editor with a + // toolbar of three buttons. + topBar.addChild(cloneDropDown); deleteButton = new Button({ label: tr("Delete"), @@ -250,7 +255,7 @@ function (IdMangler) { deleteStyle(stylePicker.value()); } }); - topBar.addChild(deleteButton, 2); + topBar.addChild(deleteButton); // Tab Container tabContainer = new TabContainer({ @@ -285,7 +290,7 @@ function (IdMangler) { stylePicker.widget().domNode.style.float = "left"; stylePicker.widget().domNode.style.width = "350px"; stylePicker.widget().domNode.style.marginTop = "5px"; - topBar.addChild(stylePicker.widget(), 0); + topBar.addChild(stylePicker.widget()); stylePicker.onRemove = function () { // The style picker automatically falls back diff --git a/programs/editor/widgets/simpleStyles.js b/programs/editor/widgets/simpleStyles.js index c8b0edfad..5a39c10fd 100644 --- a/programs/editor/widgets/simpleStyles.js +++ b/programs/editor/widgets/simpleStyles.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global define, require, runtime, gui, ops */ diff --git a/programs/editor/widgets/toolbarWidgets/currentStyle.js b/programs/editor/widgets/toolbarWidgets/currentStyle.js index 5503ceeb1..95e2a8d33 100644 --- a/programs/editor/widgets/toolbarWidgets/currentStyle.js +++ b/programs/editor/widgets/toolbarWidgets/currentStyle.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global define, require */ diff --git a/programs/editor/widgets/undoRedoMenu.js b/programs/editor/widgets/undoRedoMenu.js index 9c2f2c398..4c2380dc5 100644 --- a/programs/editor/widgets/undoRedoMenu.js +++ b/programs/editor/widgets/undoRedoMenu.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global define, require, runtime*/ diff --git a/programs/editor/widgets/zoomSlider.js b/programs/editor/widgets/zoomSlider.js index bf37cd8fc..ff8c4806e 100644 --- a/programs/editor/widgets/zoomSlider.js +++ b/programs/editor/widgets/zoomSlider.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global define, require, gui*/ diff --git a/programs/editor/wodocollabpane.css b/programs/editor/wodocollabpane.css index 13068bb5b..605767247 100644 --- a/programs/editor/wodocollabpane.css +++ b/programs/editor/wodocollabpane.css @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ .webodfeditor-members { diff --git a/programs/editor/wodocollabtexteditor.js b/programs/editor/wodocollabtexteditor.js index f744f3447..036fc4084 100644 --- a/programs/editor/wodocollabtexteditor.js +++ b/programs/editor/wodocollabtexteditor.js @@ -18,11 +18,11 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ -/*global Wodo, require, navigator, dojo, runtime, document, window, core, ops, gui, odf*/ +/*global Wodo, require, navigator, dojo, runtime, document, window, webodfcore, ops, gui, odf*/ window.Wodo = window.Wodo || (function () { "use strict"; @@ -186,7 +186,7 @@ window.Wodo = window.Wodo || (function () { pendingMemberId, pendingEditorReadyCallback, // - eventNotifier = new core.EventNotifier([ + eventNotifier = new webodfcore.EventNotifier([ EVENT_UNKNOWNERROR, EVENT_METADATACHANGED, EVENT_BEFORESAVETOFILE, @@ -375,7 +375,7 @@ window.Wodo = window.Wodo || (function () { } /** - * @param {!function(err:?Error, !Uint8Array=):undefined} cb receiving zip as bytearray + * @param {!function(?Error, !Uint8Array=):undefined} cb receiving zip as bytearray * @return {undefined} */ function getDocumentAsByteArray(cb) { @@ -470,7 +470,7 @@ window.Wodo = window.Wodo || (function () { destroyInternal ]); - core.Async.destroyAll(destroyCallbacks, callback); + webodfcore.Async.destroyAll(destroyCallbacks, callback); } ///////////////////////////////////////////////////////////////////// @@ -591,7 +591,7 @@ window.Wodo = window.Wodo || (function () { /** * @param {!string} mainContainerElementId * @param {!Object.} editorOptions - * @param {!function(err:?Error, editor:!CollabTextEditor=):undefined} onEditorCreated + * @param {!function(?Error, !CollabTextEditor=):undefined} onEditorCreated * @return {undefined} */ function createInstance(mainContainerElementId, editorOptions, onEditorCreated) { diff --git a/programs/editor/wodotexteditor.css b/programs/editor/wodotexteditor.css index 0c58b92a1..b9bdb242c 100644 --- a/programs/editor/wodotexteditor.css +++ b/programs/editor/wodotexteditor.css @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ .webodfeditor-editor *:focus { @@ -52,9 +52,6 @@ margin-bottom: 30px; transform-origin: top center; - -webkit-transform-origin: top center; - -moz-transform-origin: top center; - -o-transform-origin: top center; box-shadow: 0px 0px 20px #aaa; border: 1px solid #ccc; @@ -95,10 +92,6 @@ .dijitDialog { border: none !important; box-shadow: 0 1px 50px rgba(0, 0, 0, 0.25) !important; - -moz-box-shadow: 0 1px 50px rgba(0, 0, 0, 0.25) !important; - -o-box-shadow: 0 1px 50px rgba(0, 0, 0, 0.25) !important; - -webkit-box-shadow: 0 1px 50px rgba(0, 0, 0, 0.25) !important; - -ms-box-shadow: 0 1px 50px rgba(0, 0, 0, 0.25) !important; } .dijitDialogTitle { @@ -132,11 +125,7 @@ } .dijitDialogUnderlay { - background: radial-gradient(center, transparent, #333) !important; - background: -webkit-radial-gradient(center, transparent , #333) !important; - background: -moz-radial-gradient(center, transparent , #333) !important; - background: -o-radial-gradient(center, transparent , #333) !important; - background: -ms-radial-gradient(center, transparent , #333) !important; + background: radial-gradient(circle, transparent, #333) !important; } .labeledSelect > table { diff --git a/programs/editor/wodotexteditor.js b/programs/editor/wodotexteditor.js index 8d8a4fc6a..ac11952a3 100644 --- a/programs/editor/wodotexteditor.js +++ b/programs/editor/wodotexteditor.js @@ -18,11 +18,11 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ -/*global window, document, alert, navigator, require, dojo, runtime, core, gui, ops, odf, WodoFromSource*/ +/*global window, document, alert, navigator, require, dojo, runtime, webodfcore, gui, ops, odf, WodoFromSource*/ /** * Namespace of the Wodo.TextEditor @@ -311,7 +311,7 @@ window.Wodo = window.Wodo || (function () { pendingMemberId, pendingEditorReadyCallback, // - eventNotifier = new core.EventNotifier([ + eventNotifier = new webodfcore.EventNotifier([ EVENT_UNKNOWNERROR, EVENT_DOCUMENTMODIFIEDCHANGED, EVENT_METADATACHANGED @@ -472,7 +472,7 @@ window.Wodo = window.Wodo || (function () { /** * @name TextEditor#getDocumentAsByteArray * @function - * @param {!function(err:?Error, file:!Uint8Array=):undefined} callback Called with the current document as ODT file as bytearray, passes an error object in case of error + * @param {!function(?Error, !Uint8Array=):undefined} callback Called with the current document as ODT file as bytearray, passes an error object in case of error * @return {undefined} */ this.getDocumentAsByteArray = function (callback) { @@ -630,11 +630,11 @@ window.Wodo = window.Wodo || (function () { destroyInternal ]); - core.Async.destroyAll(destroyCallbacks, callback); + webodfcore.Async.destroyAll(destroyCallbacks, callback); }; // TODO: - // this.openDocumentFromByteArray = openDocumentFromByteArray; see also https://github.com/kogmbh/WebODF/issues/375 + // this.openDocumentFromByteArray = openDocumentFromByteArray; see also https://github.com/webodf/WebODF/issues/375 // setReadOnly: setReadOnly, /** @@ -809,7 +809,7 @@ window.Wodo = window.Wodo || (function () { * @param [editorOptions.userData] data about the user editing the document * @param [editorOptions.userData.fullName] full name of the user, used for annotations and in the metadata of the document * @param [editorOptions.userData.color="black"] color to use for any user related indicators like cursor or annotations - * @param {!function(err:?Error, editor:!TextEditor=):undefined} onEditorCreated + * @param {!function(?Error, !TextEditor=):undefined} onEditorCreated * @return {undefined} */ function createTextEditor(editorContainerElementId, editorOptions, onEditorCreated) { diff --git a/programs/firefoxextension/CMakeLists.txt b/programs/firefoxextension/CMakeLists.txt deleted file mode 100644 index 29a40da51..000000000 --- a/programs/firefoxextension/CMakeLists.txt +++ /dev/null @@ -1,42 +0,0 @@ -# the files that go into the extension -set(FIREFOXEXTENSIONFILES - bootstrap.js - chrome.manifest - skin/default/icon.png - components/OdfStreamConverter.js -) -configure_file(install.rdf.in ${CMAKE_CURRENT_BINARY_DIR}/install.rdf) - -# write webodf.js into viewer.html -add_custom_command( - OUTPUT ${FIREFOX_EXTENSION_ODFVIEWER} - COMMAND ${CMAKE_COMMAND} -E remove_directory ${FIREFOX_EXTENSION_ODFVIEWER_DIR} - COMMAND ${CMAKE_COMMAND} -E make_directory ${FIREFOX_EXTENSION_ODFVIEWER_DIR}/content/web - COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/install.rdf ${FIREFOX_EXTENSION_ODFVIEWER_DIR} - COMMAND ${NODE} ARGS ${RUNTIMEJS} ${TOOLS_DIR}/replaceByFileContents.js - ${CMAKE_CURRENT_SOURCE_DIR}/content/web/viewer.html.in - ${FIREFOX_EXTENSION_ODFVIEWER_DIR}/content/web/viewer.html - @WEBODF_JS@ ${CMAKE_BINARY_DIR}/webodf/webodf.js - COMMAND ${NODE} ARGS ${RUNTIMEJS} packextension.js - ${FIREFOX_EXTENSION_ODFVIEWER_DIR} - ${FIREFOXEXTENSIONFILES} - COMMAND ${NODE} ARGS ${RUNTIMEJS} ${TOOLS_DIR}/zipdir.js - ${FIREFOX_EXTENSION_ODFVIEWER_DIR} - ${FIREFOX_EXTENSION_ODFVIEWER} - notopdir - DEPENDS ${NODE} - ${TOOLS_DIR}/replaceByFileContents.js - ${TOOLS_DIR}/zipdir.js - packextension.js - ${FIREFOXEXTENSIONFILES} - webodf.js-target - content/web/viewer.html.in - ${CMAKE_CURRENT_BINARY_DIR}/install.rdf - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} -) - -add_custom_target(build-firefoxextension DEPENDS ${FIREFOX_EXTENSION_ODFVIEWER}) - -add_custom_target(test-firefoxextension DEPENDS webodf.js-tests) - -add_custom_target(product-firefoxextension DEPENDS build-firefoxextension test-firefoxextension) diff --git a/programs/firefoxextension/bootstrap.js b/programs/firefoxextension/bootstrap.js deleted file mode 100644 index bb0696e4e..000000000 --- a/programs/firefoxextension/bootstrap.js +++ /dev/null @@ -1,154 +0,0 @@ -/** - * Copyright (C) 2012 KO GmbH - * - * @licstart - * This file is part of WebODF. - * - * WebODF is free software: you can redistribute it and/or modify it - * under the terms of the GNU Affero General Public License (GNU AGPL) - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * WebODF is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with WebODF. If not, see . - * @licend - * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ - */ - -/*global Components, Services, dump, XPCOMUtils, APP_SHUTDOWN, - OdtStreamConverter, FOdtStreamConverter, OttStreamConverter, - OdsStreamConverter, FOdsStreamConverter, OtsStreamConverter, - OdpStreamConverter, FOdpStreamConverter, OtpStreamConverter*/ - -function bootstrap() { - "use strict"; - var RESOURCE_NAME = "webodf.js", - EXT_PREFIX = 'extensions.uriloader@webodf.js', - Ci = Components.interfaces, - Cm = Components.manager, - Cr = Components.results, - Cu = Components.utils, - ComponentFactory, - componentFactories = [], - odfStreamConverterUrl = null; - - Cu["import"]('resource://gre/modules/XPCOMUtils.jsm'); - Cu["import"]('resource://gre/modules/Services.jsm'); - - function log(str) { - dump(str + '\n'); - } - - // Register/unregister a constructor as a component. - ComponentFactory = function () { - var self = this, - targetConstructor = null; - - this.QueryInterface = XPCOMUtils.generateQI([Ci.nsIFactory]); - - this.register = function(tC) { - var proto = tC.prototype, - registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar); - targetConstructor = tC; - - registrar.registerFactory(proto.classID, proto.classDescription, - proto.contractID, self); - }; - - this.unregister = function() { - var proto = targetConstructor.prototype, - registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar); - - registrar.unregisterFactory(proto.classID, self); - targetConstructor = null; - }; - - // nsIFactory - this.createInstance = function(aOuter, iid) { - if (aOuter !== null) { - throw Cr.NS_ERROR_NO_AGGREGATION; - } - return (new targetConstructor()).QueryInterface(iid); - }; - // nsIFactory - this.lockFactory = function(lock) { - // No longer used as of gecko 1.7. - throw Cr.NS_ERROR_NOT_IMPLEMENTED; - }; - }; - - -// As of Firefox 13 bootstrapped add-ons don't support automatic registering and -// unregistering of resource urls and components/contracts. Until then we do -// it programatically. See ManifestDirective ManifestParser.cpp for support. - - function startup(aData, aReason) { - // Setup the resource url. - var ioService = Services.io, - resProt = ioService.getProtocolHandler('resource') - .QueryInterface(Ci.nsIResProtocolHandler), - aliasURI = ioService.newURI('content/', 'UTF-8', aData.resourceURI), - converters, - i; - resProt.setSubstitution(RESOURCE_NAME, aliasURI); - - // Load the component and register it. - odfStreamConverterUrl = aData.resourceURI.spec - + 'components/OdfStreamConverter.js'; - Cu["import"](odfStreamConverterUrl); - converters = [ - OdtStreamConverter, FOdtStreamConverter, OttStreamConverter, - OdsStreamConverter, FOdsStreamConverter, OtsStreamConverter, - OdpStreamConverter, FOdpStreamConverter, OtpStreamConverter]; - - if (componentFactories.length === 0) { - for (i = 0; i < converters.length; i += 1) { - componentFactories.push(new ComponentFactory()); - } - } - for (i = 0; i < componentFactories.length; i += 1) { - componentFactories[i].register(converters[i]); - } - } - function shutdown(aData, aReason) { - if (aReason === APP_SHUTDOWN) { - return; - } - var ioService = Services.io, - resProt = ioService.getProtocolHandler('resource') - .QueryInterface(Ci.nsIResProtocolHandler); - // Remove the resource url. - resProt.setSubstitution(RESOURCE_NAME, null); - // Remove the contract/component. - componentFactories.forEach(function(componentFactory) { - componentFactory.unregister(); - }); - // Unload the converter - Cu.unload(odfStreamConverterUrl); - odfStreamConverterUrl = null; - } - function install(aData, aReason) { - } - function uninstall(aData, aReason) { - } - return { - install: install, - uninstall: uninstall, - startup: startup, - shutdown: shutdown - }; -} - -var b = bootstrap(), - install = b.install, - uninstall = b.uninstall, - startup = b.startup, - shutdown = b.shutdown; - diff --git a/programs/firefoxextension/chrome.manifest b/programs/firefoxextension/chrome.manifest deleted file mode 100644 index e69de29bb..000000000 diff --git a/programs/firefoxextension/components/OdfStreamConverter.js b/programs/firefoxextension/components/OdfStreamConverter.js deleted file mode 100644 index 81515e7f6..000000000 --- a/programs/firefoxextension/components/OdfStreamConverter.js +++ /dev/null @@ -1,435 +0,0 @@ -/** - * Copyright (C) 2012 KO GmbH - * - * @licstart - * This file is part of WebODF. - * - * WebODF is free software: you can redistribute it and/or modify it - * under the terms of the GNU Affero General Public License (GNU AGPL) - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * WebODF is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with WebODF. If not, see . - * @licend - * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ - */ - -/*global Components, XPCOMUtils, Services, NetUtil, dump*/ - -function makeODFStreamConverters() { - "use strict"; - var Cc = Components.classes, - Ci = Components.interfaces, - Cr = Components.results, - Cu = Components.utils, - WEBODF_MESSAGE_EVENT_ID = 'webodf.message'; - - Cu["import"]('resource://gre/modules/XPCOMUtils.jsm'); - Cu["import"]('resource://gre/modules/Services.jsm'); - Cu["import"]('resource://gre/modules/NetUtil.jsm'); - - function log(aMsg) { - var msg = 'OdfStreamConverter.js: ' - + (aMsg.join ? aMsg.join('') : aMsg); - Services.console.logStringMessage(msg); - dump(msg + '\n'); - } - - function getDOMWindow(aChannel) { - var requestor = aChannel.notificationCallbacks, - win = requestor.getInterface(Components.interfaces.nsIDOMWindow); - return win; - } - - function getMimetypeByPath(path) { - var mimetypeLookupTable = [ - {ext: ".odt", type: "application/vnd.oasis.opendocument.text"}, - {ext: ".fodt", type: "application/vnd.oasis.opendocument.text-flat-xml"}, - {ext: ".ott", type: "application/vnd.oasis.opendocument.text-template"}, - {ext: ".odp", type: "application/vnd.oasis.opendocument.presentation"}, - {ext: ".fodp", type: "application/vnd.oasis.opendocument.presentation-flat-xml"}, - {ext: ".otp", type: "application/vnd.oasis.opendocument.presentation-template"}, - {ext: ".ods", type: "application/vnd.oasis.opendocument.spreadsheet"}, - {ext: ".fods", type: "application/vnd.oasis.opendocument.spreadsheet-flat-xml"}, - {ext: ".ots", type: "application/vnd.oasis.opendocument.spreadsheet-template"} - ], - mimetype = "application/vnd.oasis.opendocument.text"; - - mimetypeLookupTable.some(function (mimetypeEntry) { - var isMatch = (path.substr(-mimetypeEntry.ext.length) === mimetypeEntry.ext); - if (isMatch) { - mimetype = mimetypeEntry.type; - } - return isMatch; - }); - - return mimetype; - } - - - // All the priviledged actions. - function ChromeActions(domWindow) { - this.domWindow = domWindow; - } - ChromeActions.prototype = { - download: function (data, sendResponse) { - var originalUrl = data.originalUrl, - // The data may not be downloaded so we need just retry getting - // the odf with the original url. - originalUri = NetUtil.newURI(data.originalUrl), - blobUri = data.blobUrl ? NetUtil.newURI(data.blobUrl) - : originalUri, - extHelperAppSvc = - Cc['@mozilla.org/uriloader/external-helper-app-service;1']. - getService(Ci.nsIExternalHelperAppService), - frontWindow = Cc['@mozilla.org/embedcomp/window-watcher;1']. - getService(Ci.nsIWindowWatcher).activeWindow, - listener; - - listener = { - extListener: null, - onStartRequest: function (aRequest, aContext) { - var mimetype = getMimetypeByPath(aRequest.URI.path); - - this.extListener = extHelperAppSvc.doContent(mimetype, - aRequest, frontWindow, false); - this.extListener.onStartRequest(aRequest, aContext); - }, - onStopRequest: function (aRequest, aContext, aStatusCode) { - if (this.extListener) { - this.extListener.onStopRequest(aRequest, aContext, - aStatusCode); - } - // Notify the content code we're done downloading. - if (sendResponse) { - sendResponse(false); - } - }, - onDataAvailable: function (aRequest, aContext, aInputStream, - aOffset, aCount) { - this.extListener.onDataAvailable(aRequest, aContext, - aInputStream, aOffset, aCount); - } - }; - - NetUtil.asyncFetch(blobUri, function (aInputStream, aResult) { - if (!Components.isSuccessCode(aResult)) { - if (sendResponse) { - sendResponse(true); - } - return; - } - // Create a nsIInputStreamChannel so we can set the url on the - // channel so the filename will be correct. - var channel = Cc['@mozilla.org/network/input-stream-channel;1']. - createInstance(Ci.nsIInputStreamChannel); - channel.setURI(originalUri); - channel.contentStream = aInputStream; - channel.QueryInterface(Ci.nsIChannel); - channel.asyncOpen(listener, null); - }); - } - }; - - // Event listener to trigger chrome privedged code. - function RequestListener(actions) { - this.actions = actions; - } - // Receive an event and synchronously or asynchronously responds. - RequestListener.prototype.receive = function (event) { - var action = event.detail.action, - data = event.detail.data, - actions = this.actions; - if (!(action in actions)) { - log('Unknown action: ' + action); - return; - } - actions[action].call(this.actions, data); - }; - - /* common base for ODT/OTT, ODS/OTS and ODP/OTP stream converters */ - var OdfStreamConverter = { - - // properties required for XPCOM registration, except classID - classDescription: 'webodf.js Component', - contractID: '@mozilla.org/streamconv;1?from=application/vnd.oasis.opendocument.text&to=*/*', - - QueryInterface: XPCOMUtils.generateQI([ - Ci.nsISupports, - Ci.nsIStreamConverter, - Ci.nsIStreamListener, - Ci.nsIRequestObserver - ]), - - /* - * This component works as such: - * 1. asyncConvertData stores the listener - * 2. onStartRequest creates a new channel, streams the viewer and - * cancels the request so webodf.js can do the request - * Since the request is cancelled onDataAvailable should not be called. - * The onStopRequest does nothing. The convert function just returns the - * stream, it's just the synchronous version of asyncConvertData. - */ - - // nsIStreamConverter::convert - convert: function (aFromStream, aFromType, aToType, aCtxt) { - throw Cr.NS_ERROR_NOT_IMPLEMENTED; - }, - - // nsIStreamConverter::asyncConvertData - asyncConvertData: function (aFromType, aToType, aListener, aCtxt) { - // Ignoring HTTP POST requests -- webodf.js has to repeat the - // request. - var skipConversion = false; - try { - var request = aCtxt; - request.QueryInterface(Ci.nsIHttpChannel); - skipConversion = (request.requestMethod !== 'GET'); - } catch (e) { - // Non-HTTP request... continue normally. - } - if (skipConversion) { - throw Cr.NS_ERROR_NOT_IMPLEMENTED; - } - - // Store the listener passed to us - this.listener = aListener; - }, - - // nsIStreamListener::onDataAvailable - onDataAvailable: function (aRequest, aContext, aInputStream, aOffset, - aCount) { - // Do nothing since all the data loading is handled by the viewer. - log('SANITY CHECK: onDataAvailable SHOULD NOT BE CALLED!'); - }, - - // nsIRequestObserver::onStartRequest - onStartRequest: function (aRequest, aContext) { - - // Setup the request so we can use it below. - aRequest.QueryInterface(Ci.nsIChannel); - // Cancel the request so the viewer can handle it. - aRequest.cancel(Cr.NS_BINDING_ABORTED); - - // Create a new channel that is viewer loaded as a resource. - var ioService = Services.io, - channel = ioService.newChannel( - 'resource://webodf.js/web/viewer.html', - null, - null - ), - listener = this.listener, - // Proxy all the request observer calls, when it gets to - // onStopRequest we can get the dom window. - proxy = { - onStartRequest: function () { - listener.onStartRequest.apply(listener, arguments); - }, - onDataAvailable: function () { - listener.onDataAvailable.apply(listener, arguments); - }, - onStopRequest: function () { - var domWindow = getDOMWindow(channel), - requestListener; - // Double check the url is still the correct one. - if (domWindow.document.documentURIObject.equals( - aRequest.URI - )) { - requestListener = new RequestListener( - new ChromeActions(domWindow) - ); - domWindow.addEventListener(WEBODF_MESSAGE_EVENT_ID, - function (event) { - requestListener.receive(event); - }, false, true, true); - } - listener.onStopRequest.apply(listener, arguments); - } - }; - - // Keep the URL the same so the browser sees it as the same. - channel.originalURI = aRequest.URI; - channel.asyncOpen(proxy, aContext); - }, - - // nsIRequestObserver::onStopRequest - onStopRequest: function (aRequest, aContext, aStatusCode) { - // Do nothing. - } - }; - - function OdtStreamConverter() { - } - OdtStreamConverter.prototype = { - // properties required for XPCOM registration: - classID: Components.ID('{43d50559-3e72-435e-9526-482b93f05bf1}'), - classDescription: 'OpenDocument Text converter', - contractID: '@mozilla.org/streamconv;1?from=application/vnd.oasis.opendocument.text&to=*/*', - QueryInterface: OdfStreamConverter.QueryInterface, - convert: OdfStreamConverter.convert, - asyncConvertData: OdfStreamConverter.asyncConvertData, - onDataAvailable: OdfStreamConverter.onDataAvailable, - onStartRequest: OdfStreamConverter.onStartRequest, - onStopRequest: OdfStreamConverter.onStopRequest - }; - - function FOdtStreamConverter() { - } - FOdtStreamConverter.prototype = { - // properties required for XPCOM registration: - classID: Components.ID('{44327fea-721f-4f09-beeb-488179e3b308}'), - classDescription: 'OpenDocument Text Flat converter', - contractID: '@mozilla.org/streamconv;1?from=application/vnd.oasis.opendocument.text-flat-xml&to=*/*', - QueryInterface: OdfStreamConverter.QueryInterface, - convert: OdfStreamConverter.convert, - asyncConvertData: OdfStreamConverter.asyncConvertData, - onDataAvailable: OdfStreamConverter.onDataAvailable, - onStartRequest: OdfStreamConverter.onStartRequest, - onStopRequest: OdfStreamConverter.onStopRequest - }; - - function OttStreamConverter() { - } - OttStreamConverter.prototype = { - // properties required for XPCOM registration: - classID: Components.ID('{0600b310-ae8d-4912-b21e-4d0aeef005af}'), - classDescription: 'OpenDocument Text Template converter', - contractID: '@mozilla.org/streamconv;1?from=application/vnd.oasis.opendocument.text-template&to=*/*', - QueryInterface: OdfStreamConverter.QueryInterface, - convert: OdfStreamConverter.convert, - asyncConvertData: OdfStreamConverter.asyncConvertData, - onDataAvailable: OdfStreamConverter.onDataAvailable, - onStartRequest: OdfStreamConverter.onStartRequest, - onStopRequest: OdfStreamConverter.onStopRequest - }; - - function OdsStreamConverter() { - } - OdsStreamConverter.prototype = { - // properties required for XPCOM registration: - classID: Components.ID('{5f0cd28d-cd7a-4c38-8dd9-72135474b9bc}'), - classDescription: 'OpenDocument Spreadsheet converter', - contractID: '@mozilla.org/streamconv;1?from=application/vnd.oasis.opendocument.spreadsheet&to=*/*', - QueryInterface: OdfStreamConverter.QueryInterface, - convert: OdfStreamConverter.convert, - asyncConvertData: OdfStreamConverter.asyncConvertData, - onDataAvailable: OdfStreamConverter.onDataAvailable, - onStartRequest: OdfStreamConverter.onStartRequest, - onStopRequest: OdfStreamConverter.onStopRequest - }; - - function FOdsStreamConverter() { - } - FOdsStreamConverter.prototype = { - // properties required for XPCOM registration: - classID: Components.ID('{03fb3d00-3274-4573-87ab-542e9649a2dc}'), - classDescription: 'OpenDocument Spreadsheet Flat converter', - contractID: '@mozilla.org/streamconv;1?from=application/vnd.oasis.opendocument.spreadsheet-flat-xml&to=*/*', - QueryInterface: OdfStreamConverter.QueryInterface, - convert: OdfStreamConverter.convert, - asyncConvertData: OdfStreamConverter.asyncConvertData, - onDataAvailable: OdfStreamConverter.onDataAvailable, - onStartRequest: OdfStreamConverter.onStartRequest, - onStopRequest: OdfStreamConverter.onStopRequest - }; - - function OtsStreamConverter() { - } - OtsStreamConverter.prototype = { - // properties required for XPCOM registration: - classID: Components.ID('{4f93193f-4dff-40a0-98e7-f96138798dd0}'), - classDescription: 'OpenDocument Spreadsheet Template converter', - contractID: '@mozilla.org/streamconv;1?from=application/vnd.oasis.opendocument.spreadsheet-template&to=*/*', - QueryInterface: OdfStreamConverter.QueryInterface, - convert: OdfStreamConverter.convert, - asyncConvertData: OdfStreamConverter.asyncConvertData, - onDataAvailable: OdfStreamConverter.onDataAvailable, - onStartRequest: OdfStreamConverter.onStartRequest, - onStopRequest: OdfStreamConverter.onStopRequest - }; - - function OdpStreamConverter() { - } - OdpStreamConverter.prototype = { - // properties required for XPCOM registration: - classID: Components.ID('{84492c35-9c3a-41cc-a512-eb8eb06744a0}'), - classDescription: 'OpenDocument Presentation converter', - contractID: '@mozilla.org/streamconv;1?from=application/vnd.oasis.opendocument.presentation&to=*/*', - QueryInterface: OdfStreamConverter.QueryInterface, - convert: OdfStreamConverter.convert, - asyncConvertData: OdfStreamConverter.asyncConvertData, - onDataAvailable: OdfStreamConverter.onDataAvailable, - onStartRequest: OdfStreamConverter.onStartRequest, - onStopRequest: OdfStreamConverter.onStopRequest - }; - - function FOdpStreamConverter() { - } - FOdpStreamConverter.prototype = { - // properties required for XPCOM registration: - classID: Components.ID('{b1daa2e0-886d-41a6-8101-323be49531d3}'), - classDescription: 'OpenDocument Presentation Flat converter', - contractID: '@mozilla.org/streamconv;1?from=application/vnd.oasis.opendocument.presentation-flat-xml&to=*/*', - QueryInterface: OdfStreamConverter.QueryInterface, - convert: OdfStreamConverter.convert, - asyncConvertData: OdfStreamConverter.asyncConvertData, - onDataAvailable: OdfStreamConverter.onDataAvailable, - onStartRequest: OdfStreamConverter.onStartRequest, - onStopRequest: OdfStreamConverter.onStopRequest - }; - - function OtpStreamConverter() { - } - OtpStreamConverter.prototype = { - // properties required for XPCOM registration: - classID: Components.ID('{17192d8d-fc0a-4f15-b752-23652c781fec}'), - classDescription: 'OpenDocument Presentation Template converter', - contractID: '@mozilla.org/streamconv;1?from=application/vnd.oasis.opendocument.presentation-template&to=*/*', - QueryInterface: OdfStreamConverter.QueryInterface, - convert: OdfStreamConverter.convert, - asyncConvertData: OdfStreamConverter.asyncConvertData, - onDataAvailable: OdfStreamConverter.onDataAvailable, - onStartRequest: OdfStreamConverter.onStartRequest, - onStopRequest: OdfStreamConverter.onStopRequest - }; - - return { - EXPORTED_SYMBOLS: [ - 'OdtStreamConverter', 'FOdtStreamConverter', 'OttStreamConverter', - 'OdsStreamConverter', 'FOdsStreamConverter', 'OtsStreamConverter', - 'OdpStreamConverter', 'FOdpStreamConverter', 'OtpStreamConverter'], - OdtStreamConverter: OdtStreamConverter, - FOdtStreamConverter: FOdtStreamConverter, - OttStreamConverter: OttStreamConverter, - OdsStreamConverter: OdsStreamConverter, - FOdsStreamConverter: FOdsStreamConverter, - OtsStreamConverter: OtsStreamConverter, - OdpStreamConverter: OdpStreamConverter, - FOdpStreamConverter: FOdpStreamConverter, - OtpStreamConverter: OtpStreamConverter, - NSGetFactory: XPCOMUtils.generateNSGetFactory([ - OdtStreamConverter, FOdtStreamConverter, OttStreamConverter, - OdsStreamConverter, FOdsStreamConverter, OtsStreamConverter, - OdpStreamConverter, FOdpStreamConverter, OtpStreamConverter]) - }; -} -var o = makeODFStreamConverters(), - EXPORTED_SYMBOLS = o.EXPORTED_SYMBOLS, - OdtStreamConverter = o.OdtStreamConverter, - FOdtStreamConverter = o.FOdtStreamConverter, - OttStreamConverter = o.OttStreamConverter, - OdsStreamConverter = o.OdsStreamConverter, - FOdsStreamConverter = o.FOdsStreamConverter, - OtsStreamConverter = o.OtsStreamConverter, - OdpStreamConverter = o.OdpStreamConverter, - FOdpStreamConverter = o.FOdpStreamConverter, - OtpStreamConverter = o.OtpStreamConverter, - NSGetFactory = o.NSGetFactory; diff --git a/programs/firefoxextension/content/web/viewer.html.in b/programs/firefoxextension/content/web/viewer.html.in deleted file mode 100644 index c84cb9e25..000000000 --- a/programs/firefoxextension/content/web/viewer.html.in +++ /dev/null @@ -1,117 +0,0 @@ - - - - - - - - - - - -
-
-
- - diff --git a/programs/firefoxextension/install.rdf.in b/programs/firefoxextension/install.rdf.in deleted file mode 100644 index 217944a38..000000000 --- a/programs/firefoxextension/install.rdf.in +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - uriloader@webodf.js - 2 - true - - ODF Viewer - @WEBODF_VERSION@ - Viewer for files in OpenDocument Format (ODF), powered by WebODF, using only web standard technologies. - KO GmbH - http://webodf.org/ - - - - - {ec8030f7-c20a-464f-9b0e-13a3a9e97384} - 6.0 - 15.0a1 - - - - - - - {92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a} - 2.1.* - 2.12a1 - - - - - - - {aa3c5121-dab2-40e2-81ca-7ea25febc110} - 11.0 - 16.0a1 - - - - diff --git a/programs/firefoxextension/packextension.js b/programs/firefoxextension/packextension.js deleted file mode 100644 index 24c258713..000000000 --- a/programs/firefoxextension/packextension.js +++ /dev/null @@ -1,80 +0,0 @@ -/** - * Copyright (C) 2012 KO GmbH - * - * @licstart - * This file is part of WebODF. - * - * WebODF is free software: you can redistribute it and/or modify it - * under the terms of the GNU Affero General Public License (GNU AGPL) - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * WebODF is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with WebODF. If not, see . - * @licend - * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ - */ - -/*global require, runtime, core*/ - -runtime.loadClass("core.Base64"); - -function addFiles(dirname, pos, files, callback) { - "use strict"; - if (pos >= files.length) { - return; - } - var path = require("path"), - fs = require("fs"), - filepath = files[pos]; - runtime.readFile(filepath, "binary", function (err, data) { - var base64, - target = path.join(dirname, filepath), - dir = dirname, - reldir = path.relative(dir, path.dirname(target)); - if (err) { - return callback(err); - } - if (filepath === "content/webodf.js") { - // replace eval() with evil(), since Firefox does not approve of it - base64 = new core.Base64(); - data = base64.convertUTF8ArrayToUTF16String(data); - data = data.replace(new RegExp('eval\\(', 'g'), 'evil('); - data = runtime.byteArrayFromString(data); - } - reldir.split(path.sep).forEach(function (part) { - dir = path.join(dir, part); - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir); - } - }); - fs.writeFile(target, new Buffer(data), {encoding: "binary"}, function (err) { - if (err) { - throw err; - } - addFiles(dirname, pos + 1, files, callback); - }); - }); -} - -var args = arguments, - dirname = args[1], - members = [], - i; -for (i = 2; i < arguments.length; i += 1) { - members.push(arguments[i]); -} - -addFiles(dirname, 0, members, function (err) { - "use strict"; - if (err) { - runtime.log(err); - } -}); diff --git a/programs/firefoxextension/skin/default/icon.png b/programs/firefoxextension/skin/default/icon.png deleted file mode 100644 index 94d7d8da5..000000000 Binary files a/programs/firefoxextension/skin/default/icon.png and /dev/null differ diff --git a/programs/opendocumentviewer-android/CMakeLists.txt b/programs/opendocumentviewer-android/CMakeLists.txt new file mode 100644 index 000000000..66b6d138d --- /dev/null +++ b/programs/opendocumentviewer-android/CMakeLists.txt @@ -0,0 +1,130 @@ +# The viewer of OpenDocument for android is a web view that shows a document +# with WebODF. It replaces the one of programs/cordova, that drove cordova 3.5, +# of 2014, and built with ant, which google dropped in 2015 for gradle. +# +# It is opt-in, as it needs the sdk of android and a jdk, that the build does +# not download. The sdk is found from -DANDROID_SDK, from $ANDROID_HOME or +# $ANDROID_SDK_ROOT, or where a sdk is usually installed, see below. +option(WEBODF_ANDROID "Build the viewer of OpenDocument for android, that needs its sdk" OFF) + +if (WEBODF_ANDROID) + # The sdk is looked for here rather than left to gradle, that only says so + # once it has resolved the plugin and started the tasks of android. It is + # read from -DANDROID_SDK, from the environment, from a "local.properties" + # that is already there, or from the two places a sdk is usually installed + # in, and it is written into the cache: a build is configured once and + # made many times, often from another shell, where the environment of the + # first one is gone. + if (NOT ANDROID_SDK) + if (DEFINED ENV{ANDROID_HOME}) + set(ANDROID_SDK $ENV{ANDROID_HOME}) + elseif (DEFINED ENV{ANDROID_SDK_ROOT}) + set(ANDROID_SDK $ENV{ANDROID_SDK_ROOT}) + elseif (EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/local.properties) + file(STRINGS ${CMAKE_CURRENT_SOURCE_DIR}/local.properties SDK_LINE + REGEX "^sdk\\.dir=") + string(REPLACE "sdk.dir=" "" ANDROID_SDK "${SDK_LINE}") + else () + foreach (dir "$ENV{HOME}/Android/Sdk" "/usr/lib/android-sdk") + if (IS_DIRECTORY ${dir}/platforms) + set(ANDROID_SDK ${dir}) + break() + endif() + endforeach() + endif() + endif() + + if (NOT IS_DIRECTORY "${ANDROID_SDK}/platforms") + message(FATAL_ERROR + "The sdk of android was not found, so the viewer cannot be built." + " Configure with -DANDROID_SDK=/path/to/sdk, or export ANDROID_HOME" + " with the directory it is installed in, in the same command as" + " cmake: \"ANDROID_HOME=/path/to/sdk cmake ...\", with no semicolon" + " between the two, as a variable that is set apart is not passed on." + " The command line tools that install the sdk are at" + " https://developer.android.com/studio#command-tools, and the" + " packages the build needs are \"platforms;android-36\" and" + " \"build-tools;36.0.0\".") + endif() + + set(ANDROID_SDK ${ANDROID_SDK} CACHE PATH "The directory the sdk of android is installed in") + + # Gradle reads the sdk from this file, that is written here rather than by + # hand, so that "make" needs no environment of its own. + file(WRITE ${CMAKE_CURRENT_SOURCE_DIR}/local.properties + "# Written by cmake, see \"CMakeLists.txt\": configure with\n" + "# -DANDROID_SDK=/path/to/sdk to change it.\n" + "sdk.dir=${ANDROID_SDK}\n") + message(STATUS "Android sdk found: ${ANDROID_SDK}") + + # Gradle is downloaded, as the closure compiler and Rhino are: Debian 13 + # packages the 4.4.1 of 2017, where the plugin of android asks for a 9, + # so the one of the distribution is never the right one. The version is + # the oldest line the plugin accepts, 9.5, rather than the newest: the plugin + # says which one it needs when it is applied, and refuses to run below it. + set(GRADLE_VERSION 9.5.1) + ExternalProject_Add( + Gradle + DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} + URL "https://services.gradle.org/distributions/gradle-${GRADLE_VERSION}-bin.zip" + URL_HASH SHA256=bafc141b619ad6350fd975fc903156dd5c151998cc8b058e8c1044ab5f7b031f + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND "" + ) + set(GRADLE ${CMAKE_CURRENT_BINARY_DIR}/Gradle-prefix/src/Gradle/bin/gradle) + + # The two pages that tell what the format is worth hold a text that is + # written once, see "INSERT_TEXT" in the CMakeLists of the root. They are + # written among the assets, as the library is: gradle reads them from there. + foreach (LANGUAGE en fr) + INSERT_TEXT(${CMAKE_CURRENT_SOURCE_DIR}/about.${LANGUAGE}.html.in + ${CMAKE_CURRENT_SOURCE_DIR}/src/main/assets/about.${LANGUAGE}.html + ${LANGUAGE}) + endforeach () + + set(OPENDOCUMENTVIEWER_ANDROID_SOURCES + ${CMAKE_CURRENT_SOURCE_DIR}/build.gradle + ${CMAKE_CURRENT_SOURCE_DIR}/src/main/AndroidManifest.xml + ${CMAKE_CURRENT_SOURCE_DIR}/src/main/java/org/webodf/viewer/ViewerActivity.java + ${CMAKE_CURRENT_SOURCE_DIR}/src/main/assets/index.html + ${CMAKE_CURRENT_SOURCE_DIR}/src/main/assets/index.js + ${CMAKE_CURRENT_SOURCE_DIR}/src/main/assets/index.css + ${CMAKE_CURRENT_SOURCE_DIR}/about.en.html.in + ${CMAKE_CURRENT_SOURCE_DIR}/about.fr.html.in + ${CMAKE_CURRENT_SOURCE_DIR}/src/main/assets/about.css) + + # Two apk are built from the same sources. The one of the release is not + # signed, as only its author may sign it; the one of the debug is, with + # the key gradle writes for that, so that it installs on a device or in an + # emulator without anything else, see "README-Products.md". + macro(BUILD_OPENDOCUMENTVIEWER_ANDROID apk task) + add_custom_command( + OUTPUT ${apk} + # The library is read from the assets, as the page is. + COMMAND ${CMAKE_COMMAND} -E copy + ${CMAKE_BINARY_DIR}/webodf/webodf.js + ${CMAKE_CURRENT_SOURCE_DIR}/src/main/assets/webodf.js + COMMAND ${GRADLE} --project-dir ${CMAKE_CURRENT_SOURCE_DIR} + --project-cache-dir ${CMAKE_CURRENT_BINARY_DIR}/.gradle + -PwebodfVersion=${WEBODF_MANIFEST_VERSION} + -PbuildDir=${CMAKE_CURRENT_BINARY_DIR}/build + ${task} + DEPENDS webodf.js-target Gradle ${OPENDOCUMENTVIEWER_ANDROID_SOURCES} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + ) + endmacro() + + set(OPENDOCUMENTVIEWER_ANDROID_APK + ${CMAKE_CURRENT_BINARY_DIR}/build/outputs/apk/release/opendocumentviewer-android-release-unsigned.apk) + set(OPENDOCUMENTVIEWER_ANDROID_APK_DEBUG + ${CMAKE_CURRENT_BINARY_DIR}/build/outputs/apk/debug/opendocumentviewer-android-debug.apk) + + BUILD_OPENDOCUMENTVIEWER_ANDROID(${OPENDOCUMENTVIEWER_ANDROID_APK} assembleRelease) + BUILD_OPENDOCUMENTVIEWER_ANDROID(${OPENDOCUMENTVIEWER_ANDROID_APK_DEBUG} assembleDebug) + + add_custom_target(product-opendocumentviewer-android DEPENDS ${OPENDOCUMENTVIEWER_ANDROID_APK}) + WEBODF_PRODUCT(product-opendocumentviewer-android + ${OPENDOCUMENTVIEWER_ANDROID_APK}) + add_custom_target(opendocumentviewer-android-debug DEPENDS ${OPENDOCUMENTVIEWER_ANDROID_APK_DEBUG}) +endif () diff --git a/programs/opendocumentviewer-android/about.en.html.in b/programs/opendocumentviewer-android/about.en.html.in new file mode 100644 index 000000000..0f4352ae0 --- /dev/null +++ b/programs/opendocumentviewer-android/about.en.html.in @@ -0,0 +1,30 @@ + + + + + + OpenDocument Viewer + + + +
+

OpenDocument Viewer

+

It reads the documents of the OpenDocument standard, the ones + LibreOffice and every other office suite write, from a mail or a file + manager, without an office suite.

+

It is offered for the nine types of OpenDocument, and the system is what + reads the file and hands it over: the viewer holds no permission to reach + the storage, and it reaches no network at all.

+ +@FORMAT_TEXT@ + +

Elsewhere

+

The same viewer is an add-on of Firefox, of Chrome and of Thunderbird, + that shows those documents in the browser or in a message instead of + downloading them.

+

It is built on WebODF, a library that reads the format in JavaScript, + and it is free software, under the AGPL 3.

+

Open a document

+
+ + diff --git a/programs/opendocumentviewer-android/about.fr.html.in b/programs/opendocumentviewer-android/about.fr.html.in new file mode 100644 index 000000000..aada49af3 --- /dev/null +++ b/programs/opendocumentviewer-android/about.fr.html.in @@ -0,0 +1,30 @@ + + + + + + OpenDocument Viewer + + + +
+

Le lecteur OpenDocument

+

Il lit les documents au standard OpenDocument créés par + LibreOffice et toutes les suites bureautiques, depuis un courriel ou un + gestionnaire de fichiers, sans suite bureautique.

+

Il se déclare pour les neuf types d’OpenDocument, et c’est le système qui + lit le fichier et le lui remet : le lecteur ne détient donc aucune + permission d’accès au stockage, et n’accède à aucun réseau.

+ +@FORMAT_TEXT@ + +

Ailleurs

+

Le même lecteur existe en extension pour Firefox, pour Chrome et pour + Thunderbird, qui affiche ces documents dans le navigateur ou dans un + message au lieu de les télécharger.

+

Il repose sur WebODF, une bibliothèque qui lit le format en JavaScript, + et c’est un logiciel libre, sous licence AGPL 3.

+

Ouvrir un document

+
+ + diff --git a/programs/opendocumentviewer-android/build.gradle b/programs/opendocumentviewer-android/build.gradle new file mode 100644 index 000000000..502c1e0d4 --- /dev/null +++ b/programs/opendocumentviewer-android/build.gradle @@ -0,0 +1,41 @@ +// The viewer is a web view that shows a document with WebODF: no framework, +// no plugin, only the library and a page, both read from the assets. The +// library is copied there by cmake, see CMakeLists.txt. +plugins { + id 'com.android.application' version '9.3.1' +} + +android { + namespace = 'org.webodf.viewer' + compileSdk = 36 + + defaultConfig { + applicationId = 'org.webodf.viewer' + // 21 is the release the web view began to be updated apart from the + // system in, so it holds a recent engine from there on, and the one + // call the viewer intercepts its requests with was added there too. + minSdk = 21 + targetSdk = 36 + versionCode = 1 + versionName = project.findProperty('webodfVersion') ?: '0.0.0' + } + + buildTypes { + release { + minifyEnabled = false + } + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } +} + +// javac only says that a deprecated api is used, never which one, unless it +// is asked to. +tasks.withType(JavaCompile).configureEach { + options.compilerArgs << '-Xlint:deprecation' +} + +// No dependency at all: the viewer serves its own files, see ViewerActivity. diff --git a/programs/opendocumentviewer-android/gradle.properties b/programs/opendocumentviewer-android/gradle.properties new file mode 100644 index 000000000..c33552957 --- /dev/null +++ b/programs/opendocumentviewer-android/gradle.properties @@ -0,0 +1,2 @@ +android.useAndroidX=true +org.gradle.jvmargs=-Xmx2g diff --git a/programs/opendocumentviewer-android/settings.gradle b/programs/opendocumentviewer-android/settings.gradle new file mode 100644 index 000000000..d7d1496cf --- /dev/null +++ b/programs/opendocumentviewer-android/settings.gradle @@ -0,0 +1,14 @@ +pluginManagement { + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} +dependencyResolutionManagement { + repositories { + google() + mavenCentral() + } +} +rootProject.name = 'opendocumentviewer-android' diff --git a/programs/opendocumentviewer-android/src/main/AndroidManifest.xml b/programs/opendocumentviewer-android/src/main/AndroidManifest.xml new file mode 100644 index 000000000..20a67b479 --- /dev/null +++ b/programs/opendocumentviewer-android/src/main/AndroidManifest.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/programs/opendocumentviewer-android/src/main/assets/about.css b/programs/opendocumentviewer-android/src/main/assets/about.css new file mode 100644 index 000000000..9924b36c2 --- /dev/null +++ b/programs/opendocumentviewer-android/src/main/assets/about.css @@ -0,0 +1,74 @@ +/* The page follows the theme of the system, as the viewer does. A link is + opened in a browser, which the activity hands over, see "ViewerActivity". */ +html, body { + margin: 0; + padding: 0; + background: #ffffff; + color: #15141a; + font-family: sans-serif; + line-height: 1.5; +} +main { + max-width: 40em; + margin: 0 auto; + padding: 2em 1.5em 4em; +} +h1 { + font-size: 1.6em; +} +h2 { + margin-top: 2em; + font-size: 1.15em; +} +#lead { + font-size: 1.1em; +} +figure { + margin: 1em 0; +} +figure > img { + max-width: 100%; + border: 1px solid rgba(0, 0, 0, 0.2); + border-radius: 4px; +} +/* A caption without its image says nothing, so it goes with it. */ +figure > img[hidden] { + display: none; +} +figure > img[hidden] + figcaption { + display: none; +} +figcaption { + padding-top: 0.4em; + font-size: 0.9em; + opacity: 0.75; +} +a { + color: #0060df; +} +@media (prefers-color-scheme: dark) { + html, body { + background: #1c1b22; + color: #fbfbfe; + } + figure > img { + border-color: rgba(255, 255, 255, 0.2); + } + a { + color: #00ddff; + } +} +/* The way back to a document: the page that tells what the viewer is is + reached with no document open, so it carries the same offer as the empty + page, that the activity answers by opening the picker. */ +p.ways { + margin-top: 2.5em; + padding-top: 1.2em; + border-top: 1px solid rgba(0, 0, 0, 0.15); + font-size: 1.05em; +} +@media (prefers-color-scheme: dark) { + p.ways { + border-top-color: rgba(255, 255, 255, 0.15); + } +} diff --git a/programs/opendocumentviewer-android/src/main/assets/index.css b/programs/opendocumentviewer-android/src/main/assets/index.css new file mode 100644 index 000000000..4a623dee6 --- /dev/null +++ b/programs/opendocumentviewer-android/src/main/assets/index.css @@ -0,0 +1,79 @@ +html { + /* The page of a document is white paper, so it is declared light whatever + the theme is: chromium then leaves its colours alone instead of darkening + them as it paints, and the ground below still follows the theme, from the + rules of this file. */ + color-scheme: light; +} +/* Every rule of this page names "html > body" rather than "body": an element of + a document carries the local name "body", "office:body", which a selector of + one name reaches as well, so the colour, the font and the alignment of the + page were inherited by the whole document. */ +html, html > body, div { + padding: 0; + margin: 0; + border: 0; +} +html, html > body { + height: 100%; + /* The ground sets the page apart, as a reader of pdf does, and follows the + theme of the system. The page itself is white, from the style sheet of + the library. */ + background: #d4d4d7; + color: #15141a; + font-family: sans-serif; + /* The library turns the element it draws in into an inline block, so the + page is put in the middle by this alignment. */ + text-align: center; +} +/* Fills the screen as long as no document is shown, so a touch anywhere asks + for one. */ +a#empty > span > strong { + display: block; + padding-bottom: 0.8em; + font-size: 1.2em; +} +a#empty > span > small { + display: block; + max-width: 24em; + line-height: 1.5; + opacity: 0.85; +} +a#empty { + display: flex; + align-items: center; + justify-content: center; + box-sizing: border-box; + height: 100%; + padding: 2em; + /* The words of the empty screen are read on the ground of the theme, so + they take the colour of the theme and not one of their own. */ + color: inherit; + text-decoration: none; +} +div#odf { + /* Keeps the page off the edges of a phone. */ + padding: 0.6em; + /* The page is white paper, from the style sheet of the library, whatever + the theme is, so the ink is dark in both themes: a document that names no + colour for a text would take the one of the ground below, which is white + under a dark theme, on white paper. */ + color: #15141a; +} +/* A way to the page that tells what the viewer is, kept out of the way of the + touch that asks for a document. */ +a#about { + position: fixed; + left: 0; + right: 0; + bottom: 1.2em; + color: inherit; + font-size: 0.9em; + opacity: 0.75; +} +@media (prefers-color-scheme: dark) { + html, html > body { + background: #2a2a2e; + color: #fbfbfe; + } +} diff --git a/programs/opendocumentviewer-android/src/main/assets/index.html b/programs/opendocumentviewer-android/src/main/assets/index.html new file mode 100644 index 000000000..9c312f6f4 --- /dev/null +++ b/programs/opendocumentviewer-android/src/main/assets/index.html @@ -0,0 +1,34 @@ + + + + + + OpenDocument Viewer + + + + + + + + Touch to open a + document + Text (.odt), spreadsheets (.ods) and + presentations (.odp) of LibreOffice and of every other office suite, in + the OpenDocument format: the first format of office documents approved as + an international standard, and the only one that is shareable, lasting + and independent of any company. + + About + this viewer +
+ + diff --git a/programs/opendocumentviewer-android/src/main/assets/index.js b/programs/opendocumentviewer-android/src/main/assets/index.js new file mode 100644 index 000000000..c283e5141 --- /dev/null +++ b/programs/opendocumentviewer-android/src/main/assets/index.js @@ -0,0 +1,48 @@ +/*global document, window, odf, URLSearchParams*/ + +(function () { + "use strict"; + + var url = new URLSearchParams(window.location.search).get("file"), + container = document.getElementById("odf"), + about = document.getElementById("about"), + translated, + i, + canvas; + + // A page is wider than the screen of a phone, so it is scaled down to the + // width that is left once the padding is taken off; on a tablet, where it + // holds, it is drawn at its own size, as fitSmart never scales up. The + // width is read on the document, not on the container, that the page it + // holds widens. The scale is set again when the screen is turned. + function fit() { + var style = window.getComputedStyle(container); + canvas.fitSmart(document.documentElement.clientWidth + - parseFloat(style.paddingLeft) + - parseFloat(style.paddingRight)); + } + + // The words of the reader are written in the page, in the attribute of + // the language: this file holds no sentence of its own. + if (String(window.navigator.language).indexOf("fr") === 0) { + translated = document.querySelectorAll("[data-fr]"); + for (i = 0; i < translated.length; i += 1) { + translated[i].firstChild.nodeValue = + translated[i].getAttribute("data-fr"); + } + about.href = "/about.fr.html"; + } + + if (!url) { + return; + } + document.getElementById("empty").style.display = "none"; + about.style.display = "none"; + canvas = new odf.OdfCanvas(container); + // A text is drawn over pages, as it is printed, which the library does + // not do on its own. + canvas.setPaginated(true); + canvas.addListener("statereadychange", fit); + window.addEventListener("resize", fit); + canvas.load(url); +}()); diff --git a/programs/opendocumentviewer-android/src/main/fastlane/metadata/android/en-US/changelogs/1.txt b/programs/opendocumentviewer-android/src/main/fastlane/metadata/android/en-US/changelogs/1.txt new file mode 100644 index 000000000..b82b541ea --- /dev/null +++ b/programs/opendocumentviewer-android/src/main/fastlane/metadata/android/en-US/changelogs/1.txt @@ -0,0 +1,3 @@ +First release of the viewer for Android: it shows the texts, the spreadsheets +and the presentations of the OpenDocument format, from a file manager, a mail +or the picker of the system. diff --git a/programs/opendocumentviewer-android/src/main/fastlane/metadata/android/en-US/full_description.txt b/programs/opendocumentviewer-android/src/main/fastlane/metadata/android/en-US/full_description.txt new file mode 100644 index 000000000..cd3ddb5a9 --- /dev/null +++ b/programs/opendocumentviewer-android/src/main/fastlane/metadata/android/en-US/full_description.txt @@ -0,0 +1,27 @@ +OpenDocument Viewer shows the documents of LibreOffice and OpenOffice, the +texts, the spreadsheets and the presentations, so that a document a phone +receives is read at once, without an office suite. + +It registers for the nine types of the format, so the system offers it when one +is opened from a mail, a message or a file manager. Started on its own, it +shows an empty page that asks for a document, and opens the picker of the +system when it is touched: the system reads the file and hands it over, so the +viewer holds no permission to reach the storage, and it reaches no network. + +It is light: the whole application weighs less than a megabyte, where the other +viewers of the format weigh a hundred times that, so it is installed on a phone +that has no room left, and it opens at once. + +OpenDocument is the first format of office documents that was approved as an +international standard, ISO/IEC 26300, in 2006, and the only one that works as +one: it is written in the open by OASIS, it belongs to no company, and several +programs of several makers write and read the whole of it. + +A document written today is still read in twenty years, by whoever, with +whatever. + +The same viewer is an add-on of Firefox and of Chrome, that shows those +documents in the browser instead of downloading them. + +It is built on WebODF, a library that reads the format in JavaScript, and it is +free software, under the AGPL 3. diff --git a/programs/opendocumentviewer-android/src/main/fastlane/metadata/android/en-US/images/icon.png b/programs/opendocumentviewer-android/src/main/fastlane/metadata/android/en-US/images/icon.png new file mode 100644 index 000000000..b0b7aef32 Binary files /dev/null and b/programs/opendocumentviewer-android/src/main/fastlane/metadata/android/en-US/images/icon.png differ diff --git a/programs/opendocumentviewer-android/src/main/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png b/programs/opendocumentviewer-android/src/main/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png new file mode 100644 index 000000000..9d0145e48 Binary files /dev/null and b/programs/opendocumentviewer-android/src/main/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png differ diff --git a/programs/opendocumentviewer-android/src/main/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png b/programs/opendocumentviewer-android/src/main/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png new file mode 100644 index 000000000..2ad2a465f Binary files /dev/null and b/programs/opendocumentviewer-android/src/main/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png differ diff --git a/programs/opendocumentviewer-android/src/main/fastlane/metadata/android/en-US/short_description.txt b/programs/opendocumentviewer-android/src/main/fastlane/metadata/android/en-US/short_description.txt new file mode 100644 index 000000000..06292e5e2 --- /dev/null +++ b/programs/opendocumentviewer-android/src/main/fastlane/metadata/android/en-US/short_description.txt @@ -0,0 +1 @@ +Reads the .odt, .ods and .odp documents without an office suite. diff --git a/programs/opendocumentviewer-android/src/main/fastlane/metadata/android/en-US/title.txt b/programs/opendocumentviewer-android/src/main/fastlane/metadata/android/en-US/title.txt new file mode 100644 index 000000000..84c5b1d96 --- /dev/null +++ b/programs/opendocumentviewer-android/src/main/fastlane/metadata/android/en-US/title.txt @@ -0,0 +1 @@ +OpenDocument Viewer diff --git a/programs/opendocumentviewer-android/src/main/fastlane/metadata/android/fr-FR/changelogs/1.txt b/programs/opendocumentviewer-android/src/main/fastlane/metadata/android/fr-FR/changelogs/1.txt new file mode 100644 index 000000000..2e5f9ee57 --- /dev/null +++ b/programs/opendocumentviewer-android/src/main/fastlane/metadata/android/fr-FR/changelogs/1.txt @@ -0,0 +1,3 @@ +Première version du lecteur pour Android : il affiche les textes, les +tableurs et les présentations du format OpenDocument, depuis un gestionnaire de +fichiers, un courriel ou le sélecteur du système. diff --git a/programs/opendocumentviewer-android/src/main/fastlane/metadata/android/fr-FR/full_description.txt b/programs/opendocumentviewer-android/src/main/fastlane/metadata/android/fr-FR/full_description.txt new file mode 100644 index 000000000..b2d75a13f --- /dev/null +++ b/programs/opendocumentviewer-android/src/main/fastlane/metadata/android/fr-FR/full_description.txt @@ -0,0 +1,29 @@ +OpenDocument Viewer affiche les documents de LibreOffice et d’OpenOffice, les +textes, les tableurs et les présentations, pour qu’un document reçu sur un +téléphone se lise aussitôt, sans suite bureautique. + +Il se déclare pour les neuf types du format, et le système le propose donc dès +qu’un document est ouvert depuis un courriel, un message ou un gestionnaire de +fichiers. Lancé seul, il affiche une page vide qui demande un document et +ouvre le sélecteur du système quand on la touche : c’est le système qui lit le +fichier et le lui remet, si bien que le lecteur ne détient aucune permission +d’accès au stockage, et n’accède à aucun réseau. + +Il est léger : l’application entière pèse moins d’un mégaoctet, là où les +autres lecteurs du format en pèsent cent fois plus, si bien qu’il s’installe +sur un téléphone qui n’a plus de place, et qu’il s’ouvre aussitôt. + +OpenDocument est le premier format de documents bureautiques approuvé comme +norme internationale, ISO/IEC 26300, en 2006, et le seul qui en tienne le rôle +: il est écrit au grand jour par OASIS, il n’appartient à aucune entreprise, +et plusieurs programmes de plusieurs auteurs l’écrivent et le lisent en +entier. + +Un document écrit aujourd’hui se lira encore dans vingt ans, par qui que ce +soit, avec quoi que ce soit. + +Le même lecteur existe en extension pour Firefox et pour Chrome, qui affiche +ces documents dans le navigateur au lieu de les télécharger. + +Il repose sur WebODF, une bibliothèque qui lit le format en JavaScript, et +c’est un logiciel libre, sous licence AGPL 3. diff --git a/programs/opendocumentviewer-android/src/main/fastlane/metadata/android/fr-FR/short_description.txt b/programs/opendocumentviewer-android/src/main/fastlane/metadata/android/fr-FR/short_description.txt new file mode 100644 index 000000000..344851559 --- /dev/null +++ b/programs/opendocumentviewer-android/src/main/fastlane/metadata/android/fr-FR/short_description.txt @@ -0,0 +1 @@ +Lit les documents .odt, .ods et .odp sans suite bureautique. diff --git a/programs/opendocumentviewer-android/src/main/fastlane/metadata/android/fr-FR/title.txt b/programs/opendocumentviewer-android/src/main/fastlane/metadata/android/fr-FR/title.txt new file mode 100644 index 000000000..84c5b1d96 --- /dev/null +++ b/programs/opendocumentviewer-android/src/main/fastlane/metadata/android/fr-FR/title.txt @@ -0,0 +1 @@ +OpenDocument Viewer diff --git a/programs/opendocumentviewer-android/src/main/java/org/webodf/viewer/ViewerActivity.java b/programs/opendocumentviewer-android/src/main/java/org/webodf/viewer/ViewerActivity.java new file mode 100644 index 000000000..ade0c13c9 --- /dev/null +++ b/programs/opendocumentviewer-android/src/main/java/org/webodf/viewer/ViewerActivity.java @@ -0,0 +1,349 @@ +/** + * Copyright (C) 2026 Daniel Berthereau + * + * @licstart + * This file is part of WebODF. + * + * WebODF is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License (GNU AGPL) + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * WebODF is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with WebODF. If not, see . + * @licend + * + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ + */ + +package org.webodf.viewer; + +import android.app.Activity; +import android.content.ActivityNotFoundException; +import android.content.Intent; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.webkit.WebResourceRequest; +import android.webkit.WebSettings; +import android.webkit.WebResourceResponse; +import android.webkit.WebView; +import android.webkit.WebViewClient; +import android.widget.Toast; +import android.window.OnBackInvokedDispatcher; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * Show a document in the OpenDocument format with WebODF, in a web view. + * + * The page and the library are read from the assets, and the document the + * system hands over is copied into the directory of the cache, as it comes as + * a "content://" uri that the web view may not read by itself. + * + * Everything is served over "https://webodf.invalid/", from the requests the + * web view is intercepted on: a page loaded from "file://" may not read + * another file with XMLHttpRequest, which is how the library reads a + * document. Only the four files of the viewer and the one document are + * served, so no name from a document may reach anything else. + */ +public class ViewerActivity extends Activity { + + private static final String DOMAIN = "webodf.invalid"; + private static final String PAGE = "https://" + DOMAIN + "/index.html"; + private static final String CACHED = "document.odf"; + private static final String[] FILES = { + "index.html", "index.css", "index.js", "webodf.js", + "about.en.html", "about.fr.html", "about.css" + }; + /** The nine types of the format, as the picker only offers those. */ + private static final String[] TYPES = { + "application/vnd.oasis.opendocument.text", + "application/vnd.oasis.opendocument.text-flat-xml", + "application/vnd.oasis.opendocument.text-template", + "application/vnd.oasis.opendocument.presentation", + "application/vnd.oasis.opendocument.presentation-flat-xml", + "application/vnd.oasis.opendocument.presentation-template", + "application/vnd.oasis.opendocument.spreadsheet", + "application/vnd.oasis.opendocument.spreadsheet-flat-xml", + "application/vnd.oasis.opendocument.spreadsheet-template" + }; + private static final int PICK = 1; + private static final String OPEN = "/open"; + + private WebView view; + + /** + * Whether a path is one of the files of the viewer. + */ + private static boolean served(String path) { + for (String file : FILES) { + if (("/" + file).equals(path)) { + return true; + } + } + return false; + } + + /** + * The type of a file of the viewer, as the web view only reads a script + * and a style sheet when they are served as such. + */ + private static String typeOf(String name) { + if (name.endsWith(".html")) { + return "text/html"; + } + if (name.endsWith(".js")) { + return "application/javascript"; + } + if (name.endsWith(".css")) { + return "text/css"; + } + return "application/octet-stream"; + } + + /** + * Copy the document into the cache, so that it is served as a file of its + * own. + */ + private boolean cache(Uri uri) { + File target = new File(getCacheDir(), CACHED); + try (InputStream in = getContentResolver().openInputStream(uri); + OutputStream out = new FileOutputStream(target)) { + if (in == null) { + return false; + } + byte[] buffer = new byte[16384]; + int read = in.read(buffer); + while (read > 0) { + out.write(buffer, 0, read); + read = in.read(buffer); + } + return true; + } catch (IOException e) { + return false; + } + } + + /** + * An empty answer, that the web view takes as the whole of a request: it + * asks nothing from the network, where returning null would let it fetch + * the address itself. + */ + private static WebResourceResponse nothing() { + return new WebResourceResponse("text/plain", "utf-8", + new ByteArrayInputStream(new byte[0])); + } + + /** + * Answer a request of the page, and nothing else: the name is compared to + * the files that are served, never used to build a path. Anything else is + * answered with nothing at all, so that a document holding an image or a + * style sheet of the web reaches no server: no request of this viewer ever + * leaves the device. + */ + private WebResourceResponse answer(Uri uri) { + if (!DOMAIN.equals(uri.getHost())) { + return nothing(); + } + String path = uri.getPath(); + if (path == null) { + return nothing(); + } + try { + if (("/" + CACHED).equals(path)) { + return new WebResourceResponse("application/octet-stream", null, + new FileInputStream(new File(getCacheDir(), CACHED))); + } + for (String file : FILES) { + if (("/" + file).equals(path)) { + return new WebResourceResponse(typeOf(file), "utf-8", + getAssets().open(file)); + } + } + } catch (IOException e) { + return nothing(); + } + return nothing(); + } + + /** + * Whether the back of the system was answered here. + * + * The page of the details is left by the back of the system, that goes + * back to the document; where there is nowhere to go back to, the system + * is left to do what it does, which is to leave the viewer. + */ + private boolean wentBack() { + if (view != null && view.canGoBack()) { + view.goBack(); + return true; + } + return false; + } + + /** + * The back of the system, on android 12 and older. + * + * Android 13 asks for a callback registered on the dispatcher, which is + * what "onCreate" does where the system has one: this is answered by the + * systems that have none, and by them alone. + */ + @Override + @SuppressWarnings("deprecation") + public void onBackPressed() { + if (wentBack()) { + return; + } + super.onBackPressed(); + } + + @Override + protected void onCreate(Bundle state) { + super.onCreate(state); + + view = new WebView(this); + WebSettings settings = view.getSettings(); + // The library runs in the page, so scripts are needed. Everything + // else a web view may reach is closed: the page reads no file and no + // content provider, and it is never allowed to read the network, see + // answer() above. The databases of a page are not closed, as the api + // that did it is deprecated: the web view dropped WebSQL itself. + settings.setJavaScriptEnabled(true); + settings.setAllowFileAccess(false); + settings.setAllowContentAccess(false); + settings.setGeolocationEnabled(false); + settings.setDomStorageEnabled(false); + settings.setCacheMode(WebSettings.LOAD_NO_CACHE); + // A page is drawn at the width of the screen at most, and a reader + // zooms in on it with two fingers from there. The buttons the web view + // draws for that are left out, as every phone pinches. + settings.setSupportZoom(true); + settings.setBuiltInZoomControls(true); + settings.setDisplayZoomControls(false); + view.setWebViewClient(new WebViewClient() { + @Override + public WebResourceResponse shouldInterceptRequest( + WebView webView, WebResourceRequest request) { + return answer(request.getUrl()); + } + + // The page asks for a document by going to "/open", which is + // never loaded: the picker is opened instead. That spares the + // page a bridge to the code around it. + // + // Nothing else is loaded either: a link a document holds leads + // nowhere, as the viewer only ever shows the one page it carries. + @Override + public boolean shouldOverrideUrlLoading( + WebView webView, WebResourceRequest request) { + Uri uri = request.getUrl(); + if (OPEN.equals(uri.getPath())) { + pick(); + return true; + } + // The page that tells what the viewer is belongs to it, and is + // loaded as the first one was. A link it holds leads out, to + // the browser of the system, that opens it: this viewer shows + // documents, and never a page of the web. + if (DOMAIN.equals(uri.getHost()) && served(uri.getPath())) { + return false; + } + if ("http".equals(uri.getScheme()) + || "https".equals(uri.getScheme())) { + startActivity(new Intent(Intent.ACTION_VIEW, uri)); + } + return true; + } + }); + setContentView(view); + + // Android 13 does the back of the system by a callback registered on + // a dispatcher, and calls "onBackPressed" no more: the callback is + // registered where the system has one, and the older systems are + // answered by "onBackPressed", which they still call. The viewer runs + // on android 5 and newer, so both are needed. + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + getOnBackInvokedDispatcher().registerOnBackInvokedCallback( + OnBackInvokedDispatcher.PRIORITY_DEFAULT, + () -> { + if (!wentBack()) { + finish(); + } + }); + } + + Intent intent = getIntent(); + Uri uri = (intent == null) + ? null + : intent.getData(); + if (uri != null && cache(uri)) { + view.loadUrl(PAGE + "?file=/" + CACHED); + return; + } + // Started on its own rather than on a document: the page says how to + // open one, and asks for it when it is touched. The picker is not + // opened here, as an application that opens one before it is even + // seen gives no way back. + view.loadUrl(PAGE); + } + + /** + * @param action ACTION_OPEN_DOCUMENT or ACTION_GET_CONTENT + */ + private Intent asking(String action) { + Intent intent = new Intent(action); + intent.addCategory(Intent.CATEGORY_OPENABLE); + intent.setType("*/*"); + intent.putExtra(Intent.EXTRA_MIME_TYPES, TYPES); + return intent; + } + + /** + * Ask the system for a document, so that the viewer opens one when it is + * started from the list of the applications, and not only when a document + * is handed to it. The system reads the file, so the viewer needs no + * permission to reach the storage. + * + * Two ways of asking are tried, as a system may carry neither: the picker + * of the documents, that a system without it answers nothing to, then the + * older way, that a file manager usually answers. The viewer says so + * rather than stopping when no application answers either. + */ + private void pick() { + try { + startActivityForResult(asking(Intent.ACTION_OPEN_DOCUMENT), PICK); + return; + } catch (ActivityNotFoundException e) { + // The picker of the documents is not there: ask any application. + } + try { + startActivityForResult(asking(Intent.ACTION_GET_CONTENT), PICK); + } catch (ActivityNotFoundException e) { + Toast.makeText(this, R.string.no_picker, Toast.LENGTH_LONG).show(); + } + } + + @Override + protected void onActivityResult(int request, int result, Intent data) { + super.onActivityResult(request, result, data); + Uri uri = (data == null) + ? null + : data.getData(); + if (request == PICK && result == RESULT_OK && uri != null && cache(uri)) { + view.loadUrl(PAGE + "?file=/" + CACHED); + } + } +} diff --git a/programs/opendocumentviewer-android/src/main/res/mipmap-anydpi-v26/icon.xml b/programs/opendocumentviewer-android/src/main/res/mipmap-anydpi-v26/icon.xml new file mode 100644 index 000000000..0cc7644b7 --- /dev/null +++ b/programs/opendocumentviewer-android/src/main/res/mipmap-anydpi-v26/icon.xml @@ -0,0 +1,7 @@ + + + + + + diff --git a/programs/opendocumentviewer-android/src/main/res/mipmap-hdpi/icon.png b/programs/opendocumentviewer-android/src/main/res/mipmap-hdpi/icon.png new file mode 100644 index 000000000..285b5cfdb Binary files /dev/null and b/programs/opendocumentviewer-android/src/main/res/mipmap-hdpi/icon.png differ diff --git a/programs/opendocumentviewer-android/src/main/res/mipmap-hdpi/icon_foreground.png b/programs/opendocumentviewer-android/src/main/res/mipmap-hdpi/icon_foreground.png new file mode 100644 index 000000000..c358f14bf Binary files /dev/null and b/programs/opendocumentviewer-android/src/main/res/mipmap-hdpi/icon_foreground.png differ diff --git a/programs/opendocumentviewer-android/src/main/res/mipmap-mdpi/icon.png b/programs/opendocumentviewer-android/src/main/res/mipmap-mdpi/icon.png new file mode 100644 index 000000000..2cd13a1fc Binary files /dev/null and b/programs/opendocumentviewer-android/src/main/res/mipmap-mdpi/icon.png differ diff --git a/programs/opendocumentviewer-android/src/main/res/mipmap-mdpi/icon_foreground.png b/programs/opendocumentviewer-android/src/main/res/mipmap-mdpi/icon_foreground.png new file mode 100644 index 000000000..e2014308a Binary files /dev/null and b/programs/opendocumentviewer-android/src/main/res/mipmap-mdpi/icon_foreground.png differ diff --git a/programs/opendocumentviewer-android/src/main/res/mipmap-xhdpi/icon.png b/programs/opendocumentviewer-android/src/main/res/mipmap-xhdpi/icon.png new file mode 100644 index 000000000..0f9fc7e3d Binary files /dev/null and b/programs/opendocumentviewer-android/src/main/res/mipmap-xhdpi/icon.png differ diff --git a/programs/opendocumentviewer-android/src/main/res/mipmap-xhdpi/icon_foreground.png b/programs/opendocumentviewer-android/src/main/res/mipmap-xhdpi/icon_foreground.png new file mode 100644 index 000000000..a41c8699b Binary files /dev/null and b/programs/opendocumentviewer-android/src/main/res/mipmap-xhdpi/icon_foreground.png differ diff --git a/programs/opendocumentviewer-android/src/main/res/mipmap-xxhdpi/icon.png b/programs/opendocumentviewer-android/src/main/res/mipmap-xxhdpi/icon.png new file mode 100644 index 000000000..90d6ea5b2 Binary files /dev/null and b/programs/opendocumentviewer-android/src/main/res/mipmap-xxhdpi/icon.png differ diff --git a/programs/opendocumentviewer-android/src/main/res/mipmap-xxhdpi/icon_foreground.png b/programs/opendocumentviewer-android/src/main/res/mipmap-xxhdpi/icon_foreground.png new file mode 100644 index 000000000..0a2693775 Binary files /dev/null and b/programs/opendocumentviewer-android/src/main/res/mipmap-xxhdpi/icon_foreground.png differ diff --git a/programs/opendocumentviewer-android/src/main/res/mipmap-xxxhdpi/icon.png b/programs/opendocumentviewer-android/src/main/res/mipmap-xxxhdpi/icon.png new file mode 100644 index 000000000..395eb32ec Binary files /dev/null and b/programs/opendocumentviewer-android/src/main/res/mipmap-xxxhdpi/icon.png differ diff --git a/programs/opendocumentviewer-android/src/main/res/mipmap-xxxhdpi/icon_foreground.png b/programs/opendocumentviewer-android/src/main/res/mipmap-xxxhdpi/icon_foreground.png new file mode 100644 index 000000000..255a039e9 Binary files /dev/null and b/programs/opendocumentviewer-android/src/main/res/mipmap-xxxhdpi/icon_foreground.png differ diff --git a/programs/opendocumentviewer-android/src/main/res/values-fr/strings.xml b/programs/opendocumentviewer-android/src/main/res/values-fr/strings.xml new file mode 100644 index 000000000..b7970d084 --- /dev/null +++ b/programs/opendocumentviewer-android/src/main/res/values-fr/strings.xml @@ -0,0 +1,6 @@ + + + Aucune application de ce système n’ouvre un + fichier : un document doit donc être ouvert depuis un gestionnaire de + fichiers. + diff --git a/programs/opendocumentviewer-android/src/main/res/values/colors.xml b/programs/opendocumentviewer-android/src/main/res/values/colors.xml new file mode 100644 index 000000000..8996ddd12 --- /dev/null +++ b/programs/opendocumentviewer-android/src/main/res/values/colors.xml @@ -0,0 +1,6 @@ + + + + #FFFFFF + diff --git a/programs/opendocumentviewer-android/src/main/res/values/strings.xml b/programs/opendocumentviewer-android/src/main/res/values/strings.xml new file mode 100644 index 000000000..6c76d50f2 --- /dev/null +++ b/programs/opendocumentviewer-android/src/main/res/values/strings.xml @@ -0,0 +1,6 @@ + + + OpenDocument Viewer + No application of this system opens a file, so a + document has to be opened from a file manager. + diff --git a/programs/opendocumentviewer-android/store/icon-play.png b/programs/opendocumentviewer-android/store/icon-play.png new file mode 100644 index 000000000..8144ce375 Binary files /dev/null and b/programs/opendocumentviewer-android/store/icon-play.png differ diff --git a/programs/opendocumentviewer-desktop/CMakeLists.txt b/programs/opendocumentviewer-desktop/CMakeLists.txt new file mode 100644 index 000000000..b483780bb --- /dev/null +++ b/programs/opendocumentviewer-desktop/CMakeLists.txt @@ -0,0 +1,215 @@ +# The viewer of OpenDocument for the desktop: a window of qt around the page +# that the library draws a document in, the same page as the extensions of the +# browsers and the viewer for android use. +set(CMAKE_AUTOMOC ON) + +add_executable(opendocumentviewer-desktop + main.cpp + mainwindow.cpp + viewerscheme.cpp + words.h +) + +# The page, its style, its script and the library are put in the program itself, +# so that it runs from a single file, wherever it is copied. The library is +# built, so it is named where it is written and aliased to its own name, as the +# page reads it beside itself. +set(WEBODF_JS ${CMAKE_BINARY_DIR}/webodf/webodf.js) +set_source_files_properties(${WEBODF_JS} PROPERTIES + QT_RESOURCE_ALIAS webodf.js + GENERATED TRUE) +qt_add_resources(opendocumentviewer-desktop "assets" + PREFIX "/" + BASE assets + FILES + assets/viewer.html + assets/viewer.css + assets/viewer.js + assets/about.css +) + +# The two pages that tell what the format is worth hold a text that is written +# once, see "INSERT_TEXT" in the CMakeLists of the root, so they are written into +# the build directory and read from there. +foreach (LANGUAGE en fr) + INSERT_TEXT(assets/about.${LANGUAGE}.html.in + ${CMAKE_CURRENT_BINARY_DIR}/about.${LANGUAGE}.html ${LANGUAGE}) + set_source_files_properties(${CMAKE_CURRENT_BINARY_DIR}/about.${LANGUAGE}.html + PROPERTIES QT_RESOURCE_ALIAS about.${LANGUAGE}.html) +endforeach () +qt_add_resources(opendocumentviewer-desktop "text" + PREFIX "/" + FILES + ${CMAKE_CURRENT_BINARY_DIR}/about.en.html + ${CMAKE_CURRENT_BINARY_DIR}/about.fr.html +) +qt_add_resources(opendocumentviewer-desktop "library" + PREFIX "/" + FILES ${WEBODF_JS} +) + +# One icon serves every product, so it is read where it lies rather than copied +# here, and it names the window and the entry of the desktop. The one of the +# window is the small one, that the extensions carry; the icons of windows and +# of macos are made from the large one, as a dock draws it at 512. +set(ICON ${CMAKE_CURRENT_SOURCE_DIR}/../opendocumentviewer-webext/skin/default/icon.png) +set(ICON_MASTER ${CMAKE_CURRENT_SOURCE_DIR}/../opendocumentviewer-android/src/main/fastlane/metadata/android/en-US/images/icon.png) +set_source_files_properties(${ICON} PROPERTIES QT_RESOURCE_ALIAS icon.png) +qt_add_resources(opendocumentviewer-desktop "icon" + PREFIX "/" + FILES ${ICON} +) +add_dependencies(opendocumentviewer-desktop webodf.js-target) + +target_compile_definitions(opendocumentviewer-desktop PRIVATE + OPENDOCUMENTVIEWER_VERSION="${WEBODF_MANIFEST_VERSION}") + +target_link_libraries(opendocumentviewer-desktop + Qt6::Core + Qt6::Gui + Qt6::Widgets + Qt6::PrintSupport + Qt6::WebEngineCore + Qt6::WebEngineWidgets +) + +# Windows draws a program with the icon of its resources, in the format of its +# own, and shows what the version tells in the properties of the file. The icon +# is made from the one of the project, at the sizes windows asks for, rather +# than kept as a second file to hold in step, see "webodf/tools/makeico.js". +if (WIN32) + set(OPENDOCUMENTVIEWER_ICO ${CMAKE_CURRENT_BINARY_DIR}/opendocumentviewer.ico) + add_custom_command( + OUTPUT ${OPENDOCUMENTVIEWER_ICO} + COMMAND ${NODE} ARGS ${TOOLS_DIR}/makeico.js ${ICON_MASTER} + ${OPENDOCUMENTVIEWER_ICO} + DEPENDS ${ICON_MASTER} ${TOOLS_DIR}/makeico.js + ) + + # The version of a resource is four numbers separated by commas, where the + # one of the build may hold fewer, "0.5.10" on a tag. The numbers are + # counted rather than matched: the regular expressions of cmake know no + # repetition, "{3}". + string(REPLACE "." ";" OPENDOCUMENTVIEWER_VERSION_PARTS + ${WEBODF_MANIFEST_VERSION}) + list(LENGTH OPENDOCUMENTVIEWER_VERSION_PARTS OPENDOCUMENTVIEWER_VERSION_HELD) + while (OPENDOCUMENTVIEWER_VERSION_HELD LESS 4) + list(APPEND OPENDOCUMENTVIEWER_VERSION_PARTS 0) + math(EXPR OPENDOCUMENTVIEWER_VERSION_HELD + "${OPENDOCUMENTVIEWER_VERSION_HELD} + 1") + endwhile () + string(REPLACE ";" "," OPENDOCUMENTVIEWER_VERSION_NUMBERS + "${OPENDOCUMENTVIEWER_VERSION_PARTS}") + configure_file(data/opendocumentviewer.rc.in + ${CMAKE_CURRENT_BINARY_DIR}/opendocumentviewer.rc @ONLY) + set_source_files_properties(${CMAKE_CURRENT_BINARY_DIR}/opendocumentviewer.rc + PROPERTIES OBJECT_DEPENDS ${OPENDOCUMENTVIEWER_ICO}) + target_sources(opendocumentviewer-desktop PRIVATE + ${CMAKE_CURRENT_BINARY_DIR}/opendocumentviewer.rc) + + # A viewer opens no terminal behind its window. + set_target_properties(opendocumentviewer-desktop PROPERTIES + WIN32_EXECUTABLE TRUE) + + # The script of the installer, that gathers what was deployed, see + # "README-Products.md". + # Inno Setup reads the paths of windows, with backslashes. + file(TO_NATIVE_PATH "${CMAKE_INSTALL_PREFIX}" OPENDOCUMENTVIEWER_DEPLOYED) + configure_file(data/opendocumentviewer.iss.in + ${CMAKE_CURRENT_BINARY_DIR}/opendocumentviewer.iss @ONLY) +endif () + +# Macos draws a program with the icon of its bundle, in the format of its own, +# and reads what the bundle tells about it, the types of documents it opens +# among them. +if (APPLE) + set(OPENDOCUMENTVIEWER_ICNS + ${CMAKE_CURRENT_BINARY_DIR}/opendocumentviewer.icns) + add_custom_command( + OUTPUT ${OPENDOCUMENTVIEWER_ICNS} + COMMAND ${NODE} ARGS ${TOOLS_DIR}/makeicns.js ${ICON_MASTER} + ${OPENDOCUMENTVIEWER_ICNS} + DEPENDS ${ICON_MASTER} ${TOOLS_DIR}/makeicns.js + ${TOOLS_DIR}/makeico.js + ) + # The icon goes into the resources of the bundle, where the plist names it. + set_source_files_properties(${OPENDOCUMENTVIEWER_ICNS} PROPERTIES + MACOSX_PACKAGE_LOCATION Resources) + target_sources(opendocumentviewer-desktop PRIVATE + ${OPENDOCUMENTVIEWER_ICNS}) + + # The oldest macos the program runs on is the one qt was built for, that + # cmake holds when it is told, and the one of Qt 6.8 otherwise: a bundle + # that claimed less would be offered to a machine that cannot run it. + if (CMAKE_OSX_DEPLOYMENT_TARGET) + set(OPENDOCUMENTVIEWER_MACOS ${CMAKE_OSX_DEPLOYMENT_TARGET}) + else () + set(OPENDOCUMENTVIEWER_MACOS 12.0) + endif () + configure_file(data/Info.plist.in + ${CMAKE_CURRENT_BINARY_DIR}/Info.plist @ONLY) + # The name of the bundle holds no space: the script that qt writes to + # deploy the libraries does not quote the path it is given, and a space + # would cut it in two. The name that a desktop shows is in the plist, + # under "CFBundleDisplayName". + set_target_properties(opendocumentviewer-desktop PROPERTIES + MACOSX_BUNDLE TRUE + MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_BINARY_DIR}/Info.plist + OUTPUT_NAME "OpenDocumentViewer") +endif () + +add_custom_target(product-opendocumentviewer-desktop + DEPENDS opendocumentviewer-desktop) + +# What a desktop needs to offer the viewer for a document: the entry names the +# nine types of the format, so that a file manager proposes it, and the icon is +# named after the entry, as the specification of the freedesktop asks. +include(GNUInstallDirs) +install(TARGETS opendocumentviewer-desktop + BUNDLE DESTINATION . + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) +# The entry of the desktop, the metadata of the store and the icon follow the +# specifications of the freedesktop: they are of the desktops of linux and of +# the unix alike, where macos and windows have their own ways. +if (UNIX AND NOT APPLE) + install(FILES data/org.webodf.OpenDocumentViewer.desktop + DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/applications) + # What a store of software reads of the program, that the tools of the + # AppImage and of the flatpak ask for. The version is the one of the + # build, in the three numbers a release carries. + set(OPENDOCUMENTVIEWER_METAINFO_VERSION ${WEBODF_MANIFEST_VERSION}) + string(TIMESTAMP OPENDOCUMENTVIEWER_METAINFO_DATE "%Y-%m-%d" UTC) + configure_file(data/org.webodf.OpenDocumentViewer.metainfo.xml.in + ${CMAKE_CURRENT_BINARY_DIR}/org.webodf.OpenDocumentViewer.metainfo.xml + @ONLY) + install(FILES + ${CMAKE_CURRENT_BINARY_DIR}/org.webodf.OpenDocumentViewer.metainfo.xml + DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/metainfo) +endif () +if (UNIX AND NOT APPLE) + install(FILES ${ICON} + DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/128x128/apps + RENAME org.webodf.OpenDocumentViewer.png) +endif () + +# The libraries of qt are gathered beside the program by the tool of the +# platform, "windeployqt" on windows and "macdeployqt" on macos, so that +# "cmake --install" writes a directory that runs on a machine without qt. +# +# On linux the program is installed among the files of the system, that carry +# qt already, and a package of the system names them, see "packaging.cmake": +# the tool would only write a "qt.conf" that points the program at a prefix of +# its own, which is wrong once the program is installed in "/usr". What runs +# without the qt of the system is a universal package, that carries it. +if (WIN32 OR APPLE) + qt_generate_deploy_app_script( + TARGET opendocumentviewer-desktop + OUTPUT_SCRIPT OPENDOCUMENTVIEWER_DEPLOY + NO_UNSUPPORTED_PLATFORM_ERROR + ) + install(SCRIPT ${OPENDOCUMENTVIEWER_DEPLOY}) +endif () + +# The archives, the packages and the universal packages of the viewer. +include(${CMAKE_CURRENT_SOURCE_DIR}/packaging.cmake) + diff --git a/programs/opendocumentviewer-desktop/assets/about.css b/programs/opendocumentviewer-desktop/assets/about.css new file mode 100644 index 000000000..6d646b10a --- /dev/null +++ b/programs/opendocumentviewer-desktop/assets/about.css @@ -0,0 +1,60 @@ +/* The page follows the theme of the system, as the viewer does. A link that + leaves it is opened in the browser of the system, see "mainwindow.cpp". */ +html, body { + margin: 0; + padding: 0; + background: #ffffff; + color: #15141a; + font-family: sans-serif; + line-height: 1.5; +} +main { + max-width: 40em; + margin: 0 auto; + padding: 2em 1.5em 4em; +} +h1 { + font-size: 1.6em; +} +h2 { + margin-top: 2em; + font-size: 1.15em; +} +#lead { + font-size: 1.1em; +} +figure { + margin: 1em 0; +} +figure > img { + max-width: 100%; + border: 1px solid rgba(0, 0, 0, 0.2); + border-radius: 4px; +} +/* A caption without its image says nothing, so it goes with it. */ +figure > img[hidden] { + display: none; +} +figure > img[hidden] + figcaption { + display: none; +} +figcaption { + padding-top: 0.4em; + font-size: 0.9em; + opacity: 0.75; +} +a { + color: #0060df; +} +@media (prefers-color-scheme: dark) { + html, body { + background: #1c1b22; + color: #fbfbfe; + } + figure > img { + border-color: rgba(255, 255, 255, 0.2); + } + a { + color: #00ddff; + } +} diff --git a/programs/opendocumentviewer-desktop/assets/about.en.html.in b/programs/opendocumentviewer-desktop/assets/about.en.html.in new file mode 100644 index 000000000..b810f7927 --- /dev/null +++ b/programs/opendocumentviewer-desktop/assets/about.en.html.in @@ -0,0 +1,29 @@ + + + + + OpenDocument Viewer + + + +
+

The OpenDocument Viewer

+

It reads the documents of the OpenDocument standard written by + LibreOffice and by every other office suite: text, spreadsheets, + presentations and drawings, with no office suite of its own.

+

Once it is installed, it is offered for the nine types of OpenDocument: a + double click on a document then offers it among the applications that read + the format. It reaches no network, and it reads the document it is given + and nothing else.

+ +@FORMAT_TEXT@ + +

Elsewhere

+

The same viewer is an add-on of Firefox, of Chrome and of Thunderbird, + that shows those documents in the browser or in a message instead of + downloading them, and an application for Android.

+

It is built on WebODF, a library that reads the format in JavaScript, and + it is free software, under the AGPL 3.

+
+ + diff --git a/programs/opendocumentviewer-desktop/assets/about.fr.html.in b/programs/opendocumentviewer-desktop/assets/about.fr.html.in new file mode 100644 index 000000000..05a65186c --- /dev/null +++ b/programs/opendocumentviewer-desktop/assets/about.fr.html.in @@ -0,0 +1,29 @@ + + + + + OpenDocument Viewer + + + +
+

Le lecteur OpenDocument

+

Il lit les documents au standard OpenDocument créés par + LibreOffice et toutes les suites bureautiques : les textes, les tableurs, + les présentations et les dessins, sans suite bureautique.

+

Une fois installé, il se déclare pour les neuf types d’OpenDocument : un + double-clic sur un document le propose alors parmi les applications qui + lisent le format. Il n’accède à aucun réseau et ne lit que le document qui + lui est donné.

+ +@FORMAT_TEXT@ + +

Ailleurs

+

Le même lecteur existe en extension pour Firefox, pour Chrome et pour + Thunderbird, qui affiche ces documents dans le navigateur ou dans un + message au lieu de les télécharger, et en application pour Android.

+

Il repose sur WebODF, une bibliothèque qui lit le format en JavaScript, + et c’est un logiciel libre, sous licence AGPL 3.

+
+ + diff --git a/programs/opendocumentviewer-desktop/assets/viewer.css b/programs/opendocumentviewer-desktop/assets/viewer.css new file mode 100644 index 000000000..9daef5015 --- /dev/null +++ b/programs/opendocumentviewer-desktop/assets/viewer.css @@ -0,0 +1,77 @@ +/* Every rule of this page names "html > body" rather than "body": an element of + a document carries the local name "body", "office:body", which a selector of + one name reaches as well, so the colour, the font and the alignment of the + page were inherited by the whole document. */ +html, html > body, div { + padding: 0; + margin: 0; + border: 0; +} +html { + /* The page of a document is white paper, so it is declared light whatever + the theme of the system is: chromium then leaves its colours alone + instead of darkening them as it paints, and the ground below still + follows the theme, from the rules of this file. */ + color-scheme: light; +} +html, html > body { + height: 100%; + /* The ground sets the page apart, as a reader of pdf does, and follows the + theme of the system. The page itself is white, from the style sheet of + the library. */ + background: #d4d4d7; + color: #15141a; + font-family: sans-serif; + /* The library turns the element it draws in into an inline block, so the + page is put in the middle by this alignment. */ + text-align: center; +} +/* What is said when there is no document, or when it cannot be drawn, read in + the middle of the ground. */ +p#message, p#failure { + box-sizing: border-box; + max-width: 28em; + margin: 0 auto; + padding: 4em 2em; + line-height: 1.5; + opacity: 0.85; +} +p#failure { + opacity: 1; +} +/* The way to the page of the format, read on the ground of the theme, under + the words that ask for a document. */ +p#ways { + padding-bottom: 2em; +} +p#ways > a { + color: inherit; + font-size: 0.95em; + opacity: 0.75; +} +div#odf { + /* Keeps the page off the edges of the window. */ + padding: 1em; + /* The page is white paper, from the style sheet of the library, whatever + the theme of the system is, so the ink is dark in both themes: a + document that names no colour for a text would take the one of the + ground below, which is white under a dark theme, on white paper. */ + color: #15141a; +} +@media (prefers-color-scheme: dark) { + html, html > body { + background: #2a2a2e; + color: #fbfbfe; + } +} + +/* On paper, the ground has nothing to do: the page of the document is the page + that is printed, and the grey around it would be printed with it. */ +@media print { + html, html > body { + background: white; + } + div#odf { + padding: 0; + } +} diff --git a/programs/opendocumentviewer-desktop/assets/viewer.html b/programs/opendocumentviewer-desktop/assets/viewer.html new file mode 100644 index 000000000..19d771d8f --- /dev/null +++ b/programs/opendocumentviewer-desktop/assets/viewer.html @@ -0,0 +1,28 @@ + + + + + OpenDocument Viewer + + + + + + +

Open a document with Ctrl+O, or drop it in the window.

+ + +

About OpenDocument and this viewer

+
+ + diff --git a/programs/opendocumentviewer-desktop/assets/viewer.js b/programs/opendocumentviewer-desktop/assets/viewer.js new file mode 100644 index 000000000..0326cb31a --- /dev/null +++ b/programs/opendocumentviewer-desktop/assets/viewer.js @@ -0,0 +1,271 @@ +/*global document, window, odf*/ + +(function () { + "use strict"; + + var container = document.getElementById("odf"), + message = document.getElementById("message"), + failure = document.getElementById("failure"), + ways = document.getElementById("ways"), + canvas = null, + refitting = false, + translated, + i; + + // The words of the viewer are written in the page, in the attribute of the + // language: this file holds no sentence of its own. + if (String(window.navigator.language).indexOf("fr") === 0) { + translated = document.querySelectorAll("[data-fr]"); + for (i = 0; i < translated.length; i += 1) { + translated[i].firstChild.nodeValue = + translated[i].getAttribute("data-fr"); + } + document.getElementById("about").href = "odf:/about.fr.html"; + } + + // The width the page may take, once the space that keeps it off the edges + // of the window is taken off. + function available() { + var style = window.getComputedStyle(container); + return document.documentElement.clientWidth + - parseFloat(style.paddingLeft) + - parseFloat(style.paddingRight); + } + + // A document of eight hundred pages is a very tall thing to paint: the + // engine rasterises what is near the window and leaves the rest, and a + // reader that jumps far ahead lands on a page that was never painted. + // The pages are told to be drawn only when they come near the window, + // each keeping the room of a page while it is away, so that the reader + // scrolls over the same length and the engine paints far less. + function skipPagesAway(on) { + var sheet = document.getElementById("pagesaway"), + first = document.querySelector(".webodf-pageSheet"), + box; + if (!on) { + if (sheet) { + sheet.parentNode.removeChild(sheet); + } + return; + } + if (!first) { + return; + } + box = first.getBoundingClientRect(); + if (!sheet) { + sheet = document.createElement("style"); + sheet.id = "pagesaway"; + document.head.appendChild(sheet); + } + sheet.textContent = ".webodf-pageSheet {content-visibility:auto;" + + "contain-intrinsic-size:" + Math.round(box.width) + "px " + + Math.round(box.height) + "px;}"; + } + + // How the first page came out: the height of its text, how far its text + // reaches, and where the foot drawn on it begins. A foot that begins + // above the end of the text is drawn over the last line of it. + function pageReport() { + var box = document.querySelector(".webodf-pageBox"), + furniture = document.querySelector(".webodf-pageFurniture"), + drawn = document.querySelectorAll(".webodf-pageSheet").length, + edge, + bottom = 0, + top = Infinity, + i, + parts, + rect; + if (!box) { + return String(drawn) + " drawn, no page box"; + } + edge = box.getBoundingClientRect(); + for (i = 0; i < box.children.length; i += 1) { + rect = box.children[i].getBoundingClientRect(); + if (rect.height > 0) { + bottom = Math.max(bottom, rect.bottom); + } + } + if (furniture) { + parts = furniture.getElementsByTagName("*"); + for (i = 0; i < parts.length; i += 1) { + rect = parts[i].getBoundingClientRect(); + if (rect.height > 0 + && rect.top > edge.bottom - edge.height / 2) { + top = Math.min(top, rect.top); + } + } + } + return String(drawn) + " drawn, zoom " + zoom() + + ", box " + Math.round(edge.height) + + ", text ends " + Math.round(bottom - edge.top) + + ", foot begins " + (top === Infinity + ? "nowhere" + : Math.round(top - edge.top)) + + ", over by " + (top === Infinity + ? 0 + : Math.round(bottom - top)); + } + + // What the first text of the document is drawn in, and what it is drawn on: + // a text takes the colour of the ground when the document names none, so + // this is what tells a white text on white paper. + function ink() { + var drawn = container.getElementsByTagName("*"), + style, + box, + i; + for (i = 0; i < drawn.length; i += 1) { + // Only a text that is drawn tells anything: the metadata of a + // document, its date and its author, are in the page as well, and + // they are the first ones there. + box = drawn[i].getBoundingClientRect(); + style = window.getComputedStyle(drawn[i]); + if (drawn[i].firstChild + && drawn[i].firstChild.nodeType === 3 + && drawn[i].firstChild.nodeValue.trim() + && box.width > 0 + && box.height > 0 + && style.display !== "none") { + return "ink " + style.color + " on " + + window.getComputedStyle(canvas.getSizer()).backgroundColor + + " in " + style.fontFamily; + } + } + return "no text drawn"; + } + + function zoom() { + return canvas + ? canvas.getZoomLevel() + : 1; + } + + window.viewer = { + /** + * Draw the document the window is serving. The canvas is made again + * every time, as the one before holds the document it drew. + */ + load: function () { + if (canvas) { + canvas.destroy(function () { + return; + }); + container.innerHTML = ""; + } + message.hidden = true; + failure.hidden = true; + ways.hidden = true; + skipPagesAway(false); + canvas = new odf.OdfCanvas(container); + // A row of two pages is twice as wide as one, so the document is + // scaled to the window anew when the reader asks for another way + // of laying the pages out, and then alone: a document is read at + // the size it was written at, and a window that holds a page + // whole leaves it at that size. + canvas.addListener("pagesdrawn", function () { + // What the pages came to, written when the category + // "webodf.viewer" is turned on: it tells a foot drawn over + // the last line of a page apart from one that stands clear. + window.console.log("pages " + pageReport()); + // The pages are all broken, so nothing of them is measured + // any more: the ones away from the window may be left + // undrawn until the reader comes to them. + skipPagesAway(true); + if (!refitting) { + return; + } + refitting = false; + canvas.fitSmart(available()); + }); + // A text is drawn over pages, as it is printed, which the + // library does not do on its own. + canvas.setPaginated(true); + canvas.addListener("statereadychange", function (odfcontainer) { + if (odfcontainer.state === odf.OdfContainer.INVALID) { + failure.hidden = false; + return; + } + // A document is drawn at its own size, which is what it was + // written at; it is only scaled down when it is wider than the + // window, as fitSmart never scales up. + canvas.fitSmart(available()); + // The window shows this when the category "webodf.viewer" is + // turned on, which is how a build is checked without a screen, + // and how the colours of a document that reads badly are told + // apart from the theme of the system. + window.console.log("drawn " + + canvas.getSizer().offsetWidth + "x" + + canvas.getSizer().offsetHeight + " at zoom " + + canvas.getZoomLevel() + ", " + ink() + + ", dark theme " + + window.matchMedia("(prefers-color-scheme: dark)").matches); + }); + // The window serves the document that is open at this one address, + // whichever file it is, see "viewerscheme.cpp". + canvas.load("odf:/document"); + }, + + /** + * Put the document away and show what the window shows with none. + * @return {undefined} + */ + unload: function () { + if (canvas) { + canvas.destroy(function () { + return; + }); + canvas = null; + container.innerHTML = ""; + } + failure.hidden = true; + message.hidden = false; + ways.hidden = false; + }, + + /** + * @param {!number} factor + */ + zoomBy: function (factor) { + var wanted = zoom() * factor; + if (canvas && wanted >= 0.1 && wanted <= 10) { + canvas.setZoomLevel(wanted); + } + }, + + /** + * @param {!number} level + */ + setZoom: function (level) { + if (canvas) { + canvas.setZoomLevel(level); + } + }, + + /** Draw the page as wide as the window allows, scaling up as needed. */ + fit: function () { + if (canvas) { + canvas.fitToWidth(available()); + } + }, + + /** + * How the pages are laid out: one to a row, as a document is + * scrolled; two to a row, as a book is read; or two to a row with + * the first on the right of its own, as the first page of a book + * faces nothing. + * @param {!number} perRow one or two + * @param {!boolean} firstAlone + * @return {undefined} + */ + setPages: function (perRow, firstAlone) { + if (canvas) { + refitting = true; + // The pages are about to be broken again, and what is broken + // is measured: a page that is not drawn measures nothing. + skipPagesAway(false); + canvas.setPagesPerRow(perRow); + canvas.setFirstPageOnItsOwn(firstAlone); + } + } + }; +}()); diff --git a/programs/opendocumentviewer-desktop/data/Info.plist.in b/programs/opendocumentviewer-desktop/data/Info.plist.in new file mode 100644 index 000000000..1fb07e800 --- /dev/null +++ b/programs/opendocumentviewer-desktop/data/Info.plist.in @@ -0,0 +1,65 @@ + + + + + + CFBundleDevelopmentRegion + en + CFBundleDisplayName + OpenDocument Viewer + CFBundleExecutable + OpenDocumentViewer + CFBundleIconFile + opendocumentviewer.icns + CFBundleIdentifier + org.webodf.OpenDocumentViewer + CFBundleName + OpenDocument Viewer + CFBundlePackageType + APPL + CFBundleShortVersionString + @WEBODF_MANIFEST_VERSION@ + CFBundleVersion + @WEBODF_MANIFEST_VERSION@ + NSHighResolutionCapable + + NSHumanReadableCopyright + GNU Affero General Public License 3 + + LSMinimumSystemVersion + @OPENDOCUMENTVIEWER_MACOS@ + + CFBundleDocumentTypes + + + CFBundleTypeName + OpenDocument + CFBundleTypeRole + Viewer + LSHandlerRank + Alternate + LSItemContentTypes + + org.oasis-open.opendocument.text + org.oasis-open.opendocument.text-template + org.oasis-open.opendocument.text-flat-xml + org.oasis-open.opendocument.presentation + org.oasis-open.opendocument.presentation-template + org.oasis-open.opendocument.presentation-flat-xml + org.oasis-open.opendocument.spreadsheet + org.oasis-open.opendocument.spreadsheet-template + org.oasis-open.opendocument.spreadsheet-flat-xml + org.oasis-open.opendocument.graphics + org.oasis-open.opendocument.graphics-template + org.oasis-open.opendocument.graphics-flat-xml + + + + + diff --git a/programs/opendocumentviewer-desktop/data/copy-packages.cmake b/programs/opendocumentviewer-desktop/data/copy-packages.cmake new file mode 100644 index 000000000..fba57a631 --- /dev/null +++ b/programs/opendocumentviewer-desktop/data/copy-packages.cmake @@ -0,0 +1,11 @@ +# Copy the package CPack wrote into the products of the build. The one of this +# build is named, and not every package of the kind that is there: CPack +# leaves the packages of the builds that went before beside it, whose names +# hold their own version, and they are none of this build's business. +set(PACKAGE "${DIR}/${NAME}.${EXT}") +if (NOT EXISTS "${PACKAGE}") + message(FATAL_ERROR "No package was written as ${PACKAGE}.") +endif () +file(MAKE_DIRECTORY "${DEST}") +file(COPY "${PACKAGE}" DESTINATION "${DEST}") +message("Wrote ${PACKAGE}") diff --git a/programs/opendocumentviewer-desktop/data/opendocumentviewer.iss.in b/programs/opendocumentviewer-desktop/data/opendocumentviewer.iss.in new file mode 100644 index 000000000..9c4760cfd --- /dev/null +++ b/programs/opendocumentviewer-desktop/data/opendocumentviewer.iss.in @@ -0,0 +1,102 @@ +; The installer of the viewer for windows, for Inno Setup, that is free and +; scripted: https://jrsoftware.org/isinfo.php. Cmake writes the version and the +; directory the files are taken from, see "CMakeLists.txt", so it is compiled +; with: +; +; iscc build/programs/opendocumentviewer-desktop/opendocumentviewer.iss +; +; The nine types of the format are declared under one identifier rather than +; nine, so a document is offered to the viewer whichever of them it is, and the +; entry is removed whole when the program is uninstalled. The viewer is offered +; beside the office suite of the machine, and never in its place: it is written +; under "OpenWithProgids", which adds it to "Open with", and it never takes the +; place of what a double click opens. + +[Setup] +AppId={{2E0A3E4C-9E2E-4C1E-9E7D-0B1D0F2A5C61} +AppName=OpenDocument Viewer +AppVersion=@WEBODF_MANIFEST_VERSION@ +AppPublisher=webodf.org +AppPublisherURL=https://webodf.org/ +DefaultDirName={autopf}\OpenDocument Viewer +DefaultGroupName=OpenDocument Viewer +UninstallDisplayIcon={app}\opendocumentviewer-desktop.exe +OutputBaseFilename=opendocumentviewer-@WEBODF_MANIFEST_VERSION@-setup +Compression=lzma2 +SolidCompression=yes +; The viewer runs on the same versions of windows as the webengine of qt, and on +; the same architectures: qt 6 is built for x86_64 and for arm64, and for no +; 32 bits windows at all, so the installer refuses a machine that could not run +; what it carries. +MinVersion=10.0 +ArchitecturesAllowed=x64compatible +ArchitecturesInstallIn64BitMode=x64compatible +PrivilegesRequiredOverridesAllowed=dialog + +[Languages] +Name: "en"; MessagesFile: "compiler:Default.isl" +Name: "fr"; MessagesFile: "compiler:Languages\French.isl" + +[Files] +; Everything windeployqt gathered beside the program, the libraries of qt, +; "QtWebEngineProcess.exe" and its resources among them. +Source: "@OPENDOCUMENTVIEWER_DEPLOYED@\*"; DestDir: "{app}"; \ + Flags: recursesubdirs createallsubdirs ignoreversion + +[Icons] +Name: "{group}\OpenDocument Viewer"; Filename: "{app}\opendocumentviewer-desktop.exe" + +[Registry] +Root: HKA; Subkey: "Software\Classes\OpenDocumentViewer.Document"; \ + ValueType: string; ValueName: ""; ValueData: "OpenDocument"; \ + Flags: uninsdeletekey +Root: HKA; Subkey: "Software\Classes\OpenDocumentViewer.Document\DefaultIcon"; \ + ValueType: string; ValueName: ""; \ + ValueData: "{app}\opendocumentviewer-desktop.exe,0" +Root: HKA; \ + Subkey: "Software\Classes\OpenDocumentViewer.Document\shell\open\command"; \ + ValueType: string; ValueName: ""; \ + ValueData: """{app}\opendocumentviewer-desktop.exe"" ""%1""" +Root: HKA; Subkey: "Software\Classes\.odt\OpenWithProgids"; ValueType: string; \ + ValueName: "OpenDocumentViewer.Document"; ValueData: ""; \ + Flags: uninsdeletevalue +Root: HKA; Subkey: "Software\Classes\.ott\OpenWithProgids"; ValueType: string; \ + ValueName: "OpenDocumentViewer.Document"; ValueData: ""; \ + Flags: uninsdeletevalue +Root: HKA; Subkey: "Software\Classes\.ods\OpenWithProgids"; ValueType: string; \ + ValueName: "OpenDocumentViewer.Document"; ValueData: ""; \ + Flags: uninsdeletevalue +Root: HKA; Subkey: "Software\Classes\.ots\OpenWithProgids"; ValueType: string; \ + ValueName: "OpenDocumentViewer.Document"; ValueData: ""; \ + Flags: uninsdeletevalue +Root: HKA; Subkey: "Software\Classes\.odp\OpenWithProgids"; ValueType: string; \ + ValueName: "OpenDocumentViewer.Document"; ValueData: ""; \ + Flags: uninsdeletevalue +Root: HKA; Subkey: "Software\Classes\.otp\OpenWithProgids"; ValueType: string; \ + ValueName: "OpenDocumentViewer.Document"; ValueData: ""; \ + Flags: uninsdeletevalue +Root: HKA; Subkey: "Software\Classes\.odg\OpenWithProgids"; ValueType: string; \ + ValueName: "OpenDocumentViewer.Document"; ValueData: ""; \ + Flags: uninsdeletevalue +Root: HKA; Subkey: "Software\Classes\.otg\OpenWithProgids"; ValueType: string; \ + ValueName: "OpenDocumentViewer.Document"; ValueData: ""; \ + Flags: uninsdeletevalue +Root: HKA; Subkey: "Software\Classes\.odf\OpenWithProgids"; ValueType: string; \ + ValueName: "OpenDocumentViewer.Document"; ValueData: ""; \ + Flags: uninsdeletevalue +Root: HKA; Subkey: "Software\Classes\.fodt\OpenWithProgids"; ValueType: string; \ + ValueName: "OpenDocumentViewer.Document"; ValueData: ""; \ + Flags: uninsdeletevalue +Root: HKA; Subkey: "Software\Classes\.fods\OpenWithProgids"; ValueType: string; \ + ValueName: "OpenDocumentViewer.Document"; ValueData: ""; \ + Flags: uninsdeletevalue +Root: HKA; Subkey: "Software\Classes\.fodp\OpenWithProgids"; ValueType: string; \ + ValueName: "OpenDocumentViewer.Document"; ValueData: ""; \ + Flags: uninsdeletevalue +Root: HKA; Subkey: "Software\Classes\.fodg\OpenWithProgids"; ValueType: string; \ + ValueName: "OpenDocumentViewer.Document"; ValueData: ""; \ + Flags: uninsdeletevalue + +[Run] +Filename: "{app}\opendocumentviewer-desktop.exe"; \ + Description: "Open a document"; Flags: nowait postinstall skipifsilent diff --git a/programs/opendocumentviewer-desktop/data/opendocumentviewer.rc.in b/programs/opendocumentviewer-desktop/data/opendocumentviewer.rc.in new file mode 100644 index 000000000..5c53c61ca --- /dev/null +++ b/programs/opendocumentviewer-desktop/data/opendocumentviewer.rc.in @@ -0,0 +1,33 @@ +// The resources of the program under windows: the icon that the desktop and +// the bar of the tasks draw it with, and what the properties of the file show. +// Written by cmake, see "CMakeLists.txt". + +IDI_ICON1 ICON "@OPENDOCUMENTVIEWER_ICO@" + +1 VERSIONINFO +FILEVERSION @OPENDOCUMENTVIEWER_VERSION_NUMBERS@ +PRODUCTVERSION @OPENDOCUMENTVIEWER_VERSION_NUMBERS@ +FILEOS 0x40004L +FILETYPE 0x1L +BEGIN + BLOCK "StringFileInfo" + BEGIN + // 040904b0: English of the United States, unicode. The words below are + // the ones windows shows, and it shows them in that language only. + BLOCK "040904b0" + BEGIN + VALUE "CompanyName", "webodf.org" + VALUE "FileDescription", "OpenDocument Viewer" + VALUE "FileVersion", "@WEBODF_MANIFEST_VERSION@" + VALUE "InternalName", "opendocumentviewer" + VALUE "LegalCopyright", "GNU Affero General Public License 3" + VALUE "OriginalFilename", "opendocumentviewer-desktop.exe" + VALUE "ProductName", "OpenDocument Viewer" + VALUE "ProductVersion", "@WEBODF_MANIFEST_VERSION@" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END diff --git a/programs/opendocumentviewer-desktop/data/org.webodf.OpenDocumentViewer.desktop b/programs/opendocumentviewer-desktop/data/org.webodf.OpenDocumentViewer.desktop new file mode 100644 index 000000000..fc7f04855 --- /dev/null +++ b/programs/opendocumentviewer-desktop/data/org.webodf.OpenDocumentViewer.desktop @@ -0,0 +1,15 @@ +[Desktop Entry] +Type=Application +Version=1.0 +Name=OpenDocument Viewer +GenericName=Viewer of OpenDocument +GenericName[fr]=Lecteur OpenDocument +Comment=Read the text, spreadsheets and presentations of the OpenDocument format +Comment[fr]=Lire les textes, tableurs et présentations au format OpenDocument +Exec=opendocumentviewer-desktop %f +Icon=org.webodf.OpenDocumentViewer +Terminal=false +Categories=Office;Viewer; +Keywords=OpenDocument;ODF;odt;ods;odp;LibreOffice; +Keywords[fr]=OpenDocument;ODF;odt;ods;odp;LibreOffice;lecteur; +MimeType=application/vnd.oasis.opendocument.text;application/vnd.oasis.opendocument.text-template;application/vnd.oasis.opendocument.spreadsheet;application/vnd.oasis.opendocument.spreadsheet-template;application/vnd.oasis.opendocument.presentation;application/vnd.oasis.opendocument.presentation-template;application/vnd.oasis.opendocument.graphics;application/vnd.oasis.opendocument.graphics-template;application/vnd.oasis.opendocument.formula;application/vnd.oasis.opendocument.text-flat-xml;application/vnd.oasis.opendocument.spreadsheet-flat-xml;application/vnd.oasis.opendocument.presentation-flat-xml;application/vnd.oasis.opendocument.graphics-flat-xml; diff --git a/programs/opendocumentviewer-desktop/data/org.webodf.OpenDocumentViewer.metainfo.xml.in b/programs/opendocumentviewer-desktop/data/org.webodf.OpenDocumentViewer.metainfo.xml.in new file mode 100644 index 000000000..00e5b9468 --- /dev/null +++ b/programs/opendocumentviewer-desktop/data/org.webodf.OpenDocumentViewer.metainfo.xml.in @@ -0,0 +1,70 @@ + + + + org.webodf.OpenDocumentViewer + CC0-1.0 + AGPL-3.0-or-later + OpenDocument Viewer + Lecteur OpenDocument + A reader for OpenDocument files + Un lecteur de fichiers OpenDocument + + +

+ OpenDocument Viewer opens the files of the open standard OpenDocument + and shows them, with no office suite to hand. +

+

+ Le Lecteur OpenDocument ouvre les fichiers au standard ouvert + OpenDocument et les affiche sans avoir besoin d’une suite bureautique. +

+

It opens:

+

Il ouvre :

+
    +
  • texts (.odt, .fodt)
  • +
  • les textes (.odt, .fodt)
  • +
  • spreadsheets (.ods, .fods)
  • +
  • les tableurs (.ods, .fods)
  • +
  • presentations (.odp, .fodp)
  • +
  • les présentations (.odp, .fodp)
  • +
  • drawings (.odg) and formulas (.odf)
  • +
  • les dessins (.odg) et les formules (.odf)
  • +
+

+ OpenDocument Format is the one standard of the office published by the + ISO and free to use: the viewer reads a file whatever wrote it. It is + light, and spares opening a suite of the office or sending the document + who knows where. +

+

+ OpenDocument Format est la seule norme bureautique ISO publiée et + libre : le lecteur peut lire le fichier quel que soit le logiciel qui + l’a écrit. Le lecteur est léger et évite d’ouvrir une suite bureautique + ou d’envoyer les données on ne sait où. +

+
+ org.webodf.OpenDocumentViewer.desktop + https://webodf.org/ + https://github.com/webodf/WebODF/issues + + opendocumentviewer-desktop + + + Office + Viewer + + + + WebODF + + + + + +
diff --git a/programs/opendocumentviewer-desktop/data/org.webodf.OpenDocumentViewer.yml.in b/programs/opendocumentviewer-desktop/data/org.webodf.OpenDocumentViewer.yml.in new file mode 100644 index 000000000..7472c1c25 --- /dev/null +++ b/programs/opendocumentviewer-desktop/data/org.webodf.OpenDocumentViewer.yml.in @@ -0,0 +1,41 @@ +# The manifest of the flatpak of the viewer of OpenDocument. The runtime of +# KDE carries qt and its webengine, so nothing of qt is built here: the viewer +# alone is, from the sources of this tree. +# +# The sandbox is given what a reader of documents needs and no more: a window, +# and the files the reader opens by the portal of the desktop. It reaches no +# network, as a document is drawn on the machine and nothing of it is sent. +app-id: org.webodf.OpenDocumentViewer +runtime: org.kde.Platform +runtime-version: '@VIEWER_FLATPAK_RUNTIME@' +sdk: org.kde.Sdk +# The runtime of KDE carries qt, but not its webengine, which the viewer is +# drawn in: the base app of qt carries that one, and is built into the app. +base: io.qt.qtwebengine.BaseApp +base-version: '@VIEWER_FLATPAK_RUNTIME@' +command: opendocumentviewer-desktop +finish-args: + - --share=ipc + - --socket=wayland + - --socket=fallback-x11 + - --device=dri + - --filesystem=xdg-documents + - --filesystem=xdg-download +modules: + - name: opendocumentviewer + buildsystem: cmake-ninja + # The build stands apart from the sources, which the root of this tree + # asks for; the tool builds in the sources themselves without it. + builddir: true + config-opts: + - -DCMAKE_BUILD_TYPE=Release + - -DWEBODF_DESKTOP=ON + # The library was built outside, by the build that writes this manifest: + # the sandbox has neither node nor java, nor a network to install their + # tools from, and it builds the viewer alone. + - -DWEBODF_PREBUILT_LIBRARY=../webodf.js + sources: + - type: dir + path: '@CMAKE_SOURCE_DIR@' + - type: file + path: '@WEBODF_JS_FILE@' diff --git a/programs/opendocumentviewer-desktop/data/setup-windows.ps1 b/programs/opendocumentviewer-desktop/data/setup-windows.ps1 new file mode 100644 index 000000000..eed6675ad --- /dev/null +++ b/programs/opendocumentviewer-desktop/data/setup-windows.ps1 @@ -0,0 +1,137 @@ +# Installs what the viewer of the desktop is built with on windows, so that a +# machine that has just been installed, in a virtual one or not, builds it. +# +# It is run once, in a terminal opened as an administrator: +# +# powershell -ExecutionPolicy Bypass -File setup-windows.ps1 +# +# What it installs is what "README-Products.md" lists: the compiler of +# Microsoft, since Qt WebEngine does not compile with MinGW, Qt with the +# modules the viewer links to, and the tools the build runs, node among them. +# Everything comes from the maker of each, over https, and nothing is taken +# from this repository. + +[CmdletBinding()] +param( + [string] $QtVersion = "6.8.2", + [string] $QtDirectory = "C:\Qt", + # The compiler weighs several gigabytes: a machine that already has Visual + # Studio skips it. + [switch] $SkipCompiler +) + +$ErrorActionPreference = "Stop" + +function Say([string] $words) { + Write-Host "==> $words" -ForegroundColor Cyan +} + +function Fail([string] $words) { + Write-Host "!!! $words" -ForegroundColor Red + exit 1 +} + +if (-not ([Security.Principal.WindowsPrincipal] ` + [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole( + [Security.Principal.WindowsBuiltInRole]::Administrator)) { + Fail "This script installs programs, so it is run as an administrator." +} + +if (-not (Get-Command winget -ErrorAction SilentlyContinue)) { + Fail ("Winget was not found. It comes with windows 11 and with the recent" + + " windows 10; on an older one, install 'App Installer' from the store.") +} + +# The tools of the build. Winget answers 0 when it installed something, and a +# code of its own when the program is already there, which is not a failure. +$tools = @( + "Git.Git", + "Kitware.CMake", + "Ninja-build.Ninja", + "OpenJS.NodeJS.LTS", + "EclipseAdoptium.Temurin.21.JRE", + "Python.Python.3.12", + "JRSoftware.InnoSetup" +) +foreach ($tool in $tools) { + Say "Installing $tool" + winget install --id $tool --exact --silent --accept-package-agreements ` + --accept-source-agreements --disable-interactivity | Out-Host +} + +if (-not $SkipCompiler) { + # Only the workload of C++ is asked for, not the whole of Visual Studio. + Say "Installing the build tools of Visual Studio 2022" + winget install --id Microsoft.VisualStudio.2022.BuildTools --exact ` + --silent --accept-package-agreements --accept-source-agreements ` + --disable-interactivity --override ("--quiet --wait --norestart" + + " --add Microsoft.VisualStudio.Workload.VCTools --includeRecommended") | + Out-Host +} + +# The paths of what was just installed are not in this session yet. +$env:Path = [System.Environment]::GetEnvironmentVariable("Path", "Machine") + + ";" + [System.Environment]::GetEnvironmentVariable("Path", "User") + +# Qt is installed with aqtinstall, that reads the repository of Qt and takes +# the archives from it: the installer of Qt asks for an account, which a script +# cannot answer for. +Say "Installing aqtinstall" +python -m pip install --upgrade --quiet pip aqtinstall | Out-Host + +# Since Qt 6.8, WebEngine is an extension rather than a module, and it lives in +# a repository of its own: aqt is told to take it as a module all the same, and +# what it wrote is checked below. +Say "Installing Qt $QtVersion with WebEngine, in $QtDirectory" +python -m aqt install-qt windows desktop $QtVersion win64_msvc2022_64 ` + --outputdir $QtDirectory ` + --modules qtwebengine qtwebchannel qtpositioning | Out-Host + +$prefix = Join-Path $QtDirectory "$QtVersion\msvc2022_64" +$webengine = Join-Path $prefix "lib\cmake\Qt6WebEngineWidgets" +if (-not (Test-Path $webengine)) { + Write-Host "" + Fail ("Qt was installed in $prefix, but WebEngine was not: it is the" + + " extension 'extensions.qtwebengine.$($QtVersion -replace '\.','')" + + ".win64_msvc2022_64'. Install it with the online installer of Qt, or" + + " with a newer aqtinstall, and run this script again.") +} + +Say "What is installed" +$found = [ordered] @{} +foreach ($pair in @( + @("git", "git --version"), + @("cmake", "cmake --version"), + @("ninja", "ninja --version"), + @("node", "node --version"), + @("npm", "npm --version"), + @("java", "java -version"))) { + try { + $line = (Invoke-Expression ($pair[1] + " 2>&1") | Select-Object -First 1) + } catch { + $line = "not found" + } + $found[$pair[0]] = $line +} +$found["qt"] = $prefix +$found.GetEnumerator() | ForEach-Object { + "{0,-8} {1}" -f $_.Key, $_.Value +} + +Write-Host "" +Say "The viewer is built from a terminal of the compiler:" +Write-Host @" + "C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools\VC\Auxiliary\Build\vcvars64.bat" + cd \path\to\webodf + npm install + cmake -S . -B build -G Ninja -DWEBODF_DESKTOP=ON -DWEBODF_QTJSRUNTIME=ON -DCMAKE_PREFIX_PATH=$prefix + + rem The tests of the library, run in the webengine of qt: the whole suite of + rem the browser, which is what tells that the library behaves here as it does + rem elsewhere. + cmake --build build --target test-qtjsruntime + + cmake --build build --target product-opendocumentviewer-desktop + cmake --install build --prefix build\dist + iscc build\programs\opendocumentviewer-desktop\opendocumentviewer.iss +"@ diff --git a/programs/opendocumentviewer-desktop/main.cpp b/programs/opendocumentviewer-desktop/main.cpp new file mode 100644 index 000000000..dce447bd2 --- /dev/null +++ b/programs/opendocumentviewer-desktop/main.cpp @@ -0,0 +1,107 @@ +/** + * Copyright (C) 2026 Daniel Berthereau + * + * @licstart + * This file is part of WebODF. + * + * WebODF is free software: you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License (GNU AGPL) as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * WebODF is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU Affero General Public License for more + * details. + * + * You should have received a copy of the GNU Affero General Public License + * along with WebODF. If not, see . + * @licend + * + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ + */ + +/** + * A viewer of OpenDocument for the desktop, on linux, on windows and on macos. + * + * It is the same library, drawn in the webengine of qt, as the extensions for + * the browsers and the viewer for android use: a document is read the same way + * everywhere, and there is one place where the reading of the format lives. + */ + +#include "viewerscheme.h" +#include "mainwindow.h" + +#include +#include +#include +#include +#include + +namespace { + +/** + * Hear the documents macos hands over. + * + * A double click, or "Open with", does not name the document on the command + * line there: the system sends it to the application, once it is running, as + * an event of its own. Without this, the viewer would open its empty window + * and forget what it was asked for. + */ +class Documents : public QObject { +public: + Documents(QObject* parent, MainWindow& window) + : QObject(parent), + viewer(window) { + } + +protected: + bool eventFilter(QObject* watched, QEvent* event) override { + if (event->type() == QEvent::FileOpen) { + viewer.open(static_cast(event)->file()); + return true; + } + return QObject::eventFilter(watched, event); + } + +private: + MainWindow& viewer; +}; + +} // namespace + +int main(int argc, char** argv) { + // The scheme the document is served at has to be declared before the + // application is built, as webengine reads the schemes once, when it + // starts. + ViewerScheme::registerScheme(); + + QApplication app(argc, argv); + // The name of the organisation is what the settings of the window are kept + // under, and it is the domain of the project rather than a company: the + // viewer belongs to no one. + app.setOrganizationName("webodf.org"); + app.setApplicationName("opendocumentviewer"); + app.setApplicationDisplayName("OpenDocument Viewer"); + app.setApplicationVersion(OPENDOCUMENTVIEWER_VERSION); + app.setDesktopFileName("org.webodf.OpenDocumentViewer"); + // The icon is in the program, so the window carries it even where no + // desktop serves one, on windows and on macos. + app.setWindowIcon(QIcon(":/icon.png")); + + MainWindow window; + window.show(); + + // Only macos sends this event; elsewhere the filter is never called. + app.installEventFilter(new Documents(&app, window)); + + // A document may be given on the command line, which is how a file manager + // opens one, and the rest of the arguments are left to qt. + const QStringList arguments = app.arguments(); + if (arguments.size() > 1 && !arguments.at(1).startsWith("-")) { + window.open(arguments.at(1)); + } + + return app.exec(); +} diff --git a/programs/opendocumentviewer-desktop/mainwindow.cpp b/programs/opendocumentviewer-desktop/mainwindow.cpp new file mode 100644 index 000000000..67c764f83 --- /dev/null +++ b/programs/opendocumentviewer-desktop/mainwindow.cpp @@ -0,0 +1,499 @@ +/** + * Copyright (C) 2026 Daniel Berthereau + * + * @licstart + * This file is part of WebODF. + * + * WebODF is free software: you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License (GNU AGPL) as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * WebODF is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU Affero General Public License for more + * details. + * + * You should have received a copy of the GNU Affero General Public License + * along with WebODF. If not, see . + * @licend + * + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ + */ + +#include "mainwindow.h" + +#include "viewerscheme.h" +#include "words.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +Q_LOGGING_CATEGORY(viewerLog, "webodf.viewer") + +namespace { + +/** The name every document of the format is read under. */ +const char* documentFilter = + "*.odt *.ott *.ods *.ots *.odp *.otp *.odg *.otg *.odf *.fodt *.fods" + " *.fodp *.fodg"; + +/** + * A page that writes what the document says on the terminal. A document that + * cannot be drawn says so in the page as well, so this is for the one who + * builds the viewer, not for the one who reads a document with it: it is quiet + * unless the category "webodf.viewer" is turned on. + */ +class ViewerPage : public QWebEnginePage { +public: + ViewerPage(QObject* parent, const std::function& onAbout) + : QWebEnginePage(parent), + about(onAbout) { + } + +protected: + /** + * The page holds one link, the one of the page of the format: it is opened + * in a window of its own rather than in place of the document. + */ + bool acceptNavigationRequest(const QUrl& url, NavigationType /*type*/, + bool /*isMainFrame*/) override { + if (url.path().startsWith("/about")) { + about(); + return false; + } + return true; + } + + void javaScriptConsoleMessage(JavaScriptConsoleMessageLevel level, + const QString& message, int lineNumber, + const QString& sourceID) override { + if (level == ErrorMessageLevel) { + qCWarning(viewerLog) << sourceID << lineNumber << message; + } else { + qCDebug(viewerLog) << sourceID << lineNumber << message; + } + } + +private: + const std::function about; +}; + +/** + * A page of prose, whose links leave for the browser of the system: the viewer + * shows documents, and it is no browser. + */ +class ReadingPage : public QWebEnginePage { +public: + explicit ReadingPage(QObject* parent) + : QWebEnginePage(parent) { + } + +protected: + bool acceptNavigationRequest(const QUrl& url, NavigationType type, + bool /*isMainFrame*/) override { + if (type == NavigationTypeLinkClicked && url.scheme() != "odf") { + QDesktopServices::openUrl(url); + return false; + } + return true; + } +}; + +/** + * Tell whether a file is a document of the format, by its first bytes. + * + * A document of OpenDocument is a zip whose first entry is its type, stored + * uncompressed, so the name of the format is read in the head of the file; a + * document may also be written as one xml file, ".fodt" and its kin. The check + * is made here rather than left to the library, that draws what it is given and + * waits for a document that never comes when it is not one, see "refreshOdf" in + * "OdfCanvas.js". + */ +bool isDocument(const QString& path) { + QFile file(path); + if (!file.open(QIODevice::ReadOnly)) { + return false; + } + const QByteArray head = file.read(128); + return (head.startsWith("PK") + && head.contains("application/vnd.oasis.opendocument")) + || head.startsWith(" urls = data->urls(); + for (const QUrl& url : urls) { + if (url.isLocalFile()) { + return url.toLocalFile(); + } + } + return QString(); +} + +} // namespace + +MainWindow::MainWindow(QWidget* parent) + : QMainWindow(parent), + view(new QWebEngineView(this)), + server(new ViewerScheme(this)), + keeper(new QTimer(this)), + reading(nullptr), + closeAction(nullptr) { + + QWebEngineProfile::defaultProfile()->installUrlSchemeHandler("odf", server); + + view->setPage(new ViewerPage(view, [this] { aboutFormat(); })); + view->settings()->setAttribute(QWebEngineSettings::PluginsEnabled, false); + view->settings()->setAttribute(QWebEngineSettings::ErrorPageEnabled, false); +#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0) + // A document is shown in the colours it was written in. Chromium is able to + // turn a page it finds too bright into a dark one, by changing the colours + // as it paints them, which no style sheet can undo and which would make a + // document read differently from the way it prints: it is turned off here + // whatever the theme of the system is. + view->settings()->setAttribute(QWebEngineSettings::ForceDarkMode, false); +#endif + // Nothing is typed in a document that is only read, and the page holds no + // field, so the menus of the web are left out. + view->setContextMenuPolicy(Qt::NoContextMenu); + setCentralWidget(view); + setAcceptDrops(true); + + createMenus(); + statusBar(); + + // What a report of a document that reads badly needs, beside the line the + // page writes when it has drawn one. + qCDebug(viewerLog) << "qt" << qVersion() << "platform" + << QGuiApplication::platformName(); + + // The window is kept a moment after it was last moved or resized, rather + // than when it is closed: a program that is killed, from the terminal it + // was started in for instance, never sees its window closed, and the place + // of the window would be lost every time. + keeper->setSingleShot(true); + keeper->setInterval(1000); + connect(keeper, &QTimer::timeout, this, &MainWindow::saveGeometry); + + // A document may be given before the page is there to draw it, when the + // viewer is started with one, so it is drawn once the page has loaded. + connect(view, &QWebEngineView::loadFinished, this, [this](bool ok) { + if (ok && !path.isEmpty()) { + ask("load()"); + } + }); + view->setUrl(QUrl(ViewerScheme::pageUrl)); + + // A window that was closed opens as it was left. A first one is made large + // enough to show a page of A4 whole, which is 794 pixels wide at the size + // it was written at, and no larger than the screen it opens on: the size + // the view asks for is the one of an empty page, a few pixels. + const QSettings settings; + if (!restoreGeometry(settings.value("geometry").toByteArray())) { + const QRect screen = QGuiApplication::primaryScreen()->availableGeometry(); + QRect wanted(QPoint(), QSize(qMin(900, int(screen.width() * 0.8)), + qMin(1150, int(screen.height() * 0.9)))); + wanted.moveCenter(screen.center()); + setGeometry(wanted); + } + setWindowTitle(QApplication::applicationDisplayName()); +} + +void MainWindow::createMenus() { + QMenu* const file = menuBar()->addMenu(words::of("&File", "&Fichier")); + QAction* const openAction = file->addAction( + words::of("&Open…", "&Ouvrir…"), QKeySequence::Open, + this, &MainWindow::chooseDocument); + openAction->setStatusTip(words::of("Read a document of the OpenDocument" + " format", "Lire un document au format OpenDocument")); + closeAction = file->addAction(words::of("&Close", "&Fermer"), + QKeySequence::Close, this, &MainWindow::closeDocument); + closeAction->setStatusTip(words::of("Put the document away", + "Refermer le document")); + closeAction->setEnabled(false); + file->addSeparator(); + file->addAction(words::of("&Print…", "Im&primer…"), QKeySequence::Print, + this, &MainWindow::printDocument); + file->addAction(words::of("Export as &PDF…", "Exporter en &PDF…"), + this, &MainWindow::exportPdf); + file->addSeparator(); + file->addAction(words::of("&Quit", "&Quitter"), QKeySequence::Quit, + this, &QWidget::close); + + // The zoom is the one of the library, that draws the page at the size it + // asks for: the numbers are kept in the page, see "viewer.js". + QMenu* const display = menuBar()->addMenu( + words::of("&Display", "&Affichage")); + display->addAction(words::of("Zoom &in", "Zoom a&vant"), + QKeySequence::ZoomIn, this, [this] { ask("zoomBy(1.25)"); }); + display->addAction(words::of("Zoom &out", "Zoom a&rrière"), + QKeySequence::ZoomOut, this, [this] { ask("zoomBy(0.8)"); }); + display->addAction(words::of("&Actual size", "&Taille réelle"), + QKeySequence(Qt::CTRL | Qt::Key_0), this, [this] { ask("setZoom(1)"); }); + display->addAction(words::of("Fit to &width", "Ajuster à la &largeur"), + QKeySequence(Qt::CTRL | Qt::Key_9), this, [this] { ask("fit()"); }); + + // How the pages are laid out: one under another as a document is + // scrolled, or two beside one another as a book is read, where the first + // page faces nothing of its own. + display->addSeparator(); + QActionGroup* const rows = new QActionGroup(this); + QAction* const onePage = display->addAction( + words::of("&One page", "Une &page"), this, + [this] { ask("setPages(1, false)"); }); + QAction* const twoPages = display->addAction( + words::of("&Two pages", "&Deux pages"), this, + [this] { ask("setPages(2, false)"); }); + QAction* const twoPagesRight = display->addAction( + words::of("Two pages, first on the &right", + "Deux pages, la p&remière à droite"), this, + [this] { ask("setPages(2, true)"); }); + for (QAction* const one : {onePage, twoPages, twoPagesRight}) { + one->setCheckable(true); + rows->addAction(one); + } + onePage->setChecked(true); + + QMenu* const help = menuBar()->addMenu(words::of("&Help", "Aid&e")); + help->addAction(words::of("About &OpenDocument and this viewer", + "À propos d’&OpenDocument et du lecteur"), + this, &MainWindow::aboutFormat); + help->addAction(words::of("&About", "À &propos"), this, &MainWindow::about); +} + +void MainWindow::ask(const QString& call) { + view->page()->runJavaScript("window.viewer && window.viewer." + call); +} + +bool MainWindow::open(const QString& wanted) { + const QFileInfo info(wanted); + if (!info.isReadable() || !info.isFile()) { + QMessageBox::warning(this, QApplication::applicationDisplayName(), + words::of("This document cannot be read: ", + "Ce document ne peut pas être lu : ") + + info.filePath()); + return false; + } + if (!isDocument(info.absoluteFilePath())) { + QMessageBox::warning(this, QApplication::applicationDisplayName(), + words::of("This file is not a document of the OpenDocument" + " format: ", + "Ce fichier n’est pas un document au format" + " OpenDocument : ") + + info.fileName()); + return false; + } + path = info.absoluteFilePath(); + setLastDirectory(info.absolutePath()); + server->setPath(path); + setWindowTitle(info.fileName() + " — " + + QApplication::applicationDisplayName()); + statusBar()->showMessage(path); + closeAction->setEnabled(true); + // The page reads the document at a single address, so it is told to read it + // again rather than sent to another one. + ask("load()"); + return true; +} + +void MainWindow::closeDocument() { + if (path.isEmpty()) { + return; + } + path.clear(); + server->setPath(QString()); + closeAction->setEnabled(false); + setWindowTitle(QApplication::applicationDisplayName()); + statusBar()->clearMessage(); + ask("unload()"); +} + +QString MainWindow::lastDirectory() const { + // The directory of the last document, so that the next one is looked for + // where the last one was found rather than where the program was started + // from, which is a directory of the build or of the system. + const QSettings settings; + const QString kept = settings.value("directory").toString(); + if (!kept.isEmpty() && QFileInfo(kept).isDir()) { + return kept; + } + const QString documents = QStandardPaths::writableLocation( + QStandardPaths::DocumentsLocation); + return documents.isEmpty() + ? QDir::homePath() + : documents; +} + +void MainWindow::setLastDirectory(const QString& directory) { + QSettings settings; + settings.setValue("directory", directory); +} + +void MainWindow::chooseDocument() { + const QString chosen = QFileDialog::getOpenFileName(this, + words::of("Open a document", "Ouvrir un document"), + lastDirectory(), + words::of("Documents of the OpenDocument format (", + "Documents au format OpenDocument (") + + documentFilter + ");;" + + words::of("Every file (*)", "Tous les fichiers (*)")); + if (!chosen.isEmpty()) { + open(chosen); + } +} + +void MainWindow::exportPdf() { + if (path.isEmpty()) { + return; + } + const QString chosen = QFileDialog::getSaveFileName(this, + words::of("Export as PDF", "Exporter en PDF"), + lastDirectory() + "/" + QFileInfo(path).completeBaseName() + ".pdf", + "*.pdf"); + if (chosen.isEmpty()) { + return; + } + setLastDirectory(QFileInfo(chosen).absolutePath()); + QMetaObject::Connection* const connection = new QMetaObject::Connection; + *connection = connect(view->page(), &QWebEnginePage::pdfPrintingFinished, + this, [this, connection](const QString& file, bool ok) { + disconnect(*connection); + delete connection; + statusBar()->showMessage(ok + ? words::of("Written: ", "Écrit : ") + file + : words::of("This file cannot be written: ", + "Ce fichier ne peut pas être écrit : ") + file); + }); + view->page()->printToPdf(chosen); +} + +void MainWindow::printDocument() { + if (path.isEmpty()) { + return; + } + QPrinter printer(QPrinter::HighResolution); + QPrintDialog dialog(&printer, this); + if (dialog.exec() == QDialog::Accepted) { + view->print(&printer); + } +} + +void MainWindow::aboutFormat() { + // One window at a time: a second call brings the one that is open forward. + if (!reading) { + QWebEngineView* const page = new QWebEngineView(); + page->setPage(new ReadingPage(page)); + page->setAttribute(Qt::WA_DeleteOnClose); + page->setWindowTitle(words::of("About OpenDocument and this viewer", + "À propos d’OpenDocument et du lecteur")); + page->resize(QSize(760, 900).boundedTo( + QGuiApplication::primaryScreen()->availableSize())); + page->setUrl(QUrl(QString("odf:/") + + words::of("about.en.html", "about.fr.html"))); + connect(page, &QObject::destroyed, this, [this] { + reading = nullptr; + }); + reading = page; + } + reading->show(); + reading->raise(); + reading->activateWindow(); +} + +void MainWindow::about() { + QMessageBox::about(this, words::of("About", "À propos"), + "

" + QApplication::applicationDisplayName() + " " + + QApplication::applicationVersion() + "

" + + words::of( + "Reads the documents of the OpenDocument standard written by" + " LibreOffice and by every other office suite: text (.odt)," + " spreadsheets (.ods) and presentations (.odp).", + "Lit les documents au standard OpenDocument créés par LibreOffice" + " et toutes les suites bureautiques : textes (.odt), tableurs" + " (.ods) et présentations (.odp).") + + "

" + + words::of( + "OpenDocument is the first format of office documents approved as" + " an international standard, ISO/IEC 26300, and the only one that" + " is shareable, lasting and independent of any company.", + "OpenDocument est le premier format de documents bureautiques" + " approuvé comme norme internationale, ISO/IEC 26300, et le seul" + " qui soit partageable, durable et indépendant d’une entreprise.") + + "

" + + words::of("It draws the documents with WebODF, a library that reads" + " the format in javascript, and it is free software, under the" + " AGPL 3 license.", + "Il affiche les documents avec WebODF, une bibliothèque qui lit le" + " format en javascript, et c’est un logiciel libre, sous licence" + " AGPL 3.") + // The certificate of the site does not cover "www", so the name is + // written without it. + + "

webodf.org · " + + words::of("OpenDocument on Wikipedia", "OpenDocument sur Wikipédia") + + "

"); +} + +void MainWindow::saveGeometry() { + QSettings settings; + settings.setValue("geometry", QMainWindow::saveGeometry()); +} + +void MainWindow::closeEvent(QCloseEvent* event) { + saveGeometry(); + event->accept(); +} + +void MainWindow::moveEvent(QMoveEvent* event) { + QMainWindow::moveEvent(event); + keeper->start(); +} + +void MainWindow::resizeEvent(QResizeEvent* event) { + QMainWindow::resizeEvent(event); + keeper->start(); +} + +void MainWindow::dragEnterEvent(QDragEnterEvent* event) { + if (!droppedDocument(event->mimeData()).isEmpty()) { + event->acceptProposedAction(); + } +} + +void MainWindow::dropEvent(QDropEvent* event) { + const QString dropped = droppedDocument(event->mimeData()); + if (!dropped.isEmpty()) { + event->acceptProposedAction(); + open(dropped); + } +} diff --git a/programs/opendocumentviewer-desktop/mainwindow.h b/programs/opendocumentviewer-desktop/mainwindow.h new file mode 100644 index 000000000..296e0fd54 --- /dev/null +++ b/programs/opendocumentviewer-desktop/mainwindow.h @@ -0,0 +1,92 @@ +/** + * Copyright (C) 2026 Daniel Berthereau + * + * @licstart + * This file is part of WebODF. + * + * WebODF is free software: you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License (GNU AGPL) as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * WebODF is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU Affero General Public License for more + * details. + * + * You should have received a copy of the GNU Affero General Public License + * along with WebODF. If not, see . + * @licend + * + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ + */ + +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include +#include + +class ViewerScheme; +class QAction; +class QTimer; +class QWebEngineView; + +/** + * The window of the viewer: a menu, a page that draws the document, and the + * name of the document in the title. + * + * The document is drawn by the library, in the webengine of qt, which is the + * same code as the one of the extensions for the browsers and of the viewer for + * android: what is written here is the shell a document is opened, zoomed and + * printed from. + */ +class MainWindow : public QMainWindow { + Q_OBJECT +public: + explicit MainWindow(QWidget* parent = nullptr); + + /** Open a document, and tell whether it could be read. */ + bool open(const QString& path); + +protected: + void closeEvent(QCloseEvent* event) override; + void dragEnterEvent(QDragEnterEvent* event) override; + void dropEvent(QDropEvent* event) override; + +protected: + void moveEvent(QMoveEvent* event) override; + void resizeEvent(QResizeEvent* event) override; + +private slots: + void chooseDocument(); + /** Put the document away, leaving the window as it opens with none. */ + void closeDocument(); + /** Show the page that tells what the format is worth and what this does. */ + void aboutFormat(); + void exportPdf(); + void printDocument(); + void about(); + +private: + void createMenus(); + /** Keep the place and the size of the window for the next time. */ + void saveGeometry(); + /** The directory a document was last read from or written to. */ + QString lastDirectory() const; + void setLastDirectory(const QString& directory); + /** Call one of the functions the page of the viewer offers. */ + void ask(const QString& call); + + QWebEngineView* view; + ViewerScheme* server; + QTimer* keeper; + /** The window of the page above, kept so that one is opened at a time. */ + QWidget* reading; + /** Turned off while no document is open, as there is none to close. */ + QAction* closeAction; + QString path; +}; + +#endif diff --git a/programs/opendocumentviewer-desktop/packaging.cmake b/programs/opendocumentviewer-desktop/packaging.cmake new file mode 100644 index 000000000..339ffb7d1 --- /dev/null +++ b/programs/opendocumentviewer-desktop/packaging.cmake @@ -0,0 +1,386 @@ +# Copyright (C) 2026 Daniel Berthereau +# +# @licstart +# This file is part of WebODF. +# +# WebODF is free software: you can redistribute it and/or modify it under the +# terms of the GNU Affero General Public License (GNU AGPL) as published by the +# Free Software Foundation, either version 3 of the License, or (at your +# option) any later version. +# +# WebODF is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more +# details. +# +# You should have received a copy of the GNU Affero General Public License +# along with WebODF. If not, see . +# @licend +# +# @source: https://webodf.org/ +# @source: https://github.com/webodf/WebODF/ + +# The ways the viewer of the desktop is handed over, from the shortest to the +# most finished: +# +# * an archive, that is unpacked by hand and reads the qt of the machine; +# * a package, "deb" or "rpm", that a system installs and keeps up to date, +# and that names the qt it needs rather than carrying it; +# * a universal package, an AppImage or a flatpak, that carries qt itself and +# runs on any distribution. +# +# Each one is a target of its own and each one is a product of the build, so +# "products" makes the ones the tools of this machine allow and names the +# others at the end, with the tool that is wanting. The tools are looked for +# rather than required: a machine that has none of them still builds the +# viewer. + +set(VIEWER_STAGE ${CMAKE_CURRENT_BINARY_DIR}/stage) +set(VIEWER_PRODUCTS ${CMAKE_BINARY_DIR}/products) +set(VIEWER_ID org.webodf.OpenDocumentViewer) + +# The version of a package is read by a machine, so it holds numbers alone: +# "0.5.10-274-gabc1234" of git becomes "0.5.10.274". +string(REGEX MATCH "^[0-9]+\\.[0-9]+\\.[0-9]+" VIEWER_VERSION ${WEBODF_VERSION}) +if (NOT VIEWER_VERSION) + set(VIEWER_VERSION "0.0.0") +endif () +string(REGEX MATCH "-([0-9]+)-g" VIEWER_COMMITS ${WEBODF_VERSION}) +if (CMAKE_MATCH_1) + set(VIEWER_VERSION "${VIEWER_VERSION}.${CMAKE_MATCH_1}") +endif () + +if (CMAKE_SYSTEM_PROCESSOR) + set(VIEWER_ARCH ${CMAKE_SYSTEM_PROCESSOR}) +else () + set(VIEWER_ARCH "x86_64") +endif () + +if (WIN32) + set(VIEWER_ARCHIVE + ${VIEWER_PRODUCTS}/opendocumentviewer-${WEBODF_VERSION}-windows-${VIEWER_ARCH}.zip) + set(VIEWER_ARCHIVE_FORMAT --format=zip) +elseif (APPLE) + set(VIEWER_ARCHIVE + ${VIEWER_PRODUCTS}/opendocumentviewer-${WEBODF_VERSION}-macos-${VIEWER_ARCH}.tar.gz) + set(VIEWER_ARCHIVE_FORMAT "") +else () + set(VIEWER_ARCHIVE + ${VIEWER_PRODUCTS}/opendocumentviewer-${WEBODF_VERSION}-linux-${VIEWER_ARCH}.tar.gz) + set(VIEWER_ARCHIVE_FORMAT "") +endif () + +# The archive holds what "cmake --install" writes: the program, the entry of +# the menu and the icon, and beside them the libraries of qt on the systems +# whose tool gathers them. +add_custom_target(package-archive + COMMAND ${CMAKE_COMMAND} -E rm -rf ${VIEWER_STAGE} + COMMAND ${CMAKE_COMMAND} -E make_directory ${VIEWER_PRODUCTS} + COMMAND ${CMAKE_COMMAND} --install ${CMAKE_BINARY_DIR} + --prefix ${VIEWER_STAGE} + COMMAND ${CMAKE_COMMAND} -E chdir ${VIEWER_STAGE} + ${CMAKE_COMMAND} -E tar czf ${VIEWER_ARCHIVE} ${VIEWER_ARCHIVE_FORMAT} . + COMMAND ${CMAKE_COMMAND} -E echo "Wrote ${VIEWER_ARCHIVE}" + DEPENDS opendocumentviewer-desktop + COMMENT "The archive of the viewer of the desktop") + +# The archive is the way the viewer of the desktop is handed over without a +# tool of a distribution: it stands among the products beside the add-ons and +# the editors, as every other way of handing it over does. +WEBODF_PRODUCT(package-archive ${VIEWER_ARCHIVE}) + +# A package of a system, that names the qt it needs rather than carrying it. +# CPack writes it from the same "install" rules as everything else. +set(CPACK_PACKAGE_NAME "opendocumentviewer") +set(CPACK_PACKAGE_VERSION ${VIEWER_VERSION}) +set(CPACK_PACKAGE_DESCRIPTION_SUMMARY + "A reader of the OpenDocument format, that draws a document as it is printed") +set(CPACK_PACKAGE_VENDOR "webodf.org") +set(CPACK_PACKAGE_CONTACT "webodf@nlnet.nl") +set(CPACK_PACKAGE_HOMEPAGE_URL "https://webodf.org/") +# The name is kept apart as well: "include(CPack)" sets the variable of CPack +# back to the name of a package of the sources, so what is read after it is +# not what the package was named. +set(VIEWER_PACKAGE_NAME "opendocumentviewer-${WEBODF_VERSION}-${VIEWER_ARCH}") +set(CPACK_PACKAGE_FILE_NAME ${VIEWER_PACKAGE_NAME}) +# CPack leaves a directory of its own beside what it writes, so it writes +# aside and the package alone is copied among the products. +set(VIEWER_CPACK_DIR ${CMAKE_CURRENT_BINARY_DIR}/packages) +set(CPACK_PACKAGE_DIRECTORY ${VIEWER_CPACK_DIR}) +set(CPACK_RESOURCE_FILE_LICENSE ${CMAKE_SOURCE_DIR}/AGPL-3.0.txt) +set(CPACK_STRIP_FILES TRUE) +# The names of the packages of Debian and of Fedora for the same libraries. +set(CPACK_DEBIAN_PACKAGE_SECTION "text") +set(CPACK_DEBIAN_PACKAGE_DEPENDS + "libqt6core6, libqt6gui6, libqt6widgets6, libqt6webenginewidgets6, libqt6webenginecore6") +set(CPACK_RPM_PACKAGE_LICENSE "AGPL-3.0") +set(CPACK_RPM_PACKAGE_GROUP "Applications/Publishing") +set(CPACK_RPM_PACKAGE_REQUIRES "qt6-qtbase, qt6-qtwebengine") +include(CPack) + +find_program(DPKG_DEB dpkg-deb) +if (DPKG_DEB) + add_custom_target(package-deb + COMMAND ${CMAKE_COMMAND} -E make_directory ${VIEWER_PRODUCTS} + COMMAND ${CMAKE_CPACK_COMMAND} -G DEB + --config ${CMAKE_BINARY_DIR}/CPackConfig.cmake + COMMAND ${CMAKE_COMMAND} -DDIR=${VIEWER_CPACK_DIR} + -DDEST=${VIEWER_PRODUCTS} -DEXT=deb + -DNAME=${VIEWER_PACKAGE_NAME} + -P ${CMAKE_CURRENT_SOURCE_DIR}/data/copy-packages.cmake + DEPENDS opendocumentviewer-desktop + COMMENT "The package of debian of the viewer of the desktop") + WEBODF_PRODUCT_MADE(package-deb + "opendocumentviewer-${WEBODF_VERSION}-${VIEWER_ARCH}.deb") +else () + WEBODF_PRODUCT_MISSING("the package of debian" "dpkg-deb") +endif () + +find_program(RPMBUILD rpmbuild) +if (RPMBUILD) + add_custom_target(package-rpm + COMMAND ${CMAKE_COMMAND} -E make_directory ${VIEWER_PRODUCTS} + COMMAND ${CMAKE_CPACK_COMMAND} -G RPM + --config ${CMAKE_BINARY_DIR}/CPackConfig.cmake + COMMAND ${CMAKE_COMMAND} -DDIR=${VIEWER_CPACK_DIR} + -DDEST=${VIEWER_PRODUCTS} -DEXT=rpm + -DNAME=${VIEWER_PACKAGE_NAME} + -P ${CMAKE_CURRENT_SOURCE_DIR}/data/copy-packages.cmake + DEPENDS opendocumentviewer-desktop + COMMENT "The package of fedora of the viewer of the desktop") + WEBODF_PRODUCT_MADE(package-rpm + "opendocumentviewer-${WEBODF_VERSION}-${VIEWER_ARCH}.rpm") +else () + WEBODF_PRODUCT_MISSING("the package of fedora" "rpmbuild") +endif () + +# A universal package carries qt with it and runs on any distribution, where a +# package of a system names the qt of that system. Two of them are made, as +# neither is the one everyone has: an AppImage is one file that is run as it +# is, a flatpak is installed from a repository and runs in a sandbox. +if (UNIX AND NOT APPLE) + set(VIEWER_APPDIR ${CMAKE_CURRENT_BINARY_DIR}/AppDir) + set(VIEWER_APPIMAGE + ${VIEWER_PRODUCTS}/OpenDocumentViewer-${WEBODF_VERSION}-${VIEWER_ARCH}.AppImage) + + # The tool of the AppImage gathers the libraries an executable reads, and + # the one of qt gathers what the webengine reads beside them, the + # process of the engine among the rest. + # The plugin of qt is told where qmake is: it reads from it where the + # plugins of the platform, the images and the media stand, and it looks + # for "qmake" alone, which is the one of qt 5 where both are installed. + # An empty value is worse than none, as the plugin then looks no further + # and stops with "Could not find qmake". + find_program(QMAKE6 NAMES qmake6 qmake + PATHS /usr/lib/qt6/bin /usr/lib/x86_64-linux-gnu/qt6/bin) + if (QMAKE6) + set(VIEWER_QMAKE QMAKE=${QMAKE6}) + else () + # The variable is left undefined and not set to nothing: a variable + # that holds nothing is still one argument, and the plugin reads it + # as a qmake of no name and looks no further. + unset(VIEWER_QMAKE) + endif () + find_program(LINUXDEPLOY linuxdeploy) + find_program(LINUXDEPLOY_QT linuxdeploy-plugin-qt) + find_program(APPIMAGETOOL appimagetool) + + if (LINUXDEPLOY) + add_custom_target(package-appimage + COMMAND ${CMAKE_COMMAND} -E rm -rf ${VIEWER_APPDIR} + COMMAND ${CMAKE_COMMAND} -E make_directory ${VIEWER_PRODUCTS} + COMMAND ${CMAKE_COMMAND} --install ${CMAKE_BINARY_DIR} + --prefix ${VIEWER_APPDIR}/usr + # LDAI_OUTPUT is the name the plugin of the AppImage reads + # today, OUTPUT the one it read before and warns against. The + # desktop file is named rather than left to be found: the tool + # takes the first one of the directory otherwise, which is this + # one alone today and would be another the day a second is + # installed. + # The tool of the AppImage looks for the metadata under the + # name it bore before, "appdata.xml", and warns where it finds + # none: the file is written under both names, the one of today + # and the one of yesterday. + COMMAND ${CMAKE_COMMAND} -E copy + ${VIEWER_APPDIR}/usr/share/metainfo/${VIEWER_ID}.metainfo.xml + ${VIEWER_APPDIR}/usr/share/metainfo/${VIEWER_ID}.appdata.xml + COMMAND ${CMAKE_COMMAND} -E env + LDAI_OUTPUT=${VIEWER_APPIMAGE} + ${VIEWER_QMAKE} + # The plugin of qt looks for the modules of qml a program + # asks for and says it found none: the viewer holds no qml of + # its own, and the qml of qt is carried all the same, as the + # engine of the web leans on it. The warning is looked for + # and left alone: there is nothing to scan, and skipping the + # module would take from the engine what it runs on. + QT_SKIP_MODULES=qml + ${LINUXDEPLOY} --appdir ${VIEWER_APPDIR} + --desktop-file + ${VIEWER_APPDIR}/usr/share/applications/org.webodf.OpenDocumentViewer.desktop + --plugin qt --output appimage + COMMAND ${CMAKE_COMMAND} -E echo "Wrote ${VIEWER_APPIMAGE}" + DEPENDS opendocumentviewer-desktop + COMMENT "The AppImage of the viewer of the desktop") + WEBODF_PRODUCT_MADE(package-appimage + "OpenDocumentViewer-${WEBODF_VERSION}-${VIEWER_ARCH}.AppImage") + else () + WEBODF_PRODUCT_MISSING("the AppImage" "linuxdeploy") + add_custom_target(package-appimage + COMMAND ${CMAKE_COMMAND} -E echo + "linuxdeploy is not installed: an AppImage carries qt with it, and the tool is what gathers it, see https://github.com/linuxdeploy/linuxdeploy" + COMMAND ${CMAKE_COMMAND} -E false + COMMENT "The AppImage of the viewer of the desktop") + endif () + + # A machine where the tool cannot write the flatpak, for want of the + # rights its build dir asks for, leaves it out rather than stopping the + # build of everything else. + option(WEBODF_PACKAGE_FLATPAK "Make the flatpak of the viewer" ON) + if (WEBODF_PACKAGE_FLATPAK) + find_program(FLATPAK_BUILDER flatpak-builder) + find_program(FLATPAK flatpak) + find_program(OSTREE ostree) + else () + set(FLATPAK_BUILDER "") + set(FLATPAK "") + endif () + + # The manifest of the flatpak names the runtime of KDE, which carries qt + # and its webengine, so nothing of qt is built here: the tool reads it and + # writes the package. The tool alone is not enough, as that runtime is + # installed apart and weighs some gigabytes; and a branch of it is + # declared end of life as soon as a newer one is out, so the newest branch + # this machine has is taken rather than one written here, which would ask + # for a version that is old before the next release. + # + # Whether one is there at all is asked now rather than in the middle of + # the build, where the failure reads as a broken build and not as a + # missing part of the machine. + # + # The branch of the runtime is the version of qt it carries, so the least + # one that will do is the least version of qt the viewer is built with, + # see "find_package(Qt6 ...)" in the root: a machine that kept only an + # older branch has nothing to build against, and is told so. + # The library the sandbox is given, written by this build: the manifest + # names it beside the sources. + set(WEBODF_JS_FILE ${CMAKE_BINARY_DIR}/webodf/webodf.js) + set(VIEWER_FLATPAK_REPO ${CMAKE_CURRENT_BINARY_DIR}/flatpak-repo) + set(VIEWER_FLATPAK_RUNTIME_LEAST "6.4") + set(WEBODF_FLATPAK_RUNTIME "" CACHE STRING + "The branch of org.kde.Sdk the flatpak is built against, the newest installed one by default") + set(VIEWER_FLATPAK_RUNTIME "${WEBODF_FLATPAK_RUNTIME}") + if (FLATPAK AND NOT VIEWER_FLATPAK_RUNTIME) + execute_process(COMMAND ${FLATPAK} list --app=false --columns=application,branch + OUTPUT_VARIABLE VIEWER_FLATPAK_LIST + RESULT_VARIABLE VIEWER_FLATPAK_LISTED + ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) + if (VIEWER_FLATPAK_LISTED EQUAL 0) + string(REGEX MATCHALL "org\\.kde\\.Sdk[ \t]+[0-9]+\\.[0-9]+" + VIEWER_FLATPAK_SDKS "${VIEWER_FLATPAK_LIST}") + set(VIEWER_FLATPAK_BRANCHES "") + foreach (VIEWER_FLATPAK_ONE ${VIEWER_FLATPAK_SDKS}) + string(REGEX REPLACE "^org\\.kde\\.Sdk[ \t]+" "" + VIEWER_FLATPAK_ONE "${VIEWER_FLATPAK_ONE}") + # A branch of qt 5, or one older than the viewer asks for, + # carries no webengine the viewer can be built against. + if (NOT VIEWER_FLATPAK_ONE VERSION_LESS + VIEWER_FLATPAK_RUNTIME_LEAST) + list(APPEND VIEWER_FLATPAK_BRANCHES ${VIEWER_FLATPAK_ONE}) + endif () + endforeach () + if (VIEWER_FLATPAK_BRANCHES) + list(SORT VIEWER_FLATPAK_BRANCHES COMPARE NATURAL ORDER + DESCENDING) + list(GET VIEWER_FLATPAK_BRANCHES 0 VIEWER_FLATPAK_RUNTIME) + endif () + endif () + endif () + # A branch that is named by hand is taken as it is asked for, but not one + # that carries a qt the viewer is not built with: that is a mistake of the + # one who asked, and it is told now and not by the compiler. + if (WEBODF_FLATPAK_RUNTIME AND WEBODF_FLATPAK_RUNTIME VERSION_LESS + VIEWER_FLATPAK_RUNTIME_LEAST) + message(FATAL_ERROR + "WEBODF_FLATPAK_RUNTIME is ${WEBODF_FLATPAK_RUNTIME}, and the viewer is built with qt ${VIEWER_FLATPAK_RUNTIME_LEAST} or newer, which the runtime of KDE carries under the branch of the same number.") + endif () + # What the manifest names when nothing is installed, so that it is written + # and read even where no flatpak is built. + if (NOT VIEWER_FLATPAK_RUNTIME) + set(VIEWER_FLATPAK_RUNTIME "6.9") + endif () + configure_file(data/org.webodf.OpenDocumentViewer.yml.in + ${CMAKE_CURRENT_BINARY_DIR}/${VIEWER_ID}.yml @ONLY) + + # The runtime of KDE carries qt but not its webengine, which the viewer is + # drawn in: that one comes from the base app of qt, built into the app, so + # both are asked for here. + set(VIEWER_FLATPAK_SDK "org.kde.Sdk//${VIEWER_FLATPAK_RUNTIME}") + set(VIEWER_FLATPAK_BASE + "io.qt.qtwebengine.BaseApp//${VIEWER_FLATPAK_RUNTIME}") + set(VIEWER_HAS_FLATPAK_SDK FALSE) + if (FLATPAK_BUILDER AND FLATPAK) + execute_process(COMMAND ${FLATPAK} info ${VIEWER_FLATPAK_SDK} + RESULT_VARIABLE VIEWER_FLATPAK_SDK_ANSWERED + OUTPUT_QUIET ERROR_QUIET) + execute_process(COMMAND ${FLATPAK} info ${VIEWER_FLATPAK_BASE} + RESULT_VARIABLE VIEWER_FLATPAK_BASE_ANSWERED + OUTPUT_QUIET ERROR_QUIET) + if (VIEWER_FLATPAK_SDK_ANSWERED EQUAL 0 + AND VIEWER_FLATPAK_BASE_ANSWERED EQUAL 0) + set(VIEWER_HAS_FLATPAK_SDK TRUE) + elseif (VIEWER_FLATPAK_SDK_ANSWERED EQUAL 0) + set(VIEWER_FLATPAK_SDK ${VIEWER_FLATPAK_BASE}) + endif () + endif () + + if (FLATPAK_BUILDER AND VIEWER_HAS_FLATPAK_SDK) + # The repository the flatpak is exported to refuses to write when + # less than a part of the disk is free, three hundredths of it by + # default, which is a hundred gigabytes on a disk of three terabytes: + # a size is asked for instead, as the flatpak of this viewer weighs + # some tens of megabytes. + if (OSTREE) + set(VIEWER_FLATPAK_ROOM + COMMAND ${OSTREE} init --repo=${VIEWER_FLATPAK_REPO} + --mode=archive-z2 + COMMAND ${OSTREE} config --repo=${VIEWER_FLATPAK_REPO} + set core.min-free-space-size 500MB) + else () + set(VIEWER_FLATPAK_ROOM "") + endif () + add_custom_target(package-flatpak + COMMAND ${CMAKE_COMMAND} -E make_directory ${VIEWER_PRODUCTS} + ${VIEWER_FLATPAK_ROOM} + COMMAND ${FLATPAK_BUILDER} --force-clean --repo=${VIEWER_FLATPAK_REPO} + ${CMAKE_CURRENT_BINARY_DIR}/flatpak-build + ${CMAKE_CURRENT_BINARY_DIR}/${VIEWER_ID}.yml + COMMAND ${FLATPAK_BUILDER} --run + ${CMAKE_CURRENT_BINARY_DIR}/flatpak-build + ${CMAKE_CURRENT_BINARY_DIR}/${VIEWER_ID}.yml true + COMMAND flatpak build-bundle ${VIEWER_FLATPAK_REPO} + ${VIEWER_PRODUCTS}/${VIEWER_ID}-${WEBODF_VERSION}.flatpak ${VIEWER_ID} + DEPENDS opendocumentviewer-desktop webodf.js-target + COMMENT "The flatpak of the viewer of the desktop") + WEBODF_PRODUCT_MADE(package-flatpak + "${VIEWER_ID}-${WEBODF_VERSION}.flatpak") + else () + if (FLATPAK_BUILDER) + WEBODF_PRODUCT_MISSING("the flatpak" "${VIEWER_FLATPAK_SDK}") + set(VIEWER_NO_FLATPAK + "${VIEWER_FLATPAK_SDK} is not installed: the flatpak is built against the runtime of KDE, which carries qt, and against the base app of qt, which carries its webengine. Install them with: flatpak install --user flathub org.kde.Platform//${VIEWER_FLATPAK_RUNTIME} org.kde.Sdk//${VIEWER_FLATPAK_RUNTIME} ${VIEWER_FLATPAK_BASE}") + else () + if (WEBODF_PACKAGE_FLATPAK) + WEBODF_PRODUCT_MISSING("the flatpak" "flatpak-builder") + set(VIEWER_NO_FLATPAK + "flatpak-builder is not installed: the manifest is written in ${CMAKE_CURRENT_BINARY_DIR}/${VIEWER_ID}.yml, see https://docs.flatpak.org/") + else () + set(VIEWER_NO_FLATPAK + "The flatpak was left out by WEBODF_PACKAGE_FLATPAK=OFF: configure again with -DWEBODF_PACKAGE_FLATPAK=ON to make it.") + endif () + endif () + add_custom_target(package-flatpak + COMMAND ${CMAKE_COMMAND} -E echo "${VIEWER_NO_FLATPAK}" + COMMAND ${CMAKE_COMMAND} -E false + COMMENT "The flatpak of the viewer of the desktop") + endif () +endif () diff --git a/programs/opendocumentviewer-desktop/viewerscheme.cpp b/programs/opendocumentviewer-desktop/viewerscheme.cpp new file mode 100644 index 000000000..a62c7bdd1 --- /dev/null +++ b/programs/opendocumentviewer-desktop/viewerscheme.cpp @@ -0,0 +1,130 @@ +/** + * Copyright (C) 2026 Daniel Berthereau + * + * @licstart + * This file is part of WebODF. + * + * WebODF is free software: you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License (GNU AGPL) as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * WebODF is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU Affero General Public License for more + * details. + * + * You should have received a copy of the GNU Affero General Public License + * along with WebODF. If not, see . + * @licend + * + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ + */ + +#include "viewerscheme.h" + +#include +#include +#include +#include +#include + +namespace { + +/** The name the page reads the document under. */ +const char* documentPath = "/document"; + +/** + * The type of a resource of the program. Chromium refuses a script or a style + * that is served under another type, so they are named here rather than + * guessed. + */ +QByteArray typeOf(const QString& name) { + if (name.endsWith(".html")) { + return "text/html;charset=UTF-8"; + } + if (name.endsWith(".css")) { + return "text/css;charset=UTF-8"; + } + if (name.endsWith(".js")) { + return "text/javascript;charset=UTF-8"; + } + return "application/octet-stream"; +} + +/** Answer with bytes the page keeps until it is done with them. */ +void answer(QWebEngineUrlRequestJob* job, const QByteArray& type, + const QByteArray& data) { + QBuffer* const buffer = new QBuffer(job); + buffer->setData(data); + buffer->open(QIODevice::ReadOnly); + job->reply(type, buffer); +} + +} // namespace + +const char* ViewerScheme::pageUrl = "odf:/viewer.html"; + +void ViewerScheme::registerScheme() { + QWebEngineUrlScheme scheme("odf"); + // The scheme is declared secure, so that the page is a trustworthy origin, + // and subject to the rules of cors, which every request of the page passes + // anyway: they are all served from here, so they are all of its own origin. + // It is deliberately not a local scheme: those are held apart by chromium, + // and the page would then be refused the document unless the whole disk + // were opened to it. + scheme.setSyntax(QWebEngineUrlScheme::Syntax::Path); + scheme.setFlags(QWebEngineUrlScheme::SecureScheme + | QWebEngineUrlScheme::CorsEnabled); + QWebEngineUrlScheme::registerScheme(scheme); +} + +ViewerScheme::ViewerScheme(QObject* parent) + : QWebEngineUrlSchemeHandler(parent) { +} + +void ViewerScheme::setPath(const QString& path_) { + path = path_; +} + +void ViewerScheme::requestStarted(QWebEngineUrlRequestJob* job) { + const QString wanted = job->requestUrl().path(); + if (wanted == documentPath) { + serveDocument(job); + } else { + serveResource(job, wanted); + } +} + +void ViewerScheme::serveDocument(QWebEngineUrlRequestJob* job) { + if (path.isEmpty()) { + job->fail(QWebEngineUrlRequestJob::UrlNotFound); + return; + } + QFile file(path); + if (!file.open(QIODevice::ReadOnly)) { + job->fail(QWebEngineUrlRequestJob::RequestFailed); + return; + } + // The whole document is read at once: the page reads it whole as well, as a + // zip is read from its end, and a document that would not fit in memory + // would not be drawn either. + const QMimeType type = QMimeDatabase().mimeTypeForFile(path); + answer(job, type.isValid() + ? type.name().toUtf8() + : QByteArray("application/octet-stream"), file.readAll()); +} + +void ViewerScheme::serveResource(QWebEngineUrlRequestJob* job, + const QString& name) { + // Only the files of the program are served, and only by their own name: a + // path is never taken apart here, so nothing outside the resources can be + // asked for. + QFile file(":" + name); + if (name.lastIndexOf('/') > 0 || !file.open(QIODevice::ReadOnly)) { + job->fail(QWebEngineUrlRequestJob::UrlNotFound); + return; + } + answer(job, typeOf(name), file.readAll()); +} diff --git a/programs/opendocumentviewer-desktop/viewerscheme.h b/programs/opendocumentviewer-desktop/viewerscheme.h new file mode 100644 index 000000000..d859f4f9c --- /dev/null +++ b/programs/opendocumentviewer-desktop/viewerscheme.h @@ -0,0 +1,72 @@ +/** + * Copyright (C) 2026 Daniel Berthereau + * + * @licstart + * This file is part of WebODF. + * + * WebODF is free software: you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License (GNU AGPL) as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * WebODF is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU Affero General Public License for more + * details. + * + * You should have received a copy of the GNU Affero General Public License + * along with WebODF. If not, see . + * @licend + * + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ + */ + +#ifndef VIEWERSCHEME_H +#define VIEWERSCHEME_H + +#include +#include + +/** + * Serve the viewer and the document that is open, and nothing else. + * + * Everything the window shows comes from this one scheme: the page, its style, + * its script and the library, read from the resources of the program, and the + * document that was chosen, read from the disk. Serving them together is what + * makes them one origin, which is what the page needs: a page of one scheme may + * not read what another serves, so a page of "qrc:" is refused the document, + * where "odf:/viewer.html" reads "odf:/document" as it reads anything of its + * own. + * + * The disk is not opened to the page for that: the one document that was chosen + * is served, at one address, whichever file it is. + */ +class ViewerScheme : public QWebEngineUrlSchemeHandler { + Q_OBJECT +public: + /** The page the window loads. */ + static const char* pageUrl; + + /** + * Declare the scheme to webengine. It has to be called before the + * application is built, as webengine reads the schemes once, when it + * starts. + */ + static void registerScheme(); + + explicit ViewerScheme(QObject* parent); + + /** Choose the document that is served from now on. */ + void setPath(const QString& path); + + void requestStarted(QWebEngineUrlRequestJob* job) override; + +private: + void serveDocument(QWebEngineUrlRequestJob* job); + void serveResource(QWebEngineUrlRequestJob* job, const QString& name); + + QString path; +}; + +#endif diff --git a/programs/opendocumentviewer-desktop/words.h b/programs/opendocumentviewer-desktop/words.h new file mode 100644 index 000000000..df7398256 --- /dev/null +++ b/programs/opendocumentviewer-desktop/words.h @@ -0,0 +1,54 @@ +/** + * Copyright (C) 2026 Daniel Berthereau + * + * @licstart + * This file is part of WebODF. + * + * WebODF is free software: you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License (GNU AGPL) as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * WebODF is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU Affero General Public License for more + * details. + * + * You should have received a copy of the GNU Affero General Public License + * along with WebODF. If not, see . + * @licend + * + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ + */ + +#ifndef WORDS_H +#define WORDS_H + +#include +#include + +/** + * The words of the two languages the viewer is written in. + * + * Qt translates with tr() and a catalogue that lrelease compiles, which is a + * tool of qt that is packaged apart and that the build does not need for + * anything else. The viewer holds two languages, as its pages do, so the pair + * is written where it is used and the one of the language of the system is + * taken: nothing to compile, and nothing to keep in step in another file. + */ +namespace words { + +/** + * @param en the words in English, used for every language but French + * @param fr the words in French + */ +inline QString of(const char* en, const char* fr) { + return QLocale::system().language() == QLocale::French + ? QString::fromUtf8(fr) + : QString::fromUtf8(en); +} + +} // namespace words + +#endif diff --git a/programs/opendocumentviewer-ios/.gitignore b/programs/opendocumentviewer-ios/.gitignore new file mode 100644 index 000000000..66afd82eb --- /dev/null +++ b/programs/opendocumentviewer-ios/.gitignore @@ -0,0 +1 @@ +Resources/ diff --git a/programs/opendocumentviewer-ios/CMakeLists.txt b/programs/opendocumentviewer-ios/CMakeLists.txt new file mode 100644 index 000000000..76c430b8c --- /dev/null +++ b/programs/opendocumentviewer-ios/CMakeLists.txt @@ -0,0 +1,34 @@ +# The viewer of OpenDocument for iOS is a web view of the system, WKWebView, +# around the same page and the same library as the viewer for android: Apple +# allows no other engine on iOS, so a viewer there is the shell of one, and the +# shell alone is written in Swift, see "Sources". +# +# Cmake gathers what the application carries, in "Resources", and the +# application itself is built by Xcode, on a mac, see "README-Products.md": +# nothing else builds an application of iOS. +option(WEBODF_IOS "Gather what the viewer of OpenDocument for iOS carries" OFF) + +if (WEBODF_IOS) + set(IOS_RESOURCES ${CMAKE_CURRENT_SOURCE_DIR}/Resources) + set(ANDROID_ASSETS + ${CMAKE_CURRENT_SOURCE_DIR}/../opendocumentviewer-android/src/main/assets) + + # The page of the viewer is the one of android, word for word: the two read + # a document the same way, and a page that differed would be a second one to + # keep in step. + COPY_FILES(OPENDOCUMENTVIEWER_IOS_FILES ${ANDROID_ASSETS} ${IOS_RESOURCES} + index.html index.css index.js about.css) + COPY_FILES(OPENDOCUMENTVIEWER_IOS_FILES ${CMAKE_BINARY_DIR}/webodf + ${IOS_RESOURCES} webodf.js) + + # The pages that tell what the format is worth hold a text that is written + # once, see "INSERT_TEXT" in the CMakeLists of the root. + foreach (LANGUAGE en fr) + INSERT_TEXT( + ${CMAKE_CURRENT_SOURCE_DIR}/../opendocumentviewer-android/about.${LANGUAGE}.html.in + ${IOS_RESOURCES}/about.${LANGUAGE}.html ${LANGUAGE}) + endforeach () + + add_custom_target(product-opendocumentviewer-ios + DEPENDS webodf.js-target ${OPENDOCUMENTVIEWER_IOS_FILES}) +endif () diff --git a/programs/opendocumentviewer-ios/Info.plist b/programs/opendocumentviewer-ios/Info.plist new file mode 100644 index 000000000..d3f220120 --- /dev/null +++ b/programs/opendocumentviewer-ios/Info.plist @@ -0,0 +1,71 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleDisplayName + OpenDocument Viewer + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(MARKETING_VERSION) + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + LSRequiresIPhoneOS + + + LSSupportsOpeningDocumentsInPlace + + UIFileSharingEnabled + + UILaunchScreen + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + CFBundleDocumentTypes + + + CFBundleTypeName + OpenDocument + CFBundleTypeRole + Viewer + LSHandlerRank + Alternate + LSItemContentTypes + + org.oasis-open.opendocument.text + org.oasis-open.opendocument.text-template + org.oasis-open.opendocument.text-flat-xml + org.oasis-open.opendocument.presentation + org.oasis-open.opendocument.presentation-template + org.oasis-open.opendocument.presentation-flat-xml + org.oasis-open.opendocument.spreadsheet + org.oasis-open.opendocument.spreadsheet-template + org.oasis-open.opendocument.spreadsheet-flat-xml + + + + + diff --git a/programs/opendocumentviewer-ios/Sources/AppDelegate.swift b/programs/opendocumentviewer-ios/Sources/AppDelegate.swift new file mode 100644 index 000000000..d649827db --- /dev/null +++ b/programs/opendocumentviewer-ios/Sources/AppDelegate.swift @@ -0,0 +1,53 @@ +/** + * Copyright (C) 2026 Daniel Berthereau + * + * @licstart + * This file is part of WebODF. + * + * WebODF is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License (GNU AGPL) + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * WebODF is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with WebODF. If not, see . + * @licend + * + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ + */ + +import UIKit + +/// The whole of the application: one window, one view, and the document the +/// system hands over. +@main +final class AppDelegate: UIResponder, UIApplicationDelegate { + + var window: UIWindow? + private let viewer = ViewerController() + + func application(_ application: UIApplication, didFinishLaunchingWithOptions + options: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + let window = UIWindow(frame: UIScreen.main.bounds) + window.rootViewController = viewer + window.makeKeyAndVisible() + self.window = window + return true + } + + /// A document is opened from a mail, from the files of the system or from + /// another application: it is drawn at once, whether the viewer was + /// already running or is starting for it. + func application(_ application: UIApplication, open document: URL, + options: [UIApplication.OpenURLOptionsKey: Any] = [:]) + -> Bool { + viewer.show(document: document) + return true + } +} diff --git a/programs/opendocumentviewer-ios/Sources/ViewerController.swift b/programs/opendocumentviewer-ios/Sources/ViewerController.swift new file mode 100644 index 000000000..eb18a96e2 --- /dev/null +++ b/programs/opendocumentviewer-ios/Sources/ViewerController.swift @@ -0,0 +1,247 @@ +/** + * Copyright (C) 2026 Daniel Berthereau + * + * @licstart + * This file is part of WebODF. + * + * WebODF is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License (GNU AGPL) + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * WebODF is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with WebODF. If not, see . + * @licend + * + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ + */ + +import UIKit +import UniformTypeIdentifiers +import WebKit + +/// Show a document of the OpenDocument format with WebODF, in a web view. +/// +/// The page and the library are read from the resources of the application, +/// and the document the system hands over is copied beside them, as it comes +/// from a place the application may only read while it is being handed the +/// file. +/// +/// Everything is served under "odf://viewer/", by a handler of that scheme: +/// a page loaded from a file may not read another file with XMLHttpRequest, +/// which is how the library reads a document, whereas everything served here +/// is of one origin. Only the files of the viewer and the one document are +/// served, so no name a document holds may reach anything else. +/// +/// It is the shell that Android has, in the words of another system: the page, +/// the library and the behaviour are the same, see "ViewerActivity.java". +final class ViewerController: UIViewController { + + /// The files of the viewer, served under their own name and no other. + private static let files = [ + "index.html", "index.css", "index.js", "webodf.js", + "about.en.html", "about.fr.html", "about.css" + ] + + /// The name the document is served under, whichever file it is. + private static let document = "document.odf" + + /// The nine types of the format, as the picker only offers those. + private static let types = [ + "org.oasis-open.opendocument.text", + "org.oasis-open.opendocument.text-template", + "org.oasis-open.opendocument.text-flat-xml", + "org.oasis-open.opendocument.presentation", + "org.oasis-open.opendocument.presentation-template", + "org.oasis-open.opendocument.presentation-flat-xml", + "org.oasis-open.opendocument.spreadsheet", + "org.oasis-open.opendocument.spreadsheet-template", + "org.oasis-open.opendocument.spreadsheet-flat-xml" + ].compactMap { UTType($0) } + + private var web: WKWebView! + + /// Where the document that is open is kept, beside the caches. + private var cached: URL { + FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0] + .appendingPathComponent(ViewerController.document) + } + + override func viewDidLoad() { + super.viewDidLoad() + + let settings = WKWebViewConfiguration() + settings.setURLSchemeHandler(ViewerScheme(controller: self), + forURLScheme: "odf") + // A document is read, never typed in, so nothing of the page is kept + // between two runs. + settings.websiteDataStore = .nonPersistent() + + web = WKWebView(frame: .zero, configuration: settings) + web.navigationDelegate = self + // The page that tells what the viewer is is left by the gesture of the + // system, as there is no bar to hold a button. + web.allowsBackForwardNavigationGestures = true + web.translatesAutoresizingMaskIntoConstraints = false + view.addSubview(web) + NSLayoutConstraint.activate([ + web.topAnchor.constraint(equalTo: view.topAnchor), + web.bottomAnchor.constraint(equalTo: view.bottomAnchor), + web.leadingAnchor.constraint(equalTo: view.leadingAnchor), + web.trailingAnchor.constraint(equalTo: view.trailingAnchor) + ]) + + show(document: nil) + } + + /// Draw a document, or the page that asks for one when there is none. + func show(document: URL?) { + guard let page = URL(string: "odf://viewer/index.html") else { + return + } + if let document, keep(document) { + web.load(URLRequest(url: URL( + string: "odf://viewer/index.html?file=/" + + ViewerController.document) ?? page)) + return + } + web.load(URLRequest(url: page)) + } + + /// Copy the document that was handed over, so that it is read from a place + /// of the application: the system only lends the file it gives. + private func keep(_ document: URL) -> Bool { + let reached = document.startAccessingSecurityScopedResource() + defer { + if reached { + document.stopAccessingSecurityScopedResource() + } + } + do { + let bytes = try Data(contentsOf: document) + try bytes.write(to: cached, options: .atomic) + return true + } catch { + return false + } + } + + /// Ask the system for a document. The system reads the file and hands it + /// over, so the viewer holds no permission of its own. + func pick() { + let picker = UIDocumentPickerViewController( + forOpeningContentTypes: ViewerController.types) + picker.delegate = self + present(picker, animated: true) + } + + /// Answer a request of the page, and nothing else: the name is compared to + /// the files that are served, never used to build a path. + func answer(_ url: URL) -> (Data, String)? { + let name = String(url.path.dropFirst()) + if name == ViewerController.document { + guard let bytes = try? Data(contentsOf: cached) else { + return nil + } + return (bytes, "application/octet-stream") + } + guard ViewerController.files.contains(name), + let file = Bundle.main.url(forResource: name, + withExtension: nil), + let bytes = try? Data(contentsOf: file) else { + return nil + } + return (bytes, ViewerController.type(of: name)) + } + + /// The type of a file of the viewer, as a web view only reads a script and + /// a style sheet when they are served as such. + private static func type(of name: String) -> String { + if name.hasSuffix(".html") { + return "text/html" + } + if name.hasSuffix(".js") { + return "text/javascript" + } + if name.hasSuffix(".css") { + return "text/css" + } + return "application/octet-stream" + } +} + +extension ViewerController: WKNavigationDelegate { + + /// The page asks for a document by going to "/open", which is never + /// loaded: the picker is opened instead. That spares the page a bridge to + /// the code around it, as it does on Android. + /// + /// A link that leaves the viewer is handed to the browser of the system: + /// this viewer shows documents, and never a page of the web. + func webView(_ webView: WKWebView, + decidePolicyFor action: WKNavigationAction, + decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { + guard let url = action.request.url else { + decisionHandler(.cancel) + return + } + if url.scheme == "odf" { + if url.path == "/open" { + decisionHandler(.cancel) + pick() + return + } + decisionHandler(.allow) + return + } + if url.scheme == "http" || url.scheme == "https" { + UIApplication.shared.open(url) + } + decisionHandler(.cancel) + } +} + +extension ViewerController: UIDocumentPickerDelegate { + + func documentPicker(_ picker: UIDocumentPickerViewController, + didPickDocumentsAt urls: [URL]) { + if let first = urls.first { + show(document: first) + } + } +} + +/// Serve the files of the viewer and the document that is open, and nothing +/// else. It is what "shouldInterceptRequest" does on Android. +final class ViewerScheme: NSObject, WKURLSchemeHandler { + + private weak var controller: ViewerController? + + init(controller: ViewerController) { + self.controller = controller + } + + func webView(_ webView: WKWebView, start task: WKURLSchemeTask) { + guard let url = task.request.url, url.host == "viewer", + let (bytes, type) = controller?.answer(url) else { + task.didFailWithError(URLError(.fileDoesNotExist)) + return + } + task.didReceive(URLResponse(url: url, mimeType: type, + expectedContentLength: bytes.count, + textEncodingName: "utf-8")) + task.didReceive(bytes) + task.didFinish() + } + + /// A request of this scheme is answered at once, so there is nothing to + /// stop. + func webView(_ webView: WKWebView, stop task: WKURLSchemeTask) { + } +} diff --git a/programs/opendocumentviewer-ios/project.yml b/programs/opendocumentviewer-ios/project.yml new file mode 100644 index 000000000..c5c04372c --- /dev/null +++ b/programs/opendocumentviewer-ios/project.yml @@ -0,0 +1,44 @@ +# The project of Xcode, written by XcodeGen: https://github.com/yonaskolb/XcodeGen +# +# brew install xcodegen +# cd programs/opendocumentviewer-ios +# xcodegen generate +# open OpenDocumentViewer.xcodeproj +# +# A project of Xcode is a file of some thousand lines that no one writes by +# hand and that every change rewrites whole, so this file is kept instead: it +# says what the project holds, and the project is made from it. +# +# The resources are the page, the library and the pages of the format, that +# cmake writes into "Resources", see "CMakeLists.txt": they are the ones the +# viewer for android reads, so a document is drawn the same way on both. + +name: OpenDocumentViewer +options: + bundleIdPrefix: org.webodf + deploymentTarget: + iOS: "15.0" + createIntermediateGroups: true + +targets: + OpenDocumentViewer: + type: application + platform: iOS + sources: + - path: Sources + - path: Resources + type: folder + buildPhase: resources + info: + path: Info.plist + settings: + base: + PRODUCT_BUNDLE_IDENTIFIER: org.webodf.viewer + PRODUCT_NAME: OpenDocument Viewer + MARKETING_VERSION: 0.0.0 + CURRENT_PROJECT_VERSION: 1 + SWIFT_VERSION: 5.9 + TARGETED_DEVICE_FAMILY: "1,2" + # The viewer reads a document and nothing else: no network is reached, + # so no exception of the transport security is asked for. + ENABLE_USER_SCRIPT_SANDBOXING: true diff --git a/programs/opendocumentviewer-webext/CMakeLists.txt b/programs/opendocumentviewer-webext/CMakeLists.txt new file mode 100644 index 000000000..0964e9be4 --- /dev/null +++ b/programs/opendocumentviewer-webext/CMakeLists.txt @@ -0,0 +1,127 @@ +# The files that go into the extension, copied as they are. "webodf.js" comes +# from the build and the manifest carries the version, see below. +set(OPENDOCUMENTVIEWER_WEBEXT_FILES + background.js + background-chrome.js + background-thunderbird.js + viewer.html + viewer.css + viewer.js + welcome.html + welcome-open.js + welcome-browser.en.html.in + welcome-browser.fr.html.in + welcome-thunderbird.en.html.in + welcome-thunderbird.fr.html.in + welcome.css + welcome.js + skin/default/icon.png + _locales/en/messages.json + _locales/fr/messages.json +) + +# The page of the welcome comes in two: what is to be done to read a document +# is not the same in a browser, where a link is enough, as in Thunderbird, where +# an attachment is opened from its menu. Each package carries the one of its own +# under the same name, and both hold the text of the format, that is written +# once, see "INSERT_TEXT" in the CMakeLists of the root. +foreach (FLAVOUR browser thunderbird) + foreach (LANGUAGE en fr) + INSERT_TEXT(welcome-${FLAVOUR}.${LANGUAGE}.html.in + ${CMAKE_CURRENT_BINARY_DIR}/welcome-${FLAVOUR}.${LANGUAGE}.html + ${LANGUAGE}) + endforeach () +endforeach () + +configure_file(manifest-v3.json.in ${CMAKE_CURRENT_BINARY_DIR}/manifest-v3.json) +configure_file(manifest-v2.json.in ${CMAKE_CURRENT_BINARY_DIR}/manifest-v2.json) +configure_file(manifest-chrome.json.in ${CMAKE_CURRENT_BINARY_DIR}/manifest-chrome.json) +configure_file(manifest-thunderbird-v3.json.in ${CMAKE_CURRENT_BINARY_DIR}/manifest-thunderbird-v3.json) +configure_file(manifest-thunderbird-v2.json.in ${CMAKE_CURRENT_BINARY_DIR}/manifest-thunderbird-v2.json) + +# Pack the add-on from one of the five manifests. The viewer is the same in +# all of them: only the way the documents reach it differs, as Chrome takes a +# file of rules rather than a script, and Thunderbird reads an attachment of a +# message rather than a request, see "README-Products.md". +macro(PACK_OPENDOCUMENTVIEWER_WEBEXT xpi dir manifest extra flavour) + add_custom_command( + OUTPUT ${xpi} + COMMAND ${CMAKE_COMMAND} -E remove_directory ${dir} + COMMAND ${CMAKE_COMMAND} -E make_directory ${dir} + COMMAND ${CMAKE_COMMAND} -E copy + ${CMAKE_CURRENT_BINARY_DIR}/${manifest} ${dir}/manifest.json + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/webodf/webodf.js ${dir} + COMMAND ${CMAKE_COMMAND} -E copy + ${CMAKE_CURRENT_SOURCE_DIR}/viewer.html + ${CMAKE_CURRENT_SOURCE_DIR}/viewer.css + ${CMAKE_CURRENT_SOURCE_DIR}/viewer.js + ${CMAKE_CURRENT_SOURCE_DIR}/welcome.html + ${CMAKE_CURRENT_SOURCE_DIR}/welcome-open.js + ${CMAKE_CURRENT_SOURCE_DIR}/welcome.css + ${CMAKE_CURRENT_SOURCE_DIR}/welcome.js + ${CMAKE_CURRENT_SOURCE_DIR}/${extra} + ${dir} + # The whole of the skin is copied, as the pictures of the page of the + # welcome sit beside the icon, one for each language they are taken in. + COMMAND ${CMAKE_COMMAND} -E copy + ${CMAKE_CURRENT_BINARY_DIR}/welcome-${flavour}.en.html + ${dir}/welcome.en.html + COMMAND ${CMAKE_COMMAND} -E copy + ${CMAKE_CURRENT_BINARY_DIR}/welcome-${flavour}.fr.html + ${dir}/welcome.fr.html + COMMAND ${CMAKE_COMMAND} -E copy_directory + ${CMAKE_CURRENT_SOURCE_DIR}/skin ${dir}/skin + # The words of the menus, one file for each language. + COMMAND ${CMAKE_COMMAND} -E copy_directory + ${CMAKE_CURRENT_SOURCE_DIR}/_locales ${dir}/_locales + # An xpi is a zip whose manifest.json sits at its root. + COMMAND ${NODE} ARGS ${RUNTIMEJS} ${TOOLS_DIR}/zipdir.js + ${dir} + ${xpi} + notopdir + DEPENDS ${NODE} + ${TOOLS_DIR}/zipdir.js + ${OPENDOCUMENTVIEWER_WEBEXT_FILES} + webodf.js-target + ${CMAKE_CURRENT_BINARY_DIR}/${manifest} + ${CMAKE_CURRENT_BINARY_DIR}/welcome-${flavour}.en.html + ${CMAKE_CURRENT_BINARY_DIR}/welcome-${flavour}.fr.html + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + ) +endmacro() + +PACK_OPENDOCUMENTVIEWER_WEBEXT(${OPENDOCUMENTVIEWER_FIREFOX} + ${OPENDOCUMENTVIEWER_FIREFOX_DIR} manifest-v3.json background.js browser) +PACK_OPENDOCUMENTVIEWER_WEBEXT(${OPENDOCUMENTVIEWER_FIREFOX_MV2} + ${OPENDOCUMENTVIEWER_FIREFOX_MV2_DIR} manifest-v2.json background.js browser) +PACK_OPENDOCUMENTVIEWER_WEBEXT(${OPENDOCUMENTVIEWER_CHROME} + ${OPENDOCUMENTVIEWER_CHROME_DIR} manifest-chrome.json background-chrome.js + browser) +PACK_OPENDOCUMENTVIEWER_WEBEXT(${OPENDOCUMENTVIEWER_THUNDERBIRD} + ${OPENDOCUMENTVIEWER_THUNDERBIRD_DIR} manifest-thunderbird-v3.json + background-thunderbird.js thunderbird) +PACK_OPENDOCUMENTVIEWER_WEBEXT(${OPENDOCUMENTVIEWER_THUNDERBIRD_MV2} + ${OPENDOCUMENTVIEWER_THUNDERBIRD_MV2_DIR} manifest-thunderbird-v2.json + background-thunderbird.js thunderbird) + +add_custom_target(build-opendocumentviewer-webext + DEPENDS ${OPENDOCUMENTVIEWER_FIREFOX} ${OPENDOCUMENTVIEWER_FIREFOX_MV2} + ${OPENDOCUMENTVIEWER_CHROME}) + +add_custom_target(test-opendocumentviewer-webext DEPENDS webodf.js-tests) + +add_custom_target(product-opendocumentviewer-webext DEPENDS build-opendocumentviewer-webext test-opendocumentviewer-webext) +WEBODF_PRODUCT(product-opendocumentviewer-webext ${OPENDOCUMENTVIEWER_FIREFOX}) +WEBODF_PRODUCT(product-opendocumentviewer-webext ${OPENDOCUMENTVIEWER_FIREFOX_MV2}) +WEBODF_PRODUCT(product-opendocumentviewer-webext ${OPENDOCUMENTVIEWER_CHROME}) + +# The add-on of Thunderbird is packed from the same files, and is a product of +# its own, as it is published on addons.thunderbird.net and not on AMO. +add_custom_target(build-opendocumentviewer-thunderbird + DEPENDS ${OPENDOCUMENTVIEWER_THUNDERBIRD} ${OPENDOCUMENTVIEWER_THUNDERBIRD_MV2}) + +add_custom_target(test-opendocumentviewer-thunderbird DEPENDS webodf.js-tests) + +add_custom_target(product-opendocumentviewer-thunderbird DEPENDS build-opendocumentviewer-thunderbird test-opendocumentviewer-thunderbird) +WEBODF_PRODUCT(product-opendocumentviewer-thunderbird ${OPENDOCUMENTVIEWER_THUNDERBIRD}) +WEBODF_PRODUCT(product-opendocumentviewer-thunderbird ${OPENDOCUMENTVIEWER_THUNDERBIRD_MV2}) diff --git a/programs/opendocumentviewer-webext/_locales/en/messages.json b/programs/opendocumentviewer-webext/_locales/en/messages.json new file mode 100644 index 000000000..44db71c16 --- /dev/null +++ b/programs/opendocumentviewer-webext/_locales/en/messages.json @@ -0,0 +1,34 @@ +{ + "openInViewer": { + "message": "Open in OpenDocument Viewer", + "description": "The entry of the menus that opens an attachment in the viewer." + }, + "menu": { + "message": "Menu", + "description": "The name of the button that opens the menu of the viewer." + }, + "openLocal": { + "message": "Open a local document", + "description": "The entry of the menu of the viewer that opens a file of the disk." + }, + "download": { + "message": "Save this document", + "description": "The entry of the menu of the viewer that saves the document." + }, + "about": { + "message": "About this viewer", + "description": "The entry of the menu of the viewer that leads to the page of the welcome." + }, + "onePage": { + "message": "One page", + "description": "The entry of the menu that lays the pages one under another." + }, + "twoPages": { + "message": "Two pages", + "description": "The entry of the menu that lays the pages two to a row." + }, + "twoPagesRight": { + "message": "Two pages, first on the right", + "description": "The entry of the menu that lays the pages two to a row, the first one alone on the right." + } +} diff --git a/programs/opendocumentviewer-webext/_locales/fr/messages.json b/programs/opendocumentviewer-webext/_locales/fr/messages.json new file mode 100644 index 000000000..331c8b5d5 --- /dev/null +++ b/programs/opendocumentviewer-webext/_locales/fr/messages.json @@ -0,0 +1,34 @@ +{ + "openInViewer": { + "message": "Ouvrir dans le lecteur OpenDocument", + "description": "The entry of the menus that opens an attachment in the viewer." + }, + "menu": { + "message": "Menu", + "description": "The name of the button that opens the menu of the viewer." + }, + "openLocal": { + "message": "Ouvrir un document local", + "description": "The entry of the menu of the viewer that opens a file of the disk." + }, + "download": { + "message": "Enregistrer ce document", + "description": "The entry of the menu of the viewer that saves the document." + }, + "about": { + "message": "À propos de ce lecteur", + "description": "The entry of the menu of the viewer that leads to the page of the welcome." + }, + "onePage": { + "message": "Une page", + "description": "L’entrée du menu qui dispose les pages l’une sous l’autre." + }, + "twoPages": { + "message": "Deux pages", + "description": "L’entrée du menu qui dispose les pages deux par rangée." + }, + "twoPagesRight": { + "message": "Deux pages, la première à droite", + "description": "L’entrée du menu qui dispose les pages deux par rangée, la première seule à droite." + } +} diff --git a/programs/opendocumentviewer-webext/background-chrome.js b/programs/opendocumentviewer-webext/background-chrome.js new file mode 100644 index 000000000..f43767776 --- /dev/null +++ b/programs/opendocumentviewer-webext/background-chrome.js @@ -0,0 +1,69 @@ +/** + * Copyright (C) 2026 Daniel Berthereau + * + * @licstart + * This file is part of WebODF. + * + * WebODF is free software: you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License (GNU AGPL) as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * WebODF is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU Affero General Public License for more + * details. + * + * You should have received a copy of the GNU Affero General Public License + * along with WebODF. If not, see . + * @licend + * + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ + */ + +/*global chrome*/ + +/** + * Send the documents in the OpenDocument format to the viewer of the extension, + * instead of letting Chrome download them. + * + * Chrome dropped the blocking webRequest the add-on of Firefox redirects with, + * so a rule of declarativeNetRequest does it. The rule is written here rather + * than in a file of the manifest, as the url it redirects to has to be an + * absolute one, and the identifier of the extension is only known once it is + * installed. + */ +(function () { + "use strict"; + + var /**@const@type{!string}*/ + extensions = "odt|fodt|ott|odp|fodp|otp|ods|fods|ots|odg|fodg|otg|odf", + /**@const@type{!number}*/ + ruleId = 1; + + function install() { + chrome.declarativeNetRequest.updateDynamicRules({ + removeRuleIds: [ruleId], + addRules: [{ + id: ruleId, + priority: 1, + action: { + type: "redirect", + redirect: { + regexSubstitution: chrome.runtime.getURL("viewer.html") + + "?file=\\0" + } + }, + condition: { + regexFilter: "^https?://[^?#]+\\.(" + extensions + + ")(\\?[^#]*)?$", + resourceTypes: ["main_frame", "sub_frame"] + } + }] + }); + } + + chrome.runtime.onInstalled.addListener(install); + chrome.runtime.onStartup.addListener(install); +}()); diff --git a/programs/opendocumentviewer-webext/background-thunderbird.js b/programs/opendocumentviewer-webext/background-thunderbird.js new file mode 100644 index 000000000..b5e723e81 --- /dev/null +++ b/programs/opendocumentviewer-webext/background-thunderbird.js @@ -0,0 +1,409 @@ +/** + * Copyright (C) 2026 Daniel Berthereau + * + * @licstart + * This file is part of WebODF. + * + * WebODF is free software: you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License (GNU AGPL) as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * WebODF is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU Affero General Public License for more + * details. + * + * You should have received a copy of the GNU Affero General Public License + * along with WebODF. If not, see . + * @licend + * + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ + */ + +/*global messenger*/ + +// An attachment of a message never travels over http, it is a part of the +// message itself, so the way of the browsers, that watches the requests, does +// not reach it here. Thunderbird hands the attachment over as a file instead, +// and the viewer reads it from there, as it reads the file a reader picks. +// +// Thunderbird carries no api to answer for an attachment that is opened: what +// a double click on it runs is the program of the system the settings name, +// and no add-on may take that over. The document is opened from an entry of +// the menu of the attachment, and of the menu of a message that carries one. +// There is no button of its own in the header of a message: a button may be +// disabled but not hidden, and one that is grey on nearly every message is +// noise. +(function () { + "use strict"; + + var ID = "opendocumentviewer-open", + // An entry without a title is an entry no reader sees, so the words + // of the package answer for the ones of the locale when those are not + // there. + TITLE = messenger.i18n.getMessage("openInViewer") + || "Open in OpenDocument Viewer", + TYPES = [ + "application/vnd.oasis.opendocument.text", + "application/vnd.oasis.opendocument.text-template", + "application/vnd.oasis.opendocument.text-flat-xml", + "application/vnd.oasis.opendocument.spreadsheet", + "application/vnd.oasis.opendocument.spreadsheet-template", + "application/vnd.oasis.opendocument.spreadsheet-flat-xml", + "application/vnd.oasis.opendocument.presentation", + "application/vnd.oasis.opendocument.presentation-template", + "application/vnd.oasis.opendocument.presentation-flat-xml", + "application/vnd.oasis.opendocument.graphics", + "application/vnd.oasis.opendocument.graphics-template", + "application/vnd.oasis.opendocument.graphics-flat-xml", + "application/vnd.oasis.opendocument.formula" + ], + EXTENSIONS = /\.(odt|ott|fodt|ods|ots|fods|odp|otp|fodp|odg|otg|fodg|odf)$/i, + // The languages the page of the welcome is written in, beside the + // English one every other language falls on. + LANGUAGES = ["en", "fr"], + // The entries of the documents of a message, that are made anew each + // time a menu is shown, and what each of them opens. + children = [], + documentsOfChildren = {}; + + /** + * @param {?Object} attachment + * @return {!boolean} + */ + function isDocument(attachment) { + // A mail server that knows no OpenDocument sends the attachment as an + // octet stream, so the name is read as well. + if (!attachment) { + return false; + } + if (TYPES.indexOf(attachment.contentType) !== -1) { + return true; + } + return EXTENSIONS.test(String(attachment.name)); + } + + /** + * @param {!Array.} attachments + * @return {?Object} + */ + function firstDocument(attachments) { + var i; + for (i = 0; i < attachments.length; i += 1) { + if (isDocument(attachments[i])) { + return attachments[i]; + } + } + return null; + } + + /** + * @param {!number} messageId + * @param {!Object} attachment + * @return {undefined} + */ + function open(messageId, attachment) { + // The attachment is named to the tab, and read there. It used to be + // read here and handed over as the url of a blob, but such a url + // belongs to the page that made it, and this background is a page + // that Thunderbird unloads as soon as it falls idle: the url died + // with it, and the tab was left with nothing to read. + messenger.tabs.create({ + url: messenger.runtime.getURL("viewer.html") + + "?message=" + encodeURIComponent(messageId) + + "&part=" + encodeURIComponent(attachment.partName) + + "&name=" + encodeURIComponent(attachment.name) + }); + } + + // The messages that are shown are read as a list, which is the only way + // Thunderbird carries them since its version 121: the calls that named a + // single message, "onMessageDisplayed" and "getDisplayedMessage", are + // gone. The list holds the messages a reader selected together, and the + // first of them is the one the header shows. Up to the version 120 the + // list is an array, from the 121 on it is an object that holds one, as it + // is read a page at a time. + /** + * @param {(!Array.|!Object|null)} displayed + * @return {?Object} + */ + function firstMessage(displayed) { + var messages; + if (!displayed) { + return null; + } + messages = displayed.messages || displayed; + if (messages.length === undefined) { + return messages; + } + return messages.length ? messages[0] : null; + } + + /** + * @param {!number} tabId + * @return {!Promise} + */ + function displayedMessage(tabId) { + return messenger.messageDisplay.getDisplayedMessages(tabId).then( + firstMessage + ); + } + + /** + * @return {!string} + */ + function welcome() { + // The page is written once for each language it is translated in, + // "welcome.fr.html" beside "welcome.en.html", since a page of prose is + // read and corrected far more easily as a page than as a file of + // sentences apart. English is the one every other language falls on, + // and it is named after its language as the others are. + var language = messenger.i18n.getUILanguage().split("-")[0]; + if (LANGUAGES.indexOf(language) === -1) { + language = "en"; + } + return "welcome." + language + ".html"; + } + + // The page of the welcome is shown when the add-on is installed, and once + // more when it is updated from a version that never showed it, which the + // flag in the storage tells. The settings that name what opens a file are + // read by no api at all, so the page tells how to set them rather than + // reading them. + messenger.runtime.onInstalled.addListener(function (details) { + if (details.reason !== "install" && details.reason !== "update") { + return; + } + messenger.storage.local.get("welcomed").then(function (stored) { + if (details.reason === "update" && stored.welcomed) { + return; + } + messenger.storage.local.set({welcomed: true}); + messenger.tabs.create({ + url: messenger.runtime.getURL(welcome()) + + "?reason=" + details.reason + }); + }); + }); + + messenger.menus.create({ + id: ID, + title: TITLE, + contexts: ["message_attachments", "message_list", "page"] + }); + + /** + * @param {!boolean} shown + * @return {undefined} + */ + function showEntry(shown) { + messenger.menus.update(ID, {visible: shown}); + messenger.menus.refresh(); + } + + /** + * Where a part of a message stands in it, as a list of numbers: the name + * of a part is written "1.2", "1.10", and the two are read as numbers so + * that the tenth part comes after the second one and not before it. + * @param {!Object} attachment + * @return {!Array.} + */ + function partNumbers(attachment) { + return String(attachment.partName || "").split(".").map(Number); + } + + /** + * @param {!Object} a + * @param {!Object} b + * @return {!number} + */ + function byPart(a, b) { + var left = partNumbers(a), + right = partNumbers(b), + i; + for (i = 0; i < left.length && i < right.length; i += 1) { + if (left[i] !== right[i]) { + return left[i] - right[i]; + } + } + return left.length - right.length; + } + + /** + * The documents a message carries, in the order the message carries them. + * The list the mail answers with follows no order of its own, so it is put + * back in the order of the parts, which is the order the attachments are + * shown in. + * @param {!Array.} attachments + * @return {!Array.} + */ + function documents(attachments) { + var i, found = []; + for (i = 0; i < attachments.length; i += 1) { + if (isDocument(attachments[i])) { + found.push(attachments[i]); + } + } + found.sort(byPart); + return found; + } + + /** + * @return {undefined} + */ + function clearChildren() { + // A parent that keeps a child draws the arrow of a submenu, even where + // the child is out of context and the submenu opens on nothing, so the + // children of the menu before are taken away before anything else. + children.forEach(function (id) { + messenger.menus.remove(id); + }); + children = []; + documentsOfChildren = {}; + } + + /** + * @param {!number} messageId + * @param {!Array.} found + * @return {undefined} + */ + function buildChildren(messageId, found) { + // A message that carries one document is opened by the entry itself. + // A message that carries several gets one child by document, named + // after it, since only the reader knows which one is meant. + clearChildren(); + if (found.length < 2) { + return; + } + found.forEach(function (attachment, index) { + var id = ID + "-" + index; + messenger.menus.create({ + id: id, + parentId: ID, + title: attachment.name.replace(/&/g, "&&"), + contexts: ["message_list", "page"] + }); + children.push(id); + documentsOfChildren[id] = { + messageId: messageId, + attachment: attachment + }; + }); + } + + /** + * @param {!Object} message + * @return {undefined} + */ + function showEntryOnDocument(message) { + // The attachments have to be read before the entry is settled, and the + // menu is refreshed once the answer is there, which is what refresh() + // is for. It is hidden meanwhile, so that it never shows on a message + // that carries no document. + showEntry(false); + messenger.messages.listAttachments(message.id).then( + function (attachments) { + var found = documents(attachments); + buildChildren(message.id, found); + if (found.length) { + showEntry(true); + } + } + ); + } + + /** + * @param {!Object} tab + * @return {undefined} + */ + function showEntryOnDisplayed(tab) { + // The body of a message is read in a tab of the mail, in a window of + // its own, and in the frames of the add-ons that lay the messages out + // their own way, Conversations among them: all of them answer here, + // and a tab that shows no message answers with nothing. + displayedMessage(tab.id).then(function (message) { + if (message) { + showEntryOnDocument(message); + } + }, function () { + return; + }); + } + + // The entry is shown on the documents alone, so the other menus are left + // as they are. + messenger.menus.onShown.addListener(function (info, tab) { + var message; + if (info.attachments !== undefined) { + // The reader points at one attachment, so there is nothing to + // choose from and no submenu to draw. + clearChildren(); + showEntry(Boolean(firstDocument(info.attachments))); + return; + } + message = firstMessage(info.selectedMessages); + if (message) { + showEntryOnDocument(message); + return; + } + showEntry(false); + showEntryOnDisplayed(tab); + }); + + /** + * @param {!Object} message + * @return {undefined} + */ + function openFirstDocument(message) { + messenger.messages.listAttachments(message.id).then( + function (attachments) { + var found = firstDocument(attachments); + if (found) { + open(message.id, found); + } + } + ); + } + + messenger.menus.onClicked.addListener(function (info, tab) { + var attachment, message, child = documentsOfChildren[info.menuItemId]; + if (child) { + open(child.messageId, child.attachment); + return; + } + if (info.menuItemId !== ID) { + return; + } + // On the menu of an attachment, the document is the one that was + // clicked, and the message is the one the tab shows: the data of the + // click carries the attachment, not the message. On the menu of a + // message, the message is there and the document is looked for. + if (info.attachments !== undefined) { + attachment = firstDocument(info.attachments); + if (!attachment) { + return; + } + displayedMessage(tab.id).then(function (shown) { + if (shown) { + open(shown.id, attachment); + } + }); + return; + } + message = firstMessage(info.selectedMessages); + if (!message) { + // The menu of the body of a message carries no message at all, so + // the one the tab shows is the one that is meant. + displayedMessage(tab.id).then(function (shown) { + if (shown) { + openFirstDocument(shown); + } + }); + return; + } + openFirstDocument(message); + }); + +}()); diff --git a/programs/opendocumentviewer-webext/background.js b/programs/opendocumentviewer-webext/background.js new file mode 100644 index 000000000..9cd794284 --- /dev/null +++ b/programs/opendocumentviewer-webext/background.js @@ -0,0 +1,113 @@ +/** + * Copyright (C) 2012 KO GmbH + * Copyright (C) 2026 Daniel Berthereau + * + * @licstart + * This file is part of WebODF. + * + * WebODF is free software: you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License (GNU AGPL) as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * WebODF is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU Affero General Public License for more + * details. + * + * You should have received a copy of the GNU Affero General Public License + * along with WebODF. If not, see . + * @licend + * + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ + */ + +/*global browser, URL*/ + +/** + * Send the documents in the OpenDocument format to the viewer of the extension, + * instead of letting the browser download them. + * + * The stream converter this replaces was an XPCOM component, a kind of add-on + * Firefox dropped in its version 57. + */ +(function () { + "use strict"; + + var /**@const@type{!Array.}*/ + mimetypes = [ + "application/vnd.oasis.opendocument.text", + "application/vnd.oasis.opendocument.text-flat-xml", + "application/vnd.oasis.opendocument.text-template", + "application/vnd.oasis.opendocument.presentation", + "application/vnd.oasis.opendocument.presentation-flat-xml", + "application/vnd.oasis.opendocument.presentation-template", + "application/vnd.oasis.opendocument.spreadsheet", + "application/vnd.oasis.opendocument.spreadsheet-flat-xml", + "application/vnd.oasis.opendocument.spreadsheet-template", + "application/vnd.oasis.opendocument.graphics", + "application/vnd.oasis.opendocument.graphics-flat-xml", + "application/vnd.oasis.opendocument.graphics-template", + "application/vnd.oasis.opendocument.formula" + ], + /**@const@type{!Array.}*/ + extensions = [ + ".odt", ".fodt", ".ott", + ".odp", ".fodp", ".otp", + ".ods", ".fods", ".ots", + ".odg", ".fodg", ".otg", + ".odf" + ]; + + /** + * @param {!Array.} headers + * @param {!string} name + * @return {?string} + */ + function header(headers, name) { + var i, l = headers.length; + for (i = 0; i < l; i += 1) { + if (headers[i].name.toLowerCase() === name) { + return headers[i].value; + } + } + return null; + } + + /** + * A server that does not know the format sends it as a generic type, so the + * extension of the path is read as well, as the stream converter did. + * @param {!string} url + * @param {!Array.} headers + * @return {!boolean} + */ + function isOpenDocument(url, headers) { + var type = header(headers, "content-type"), + path; + if (type && mimetypes.indexOf(type.split(";")[0].trim()) !== -1) { + return true; + } + path = new URL(url).pathname.toLowerCase(); + return extensions.some(function (extension) { + return path.slice(-extension.length) === extension; + }); + } + + browser.webRequest.onHeadersReceived.addListener(function (details) { + if (details.method !== "GET" || details.statusCode !== 200 + || !isOpenDocument(details.url, details.responseHeaders)) { + return undefined; + } + // The viewer reads the document itself, from the url it is given: the + // response that is redirected here is dropped, and fetched again by the + // viewer, out of the cache of the browser. + return { + redirectUrl: browser.runtime.getURL("viewer.html") + + "?file=" + encodeURIComponent(details.url) + }; + }, { + urls: [""], + types: ["main_frame", "sub_frame"] + }, ["blocking", "responseHeaders"]); +}()); diff --git a/programs/opendocumentviewer-webext/manifest-chrome.json.in b/programs/opendocumentviewer-webext/manifest-chrome.json.in new file mode 100644 index 000000000..bac148d69 --- /dev/null +++ b/programs/opendocumentviewer-webext/manifest-chrome.json.in @@ -0,0 +1,29 @@ +{ + "manifest_version": 3, + "name": "OpenDocument Viewer", + "version": "@WEBODF_MANIFEST_VERSION@", + "description": "Light reader for the documents of LibreOffice and compatible applications, in the OpenDocument format — text, spreadsheet, presentation, drawing and formula — without an office suite.", + "author": "Daniel Berthereau", + "homepage_url": "http://webodf.org/", + "default_locale": "en", + "minimum_chrome_version": "88", + "icons": { + "128": "skin/default/icon.png" + }, + "permissions": [ + "declarativeNetRequest", + "downloads" + ], + "host_permissions": [ + "" + ], + "background": { + "service_worker": "background-chrome.js" + }, + "web_accessible_resources": [ + { + "resources": ["viewer.html"], + "matches": [""] + } + ] +} diff --git a/programs/opendocumentviewer-webext/manifest-thunderbird-v2.json.in b/programs/opendocumentviewer-webext/manifest-thunderbird-v2.json.in new file mode 100644 index 000000000..90f47e5c3 --- /dev/null +++ b/programs/opendocumentviewer-webext/manifest-thunderbird-v2.json.in @@ -0,0 +1,31 @@ +{ + "manifest_version": 2, + "name": "OpenDocument Viewer", + "version": "@WEBODF_MANIFEST_VERSION@", + "description": "Reads the attachments of a message in the OpenDocument format — text, spreadsheet, presentation, drawing and formula — inside Thunderbird, without saving them and without an office suite.", + "author": "Daniel Berthereau", + "homepage_url": "http://webodf.org/", + "default_locale": "en", + "browser_specific_settings": { + "gecko": { + "id": "thunderbird@webodf.js", + "strict_min_version": "98.0" + } + }, + "options_ui": { + "page": "welcome.html", + "open_in_tab": true + }, + "icons": { + "128": "skin/default/icon.png" + }, + "permissions": [ + "menus", + "storage", + "messagesRead", + "downloads" + ], + "background": { + "scripts": ["background-thunderbird.js"] + } +} diff --git a/programs/opendocumentviewer-webext/manifest-thunderbird-v3.json.in b/programs/opendocumentviewer-webext/manifest-thunderbird-v3.json.in new file mode 100644 index 000000000..0f96f74fb --- /dev/null +++ b/programs/opendocumentviewer-webext/manifest-thunderbird-v3.json.in @@ -0,0 +1,31 @@ +{ + "manifest_version": 3, + "name": "OpenDocument Viewer", + "version": "@WEBODF_MANIFEST_VERSION@", + "description": "Reads the attachments of a message in the OpenDocument format — text, spreadsheet, presentation, drawing and formula — inside Thunderbird, without saving them and without an office suite.", + "author": "Daniel Berthereau", + "homepage_url": "http://webodf.org/", + "default_locale": "en", + "browser_specific_settings": { + "gecko": { + "id": "thunderbird@webodf.js", + "strict_min_version": "128.0" + } + }, + "options_ui": { + "page": "welcome.html", + "open_in_tab": true + }, + "icons": { + "128": "skin/default/icon.png" + }, + "permissions": [ + "menus", + "storage", + "messagesRead", + "downloads" + ], + "background": { + "scripts": ["background-thunderbird.js"] + } +} diff --git a/programs/opendocumentviewer-webext/manifest-v2.json.in b/programs/opendocumentviewer-webext/manifest-v2.json.in new file mode 100644 index 000000000..f1082e1b5 --- /dev/null +++ b/programs/opendocumentviewer-webext/manifest-v2.json.in @@ -0,0 +1,31 @@ +{ + "manifest_version": 2, + "name": "OpenDocument Viewer", + "version": "@WEBODF_MANIFEST_VERSION@", + "description": "Light reader for the documents of LibreOffice and compatible applications, in the OpenDocument format — text, spreadsheet, presentation, drawing and formula — without an office suite.", + "author": "Daniel Berthereau", + "homepage_url": "http://webodf.org/", + "default_locale": "en", + "browser_specific_settings": { + "gecko": { + "id": "uriloader@webodf.js", + "data_collection_permissions": { + "required": ["none"] + }, + "strict_min_version": "52.0" + } + }, + "icons": { + "128": "skin/default/icon.png" + }, + "permissions": [ + "webRequest", + "webRequestBlocking", + "downloads", + "" + ], + "background": { + "scripts": ["background.js"] + }, + "web_accessible_resources": ["viewer.html"] +} diff --git a/programs/opendocumentviewer-webext/manifest-v3.json.in b/programs/opendocumentviewer-webext/manifest-v3.json.in new file mode 100644 index 000000000..b62322736 --- /dev/null +++ b/programs/opendocumentviewer-webext/manifest-v3.json.in @@ -0,0 +1,38 @@ +{ + "manifest_version": 3, + "name": "OpenDocument Viewer", + "version": "@WEBODF_MANIFEST_VERSION@", + "description": "Light reader for the documents of LibreOffice and compatible applications, in the OpenDocument format — text, spreadsheet, presentation, drawing and formula — without an office suite.", + "author": "Daniel Berthereau", + "homepage_url": "http://webodf.org/", + "default_locale": "en", + "browser_specific_settings": { + "gecko": { + "id": "uriloader@webodf.js", + "data_collection_permissions": { + "required": ["none"] + }, + "strict_min_version": "109.0" + } + }, + "icons": { + "128": "skin/default/icon.png" + }, + "permissions": [ + "webRequest", + "webRequestBlocking", + "downloads" + ], + "host_permissions": [ + "" + ], + "background": { + "scripts": ["background.js"] + }, + "web_accessible_resources": [ + { + "resources": ["viewer.html"], + "matches": [""] + } + ] +} diff --git a/programs/opendocumentviewer-webext/skin/default/icon.png b/programs/opendocumentviewer-webext/skin/default/icon.png new file mode 100644 index 000000000..dfec99855 Binary files /dev/null and b/programs/opendocumentviewer-webext/skin/default/icon.png differ diff --git a/programs/opendocumentviewer-webext/skin/default/menu-attachment.en.png b/programs/opendocumentviewer-webext/skin/default/menu-attachment.en.png new file mode 100644 index 000000000..84bf39706 Binary files /dev/null and b/programs/opendocumentviewer-webext/skin/default/menu-attachment.en.png differ diff --git a/programs/opendocumentviewer-webext/skin/default/menu-attachment.fr.png b/programs/opendocumentviewer-webext/skin/default/menu-attachment.fr.png new file mode 100644 index 000000000..e6b64141b Binary files /dev/null and b/programs/opendocumentviewer-webext/skin/default/menu-attachment.fr.png differ diff --git a/programs/opendocumentviewer-webext/skin/default/menu-message.en.png b/programs/opendocumentviewer-webext/skin/default/menu-message.en.png new file mode 100644 index 000000000..4a44974e4 Binary files /dev/null and b/programs/opendocumentviewer-webext/skin/default/menu-message.en.png differ diff --git a/programs/opendocumentviewer-webext/skin/default/menu-message.fr.png b/programs/opendocumentviewer-webext/skin/default/menu-message.fr.png new file mode 100644 index 000000000..7eeaf6209 Binary files /dev/null and b/programs/opendocumentviewer-webext/skin/default/menu-message.fr.png differ diff --git a/programs/opendocumentviewer-webext/viewer.css b/programs/opendocumentviewer-webext/viewer.css new file mode 100644 index 000000000..2e662bc74 --- /dev/null +++ b/programs/opendocumentviewer-webext/viewer.css @@ -0,0 +1,155 @@ +html { + /* The page of a document is white paper, so it is declared light whatever + the theme is: chromium then leaves its colours alone instead of darkening + them as it paints, and the ground below still follows the theme, from the + rules of this file. */ + color-scheme: light; +} +/* Every rule of this page names "html > body" rather than "body": an element of + a document carries the local name "body", "office:body", which a selector of + one name reaches as well, so the colour, the font and the alignment of the + page were inherited by the whole document. */ +html, html > body, div { + padding: 0; + margin: 0; + border: 0; +} +html, html > body { + height: 100%; + text-align: center; +} +/* The ground sets the page apart, as a reader of pdf does, and follows the + theme of the browser: the black it used to be is the ground of no theme. */ +html > body { + background: #d4d4d7; + color: black; +} +@media (prefers-color-scheme: dark) { + html > body { + background: #2a2a2e; + color: white; + } +} +/* The page is white paper, from the style sheet of the library, whatever the + theme is, so the ink is dark in both themes: a document that names no colour + for a text would take the one of the ground below, which is white under a + dark theme, on white paper. */ +div#odf { + color: black; +} +/* The button of the menu floats over the document, in the corner, so that no + room is taken from the page. */ +button#menubutton { + position: fixed; + top: 8px; + right: 8px; + z-index: 2; + /* The glyph is centred by the box itself: a line height never puts it in + the middle of a square, the ink of a character sitting above its + baseline. */ + display: flex; + align-items: center; + justify-content: center; + width: 32px; + height: 32px; + padding: 0; + border: 1px solid rgba(0, 0, 0, 0.25); + border-radius: 4px; + background: rgba(255, 255, 255, 0.85); + color: black; + font-size: 18px; + line-height: 1; + cursor: default; +} +button#menubutton:hover { + background: white; +} +div#menu { + position: fixed; + top: 44px; + right: 8px; + z-index: 2; + min-width: 12em; + padding: 4px 0; + border: 1px solid rgba(0, 0, 0, 0.25); + border-radius: 4px; + background: white; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.25); + text-align: left; +} +div#menu > .entry { + display: block; + width: 100%; + box-sizing: border-box; + padding: 6px 12px; + border: 0; + background: none; + color: black; + font: inherit; + text-align: left; + text-decoration: none; + cursor: default; +} +div#menu > .entry:hover { + background: #e0e0e0; +} +/* The label opens the picker. The input is taken out of sight, but not out of + the page: "display: none" would take it from the keyboard as well, and the + entry could then only be reached with a mouse. */ +div#menu > #openlabel > input { + position: absolute; + width: 1px; + height: 1px; + opacity: 0; +} +/* The entry the keyboard is on is drawn as the one the mouse is on. */ +div#menu > .entry:focus, +div#menu > #openlabel:focus-within { + background: #e0e0e0; + outline: 2px solid #0060df; + outline-offset: -2px; +} +@media (prefers-color-scheme: dark) { + button#menubutton { + border-color: rgba(255, 255, 255, 0.25); + background: rgba(43, 42, 51, 0.85); + color: white; + } + button#menubutton:hover { + background: #2b2a33; + } + div#menu { + border-color: rgba(255, 255, 255, 0.25); + background: #2b2a33; + } + div#menu > .entry { + color: white; + } + div#menu > .entry:hover { + background: #52525e; + } + div#menu > .entry:focus, + div#menu > #openlabel:focus-within { + background: #52525e; + outline-color: #00ddff; + } +} + +/* On paper, the ground has nothing to do: the page of the document is the page + that is printed, and the grey around it would be printed with it. */ +@media print { + html, html > body { + background: white; + } + div#odf { + padding: 0; + } +} + +/* The parts of the menu are told apart by a line: what is done with the + document, how it is laid out, and what the viewer is. */ +div#menu > hr.parting { + border: 0; + border-top: 1px solid rgba(0, 0, 0, 0.2); + margin: 0.25em 0; +} diff --git a/programs/opendocumentviewer-webext/viewer.html b/programs/opendocumentviewer-webext/viewer.html new file mode 100644 index 000000000..f38424a31 --- /dev/null +++ b/programs/opendocumentviewer-webext/viewer.html @@ -0,0 +1,44 @@ + + + + + OpenDocument Viewer + + + + + + + + + +
+ + diff --git a/programs/opendocumentviewer-webext/viewer.js b/programs/opendocumentviewer-webext/viewer.js new file mode 100644 index 000000000..ed90de1e0 --- /dev/null +++ b/programs/opendocumentviewer-webext/viewer.js @@ -0,0 +1,241 @@ +/** + * Copyright (C) 2012 KO GmbH + * Copyright (C) 2026 Daniel Berthereau + * + * @licstart + * This file is part of WebODF. + * + * WebODF is free software: you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License (GNU AGPL) as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * WebODF is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU Affero General Public License for more + * details. + * + * You should have received a copy of the GNU Affero General Public License + * along with WebODF. If not, see . + * @licend + * + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ + */ + +/*global document, window, browser, chrome, odf, URLSearchParams, URL*/ + +(function () { + "use strict"; + + // Firefox names the api "browser", Chrome names it "chrome". A page of an + // add-on carries its permissions, so it downloads by itself. + var api = (String(typeof browser) !== "undefined") + ? browser + : chrome, + query = new URLSearchParams(window.location.search), + url = query.get("file"), + // The name of a document is carried apart, as an attachment of a + // message is named by its part, not by an address. + name = query.get("name"), + message = query.get("message"), + part = query.get("part"), + canvas = new odf.OdfCanvas(document.getElementById("odf")), + open = document.getElementById("open"), + menu = document.getElementById("menu"), + menubutton = document.getElementById("menubutton"), + ratio = window.devicePixelRatio, + // Whether the document is to be scaled to the window once the pages + // are drawn anew, which the reader asks for by laying them out + // another way. + refitting = false, + // The languages the page of the welcome is written in, beside the + // English one every other language falls on. + LANGUAGES = ["en", "fr"]; + + // A text is drawn over pages, as it is printed, which the library + // does not do on its own. + canvas.setPaginated(true); + + /** + * @param {!boolean} open + * @return {undefined} + */ + function showMenu(open) { + menu.hidden = !open; + menubutton.setAttribute("aria-expanded", String(open)); + // The keyboard follows the menu: it opens on its first entry, and it + // goes back to the button that opened it, rather than to the top of + // the page. + if (open) { + menu.querySelector(".entry, input").focus(); + } + } + + // A page is drawn at its own size, and is scaled down only when the window + // is narrower than it, so that a document is read as it is written on a + // screen that holds it: fitSmart never scales up. + function fit() { + canvas.fitSmart(document.documentElement.clientWidth); + } + + // The zoom of the browser narrows the window as it is read here, in css + // pixels, so a fit on every resize would scale the page down by as much as + // the reader zoomed in, and the document would never grow. A zoom changes + // the ratio of the pixels of the device, a resize does not, which is how + // the two are told apart. + function refit() { + if (window.devicePixelRatio !== ratio) { + ratio = window.devicePixelRatio; + return; + } + fit(); + } + + /** + * @param {!string} name + * @return {undefined} + */ + function show(name) { + document.title = name; + document.getElementById("download").style.display = url + ? "" + : "none"; + } + + // The document is saved as it came, under the name it carries, whether it + // was read from a message, from an address or from the disk. + document.getElementById("download").addEventListener("click", function () { + if (url) { + api.downloads.download({url: url, filename: document.title}); + } + }); + + // A document that is read from the disk is opened here, as the requests of + // a file:// url never reach the background script: webRequest only watches + // http, https and the web sockets. + open.addEventListener("change", function () { + var file = open.files[0]; + if (file) { + url = null; + show(file.name); + canvas.load(URL.createObjectURL(file)); + } + }); + + // The words of the menu are written in the language of the reader. The + // label of the picker holds the input of the file, so only its first node + // of text is written over. The button of the menu carries no word, only a + // glyph, so what it is called is read by a screen reader alone. + (function () { + var entries = document.querySelectorAll("[data-message]"), + labelled = document.querySelectorAll("[data-label]"), + i, + entry, + word; + for (i = 0; i < entries.length; i += 1) { + entry = entries[i]; + entry.firstChild.nodeValue = + api.i18n.getMessage(entry.getAttribute("data-message")); + } + for (i = 0; i < labelled.length; i += 1) { + entry = labelled[i]; + word = api.i18n.getMessage(entry.getAttribute("data-label")); + entry.setAttribute("aria-label", word); + entry.setAttribute("title", word); + } + }()); + + // The entry leads to the page that tells how the viewer is used and what + // the format is worth, in the language of the reader, rather than to the + // site of the project: it is in the package, so it is read offline and it + // outlives the site. + (function () { + var language = api.i18n.getUILanguage().split("-")[0]; + if (LANGUAGES.indexOf(language) === -1) { + language = "en"; + } + document.getElementById("about").href = + api.runtime.getURL("welcome." + language + ".html"); + }()); + + // How the pages are laid out, which the menu asks of the canvas: one + // page under another as a document is scrolled, two beside one another as + // a book is read, and the first of them alone on the right where a book + // begins on a right page. + (function () { + var ways = [ + {id: "onepage", perRow: 1, alone: false}, + {id: "twopages", perRow: 2, alone: false}, + {id: "twopagesright", perRow: 2, alone: true} + ]; + ways.forEach(function (way) { + var entry = document.getElementById(way.id); + if (!entry) { + return; + } + entry.addEventListener("click", function () { + refitting = true; + canvas.setPagesPerRow(way.perRow); + canvas.setFirstPageOnItsOwn(way.alone); + showMenu(false); + }); + }); + }()); + + menubutton.addEventListener("click", function (event) { + event.stopPropagation(); + showMenu(menu.hidden); + }); + // A click anywhere else closes the menu, as a menu of a window does. The + // entries close it themselves, since the click reaches the document. + document.addEventListener("click", function () { + showMenu(false); + }); + document.addEventListener("keydown", function (event) { + if (event.key === "Escape" && !menu.hidden) { + showMenu(false); + menubutton.focus(); + } + }); + + canvas.addListener("statereadychange", fit); + // A row of two pages is twice as wide as one, so the document is scaled to + // the window anew when the reader asks for another way of laying the pages + // out, and then alone: a document is read at the size it was written at. + canvas.addListener("pagesdrawn", function () { + if (refitting) { + refitting = false; + fit(); + } + }); + window.addEventListener("resize", refit); + + // Thunderbird names the attachment of a message rather than an address: + // the document is read here, in the tab, and not in the background. A url + // of a blob belongs to the page that made it, and the background of an + // add-on is a page that is unloaded as soon as it falls idle, taking the + // url of the blob with it, which left the tab with nothing to read. + if (message && part) { + api.messages.getAttachmentFile(Number(message), part).then( + function (file) { + // The document is drawn and saved from the same blob, so the + // attachment is read once and written as it came. The url of + // the blob is made here, in the tab, and not in the background + // of the add-on: that background is a page Thunderbird unloads + // as soon as it falls idle, and the url would die with it. + url = URL.createObjectURL(file); + show(name || file.name); + canvas.load(url); + } + ); + return; + } + + if (!url) { + show("OpenDocument Viewer"); + return; + } + show(name || decodeURIComponent(url.split("/").pop())); + canvas.load(url); +}()); diff --git a/programs/opendocumentviewer-webext/welcome-browser.en.html.in b/programs/opendocumentviewer-webext/welcome-browser.en.html.in new file mode 100644 index 000000000..350447bda --- /dev/null +++ b/programs/opendocumentviewer-webext/welcome-browser.en.html.in @@ -0,0 +1,39 @@ + + + + + OpenDocument Viewer + + + + +
+

OpenDocument + Viewer is installed

+

It reads the documents of the OpenDocument standard + written by LibreOffice and by every other office suite, in the browser, + without saving them and with no office suite.

+

The format is worth as much as the reader, and why this + format tells what sets OpenDocument apart from OOXML, and why a + document written today with ODF will still be read in twenty years.

+ +

Opening a document

+

There is nothing to do: a link to a document of the OpenDocument format + opens in the reader, in a tab, instead of being downloaded. A document + that is already saved opens by dragging it into a window of the browser, + if the add-on is allowed to read local files, which is granted on its page + in the manager of the add-ons.

+ +@FORMAT_TEXT@ + +

Elsewhere

+

The same reader is an add-on of Thunderbird, that shows the attachments + in the message itself, and an application for Android, that reads them + from an email or from a file manager.

+

It is built on WebODF, a library that reads the format in JavaScript, + and it is free software, under the AGPL 3. + About WebODF.

+
+ + diff --git a/programs/opendocumentviewer-webext/welcome-browser.fr.html.in b/programs/opendocumentviewer-webext/welcome-browser.fr.html.in new file mode 100644 index 000000000..0531ba198 --- /dev/null +++ b/programs/opendocumentviewer-webext/welcome-browser.fr.html.in @@ -0,0 +1,40 @@ + + + + + OpenDocument Viewer + + + + +
+

OpenDocument + Viewer est installé

+

Il lit les documents au standard OpenDocument créés par + LibreOffice et toutes les suites bureautiques, dans le navigateur, sans + les enregistrer et sans suite bureautique.

+

Le format compte autant que le lecteur et le point + pourquoi ce format rappelle ce qui distingue OpenDocument d’OOXML et + pourquoi un document écrit aujourd’hui avec ODF se lira encore dans vingt + ans.

+ +

Ouvrir un document

+

Il n’y a rien à faire : un lien vers un document au format OpenDocument + s’ouvre dans le lecteur, dans un onglet, au lieu d’être téléchargé. Un + document déjà enregistré s’ouvre en le faisant glisser dans une fenêtre du + navigateur, si l’extension a l’autorisation de lire les fichiers locaux, + qui s’accorde sur sa page dans le gestionnaire des extensions.

+ +@FORMAT_TEXT@ + +

Ailleurs

+

Le même lecteur existe en extension pour Thunderbird, qui affiche les + pièces jointes dans le message même, et en application pour Android, qui + les lit depuis un courriel ou un gestionnaire de fichiers.

+

Il repose sur WebODF, une bibliothèque qui lit le format en JavaScript, + et c’est un logiciel libre, sous licence AGPL 3. + À propos de WebODF.

+
+ + diff --git a/programs/opendocumentviewer-webext/welcome-open.js b/programs/opendocumentviewer-webext/welcome-open.js new file mode 100644 index 000000000..906439e82 --- /dev/null +++ b/programs/opendocumentviewer-webext/welcome-open.js @@ -0,0 +1,50 @@ +/** + * Copyright (C) 2026 Daniel Berthereau + * + * @licstart + * This file is part of WebODF. + * + * WebODF is free software: you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License (GNU AGPL) as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * WebODF is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU Affero General Public License for more + * details. + * + * You should have received a copy of the GNU Affero General Public License + * along with WebODF. If not, see . + * @licend + * + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ + */ + +/*global window, browser, chrome*/ + +// The manager of the add-ons opens one address, and the page of the welcome is +// written once for each language, so this one leads to the right one. The +// address is replaced rather than followed, so that the page of a language is +// the one the reader steps back from. +(function () { + "use strict"; + + var api = (String(typeof browser) !== "undefined") + ? browser + : chrome, + // The languages the page is written in, English being the one every + // other language falls on. + languages = ["en", "fr"], + language = api.i18n.getUILanguage().split("-")[0]; + + if (languages.indexOf(language) === -1) { + language = "en"; + } + // The reason names the opening of the page: read from the manager of the + // add-ons, it greets no one and tells what the add-on is. + window.location.replace( + api.runtime.getURL("welcome." + language + ".html") + "?reason=about" + ); +}()); diff --git a/programs/opendocumentviewer-webext/welcome-thunderbird.en.html.in b/programs/opendocumentviewer-webext/welcome-thunderbird.en.html.in new file mode 100644 index 000000000..b15619219 --- /dev/null +++ b/programs/opendocumentviewer-webext/welcome-thunderbird.en.html.in @@ -0,0 +1,64 @@ + + + + + OpenDocument Viewer + + + + +
+

OpenDocument + Viewer is installed

+

It reads the documents of the + OpenDocument standard, the ones LibreOffice and every other office suite + write, as they arrive by mail, without saving them and without an office + suite.

+

The format matters as much as the viewer, and the point + why this format recalls what sets OpenDocument apart from OOXML and + why a document written today with ODF will still be read in twenty + years.

+ +

How a document is opened

+

Right-click the attachment, at the foot of the message, and choose + Open in OpenDocument Viewer. The entry is there on the + documents of the OpenDocument format alone.

+
+ +
The menu of an attachment.
+
+

The same entry is in the context menu of the messages that hold a file + of the OpenDocument format. When a message holds several of them, it is a + submenu with the list of their names.

+
+ +
The menu of a message.
+
+ +

A double click still opens your office suite

+

Thunderbird hands a double click to the program the settings of the + system name, and no add-on may change them. To open the documents of the + format in Thunderbird, those settings have to be set:

+
    +
  1. Settings, then General, then Files & Attachments;
  2. +
  3. under Incoming, find the OpenDocument types, the text, the + spreadsheet and the presentation;
  4. +
  5. set each of them to Save File or to Always ask, so that Thunderbird + stops handing them over to the system.
  6. +
+

The menu of the attachment then becomes the quickest way to read a + document. And nothing is written to the disk to read it.

+ +@FORMAT_TEXT@ + +

Elsewhere

+

The same viewer is an add-on of Firefox and of Chrome, that shows those + documents in the browser instead of downloading them, and an application + for Android that reads them from a mail or a file manager.

+

It is built on WebODF, a library that reads the format in JavaScript, + and it is free software, under the AGPL 3. + About WebODF.

+
+ + diff --git a/programs/opendocumentviewer-webext/welcome-thunderbird.fr.html.in b/programs/opendocumentviewer-webext/welcome-thunderbird.fr.html.in new file mode 100644 index 000000000..b326eb5ca --- /dev/null +++ b/programs/opendocumentviewer-webext/welcome-thunderbird.fr.html.in @@ -0,0 +1,66 @@ + + + + + OpenDocument Viewer + + + + +
+

OpenDocument + Viewer est installé

+

Il lit les documents au standard + OpenDocument créés par LibreOffice et toutes les suites bureautiques, à + leur arrivée par courriel, sans les enregistrer et sans suite + bureautique.

+

Le format compte autant que le lecteur et le point + pourquoi ce format rappelle ce qui distingue OpenDocument d’OOXML et + pourquoi un document écrit aujourd’hui avec ODF se lira encore dans vingt + ans.

+ +

Ouvrir un document

+

Faire un clic droit sur la pièce jointe, au bas du message, et + choisir Ouvrir dans le lecteur OpenDocument. + L’entrée ne figure que sur les documents au format OpenDocument.

+
+ +
Le menu d’une pièce jointe.
+
+

La même entrée se trouve dans le menu contextuel des messages qui + contiennent un fichier au format OpenDocument. Lorsque le message a + plusieurs fichiers ODF, c’est un sous-menu avec la liste des noms.

+
+ +
Le menu d’un message.
+
+ +

Le double-clic ouvre toujours votre suite bureautique

+

Thunderbird confie un double-clic au programme indiqué par les réglages + du système et aucune extension ne peut les modifier. Pour ouvrir les + documents ODF dans Thunderbird, il convient donc de les configurer :

+
    +
  1. Paramètres, puis Général, puis Fichiers et pièces jointes ;
  2. +
  3. sous Entrant, chercher les types OpenDocument, le texte, le classeur + et la présentation ;
  4. +
  5. régler chacun sur Enregistrer le fichier ou sur Toujours demander, + pour que Thunderbird cesse de les transmettre au système.
  6. +
+

Le menu de la pièce jointe devient alors la solution la plus rapide pour + consulter le document. De plus, rien n’est écrit sur le disque pour le + lire.

+ +@FORMAT_TEXT@ + +

Ailleurs

+

Le même lecteur existe en extension pour Firefox et pour Chrome, qui + affiche ces documents dans le navigateur au lieu de les télécharger, et + en application pour Android, qui les lit depuis un courriel ou un + gestionnaire de fichiers.

+

Il repose sur WebODF, une bibliothèque qui lit le format en JavaScript, + et c’est un logiciel libre, sous licence AGPL 3. + À propos de WebODF.

+
+ + diff --git a/programs/opendocumentviewer-webext/welcome.css b/programs/opendocumentviewer-webext/welcome.css new file mode 100644 index 000000000..57095ed65 --- /dev/null +++ b/programs/opendocumentviewer-webext/welcome.css @@ -0,0 +1,59 @@ +/* The page follows the theme of the reader, as the viewer does. */ +html, body { + margin: 0; + padding: 0; + background: #ffffff; + color: #15141a; + font-family: sans-serif; + line-height: 1.5; +} +main { + max-width: 40em; + margin: 0 auto; + padding: 2em 1.5em 4em; +} +h1 { + font-size: 1.6em; +} +h2 { + margin-top: 2em; + font-size: 1.15em; +} +#lead { + font-size: 1.1em; +} +figure { + margin: 1em 0; +} +figure > img { + max-width: 100%; + border: 1px solid rgba(0, 0, 0, 0.2); + border-radius: 4px; +} +/* A caption without its image says nothing, so it goes with it. */ +figure > img[hidden] { + display: none; +} +figure > img[hidden] + figcaption { + display: none; +} +figcaption { + padding-top: 0.4em; + font-size: 0.9em; + opacity: 0.75; +} +a { + color: #0060df; +} +@media (prefers-color-scheme: dark) { + html, body { + background: #1c1b22; + color: #fbfbfe; + } + figure > img { + border-color: rgba(255, 255, 255, 0.2); + } + a { + color: #00ddff; + } +} diff --git a/programs/opendocumentviewer-webext/welcome.html b/programs/opendocumentviewer-webext/welcome.html new file mode 100644 index 000000000..ea173378f --- /dev/null +++ b/programs/opendocumentviewer-webext/welcome.html @@ -0,0 +1,13 @@ + + + + + OpenDocument Viewer + + + + + + diff --git a/programs/opendocumentviewer-webext/welcome.js b/programs/opendocumentviewer-webext/welcome.js new file mode 100644 index 000000000..0cc20068e --- /dev/null +++ b/programs/opendocumentviewer-webext/welcome.js @@ -0,0 +1,112 @@ +/** + * Copyright (C) 2026 Daniel Berthereau + * + * @licstart + * This file is part of WebODF. + * + * WebODF is free software: you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License (GNU AGPL) as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * WebODF is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU Affero General Public License for more + * details. + * + * You should have received a copy of the GNU Affero General Public License + * along with WebODF. If not, see . + * @licend + * + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ + */ + +/*global document, window, browser, chrome, URLSearchParams, Image*/ + +(function () { + "use strict"; + + var api = (String(typeof browser) !== "undefined") + ? browser + : chrome, + query = new URLSearchParams(window.location.search), + images = document.querySelectorAll("figure > img"), + i; + + // The page is opened when the add-on is installed, and again when it is + // updated from a version that never showed it: the same page then reads as + // a reminder rather than as a greeting. + // The page is opened when the add-on is installed, when it is updated, + // and from the manager of the add-ons, which is neither. Each of those + // has its own opening, written in the page, in the attribute the reason + // names: this file holds no sentence of its own. + function opening(reason, id) { + var element = document.getElementById(id), + words = element.getAttribute("data-" + reason); + if (words) { + element.textContent = words; + } + } + + if (query.get("reason")) { + opening(query.get("reason"), "heading"); + opening(query.get("reason"), "lead"); + } + + // The pictures of the menus carry the words of Thunderbird, so one is kept + // for each language they are taken in: "menu-message.fr.png" beside + // "menu-message.png". The one of the language of the reader is tried + // first, then the one of its language alone, "pt" for "pt-BR", then the + // English one. A picture that is in the package under none of those names + // leaves its figure out, so that the page holds together with the + // pictures that are there, whichever they are. + function show(image, names) { + var probe; + if (!names.length) { + return; + } + probe = new Image(); + probe.onload = function () { + image.src = names[0]; + image.hidden = false; + }; + probe.onerror = function () { + show(image, names.slice(1)); + }; + probe.src = names[0]; + } + + /** + * @param {!string} src + * @return {!Array.} + */ + function names(src) { + var base = src.replace(/\.png$/, ""), + language = api.i18n.getUILanguage(), + list = [base + "." + language + ".png"]; + if (language.indexOf("-") !== -1) { + list.push(base + "." + language.split("-")[0] + ".png"); + } + // A picture in English says more than no picture at all: the menus it + // shows are at the same place whatever the words in them are. + list.push(base + ".en.png"); + list.push(src); + return list; + } + + for (i = 0; i < images.length; i += 1) { + show(images[i], names(images[i].getAttribute("src"))); + } + + // Thunderbird opens no web page in a tab of its own, so every link that + // leaves the add-on is handed to the browser of the system. Firefox and + // Chrome follow them as they follow any other. + document.addEventListener("click", function (event) { + var link = event.target.closest("a[href^=\"http\"]"); + if (link && api.windows && api.windows.openDefaultBrowser) { + event.preventDefault(); + api.windows.openDefaultBrowser(link.href); + } + }); +}()); diff --git a/programs/qtjsruntime/CMakeLists.txt b/programs/qtjsruntime/CMakeLists.txt index da5f77f98..c8a444010 100644 --- a/programs/qtjsruntime/CMakeLists.txt +++ b/programs/qtjsruntime/CMakeLists.txt @@ -1,9 +1,22 @@ +# The moc of qt reads the classes that declare Q_OBJECT and writes the code of +# their signals and slots, which is compiled along with them. The header of the +# filter is listed as a source so that moc sees it, as it holds a class of its +# own. set(CMAKE_AUTOMOC ON) -add_executable(qtjsruntime qtjsruntime.cpp pagerunner.cpp nativeio.cpp nam.h) +add_executable(qtjsruntime + main.cpp + runner.cpp + nativeio.cpp + bindings.cpp + requestfilter.h +) target_link_libraries(qtjsruntime - Qt5::WebKitWidgets - Qt5::Network - Qt5::PrintSupport + Qt6::Core + Qt6::Gui + Qt6::Widgets + Qt6::WebChannel + Qt6::WebEngineCore + Qt6::WebEngineWidgets ) diff --git a/programs/qtjsruntime/bindings.cpp b/programs/qtjsruntime/bindings.cpp new file mode 100644 index 000000000..c7a7194a7 --- /dev/null +++ b/programs/qtjsruntime/bindings.cpp @@ -0,0 +1,178 @@ +/** + * Copyright (C) 2026 Daniel Berthereau + * + * @licstart + * This file is part of WebODF. + * + * WebODF is free software: you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License (GNU AGPL) as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * WebODF is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU Affero General Public License for more + * details. + * + * You should have received a copy of the GNU Affero General Public License + * along with WebODF. If not, see . + * @licend + * + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ + */ + +#include "bindings.h" + +#include + +// The two scripts below run in the page, in the blink of WebEngine, so they are +// written in the javascript of today rather than in the one of the library, +// that has to run in older engines as well. + +QByteArray runtimeBindings() { + return R"JS( +(function () { + "use strict"; + const api = window.__qtjsruntime = window.__qtjsruntime || {}; + + // What travels over the channel is text, so the bytes of a file are sent + // in base64: a string of them would be encoded as utf-8 on the way, which + // would change every byte above 127. + function base64(data) { + const bytes = typeof data === "string" + ? runtime.byteArrayFromString(data, "binary") + : data; + let text = ""; + // The characters are made in slices, as a call with a whole document + // of arguments at once is refused. + for (let i = 0; i < bytes.length; i += 8192) { + text += String.fromCharCode.apply(null, + bytes.subarray(i, i + 8192)); + } + return window.btoa(text); + } + + // Reading a file is left to the runtime of the browser, that reads it with + // a request on it, so only what a page may not do is given here. Each of + // these takes a callback in the runtime of the library already, which is + // what makes the channel enough: it answers when it answers. + api.install = function (nativeio, libraryPaths, currentDirectory) { + runtime.libraryPaths = function () { + return libraryPaths.slice(); + }; + runtime.currentDirectory = function () { + return currentDirectory; + }; + runtime.writeFile = function (path, data, callback) { + nativeio.writeFile(path, base64(data), function (error) { + callback(error || null); + }); + }; + runtime.deleteFile = function (path, callback) { + nativeio.deleteFile(path, function (error) { + callback(error || null); + }); + }; + runtime.getFileSize = function (path, callback) { + nativeio.getFileSize(path, callback); + }; + runtime.exit = function (code) { + nativeio.exit(code || 0); + }; + }; + + // Run a script the way runtime.js runs the ones it is given on the command + // line. It is done here rather than there, by handing the script to + // runtime.js as an argument, so that the bindings above are surely in + // place before the script runs: runtime.js reads the script with a request + // and runs it in the answer, which may come before or after the script tag + // that installs them. + api.run = function (argv) { + const script = argv[0]; + runtime.readFile(script, "utf8", function (err, code) { + if (err || code === null) { + runtime.log(String(err || `No code found for ${script}`)); + runtime.exit(1); + return; + } + const end = script.lastIndexOf("/"); + runtime.setCurrentDirectory(end === -1 + ? "." + : script.slice(0, end)); + // The script is run with the arguments bound to its "arguments", + // as node and rhino do, and an exit code it returns is used. + (function () { + /*jslint evil: true*/ + const result = eval(String(code)); + /*jslint evil: false*/ + if (result) { + runtime.exit(result); + } + }).apply(null, argv); + }); + }; +}()); +)JS"; +} + +QByteArray webChannelScript() { + QFile file(":/qtwebchannel/qwebchannel.js"); + if (!file.open(QIODevice::ReadOnly)) { + return QByteArray(); + } + return file.readAll(); +} + +QByteArray idleWatcher() { + return R"JS( +(function () { + "use strict"; + const api = window.__qtjsruntime = window.__qtjsruntime || {}; + let pending = 0; + let dirty = true; + + // WebKit told when the page had changed or asked to be painted again, and + // its network manager knew how many requests were still open. WebEngine + // tells neither, as both happen in the process of the page, so the page + // counts them itself: what is left of a document that is done loading is a + // tree that no longer changes and no request in the air. + const send = XMLHttpRequest.prototype.send; + XMLHttpRequest.prototype.send = function () { + pending += 1; + this.addEventListener("loadend", function () { + pending -= 1; + }); + return send.apply(this, arguments); + }; + + if (window.fetch) { + const fetch = window.fetch; + window.fetch = function () { + pending += 1; + return fetch.apply(window, arguments).finally(function () { + pending -= 1; + }); + }; + } + + new MutationObserver(function () { + dirty = true; + }).observe(document, { + attributes: true, + characterData: true, + childList: true, + subtree: true + }); + + // Asking resets the flag, so that the answer is "nothing changed since the + // last time you asked" rather than "nothing ever changed". + api.idle = function () { + const quiet = !dirty && pending === 0 + && document.readyState === "complete"; + dirty = false; + return quiet; + }; +}()); +)JS"; +} diff --git a/programs/qtjsruntime/bindings.h b/programs/qtjsruntime/bindings.h new file mode 100644 index 000000000..aa1c7b808 --- /dev/null +++ b/programs/qtjsruntime/bindings.h @@ -0,0 +1,50 @@ +/** + * Copyright (C) 2026 Daniel Berthereau + * + * @licstart + * This file is part of WebODF. + * + * WebODF is free software: you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License (GNU AGPL) as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * WebODF is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU Affero General Public License for more + * details. + * + * You should have received a copy of the GNU Affero General Public License + * along with WebODF. If not, see . + * @licend + * + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ + */ + +#ifndef BINDINGS_H +#define BINDINGS_H + +#include + +/** + * The script that gives the runtime of the library the things a page cannot do, + * through the object nativeio of the channel. It defines + * "window.__qtjsruntime.install" and "window.__qtjsruntime.run", and changes + * nothing until one of them is called. + */ +QByteArray runtimeBindings(); + +/** + * The script of QWebChannel, that qt ships as a resource of webengine. + */ +QByteArray webChannelScript(); + +/** + * The script that watches whether the page is still doing something. It is put + * in every page before anything else runs, and defines + * "window.__qtjsruntime.idle" and "window.__qtjsruntime.size". + */ +QByteArray idleWatcher(); + +#endif diff --git a/programs/qtjsruntime/main.cpp b/programs/qtjsruntime/main.cpp new file mode 100644 index 000000000..adfe673c7 --- /dev/null +++ b/programs/qtjsruntime/main.cpp @@ -0,0 +1,82 @@ +/** + * Copyright (C) 2026 Daniel Berthereau + * + * @licstart + * This file is part of WebODF. + * + * WebODF is free software: you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License (GNU AGPL) as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * WebODF is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU Affero General Public License for more + * details. + * + * You should have received a copy of the GNU Affero General Public License + * along with WebODF. If not, see . + * @licend + * + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ + */ + +/** + * Run a script or a page of the library in the webengine of qt, which is the + * blink of chromium, and end with the code the script asked for. It is what + * the target "test-qtjsruntime" runs the tests of the library with. + */ + +#include "runner.h" + +#include +#include + +namespace { + +/** + * Read the options, that come before the name of the script or of the page, and + * leave the rest to the script. + */ +bool parse(const QStringList& given, Options& options, QTextStream& err) { + int i = 0; + while (i < given.size() && given.at(i).startsWith("--")) { + const QString name = given.at(i).mid(2); + if (i + 1 >= given.size()) { + err << "The option '" << given.at(i) << "' takes a value.\n"; + return false; + } + const QString value = given.at(i + 1); + if (name == "export-pdf") { + options.exportPdf = value; + } else if (name == "timeout") { + options.timeout = value.toInt(); + } else { + err << "Unknown option '" << given.at(i) << "'.\n"; + return false; + } + i += 2; + } + options.arguments = given.mid(i); + return !options.arguments.isEmpty(); +} + +} // namespace + +int main(int argc, char** argv) { + QTextStream err(stderr); + QApplication app(argc, argv); + app.setApplicationName("qtjsruntime"); + + Options options; + if (!parse(app.arguments().mid(1), options, err)) { + err << "Usage: " << argv[0] << " [--export-pdf pdffile]" + " [--timeout seconds] html/javascriptfile [arguments]\n"; + err.flush(); + return 1; + } + + Runner runner(options); + return app.exec(); +} diff --git a/programs/qtjsruntime/nam.h b/programs/qtjsruntime/nam.h deleted file mode 100644 index fbb7fc4bf..000000000 --- a/programs/qtjsruntime/nam.h +++ /dev/null @@ -1,48 +0,0 @@ -#ifndef NAM_H -#define NAM_H - -#include -#include - -class NAM : public QNetworkAccessManager { -Q_OBJECT -private: - const QString host; - const int port; - int outstandingRequests; -public: - NAM(QObject* parent, const QString& host_ = QString(), int port_ = -1) - :QNetworkAccessManager(parent), host(host_), port(port_) { - outstandingRequests = 0; - connect(this, SIGNAL(finished(QNetworkReply*)), - this, SLOT(requestFinished())); - } - QNetworkReply* createRequest(QNetworkAccessManager::Operation o, - QNetworkRequest const& r, QIODevice* d) { - outstandingRequests += 1; - bool samehost = false; - if (port > 0) { - samehost = r.url().host() == host - || r.url().host().endsWith("." + host) - || host.endsWith("." + r.url().host()); - samehost &= r.url().port() != port; - } else { - // use host string as a prefix - samehost = r.url().toString().startsWith(host); - } - if (!samehost) { - // if not same host or domain and port, block - return QNetworkAccessManager::createRequest(o, QNetworkRequest(), - d); - } - return QNetworkAccessManager::createRequest(o, r, d); - } - bool hasOutstandingRequests() { - return outstandingRequests > 0; - } -public slots: - void requestFinished() { - outstandingRequests -= 1; - } -}; -#endif diff --git a/programs/qtjsruntime/nativeio.cpp b/programs/qtjsruntime/nativeio.cpp index 78aae88b6..33ccf56b1 100644 --- a/programs/qtjsruntime/nativeio.cpp +++ b/programs/qtjsruntime/nativeio.cpp @@ -1,114 +1,72 @@ +/** + * Copyright (C) 2026 Daniel Berthereau + * + * @licstart + * This file is part of WebODF. + * + * WebODF is free software: you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License (GNU AGPL) as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * WebODF is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU Affero General Public License for more + * details. + * + * You should have received a copy of the GNU Affero General Public License + * along with WebODF. If not, see . + * @licend + * + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ + */ + #include "nativeio.h" -#include + #include -#include +#include +#include -NativeIO::NativeIO(QObject* parent, const QDir& runtimedir_, - const QDir& cwd_, - const QMap& pathPermissions_) - :QObject(parent), runtimedir(runtimedir_), cwd(cwd_), - pathPermissions(pathPermissions_) { -} -QString -NativeIO::readFileSync(const QString& path, const QString& encoding) { - errstr = QString(); - QFile file(cwd.absoluteFilePath(path)); - QByteArray data; - if (file.open(QIODevice::ReadOnly)) { - data = file.readAll(); - } else { - errstr = "Could not read file."; - return QString(); - } - QString out; - if (encoding != "binary") { - QTextCodec *codec = QTextCodec::codecForName(encoding.toLatin1()); - if (codec) { - out = codec->toUnicode(data); - } - } - if (out.length() == 0 && data.length() > 0) { - out = QString(data.length(), 0); - for (int i = 0; i < data.length(); ++i) { - out[i] = data[i]; - } - } - return out; +NativeIO::NativeIO(QObject* parent, const QDir& cwd_) + : QObject(parent), + cwd(cwd_) { } -QString -NativeIO::read(const QString& path, int offset, int length) { - errstr = QString(); - QFile file(cwd.absoluteFilePath(path)); - QByteArray data; - if (file.open(QIODevice::ReadOnly) && (offset == 0 || file.seek(offset))) { - int lastLength = 0; - do { - lastLength = data.length(); - data += file.read(length - data.length()); - } while (data.length() < length && data.length() != lastLength); - } - if (length != data.length()) { - errstr = "Not enough data: " + QString::number(length) + - " instead of " + QString::number(data.length()); - return QString(); - } - QString out(length, 0); - for (int i = 0; i < length; ++i) { - out[i] = data[i]; - } - return out; + +QString NativeIO::absolute(const QString& path) const { + return cwd.absoluteFilePath(path); } -void -NativeIO::writeFile(const QString& path, const QString& data) { - QFile file(cwd.absoluteFilePath(path)); - errstr = QString(); + +QString NativeIO::writeFile(const QString& path, const QString& base64) { + const QByteArray data = QByteArray::fromBase64(base64.toLatin1()); + QFile file(absolute(path)); if (!file.open(QIODevice::WriteOnly)) { - errstr = "Could not open file for writing."; - return; - } - int length = data.length(); - QByteArray out(length, 0); - for (int i = 0; i < length; ++i) { - out[i] = data[i].unicode(); + return "Could not open file for writing."; } - if (file.write(out) != out.length()) { - errstr = "Could not write to file."; + if (file.write(data) != data.length()) { + return "Could not write to file."; } - return; + return QString(); } -void -NativeIO::unlink(const QString& path) { - errstr = QString(); - QFile file(cwd.absoluteFilePath(path)); + +QString NativeIO::deleteFile(const QString& path) { + QFile file(absolute(path)); if (!file.exists()) { - errstr = "File does not exist."; - } else if (!file.remove()) { - errstr = "Could not delete file"; + return "File does not exist."; } - if (QFile(cwd.absoluteFilePath(path)).exists()) { - errstr = "File still exists."; + if (!file.remove()) { + return "Could not delete file."; } + return QString(); } -int -NativeIO::getFileSize(const QString& path) { - errstr = QString(); - QFile file(cwd.absoluteFilePath(path)); - if (!file.exists()) { - errstr = "Could not determine file size."; - } - return file.size(); -} -void -NativeIO::exit(int exitcode) { - qApp->exit(exitcode); -} -QString -NativeIO::currentDirectory() const { - return QDir::currentPath(); + +qint64 NativeIO::getFileSize(const QString& path) { + const QFileInfo info(absolute(path)); + return info.exists() + ? info.size() + : -1; } -QStringList -NativeIO::libraryPaths() const { - QStringList paths; - paths << runtimedir.absolutePath() << cwd.absolutePath(); - return paths; + +void NativeIO::exit(int code) { + qApp->exit(code); } diff --git a/programs/qtjsruntime/nativeio.h b/programs/qtjsruntime/nativeio.h index 91855a6d6..5f4323aa4 100644 --- a/programs/qtjsruntime/nativeio.h +++ b/programs/qtjsruntime/nativeio.h @@ -1,41 +1,78 @@ +/** + * Copyright (C) 2026 Daniel Berthereau + * + * @licstart + * This file is part of WebODF. + * + * WebODF is free software: you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License (GNU AGPL) as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * WebODF is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU Affero General Public License for more + * details. + * + * You should have received a copy of the GNU Affero General Public License + * along with WebODF. If not, see . + * @licend + * + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ + */ + #ifndef NATIVEIO_H #define NATIVEIO_H -#include #include -#include - -class QWebPage; +#include -// class that exposes filesystem to web environment +/** + * The three things the page cannot do on its own, and the way it ends. + * + * It is given to the page over a QWebChannel, so every call answers through a + * callback. That is enough because the runtime of the library asks for these + * with a callback of its own: only "readFileSync" needs an answer in the call + * itself, and reading is left to the page, that reads a file with a request on + * it, the way the runtime of a browser does. + * + * Paths are taken as they are given, and a relative one is read from the + * directory the program was started in, as node does. + */ class NativeIO : public QObject { -Q_OBJECT -private: - QWebPage* webpage; - QString errstr; - const QDir runtimedir; - const QDir cwd; - const QMap pathPermissions; + Q_OBJECT public: - typedef QMap PathMap; - PathMap v; - NativeIO(QObject* parent, const QDir& runtimedir, const QDir& cwd, - const PathMap& pathPermissions = PathMap()); + NativeIO(QObject* parent, const QDir& cwd); + public slots: /** - * Return the last error. + * Write the bytes of a file, given in base64: what travels over the + * channel is text, and the bytes of a document are not. + * @return the error, or an empty string + */ + QString writeFile(const QString& path, const QString& base64); + + /** + * @return the error, or an empty string + */ + QString deleteFile(const QString& path); + + /** + * @return the size of the file, or -1 when it cannot be read + */ + qint64 getFileSize(const QString& path); + + /** + * End the program with a code. The page is not waiting for anything, so it + * is done as soon as the call comes back here. */ - QString error() { - return errstr; - } - QString readFileSync(const QString& path, const QString& encoding); - QString read(const QString& path, int offset, int length); - void writeFile(const QString& path, const QString& data); - void unlink(const QString& path); - int getFileSize(const QString& path); - void exit(int exitcode); - QString currentDirectory() const; - QStringList libraryPaths() const; + void exit(int code); + +private: + QString absolute(const QString& path) const; + + const QDir cwd; }; #endif diff --git a/programs/qtjsruntime/pagerunner.cpp b/programs/qtjsruntime/pagerunner.cpp deleted file mode 100644 index 0f8abefb5..000000000 --- a/programs/qtjsruntime/pagerunner.cpp +++ /dev/null @@ -1,206 +0,0 @@ -#include "pagerunner.h" - -#include "nam.h" -#include "nativeio.h" -#include -#include -#include -#include -#include -#include -#include -#include - -QByteArray getRuntimeBindings() { - return - "if (typeof(runtime) !== 'undefined' && typeof(nativeio) !== 'undefined') {" - " runtime.readFileSync = function (path, encoding) {" - " return nativeio.readFileSync(path, encoding);" - " };" - " runtime.readFile = function (path, encoding, callback) {" - " var data = nativeio.readFileSync(path, encoding)," - " err = nativeio.error()||null;" - " if (err) {" - " data = undefined;" - " } else if (encoding === 'binary') {" - " data = runtime.byteArrayFromString(data, 'binary');" - " }" - " callback(nativeio.error()||null, data);" - " };" - " runtime.read = function (path, offset, length, callback) {" - " var data = nativeio.read(path, offset, length);" - " data = runtime.byteArrayFromString(data, 'binary');" - " callback(nativeio.error()||null, data);" - " };" - " runtime.writeFile = function (path, data, callback) {" - " data = runtime.byteArrayToString(data, 'binary');" - " nativeio.writeFile(path, data);" - " callback(nativeio.error()||null);" - " };" - " runtime.deleteFile = function (path, callback) {" - " nativeio.unlink(path);" - " callback(nativeio.error()||null);" - " };" - " runtime.getFileSize = function (path, callback) {" - " callback(nativeio.getFileSize(path));" - " };" - " runtime.exit = function (exitCode) {" - " nativeio.exit(exitCode);" - " };" - " runtime.currentDirectory = function () {" - " return nativeio.currentDirectory();" - " };" - "}"; -} - -PageRunner::PageRunner(const QStringList& args) - : QWebPage(0), - out(stdout), - err(stderr), - view(new QWidget()) { - - QMap settings = parseArguments(args); - QStringList arguments = args.mid(settings.size() * 2); - exportpdf = settings.value("export-pdf"); - exportpng = settings.value("export-png"); - url = QUrl(arguments[0]); - nativeio = new NativeIO(this, QFileInfo(arguments[0]).dir(), - QDir::current()); - if (url.scheme() == "file" || url.isRelative()) { - QFileInfo info(arguments[0]); - url = QUrl::fromLocalFile(info.absoluteFilePath()); - if (!info.isReadable() || !info.isFile()) { - QTextStream err(stderr); - err << "Cannot read file '" + url.toString() + "'.\n"; - qApp->exit(1); - } - } - nam = new NAM(this, QUrl(url).host(), QUrl(url).port()); - - setNetworkAccessManager(nam); - connect(this, SIGNAL(loadFinished(bool)), this, SLOT(finished(bool))); - connect(mainFrame(), SIGNAL(javaScriptWindowObjectCleared()), - this, SLOT(slotInitWindowObjects())); - sawJSError = false; - - setView(view); - scriptMode = arguments[0].endsWith(".js"); - if (scriptMode) { - QByteArray html = "'" + arguments[0].toUtf8().replace('\'', "\\'") - + "'"; - for (int i = 1; i < arguments.length(); ++i) { - html += ",'" + arguments[i].toUtf8().replace('\'', "\\'") + "'"; - } - html = "" - "" - "" - ""; - // add runtime modification - html += ""; - html += "\n"; - QTemporaryFile tmp("XXXXXX.html"); - tmp.setAutoRemove(true); - tmp.open(); - tmp.write(html); - tmp.close(); - QFileInfo info(tmp.fileName()); - mainFrame()->load(QUrl::fromLocalFile(info.absoluteFilePath())); - } else { - // Make the url absolute. If it is not done here, QWebFrame will do - // it, and it will lose the query and fragment part. - QUrl absurl; - if (url.isRelative()) { - absurl = QUrl::fromLocalFile(QFileInfo(url.toLocalFile()).absoluteFilePath()); - absurl.setQuery(url.query()); - absurl.setFragment(url.fragment()); - } else { - absurl = url; - } - mainFrame()->load(absurl); - } -} -QString PageRunner::userAgentForUrl ( const QUrl & url ) const -{ - return "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.2 (KHTML, like Gecko) Chrome/15.0.874.121 Safari/535.2"; -} -PageRunner::~PageRunner() { - delete view; -} -void PageRunner::finished(bool ok) { - // bind nativeio - if (!ok) { - qApp->exit(1); - } - if (!scriptMode) { - mainFrame()->evaluateJavaScript(getRuntimeBindings()); - } - - // connect signals - connect(this, SIGNAL(contentsChanged()), this, SLOT(noteChange())); - connect(this, SIGNAL(downloadRequested(QNetworkRequest)), - this, SLOT(noteChange())); - connect(this, SIGNAL(repaintRequested(QRect)), - this, SLOT(noteChange())); - connect(mainFrame(), SIGNAL(pageChanged()), this, SLOT(noteChange())); - connect(this, SIGNAL(geometryChangeRequested(QRect)), - this, SLOT(noteChange())); - QTimer::singleShot(150, this, SLOT(reallyFinished())); - changed = false; - time.start(); -} -void PageRunner::reallyFinished() { - int latency = time.restart(); - // err << latency << " " << changed << " " << nam->hasOutstandingRequests() << endl; - if (changed || latency >= 152 || nam->hasOutstandingRequests()) { - QTimer::singleShot(150, this, SLOT(reallyFinished())); - changed = false; - return; - } - if (!exportpdf.isEmpty() || !exportpng.isEmpty()) { - setViewportSize(mainFrame()->contentsSize()); - } - if (!exportpng.isEmpty()) { - renderToFile(exportpng); - } - if (!exportpdf.isEmpty()) { - printToFile(exportpdf); - } - qApp->exit(sawJSError); -} -QMap PageRunner::parseArguments(const QStringList& args) { - int i = 0; - QMap settings; - while (i + 2 < args.length()) { - if (args[i].startsWith("--")) { - settings[args[i].mid(2)] = args[i+1]; - } - i += 2; - } - return settings; -} -void PageRunner::slotInitWindowObjects() { - mainFrame()->addToJavaScriptWindowObject("nativeio", nativeio); -} -void PageRunner::renderToFile(const QString& filename) { - QImage pixmap(mainFrame()->contentsSize().boundedTo(QSize(10000,10000)), - QImage::Format_ARGB32_Premultiplied); - QPainter painter(&pixmap); - mainFrame()->render(&painter, QWebFrame::ContentsLayer); - painter.end(); - pixmap.save(filename); -} -void PageRunner::printToFile(const QString& filename) { - QPrinter printer(QPrinter::HighResolution); - printer.setFontEmbeddingEnabled(true); - printer.setOutputFormat(QPrinter::PdfFormat); - printer.setOutputFileName(filename); - mainFrame()->print(&printer); -} diff --git a/programs/qtjsruntime/pagerunner.h b/programs/qtjsruntime/pagerunner.h deleted file mode 100644 index 3f8809ce1..000000000 --- a/programs/qtjsruntime/pagerunner.h +++ /dev/null @@ -1,66 +0,0 @@ -#ifndef PAGERUNNER_H -#define PAGERUNNER_H - -#include -#include -#include - -class NAM; -class NativeIO; - -class PageRunner : public QWebPage { -Q_OBJECT -private: - QUrl url; - NAM* nam; - QTextStream out; - QTextStream err; - bool changed; - QWidget* const view; - QTime time; - bool scriptMode; - NativeIO* nativeio; - QString exportpdf; - QString exportpng; - bool sawJSError; -public: - PageRunner(const QStringList& args); - ~PageRunner(); -private slots: - void finished(bool ok); - void noteChange() { - changed = true; - } - void reallyFinished(); - void slotInitWindowObjects(); - bool shouldInterruptJavaScript() { - changed = true; - return false; - } -private: - void javaScriptConsoleMessage(const QString& message, int lineNumber, - const QString& sourceID) { - changed = true; - if (scriptMode) { - err << message << endl; - } else { - err << sourceID << ":" << lineNumber << " " << message << endl; - } - sawJSError = true; - } - void javaScriptAlert(QWebFrame* /*frame*/, const QString& msg) { - changed = true; - err << "ALERT: " << msg << endl; - } - bool javaScriptPrompt(QWebFrame*, const QString&, const QString&, QString*){ - changed = true; - return false; - } - void renderToFile(const QString& filename); - void printToFile(const QString& filename); - // overload because default impl was causing a crash - QString userAgentForUrl(const QUrl&) const; - QMap parseArguments(const QStringList& args); -}; - -#endif diff --git a/programs/qtjsruntime/qtjsruntime.cpp b/programs/qtjsruntime/qtjsruntime.cpp deleted file mode 100644 index 6a47d0b19..000000000 --- a/programs/qtjsruntime/qtjsruntime.cpp +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Small executable that loads a javascript or html page from local a URI. - * If the URI ends in .js it will be run in QtScript engine, otherwise, it will - * be assumed to be a webpage that will be opened in - */ -#include "pagerunner.h" -#include -int -main(int argc, char** argv) { - if (argc < 2) { - QTextStream err(stderr); - err << "Usage: " << argv[0] << " [--export-pdf pdffile] " - "[--export-png pngfile] html/javascripfile [arguments]\n"; - return 1; - } - QApplication app(argc, argv); - app.setApplicationName(argv[0]); - PageRunner p(QCoreApplication::arguments().mid(1)); - return app.exec(); -} diff --git a/programs/qtjsruntime/requestfilter.h b/programs/qtjsruntime/requestfilter.h new file mode 100644 index 000000000..2b3817c22 --- /dev/null +++ b/programs/qtjsruntime/requestfilter.h @@ -0,0 +1,74 @@ +/** + * Copyright (C) 2026 Daniel Berthereau + * + * @licstart + * This file is part of WebODF. + * + * WebODF is free software: you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License (GNU AGPL) as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * WebODF is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU Affero General Public License for more + * details. + * + * You should have received a copy of the GNU Affero General Public License + * along with WebODF. If not, see . + * @licend + * + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ + */ + +#ifndef REQUESTFILTER_H +#define REQUESTFILTER_H + +#include +#include +#include + +/** + * Let through what the page needs, and nothing else. + * + * A page that is run to test the library, or to draw a document into a file, + * has no business on the network: a test that reaches a server is a test that + * fails when the network is down, and a document that names a picture on a + * server would have it read without anyone asking. So only the files of the + * machine, the scheme of nativeio and what the page holds itself are served; + * a page that was itself loaded from a server keeps the right to talk to that + * one server, which is what the editor of the collaboration needs. + * + * It replaces the network access manager of the port to WebKit: WebEngine has + * none, as the network is handled in the process of the page, and an + * interceptor is where a request is seen. + */ +class RequestFilter : public QWebEngineUrlRequestInterceptor { + Q_OBJECT +public: + RequestFilter(QObject* parent, const QUrl& page) + : QWebEngineUrlRequestInterceptor(parent), + host(page.host()), + port(page.port()) { + } + + void interceptRequest(QWebEngineUrlRequestInfo& info) override { + const QUrl url = info.requestUrl(); + const QString scheme = url.scheme(); + if (scheme == "file" || scheme == "qrc" || scheme == "nativeio" + || scheme == "data" || scheme == "blob" || scheme == "about") { + return; + } + if (!host.isEmpty() && url.host() == host && url.port() == port) { + return; + } + info.block(true); + } + +private: + const QString host; + const int port; +}; + +#endif diff --git a/programs/qtjsruntime/runner.cpp b/programs/qtjsruntime/runner.cpp new file mode 100644 index 000000000..187c27f57 --- /dev/null +++ b/programs/qtjsruntime/runner.cpp @@ -0,0 +1,343 @@ +/** + * Copyright (C) 2026 Daniel Berthereau + * + * @licstart + * This file is part of WebODF. + * + * WebODF is free software: you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License (GNU AGPL) as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * WebODF is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU Affero General Public License for more + * details. + * + * You should have received a copy of the GNU Affero General Public License + * along with WebODF. If not, see . + * @licend + * + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ + */ + +#include "runner.h" + +#include "bindings.h" +#include "nativeio.h" +#include "requestfilter.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +/** How long the page is left between two questions, in milliseconds. */ +const int pollInterval = 150; + +/** How many quiet answers in a row mean that the page is done. */ +const int quietRoundsNeeded = 2; + +/** The size the page is drawn in when no file is written. */ +const QSize defaultViewSize(1024, 768); + +/** + * Write a list of strings the way javascript reads it. + */ +QString asJson(const QStringList& strings) { + QJsonArray array; + for (const QString& string : strings) { + array.append(string); + } + return QString::fromUtf8(QJsonDocument(array) + .toJson(QJsonDocument::Compact)).trimmed(); +} + +/** + * Write a string the way javascript reads it. + */ +QString asJson(const QString& string) { + const QString array = asJson(QStringList{string}); + return array.mid(1).chopped(1); +} + +/** + * Make the url absolute, keeping the query and the fragment: they are what a + * page is often told to do, and loading a relative path loses them. + */ +QUrl absolute(const QString& name) { + const QUrl url(name); + if (!url.isRelative() && url.scheme() != "file") { + return url; + } + const QString path = url.scheme() == "file" + ? url.toLocalFile() + : url.path(); + QUrl result = QUrl::fromLocalFile(QFileInfo(path).absoluteFilePath()); + result.setQuery(url.query()); + result.setFragment(url.fragment()); + return result; +} + +} // namespace + +Runner::Runner(const Options& options_) + : QWebEnginePage(static_cast(nullptr)), + options(options_), + scriptMode(options_.arguments.at(0).endsWith(".js")), + err(stderr), + nativeio(new NativeIO(this, QDir::current())), + view(new QWebEngineView()), + bootstrap(nullptr), + quietRounds(0), + watching(false), + sawError(false) { + + QWebEngineProfile* const profile = QWebEngineProfile::defaultProfile(); + profile->setUrlRequestInterceptor(new RequestFilter(this, + absolute(options.arguments.at(0)))); + + QWebChannel* const channel = new QWebChannel(this); + channel->registerObject("nativeio", nativeio); + setWebChannel(channel); + + // The watcher goes in every page before anything else runs, so that it + // sees the whole of what the page does. + QWebEngineScript watcher; + watcher.setName("qtjsruntime-idle"); + watcher.setSourceCode(QString::fromUtf8(idleWatcher())); + watcher.setInjectionPoint(QWebEngineScript::DocumentCreation); + watcher.setWorldId(QWebEngineScript::MainWorld); + watcher.setRunsOnSubFrames(true); + profile->scripts()->insert(watcher); + + QWebEngineSettings* const preferences = settings(); + // A page loaded from a file reads the library and the documents beside it, + // which chromium forbids unless it is told otherwise; it has no business + // on the network, which the filter above sees to as well. + preferences->setAttribute(QWebEngineSettings::LocalContentCanAccessFileUrls, + true); + preferences->setAttribute(QWebEngineSettings::LocalContentCanAccessRemoteUrls, + false); + preferences->setAttribute(QWebEngineSettings::ErrorPageEnabled, false); + preferences->setAttribute(QWebEngineSettings::ShowScrollBars, false); + + connect(this, &QWebEnginePage::loadFinished, this, &Runner::loaded); + + // The page is shown, even when nothing is drawn into a file: chromium + // slows down the timers of a page that is not seen, and stops asking for + // frames, which a test that waits for one would wait for forever. Under a + // platform without a screen, "-platform offscreen", showing it costs + // nothing. + view->setPage(this); + view->resize(defaultViewSize); + view->show(); + + if (options.timeout > 0) { + QTimer::singleShot(options.timeout * 1000, this, [this] { + err << "The page did not end within " << options.timeout + << " seconds.\n"; + err.flush(); + qApp->exit(3); + }); + } + + if (scriptMode) { + loadScript(); + } else { + loadPage(); + } +} + +Runner::~Runner() { + QWebEngineProfile::defaultProfile()->setUrlRequestInterceptor(nullptr); + delete view; +} + +void Runner::loadScript() { + const QStringList arguments = options.arguments; + const QFileInfo info(arguments.at(0)); + if (!info.isReadable() || !info.isFile()) { + err << "Cannot read file '" << arguments.at(0) << "'.\n"; + err.flush(); + qApp->exit(1); + return; + } + // The runtime of the library looks for the classes it loads in the + // directory of the script that holds it and in the current one, as it does + // under node. + const QStringList libraryPaths = {info.dir().absolutePath(), + QDir::currentPath()}; + const QString page = QStringLiteral(R"HTML( + + + +qtjsruntime + + + + + + + +)HTML") + .arg(QString::fromUtf8(webChannelScript()), + QString::fromUtf8(runtimeBindings()), + QString::fromUtf8(QUrl::fromLocalFile(info.absoluteFilePath()) + .toEncoded()), + asJson(libraryPaths), + asJson(QDir::currentPath()), + arguments.size() > 1 + ? "window.__qtjsruntime.run(" + + asJson(arguments.mid(1)) + ");" + : QString()); + + // The page is written beside the document rather than in the directory of + // the temporary files, so that a path the script reads is read from the + // same place as under node, and so that the scripts of the library, whose + // paths are absolute, are read from a page of the same scheme. + bootstrap = new QTemporaryFile(QDir::current() + .absoluteFilePath("qtjsruntimeXXXXXX.html"), this); + if (!bootstrap->open() || bootstrap->write(page.toUtf8()) < 0) { + err << "Cannot write the page that runs the script in " + << QDir::currentPath() << ".\n"; + err.flush(); + qApp->exit(1); + return; + } + bootstrap->flush(); + setUrl(QUrl::fromLocalFile(QFileInfo(*bootstrap).absoluteFilePath())); +} + +void Runner::loadPage() { + const QUrl url = absolute(options.arguments.at(0)); + if (url.isLocalFile()) { + const QFileInfo info(url.toLocalFile()); + if (!info.isReadable() || !info.isFile()) { + err << "Cannot read file '" << url.toString() << "'.\n"; + err.flush(); + qApp->exit(1); + return; + } + } + setUrl(url); +} + +void Runner::loaded(bool ok) { + if (!ok) { + err << "Cannot load '" << url().toString() << "'.\n"; + err.flush(); + qApp->exit(1); + return; + } + if (!scriptMode) { + // A page that uses the library gets the same bindings, so that it may + // write a file or end the program as a script does. + runJavaScript(QString::fromUtf8(webChannelScript()) + + QString::fromUtf8(runtimeBindings()) + + "new QWebChannel(qt.webChannelTransport, function (channel) {" + "if (typeof runtime !== \"undefined\") {" + "window.__qtjsruntime.install(channel.objects.nativeio, [], " + + asJson(QDir::currentPath()) + "); } });"); + } + // A script says when it is done by calling "runtime.exit", so there is + // nothing to watch for. A page does not, so it is watched until it stops + // changing, which is also when it may be printed into a file. + if (!scriptMode || !options.exportPdf.isEmpty()) { + startWatching(); + } +} + +void Runner::startWatching() { + if (!watching) { + watching = true; + QTimer::singleShot(pollInterval, this, &Runner::pollIdle); + } +} + +void Runner::pollIdle() { + runJavaScript(QStringLiteral( + "!!(window.__qtjsruntime && window.__qtjsruntime.idle())"), + [this](const QVariant& answer) { + quietRounds = answer.toBool() + ? quietRounds + 1 + : 0; + if (quietRounds >= quietRoundsNeeded) { + writeExports(); + } else { + QTimer::singleShot(pollInterval, this, &Runner::pollIdle); + } + }); +} + +void Runner::writeExports() { + writePdf([this] { + qApp->exit(sawError ? 1 : 0); + }); +} + +void Runner::writePdf(const std::function& next) { + if (options.exportPdf.isEmpty()) { + next(); + return; + } + QMetaObject::Connection* const connection = new QMetaObject::Connection; + *connection = connect(this, &QWebEnginePage::pdfPrintingFinished, this, + [this, next, connection](const QString& path, bool ok) { + disconnect(*connection); + delete connection; + if (!ok) { + err << "Cannot write '" << path << "'.\n"; + err.flush(); + sawError = true; + } + next(); + }); + printToPdf(options.exportPdf); +} + +void Runner::javaScriptConsoleMessage(JavaScriptConsoleMessageLevel level, + const QString& message, int lineNumber, const QString& sourceID) { + if (scriptMode) { + err << message << "\n"; + } else { + err << sourceID << ":" << lineNumber << " " << message << "\n"; + } + err.flush(); + if (level == ErrorMessageLevel) { + sawError = true; + // A script that threw will never call "runtime.exit", so it is watched + // from here on: it ends as soon as nothing is left to do, with the + // code of a failure, rather than at the end of the time it is given. + startWatching(); + } +} + +void Runner::javaScriptAlert(const QUrl& /*securityOrigin*/, + const QString& msg) { + err << "ALERT: " << msg << "\n"; + err.flush(); +} + +bool Runner::javaScriptPrompt(const QUrl& /*securityOrigin*/, + const QString& /*msg*/, const QString& /*defaultValue*/, + QString* /*result*/) { + return false; +} diff --git a/programs/qtjsruntime/runner.h b/programs/qtjsruntime/runner.h new file mode 100644 index 000000000..bf6664840 --- /dev/null +++ b/programs/qtjsruntime/runner.h @@ -0,0 +1,103 @@ +/** + * Copyright (C) 2026 Daniel Berthereau + * + * @licstart + * This file is part of WebODF. + * + * WebODF is free software: you can redistribute it and/or modify it under the + * terms of the GNU Affero General Public License (GNU AGPL) as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * WebODF is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU Affero General Public License for more + * details. + * + * You should have received a copy of the GNU Affero General Public License + * along with WebODF. If not, see . + * @licend + * + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ + */ + +#ifndef RUNNER_H +#define RUNNER_H + +#include +#include +#include + +#include + +class NativeIO; +class QTemporaryFile; +class QWebEngineView; + +/** + * What was asked on the command line. + */ +struct Options { + /** The file the page is printed into, if any. */ + QString exportPdf; + /** How long the program may run, in seconds; 0 for as long as it takes. */ + int timeout = 600; + /** The script or the page, then the arguments of the script. */ + QStringList arguments; +}; + +/** + * Load a script or a page, and end when it is done. + * + * A file that ends in ".js" is a script: a page is made that loads the runtime + * of the library and hands the script to it, the way node does. Anything else + * is a page, loaded as it is. In both cases the program ends when the script + * calls "runtime.exit", or, when a file is to be written, once the page has + * stopped changing. + */ +class Runner : public QWebEnginePage { + Q_OBJECT +public: + Runner(const Options& options); + ~Runner() override; + +protected: + void javaScriptConsoleMessage(JavaScriptConsoleMessageLevel level, + const QString& message, int lineNumber, + const QString& sourceID) override; + void javaScriptAlert(const QUrl& securityOrigin, + const QString& msg) override; + bool javaScriptPrompt(const QUrl& securityOrigin, const QString& msg, + const QString& defaultValue, + QString* result) override; + +private slots: + void loaded(bool ok); + /** Ask the page whether it is still doing something. */ + void pollIdle(); + +private: + /** Load the script through a page that gives it the runtime. */ + void loadScript(); + /** Load the page named on the command line, as it is. */ + void loadPage(); + /** Watch the page until it stops doing anything, then end. */ + void startWatching(); + /** Write the file that was asked for, then end. */ + void writeExports(); + void writePdf(const std::function& next); + + const Options options; + const bool scriptMode; + QTextStream err; + NativeIO* nativeio; + QWebEngineView* view; + QTemporaryFile* bootstrap; + /** How many times in a row the page answered that it had nothing to do. */ + int quietRounds; + bool watching; + bool sawError; +}; + +#endif diff --git a/programs/text/format.en.html b/programs/text/format.en.html new file mode 100644 index 000000000..396223ac8 --- /dev/null +++ b/programs/text/format.en.html @@ -0,0 +1,63 @@ +

Why choose the OpenDocument format

+

OpenDocument is the first format of office documents that was approved + as an international standard, ISO/IEC 26300, in 2006, and the only one + that works as one: it is written in the open by OASIS, it belongs to no + company, and many programs of many makers read and write all of it. + Another "standard" was approved after it, Microsoft OOXML, without the + first one being withdrawn, which is a nonsense: what makes a standard is + to be single, shared and lasting.

+

OpenDocument is therefore the only standard whose documents are shared + and last. Every office suite needs, in practice, to write elements the + standard does not provide for. What becomes of those elements is what + sets the two formats apart, fundamentally, and what makes OOXML a false + standard.

+

OpenDocument gives the elements it does not provide for a name of their + own: a program writes its additions under its own name, LibreOffice under + "loext", and the standard states that a reader that does not know them + has to ignore them and read on, rather than read them wrongly. The file + stays a file of the standard.

+

OOXML treats the elements of its own another way. Beside the strict form, + the standard approved in 2008 defined a transitional one, which keeps the + ways of writing of the old binary files of the Microsoft Office suite. It + was meant for turning the older documents over, and the intent publicly + stated was that it be dropped from the standard. Yet it is that temporary + form the office suites still write today: the format Microsoft 365 saves + in is the transitional one to this day, in the desktop version as in the + web one. The strict form, the one that was to be the default, is still + only a choice made by hand, which the web version does not even offer.

+

Besides, the adoption of OOXML by the fast track of 2008 was secured by + politico-commercial pressure and by acts of corruption of Microsoft. It + drew, accordingly, several appeals and the stuffing of the committees + that voted: in several countries, dozens of companies close to Microsoft + joined the national body of standards on the eve of the vote, carried it, + and left soon after, which is + + documented country by country. A reader has to implement not the + standard alone, but the whole history of one proprietary program. It is + there to be measured: the whole standard of OpenDocument holds in a + thousand pages, the one of OOXML comes near seven thousand pages.

+

In short, a document written today in OpenDocument will still be read in + twenty years, by anyone and with anything. That is the whole difference + with a standard in name only, and the whole worth of a true one.

+ +

To read further

+ diff --git a/programs/text/format.fr.html b/programs/text/format.fr.html new file mode 100644 index 000000000..90f4c6283 --- /dev/null +++ b/programs/text/format.fr.html @@ -0,0 +1,64 @@ +

Pourquoi choisir le format OpenDocument

+

OpenDocument est le premier format de documents bureautiques approuvé + comme norme internationale, ISO/IEC 26300, en 2006, et le seul qui en + tienne le rôle : il est écrit en toute transparence par OASIS, il + n’appartient à aucune entreprise et de nombreux programmes de multiples + éditeurs le lisent et l’écrivent complètement. Un autre « standard » a été + approuvé ensuite, Microsoft OOXML, sans annuler le précédent, ce qui est + un non-sens puisque le principe d’un standard est d’être unique, partagé + et durable.

+

Or OpenDocument est la seule norme qui crée des documents partageables et + durables. En effet, en pratique, toute suite bureautique a besoin d’écrire + des éléments non prévus par la norme. C’est le traitement de ces éléments + qui sépare fondamentalement les deux formats et fait d’OOXML une fausse + norme.

+

OpenDocument prévoit une identification claire pour les éléments non + prévus : un programme écrit ses ajouts avec un nom qui lui est propre, + LibreOffice sous « loext », et la norme précise qu’un lecteur qui ne les + connaît pas doit les ignorer et poursuivre sa lecture sans les + interpréter de manière erronée. Le fichier reste un fichier de la norme.

+

OOXML traite les éléments spécifiques différemment. À côté de la forme + stricte, la norme approuvée en 2008 définissait une forme transitoire, qui + conserve les modes de codage des anciens fichiers binaires de la suite + Microsoft Office. Elle était prévue pour convertir les anciens documents + et l’intention publiquement affichée était de la retirer de la norme. + Pourtant, c’est cette variante temporaire que les suites bureautiques + écrivent toujours aujourd’hui : le format par défaut de Microsoft 365 est + toujours la forme transitoire, dans la version bureau comme dans la + version web. La forme stricte qui devait être activée par défaut n’est + toujours qu’un choix manuel que la version web ne propose même pas.

+

En outre, l’adoption d’OOXML par voie accélérée en 2008 a été établie + suite à des pressions politico-commerciales et à des actes de corruption + de Microsoft. De fait, elle a fait l’objet de multiples recours et de + bourrages de comités : dans plusieurs pays, des dizaines d’entreprises + proches de Microsoft ont adhéré au comité national de normalisation à la + veille du vote, l’ont emporté, puis s’en sont retirées, ce qui est + + documenté pays par pays. Un lecteur doit donc implémenter non pas + seulement la norme, mais toute l’histoire d’un programme propriétaire. + Cela se mesure : la norme complète OpenDocument tient en un millier de + pages, celle d’OOXML approche les sept mille pages.

+

Bref, un document écrit aujourd’hui en OpenDocument se lira encore dans + vingt ans, par qui que ce soit et avec quoi que ce soit. C’est toute la + différence avec un faux standard et tout l’intérêt d’une vraie norme.

+ +

Pour aller plus loin

+ diff --git a/programs/touchui/CMakeLists.txt b/programs/touchui/CMakeLists.txt deleted file mode 100644 index edfc1b10d..000000000 --- a/programs/touchui/CMakeLists.txt +++ /dev/null @@ -1,26 +0,0 @@ -set(TOUCHUI_FILES - app/app.js - app/controller/Files.js - app/model/FileSystem.js - app/views/FileDetail.js - app/views/FilesList.js - app/views/OdfView.js - app/views/Viewport.js - sencha-touch.css - sencha-touch.js - app/store/FileStore.js - ZoomOut.png - ZoomIn.png - go-previous.png - go-next.png - zoom-fit-width.png - zoom-fit-best.png - zoom-fit-height.png - PARENT_SCOPE -) - -COPY_FILES(TOUCHUIDEPS ${CMAKE_CURRENT_SOURCE_DIR} - ${CMAKE_CURRENT_BINARY_DIR} ${TOUCHUI_FILES}) -COPY_FILES(TOUCHUIDEPS ${CMAKE_CURRENT_SOURCE_DIR} - ${CMAKE_CURRENT_BINARY_DIR} index.html scripts.js welcome.odt) -add_custom_target(touchuiDepencencies ALL DEPENDS ${TOUCHUIDEPS}) diff --git a/programs/touchui/ZoomIn.png b/programs/touchui/ZoomIn.png deleted file mode 100644 index 6041c6ff9..000000000 Binary files a/programs/touchui/ZoomIn.png and /dev/null differ diff --git a/programs/touchui/ZoomOut.png b/programs/touchui/ZoomOut.png deleted file mode 100644 index 733a4e1ad..000000000 Binary files a/programs/touchui/ZoomOut.png and /dev/null differ diff --git a/programs/touchui/app/app.js b/programs/touchui/app/app.js deleted file mode 100644 index e9e00bbf3..000000000 --- a/programs/touchui/app/app.js +++ /dev/null @@ -1,76 +0,0 @@ -/** - * Copyright (C) 2012 KO GmbH - - * @licstart - * The JavaScript code in this page is free software: you can redistribute it - * and/or modify it under the terms of the GNU Affero General Public License - * (GNU AGPL) as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. The code is distributed - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU AGPL for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this code. If not, see . - * - * As additional permission under GNU AGPL version 3 section 7, you - * may distribute non-source (e.g., minimized or compacted) forms of - * that code without the copy of the GNU GPL normally required by - * section 4, provided you include this license notice and a URL - * through which recipients can access the Corresponding Source. - * - * As a special exception to the AGPL, any HTML file which merely makes function - * calls to this code, and for that purpose includes it by reference shall be - * deemed a separate work for copyright law purposes. In addition, the copyright - * holders of this code give you permission to combine this code with free - * software libraries that are released under the GNU LGPL. You may copy and - * distribute such a system following the terms of the GNU AGPL for this code - * and the LGPL for the libraries. If you modify this code, you may extend this - * exception to your version of the code, but you are not obligated to do so. - * If you do not wish to do so, delete this exception statement from your - * version. - * - * This license applies to this entire compilation. - * @licend - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ - */ -/*global Ext, invokeString, document*/ -/** - * When the application has been initialized, this function is called, if - * it has been set. By default, it will start scanning the files in the - * files sytem. It can be overridden by e.g. PhoneGap to wait until the device - * is ready. - */ -var onApplicationLaunch = function (app) { - "use strict"; - app.startScanningDirectories(); -}; -Ext.application({ - name : 'WebODFApp', - models: ['FileSystem'], - views: ['Viewport', 'FilesList', 'FileDetail', 'OdfView'], - controllers: ['Files'], - stores: ['FileStore'], - launch: function () { - 'use strict'; - var app = this; - Ext.create('WebODFApp.view.Viewport'); - app.openUrl = function (url) { - var proxy = Ext.getStore("FileStore").getProxy(); - proxy.getRecord(url, function (record) { - var controller; - if (!record) { - alert("Cannot open " + url); - } else { - controller = app.getController('Files'); - controller.show(null, null, null, record); - } - }); - }; - this.startScanningDirectories = function () { - var proxy = Ext.getStore("FileStore").getProxy(); - proxy.startScanningDirectories(); - }; - onApplicationLaunch(this); - } -}); diff --git a/programs/touchui/app/controller/Files.js b/programs/touchui/app/controller/Files.js deleted file mode 100644 index 57c5787a3..000000000 --- a/programs/touchui/app/controller/Files.js +++ /dev/null @@ -1,118 +0,0 @@ -/** - * Copyright (C) 2012 KO GmbH - - * @licstart - * The JavaScript code in this page is free software: you can redistribute it - * and/or modify it under the terms of the GNU Affero General Public License - * (GNU AGPL) as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. The code is distributed - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU AGPL for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this code. If not, see . - * - * As additional permission under GNU AGPL version 3 section 7, you - * may distribute non-source (e.g., minimized or compacted) forms of - * that code without the copy of the GNU GPL normally required by - * section 4, provided you include this license notice and a URL - * through which recipients can access the Corresponding Source. - * - * As a special exception to the AGPL, any HTML file which merely makes function - * calls to this code, and for that purpose includes it by reference shall be - * deemed a separate work for copyright law purposes. In addition, the copyright - * holders of this code give you permission to combine this code with free - * software libraries that are released under the GNU LGPL. You may copy and - * distribute such a system following the terms of the GNU AGPL for this code - * and the LGPL for the libraries. If you modify this code, you may extend this - * exception to your version of the code, but you are not obligated to do so. - * If you do not wish to do so, delete this exception statement from your - * version. - * - * This license applies to this entire compilation. - * @licend - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ - */ -/*global Ext, runtime*/ -Ext.define('WebODFApp.controller.Files', { - extend: 'Ext.app.Controller', - - config: { - refs: { - mainView: 'mainview', - filesList: 'fileslist list', - fileDetail: 'filedetail', - openButton: '#openButton', - odfView: 'odfview', - title: 'titlebar' - }, - control: { - mainView: { - pop: 'pop', - push: 'push', - back: 'back' - }, - filesList: { - itemtap: 'show' - }, - openButton: { - tap: 'open' - } - } - }, - back: function () { - "use strict"; - this.odfView.hideCanvas(); - }, - push: function (view, item) { - "use strict"; - if (item.xtype === "filedetail") { - this.getOpenButton().show(); - } else { - this.getOpenButton().hide(); - } - }, - pop: function (view, item) { - "use strict"; - if (item.xtype === "odfview") { // going to filedetail - this.getOpenButton().show(); - } else { - this.getOpenButton().hide(); - } - this.getFilesList().deselectAll(); - }, - show: function (list, index, target, record, e) { - "use strict"; - // set the record in the details view and the file view - // this way, document starts loading in the background - var c = this; - if (!this.fileDetail) { - this.fileDetail = Ext.create('WebODFApp.view.FileDetail'); - } - if (!this.odfView) { - this.odfView = Ext.create('WebODFApp.view.OdfView'); - this.odfView.addCanvasListener(this.fileDetail.canvasListener); - } - this.odfView.hideCanvas(); - this.fileDetail.odfView = this.odfView; - this.fileDetail.setRecord(record); - this.getMainView().push(this.fileDetail); - runtime.setTimeout(function () { - c.odfView.setRecord(record); - }, 300); - }, - open: function (options) { - "use strict"; - var c = this; - if (!this.odfView) { - this.odfView = Ext.create('WebODFApp.view.OdfView'); - } - this.odfView.hideCanvas(); - this.odfView.setRecord(this.fileDetail.getRecord()); - this.getMainView().push(this.odfView); - runtime.setTimeout(function () { - c.odfView.showCanvas(); - }, 300); - } -}); diff --git a/programs/touchui/app/model/FileSystem.js b/programs/touchui/app/model/FileSystem.js deleted file mode 100644 index a6c22dde8..000000000 --- a/programs/touchui/app/model/FileSystem.js +++ /dev/null @@ -1,301 +0,0 @@ -/** - * Copyright (C) 2012 KO GmbH - - * @licstart - * The JavaScript code in this page is free software: you can redistribute it - * and/or modify it under the terms of the GNU Affero General Public License - * (GNU AGPL) as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. The code is distributed - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU AGPL for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this code. If not, see . - * - * As additional permission under GNU AGPL version 3 section 7, you - * may distribute non-source (e.g., minimized or compacted) forms of - * that code without the copy of the GNU GPL normally required by - * section 4, provided you include this license notice and a URL - * through which recipients can access the Corresponding Source. - * - * As a special exception to the AGPL, any HTML file which merely makes function - * calls to this code, and for that purpose includes it by reference shall be - * deemed a separate work for copyright law purposes. In addition, the copyright - * holders of this code give you permission to combine this code with free - * software libraries that are released under the GNU LGPL. You may copy and - * distribute such a system following the terms of the GNU AGPL for this code - * and the LGPL for the libraries. If you modify this code, you may extend this - * exception to your version of the code, but you are not obligated to do so. - * If you do not wish to do so, delete this exception statement from your - * version. - * - * This license applies to this entire compilation. - * @licend - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ - */ -/*global Ext, console, app, window, LocalFileSystem, JSON, FileReader, runtime*/ -Ext.define("WebODFApp.model.FileSystemProxy", (function () { - "use strict"; - var self = this, - scanner; - function Scanner(proxy) { - var todo = [], - done = false, - fileSystems, - files, - cachedList = [], - dirs = {}, - lastUpdate = 0, - lastUpdateTime = new Date(); - - function getFileId(fullPath) { - var i; - if (!files) { - return -1; - } - for (i = 0; i < files.length; i += 1) { - if (files[i].get('fullPath') === fullPath) { - return i; - } - } - return -1; - } - this.getFileId = getFileId; - function addFileEntry(entry, callback) { - function fail() { - callback(-1); - } - var id = getFileId(entry.fullPath); - if (id !== -1) { - return callback(id); - } - entry.file(function (file) { - var id = files.length; - files.push(Ext.create('WebODFApp.model.FileSystem', { - id: id, - fileName: entry.name, - fullPath: entry.fullPath, - size: file.size - })); - callback(id); - }, fail); - } - function parseCachedFileList(pos, callback) { - if (pos === cachedList.length) { - return callback(); - } - window.resolveLocalFileSystemURI(cachedList[pos], function (entry) { - addFileEntry(entry, function () { - parseCachedFileList(pos + 1, callback); - }); - }, function () { - parseCachedFileList(pos + 1, callback); - }); - } - function readCachedFileList(callback) { - window.resolveLocalFileSystemURI("cachedODFList.json", - function (fileentry) { - var reader = new FileReader(); - reader.onloadend = function (evt) { - cachedList = []; - try { - cachedList = JSON.parse(evt.target.result); - } catch (e) { - alert(e); - } - parseCachedFileList(0, callback); - }; - reader.readAsText(fileentry); - }, function () { - callback(); - }); - } - function writeCachedFileList() { - var i, - l = files.length; - cachedList.length = files.length; - for (i = 0; i < l; i += 1) { - cachedList[i] = files[i].get('fullPath'); - } - window.resolveLocalFileSystemURI("cachedODFList.json", - function (fileentry) { - fileentry.createWriter(function (writer) { - writer.write(JSON.stringify(cachedList)); - }, function (e) { - runtime.log(JSON.stringify(e)); - }); - }, function (e) { - runtime.log(JSON.stringify(e)); - }); - } - function filter(name) { - var suffix = name.substr(name.length - 4); - return suffix === ".odt" || suffix === ".odp" || suffix === ".ods"; - } - function errorCallback(err) { - console.log("FILE READ ERROR " + err + " " + todo.length); - done = true; - } - function load(entry, callback) { - function fail() { - callback(-1); - } - if (entry.isDirectory) { - entry.createReader().readEntries(function (entries) { - var i = 0; - for (i = 0; i < entries.length; i += 1) { - entry = entries[i]; - if (entry.isDirectory - && !dirs.hasOwnProperty(entry.fullPath)) { - todo.push(entry); - dirs[entry.fullPath] = entry; - } else if (filter(entry.name)) { - todo.push(entry); - } - } - callback(-1); - }, fail); - } else if (entry.isFile) { - addFileEntry(entry, callback); - } else { - fail(); - } - } - function addFileSystem(fileSystem) { - var dir = fileSystems[fileSystem.name] = {}; - todo.push(fileSystem.root); - function callback() { - var now = new Date(), - // a limit is needed, otherwise the # of files too large to - // be handled by sencha touch - done = todo.length === 0 || files.length > 2000, - store = Ext.getStore('FileStore'); - if (done || lastUpdate === 0 || (files.length - lastUpdate > 0 - && now - lastUpdateTime > files.length * 100)) { - store.load(); - lastUpdate = files.length; - lastUpdateTime = now; - } - if (done) { - writeCachedFileList(); - } else { - load(todo.shift(), callback); - } - } - load(todo.shift(), callback); - } - function addFileSystems() { - window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, - addFileSystem, errorCallback); - window.requestFileSystem(LocalFileSystem.TEMPORARY, 0, - addFileSystem, function () {}); - } - this.scan = function () { - if (fileSystems !== undefined) { - return; - } - fileSystems = {}; - readCachedFileList(function () { - addFileSystems(); - }); - }; - this.load = load; - this.files = files = []; - } - function finishOperation(proxy, operation, callback, scope) { - if (operation) { - var i = 0, - recs = operation.getRecords(), - len = recs.length; - - for (i; i < len; i += 1) { - recs[i].commit(); - } - operation.setSuccessful(); - - Ext.callback(callback, scope || proxy, [operation]); - } - } - return { - extend: "Ext.data.proxy.Proxy", - xtype: 'filesystemproxy', - constructor: function (config) { - this.initConfig(config); - scanner = new Scanner(this); - this.startScanningDirectories = function () { - scanner.scan(); - }; - this.getRecord = function (url, callback) { - var id = scanner.getFileId(url); - if (id !== -1) { - return callback(scanner.files[id]); - } - window.resolveLocalFileSystemURI(url, - function (fileentry) { - scanner.load(fileentry, function (id) { - Ext.getStore('FileStore').load(); - callback(scanner.files[id]); - }); - }, - function (evt) { - callback(null); - }); - }; - }, - - create: function (operation, callback, scope) { - finishOperation(this, operation, callback, scope); - }, - - read: function (operation, callback, scope) { - var me = this, - records = scanner.files; - if (!records) { - return; - } - // return model instances in a resultset - operation.setResultSet(new Ext.data.ResultSet({ - //total: records.length, - count: records.length, - records: records, - success: true - })); - - // announce success - operation.setSuccessful(); - operation.setCompleted(); - - // finish with callback - Ext.callback(callback, scope || me, [operation]); - }, - - update: function (operation, callback, scope) { - finishOperation(this, operation, callback, scope); - }, - - destroy: function (operation, callback, scope) { - finishOperation(this, operation, callback, scope); - } - }; -}())); - -Ext.define("WebODFApp.model.FileSystem", { - extend: 'Ext.data.Model', - config: { - idProperty: 'id', - fields: [ { - name: "id", - type: "auto" - }, { - name: "fullPath", - type: "string" - }, { - name: "fileName", - type: "string" - }, { - name: "size", - type: "int" - } ] - } -}); diff --git a/programs/touchui/app/store/FileStore.js b/programs/touchui/app/store/FileStore.js deleted file mode 100644 index 81b9e3f2b..000000000 --- a/programs/touchui/app/store/FileStore.js +++ /dev/null @@ -1,66 +0,0 @@ -/** - * Copyright (C) 2012 KO GmbH - - * @licstart - * The JavaScript code in this page is free software: you can redistribute it - * and/or modify it under the terms of the GNU Affero General Public License - * (GNU AGPL) as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. The code is distributed - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU AGPL for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this code. If not, see . - * - * As additional permission under GNU AGPL version 3 section 7, you - * may distribute non-source (e.g., minimized or compacted) forms of - * that code without the copy of the GNU GPL normally required by - * section 4, provided you include this license notice and a URL - * through which recipients can access the Corresponding Source. - * - * As a special exception to the AGPL, any HTML file which merely makes function - * calls to this code, and for that purpose includes it by reference shall be - * deemed a separate work for copyright law purposes. In addition, the copyright - * holders of this code give you permission to combine this code with free - * software libraries that are released under the GNU LGPL. You may copy and - * distribute such a system following the terms of the GNU AGPL for this code - * and the LGPL for the libraries. If you modify this code, you may extend this - * exception to your version of the code, but you are not obligated to do so. - * If you do not wish to do so, delete this exception statement from your - * version. - * - * This license applies to this entire compilation. - * @licend - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ - */ -/*global Ext*/ -Ext.define('WebODFApp.store.FileStore', { - extend: 'Ext.data.Store', - config: { - storeId: 'FileStore', - model: 'WebODFApp.model.FileSystem', - autoLoad: true, - grouper: { - groupFn: function (record) { - "use strict"; - return record.get('fileName')[0].toUpperCase(); - } - }, - proxy: { - xtype: 'filesystemproxy' - }, - sorters: function (a, b) { - "use strict"; - a = a.get('fileName').toUpperCase(); - b = b.get('fileName').toUpperCase(); - if (a > b) { - return 1; - } - if (a < b) { - return -1; - } - return 0; - } - } -}); diff --git a/programs/touchui/app/views/FileDetail.js b/programs/touchui/app/views/FileDetail.js deleted file mode 100644 index ed4824bca..000000000 --- a/programs/touchui/app/views/FileDetail.js +++ /dev/null @@ -1,157 +0,0 @@ -/** - * Copyright (C) 2012 KO GmbH - - * @licstart - * The JavaScript code in this page is free software: you can redistribute it - * and/or modify it under the terms of the GNU Affero General Public License - * (GNU AGPL) as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. The code is distributed - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU AGPL for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this code. If not, see . - * - * As additional permission under GNU AGPL version 3 section 7, you - * may distribute non-source (e.g., minimized or compacted) forms of - * that code without the copy of the GNU GPL normally required by - * section 4, provided you include this license notice and a URL - * through which recipients can access the Corresponding Source. - * - * As a special exception to the AGPL, any HTML file which merely makes function - * calls to this code, and for that purpose includes it by reference shall be - * deemed a separate work for copyright law purposes. In addition, the copyright - * holders of this code give you permission to combine this code with free - * software libraries that are released under the GNU LGPL. You may copy and - * distribute such a system following the terms of the GNU AGPL for this code - * and the LGPL for the libraries. If you modify this code, you may extend this - * exception to your version of the code, but you are not obligated to do so. - * If you do not wish to do so, delete this exception statement from your - * version. - * - * This license applies to this entire compilation. - * @licend - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ - */ -/*global Ext, app, runtime, xmldom, odf*/ -runtime.loadClass("xmldom.XPath"); -runtime.loadClass("odf.Namespaces"); -Ext.define('WebODFApp.view.FileDetail', (function () { - "use strict"; - var panel, - xpath = xmldom.XPath, - fileDetail, - title, - image, - list, - emptyImageUrl = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAAXNSR0IArs4c6QAAAAtJREFUCB1jYGAAAAADAAFPSAqvAAAAAElFTkSuQmCC"; - function getTitle(body) { - var ps, - title; - ps = xpath.getODFElementsWithXPath(body, - ".//text:h", odf.Namespaces.resolvePrefix); - title = ""; - if (ps && ps.length) { - title = ps[0].nodeValue; - } else { - ps = xpath.getODFElementsWithXPath(body, - ".//text:p", odf.Namespaces.resolvePrefix); - if (ps && ps.length) { - title = ps[0].nodeValue; - } - } - return title; - } - function metaToJSON(body, meta) { - var json = [], - title = body && getTitle(body), - e = meta && meta.firstChild, - name; - if (title) { - json.push({name: "title", value: title}); - } - while (e) { - if (e.nodeType === 1 && e.textContent) { - if (e.localName === "user-defined") { - name = e.getAttributeNS( - "urn:oasis:names:tc:opendocument:xmlns:meta:1.0", - "name" - ); - } else { - name = e.localName; - } - json.push({ - name: name, - value: e.textContent - }); - } - e = e.nextSibling; - } - return json; - } - return { - extend: 'Ext.Panel', - xtype: 'filedetail', - config: { - title: 'File details', - layout: 'vbox', - items: [{ - id: "title", - dock: 'top', - xtype: 'toolbar' - }, { - id: 'details', - xtype: 'container', - layout: 'hbox', - flex: 1, - items: [{ - id: 'thumbnail', - xtype: 'image', - width: 256, - maxWidth: "50%" - }, { - id: 'metalist', - xtype: 'list', - store: { - fields: ["name", "value"], - data: [] - }, - itemTpl: "{name}: {value}", - flex: 1 - }] - }], - listeners: { - initialize: function () { - fileDetail = this.query("#details")[0]; - title = this.query("#title")[0]; - image = fileDetail.query('#thumbnail')[0]; - list = fileDetail.query('#metalist')[0]; - } - } - }, - updateRecord: function (record) { - if (record) { - fileDetail.setMasked({ - xtype: 'loadmask', - message: 'Loading...' - }); - title.setTitle(record.get('fileName')); - } - }, - canvasListener: function (odfcanvas) { - var view = this, - odfcontainer = odfcanvas.odfContainer(), - part = odfcontainer.getPart("Thumbnails/thumbnail.png"), - metajson = []; - metajson = metaToJSON(odfcontainer.rootElement.body, - odfcontainer.rootElement.meta); - part.onstatereadychange = function (part) { - image.setSrc(part.url || emptyImageUrl); - }; - part.load(); - list.getStore().setData(metajson); - fileDetail.unmask(); - } - }; -}())); diff --git a/programs/touchui/app/views/FilesList.js b/programs/touchui/app/views/FilesList.js deleted file mode 100644 index 05d3f1df9..000000000 --- a/programs/touchui/app/views/FilesList.js +++ /dev/null @@ -1,83 +0,0 @@ -/** - * Copyright (C) 2012 KO GmbH - - * @licstart - * The JavaScript code in this page is free software: you can redistribute it - * and/or modify it under the terms of the GNU Affero General Public License - * (GNU AGPL) as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. The code is distributed - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU AGPL for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this code. If not, see . - * - * As additional permission under GNU AGPL version 3 section 7, you - * may distribute non-source (e.g., minimized or compacted) forms of - * that code without the copy of the GNU GPL normally required by - * section 4, provided you include this license notice and a URL - * through which recipients can access the Corresponding Source. - * - * As a special exception to the AGPL, any HTML file which merely makes function - * calls to this code, and for that purpose includes it by reference shall be - * deemed a separate work for copyright law purposes. In addition, the copyright - * holders of this code give you permission to combine this code with free - * software libraries that are released under the GNU LGPL. You may copy and - * distribute such a system following the terms of the GNU AGPL for this code - * and the LGPL for the libraries. If you modify this code, you may extend this - * exception to your version of the code, but you are not obligated to do so. - * If you do not wish to do so, delete this exception statement from your - * version. - * - * This license applies to this entire compilation. - * @licend - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ - */ -/*global Ext, app, filestore */ -Ext.define("WebODFApp.view.FilesList", { - extend: "Ext.Panel", - xtype: 'fileslist', - config: { - layout: 'fit', - items: [{ - xtype: 'list', - store: 'FileStore', -/* - store: { - fields: ['fileName', 'fullPath'], - grouper: { - groupFn: function (record) { - "use strict"; - return record.get('fileName')[0].toUpperCase(); - } - }, - data: [ - {fileName: 'Cowper', fullPath: '-'}, - {fileName: 'Everett', fullPath: '-'}, - {fileName: 'University', fullPath: '-'}, - {fileName: 'Forest', fullPath: '-'} - ] - }, -*/ -/* - listeners: { - 'itemtap': function (view, number, item) { - "use strict"; - var record = view.getStore().getAt(number); - if (record) { - Ext.app.dispatch({ - controller: 'Files', //app.controllers.files, - action: 'show', - id: record.getId() - }); - } - } - }, -*/ - itemTpl: '{fileName}
{fullPath}', - grouped: true, - indexBar: true - }] - } -}); diff --git a/programs/touchui/app/views/OdfView.js b/programs/touchui/app/views/OdfView.js deleted file mode 100644 index 3a232b7d1..000000000 --- a/programs/touchui/app/views/OdfView.js +++ /dev/null @@ -1,203 +0,0 @@ -/** - * Copyright (C) 2012 KO GmbH - - * @licstart - * The JavaScript code in this page is free software: you can redistribute it - * and/or modify it under the terms of the GNU Affero General Public License - * (GNU AGPL) as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. The code is distributed - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU AGPL for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this code. If not, see . - * - * As additional permission under GNU AGPL version 3 section 7, you - * may distribute non-source (e.g., minimized or compacted) forms of - * that code without the copy of the GNU GPL normally required by - * section 4, provided you include this license notice and a URL - * through which recipients can access the Corresponding Source. - * - * As a special exception to the AGPL, any HTML file which merely makes function - * calls to this code, and for that purpose includes it by reference shall be - * deemed a separate work for copyright law purposes. In addition, the copyright - * holders of this code give you permission to combine this code with free - * software libraries that are released under the GNU LGPL. You may copy and - * distribute such a system following the terms of the GNU AGPL for this code - * and the LGPL for the libraries. If you modify this code, you may extend this - * exception to your version of the code, but you are not obligated to do so. - * If you do not wish to do so, delete this exception statement from your - * version. - * - * This license applies to this entire compilation. - * @licend - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ - */ -/*global Ext, runtime, core, odf, window, FileReader, PhoneGap, gui*/ -runtime.loadClass('odf.OdfCanvas'); - -Ext.define('WebODFApp.view.OdfView', (function () { - "use strict"; - var currentPath, - overridePath, - overridePathPrefix = "odf:", - data, - globalreadfunction, - globalfilesizefunction, - odfcanvas, - zoom = 1, - dom, - canvasListeners = [], - view; - function signalCanvasChange() { - var i; - for (i = 0; i < canvasListeners.length; i += 1) { - canvasListeners[i](odfcanvas); - } - } - function initCanvas() { - if (globalreadfunction === undefined) { - // overload the global read function with one that only reads - // the data from this canvas - globalreadfunction = runtime.readFile; - runtime.readFile = function (path, encoding, callback) { - if (path !== overridePath) { - globalreadfunction.apply(runtime, - [path, encoding, callback]); - } else { - callback(null, data); - } - }; - dom = Ext.getCmp('webodf').element.dom; - odfcanvas = new odf.OdfCanvas(dom); - odfcanvas.addListener("statereadychange", signalCanvasChange); - } - } - function load(path) { - if (path === currentPath) { - return; - } - currentPath = path; - overridePath = overridePathPrefix + path; - data = null; - // the url of a file can start with file:// or not depending on the - // operating system, notably this is different between iOS and android - var filepath = path; - if (filepath.substr(0, 7) !== "file://") { - filepath = "file://" + filepath; - } - window.resolveLocalFileSystemURI(filepath, function (file) { - var reader = new FileReader(); - // so far phonegap is very limited, ideally it would implement - // readAsArrayBuffer and slice() on the File object - // right now, it has a dummy function, hence breaking simple - // detection of which features are implemented - if (reader.readAsArrayBuffer - && (typeof PhoneGap === "undefined")) { - reader.onloadend = function (evt) { - data = evt.target.result; - odfcanvas.load(overridePath); - }; - reader.readAsArrayBuffer(file); - } else { - reader.onloadend = function (evt) { - var b = new core.Base64(); - data = evt.target.result; - data = data.substr(data.indexOf(",") + 1); - data = b.convertBase64ToUTF8Array(data); - odfcanvas.load(overridePath); - }; - reader.readAsDataURL(file); - } - }, function () { - runtime.log("COULD NOT RESOLVE " + path); - }); - } - function tapHandler(button) { - var id = button.getId(), - dom = Ext.getCmp('odfcontainer').element.dom, - width = dom.offsetWidth, - height = dom.offsetHeight; - if (id === 'zoomin') { - odfcanvas.setZoomLevel(odfcanvas.getZoomLevel() * 1.25); - } else if (id === 'zoomout') { - odfcanvas.setZoomLevel(odfcanvas.getZoomLevel() * 0.8); - } else if (id === 'fit-best') { - odfcanvas.fitToContainingElement(width, height); - } else if (id === 'fit-width') { - odfcanvas.fitToWidth(width); - } else if (id === 'fit-height') { - odfcanvas.fitToHeight(height); - } else if (id === 'next') { - odfcanvas.showNextPage(); - } else if (id === 'previous') { - odfcanvas.showPreviousPage(); - } - } - return { - extend: 'Ext.Container', - xtype: 'odfview', - id: 'odfcontainer', - config: { - scrollable: 'both', - items: [{ - id: 'webodf' - }, { - xtype : 'toolbar', - docked: 'bottom', - scrollable: false, - defaults: { - iconMask: false, - ui : 'plain', - handler: tapHandler - }, - items: [ - { id: 'previous', icon: 'go-previous.png'}, - { id: 'next', icon: 'go-next.png' }, - { id: 'zoomin', icon: 'ZoomIn.png'}, - { id: 'zoomout', icon: 'ZoomOut.png' }, - { id: 'fit-best', icon: 'zoom-fit-best.png' }, - { id: 'fit-height', icon: 'zoom-fit-height.png' }, - { id: 'fit-width', icon: 'zoom-fit-width.png' } - ], - layout: { - pack : 'center', - align: 'center' - } - }], - listeners: { - painted: function () { - // make sure the viewport is the right size - odfcanvas.setZoomLevel(odfcanvas.getZoomLevel()); - } - } - }, - updateRecord: function (record) { - view = this; - initCanvas(); - load(record.get('fullPath')); - }, - addCanvasListener: function (listener) { - canvasListeners.push(listener); - }, - hideCanvas: function () { - if (dom) { - dom.style.display = "none"; - } - if (view) { - view.setMasked({ - xtype: 'loadmask', - message: 'Loading...' - }); - } - }, - showCanvas: function () { - if (view) { - view.unmask(); - } - dom.style.display = "inline-block"; - odfcanvas.setZoomLevel(odfcanvas.getZoomLevel()); - } - }; -}())); diff --git a/programs/touchui/app/views/Viewport.js b/programs/touchui/app/views/Viewport.js deleted file mode 100644 index a3efbb909..000000000 --- a/programs/touchui/app/views/Viewport.js +++ /dev/null @@ -1,90 +0,0 @@ -/** - * Copyright (C) 2012 KO GmbH - - * @licstart - * The JavaScript code in this page is free software: you can redistribute it - * and/or modify it under the terms of the GNU Affero General Public License - * (GNU AGPL) as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. The code is distributed - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU AGPL for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this code. If not, see . - * - * As additional permission under GNU AGPL version 3 section 7, you - * may distribute non-source (e.g., minimized or compacted) forms of - * that code without the copy of the GNU GPL normally required by - * section 4, provided you include this license notice and a URL - * through which recipients can access the Corresponding Source. - * - * As a special exception to the AGPL, any HTML file which merely makes function - * calls to this code, and for that purpose includes it by reference shall be - * deemed a separate work for copyright law purposes. In addition, the copyright - * holders of this code give you permission to combine this code with free - * software libraries that are released under the GNU LGPL. You may copy and - * distribute such a system following the terms of the GNU AGPL for this code - * and the LGPL for the libraries. If you modify this code, you may extend this - * exception to your version of the code, but you are not obligated to do so. - * If you do not wish to do so, delete this exception statement from your - * version. - * - * This license applies to this entire compilation. - * @licend - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ - */ -/*global Ext, app*/ -Ext.define("WebODFApp.view.Viewport", { - extend: "Ext.navigation.View", - xtype: "mainview", - requires: [ - "WebODFApp.view.FilesList", - "WebODFApp.view.FileDetail" - ], - config: { - fullscreen: true, - autoDestroy: false, - navigationBar: { - items: [{ - xtype: 'button', - id: 'openButton', - text: 'Open', - align: 'right', - hidden: true - }] - }, - items: [ - { title: 'Files', xtype: 'fileslist' } - ] - } -/* - initComponent: function () { - - //put instances of cards into app.views namespace - Ext.apply(app.views, { - filesList: new app.views.FilesList(), - fileDetail: new app.views.FileDetail(), - odfView: new app.views.OdfView() - }); - //put instances of cards into viewport - Ext.apply(this, { - items: [ - app.views.filesList, - app.views.fileDetail, - app.views.odfView - ] - }); - app.views.Viewport.superclass.initComponent.apply(this, arguments); - }, - listeners: { - afterlayout: function () { - if (app.stores.filesystem.initialUrl) { - app.openUrl(app.stores.filesystem.initialUrl); - app.stores.filesystem.initialUrl = undefined; - } - } - } - }; -*/ -}); diff --git a/programs/touchui/go-next.png b/programs/touchui/go-next.png deleted file mode 100644 index c4da8a9a3..000000000 Binary files a/programs/touchui/go-next.png and /dev/null differ diff --git a/programs/touchui/go-previous.png b/programs/touchui/go-previous.png deleted file mode 100644 index 68ed8a13e..000000000 Binary files a/programs/touchui/go-previous.png and /dev/null differ diff --git a/programs/touchui/index.html b/programs/touchui/index.html deleted file mode 100644 index b642a57b7..000000000 --- a/programs/touchui/index.html +++ /dev/null @@ -1,21 +0,0 @@ - - - - - WebODF - - - - - - - - - - - - - - - - diff --git a/programs/touchui/scripts.js b/programs/touchui/scripts.js deleted file mode 100644 index c1fb0f04f..000000000 --- a/programs/touchui/scripts.js +++ /dev/null @@ -1,166 +0,0 @@ -/** - * Copyright (C) 2012 KO GmbH - - * @licstart - * The JavaScript code in this page is free software: you can redistribute it - * and/or modify it under the terms of the GNU Affero General Public License - * (GNU AGPL) as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. The code is distributed - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU AGPL for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this code. If not, see . - * - * As additional permission under GNU AGPL version 3 section 7, you - * may distribute non-source (e.g., minimized or compacted) forms of - * that code without the copy of the GNU GPL normally required by - * section 4, provided you include this license notice and a URL - * through which recipients can access the Corresponding Source. - * - * As a special exception to the AGPL, any HTML file which merely makes function - * calls to this code, and for that purpose includes it by reference shall be - * deemed a separate work for copyright law purposes. In addition, the copyright - * holders of this code give you permission to combine this code with free - * software libraries that are released under the GNU LGPL. You may copy and - * distribute such a system following the terms of the GNU AGPL for this code - * and the LGPL for the libraries. If you modify this code, you may extend this - * exception to your version of the code, but you are not obligated to do so. - * If you do not wish to do so, delete this exception statement from your - * version. - * - * This license applies to this entire compilation. - * @licend - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ - */ -/*global alert, app, window, runtime*/ -var LocalFileSystem = { - PERSISTENT: 0, - TEMPORARY: 1 -}; -function FileWriter(fullPath) { - "use strict"; - this.write = function (data) { - data = runtime.byteArrayFromString(data, "utf8"); - runtime.writeFile(fullPath, data, function () {}); - }; -} -function FileEntry(name, fullPath) { - "use strict"; - this.isFile = true; - this.isDirectory = false; - this.name = name; - this.fullPath = fullPath; - this.file = function (onsuccess, onerror) { - function File(fullPath) { - this.name = name; - this.fullPath = fullPath; - this.type = ""; - this.size = -1; - this.lastModifiedDate = -1; - } - var file = new File(fullPath); - try { - onsuccess(file); - } catch (e) { - alert("Error on determining file properties: " + e); - onerror(e); - } - }; - this.createWriter = function (onsuccess, onerror) { - onsuccess(new FileWriter(fullPath)); - }; -} -function FileReader() { - "use strict"; - var fr = this; - this.readAsArrayBuffer = function (file) { - var path = file.fullPath; - if (path.substr(0, 7) === "file://") { - path = path.substr(7); - } - runtime.readFile(path, 'binary', function (error, data) { - fr.onloadend({target: {result: data}}); - }); - }; - this.readAsText = function (file) { - var path = file.fullPath; - if (path.substr(0, 7) === "file://") { - path = path.substr(7); - } - runtime.readFile(path, 'utf8', function (error, data) { - fr.onloadend({target: {result: data}}); - }); - }; -} -var DirectoryReader; -function DirectoryEntry(name, fullPath) { - "use strict"; - this.isFile = false; - this.isDirectory = true; - this.name = name; - this.fullPath = fullPath; - this.createReader = function () { - var reader = new DirectoryReader(fullPath); - return reader; - }; -} -function DirectoryReader(fullPath) { - "use strict"; - this.readEntries = function (onsuccess, onerror) { - window.setTimeout(function () { - var entries = []; - entries[entries.length] = new FileEntry("welcome.odt", - "welcome.odt"); - entries[entries.length] = new FileEntry("Traktatenblad.odt", - "Traktatenblad.odt"); - entries[entries.length] = new FileEntry("DanskTest01.odt", - "DanskTest01.odt"); - entries[entries.length] = new FileEntry("plugfest-gouda.odp", - "plugfest-gouda.odp"); - entries[entries.length] = new FileEntry("OpenDocument-v1.2.odt", - "OpenDocument-v1.2.odt"); - entries[entries.length] = new FileEntry("OpenDocument-v1.2-part1.odt", - "OpenDocument-v1.2-part1.odt"); - entries[entries.length] = new FileEntry("OpenDocument-v1.2-part2.odt", - "OpenDocument-v1.2-part2.odt"); - try { - onsuccess(entries); - } catch (e) { - onerror(e); - } - }, 1); - }; -} -window.resolveLocalFileSystemURI = function (path, onsuccess, onerror) { - "use strict"; - var p = path.lastIndexOf("/"), - name = (p === -1) ? path : path.substr(p + 1); - onsuccess(new FileEntry(name, path)); -}; -window.requestFileSystem = function (filesystem, id, onsuccess, onerror) { - "use strict"; - var dirs = [], shared, subfolder, path; - try { - if (filesystem === LocalFileSystem.PERSISTENT) { - path = ""; - onsuccess({ - name: "root", - root: new DirectoryEntry("root", path) - }); - } else { - onerror("not defined"); - } - } catch (e) { - onerror(e); - } -}; -var device = {}; -/* - * override launch function to quickly open a file for testing -onApplicationLaunch = function (app) { - "use strict"; - app.openUrl("plugfest-gouda.odp"); -}; -*/ diff --git a/programs/touchui/sencha-touch.css b/programs/touchui/sencha-touch.css deleted file mode 100644 index 49243ee2b..000000000 --- a/programs/touchui/sencha-touch.css +++ /dev/null @@ -1,21 +0,0 @@ -/* -This file is part of Sencha Touch 2.0 - -Copyright (c) 2011-2012 Sencha Inc - -Contact: http://www.sencha.com/contact - -GNU General Public License Usage -This file may be used under the terms of the GNU General Public License version 3.0 as -published by the Free Software Foundation and appearing in the file LICENSE included in the -packaging of this file. - -Please review the following information to ensure the GNU General Public License version 3.0 -requirements will be met: http://www.gnu.org/copyleft/gpl.html. - -If you are unsure which license is appropriate for your use, please contact the sales department -at http://www.sencha.com/contact. - -Build date: 2012-06-04 15:34:28 (d81f71da2d56f5f71419dc892fbc85685098c6b7) -*/ -body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,code,form,fieldset,legend,input,textarea,p,blockquote,th,td{margin:0;padding:0}table{border-collapse:collapse;border-spacing:0}fieldset,img{border:0}address,caption,cite,code,dfn,em,strong,th,var{font-style:normal;font-weight:normal}li{list-style:none}caption,th{text-align:left}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal}q:before,q:after{content:""}abbr,acronym{border:0;font-variant:normal}sup{vertical-align:text-top}sub{vertical-align:text-bottom}input,textarea,select{font-family:inherit;font-size:inherit;font-weight:inherit}*:focus{outline:none}html,body{font-family:"Helvetica Neue", HelveticaNeue, "Helvetica-Neue", Helvetica, "BBAlpha Sans", sans-serif;font-weight:normal;position:relative;-webkit-text-size-adjust:none}body.x-desktop{overflow:hidden}*,*:after,*:before{-webkit-box-sizing:border-box;box-sizing:border-box;-webkit-tap-highlight-color:rgba(0, 0, 0, 0);-webkit-user-select:none;-webkit-touch-callout:none;-webkit-user-drag:none}.x-ios.x-tablet .x-landscape *{-webkit-text-stroke:1px transparent}body{font-size:104%}body.x-android.x-phone{font-size:116%}body.x-android.x-phone.x-silk{font-size:130%}body.x-ios.x-phone{font-size:114%}body.x-desktop{font-size:114%}input,textarea{-webkit-user-select:text}.x-hidden-visibility{visibility:hidden !important}.x-hidden-display,.x-field-hidden{display:none !important}.x-hidden-offsets{position:absolute !important;left:-10000em;top:-10000em;visibility:hidden}.x-fullscreen{position:absolute !important}.x-desktop .x-body-stretcher{margin-bottom:0px}.x-mask{position:absolute;top:0;left:0;bottom:0;right:0;height:100%;z-index:10;display:-webkit-box;display:box;-webkit-box-align:center;box-align:center;-webkit-box-pack:center;box-pack:center;background:rgba(0, 0, 0, 0.3) center center no-repeat}.x-mask.x-mask-gray{background-color:rgba(0, 0, 0, 0.5)}.x-mask.x-mask-transparent{background-color:transparent}.x-mask .x-mask-inner{background:rgba(0, 0, 0, 0.25);color:#fff;text-align:center;padding:.4em;font-size:.95em;font-weight:bold;-webkit-border-radius:0.5em;border-radius:0.5em}.x-mask .x-loading-spinner-outer{display:-webkit-box;display:box;-webkit-box-orient:vertical;box-orient:vertical;-webkit-box-align:center;box-align:center;-webkit-box-pack:center;box-pack:center;width:100%;min-width:8.5em;height:8.5em}.x-mask.x-indicator-hidden .x-loading-spinner-outer{display:none}.x-mask .x-mask-message{text-shadow:rgba(0, 0, 0, 0.5) 0 -0.08em 0;-webkit-box-flex:0 !important;max-width:13em;min-width:8.5em}.x-draggable{z-index:1}.x-dragging{opacity:0.7}.x-panel-list{background-color:#eaf6fe}.x-html{-webkit-user-select:auto;-webkit-touch-callout:inherit;line-height:1.5;color:#333;font-size:.8em;padding:1.2em}.x-html body{line-height:1.5;font-family:"Helvetica Neue", Arial, Helvetica, sans-serif;color:#333333;font-size:75%}.x-html h1,.x-html h2,.x-html h3,.x-html h4,.x-html h5,.x-html h6{font-weight:normal;color:#222222}.x-html h1 img,.x-html h2 img,.x-html h3 img,.x-html h4 img,.x-html h5 img,.x-html h6 img{margin:0}.x-html h1{font-size:3em;line-height:1;margin-bottom:0.50em}.x-html h2{font-size:2em;margin-bottom:0.75em}.x-html h3{font-size:1.5em;line-height:1;margin-bottom:1.00em}.x-html h4{font-size:1.2em;line-height:1.25;margin-bottom:1.25em}.x-html h5{font-size:1em;font-weight:bold;margin-bottom:1.50em}.x-html h6{font-size:1em;font-weight:bold}.x-html p{margin:0 0 1.5em}.x-html p .left{display:inline;float:left;margin:1.5em 1.5em 1.5em 0;padding:0}.x-html p .right{display:inline;float:right;margin:1.5em 0 1.5em 1.5em;padding:0}.x-html a{text-decoration:underline;color:#0066cc}.x-html a:visited{color:#004c99}.x-html a:focus{color:#0099ff}.x-html a:hover{color:#0099ff}.x-html a:active{color:#bf00ff}.x-html blockquote{margin:1.5em;color:#666666;font-style:italic}.x-html strong,.x-html dfn{font-weight:bold}.x-html em,.x-html dfn{font-style:italic}.x-html sup,.x-html sub{line-height:0}.x-html abbr,.x-html acronym{border-bottom:1px dotted #666666}.x-html address{margin:0 0 1.5em;font-style:italic}.x-html del{color:#666666}.x-html pre{margin:1.5em 0;white-space:pre}.x-html pre,.x-html code,.x-html tt{font:1em "andale mono", "lucida console", monospace;line-height:1.5}.x-html li ul,.x-html li ol{margin:0}.x-html ul,.x-html ol{margin:0 1.5em 1.5em 0;padding-left:1.5em}.x-html ul{list-style-type:disc}.x-html ol{list-style-type:decimal}.x-html dl{margin:0 0 1.5em 0}.x-html dl dt{font-weight:bold}.x-html dd{margin-left:1.5em}.x-html table{margin-bottom:1.4em;width:100%}.x-html th{font-weight:bold}.x-html thead th{background:#c3d9ff}.x-html th,.x-html td,.x-html caption{padding:4px 10px 4px 5px}.x-html table.striped tr:nth-child(even) td,.x-html table tr.even td{background:#e5ecf9}.x-html tfoot{font-style:italic}.x-html caption{background:#eeeeee}.x-html .quiet{color:#666666}.x-html .loud{color:#111111}.x-html ul li{list-style-type:circle}.x-html ol li{list-style-type:decimal}.x-video{background-color:#000}.x-sortable .x-dragging{opacity:1;z-index:5}.x-layout-card-item{background:#eeeeee}.x-map{background-color:#edeae2}.x-map *{-webkit-box-sizing:content-box;box-sizing:content-box}.x-mask-map{background:transparent !important}.x-img{background-repeat:no-repeat}.x-video{height:100%;width:100%}.x-video > *{height:100%;width:100%;position:absolute}.x-video-ghost{-webkit-background-size:100% auto;background:black url() center center no-repeat}audio{width:100%}.x-panel,.x-msgbox,.x-panel-body{position:relative}.x-panel.x-floating,.x-msgbox.x-floating,.x-form.x-floating{padding:6px;-webkit-border-radius:0.3em;border-radius:0.3em;-webkit-box-shadow:rgba(0, 0, 0, 0.8) 0 0.2em 0.6em;background-image:none;background-color:#03111a}.x-panel.x-floating.x-floating-light,.x-msgbox.x-floating.x-floating-light,.x-form.x-floating.x-floating-light{background-image:none;background-color:#1985d0}.x-panel.x-floating > .x-panel-inner,.x-panel.x-floating .x-scroll-view,.x-panel.x-floating .x-body,.x-msgbox.x-floating > .x-panel-inner,.x-msgbox.x-floating .x-scroll-view,.x-msgbox.x-floating .x-body,.x-form.x-floating > .x-panel-inner,.x-form.x-floating .x-scroll-view,.x-form.x-floating .x-body{background-color:#fff;-webkit-border-radius:0.3em;border-radius:0.3em}.x-anchor{width:1.631em;height:0.7em;position:absolute;left:0;top:0;z-index:1;-webkit-mask:0 0 url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACMAAAAPCAYAAABut3YUAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAPZJREFUeNpi/PX7LwOFwAyIG6HseiA+Ra5BjBQ6xg+IVwAxJ5T/HYgjgHgTOYYxUeCQUiBeh+QQBih7HVSOLiHDDMSTgTiTgLrpQJwLxH9p5RhOaLT4EakeFF3RQPyF2o6RhkaBGYkheRmIPYH4KbXSjC4QnyTDIch6danhGCcgPgwNGXKBNNQMb0ocEwXE24GYn4FyADJjI76Ej88x7UC8FIjZGKgHQDlxGtRsZmISMMjy+dBQoSXYBC0gv+NyDD80xzgx0AeAqg4fIH6NHk0qQHyMjg6B1WvHYDkNFjIgwS1ALMowMOAjEAeBHINe2Q0U+AUQYACQ10C2QNhRogAAAABJRU5ErkJggg==') no-repeat;-webkit-mask-size:1.631em 0.7em;overflow:hidden;background-color:#03111a;-webkit-transform-origin:0% 0%}.x-anchor.x-anchor-top{margin-left:-0.816em;margin-top:-0.7em}.x-anchor.x-anchor-bottom{-webkit-transform:rotate(180deg);margin-left:0.816em;margin-top:0.6em}.x-anchor.x-anchor-left{-webkit-transform:rotate(270deg);margin-left:-0.7em;margin-top:-0.1em}.x-anchor.x-anchor-right{-webkit-transform:rotate(90deg);margin-left:0.7em;margin-top:0}.x-floating.x-panel-light:after{background-color:#1985d0}.x-button{-webkit-background-clip:padding;background-clip:padding-box;-webkit-border-radius:0.4em;border-radius:0.4em;display:-webkit-box;display:box;-webkit-box-align:center;box-align:center;min-height:1.8em;padding:.3em .6em;position:relative;overflow:hidden;-webkit-user-select:none}.x-button,.x-toolbar .x-button{border:1px solid #999999;border-top-color:#a6a6a6;color:black}.x-button.x-button-back:before,.x-button.x-button-forward:before,.x-toolbar .x-button.x-button-back:before,.x-toolbar .x-button.x-button-forward:before{background:#999999}.x-button,.x-button.x-button-back:after,.x-button.x-button-forward:after,.x-toolbar .x-button,.x-toolbar .x-button.x-button-back:after,.x-toolbar .x-button.x-button-forward:after{background-image:none;background-color:#ccc;background-image:-webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #f2f2f2), color-stop(3%, #d9d9d9), color-stop(100%, #bfbfbf));background-image:-webkit-linear-gradient(top, #f2f2f2,#d9d9d9 3%,#bfbfbf);background-image:linear-gradient(top, #f2f2f2,#d9d9d9 3%,#bfbfbf)}.x-button .x-button-icon.x-icon-mask,.x-toolbar .x-button .x-button-icon.x-icon-mask{background-image:none;background-color:black;background-image:-webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #262626), color-stop(3%, #0d0d0d), color-stop(100%, #000000));background-image:-webkit-linear-gradient(top, #262626,#0d0d0d 3%,#000000);background-image:linear-gradient(top, #262626,#0d0d0d 3%,#000000)}.x-button.x-button-pressing,.x-button.x-button-pressing:after,.x-button.x-button-pressed,.x-button.x-button-pressed:after,.x-button.x-button-active,.x-button.x-button-active:after,.x-toolbar .x-button.x-button-pressing,.x-toolbar .x-button.x-button-pressing:after,.x-toolbar .x-button.x-button-pressed,.x-toolbar .x-button.x-button-pressed:after,.x-toolbar .x-button.x-button-active,.x-toolbar .x-button.x-button-active:after{background-image:none;background-color:#c4c4c4;background-image:-webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #ababab), color-stop(10%, #b8b8b8), color-stop(65%, #c4c4c4), color-stop(100%, #c6c6c6));background-image:-webkit-linear-gradient(top, #ababab,#b8b8b8 10%,#c4c4c4 65%,#c6c6c6);background-image:linear-gradient(top, #ababab,#b8b8b8 10%,#c4c4c4 65%,#c6c6c6)}.x-button .x-button-icon{width:2.1em;height:2.1em;background-repeat:no-repeat;background-position:center;display:block}.x-button .x-button-icon.x-icon-mask{width:1.3em;height:1.3em;-webkit-mask-size:1.3em}.x-button.x-item-disabled .x-button-label,.x-button.x-item-disabled .x-hasbadge .x-badge,.x-hasbadge .x-button.x-item-disabled .x-badge,.x-button.x-item-disabled .x-button-icon{opacity:.5}.x-button-round,.x-button.x-button-action-round,.x-button.x-button-confirm-round,.x-button.x-button-decline-round{-webkit-border-radius:0.9em;border-radius:0.9em;padding:0.1em 0.9em}.x-iconalign-left,.x-icon-align-right{-webkit-box-orient:horizontal;box-orient:horizontal}.x-iconalign-top,.x-iconalign-bottom{-webkit-box-orient:vertical;box-orient:vertical}.x-iconalign-bottom,.x-iconalign-right{-webkit-box-direction:reverse;box-direction:reverse}.x-iconalign-center{-webkit-box-pack:center;box-pack:center}.x-iconalign-left .x-button-label,.x-iconalign-left .x-hasbadge .x-badge,.x-hasbadge .x-iconalign-left .x-badge{margin-left:0.3em}.x-iconalign-right .x-button-label,.x-iconalign-right .x-hasbadge .x-badge,.x-hasbadge .x-iconalign-right .x-badge{margin-right:0.3em}.x-iconalign-top .x-button-label,.x-iconalign-top .x-hasbadge .x-badge,.x-hasbadge .x-iconalign-top .x-badge{margin-top:0.3em}.x-iconalign-bottom .x-button-label,.x-iconalign-bottom .x-hasbadge .x-badge,.x-hasbadge .x-iconalign-bottom .x-badge{margin-bottom:0.3em}.x-button-label,.x-hasbadge .x-badge{-webkit-box-flex:1;box-flex:1;-webkit-box-align:center;box-align:center;white-space:nowrap;text-overflow:ellipsis;text-align:center;font-weight:bold;line-height:1.2em;display:block;overflow:hidden}.x-toolbar .x-button{margin:0 .2em;padding:.3em .6em}.x-toolbar .x-button .x-button-label,.x-toolbar .x-button .x-hasbadge .x-badge,.x-hasbadge .x-toolbar .x-button .x-badge{font-size:.7em}.x-button-small,.x-button.x-button-action-small,.x-button.x-button-confirm-small,.x-button.x-button-decline-small,.x-toolbar .x-button-small,.x-toolbar .x-button.x-button-action-small,.x-toolbar .x-button.x-button-confirm-small,.x-toolbar .x-button.x-button-decline-small{-webkit-border-radius:0.3em;border-radius:0.3em;padding:.2em .4em;min-height:0}.x-button-small .x-button-label,.x-button.x-button-action-small .x-button-label,.x-button.x-button-confirm-small .x-button-label,.x-button.x-button-decline-small .x-button-label,.x-button-small .x-hasbadge .x-badge,.x-hasbadge .x-button-small .x-badge,.x-button.x-button-action-small .x-hasbadge .x-badge,.x-hasbadge .x-button.x-button-action-small .x-badge,.x-button.x-button-confirm-small .x-hasbadge .x-badge,.x-hasbadge .x-button.x-button-confirm-small .x-badge,.x-button.x-button-decline-small .x-hasbadge .x-badge,.x-hasbadge .x-button.x-button-decline-small .x-badge,.x-toolbar .x-button-small .x-button-label,.x-toolbar .x-button.x-button-action-small .x-button-label,.x-toolbar .x-button.x-button-confirm-small .x-button-label,.x-toolbar .x-button.x-button-decline-small .x-button-label,.x-toolbar .x-button-small .x-hasbadge .x-badge,.x-hasbadge .x-toolbar .x-button-small .x-badge,.x-toolbar .x-button.x-button-action-small .x-hasbadge .x-badge,.x-hasbadge .x-toolbar .x-button.x-button-action-small .x-badge,.x-toolbar .x-button.x-button-confirm-small .x-hasbadge .x-badge,.x-hasbadge .x-toolbar .x-button.x-button-confirm-small .x-badge,.x-toolbar .x-button.x-button-decline-small .x-hasbadge .x-badge,.x-hasbadge .x-toolbar .x-button.x-button-decline-small .x-badge{font-size:.6em}.x-button-small .x-button-icon,.x-button.x-button-action-small .x-button-icon,.x-button.x-button-confirm-small .x-button-icon,.x-button.x-button-decline-small .x-button-icon,.x-toolbar .x-button-small .x-button-icon,.x-toolbar .x-button.x-button-action-small .x-button-icon,.x-toolbar .x-button.x-button-confirm-small .x-button-icon,.x-toolbar .x-button.x-button-decline-small .x-button-icon{width:.75em;height:.75em}.x-button-forward,.x-button-back{position:relative;overflow:visible;height:1.8em;z-index:1}.x-button-forward:before,.x-button-forward:after,.x-button-back:before,.x-button-back:after{content:"";position:absolute;width:0.773em;height:1.8em;top:-0.1em;left:auto;z-index:2;-webkit-mask:0.145em 0 url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAABGCAYAAADb7SQ4AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAiNJREFUeNrEWb9LQlEUvj5BcHoQvMnVKXD1D3CLwqBJbHJsazQaWoSCxgbHJiMIAiNok6AhCDdXVycnJ8EQOgeOYaG+d39998KH+HyP753zzjnfd325xfdSgVeV8B6BScuEV0IRSbxHeCMk/AVFXCA8ScQKSXxPqK0fQBBfE5r/D+Y8VzUT9jb94DPimqRYIYkrhGcpKhhxIqTxrpNcExdlQJTTTnRJnCc8ykhUSOIOoZ71ZFfEZ4S2zgUu+rguxZRHEnPbfKRVsOtUl0RtYpOLTYljIS2Z3nVk2DY9SbNCEt8RDm0rUpe4La1jvXSqmtum72raZI24KuNQIYl/nSGSOJb0Jq61M0pxhjwK9304hUjHGSKILzc5Q5drUzttdYY+I97pDH1FzG0zNFUb04gTG4kzJS5kdYauiZtZnaFr4ooKsCIVaDHxKAQxt1NBnGIVHfGCcEQYh3jGU8KBfMKLiyM+lgzAq/qT0ArVTg+Ei1B9fEPoovV4fcfQd2HedScX39GprwGTNjJn0maTELN6IuSzECLB6T5x2eM66jQgnIeSxa60GnS3uL56tr7b1Ai0JPVwYi6yho2U2lgfKym19VxjMRHzEGbvS9K+RBPzetGVUpf29lZHSl2/DMnLvwh1ZMQrKW3Ic4fvJOZS6ZMQW5hpmpT63DvtlFLfm7bBNruM2C2yXb7y3U6ZpRS5P/4jpUjihRTbCJ3q1eL3GMMfAQYAJmB6SBO619IAAAAASUVORK5CYII=') no-repeat;-webkit-mask-size:0.773em 1.8em;overflow:hidden}.x-button-back,.x-toolbar .x-button-back{margin-left:0.828em;padding-left:.4em}.x-button-back:before,.x-toolbar .x-button-back:before{left:-0.693em}.x-button-back:after,.x-toolbar .x-button-back:after{left:-0.628em}.x-button-forward,.x-toolbar .x-button-forward{margin-right:0.828em;padding-right:.4em}.x-button-forward:before,.x-button-forward:after,.x-toolbar .x-button-forward:before,.x-toolbar .x-button-forward:after{-webkit-mask:-0.145em 0 url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAABGCAYAAADb7SQ4AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAXlJREFUaN7lmTFqAlEQhh8EhFSCYJXW1law9QAewMrWAwQWAmmtbPcGHiCQ1gPYCla2QsDKSsgmQecvFqImu2/fzry/2OLb9mt23vwz47Kvn5MwEFxM8DkLB6HHEIOd0GGIwUpoMcRgyRKDOUsMJizxpzBiiMFR6DPEeZl1GWKwFh4ZYvAmPDDEqmVWVQxmLPG3MGaIVcosVAz2whNDDDZCmyEG7yFlpiEGKUsMEpb4XKXMtMXeiVVb7J1YLcRgW1ZmVuLSxGopLkys1mLwwhL/mVhjie8Sayxx3kp7DPFVYo0tzhNriyEGU5Z40TjxtDE/F6WcDowHBE/msDFNImG0xZQRBAonDCvxhhH2vKZIZ9Ds+7EDfaWFnKZ4xhja5owxdcnYCAQv1p1Gi4sprn08cZbDt6ZYZasXIn5mLFHTjLCvVt1V+4rVt/M+4r3FPaJMbHaBKRKb3pyKxKZXtv/Er4yjZpRL6q042u34tzh4xV9H/FHnqBHKBQeEd6aqqwD6AAAAAElFTkSuQmCC') no-repeat}.x-button-forward:before,.x-toolbar .x-button-forward:before{right:-0.693em}.x-button-forward:after,.x-toolbar .x-button-forward:after{right:-0.628em}.x-button.x-button-plain,.x-toolbar .x-button.x-button-plain{background:none;border:0 none;-webkit-border-radius:none;border-radius:none;min-height:0;text-shadow:none;line-height:auto;height:1.9em;padding:0em 0.5em}.x-button.x-button-plain > *,.x-toolbar .x-button.x-button-plain > *{overflow:visible}.x-button.x-button-plain .x-button-icon,.x-toolbar .x-button.x-button-plain .x-button-icon{-webkit-mask-size:1.4em;width:1.4em;height:1.4em}.x-button.x-button-plain.x-button-pressing,.x-button.x-button-plain.x-button-pressed,.x-toolbar .x-button.x-button-plain.x-button-pressing,.x-toolbar .x-button.x-button-plain.x-button-pressed{background:none;background-image:-webkit-gradient(radial, 50% 50%, 0, 50% 50%, 24, color-stop(0%, rgba(182,225,255,0.7)), color-stop(100%, rgba(182,225,255,0)));background-image:-webkit-radial-gradient(rgba(182,225,255,0.7),rgba(182,225,255,0) 24px);background-image:radial-gradient(rgba(182,225,255,0.7),rgba(182,225,255,0) 24px)}.x-button.x-button-plain.x-button-pressing .x-button-icon.x-button-mask,.x-button.x-button-plain.x-button-pressed .x-button-icon.x-button-mask,.x-toolbar .x-button.x-button-plain.x-button-pressing .x-button-icon.x-button-mask,.x-toolbar .x-button.x-button-plain.x-button-pressed .x-button-icon.x-button-mask{background-image:none;background-color:#fff;background-image:-webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #e6e6e6), color-stop(10%, #f2f2f2), color-stop(65%, #ffffff), color-stop(100%, #ffffff));background-image:-webkit-linear-gradient(top, #e6e6e6,#f2f2f2 10%,#ffffff 65%,#ffffff);background-image:linear-gradient(top, #e6e6e6,#f2f2f2 10%,#ffffff 65%,#ffffff)}.x-segmentedbutton .x-button{margin:0;-webkit-border-radius:0;border-radius:0}.x-segmentedbutton .x-button.x-first{-webkit-border-top-left-radius:0.4em;border-top-left-radius:0.4em;-webkit-border-bottom-left-radius:0.4em;border-bottom-left-radius:0.4em}.x-segmentedbutton .x-button.x-last{-webkit-border-top-right-radius:0.4em;border-top-right-radius:0.4em;-webkit-border-bottom-right-radius:0.4em;border-bottom-right-radius:0.4em}.x-segmentedbutton .x-button:not(:first-child){border-left:0}.x-hasbadge{overflow:visible}.x-hasbadge .x-badge{-webkit-background-clip:padding;background-clip:padding-box;-webkit-border-radius:0.2em;border-radius:0.2em;padding:.1em .3em;z-index:2;text-shadow:rgba(0, 0, 0, 0.5) 0 -0.08em 0;-webkit-box-shadow:rgba(0, 0, 0, 0.5) 0 0.1em 0.1em;box-shadow:rgba(0, 0, 0, 0.5) 0 0.1em 0.1em;overflow:hidden;color:#ffcccc;border:1px solid #990000;position:absolute;width:auto;min-width:2em;line-height:1.2em;font-size:.6em;right:0px;top:-0.2em;max-width:95%;background-image:none;background-color:#cc0000;background-image:-webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #ff1a1a), color-stop(3%, #e60000), color-stop(100%, #b30000));background-image:-webkit-linear-gradient(top, #ff1a1a,#e60000 3%,#b30000);background-image:linear-gradient(top, #ff1a1a,#e60000 3%,#b30000);display:inline-block}.x-tab .x-button-icon.action,.x-button .x-button-icon.x-icon-mask.action{-webkit-mask-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAAFI0lEQVRoBe2YW4hVVRjHZ0yzq6lFEaMlE0PShYRAJIl6iEqKHnqI6WJB0IvdICkfEk0aIyo0KFCph8giCitI7CkoohQL7SoZDaQmXSgKo4uWNf1+zt7DOXvOOXuvvc85bc+cD36ssy/r+77/Xmt9e+3TOzIy0jORbNJEEqvWruBOH/HuCHdHuMOeQOmmdO+ozaA5oxXPunSC2Re4MbgCNiB6vvqbKbx0giNxp9BeBU/BIJqnRecLN2UVrLDj4GIYgscRfSltYSuzYMUdA/0wCI8ieglM5XduK7vgWJhTegGshucRfQHkyj1XpziLNrfmOh2ug1dhMaJn0gbZZDpNpsexQb2y3azfKXCAwns4W5dMd7m2B2ANLCT/x/A/nKknN5mUhWFp1g4Z7vM14jrbBZvgEwi1tAdkDEf3ZrgI0S/RrkP4IdqGpuA+cJo0yw7iyNfJmzAcMrokfjp93HC4XrPYCdzkgPXDPPqvJN7eRh0VrBWqfKMuev6k3Qzr4SP4HWqOFIkZ73iYA/NhLpwPZ4LLS+FZzUp+GtwAA/heS/sGwv+irWnXc9bdTRF20/8eOBWmEKwnCectOrPhSlgF2+Bb+Bl+AxP8B/6FvLn8Td8fYQXMSubgsVZU8Cv4mAeNhC7k+jLYCopzrRURlvZA9P8WLIJJlcI5zi1Ypw+Dr4oqp3EAzlsbLCjfg1PeEUxLtlnXXU4/wQboq8gpl2BHx2l5UuyosuW8I6rQb8Bp1iwRefy4VN6FReaopU3pX7jnhwSO7MmVIiNnJ3L+DtgHCm3ltA0RH4/26rhKk1tdu4kr7yeuHkKgU3rMqI5ncfAQDIKbg14oi1nJv4OvTShthC9LjmTyGB8XwhZw+oQ8+Xbc68C8AOboK6+YYPpfDV+B06YdAkJiuMtzhvrOP1JYafMLpu/Z8CmEJNGOe60fz0J/cjZmWcP0G2+sWZ/aUnCqhFosOq7gyf6uOT888th+Ot0HmxF7MOkgt2AcXQNLkg5rHPv+dffjVvPX6PdeWtf7MJhUssD578ZtEGL6sY4MIfTjeh1zCWZ0Z+DwQXAkapkjtzviPdoPYB+JuJVMNfy7QQkR7MbGPfRaYhi7ruUSjLcbwe1k0tw2vgivwy6C70/ekPE4JK+N+HySWDuz+A5xXOnvlsqD6Lf/QjwBnxNc4a02YwzBeuIdyBosWDDT7RKcn1MRYA+/V8ImAv9Rcb5VP53ufoQ8AB8S0+PMFiwYz5fDzCjCF7SLCbojOm514zZ3HViYLIZVxmD4h8B0rtWtFXkEn4tTv22thPe2SawVeDs8TTz/NqoyhLqDGoC7wervt3lNCxKMY/fIc+BLuJXgn9G20pyuVuA1sJF4vt7GjHx8nZnT7XAXzIXnoK4FCcbLVHAqLW+DWF8v78Aq2EY8v7zGDK2+EmfBI3AtTAPNTU1dCxXs/a6ht+t6bM4FNykvw/0IdYSrDLHu8iyeQ7Cg6mLKQahgd0pbSOJwit/cl6Np6p+BrxGn6hNUp1z3m/tOWAH+DrIgwSTQcBcTFLnOzcRwSjZ6j/vdvQyCxRrSanu0mWvZqp3LjkbBuYTGnSac4CxreCQqJPFD+r/bhq+dtOSyCO7DyWzIcm9avKLXXb+FcskiYjlBfB0lP9KLJp+nv6N7ZL+cp7N9sgg+L6/zMvabcEWrK7iM07CZOXVHuJlPs4y+rNJ74JkyJpczp62N+vWOfpw0uqWzrnXXcGeN53g13REe/0w660x3hDtrPMer+Q9LNCcV91c+jgAAAABJRU5ErkJggg==')}.x-tab .x-button-icon.add,.x-button .x-button-icon.x-icon-mask.add{-webkit-mask-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAABqUlEQVRoBe2awWnDUBBE843B4NxcQSAFOC4lJeTkoxtJDykgvRhcgCFNJCFgIs+ChEHSJX93YT6ZD4ssmR3NztNFH5Wu6+6iVynlEZpbp+4J3s5OjWm7DRxZuMMCdUB9oyzNmrJe01hEejMtM5exIh6bCI3JbFkDT27EckEDs5DI8iHCWcmy6IowC4ksHyKclSyLrgizkMjyIcJZybLoijALiSwfIpyVLItuOGFso/xiuEvAgJdeK0DqJrHEhtsTTh9ul9y/ChR2KE+Y1ruDt2ccI7d6PszcK+oFFblWELt3Cn6i/8epMW5/W+LKGrUZ/0NwboF5QxuPsfY8dmOxJs41cBOYHCZF2BFeE60i3AQmh0kRdoTXRKsIN4HJYVKEHeE10frvCNvr4RH1HojH3rGHr3hqA7VdkxPKvuKJ3AA4hn7BM3xxA5N71Fdv1gz/tax3P+hFHmsJwM/8wraMadqOh5GuXda76rVqNWb7wgeevQvRRQ1MBCPFiginxEokKsJEMFKsiHBKrESiIkwEI8WKCKfESiQqwkQwUqyIcEqsRKIiTAQjxcoVrP83/9czD9EAAAAASUVORK5CYII=')}.x-tab .x-button-icon.arrow_down,.x-button .x-button-icon.x-icon-mask.arrow_down{-webkit-mask-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA2ZpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDpGRTdGMTE3NDA3MjA2ODExOTJDQUMyNUQwRUE4NjdEQiIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDoxQTFBMDFDQ0I5NEYxMURGQUU1RjlGMEFERUNDQTVEMCIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDoyMkRCMDIxMkI5NEUxMURGQUU1RjlGMEFERUNDQTVEMCIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1IE1hY2ludG9zaCI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjMwRTE0QzVBNDIyMjY4MTFCQ0ZCOTAzOTcwNzcyRkVCIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOkZFN0YxMTc0MDcyMDY4MTE5MkNBQzI1RDBFQTg2N0RCIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+HfrH/AAAAeVJREFUeNrs2cFHBGEUAPA3zYqIiIhOnTpFRHSKrp26RqeuEV077R/QqWtE166dOkVERHRa9hQRnZalFcv0Hk/W1Mx+38z3vvlm5j3eZW+/9+abne+9KEkSaFPMQMtCwQpWsIIVrGAFK1jBClawgo2ik/4hiqJGwLKuvfpIc5xSkWqYr5hzU1s/mRNxXTPsJ+ZqluvXlwOmSj3XBDvG3M1rpAmYYoUrFzr4ZNqTawqm2MH8Dhh7ZXJUbcAUx4FinzBnJcAUl4FhP/jIgRSYKvkYCJaO2LbNv08RMMUy5nsA4COTLy0XYIqtil9iF6aflq7AwBWuAvuQ9ZKSBgNX2ieWjtKSzeXBNZgqfe8J+4W5aXtbcg0GrvibB/BhkeuhBJhigzsghT0veh+WAlMcCGHvMOMQwcCdcIntYy6WmXhIg2PuiAvsEHO97IhHGgzckb4D8L6LmZYPMHBnhiWwXVdDPF9g4A4Vwd66nFr6BAN3ygbbw1yoMzjmjplgB5hrrufSvsHAHesZDOD2JAbxVYCBOzfIAZ9JbR6qAgN3cPwP9kZy1VIlGLiTdluCmoOBO/pnS9Bk8DzmS3pL4BMcpZEe1qX0GI/atC4dQYXRMa1MU0IX4gpWsIIVrGAFK1jBCnYUPwIMAPUPAyFL+nRdAAAAAElFTkSuQmCC')}.x-tab .x-button-icon.arrow_left,.x-button .x-button-icon.x-icon-mask.arrow_left{-webkit-mask-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA2ZpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDpGRTdGMTE3NDA3MjA2ODExOTJDQUMyNUQwRUE4NjdEQiIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDpGMDZEQTFBREFDOTMxMURGOEQ2MUVDMjM0MzY2NTBDQSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDpGMDZEQTFBQ0FDOTMxMURGOEQ2MUVDMjM0MzY2NTBDQSIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1IE1hY2ludG9zaCI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOkFGQzJEMjQxRjIyMDY4MTE4QTZEQzUxMDg5Q0Y0RTRFIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOkZFN0YxMTc0MDcyMDY4MTE5MkNBQzI1RDBFQTg2N0RCIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+FXGmxAAAAghJREFUeNrsm09ERFEUxt+rxBAxqyFm1SqiRYpMSpFapUVaRGpTRIpIbWLaFJEoRZtilChRWiRKsyklilYRERERERGZvsN57Wfmvnnnznkfv+WM+bn3e/ePN24mk3E0pcRRllC42FOWy4dc1w30R+fz3LFthEs1TelZ0KlBuAIcgmRgHS5gqlm2RsNTmqbvrUlZycLT4BhUiliWfEwEbII+UeuwT4nzqNZq2Gm1gTu/ZaUIj4NTEBW7tTTY1zUwKH4vbaive6BBw2kpAa6DkA1CeBicgZhVx8McUg5WWNi+83CWiXFfE9ZeAGQR6ukBqJKyu/Gzw7TcXEiS9UuYbiWWeU8ckXYqMT2lozyFW6SeOU0K1/FhPS75RsHUlKbj3KV0WRPC1Nd5sCuxr6anNPV12zFwk2jLCCdtk81XeAIsahL+BVOgH3xrEPayA5rAixZhyj2oB2ktwpR30A5WtQh7vR4DQ+BHg7CXLdAMXrUIU26411dahClvoBVsaBF2uMsjYFRCrwt5a7kOOnjUVQg7vE43cr9VCDu8I6Nep7QIO7z3HgCTvHYXvbCXJe71hxZhyjmv1w9ahCnP/DDb1yLs9boXzGgR9rIAusCnFmHKCff6UYsw5Ymlj7QIU75AN5gz9YVuLu8eB/S+dA+v1+l83pe2Sfg/BRe2OeGfPELhUDgUtip/AgwAw4tbozZtKFwAAAAASUVORK5CYII=')}.x-tab .x-button-icon.arrow_right,.x-button .x-button-icon.x-icon-mask.arrow_right{-webkit-mask-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA2ZpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDpGRTdGMTE3NDA3MjA2ODExOTJDQUMyNUQwRUE4NjdEQiIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDpGMDZEQTFCMUFDOTMxMURGOEQ2MUVDMjM0MzY2NTBDQSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDpGMDZEQTFCMEFDOTMxMURGOEQ2MUVDMjM0MzY2NTBDQSIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1IE1hY2ludG9zaCI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOkFGQzJEMjQxRjIyMDY4MTE4QTZEQzUxMDg5Q0Y0RTRFIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOkZFN0YxMTc0MDcyMDY4MTE5MkNBQzI1RDBFQTg2N0RCIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+xvZexwAAAhhJREFUeNrsm8FHRFEUxu9rxhARsxqiVauYXWoTpTYtUkRqlWkz0WaiTW2iNi3atGhTm4k2E5GYSJRaZcZQtIqIISIiYhgyfZdv/oF59913X+cdfst5733u+c495743XqvVUpKiSwmLWPB/j2QnP/I8L9SH9lN3/KxwQlpKT4FtaR7eAhegR1LRmgEVMCCpSg+CGtNczLbUC8pgQ9I+rCv3LiiBbkmNxwJ93S+p08qCRzAhqbVMg2tQkNRLa1/vg6ILvrY5POTAXdi+tj0tDbOYjUoaDzPgBuQlzcMpcEhSkg4A8lztjBTBin6u0d8iBOvoYwXPSRGsuEcXuWcnJAhuR4G+TksRrGOMfXhWimDFjqzCyUuE4LavS5yxExIEt0OfopRN+DpKbx6MHAtHSfAeWPN7kWQEhDbAMjg1cTHXBdfBLHiSUKXvwZBJsS4LPgCT4NP0hV1L6SZYAcdB3cAlwe9gDlQlTEsP9Gs16Bu5IPgIjIOP/34AoP26Ss82bd00LA/r1Vzk1mM1whCsfTrPpsJ62E7pE/q1HpaPbAn+Betgib1xaGEjpb+Ywrcu7H9BC35m8//mSncTZEqfgRGXxAYpeJNp3FCOhemU/ub+euXqzGlS8AuYBq8unyiYSulLNv9OizUleIcr+6MiEF4n3x7ze2n9OkSfE5/bfmg/30v7ERxaWBcc5Yj/5BELjgXHgiMVfwIMAGPkXbHq6ClAAAAAAElFTkSuQmCC')}.x-tab .x-button-icon.arrow_up,.x-button .x-button-icon.x-icon-mask.arrow_up{-webkit-mask-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA2ZpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDpGRTdGMTE3NDA3MjA2ODExOTJDQUMyNUQwRUE4NjdEQiIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDpDQUZBQUM3NEFDOTMxMURGOEQ2MUVDMjM0MzY2NTBDQSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDpDQUZBQUM3M0FDOTMxMURGOEQ2MUVDMjM0MzY2NTBDQSIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1IE1hY2ludG9zaCI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOkFGQzJEMjQxRjIyMDY4MTE4QTZEQzUxMDg5Q0Y0RTRFIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOkZFN0YxMTc0MDcyMDY4MTE5MkNBQzI1RDBFQTg2N0RCIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+ar3jxgAAAbFJREFUeNrs2j9ExGEcx/H71YmmpoiIaIq4KSKi6dabbo1oiqamm1qboimiNZpuuikiIqLppiPipqYjIuLp+/D95vy6X/frfr/n730e3sst53XP9x7u+V2ilKpM05qpTNkCGGCAAQYYYIABBhhggAEGeNSqpl9IkiQKWNbvfBc7PDdNIz1PPVK7Trd+OMPrRr8l9Uat2nT9+CyCW4yVnnnHowTXqa8UWHcdI3iNGozASscxgReo7h9YxTtfjwXcHoOVBjwJQYNPcmKlLk9EkODGP7FSO0TwOvU+IVjxZAQD1iPZK4CVGiGAZ6lOCVjFE7LhO/i0JKzUK3KImQY3S8ZKHZ4cr8A16sMQWPHkeANepF4MYqWmD2A9arcWsIonqOYafGYJK73yRDkB71nGSnd5r4jKBG9Sn47AunOb4CWq7xAr7dsA61G69wCreMK2TIMvPMFKfZ44I+ADz7DSQ9YhVgS87fiQGtdlmeBlvkNWnndYBljfGT8FgJVDbKco+CoQrBp6mrEyKfgoMOyvpxlZ4CT9vcXj0shWNe8nE8vCfzwABhhggAEGGGCATa1vAQYAZekAmr8OukgAAAAASUVORK5CYII=')}.x-tab .x-button-icon.compose,.x-button .x-button-icon.x-icon-mask.compose{-webkit-mask-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAAF/0lEQVRoBe2aW4hVVRjH54xa6nSzm92sHiZNorJowMpxrDEoyECiUUpztIkeeumpoCB6rAwi6FHwIXqKXkqiEE0no0QNLWwyspmGsruWlVqp0+9/2t9hz3Lty+mcfTnpB/9Za397Xf7//a219lr7TGVsbKztZLL2k0mstJ4S/H+P+ESfwEqlMhn/VNAJpoOjoGibAIFfwDbWnT/DZOCrex34D4b9vvw4wVScRKEu0AcWgQtBmYb9DvgsA6OganCWhgFwL/lHEf35v3ci/mqVFrAO8AT4FugJHge6URZsg0s3aDfOAe+H8f0INAo3gavD9928iT2bgqvBYVAWgWEeG+E1G0wwAeQ18hTZ/cDKSvROECnaBD9Iod9DFa2BMqSDEgAqjtiH8H3v4XwM32ZwlZUPp/jbLgHDoAziXA7r4aXIhsVqgZLYA8Atb9eK9BbQGRarvOwxEDdfdU9D/UiOUH9bwTixAWGJ/QmYuKhUojU6xomu4HgL3AV89ipO3ZdYlc3LJOJTsAeR1bAEr56V+J4H00Aa0/D+BNxPM0NW4Wcyvqe0G7+Gu5b9IhAexnrYq8A+4OMa55PoDaA6p0kjG1jHvVqnetBFQBxAP9CrJ27qxYm2OX25IhdlxxGoRgqzYFOxHAIvgHMbIKKF7iIwVe+yMtsA5F4CjYiVPu2+lhG/z3QRNRTeKGIIB4NKgXgEHIrhF8Xb9WuxmmVayhphLVDPgimgEdtL5VWI3RNuxH0idp17hCGlAOg924zISmyXRdbSskVYYjVnmxFZvXt14DjBLKJummuEYXU3iNsuuvyirnXam2cRddNSRJjXj1bjteAc0Ih9QeU+RG6JayTqSeUSYYhpu/griOKR1j9MGze7EXWvKRPZUaaC6VebAYltxrFUYue64nzXRQ7pfki+CDpAI6bVWJuKD9M0Ere1TFO/7jLMV+2NbTXWh8JGTDuoxYjVySqVFRFhfV15DjQqdoQ2BuoRS/mqRS0KTZ3D9KTISuxvIKrPtP5R2rjFnaP4Ek93lInsvGmC6eM00A+asRp/RTu3esRej3+G63evKZOL4HvoJ/x1MW0k3XI/0E6PR0Q3/o/AHPeee53XHO6DzDRgw5ls3fYlNZYgYHO4JmvgfVy/DjqBPhDEWuaCIXQpDOYELNaQPg4SiQXlLfmazErEvmsOpbQ9j+RlcAH4G6Qyd9jYdVPmMAx6wDEgkXOBHrK+lIqg9RWXSmy3OzTxzQcjwOrq29x1bjn3mjK1ClbR0oYF07Z2U08FfewiPV8EMK3YOu8midYCNd9DWpHVSm1clZZC8HkQ2R4Qe4Z0kpEnr5Vb36oU+TBxy2uB6rXyluK7AehAb+UsTSU46zl8BcRuBBrSg5CuzTPyf+HTfPbNaUVvKWU2kLq2BMdM15n2OmvBd0BEw3cHGPaQ0r1XwNuhe/r2vAKxG0O+cNbWg7AvdT6zvTQrqH5rXhowWYeAqmD8Z+DTqroA9IKFYDqQSewDlN2kiywsM8GQnR3gCOkQQmeRanhL4J1Av2qY6SP7XvBklmLVWZaCV9D+6eAQ0DxVVK8EZiNkPgDvAS1sQ4jV2ThTy0Qw0ZwM69sD5joVdQV5iV8P9DOOxO5DpL5j5WaZCIb9AqAV+ij4A+hw/maA/XlEkr68lpXga+ltKxgE2sDs9vZegDMrwWsQuboAPYldtieW+A8F8p6X9VDMRHA9BPIuGyd4LG8yKfuL46WdW6xJcFQDU3i96LRTGoOPBGmnligsirQWre/AxZ4C1+DrpY/3PfeKcl1Gxz3AJ1inrsR3uiquBf3AZ9/g1FFMjZXBZkBCW1Sf7WSx1NEx0bSv1QZBQ7tVoYA8jeDEf7yhXNuZ4B2gSq0qeBjuM1MJViGsB6hSK4rW598BMO6/bKPE14YAFXQ2HQWtMrwVnINAYmufjqKEmr8mOIj0bVTWSUYb/qQPbBoaRUABOQz03znLwUQTkyat/hZDpZrxGjqLi4VgMbgJ6L1XFlNUPwYKymvgACL10FPbCYJT12zRgnFbyxaVFE/7lOD459P6d/8Bhs9x6sTqrJgAAAAASUVORK5CYII=')}.x-tab .x-button-icon.delete,.x-button .x-button-icon.x-icon-mask.delete{-webkit-mask-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAAGcElEQVRoBdWbzYscRRjGexY1EPK9u9mVoJH4cVBPCYR8mB0IbkISyB/gOYIeFSUQQaIX8eBBDKuCsBFFxJuieFCMEb9RiZrcxKOgB7+i0RjN+vwm9Q41Nd0z1d3Vk9mGh6rufut93l93dc9katNaWlrKymytVmuD4mek7zX2YpmxqWJVwwrl2iL9qBp+LpN3okywjNYo/qh0Sjqi/ZVlxqeIdZ5HXA1HXU3xqbnDMVJGYJ+UzktMi1+le6VrY8aniMHLeeJNDdRCTWti88fCTirpSemChJHpT/Uflq6LNawah4fzwtP8aanppDQZk3sosBJNS4tSCGumf+jcMWlFjGGVGHI7D7zM12+pjRqnh+UfCKwE66SXpL8k3yDsc/4+KfmdJqfLHVMDta4bBF0IrIFrpaeloqsaQvM83S8lgyaXy2nvjdAz3KdWal5bBJ0LrAGz0rPS31KYdNA+8Y9Jtac3OVyuKjVQ+2wedB+wAqekE9Iv0iC4onNMvUelytCMdTmGTeOiGqgdhqkQugdYAdzZBakqrBXAXXlCWhkaDttnjBtb9s6at7UwwNJzp7vAOsE3KKaCfcbZwKrtP8r1oBR9p4l1Yxhb1dcfBwtMG+xCd4A5IHFHfpL8AXX7fFw8YGbDWmIlxtT19cfDBFsHWm22UVqUfpP8wFR97tbxCNjjikt1Z8PaYYMR1uwRidd5GJRyn39k8PaeCME55s4Rk9IzzAUjrNmcdEb6VwqDUu5fUv6npGsMmr47xrmUXmEu2GCcs2d4v3Y+kZqaUlbAf/J4SOKuIvocs/NNtDDBtp8L7b+lt+vgaWkU0M/IB40CFqbt3VllnQ59lu3Tyc+kpqfYZXmgJu6o5YQBln09jD07WdZSwF6JKdA0tBXWREvtMMDS6mH0d6yvoLb0sdT0lGsClpqpvW08ftt9hv2D9LVxdb6Vmn57p4SmVmreG/LYfiGwg96hwd8sE2hgqXWHweW1A4Ed9AElOTfm0MBS44E8SP/YUGAHzfQ+O6bQwFJb4TQuDexBj9v0tmkcBdvh8OmH9XUVt0nvSE1/7415kVEDtWwbVrd/PmpK9wzIsq0y+VLi6sYU1kQM3tSw1a8tpl8amKTa2s7wakAbbDsGMIypBOygdwr6C6npr4j+DMELz50hSOx+ZWAHvVvmX0mj+EaGB167Y+Hy4iaUoM7GW/sHiSvf9IYHXnhW3/KuQswxOa6SFqSqP6X6UzW2jxeeq2JqzIupNKVlyEri81K4sBVbeJ04PPGOXjH0wUsDy2i19IJ0QapTeJ2xeFPDah8mpl8KWAbc2cel36U6BacYSw3UUupORwMr8aS0KF3NOxteKGqhpqi1YWZAFLASrpdelMYJ1uCpidrWJ5nSSjQtvSyNI6wPTY1JFsRJNMqPHoMo21IjtVZeEJ9xCZYDrF0cg54pmt65z7BAp6QT0nKC9aGpvW9tOPel5WAX1KZaNrVCRtlSOwx90D13WAEsiD8nLWdYu7AwwDJwQZypUHf13wwHtWfkgwbFpDhnf/rQtyC+SeZ8Px3FnX1LPpud6KcAG5QDJtg2dZ5hdTZKi1JTC+J+MZ/K5yZ7g9KXOObHNNHvWRA/JsPzIzB9Xx53GKy1HJM41wSonxNGWLN56Wupyd+nTiv/rQYZtpyTiPELTNmHDcb5zltanTnplHRRSmlErjek60PIcJ8YF5vaHybY5vDsfizpwB4p9TLp68p5SwhXtE+sxJhU0JeUC6Y95tkF7tBn2SGd/FxK8VcAHyjPzVLP+qwZ57XEujGMrQsNAyyHfK8eYAfNM82bsw40KwJ3Sn1/teOb5/UZ48aSoyo0tcMwH3r0ATvogwrmzwWq/Pz6nsbdLpWGteIY63KQqyw0NVP7Qcvnt7nADpq1YZYzeA5iTV9T7I1S9DT2i/H75HC5yBnrT63UXLhGXAjsoNsafFaKudOvKG6zVBvWwMnlcpJ7GDQ1Umvbxue1A4EZoO2wSzToc/ptxdwgJYO1YsnpcuNRBE1twB62cUXtUGAHzTN9TsqDflPHb5OSw1rR5HYeeIXQ1ERtuc+s5bA2CthB80yHn9P8pDIrNQbbLfQKNF54GjTPLDUVPrM23tpoYAe9S8k/kjB6VdoiNQ7bLfYKNJ54UwO17LLzMW2nWA2K3vQ/we5S8N0SL5LvZHI5enCCQPnzkcU3snukd+X/YZm0/wPdHqnTTpY+CgAAAABJRU5ErkJggg==')}.x-tab .x-button-icon.organize,.x-button .x-button-icon.x-icon-mask.organize{-webkit-mask-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAAEdUlEQVRoBe2aS2xMURjHjbbqUaLoI7RChQUiGo9YaEqkoolIkCASSki68dixsLIVYmHbkJA03UgkFRI2QgRBKl4RgtJFK0jUI+o5fv/p68ztmUlHzpzO9PZLfjP3fOfcO9//fOeee+69E4lGo6PCZKPDJFZaQyc4N1mGI5FIMfUVkAfZMPaVwE54yqn6i+8BllQwravgAEyEv5DppsQ8gYPw3hqsJi0bNJ4El0GZzSa6iHcbjLbpsp7DDGX5V8ByyDbLJ+CdUGQLPNGQnkzj3TDFspN68BNkwhDPIY5poG/T1lBYR+LOkuW4uSeR4KXssN48grF9h20NdeukYLRL96Y6vAD2wCwwbQyFvXARPpoVA85fKnXiN4HtvP2Gf0tPG3XWUKNYT4E6PxjvD3x1EDHPZZvgxTTSDBc8gMrKbql5gKHeJh7NM6/AFu91/EVmjHGTFmN+HA3qYSoE7SuO8+zcEawY4vJdfr8Z/ljiqMS3AV2RvjpTPc7V0A623rqJv8RsnynbxDUXXieJuy/LfRmmEzSd7wKtroL2Hcc5BL4LVmRCmbheEIfmHduVQ1muQV/3BN2bJZyqaANbdm/jL+xtm4nfxKcsP08Q/zX8MxV3TDXqx+PYBGUQNHVAI9AsYrsuB9sPVflDT5xH+O7OZn8kK9msJf6G3ooFOOr66+O2NOVL6A7oP/njmmREQcN5LGhy1cLJtBwK++FSLqrVSGvPcrCZGu8DZTqTBSs+zUkarTZTUrerYh50gHYY7rSpRxZCCYTByvouS2FQK42hE9w7S/tKsOaIt/AGfoMWO3OgFLyYb8FaGByHl6C1r27jlsAh8HaN14LD1+x8jN/KNVdqlAvhgq8YfJ/DLYjVUDatk8J905HObd+Cf1rEaHTp5sSL+RacaKWWyO+8E3wLdi4g1QOOCE61x7Kt/UiGsy1jqcY7kuFUeyzF9ok6WA8ZvJjLtbQWEI/hXpLIW4N1rLyiPHV5hP9MsM4or2V7hlH+702XghWE3gAcTRKN3mjY7AZOdZbNCnAug4wTrNXSItCrmmYSZ3tGTNVAo+1nvCLOyLyeT9WC7WlqXNtUCq7vlpTlGkQMeG+Vio9j6NbxMOjtn8u7udjzaJcH1H3uLViVikCzLftqEtsKbeAyNh3LuWAdVM+yr8JsU8hgt9mvGh6ATousEKwgdcvXCMWDFap2mOYBTWK6b3YtNvYDrs9hM0i9BTgB+YMRTbvp0AS6bzaP43I7LUPaDFBvHPVmIy+ZaOp1+TkJX8Dc3/V22gUrYF1jN4L1r0T4NSPXg+sZ2dZZXgRr5m6BymCW8en6rc54BrYAXfu8CFbQmoQ0c1eYoilXw0NQp7gWZzueN8H68S44DbG/IPA9H66AL7FR12tpYk9qetOwGfSaVjcMNVAFie6iqHJv6bws2YaUfLpctYP+S5WoTVr8vjOMvphN4FN4N69Dybs6yw+OCLZ0yrByhS7DmrRaoQE0Kw5707JOf/UvH/ZKewTG/kscFrHSGbpzOHSC/wHSRhVOrpN3ggAAAABJRU5ErkJggg==')}.x-tab .x-button-icon.refresh,.x-button .x-button-icon.x-icon-mask.refresh{-webkit-mask-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAAG1ElEQVRoBc2aa6hVRRiGO17yrmloWpqhllH2wyKSEIsIo8QorLSbqVRgJd3DyFAQIyIiKQz61cUgpB+B0EWii4VkGFRUJpWKphZaaVZeutjz6FmwOnuvNbPWXvvs88HD2nvNzDfzrpn55tvrnLYjR44c1wpra2vrRr8jYC9j+KOzxmCnrTL7ng2LEN+rswbRSsH/ItL+Fwqij+8M0a0UrD5Fa0vg2c4Q3WrBik3sVj480WzRXUlwG4Lnw9OI7p08haqvXUmw2tzH8+AhRPf1RtXW1QSrz4i9CJYjepA3qrSuKFh9PeEWcE9XOtMtE0yyYYROojQfa0zRc8GZ7l9TWvJGj5LtCjdj0AYll6uD90HLQMizZKZ70vzOKjKypgpmkONQMxpGwWlwAvg9STLG8jlkip4FO/H3GKJ/DzXIK2/DQV554TIGdQaNpsNkmAAjoYpj5i/8rIIFjPlXruVMwY1Czy7X8+Al+B4OgU+yag7i0wjereyYqxDrDD4Ku6FqgfX87aGfR6BPGdENCabTqfAh/A31Btesez/T32LoXVR0KcF0NByeBPdSs0SF/Nr33VBIdOEoTVDyKFkCN0OlSQH+Ys2HsReMF66ueCuyJPDqzD4HvqEIzUCzyk1WtsAcKBy8opc0zgfBU+A52CwxIb+K3Qw3FJmodN0owXTgseNxsA9Cg2pm+S76vyktoOjn2D3sfjVAhFJBqmSax8km+BZ2gBnUlXAmhMyH+B3cj8DVocq55aEnROOJsB7MdIrOnnt9DVwD48G3lAPAB21evRRCPl3G22FaaKwx5blLmk4c2DNQdN+aaa2DKdAvayCULYQ8wYnYhpZxuv+QYGf3a/gnMLD0oH+h7mIYnO6o42fK/bX0MKTbpj8nYmd1bNvI98w9zHnbh8FcDSPBwcWYe/ReWMOgfEhlTbH6ugs/75Z1Urdd1tOi8qnwGcTO7j7qXgU9snym71Mva4bt70uYmq5f1ee6M8zsOphJoOiY2XVGlsEbDKxY5kOjlLmkt4Iz+z7Xyi1LjD/QJ4PLOsbWUmklGMkbsc00fqBZYh1Y3RnmvjnyWeDREbL9VHgVdjNQZ6is/URDxb5e1kFMuyzBij0ZzLBC5n5bzUAbmV2Titvx8V6os0bLs5b0aBz3j3CuyA/A36dlzK2zFTpFrAPMmuFRlPWzQsDMpN6BMoGqO+2+h9tiZ7Y9mBpXQivPIHoYvzXjyhKsUwcUsoNU2IRjj5JCRhtXx8rYRohV5Bh4EExP8+KFK24VfAT/syzBLmeT+5Ap9LdQpYrKFTwMrgcF55k/Tj6FGsFZe/gUKhupu5q5VGOCo7Nv3RrLEryLmgdqarf2hjPsyssac9ToshobjGKepO1jzuqowQQqGVNOj+zvMPVMdWssS/Cf1IwJRAa3CcSTmABX03nBG451DMTEFleniUyNZQneQk0zqJC5xHw3HTOIkK9QuYHqQsgKtOn2Ct6ZvpF8zhK8jQou65DZ+UXQ1ADHCrKfyTAWQubK/AH8XV5jWYI3UtOzLMZMQ2cyqGbOshnZDPBYCpn79xuouyWzBLskPodDEDJf394IXiu39vgwEccXQyjDsn/H/gkovMayBCt0Hdg4xi6g0rVNmuUT8b0AzA1C5vnryjT7q3sOZ77TopH7ZQOYj+oohH89NAuKeuPBgDL7Tsrw5SmwHEJ9J+W+bLR+/8RHx2tmpzRy3yyCfZA4DF23UfcK6Nmxo6Lf8WFUfhzM10P9JuUeRZfl9ZUp2EaYeycJAInT0NU/ct0HQ/M6ziqjnft0PLwCsavLMbkNV8OQLN9HNeUWHjtfn8eJiUhIaLrcCPkaTIHo2aau+3UmbIS0v5jPnrtz8vQEBR+tcOxVz3qcmWrGdJyu42y/BXfAJKjZW9w7CaaBy/djKDKrSV/mDCsg+HCj/qmF6DsPZ8tgOJQxV8geMBnwszPobCp2IAyFYVDGXE1fwAwmaEvQQWgJtM+ySYWC90PyVLvC1aPHQHl5jI6jWqIrHpuFl3F+oAuJ/pGxzIXoP4znRumODwPHI+BFcFm2eoZ907IEBnQcZ973QoJ1hLnnXoBWiXYZ74D50CtPXL2ywoLbRRtwloKBqDNnWrEGvOugVEZXSnC76O506o8GX8QbKZst3KPnTTi33szF3istOOmAAZgVrYBm/SeeD/MruAf6Jv2WvUadw3QUNM5q30ZcCrNhDMT8lKNapil0LayCtxG4JbNmgYLKBNsnortxccbPh+lgBuUvnlhzW3iumpaaofkzbzvXyqxSwelRIb4f3w1u58AlMA6GwNkwGEwhN4PZl0vWWLABDEr7EVr3BzxlDdl/zhnCj3tOo0oAAAAASUVORK5CYII=')}.x-tab .x-button-icon.reply,.x-button .x-button-icon.x-icon-mask.reply{-webkit-mask-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAAES0lEQVRoBe2ZSWgUQRSGM24YTdSo4AYRTcxBEZJDJCoigrtGg6CIgihqogfRgEERguhB40UP6kHw4kEET4J4E9wPAdeg4ALigjuKcSMuMX7/mAmdSU/SXdM9PTPpBx/T3al67/31urq6K5G2trac3mR9epNYaQ0FZ3vFwwqHFc6yEQhv6SwraBc5YYW7DEmWXUhZhSORSC7UwKIgxzAlghE5CZFHoAEKgxTcz8/gCI3gfzHsh6l+xnLq2zfBaC0miXpYDvmgu+kXBGqeC0aohK2D7TAF+kPamKeCETseZdugGgZDSp4RxHFsnghGqKo4H/aB5uoASEtLWjBiZ6KsFlaAHlJpbUkJRmwl6rTcFKW1SktyRoIROhofdbARhlr8OTkMdBPNlWCE6iG0AA5AqRN1Nm1cxbTpn9Qlx8ERO4pIG0Br6yDDqH3pV4kvPdRewCd4C+/ZPdWx7xZxsk1LgqvIZDeUeZzRT/xJ8Dt4BQ/gGjSSVzO/3psEJ4JoY+A4fATNvVTwhjh34RSshMGJ8jO5biuWIJqrc6AJ/kIqhNrF+EFs3fqHYRoMMxFp7dNFME5Hwi5QMLskgrqmgb8M+hgZYRXh5riTYBxpFM9CUKKcxlWOSyHPjVi1jQqmYy7shQ/gNGjQ7f6Q6yWY7UY07XNK4CK0QtAiTOK/J29tLOQ7EU67nIGgtfU1mARMhz6a3zegtCfRHXOYxhXtndJBgGkOT9FQ1Z3oDsFqhBXAFngJpkGD7veN3NclEt1JcKwRHaaD3niCTt40vh6+q2N6rL+2gtUA03p8FL6AaeAg++ntsNwqNqor/kL8OZ2WgF71vEpeq8FvC36uDveJM8qqyenHwzg67oE1MAxMTeLOQyNod0SDqO2hCaDVIma6u3R9OAxq/9WxW9PT+wRsQ7RiE7Gbj4f4v9F8Fujxb1ptfR2tj/cbf04bfbbqZWgsFEM5LITNcBLc3HF6iM2IxXAlWJ0wJXEQfoFb4RJcEwtu8kv/PCiEGdAAevFQJbvL5Rh/j351uRbcLloVmA83ewgUn0TSgq2DRGzloVt9E9yDFoiPqfOvUBHN3erA7TFOtG6fBqdfVp4KtuZLDqr8DrgDdqIPcb2/UYXjAmmu1cLDBIGswX0THMuJHIrgDGglsMZu4nxI0oItgcbjUHP7MyRaanwXrHywvlAFj8E6v+dqZ8MTI9BzHO2DtaC9KY1wIEYurXCO4JrbjyA6CvzO80wwznS3tMAFDpfBKdArnkY4ECOXqwTWUqZvA1mJp4L/+4wKf8ZxDeyE26AlLBBD9HUC14GWr8mezWEc2/oiiNZM/TumGbRLkdQ6nChOT9eJWw3ffakwjjuMRF5wUg9b4QnE5hOHKTVNsSuO3qW9SosN/Yn4KmAQbnnl040f4pelVLCb5Pxq6/st7Vfipn5DwaYjlyn9wgpnSqVM8wwrbDpymdIvrHCmVMo0z15X4X9rh8wHLEjawQAAAABJRU5ErkJggg==')}.x-tab .x-button-icon.search,.x-button .x-button-icon.x-icon-mask.search{-webkit-mask-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAAGdElEQVRoBdWaa4ycUxjHd9rpbm2bqKhiUavbVZdo0LCyLl3iHhGEkkZsKBYJX4RISHwQIYIPNJoQlUjTuCakUZ9oVGRF0GywslvqbgkpDarqsn7/6XsmM5n38pzzvtudeZL/nplznvM8z//cz5ktTU5OtuWRUqk0i/qdoAN0gcXgP+CkzIcx8APYBXbi82/SaZFSKGGILiTibnA+GADHgbkgSXZT8CF4GwyDEXxvI92r4k0Yoj1EeAG4CvSDEggRkX8VbID4lhADQXXUwxZgfAF4CGwFmgdFYQJb68HJljjy6mSSJZAZ4CLwESiKZJydb7A/CGblJZVWP5UwzueBB8AfIC7IovO0mK0B89KCzlOWSBinWoBeAkWTstiT3948xJLqxhLG2Xzw4jSRdQ0yiv/upMBD8xsI40Rzdu00k3WknyeO+aHk4urFEb4TJ/80CWEdYB4BhS1kdfswe+zpGNf80RYUIr9QSdgOdNCYCfaLcABpqFxBbymu3FIlDFkdD18B5wRYHaHOJvAeGCU4fa8IdnXUPAaoMZeDk4CvfEKFM7CrhswnbpxjZQX4C7j5Y0m1d64EXc5OWoqeFsPLwTvAYt/p/Iv+6jTb1rLKHMbYgWCjZxCb0T/e6qhWj3o6hz8HRMSRykp17l5WayfksyN8oafzTegfHOLQ1aG+blc6ZGQRdeVawB4GlWno7Pim1G9rB08AZzgrfRfdw3wdxelHvl/38K01Itc2Rf22Q8BPIIuoynXQL/SQj71DwcfA4n8nev1xjWfN0yGjD2gxsYh6432LolWHQL9F91Gj/j7oacUPFhE+11hbLxbrCFBzqWh5A4PDRqN90RZqVK9XE+ET67MSv41D9s3E0nwFX1Ndu4RFjkZpjkUxTkeEdTDIEvXqW1lKoeU0pOavXj10OsuSI1CYnaWUVC7COvpliR7f9CQzlaK5/LPBQRc6mstBIsIW0WXiO4tiDh35mIr1oS4kK2ENOctwqzPu+SX0MdDLjZWw9Pb1suyv7EPYR7cuEithLRLL6moW/0VriaVRtT1qTQkSER411Cyjc4pBL4/KEirPNRj4FZ3gXy5EWM+vWaIhtJQNf2GWYkg5dtWzui9bhuqn6OkVNUhE+ANjTZG91Kjrq6bDxHnGStqvcxHWsU5bQpZ0orCK3rDs21m2quXY6+DLTWBBNTP9wxbOKZZ4E63omLYZWG4r0nkQtOtwVASwdYeH723o9uTxS/3Ks+ytHk5/R3cI5LqIK2hEDw86XVkb+wV0Z+YiHDnWCjnu4Vj3Ug3DzhDn1NPacTX4HljJ6gFPr5e5RpZ74tFz6l0ezhWk5tFTYJFPEOjrLKxhrEazktWR8zVQ9vEVp1ttLYyplyeANQinN0ydIXBUnAOXR7nsrwAbgatrTbX3nu1s5Ul1oKgIRsZYMR/jy72gY0+u6a8OJMJX1P+C9MsaqDcPAseCHtANQkRTwHIoybZd21qR0Q2k1pZP0tNJSIubLhxJOr75egO/sjbekM/VIe0qY1RDb6p//PYl6/QniO0sF2tI2kBYRpBTgVrUOWqm9DPiGgghW+GWVBGj/UCvEM1E1sWinr4sKfa0/NgedhUwqsVITzvOUTOl6gxv0qmERRw5HOi/bHz2zb3VMHp28hremYQj0rq23QhGwFSQ0ZVPu8NvAfa3Use8kJkI1wzxxRhfDcYDAotrKF0GngYnRA17D599f7KVXcVzmoszLfUi7AxhfBG4GKwFPudhBacnmpfBStDwnzrkrQIhpDW8L3ExJqXV/wBA2Vs4WelquT9Qzy8FvdHnDlKR01RQ8OrJMaAp8TnYQUA7SBsEm6pzPXgcyI6PaCG7Hdu6VcVLUkuE5ONBR8ByDGb42sPGteBPEDcV0vK0ZZ2Z5C9oSCcZKzqfwO8OJK2FbCAunqYmrICRQaA3rLRejSvTWtGwTzc94Yj0DQS/O4C05nQd6VYhrIVMpEN6Wqv3crBngY4b582aR9DXgJCFTPt05T+AtKq2jNARzxLs/UBbnY/0onwLO97sXPuwj8cidQn8OuytAe0edjUyuluqh2vIPcNnPS1rIbOKfkRf0pKEGdqSJyFwM/AZ3j+2JGHXpZDWWf4+sMvlpaTal7e3xLYEsdQ4ITIIsras29AppxrKctRM5ZDRLUvv13GnLl1p5yjellylCb5BolvWkRQMgT6g6apXmnVgPWQrc/1/boJCaHVWyukAAAAASUVORK5CYII=')}.x-tab .x-button-icon.settings,.x-button .x-button-icon.x-icon-mask.settings{-webkit-mask-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAAIkklEQVRoBdWZd6yeUxjAe2lLUbVKrFaLUhUVo1pbQtqqESOECGLGH2IkCP8YQewYtUoTKmkJ/2hVEDFixN5FadXWBjFaq0b9fl/vuc5973nf9xtvez9P8rtnPeec5zn7/W7HsmXL+vzfpKOjYxVs3hR2hlXhT/gcX94iLBYd/r+BR2vB+eBsyVJ4FPqX+eJItbUwm8rmMEZDTRAMhG1Nd4p+bABbmUZlAGwLI0D9Lmlrh7HV5boHOHuPkL6LcCisDztCEJ1aBxwYwyvgMbgfToD/pGwJ9FY5FjoZ42AuhKX7N/HX4Er4Psq33PQ0eBz+APP+gbfhAOjQl7bdvxjYH86F4Gwc/pWT74DEesYXwWWwtg6385L25J0FH0JWXOopyfrjDC+AmTj7sxWyCua1hWCgs6Ox58GPTRr1FfVmwBuhfts6rIH47NJ9Eu6BWBwM9+xU8HqaDA5OLL+ReAmm044zXZPlGzmk2iDklHUSvF4mwU4wHEbCuqDo7OdwKXgK/w4DwEfIdVC7vgjVcxnPg/fhHZjVdocWRmn8faDBKRaTf4srPoa81eFocABS9cy7ra2XNAam5BcyvZqy4vL/Er7OFsTpdnW4yK5+OBCWd+yLjw9neY04Mxsvajiru7LS3qXut2/Aq8mZ6zp0iPuOnsBeH0wYi1thL8jmW99l7ux/1G0fxHui2TiNOojdaLQt6vcF38tbwyHg0zLel57AD8Io2Ay2h+sh3r++tl6AI2AbWBv62XAlwogPoyFPVhvuJpRpyCwc/7hbQU4CPWdlMfWWEFrX2YvFpXskTIRFsD4Mgqy4Qr6gPZ+ny6XR0c/Tp7Up4GdaPBNx/KG8unn5tOV+vLOgzbj9VNwD7gHYMPRRyR5mJpyBIVDU3lD0/ISrS9B19U2A4+uqkFZywMbCYbTnqig00PJ6xYNCPCnzZD0KRuQVJvJty089PyJicdY+hfggs7y2fAl/MBGJk+DJ7grgb+YCz6ZRceY8OHaEftly08ho+AQ0IrW0zPsWjkrV72zDg+VwGB50iHse3AbhpJ5P/AzYBz6E0Jf9egqfDieBZ4Vl38E1MKirzRBJhSh6ED0D7k0bvAA2gVVifdITwQd+MCAVOgMXx/WMIx42J8M88Ep6E7YJesSd5SthBuwOzvxweBhCPw6IV5nL1y+pPWEqXAJd+7fWX2g4G6K4HTwHGhoaNnwZDoLVQh3iZ4NXRayXinuV1N7vtc779NmN9NOZejr9FowL7WdDyjyVb4TQhzY+A7Vv3qBPuquvrrwQiUMUR8JMyDobOlhI2dXgIbQaXAvhV4agkwqfQs+DxH11PrhqUnou0TkwNrYrxMn3ADoMXgUnwIm5Ano4GOqEsMceppJ76REomzGX0bNwCrgMnZmU8XGeA3UizIK8wQz6Ou0+HROMjUPyXboOngyArhUX62XjKYcvp7IHTOi4N0MH5eGs0a2kXVpZ8fBYnM3spbSrxqVdnWRHi5Y9Ne+Gn6E3Z1dnn4fBWRtbSfdY0jaGjAYf3u6j3nLabbVfK86l6qaWNP3UllGYZdMrWzzxJ8OLVXdcO8ZTjfL29CP7VvD4r71DU3qJvPnkfQ1hZWxGfMuEXl7WXxQ8AacwQ9/kKTWdn5r2kEejO8DbUM+V8yR6x8II8CM9XBdbEffJ6FVXtkUsXwC7BhuqDpN7OHRCx951flgvgTBj2XApZX7CDYHci5+ywXAOFD1QbGsq9A02VB32pXH/26Zj/cEL3JkZCs6MT7+DwfyU6PwUuBDDCq8yyr+ln5vQ3RB8ZaXOD+2xv2XovkK4AD4CB9yB+o12XG1Niw/xLeBA2Alcji5jr6Z6xJfWQRihQXULzsxG2T7rER8fbqu54J08m/7eIWxarqJm0TLLLuGQ1pCjYFUMKNwa2XLq7Au/Q2ir3tDZfQoa7jPY4LLym9Pl3Kg42q/TUDNLzDv+tUY7RF973RJNS2of1duYDv9Sr3JGz9P4jUxePUlXgnWbllYcdmY1oFnxvl3p0orDrdTV0VbrNzVYrXS6NT3mXVdlxng7bF+mlCi3Xkuiw57QzRw8Xl9DuGKaGbSNqbsrNCpuIX+YaFq86KfDuuA97AnorPl2Lju51TkTXoe6Dy8GyFm6CLwdysSJ0EH5CfwFZEqTNwNVO5+CtcjymRpKfDsY1UlI+6NZaiZ19CyYhhHey6WCv0egdDf4a2RKfiDzPVgI78OczvAD+mjphKYdjtmSRwMqPh1/VTWHz8g/AZK/Wcfto7MfzIO8thy0B+M6VccLHaZzD6aXQEPyjDTfc8CtcQD0eAWRtwdMBWevqB1n0FkdVbWjob2i7+GBdHwpnAZrQj3yPUoLQKMXwXowEhy4wVCPOLjT4AKMtL1qJXieDellEvgzS9GMrKgyz4ZTszZVkU4uaTobBrPB19CKcqqoXZf2fBhdhZNxGz0cphOvm5uhbL8VGVxFmYP9BAyMDW41nrpqDqGT8ZB3bVC0UsQfJfYGr73KJOXwLrS+QQM9NHo3NqLvw2hcA7aUqqYcdu/6ovG0LJM5KNwBX4LLuEz8Geh28OebMrE9T/p7yhQbKk/tCRrw55eXwaddaj/6a8VMGAP+93AyeBendOO85zr1hxNOA5+McXmIuwr8ifaklH2t5PU4tEJjdDYWfCdnHx1zyTsG1lAX6YAzIc/44ITh/epHffhQ8feqWEdnXWGTgl6VYa7Dnc7sQ8fvgiems3ov+M7u9poifSh4d8aGp+JXZ42nzibgP7eXgM5+CuOzelWlCx3udNqZvgGOg+QVQb467mMNTjlqnl87J6cMJ9+zZH+4BfZN6VSVV+pwPR1hpA+VNyFvz+vwJ7B3Pe2tSJ3UKY1dDctX1PBzTsfyxGeq26NXpRKHmZGleOEV4pLOk4Xo+XrrVfFir0r8bh4EG0E8057i3r8eTL0u/wJCZSL2DoplLgAAAABJRU5ErkJggg==')}.x-tab .x-button-icon.star,.x-button .x-button-icon.x-icon-mask.star{-webkit-mask-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAAFfUlEQVRoBd2aXahVRRTHz/Ujv+2mZRGZB7W6mtpFikC7+UWUZiqBD0JPFdRL1EMFPfjoU4baS0FUD/UWZBEVShA+BCpmWApRSkgllNpDmZWZt9//eOay72afvWfWOTPn3rvgz8yeWbPW+s/XmT379AwODtZSSQ+CryVgA/gVfIx/pelEhFMBVlvBOaBeFo6Cean8y09KsnMg932TqCOs9M2UhMfhMJVsxtHcAmcbmekLCsqjFKUkvAYG1xSwmEHZqoLyKEVJCDOCNxH9HUCbVl6mULAuXxjrOQlhgl8Bbi0h0Uen3FBS37GqVIQHiHh2SdR16jTlo0t0woycpuxiUDSdHcFeMv3uIWYanTDB3wIWVZBQHP10zuQKvbarUxDWT1HRz1E++Ds99fLtgp6jEmbExhPNcs+IbkZPiCpRCRP5TPCQJ4MJ6A3QSUqjSWzC2ozuC4j+fnSnB+gHq8YmvJKIJgVEpRPX9QH6waqxCa8PjEhHT981H2j6qno0wqzF63BhOUxsom3Zb7aJqGsUjTAONFJlpysXQz7VuXpavrBTzzEJaz1adlzNjHs6RTBvJyZhjZTF/kTaWZZCnlvhsyWgQkPZQpagzsX1bFlAXjGtDdAPUu1p3PPQhCCXkdwG/mta0PWLds060AuAnqtEOjpdbQR3VymX1P9F3UfgGJA9X9F92c/ADaQ2P8V0DJ4/kDbeYKaSvgI2AN0+OGJK1VAbSIhTOXEOybYll2kte77yD4rqrHyb85S9Cl4HtReAyI11/A7HpRq5PSD6oR0f3Rad+H7S1DvV7UgS+tc1cU3n3V/AWJ/SX8BxVuMinow2rNNjlPQVeH0GFg378kDBfLAPXARjZbTPwmUXmOG+bgz71EKFfqKeAUWfREZbJxyCxyOOqEuHER4qrNUWovwy0CFktBHV4eNZMNvxyaaFhKWAaBt/HJwEo4W0luSKLMF8viVhp4iBeeBd8CcYqcQ1qi+CKS7uVmklYdcQY0+C42Ckkf6EmO51cVal3oRlCFkCdKgfCWtbo7obDO3AVWQbHHyUsjo40E6uq9cvQbdG+wN892fj8s0HjXDWKA51/t4JUo72H/jTDtybjSUkbyYsJ0gdfAtSjfTn+JoWQjCv2+57a4M1QaQSvZvrMsIs7RJejGcdUlLJUhzpZsYsZsJcCen6ZwCE3IaYA2021OfUdU3fJltmwni7Fvh+KDMF16KR3ux0lWuSdgjPxeNdJq/tNdKNqJaSSUyEmVK6JNPomtqbIh3eSKNsEmvAarfJ5LEzjbbR59MtpqyEb8eZjpndkhtxvNri3Er4YZxpx+yW6Jdhi8V5MOHm+n0QZ9afo0u0fQO8A5S3iPaQ1cTSG9w4f/SqesZBH/gRWI6T+gyyxfkgvw2cMdrS+/lTzpZvGnyWxsnTwHLRd4R2a/OBqQyoztKBe/P2qp6DCBOUptKHhuA+pU1fq2Co0/F0L9CVaghxXTbWW9ktKg8lrFfCrwODeh/9wgu1bEDo6OT2Fvgb+JLWq+nQEsnaa5UPJbwKBxc8A9KXPG1O3u+u6E4F24GvD3XMDjCxFcF8uTdhjGpHfwn49L42lCeAdyDZwGi3HpwAPr6+Q29htn1ZPoSwfuz3ewShXVcBNz62lzkvq6O9DjZHgQ9p72kdQljvob9VBPAN9Q+UEQmpw5b+Sf8e0FotI/4a9ZN8bIcQXlnh9AD1y3ychuhgU0tpJyhb14epn+ljN+Sk9S9G1ct50d8SdgF9x9EO3lHB5hXwPEYfA8dbGD9LuWZBtfj0inSQWUDTKzu1dAB5Dkz2tdOOHn70LvwVyMag/FYwzse295Rukq5j+G1wEOib66PAy5FPMD46+NPmqTV7CpwGGvkJPm2l8z8GWDNDloqpGQAAAABJRU5ErkJggg==')}.x-tab .x-button-icon.trash,.x-button .x-button-icon.x-icon-mask.trash{-webkit-mask-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAAFBElEQVRoBe2aS4gdRRRA8+L/m0QIJkYNLlQUNOAvigpRcCEIcSsiCLoLLoILcaM7QVBX4koRshDxt9CFKCoiuvGDCP5QkxCiJhInRo2Ovzie80gPNWX1dL3uesM09IUz3V1169a9daur+031aG5ubkUpGY1GK7G1Dq4Cz9vKiIY74Sv8+72tkWQ7Ay4Bxo+Hu2E3/AuOZBf+ov2TsL6Ef5WNUsGazXvgEHQJMm77N/aeg3Mrh7seOweMM2bWYH+B2OES1/9g9w0oEnSngHHCYO+FGSgRXJ0NM/0idA565BRpKyxSt9J2B5xWY+Mw5Udq6uqKT6XimESlmX4d7sTnA4n6rKJjs7QSSgTrSno7nJyodtFyGr4AP4G6TeLIHweb4A44C0LR1xtgCzwP7aTtIkBvLlSfQjwNZyl7FNa0sU077V4DX0Js25X7cRjPzDb2Nd5FnK7xPbGXskdwxsxOLLRzdnwIj8GvkQFnypqobKLLrgGnOjMzP6cqJijzfn0NXPljmXRNWNC+dcBHM7HA2NELp10nwbaz5iC4OsdidTyrYp3a68ZFi7XJFfNsOBGcUmFnPpbiBWkVZefT7g+OXcTF0EUsFPtaje0Lw0LOzfoM49B4Gy36WMKwK+WDcC2cAmGwXK7YAAYdym9c+NiIdUOdnHODc6DjpPioix9LBvwtPE3QOzjWi7MjBS0M8CGY1huUA1ISg/4cNqXiqcqSwVqJ3AQ/QEmnpm3LR+IzsLYKMD4mA6bBOfAKuFpO28nS9v0Bcxckn9V1Ad9Pg2m/H5cONLT3Mf5fFGfX63hBQG8s7/LXxcdV0nvjMtgKp0MojuaroM60xYB8Z78ZTog6c515B1ylXey+ARe3/0tqFNCy0RjrkdvgOwhH0TeiB2A1uMBNGx9Ta+FZiP34mrIrQR39cECSUzqZYYIcR0mjJtmFwmHUvdenLjwmnUl7Eh05+LP40fjvoGTACYN1Rc6CecGhM7lw2lt+AA7Fg4fOespXgYO0j3pvnXmh3rY+/52+vrXtRSd841rQJ/WV1JVX9eNj14DnjeHnJVw8DBeAnX8A2ynfXwXN+cWUPQUOjNl6i7Jt1I9nCOe+1V0NT4AB/wkvw31QRIoFjDfnwRXgfVbJGZzsry44boTNUGVjlvOToPpV5FvbjXApKE7VLZ6UkpWlDGHH+96pV93/4TSsujGA8MeF51Xw6njuO3soKTth/UTnJQOeqONFlKsBW0SlfdVyDLh9NBkth4AzBqnXKkOGe52+DOeHDGcMUq9Vhgz3On0Zzg8ZzhikXqsMGe51+jKcHzKcMUi9Vhky3Ov0ZTg/ZDhjkHqtMmS41+nLcH7IcMYg9VplOWY4/Md88cEtHbDOVg5Xx9jpsM9Yx52JeAcw1ontTXRdcm9pFz3vBveHdNJN6YPVRhrnivtMlruZ5g7DFxBuXLut8j7sA/d43Yr5CIpJsYAJ7DN2/27Bsw1gwAb3I8wLOp+g4w6+nw/6HddOyszqWDg/Qv2bXFwH4+1SyhyUYtI1YLc85wXn/ORAagWdPVRKUqh3AJwtdTLeWq2rbCoP76cm3bjeLG6ELjZim03XJujyJqXF6rtmeDvGNzMN/ajEAZi2rKOD67t00jVgN7+3dnFgqdsu5XRc6tiS/eUGvBTTNengBIVZPuYG7LcYPjdluYk++bTw++pGyQ34bSy9B35Vs5zEYGfgJfg+x7H/ADoy2VfnrtXoAAAAAElFTkSuQmCC')}.x-tab .x-button-icon.maps,.x-button .x-button-icon.x-icon-mask.maps{-webkit-mask-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAADl0lEQVRoBe2b24tNURzHjfutXEPycDAltwhJbuMSJUqSB/HiES/+AK9ePc6T8uCFkImQW5KGkdwSxYyMGkZu45bbDOPzyZyTrJnjnDkGrVm/+szas2bv397f33ftPS+/Vdba2toj5igj0NcfRkG/3qWIJdcIrs/AO6gDq7cKPkOjUNAmxr8ePJsix8NUWAvLoapowSQawIUzYCZUwAqohF3QAjtgGTyCy5x/nfEu1MNDCmAxuiS4Vy8ST4DZMB9WwiTIRUGC26q1gKtWwyyYBsPB5aLIL5CNTxzotDeWTeA5DUKuO4xXoQbxHpcUbSIzJFkDi0EzdLYnBNGuYJJ4ch+YAhvB5TAORsKvib4x97vwPpk2FjJuhibu85zxAlyCangBLRQib06u68t5vk4uVYVqgO+oqy9v5ASTRLd0LQNLYB24bAfBnw5zikX0HtuhGW5ANY9ylvEBvIY3FOArcz7rWHCpboBFMAxyGjguKIZy1jzYCqfAD5BLslB8J3dCP/AdOgo+fKHXd3Sebh+EctCMieBK6Oj8QuYrXZ7roQr88PiSD4b/IVyyfhB9jQy/uppTUijYhANLytJ1F/sxzL7POpg97vQdFfwVTNYtQsHdKpLg2O1ODieHI6tAWtKRGRrISQ4HJYlsIjkcmaGBnORwUJLIJpLDkRkayEkOByWJbCI5HJmhgZzkcFCSyCaSw5EZGshJDgcliWwiORyZoYGc5HBQksgmksORGRrISQ4HJYlsIjkcmaGBnORwUJLIJpLDkRkayEkOByWJbKLbOVx0r3E7httIbttwNvzddt//JWxIfQynYX8pgu2TbgBbjw9Ds53sNHJv49gOehu5bUe2DfjXojDVpWG/9iu4CEegBp7xfO+LFfyGC5+AiQ7BFXj/c8s+xw+Z24PwvYwKnQxLoQLccGEB7Hsu9t5ckjcU2QjuozgA5+Apz9PCmItCbvqWs2vhJpwBl8ZrEuVtOebPtiWLbf2ymyL0ZVT8XJgDbgHIgFsPOhPmr4d7oAnHue9txg6jI8EfueIaHIOrcAuafieSc/IG19vw7TYD6UEBbE4vhwxMB7cizIYhYPT6MeR+WjBFPoCToEgF1hb6bD8LNpHLwT0L56EOGkhUchc6edoNcruvQWoQ7/6GMTAa3E2zACxGNjRhH9wHV4zP9oGxqCjj7C0wA06Ay/YliRT/T4MCuGnEfQ4feJ5mfvdfaG+OXSWdju+VpAoIK3D9tAAAAABJRU5ErkJggg==')}.x-tab .x-button-icon.locate,.x-button .x-button-icon.x-icon-mask.locate{-webkit-mask-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAAIDklEQVRoBe2aaaxeQxiA3eqCltpLkWotLUUtsUuJrbUFtSSaiIjljz8kQhOJiAQRQYREYvmFSPrDFiSExFpL49JSS6u0Re1bLUVRz3N7ph1z53zfud8956sf3uS5s7/zvjNzZuac7/asXr16g25IT0/PKPrZAfaFXWAMvAEL4GNYgS1/EjYqPU07jKNb4sGZcBocB0MhlYVkPAgPYM+itLDWtA43BYY6m7PBZVSFXuqd2ZQ96m3S2ZkY/0lFR+PBcFlf3ZTTjTiMwQfCR4WzfxO+D8/BTxA7Vxb/nXqzmnC6docxdDg8WTj2F+EtMBrMPxiqzvqn1N2nbqebcHg6hoaZfJn4sNho0hdB2cym+bOoOzRuP9j4EBTWJuzII1F2OngEuZQfwcBVhLG8FifaxM+jfHybOgMqrtVhet4OfH6VHsjpn9xXWu3PRKrtXK1qtVo5g6q1zNfyzJ1UFOnwCcz6ZqEq8bHErwzpCqE6JtHOsBap2+FNsGrjyLIjid+PvYfBDOJPwJSovEp0wyqVqtbJ3Xqqts3Vy83EKVSUTiWns1Nd2WesY2U0XAHfDkZBpu3vbHzu3rVI3Uv6G6z6oBbL1il5b1108LG6Hf4ak+YO3qy1Gl4ltnhtqoZIrQ6z8lZi06PwWw22qUJdn9Wkq09NrQ4Xhs0hfLgGI99Fx30MotfT+sT9oG6wbhzMAzebTviRdufUbZf6anc2GInBh8A7HTj8A23Ogw2DrjrDxhzuG80118KHMP7XCo57934Ljq/TwVRX4594cGADblmXEEyDqeCrYiy+XPhC8RzcioHfETYmXXE4WI/jXi1PDOkiXE44CUd9pWxcmtilWxnt0k5lVbecteNuO+xsplLrOZsqT9PddviL1ADSn2fyGsvqtsO5N59c3v8O1zUC3Z7hDzHcm1cs5nVNuu2wr4+pNHrupp3V/cUj1d+X5vwdTsS+RmYqjKDcT0N/cjz9kSmvNav2iwfGj8HCfcDflXaGbcGPezpsuBfEsoTEMvAnFmf7K1gCXjPnMwhfEtYmg3YYB30s9oeT4TDYCbYocGY7EWf6+wJ/qZgDj0MvA+Cdu2PpyOFiifrJ9SS4AHYDv1bW+oURfUF8J/bjgj+l3gteUZd38ggMyGEc1aHJcDb4k4nLtZW4RMMy/YW4LwonQHz29hZ1NiV0yW9VhASl4rK/G2bDAhyv/JGgssM4668K58OFMB5io0muFZ+518CPb34EWAga9VuxMvxlMIhH1FGUvUCZb1G7wu4wBfaAg8E9ISe2/RjugbvQUe1rKRXbvhOj8Ax4AxxJO0pxw3kEnHk3pezLO/mbgV81Q3v17ZmzgXxXk7rU+TSENmlo3y/C9JyeNK+lsyix08vAWUs7Mq3BL8GxMDpVnqapMwqc/aDL9lum9dI0ddwETwX7ctMK7UNonndybc0OdtBZ6jANh8GV4DMYFMfhj+TfCBsFZe1C6urwXAh6Kjkc9NLO5/wW+DXSEXQZausVUPoTa9ZhGvh8OqI+F7HCEP+I/JnBkKohbXS4N9HZdoZT/bR3JssmwpmelrYJ6aEU5mRPMp09l1JOlpI5lo1mFmHYvDyPXfqzUb6CMCc+b4thv6LQgTMvK8VGdhaFblwu2yD2uQRy9m1L/s20XYYd7xH/twTPQ0ipl4XrwY/pYUbT0DKPmBgNnwc7BV1pSJm674Sg73Xio9J6IW0Z+MyrO+7Li0nZsla39unD8KArhLkZ9iw8F0ZAmbQq+6asEfnO0nx4rIgvIiydYYz8mZnSATfPVNxjysSB9X/DboWv40o5h4+igod/Tj4j02XoaOdkHkauzBWYR5nOOcNSVeZQ0UtLTrR/AuyYFLrkvQn66HikrZMw1SGk5BooW84ukxGh7voOsWUjuBnCIxKHDvylqY1uNKnEm0Na5kiOTjPXR5ql7ixuD3uU9G/55mlZzuGfqeRI5cQb11T6yj0KufpN5vlcHwRHl3TixH2YluUMf5NKXghysgmZHuzzcXoRy6VsYHJt/QXCAZ4A6gkyoMu/jQo9vm9fBWUbqD4shH9LusYp9WxbBo5Q/EzE8Qcom5i2bZemjTelBYnerdq1S8tpvzf4Y3lsUxzXdk+ALfq17ZexZiO4g8q+1cRK0vjblM9I27dKawD8EOl1FgZ006L+TNCZ1J44re03Qb8Ntt/Vkko+7FOh7OoWK/bMdefeoZWjoYx6nvFx+8oO2wdcB98nOmJ9Ie6V+PDQbxz2c9hCZGNwhNrNspU1+hO4FiZDq5uTDls/GGZ869igOK4uUKe67SNuG3SkoUeq9fvdsvp8izuI4zTYBeZClU5Cp559D8GFcCCMh82DXuJukrE+nzV/OewbeOuCbQ4FdahLnUF/u9CLzfMwLuhMw5ZfPNgNp9H4NtgdXOoDkRVUfh/cKX3mloM76u0QdOmA1793wSW7G0yEKTAcBiIOnndzLxvev/OSjkCappVL6hlw9NqN8PoqX4Vt3s/Hp/an6ewz3K/SmhvNDSj86T/otDZp25jU7ly6ksM2RIbADHgFBvJcNTXrOvpCYdOQnHO5vMoOh8Z0sA1cDi9Cq3fSphy1z2fhYsjuxMHWXNhy00JhqbCheWtyJ54Ox8D+0KT0ovwp0NmXcMYjc8DSscOhJxwfRnxHGAfHwQFwBIyEwcgvNNY5HyHxHF6Kox5rHcugHY57xnnPWS8t4lHmIHjEeNyMBXf67WACeJNbDH+Ag+ax5fE1D5YWcd/cVuKkR04t8g94XuILUVeybgAAAABJRU5ErkJggg==')}.x-tab .x-button-icon.home,.x-button .x-button-icon.x-icon-mask.home{-webkit-mask-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAAEK0lEQVRoBe2Zy28NURzHe/vwqEepYkFIQzxWaCOC2HhELEgQImhXIrqyIEXikVQi+gdIwx9AItg1NiJELMSGhKQbobY2VY9Srfp8m5lmTO/cOXN7Zu656f0ln8zMnTNnft/z+505j5sbGxurmk5WPZ3ESuu0E1xbigjncrka3jsbftClIvsU5RZ65aLK5Lj/C75SzSjHWCuJYLxqhPXwBgYhylq4sRaixChDP8EzGIJ4UwNnCR6tgFswANegKer93LsLim4herm/JKqO8O+ZRdhL42acOwunYAacg2Hu3ePYj3Ph1A1fU2ySmZSZeCiTjxaC1LAboRs6QGJl8+AKXIU1kLqlHmHEqlFboQv2gD40QdPHqx3qKdtJkD8Hb9o+TzXCXmT1cboB+cT6evTVPgIXeWYl6DoVSy3COF2Hx0rjTthp4L0a/4xXrofn33OeqH8avKMqFcE4O4uXb4ULsNfEEa+M0v00LIIuCKc/P03NrAtGrD5Iiuh10Dia1JTOR0EZsjjpw3HlrQpGbD0v3AzFig36e4CLkeAPNs6tCUbsHBxS+mpsLSayYT2KtLBqVgQjdgFe7QP1u9VWPbRc2ZQFe2LV5zSBWG7ZP+vVTUkwYhvx6DicB+fFqvWKFuyJ1QxJ00It48rCNNgnNi+N23hQaVw2YiU0cYQRq9Q9CJdBKV1q02zMeEaWSDBil1L5JTgBDeCCzcUJ8cXImfACOeqayjbBffgDfqu6cPyJP3dgVZTvwd9jdzuoSFmgicRDGAYXRIZ9+I5fPbA6KC7feUHBVKD5rJZ1EutaZMOiv+HjbWjJJ9T/LVIwDyqyh+ApuC7WFy/RCk4r5HyRwWNewRSW2N3wGv6CX2E5HBWcB9AaFOqfTxJMQa1lNewosqNQDiLDPmqv+hFsgzpfrI7/CeamVjwnQZEtV7G+eEX6MeyHGl/0hGB+1MJdYt+B/1C5H9UdX8J2qJ6IMBfz4Ri8hXIXGfZfmdoLWr5W1zJ7ktg2aId18BuiTHNvDVUumQSNxDikLSdtBzdok0yCD8MyiLNmCqhxXBL9An+egNI3yqRT9z+O92FO/O2UuOMuymoqF06bUl53489MQw21Gm8lWmkRa6R/oVaMfT6lAmrsUVMNRa2HU3I8k2orgjNp5hK+ZLwPp/x+fR+0ONfMp9BfJ+qLmulpyze1zMtC8AACbkI/xAneQZkO0JiZimUheAjPn0MfxAnWVo3RiEG5oiwLwXJsmGFDK5iCxrCnGZNSOzVLra+EPDZ9T6EMCFVZ3KWpI8XV7uBTFcEOBsWqS5UIW21OByurRNjBoFh1qRJhq83pYGWVCDsYFKsuVSJstTkdrGz8L0VTv1i+NVF2CyTJDC0LX7E8HIx7D/Vrb3wDaLvY1D5QsI/6jXZUEwk29cDlckki5bIOY9+mneB/GfbU3e4Ey5kAAAAASUVORK5CYII=')}.x-button.x-button-action,.x-toolbar .x-button.x-button-action,.x-button.x-button-action-round,.x-toolbar .x-button.x-button-action-round,.x-button.x-button-action-small,.x-toolbar .x-button.x-button-action-small{border:1px solid #002f50;border-top-color:#003e6a;color:white}.x-button.x-button-action.x-button-back:before,.x-button.x-button-action.x-button-forward:before,.x-toolbar .x-button.x-button-action.x-button-back:before,.x-toolbar .x-button.x-button-action.x-button-forward:before,.x-button.x-button-action-round.x-button-back:before,.x-button.x-button-action-round.x-button-forward:before,.x-toolbar .x-button.x-button-action-round.x-button-back:before,.x-toolbar .x-button.x-button-action-round.x-button-forward:before,.x-button.x-button-action-small.x-button-back:before,.x-button.x-button-action-small.x-button-forward:before,.x-toolbar .x-button.x-button-action-small.x-button-back:before,.x-toolbar .x-button.x-button-action-small.x-button-forward:before{background:#002f50}.x-button.x-button-action,.x-button.x-button-action.x-button-back:after,.x-button.x-button-action.x-button-forward:after,.x-toolbar .x-button.x-button-action,.x-toolbar .x-button.x-button-action.x-button-back:after,.x-toolbar .x-button.x-button-action.x-button-forward:after,.x-button.x-button-action-round,.x-button.x-button-action-round.x-button-back:after,.x-button.x-button-action-round.x-button-forward:after,.x-toolbar .x-button.x-button-action-round,.x-toolbar .x-button.x-button-action-round.x-button-back:after,.x-toolbar .x-button.x-button-action-round.x-button-forward:after,.x-button.x-button-action-small,.x-button.x-button-action-small.x-button-back:after,.x-button.x-button-action-small.x-button-forward:after,.x-toolbar .x-button.x-button-action-small,.x-toolbar .x-button.x-button-action-small.x-button-back:after,.x-toolbar .x-button.x-button-action-small.x-button-forward:after{background-image:none;background-color:#006bb6;background-image:-webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #0398ff), color-stop(3%, #007ad0), color-stop(100%, #005c9d));background-image:-webkit-linear-gradient(top, #0398ff,#007ad0 3%,#005c9d);background-image:linear-gradient(top, #0398ff,#007ad0 3%,#005c9d)}.x-button.x-button-action .x-button-icon.x-icon-mask,.x-toolbar .x-button.x-button-action .x-button-icon.x-icon-mask,.x-button.x-button-action-round .x-button-icon.x-icon-mask,.x-toolbar .x-button.x-button-action-round .x-button-icon.x-icon-mask,.x-button.x-button-action-small .x-button-icon.x-icon-mask,.x-toolbar .x-button.x-button-action-small .x-button-icon.x-icon-mask{background-image:none;background-color:white;background-image:-webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #ffffff), color-stop(3%, #ffffff), color-stop(100%, #e5f5ff));background-image:-webkit-linear-gradient(top, #ffffff,#ffffff 3%,#e5f5ff);background-image:linear-gradient(top, #ffffff,#ffffff 3%,#e5f5ff)}.x-button.x-button-action.x-button-pressing,.x-button.x-button-action.x-button-pressing:after,.x-button.x-button-action.x-button-pressed,.x-button.x-button-action.x-button-pressed:after,.x-button.x-button-action.x-button-active,.x-button.x-button-action.x-button-active:after,.x-toolbar .x-button.x-button-action.x-button-pressing,.x-toolbar .x-button.x-button-action.x-button-pressing:after,.x-toolbar .x-button.x-button-action.x-button-pressed,.x-toolbar .x-button.x-button-action.x-button-pressed:after,.x-toolbar .x-button.x-button-action.x-button-active,.x-toolbar .x-button.x-button-action.x-button-active:after,.x-button.x-button-action-round.x-button-pressing,.x-button.x-button-action-round.x-button-pressing:after,.x-button.x-button-action-round.x-button-pressed,.x-button.x-button-action-round.x-button-pressed:after,.x-button.x-button-action-round.x-button-active,.x-button.x-button-action-round.x-button-active:after,.x-toolbar .x-button.x-button-action-round.x-button-pressing,.x-toolbar .x-button.x-button-action-round.x-button-pressing:after,.x-toolbar .x-button.x-button-action-round.x-button-pressed,.x-toolbar .x-button.x-button-action-round.x-button-pressed:after,.x-toolbar .x-button.x-button-action-round.x-button-active,.x-toolbar .x-button.x-button-action-round.x-button-active:after,.x-button.x-button-action-small.x-button-pressing,.x-button.x-button-action-small.x-button-pressing:after,.x-button.x-button-action-small.x-button-pressed,.x-button.x-button-action-small.x-button-pressed:after,.x-button.x-button-action-small.x-button-active,.x-button.x-button-action-small.x-button-active:after,.x-toolbar .x-button.x-button-action-small.x-button-pressing,.x-toolbar .x-button.x-button-action-small.x-button-pressing:after,.x-toolbar .x-button.x-button-action-small.x-button-pressed,.x-toolbar .x-button.x-button-action-small.x-button-pressed:after,.x-toolbar .x-button.x-button-action-small.x-button-active,.x-toolbar .x-button.x-button-action-small.x-button-active:after{background-image:none;background-color:#0062a7;background-image:-webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #004474), color-stop(10%, #00538d), color-stop(65%, #0062a7), color-stop(100%, #0064a9));background-image:-webkit-linear-gradient(top, #004474,#00538d 10%,#0062a7 65%,#0064a9);background-image:linear-gradient(top, #004474,#00538d 10%,#0062a7 65%,#0064a9)}.x-button.x-button-confirm,.x-toolbar .x-button.x-button-confirm,.x-button.x-button-confirm-round,.x-toolbar .x-button.x-button-confirm-round,.x-button.x-button-confirm-small,.x-toolbar .x-button.x-button-confirm-small{border:1px solid #263501;border-top-color:#374e02;color:white}.x-button.x-button-confirm.x-button-back:before,.x-button.x-button-confirm.x-button-forward:before,.x-toolbar .x-button.x-button-confirm.x-button-back:before,.x-toolbar .x-button.x-button-confirm.x-button-forward:before,.x-button.x-button-confirm-round.x-button-back:before,.x-button.x-button-confirm-round.x-button-forward:before,.x-toolbar .x-button.x-button-confirm-round.x-button-back:before,.x-toolbar .x-button.x-button-confirm-round.x-button-forward:before,.x-button.x-button-confirm-small.x-button-back:before,.x-button.x-button-confirm-small.x-button-forward:before,.x-toolbar .x-button.x-button-confirm-small.x-button-back:before,.x-toolbar .x-button.x-button-confirm-small.x-button-forward:before{background:#263501}.x-button.x-button-confirm,.x-button.x-button-confirm.x-button-back:after,.x-button.x-button-confirm.x-button-forward:after,.x-toolbar .x-button.x-button-confirm,.x-toolbar .x-button.x-button-confirm.x-button-back:after,.x-toolbar .x-button.x-button-confirm.x-button-forward:after,.x-button.x-button-confirm-round,.x-button.x-button-confirm-round.x-button-back:after,.x-button.x-button-confirm-round.x-button-forward:after,.x-toolbar .x-button.x-button-confirm-round,.x-toolbar .x-button.x-button-confirm-round.x-button-back:after,.x-toolbar .x-button.x-button-confirm-round.x-button-forward:after,.x-button.x-button-confirm-small,.x-button.x-button-confirm-small.x-button-back:after,.x-button.x-button-confirm-small.x-button-forward:after,.x-toolbar .x-button.x-button-confirm-small,.x-toolbar .x-button.x-button-confirm-small.x-button-back:after,.x-toolbar .x-button.x-button-confirm-small.x-button-forward:after{background-image:none;background-color:#6c9804;background-image:-webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #a2e306), color-stop(3%, #7eb105), color-stop(100%, #5b7f03));background-image:-webkit-linear-gradient(top, #a2e306,#7eb105 3%,#5b7f03);background-image:linear-gradient(top, #a2e306,#7eb105 3%,#5b7f03)}.x-button.x-button-confirm .x-button-icon.x-icon-mask,.x-toolbar .x-button.x-button-confirm .x-button-icon.x-icon-mask,.x-button.x-button-confirm-round .x-button-icon.x-icon-mask,.x-toolbar .x-button.x-button-confirm-round .x-button-icon.x-icon-mask,.x-button.x-button-confirm-small .x-button-icon.x-icon-mask,.x-toolbar .x-button.x-button-confirm-small .x-button-icon.x-icon-mask{background-image:none;background-color:white;background-image:-webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #ffffff), color-stop(3%, #ffffff), color-stop(100%, #f7fee6));background-image:-webkit-linear-gradient(top, #ffffff,#ffffff 3%,#f7fee6);background-image:linear-gradient(top, #ffffff,#ffffff 3%,#f7fee6)}.x-button.x-button-confirm.x-button-pressing,.x-button.x-button-confirm.x-button-pressing:after,.x-button.x-button-confirm.x-button-pressed,.x-button.x-button-confirm.x-button-pressed:after,.x-button.x-button-confirm.x-button-active,.x-button.x-button-confirm.x-button-active:after,.x-toolbar .x-button.x-button-confirm.x-button-pressing,.x-toolbar .x-button.x-button-confirm.x-button-pressing:after,.x-toolbar .x-button.x-button-confirm.x-button-pressed,.x-toolbar .x-button.x-button-confirm.x-button-pressed:after,.x-toolbar .x-button.x-button-confirm.x-button-active,.x-toolbar .x-button.x-button-confirm.x-button-active:after,.x-button.x-button-confirm-round.x-button-pressing,.x-button.x-button-confirm-round.x-button-pressing:after,.x-button.x-button-confirm-round.x-button-pressed,.x-button.x-button-confirm-round.x-button-pressed:after,.x-button.x-button-confirm-round.x-button-active,.x-button.x-button-confirm-round.x-button-active:after,.x-toolbar .x-button.x-button-confirm-round.x-button-pressing,.x-toolbar .x-button.x-button-confirm-round.x-button-pressing:after,.x-toolbar .x-button.x-button-confirm-round.x-button-pressed,.x-toolbar .x-button.x-button-confirm-round.x-button-pressed:after,.x-toolbar .x-button.x-button-confirm-round.x-button-active,.x-toolbar .x-button.x-button-confirm-round.x-button-active:after,.x-button.x-button-confirm-small.x-button-pressing,.x-button.x-button-confirm-small.x-button-pressing:after,.x-button.x-button-confirm-small.x-button-pressed,.x-button.x-button-confirm-small.x-button-pressed:after,.x-button.x-button-confirm-small.x-button-active,.x-button.x-button-confirm-small.x-button-active:after,.x-toolbar .x-button.x-button-confirm-small.x-button-pressing,.x-toolbar .x-button.x-button-confirm-small.x-button-pressing:after,.x-toolbar .x-button.x-button-confirm-small.x-button-pressed,.x-toolbar .x-button.x-button-confirm-small.x-button-pressed:after,.x-toolbar .x-button.x-button-confirm-small.x-button-active,.x-toolbar .x-button.x-button-confirm-small.x-button-active:after{background-image:none;background-color:#628904;background-image:-webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #3e5702), color-stop(10%, #507003), color-stop(65%, #628904), color-stop(100%, #648c04));background-image:-webkit-linear-gradient(top, #3e5702,#507003 10%,#628904 65%,#648c04);background-image:linear-gradient(top, #3e5702,#507003 10%,#628904 65%,#648c04)}.x-button.x-button-decline,.x-toolbar .x-button.x-button-decline,.x-button.x-button-decline-round,.x-toolbar .x-button.x-button-decline-round,.x-button.x-button-decline-small,.x-toolbar .x-button.x-button-decline-small{border:1px solid #630303;border-top-color:#7c0303;color:white}.x-button.x-button-decline.x-button-back:before,.x-button.x-button-decline.x-button-forward:before,.x-toolbar .x-button.x-button-decline.x-button-back:before,.x-toolbar .x-button.x-button-decline.x-button-forward:before,.x-button.x-button-decline-round.x-button-back:before,.x-button.x-button-decline-round.x-button-forward:before,.x-toolbar .x-button.x-button-decline-round.x-button-back:before,.x-toolbar .x-button.x-button-decline-round.x-button-forward:before,.x-button.x-button-decline-small.x-button-back:before,.x-button.x-button-decline-small.x-button-forward:before,.x-toolbar .x-button.x-button-decline-small.x-button-back:before,.x-toolbar .x-button.x-button-decline-small.x-button-forward:before{background:#630303}.x-button.x-button-decline,.x-button.x-button-decline.x-button-back:after,.x-button.x-button-decline.x-button-forward:after,.x-toolbar .x-button.x-button-decline,.x-toolbar .x-button.x-button-decline.x-button-back:after,.x-toolbar .x-button.x-button-decline.x-button-forward:after,.x-button.x-button-decline-round,.x-button.x-button-decline-round.x-button-back:after,.x-button.x-button-decline-round.x-button-forward:after,.x-toolbar .x-button.x-button-decline-round,.x-toolbar .x-button.x-button-decline-round.x-button-back:after,.x-toolbar .x-button.x-button-decline-round.x-button-forward:after,.x-button.x-button-decline-small,.x-button.x-button-decline-small.x-button-back:after,.x-button.x-button-decline-small.x-button-forward:after,.x-toolbar .x-button.x-button-decline-small,.x-toolbar .x-button.x-button-decline-small.x-button-back:after,.x-toolbar .x-button.x-button-decline-small.x-button-forward:after{background-image:none;background-color:#c70505;background-image:-webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #f91f1f), color-stop(3%, #e00606), color-stop(100%, #ae0404));background-image:-webkit-linear-gradient(top, #f91f1f,#e00606 3%,#ae0404);background-image:linear-gradient(top, #f91f1f,#e00606 3%,#ae0404)}.x-button.x-button-decline .x-button-icon.x-icon-mask,.x-toolbar .x-button.x-button-decline .x-button-icon.x-icon-mask,.x-button.x-button-decline-round .x-button-icon.x-icon-mask,.x-toolbar .x-button.x-button-decline-round .x-button-icon.x-icon-mask,.x-button.x-button-decline-small .x-button-icon.x-icon-mask,.x-toolbar .x-button.x-button-decline-small .x-button-icon.x-icon-mask{background-image:none;background-color:white;background-image:-webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #ffffff), color-stop(3%, #ffffff), color-stop(100%, #fee6e6));background-image:-webkit-linear-gradient(top, #ffffff,#ffffff 3%,#fee6e6);background-image:linear-gradient(top, #ffffff,#ffffff 3%,#fee6e6)}.x-button.x-button-decline.x-button-pressing,.x-button.x-button-decline.x-button-pressing:after,.x-button.x-button-decline.x-button-pressed,.x-button.x-button-decline.x-button-pressed:after,.x-button.x-button-decline.x-button-active,.x-button.x-button-decline.x-button-active:after,.x-toolbar .x-button.x-button-decline.x-button-pressing,.x-toolbar .x-button.x-button-decline.x-button-pressing:after,.x-toolbar .x-button.x-button-decline.x-button-pressed,.x-toolbar .x-button.x-button-decline.x-button-pressed:after,.x-toolbar .x-button.x-button-decline.x-button-active,.x-toolbar .x-button.x-button-decline.x-button-active:after,.x-button.x-button-decline-round.x-button-pressing,.x-button.x-button-decline-round.x-button-pressing:after,.x-button.x-button-decline-round.x-button-pressed,.x-button.x-button-decline-round.x-button-pressed:after,.x-button.x-button-decline-round.x-button-active,.x-button.x-button-decline-round.x-button-active:after,.x-toolbar .x-button.x-button-decline-round.x-button-pressing,.x-toolbar .x-button.x-button-decline-round.x-button-pressing:after,.x-toolbar .x-button.x-button-decline-round.x-button-pressed,.x-toolbar .x-button.x-button-decline-round.x-button-pressed:after,.x-toolbar .x-button.x-button-decline-round.x-button-active,.x-toolbar .x-button.x-button-decline-round.x-button-active:after,.x-button.x-button-decline-small.x-button-pressing,.x-button.x-button-decline-small.x-button-pressing:after,.x-button.x-button-decline-small.x-button-pressed,.x-button.x-button-decline-small.x-button-pressed:after,.x-button.x-button-decline-small.x-button-active,.x-button.x-button-decline-small.x-button-active:after,.x-toolbar .x-button.x-button-decline-small.x-button-pressing,.x-toolbar .x-button.x-button-decline-small.x-button-pressing:after,.x-toolbar .x-button.x-button-decline-small.x-button-pressed,.x-toolbar .x-button.x-button-decline-small.x-button-pressed:after,.x-toolbar .x-button.x-button-decline-small.x-button-active,.x-toolbar .x-button.x-button-decline-small.x-button-active:after{background-image:none;background-color:#b80505;background-image:-webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #860303), color-stop(10%, #9f0404), color-stop(65%, #b80505), color-stop(100%, #ba0505));background-image:-webkit-linear-gradient(top, #860303,#9f0404 10%,#b80505 65%,#ba0505);background-image:linear-gradient(top, #860303,#9f0404 10%,#b80505 65%,#ba0505)}.x-sheet,.x-sheet-action{padding:0.7em;border-top:1px solid #092e47;height:auto;background-image:none;background-color:rgba(3, 17, 26, 0.9);background-image:-webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, rgba(11,60,94,0.9)), color-stop(3%, rgba(6,31,49,0.9)), color-stop(100%, rgba(0,2,3,0.9)));background-image:-webkit-linear-gradient(top, rgba(11,60,94,0.9),rgba(6,31,49,0.9) 3%,rgba(0,2,3,0.9));background-image:linear-gradient(top, rgba(11,60,94,0.9),rgba(6,31,49,0.9) 3%,rgba(0,2,3,0.9));-webkit-border-radius:0;border-radius:0}.x-sheet-inner > .x-button,.x-sheet-action-inner > .x-button{margin-bottom:0.5em}.x-sheet-inner > .x-button:last-child,.x-sheet-action-inner > .x-button:last-child{margin-bottom:0}.x-sheet.x-picker{padding:0}.x-sheet.x-picker .x-sheet-inner{position:relative;background-color:#fff;-webkit-border-radius:0.4em;border-radius:0.4em;-webkit-background-clip:padding;background-clip:padding-box;overflow:hidden;margin:0.7em}.x-sheet.x-picker .x-sheet-inner:before,.x-sheet.x-picker .x-sheet-inner:after{z-index:1;content:"";position:absolute;width:100%;height:30%;top:0;left:0}.x-sheet.x-picker .x-sheet-inner:before{top:auto;-webkit-border-bottom-left-radius:0.4em;border-bottom-left-radius:0.4em;-webkit-border-bottom-right-radius:0.4em;border-bottom-right-radius:0.4em;bottom:0;background-image:-webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #ffffff), color-stop(100%, #bbbbbb));background-image:-webkit-linear-gradient(#ffffff,#bbbbbb);background-image:linear-gradient(#ffffff,#bbbbbb)}.x-sheet.x-picker .x-sheet-inner:after{-webkit-border-top-left-radius:0.4em;border-top-left-radius:0.4em;-webkit-border-top-right-radius:0.4em;border-top-right-radius:0.4em;background-image:-webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #bbbbbb), color-stop(100%, #ffffff));background-image:-webkit-linear-gradient(#bbbbbb,#ffffff);background-image:linear-gradient(#bbbbbb,#ffffff)}.x-sheet.x-picker .x-sheet-inner .x-picker-slot .x-body{border-left:1px solid #999999;border-right:1px solid #ACACAC}.x-sheet.x-picker .x-sheet-inner .x-picker-slot.x-first .x-body{border-left:0}.x-sheet.x-picker .x-sheet-inner .x-picker-slot.x-last .x-body{border-left:0;border-right:0}.x-picker-slot .x-scroll-view{z-index:2;position:relative;-webkit-box-shadow:rgba(0, 0, 0, 0.4) -1px 0 1px}.x-picker-slot .x-scroll-view:first-child{-webkit-box-shadow:none}.x-picker-mask{position:absolute;top:0;left:0;right:0;bottom:0;z-index:3;display:-webkit-box;display:box;-webkit-box-align:stretch;box-align:stretch;-webkit-box-orient:vertical;box-orient:vertical;-webkit-box-pack:center;box-pack:center;pointer-events:none}.x-picker-bar{border-top:0.12em solid #006bb6;border-bottom:0.12em solid #006bb6;height:2.5em;background-image:none;background-color:rgba(13, 148, 242, 0.3);background-image:-webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, rgba(85,180,246,0.3)), color-stop(3%, rgba(37,159,244,0.3)), color-stop(100%, rgba(11,133,218,0.3)));background-image:-webkit-linear-gradient(top, rgba(85,180,246,0.3),rgba(37,159,244,0.3) 3%,rgba(11,133,218,0.3));background-image:linear-gradient(top, rgba(85,180,246,0.3),rgba(37,159,244,0.3) 3%,rgba(11,133,218,0.3));-webkit-box-shadow:rgba(0, 0, 0, 0.2) 0 0.2em 0.2em}.x-use-titles .x-picker-bar{margin-top:1.5em}.x-picker-slot-title{height:1.5em;position:relative;z-index:2;background-image:none;background-color:#1295f1;background-image:-webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #5ab5f5), color-stop(3%, #2aa0f2), color-stop(100%, #0d87dc));background-image:-webkit-linear-gradient(top, #5ab5f5,#2aa0f2 3%,#0d87dc);background-image:linear-gradient(top, #5ab5f5,#2aa0f2 3%,#0d87dc);border-top:1px solid #1295f1;border-bottom:1px solid #095b94;-webkit-box-shadow:0px 0.1em 0.3em rgba(0, 0, 0, 0.3);padding:0.2em 1.02em}.x-picker-slot-title > div{font-weight:bold;font-size:0.8em;color:#113b59;text-shadow:rgba(255, 255, 255, 0.25) 0 0.08em 0}.x-picker-slot .x-dataview-inner{width:100%}.x-picker-slot .x-dataview-item{vertical-align:middle;height:2.5em;line-height:2.5em;font-weight:bold;padding:0 10px}.x-picker-slot .x-picker-item{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.x-picker-right{text-align:right}.x-picker-center{text-align:center}.x-picker-left{text-align:left}.x-tabbar.x-docked-top{border-bottom-width:.1em;border-bottom-style:solid;height:2.6em;padding:0 .8em}.x-tabbar.x-docked-top .x-tab{position:relative;padding:0.4em 0.8em;height:1.8em;-webkit-border-radius:0.9em;border-radius:0.9em}.x-tabbar.x-docked-top .x-button-label,.x-tabbar.x-docked-top .x-hasbadge .x-badge,.x-hasbadge .x-tabbar.x-docked-top .x-badge{font-size:.8em;line-height:1.2em;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased}.x-tabbar.x-docked-top .x-badge{font-size:.6em !important;top:-0.5em}.x-tabbar.x-docked-bottom{border-top-width:.1em;border-top-style:solid;height:3em;padding:0}.x-tabbar.x-docked-bottom .x-tab{-webkit-border-radius:0.25em;border-radius:0.25em;min-width:3.3em;position:relative;padding-top:.2em}.x-tabbar.x-docked-bottom .x-tab .x-button-icon{-webkit-mask-size:1.65em;width:1.65em;height:1.65em;display:block;margin:0 auto;position:relative}.x-tabbar.x-docked-bottom .x-tab .x-button-label,.x-tabbar.x-docked-bottom .x-tab .x-hasbadge .x-badge,.x-hasbadge .x-tabbar.x-docked-bottom .x-tab .x-badge{margin:0;padding:.1em 0 .2em 0;font-size:9px;line-height:12px;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased}.x-tab .x-button-icon.bookmarks,.x-button .x-button-icon.x-icon-mask.bookmarks{-webkit-mask-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAAHC0lEQVRoBe2aW4hVVRiAx8t4qXFMvGZGeLcblUVWdJEoiTIhI9KoHiIyKyh6SOvBh166vPTQQ2IXkKyIktIyLQzLUoMkSbKoVEwtK2+VZWrl9H3bs4Y1e/a5eDxzDsycHz7X2muv9f/r//+11p6zt91aWloaupJ070rO6mvd4c6e8XqGO3uGe5biYDck188y1LOGeuS3Hvs8AVrrWZ0LtUU27VbIbrCRlMVsluQwBptgHEyHS+BcGAxBDlLZCOvhY/gQ/oD/oFxxuw2Fy2AKTIIJ0AuUf2EbrIF18A7shcOQX0xCPhh1KsyEVWAES+U7+j4Co/PpLtTOOB2bA7uhVJu/0fdZmFRQd9ZNBvWB6+AjKNVgVr+vGX8fNEO3LFuhzftgRu+HrZClr5S2fYydC8Ohe9AfynbZpdPJ8CTsgSwDLiWXjcs4cIj6P3AUssYsoH0kZDptO4yHFZA13rYjoJ1g8+9cWz6bn3D/UmjjdDIBGhPhoOhL5WmYBY1J47F/gkGNfAEb4Ptjt5J9ehp19/XF4N7uDToRxL28Gu4m0mavVXKH02ganoGprTeOVXTG4Bp8HdgEv4L7WxsT4WoYlLvuQRmLc50Nn2NXHwhnbg9T9QDTWTMYR9nM7YTH4WzoDy55HQp4kPQDHX8AvgEzEuuxvhD6BZu5OZxO23JIZ8rxHkj3wDBoApMQbOq0q3E43AKr4U9I61lP25hgM3GYBpVMASMZT/IvrpdCwYMgKAsl/UfAc+CKiPUZPAPXI+esWZqf6mP//eD4gUFnsZK+JuEx2AGxTesvQHNiM2fYCfooiTsaYU+9IcWMZd1nnBl4Anw8xXpdkpPB+zMgvaJ09mHI3O9ZtuI2xt0EuyC2adZd2tpM9oKHVNzBTLwKJ8XKyqmjw1PXgybWv5LrK+CrVPsBrm8rx048Bh3T4KeUbgM9CZI9kI7Il7SPjZWUW0ePS+098OAKTptF92ccCIP8FPQs11YYhw4zOQ888IJNy9eh4cZUo0tsdhhciRJ90+GXlJ14ItYN8qhK2FMH0gye7LGdI0aiF8RipN+IGypQfxcdnxXQo81lTHRrgT7HdQtdnh2LUoMadTgJR3TDa5daxQTjHoBvgqd+lvjYW5Z14wTb2vmRnFoZSn1MVVqWoNBHRloMsEtvXfpGBa7b+ZHP4QrYaqsit8QWt21Nrn7n35e576Ojw6VqDuc8WUuZdsy95oldFam2w+7ltBwlu/5FVhWptsPt9lRVvIyMVNvhyHRtqnWHaxP36lmtZ7h6sa6NpXqGaxP36lmtZ7h6sa6NpXqGaxP36lntchn25XtJkvtC0JfOvhLyxVz8Q8Af8f4SksP8+vGVTUUk9zVEm841/TrKn5q+qNNmSb+4ijqMwQEoHA5nwjlwBoyHeHX4RnI7+PbzW8b4iWMHk/iZ8riF8QZUm+PgPBgDg8EvELEc4sL3YNsYs4FyC+zCrm9FMyWfw4dQ0MSIa+F6uAb6gxH2c0c60jQl35XMrFl2Ip+iYznlKibgpIoK/Z3PRXADTIFRoPPa9F4PiMWV5Qcz7WrTd2YfoOctSl8ZOZd24itUBwZcGnfB27AbVOLSCfdLLZ3APlgLD0JvmAzx+2l1bSEgFMmHsYWUm8G3IOkvEqXadb6+dPcD+SuQHpe8M44bde5HcMJxe1y3T0AHCgXE6DsBjT8EaUd20nYnuA0MdiFd3tNeMZvO1b3tx7V43i0ePGY4/XLNTvGhxGWDX9j3ghnbAlvBfhofASPB5egydN93h1gMoJkbEjdSNwDqHQTpJWsAfMm3AQyIifDaubmtxsBYuBAc3wwFxX2RJbGzLmv3w4uwHpy4WZMg6hH323i4AybDaAjiPUmL44amGn2fvBH8ILAEDJQZMzhmWXGOjTk8b66EaXA5DIO8YobbpD26XkHdyRu9Xu61YtBPB8ywE1gE+yGf/qz2TfR/FAxWUzF74T59DeZAmAFrIEu3be32sI1Ocg64RMr6uMU4l7TP7anwA+SbQGg3c/NhApQU3OBsXDLWgJvhueAqDPpD2c5h9+pM6BMrKreOHidwFbgHg9F0qbMvgSuprO/C6fmhx6fCLNgDsb02Duvs7dCYVnAi1+jzMDofXK6x8VB/nvZTTsRG1lh0erDNBvd/sNXqsI33QkWdDRNBr0vc88KgBuOWK2Fw6FfpEt06vQB8mmiv4eZc5X3KAZU2GOtDv8t7HriENe7z+YK4T0fUsXEW+GhLHL6VymaY2BHG0jqx0w9eA4273Nr8P6p0/0pcawOmwEEj7jNvPoo9VDpcsHOAv3VdYp7gS7k22x0qORv+jb3Yh/co2E+jj6KqCIZ93PnM3I5d91ZVBLtjdVj8gyJZ39WwjOHEZi3stvmvh9VwttY23MxdSuoOd/Z01zPc2TP8PxKYOEKWmL1pAAAAAElFTkSuQmCC')}.x-tab .x-button-icon.download,.x-button .x-button-icon.x-icon-mask.download{-webkit-mask-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAAGb0lEQVRoBd2aX4gVVRzH3V1dU5JMk9Q2wVxCo0QNTYRYS4l6CBFBomA1qjcjSOgPPUgR0VNBFBT0Bx96qAiSXipCH4rKIhGNUqE2SK3MqKwsLbXPZ7rnMo73jnPnzF6v9wefPefMnPP7/b7z58yZudtz6tSpMaNlPT09E/DdDxPhMpgNJyBtfTRG4AAchePk9BflqFhP1YIRqbCZsACWwjWwGIrYZ3TaDZ/ATjhIfh6IyqwywQhdRlaLYBVcB5Mgxn5n8HbYAjsQ/lGMs/pYz3AMOFLgG/AzeH+MBvo2xqqYXB1bSiyBe2EJvAaH4SSMhtC0T2MYy5jG7i0jvmXBBJoMj4D3VjuEpkVbN6axzWFyq6JbEkyAhfAqOJtmE2l32xzMZWErogsLxvE62As+Vtotrlk8czGndUVFFxKMw41wEM7FJdxMbNhuTua2sYjoXME4cVHwEDhZhACdWpqjufblCW8qmIHOxHfCT9CpIrN5mas5N53B8wS7kPgKOumezQrMts3VnJc1O8sNV1qsmq5k0LNwI3hZx9ovONgEPk4amcvRR+HiRjtb3KborbAB0fvOGJs9EnRwwf88HIHsESzbVuisbKzQdh/Yp6z/7DhzV8OEECOU3qd148z20FgDK+DC+o74in59Y2pm7rNPVWbualhT01T3e5pgts6D9eARrzIB3LXVzF0N60FNdasL5kj0sXUtzIf+eo/zt6IGtaytaUuU1AXTugKuhyomjsR5B/xRi5rUllgimCMwltYQzAHr3WJqUdNQTWOyuFDcpbASptnoMlOT2tQ4phfl3uBzwes9byZl93lpalLbXLV6SXtzr4BuPLvISkxtauxX8DjwW5Qv9t1qalPjOAX7vJoB3TRZIec0U5saZyl4ELr57CIvMTUOKngAqlxGJt478I8aBxQ8Hbpxds4eczVOV/BUuCC7twvbapyq4Ha8JPQVOIBF+hRwk9slWVLm9miy8xjbj0PRA/YHfU828eVm99mnyFziu6/9XT+Mh5as7KPIoE/BB/BPgYgeoP05/dx3OxQR4LrBF4IHoWUrK9j7wZeNzXxJGGk5amYAPvyovj2zuWGT1eEcdjwOpeYdL8mytpyBr5BAW5akroOxy4n5MiyFUqZg78W8+yvPsZfWEyQy3WzyOsbsq/n2Q9+TYMwypsbjCj4EXlJlzPHDcD/48W+0TN8PgF9kyh5YNR4y4e/AGbKsOVveC8OcCSeUSg2fir0H7oayc445qVGtY5bBHnDmjeFXxt8GY8Mn0dhSX+Ds/RvE5OZYNao1eQ/+kNJrPNapoocg9/edIgdCH3AL6DM2L7WpcZqXtKd6L/wJsXYRDl6ABVyK+i5ltbGLGfw06DPW1KbG5NY1MS+bbyD2SIbxO/G1HFo+046BG+ALCP5iS7WpsTf5MY3KPPgYTkCs8zD+XXzNLHL5hj70dwb2WbsNgp/YUk1qm2ecINh/MXoMfoTYAGG8gV6ES4Kgs5X2hZegivkk5KEmtU2qC04q/082u9gROlZRmvgmSH6lzBNMHx9pJlZF3LQPNQ2F2PXfh9noEvF18AGdHhBb/xd/d4SAzUr63AX2jY2XHq8WNU0LceuC3YCtBiecqgP7HF0XgmZL9m2AI5BONrauBrWsTsfLCnbV9AxU8ezLJnwAv2vSwa27DX6AbP/YthrU0p+OeZrgWgLO2FvB99zYoNnx+/B5dUiA+kL4FrL9YtvmroZkZg7xEn3pRqjTcRhGIDZwo/E+rpyNZ4D1Rn1it43gdzjoSZdnnGF3Yq5h74Oq76sg5D18b4PQrrI0Z3NvuKZvKLgmegqDNkPVs3aV4rK+zNWcp6TParreVHBN9ACDt8DfkHXeaW1zNNeBtMBsPVdwTfQgTt6CThZtbuY4mBWYbZ9VcEr0mx0qWrHmdlaxiZbsEWjWxuFkeBhcm7pkPNeXtDmYizkV/r/pQmc4HAQc+934ZtgBVa/GWjmAxjYHcxkf8itStiQ4OCTIbHgO9kM7z7axjGns2SGfVspSgkMAgq4EZ0b/i3U0hevbGMZaGeKXKRv+cylOCxufY/xCcS3cCl5ii6AXqjCFeum+A2/D54j0Pbu0RQsOkRHu+6zP7avgJvDsz4VWxStyD7wPrsi+hP0ILfIbFl3zrTLB6TCId3KbCK6X58MSmAOuocW69jUcrmH9U9gF38NRRB6jrNT+AwkLDdxcvfCRAAAAAElFTkSuQmCC')}.x-tab .x-button-icon.favorites,.x-button .x-button-icon.x-icon-mask.favorites{-webkit-mask-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAAFfUlEQVRoBd2aXahVRRTHz/Ujv+2mZRGZB7W6mtpFikC7+UWUZiqBD0JPFdRL1EMFPfjoU4baS0FUD/UWZBEVShA+BCpmWApRSkgllNpDmZWZt9//eOay72afvWfWOTPn3rvgz8yeWbPW+s/XmT379AwODtZSSQ+CryVgA/gVfIx/pelEhFMBVlvBOaBeFo6Cean8y09KsnMg932TqCOs9M2UhMfhMJVsxtHcAmcbmekLCsqjFKUkvAYG1xSwmEHZqoLyKEVJCDOCNxH9HUCbVl6mULAuXxjrOQlhgl8Bbi0h0Uen3FBS37GqVIQHiHh2SdR16jTlo0t0woycpuxiUDSdHcFeMv3uIWYanTDB3wIWVZBQHP10zuQKvbarUxDWT1HRz1E++Ds99fLtgp6jEmbExhPNcs+IbkZPiCpRCRP5TPCQJ4MJ6A3QSUqjSWzC2ozuC4j+fnSnB+gHq8YmvJKIJgVEpRPX9QH6waqxCa8PjEhHT981H2j6qno0wqzF63BhOUxsom3Zb7aJqGsUjTAONFJlpysXQz7VuXpavrBTzzEJaz1adlzNjHs6RTBvJyZhjZTF/kTaWZZCnlvhsyWgQkPZQpagzsX1bFlAXjGtDdAPUu1p3PPQhCCXkdwG/mta0PWLds060AuAnqtEOjpdbQR3VymX1P9F3UfgGJA9X9F92c/ADaQ2P8V0DJ4/kDbeYKaSvgI2AN0+OGJK1VAbSIhTOXEOybYll2kte77yD4rqrHyb85S9Cl4HtReAyI11/A7HpRq5PSD6oR0f3Rad+H7S1DvV7UgS+tc1cU3n3V/AWJ/SX8BxVuMinow2rNNjlPQVeH0GFg378kDBfLAPXARjZbTPwmUXmOG+bgz71EKFfqKeAUWfREZbJxyCxyOOqEuHER4qrNUWovwy0CFktBHV4eNZMNvxyaaFhKWAaBt/HJwEo4W0luSKLMF8viVhp4iBeeBd8CcYqcQ1qi+CKS7uVmklYdcQY0+C42Ckkf6EmO51cVal3oRlCFkCdKgfCWtbo7obDO3AVWQbHHyUsjo40E6uq9cvQbdG+wN892fj8s0HjXDWKA51/t4JUo72H/jTDtybjSUkbyYsJ0gdfAtSjfTn+JoWQjCv2+57a4M1QaQSvZvrMsIs7RJejGcdUlLJUhzpZsYsZsJcCen6ZwCE3IaYA2021OfUdU3fJltmwni7Fvh+KDMF16KR3ux0lWuSdgjPxeNdJq/tNdKNqJaSSUyEmVK6JNPomtqbIh3eSKNsEmvAarfJ5LEzjbbR59MtpqyEb8eZjpndkhtxvNri3Er4YZxpx+yW6Jdhi8V5MOHm+n0QZ9afo0u0fQO8A5S3iPaQ1cTSG9w4f/SqesZBH/gRWI6T+gyyxfkgvw2cMdrS+/lTzpZvGnyWxsnTwHLRd4R2a/OBqQyoztKBe/P2qp6DCBOUptKHhuA+pU1fq2Co0/F0L9CVaghxXTbWW9ktKg8lrFfCrwODeh/9wgu1bEDo6OT2Fvgb+JLWq+nQEsnaa5UPJbwKBxc8A9KXPG1O3u+u6E4F24GvD3XMDjCxFcF8uTdhjGpHfwn49L42lCeAdyDZwGi3HpwAPr6+Q29htn1ZPoSwfuz3ewShXVcBNz62lzkvq6O9DjZHgQ9p72kdQljvob9VBPAN9Q+UEQmpw5b+Sf8e0FotI/4a9ZN8bIcQXlnh9AD1y3ychuhgU0tpJyhb14epn+ljN+Sk9S9G1ct50d8SdgF9x9EO3lHB5hXwPEYfA8dbGD9LuWZBtfj0inSQWUDTKzu1dAB5Dkz2tdOOHn70LvwVyMag/FYwzse295Rukq5j+G1wEOib66PAy5FPMD46+NPmqTV7CpwGGvkJPm2l8z8GWDNDloqpGQAAAABJRU5ErkJggg==')}.x-tab .x-button-icon.info,.x-button .x-button-icon.x-icon-mask.info{-webkit-mask-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAAHOElEQVRoBdWbXYgVZRjHXdf8ysjUQl011lbRIFEjM6Uu0iyiEDG86EItKoIuuhDJCgoioouugqKbgi4CKwulILG0mxLTUtMyTWQNPzLTPszU1cx+v+OZw9nZM3POmZl3zQd+zMz7zvs8z//MvF+z2nLhwoU+oaylpWUQvvvDYGiDdjgP1dbKRSccglNwlpxOcwxiLUULRqTCRsNUmAk3wS3QiG3hpp2wCbbDYfLzhyjMChOM0FlkNR3mw61wFeSxv2j8FayBrQjfmMdZpa1POA84UuD7cBzsHyHQtzHm58nVtpnEErgvzIB34Rj8CyGEVvs0hrGMaey+WcQ3LZhAQ+FZsG/1htBq0Z4b09jmMLRZ0U0JJsA0eAccTeOJ9Pa1OZjLtGZENywYx0tgDzit9La4pHjmYk5LGhXdkGAcLoPDcCle4SSxUbk5mduyRkSnCsaJi4IV4GARBSj6eALfR8sxunLEMUdzbU0TniiYho7ED8GvULRI/UV9cDbnrsauheXQCVnjmas5J47gaYJdSPwAIfqsPlfEnwRl/eBBOAlZROvXnGfFfUfXNQXTYCKsg38gS+B6bT6MEogfiTcKNuaIa87mPjHu2+segrnRBf8bYN+ql3jW+ntrJVNK6OJGw+VkVt+2M3c1DIrHsZ9WjPVwCxcLYQ4MqVQUf/Jjikt3VnnX4eauhoVlTZVw3QRTOhmWwjhQfCi7ppZjkjOf62FCrfomysxdDUtBTRWrCCZYK6WLYAo4aoa0JxKcu2x9CsYk1DdTrAa1LCpru9g2ese58lddD+cgT/9ppK2j8ONR7HLf9Um8B0XOCmpR04QoVmnQosDp4BHYD40kXMQ9zsPfgSI/hyNQhN+4j/34VVu/0g9b/nXbKFgJf0O8weV+rSa1tam1b3kUm0SB77sj5KUw18OhTE1qm6RWBy07t0O4S7veto8J6FLwbng+YHC1qbE0GDtnrYXeGKzsHj7NT2AejKgMJn36DODaASZEF1KbGof4hJ2vXM45cIW2nwjwKDyA0HXgDicyl4RpC5LovixHtalxnCcd4PwX0hTjcvEFRO5ICBRyoWNINXYo2Ek+5DJyP/6fgZWI9XVNs3r1aW3r1alxjIJHQqjR+Vt8L0fnpxzrmU+45pKzXsMG69U4UsHDYWCDjRq9zYFpCzwGLi5K5qyA+KQpSMHt5VtDHNQ4XMEh+s5R/L4CuxSIUKeDO8BX1pG4lrlDmlqrosCy0jxcoL+KK5PvgFbEOka8CKsgbRd0u/dDUPMJh7ArcXon/A4PwwxwyvkKkuwuKi5bwYqaDbdBNAP8wvn3kGQ+4RDdq1u8UE/YINUjv313L/35bLfo5Qte+xs5va5WXdFlrrRMImnkLCreaRxtSnE2i7q8n3VS3Jeq1HhWwY6o7k1Dmn/r3ZgSYCZ1g1Lqi6hS41EFHwC/QIQ0P5D7vbiH8Tq7DnD7Frr/qvGAgvfBnxDSNqcsOJx7Xe2FNjXuU/BeOAah1rHn8f0FJJkDlk85pKlNjXsV7KPeA34KCWUuM5OsN760qE2NJxXcBevBfhbCOnFqsB5G/72aQj8vVVuIN01tauyKFvPbuHBhEGJ6+hK/SSLaqBsPmrFfhZe9KND0q7ZtjiM+Ye0guIXzPS/atuPQflzLxlI4Go6AOys/wq+Gn6EoU5Pa1Fj6G7Dfpp0nfeT+EkXaOZx9jf+kJ+xqbAPcxy1vwhnOd8MuKMrUtB7fauz2HcsgBuuAQVCEHcLJ8RRHrr42kExpWqRPu3mYDTektGmmyhVe9x+QYJU/mVK5AHwF/QblU8nLWnyMrY6Rds69T4Kvd964tleDWhZUx6yItRBzo+7A8QcUEXQVfkZVB6x1zj3GfQ587YqIqw81qKV/dcxugsuiJ3OT/cr+lzf4S/gYXB0wfk69HwX8YRxN88aL2pu7Gib3iBcv8BpbDJ0QOch6fB0fNf+1HOVXwD2wE7L6T2rXic/FNbXVLLw4mNmfTuRMZi/tx8djUDYHPgAHlaSks5abs7mX/lrYI3a8ILqmwTB4G9xWZQ1uu7egHQbC/aBQR+88PpPamqs5D4t0xI89+nD1DTT0A9waOANJQeqVu+j4Ddx3u26vd3/WenM01zHVGuLnqYK9GXNeXg15RGcV0Wg7czPHjrjA+HVdwVWifRX/j6LNydzqii1pif8CSdc4HApPg0u1IqeQRp9i/D5zMBdzqjkT1NLS0BOOGuLYv+E6lWyFolZjcSGNXBvbHMxlQJRfI8emBEcOCeKo+xq4A+nNp20sYxq7PcqnmWMmwVEAgs4FR0Y32CGF69sYxpobxc9yzP3feMo7nJtJxDnWV2w6RPtsTnOZQn1118JH8A0ik/bWVNe33IKjEAh3qei87Ue5eeDTnwTNilfkbvgM1oHb1oMIdX2c2woTXJ0J4h3c3NyPgikwA9zjjigT7Xf3ce0XCfF8M+wAv3icQmQXx0LtP/qKurS9uZqyAAAAAElFTkSuQmCC')}.x-tab .x-button-icon.more,.x-button .x-button-icon.x-icon-mask.more{-webkit-mask-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAADJ0lEQVRoBe2YS2sUQRSFp5MgvmLU+CAMiBJFDBHcCeoPEFciuHMjroMK4lZBcONG0JW60U1UEgRx59IXuNMoKEElKL7GRwyIqNHxO0N66FT3UNU9IHRNFXz0VNW5t+vW6RcT1ev1Sie1rk4qVrWGgn13PDgcHPZsB8Il7ZmhqXKCw6kt8WwgOOyZoalygsOpLfFsIDjsmaGpcoLDqS3xbCA47JmhqXKCw6kt8Wyg6XAURV2wEy7BM5iFtzAKu2BB0dqJ7YEtcBYmQblfwzjshUVt5O4mfhjOwwQodw3GYA8snpd77n9pFXMYvoP+qDaZZewcVKXPAzE64Qn4CmZe9f/AFSiSu4e4IzANrXJfZ24gXjO/KxEcg9+QFZQcU/CSONh2RKsraMQhr85xE/psOeN5tCr2APyA5Bqzfl9D06tYtX3wC7KE5pg2ZX98UtsR7XZo5ayZW/1DENnyzi18CO1nyMqTNXYcrTapcitHkBLJiZW2RaGRuxcg6+Stxu6i73fI3Y3uZM7cU+hXQeVvzsBP6Dc5LupxztzaiEGH3AvR3S+Qe4dc0D2cp/Uj1oPI1pR7g030n+erWlTe9pMA3cu2Jre+2ERtzBdZe01BL3Ke9Al6vQZsTbfKQ5vImH9PXxtqa3qVPbWJjHk94J6r4DPGhK17A8EHm4j7UAWP2nTG/GX6NWMs1SW3rrCroLeLaxtDqDdG4368zbHVkzM5Polus+2hEs+j7YNxx9zv0FkfhoncvegvOuZ+iW6rYhtfTXTWgV7OyeLM3w+Y3xaf0PVIzAqwFf0IzW7XnLGOmLUg58y1JvsTzA83Y5o/eLcyMQISJAN0z56G9bE275HYNXAU7kAy9xv6p2Bj3pyxntjVcBDuQTL3FH19Dg/FWh0bXzUMNhsf23JkOQzCK9B1P4NY39OFG3kjgpeB8g/AR/gG0+3mJkeF9Lp9lkIVZkDfC1r3vPs8VTAir1uRd1mpNyQUXGr7HBYfHHbYpFJLgsOlts9h8cFhh00qtSQ4XGr7HBYfHHbYpFJLgsOlts9h8cFhh00qtSQ4XGr7HBYfHHbYpFJLOs7hf5j4Vg3iLoGkAAAAAElFTkSuQmCC')}.x-tab .x-button-icon.time,.x-button .x-button-icon.x-icon-mask.time{-webkit-mask-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAAIPElEQVRoBdWae4gVVRzH97qr66vyhWbmurY+MA111dRMkLIXRuhG/pMVSUKGBGYPMTLDR0iaJBFUlIp/FJJlpWJS6vrAlCwTe1iaippSZipmPjL7fC/3XGbnzjkzc3fudTvwYWbO73d+jzlnzjkz96YuX75cUqiSSqWaYVs0hvZQBY3AW/7gYg/8A+fgPDFd5FiQkko6YZJUYj2hNwyDAXADlIOrHEO4A3bDVvgZ9hLfBY6JlUQSJkn14CAYAiNgFPh7kqpY5SDay2EjbCfxo7Fa25TVw/UBuw/BWvgT9HwUgl3YnQXX1ydWtc0rWRyr9zRcV8FpKESSfpuX8LMXnoDm+SYeO2GcXQfz4Cz4gyrGtSa3TaDHp1HcxGMljIN+sAGKkViYj+PEMRkax0k6csIYfgoOQVggxZa/R0ydoiYdaZZmFp6C0ZmgNTVu0YSzBQ6A1tuTYEqKk5ugA/SFkdAU4pbVNHiYpLWmu4vrztBSy83TcAai9pyeba2lz0E1tIFysD5vyMrgKugIY0GToW5MVJ/SWwltXPlIZh3SNNbdV9B/QRTH59GrhQehSZhjl5z2pucXc/4rRPEvHfV0B6dtm5CGI+B3iOLse/SehVgTiM23tx6bGuafwb8QJRY909ZlK7CHadATtOZFcfAmel28QSZ9jn0914/AYQiLScvW45Cen/yx5CSMYhNYA2GGtdGfDS38Rm3X6GpO0PNsKLPpBtXTbij8BGGxaWQODrThr0RxEuguuYzqeZ0Opf72tmt09TKxHU57+JLz7rY2QfXo3wpRkt6MXs7QrtPDKHSDfeBKVpPYjKBgXHW0mQVBz+HzrnZBMuwo6b3gilNb0Yn+9v6E30UpKCiv4WnoBD4ffuPea9q8YrE91asX9Rxb2loeBG9s/nO9YlZ6bWZf4dhc9EB4B2hJsBXtYd/AgAzHLfm0cfnYhvBlUE/aSlcE473CdMIkqyTvhU5eoe9cE8E8cvXulHwqxbvM3PRFeFzn8FqKbDTpdTQ6pof1BlQDtt5V7yzDySemYUM4Eo8mz4WgFwlb0RJbbYQm4e5U6JmwFe125tiEV7KepLWlFJp7goqW2WH0spbEkkacqOJ+UPfbylIMK+mGWl4lsLOO4DR69Tynv1y04DhSF5aiDcY7FllDqdbLSq0jmB7IKiXXkNYDrXFuK+sRHLMJG0I9o09zzEeOWDQ3DWI0lyphPbuqsJU1CFzDxdau2PVfhMSpiaupEh7uiEyJfsUNtE0IjqZFF2mmdi1R+j6eTriLI7T9yLT+/h/KBYLUHttWtPSWqYevtWlQfxjOOORJiJIaPRcJ5pAjIC1LnZVwL4fSEWSFTvhqh//IoszEtSekQYUSdpUTCLUsFbI8wOw5HvRNq75Fb3LOEpawa/Z2Gg4Q2mxpjdQ6v4KkBwa0i1Nl85G1EZZwVjGBE/Mx0GbqNgQfkvQECA3cZiSkPqWEtQG3lQoEiTxj2FkCW8E1SXVG/josJecqjnGLNlGuck4Jf+PQaIcsn4/vOSaZVLTE3Q0LwLVz095en3rXknQNlHMeWtBTLl1DFHdIri2ZtmZBaFnqo51bkmBT79660UE+vXV6DOZCVZh/dJrDUvC2956fRtYeSmaAV+A/vy/MWT5yfGr4PQNa9vw+/df6VDMRrB8NkWk0/gL+tuZ6G7JroOQeh5KU50Csz6lRbwB2NQyHwhYI+1Kqbe770D7IPvXaOmp+MAn6j5pDmkH6hywZ8yuY653I2gY5SaoO+y1hKujHMOPXdnwJnZwOoG52SNsJildFzlaCzYHqRyWVnMsOfsaAetsVyzTkdX674lrP7z5HO80F/U3CGlb6G4HLSS3ynLvqCj5fGX5ag37o/g38MX1HXc6Qzui7HolPTbv07MtFPzgKfgfm+m9kY/JNIp92+BsCmmhMDJrcJvltUaeXn689ekbfe3wSefrnWpOw9rHa3nmV/OebkLf2OyzkNf606XkNDsLbkPPrJHUa4hfAH6+51kipNnFm11cqtTa6Gko20zRsCEfiuREOgEku6LgKeXY58yasRTlsaGgjkr1bVzJp4tDHx8UQlKSp0+ozzhtnNmFVUh6DsI3At+hUeo0U+xz/KVgIJjHbcTU6dR4Df8Lat34cwdAGdDoWO9FMp5Tiezq4Hj/dAHVceinyxlkn4YxB7ViibADWo1fUnsafOmQW6KOErVdN/Yvo5PzKmZNwJmmtg6ah66gXgAHeO1ioc/y0g7kR49qIXqugWGwJl9EgyjOim6GJbCaE/mUoKIAoddgeDdvBdfONTDuuXja7gQlLmdIKwrZ5xol2ObqrYyC7BNicRq3HVm9YBPpUbHy5jifQe9Rl35pwJunBGNgV0ZkC0Z5V29BR0AHKXc79MvS1zdVmoy/Mg+PgStAr0yQ1BZw3PP1Qo2QtfEnQJLYY+liVggVHqF4O60DDXjsezax6ETf7Xo0iTUQ6toZb4Ha4E+IUbX1f4AbOD2sUmrAMkLR6egHo3TWfcopGO0G9oG2ieR2t4lw92g0qIZ+iz0XzSVYjIrz4h5XtGkvqgagTmXeoFfJcb0+B/8ey5mETBNVjvClMhjjPViES1s8qy6AiKE5XnXPSCmqIE23rBsIK0PNYiIRcNn/E53jI6/08dsLem4DTcbADdMddQSYh0we6t6BeW9pIkxZOrIUJrS3Cm6EG7gJ9TE+qaFbXLP8BbOZm76mv4XonbAIg8ZacV0B/GAvDQRNdPkVfOvQe+znsJ1HXh/tY9hNL2OuV5PWu2hyqQZsIra/6FCO6gClapn6AU7AbtDfXxuUknCHRSxwTLf8Bgi31NJnvpzwAAAAASUVORK5CYII=')}.x-tab .x-button-icon.user,.x-button .x-button-icon.x-icon-mask.user{-webkit-mask-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAAEWElEQVRoBe2aS0gVYRiGO1lmF8nQQlETutGFokAiqEV0ISKwgmrdMtzUpnW7drWKbFGbQAKpJIhuUGIUFUkW0T1Jq4V2U4ui7GLPexpDD+ecuX1jHqcPHseZ+f9vvnf++e8n0d/fPyZONjZOYqU1doLHRV3CiURCz5gMxTANJsJg+8XJJ+iBt9BHNdO1SCwRZR1GbAFRl8F8WAFLoRwGLME/ffAM7kETvIYPxPWDo7lFIhiheURaCVtgBywHXXOzbhJcggZoRvR7twy+76uELSEAtQsqySPwGdQN+KWDPHuh2DI2+TIVm3T455M9G0Bk6ktRvd4NBZaiTQUT3AQnSNW/VAFBzl/iZw0kq56FcOtuaQHB7QIv9ZVkrqZ2YA9Mck3pMYGZYKeh2sBz1SJb2mqcmfk0E0xQ6l9rwNoKcWjm11JwEYFVW6t1/K218mspeB5B5VsFluKnIuU88Kml4PGBo3DPqBGZiVkKNgvKRFkGJ5aCv2Z4xoi6bCm4DWUaXERhZhMJS8FfolDq+DSbRFgKjrIOa8poYpaCTQKK2sl/wSHfcFSNlll1sSzhn7ys3pAvLFP275lu+L1uKVhBPfYbgMf0zz2mc01mKfgbT7vi+kT/CeT3sv9s6XNYCtbg4CJ0pX9U4Kv3yXk3cO6UjGaCWX5Rg/UArqY8I8yp1qdPQ08YJ4Pzmgl2nCqwc2DVyKjunuddqkE0MVPBBKYSuQ7tJtEhFj9apDczU8FOVB0ctZiuHYUw9obMjbxErW2bmblgApTQengVIkq1B83QEsJH2qzmgp2n3ObYCEGndZ3krbcuXcUWiWACldCjoA0yv6a8J6HJb0Yv6SMRrAcj+gmHA+B3aneDPHXk/8jR3LR3a2rOfnAlTmfDVPDb6Khrq8bPDI5PoRPxZpMSk+1SgtOKpTa8l8BC0JaLmAkloA1xr/aOhJqEtINGWeqW7jjHXrQHbRdw4WxSJf8L8Aeh2m1QaWoBfiUsA61PTwGtUYeZ1qlP1zhan3YraBSnz/0mdAUVHqiEESoxKs0a2AxloJIMI5DsWU0vQH2z2oZToAnFI7+fu2/BiF3PgzbCKqgC1bXhNH3S6rba4BocR7TquifzLBih5XjcCSrROaAGKbJWHt9uJuGq67fgAki4zrNaVsGIzCP3dNgE20B1VJ+uro8UUz3Xr39UvxugCeEZl3UzCkZsBZn1+W6HRaB6qtZ4pJp2PtTna+58DFoR3sVxqHFxyM8euFsIW6EeXoDeoPrBXEEbAlpqqoN1kD9YY6rYxSQ4DGoE9KOSXBGZLk4NYB7CfigZEP1XMBfVEJ0BJUznIFevaSBzEEolOimYkyo4AfocclVYtrjViB0C9SzJEdE+jrn+CWcTrHvdUKuRUSm0gPrZ0W7tGjjMhTiIVWFWSbAGEnGxhAT/y+HhsL9oiVWFjo3FqnRVqrETrG5pFmiSEAuTYC3TFMVCLSIzTg9H6wuIXR2OneDfMJq1NmzzbS8AAAAASUVORK5CYII=')}.x-tab .x-button-icon.team,.x-button .x-button-icon.x-icon-mask.team{-webkit-mask-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAAFI0lEQVRoBe2ZSYgdVRSG+yUmnagRQYU4NbZKNLYKWTgg4gQOaDYqJIIGl4LixhBwoy50LSIiulEjCkpAUBBRURpdGceFMQ7YtgkOJE4xTjGa9vuedUl1Vd2qevSrFqvrwJ97695zzj3/PXd6nd7MzMzIQpJFC4msXDvCbc94l+Euwy2bgW5JtyyhOTpdhnNT0rKGLsMtS2iOTpfh3JS0rOGQ+eLT6/VWMNYJ4NjUmN9T/xLs4WfqvPxO7TU9DkTdNmvBbeAskJ7kv/n+AjwKXiSW7yibFQk3BSIPZHdTl5xZzML238DDYFlTsQS/jZF1AGQ1mAZZkkXfe9FbGwJrqmz6lL4cEmOgjhyO0jq2gGVj0hhhAl9M1FeB3gDRn4Pu/5NwQnJ0ALKqrgKHDmgzkHpjGR4oioPKP1H96+Dn8GvpKyLqneV5Lp0XgnHggTMFJjlYPqAcpnyLsz/LHBLL0fRfCzwbvNN3gLeI5WXKaik7DbF2/20A28HPYF+CPZQfg9tj9vS5h18DRSdyrO0j9FeW+PQenwTe138AJ+d34OPFa215zDa0l15LOLgamM0DIBukbQ60JjhLl7RL+HWQtSv7jhLGz1FgM3DJZ30Yy69gYzqGonrVHr4eJ+OgB7Ji2xi4lGUW8+PsD0vOwNGNwInMirF42K0nlmXZzvR3LNARDN3fx6WVI3VJF50Fzvr7EZtY8zQdLtUiOYXGIrJpXUmvTDdk61HCKEqiagD9SSwnLCeX3RYwSJafRd/zoUj2FzVm2hyzMJ6gV0Y46Myl/BzjeqfnyMg36G5NJqpoTPvnLGWEnS0f9lVStL/7NgT/C5XNoHTW6XesV4En/1wlGo+Oo4QJ1ivoxxqju+fKCG2lf1uFH7P3eEl2K8xndRt3VKKEE4sPKWOHiCreg28TaPR1RN/X6GwEO0GReJ3cg95kUWeqzT8W6KtMpujcVaZQRfgFjL8qcbCDvndi/Zz0h4Hr6L8JHBHRW0L7DejdAU6K6Nj8CfBQi4mH4xYmrmy1sXlK/gCAAyfkQaAT91kWj9HW/6tJ8MO3NmeC+4CHlqdu1q7o25Xk5Hqynw+WBp+hpO1K4JItsnfr5GyCbSirCHstnQpcKulBXMK+o1frCPGgWAomwL2gLsm0z3S9ny38XARWgEXJOI7xNMiS9ns9MN5ZCQhEQ1lIGCOXmZf4ZeAW8C4IAblv3wBXAIn6sjkZ3Arc80FvGKW/nu4H/nhZDiR0IngI+LYPY3i43gWuAeNgFBQSn0UYJZejRH3CPQ8cMDi19Jp6AviuVfd48ADwRZXWG3Z9J/6fApeAJUm2TYRE02OZjPfA3WAM9HVDdvt2iXHI1HkoPQd2g7SjUHef+NyU7AXgFRD65qOcZrybQXgFmtUDIDu2xE3CBuCWWBxIU+8vk9MozdQukDUO3x4qm5IJOp36ZyW6waaJci/jrkviWEV9qiQOdd8Ebr/+T0fKkYvBp6AqOB2fnQz0SA39Kn9z6Z9mfPeze/UlUOXrB3Q2AW36a77KwP7tYCwh7Mupjk1TOmZuNInlyZqxuN8n3ItrQF1xryvRl9W/3Y3/60QGCTGF71h5JB0Tbn7vsDqyP6Vkva5dymxoVQ+lIE6+3+lJCH3Zcp+E78y2Fny7Evw7kstC8YA7BtQZRP1hiwTDKnuGun8aSiekaDxXwrbG/zOtaOT/ss3MLSjpCLc93V2Guwy3bAa6Jd2yhObodBnOTUnLGroMtyyhOTpdhnNT0rKGfwD3f6JVZi/xSQAAAABJRU5ErkJggg==')}.x-tabbar-light{background-image:none;background-color:#2583c4;background-image:-webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #56a7df), color-stop(3%, #2b91d7), color-stop(100%, #2175af));background-image:-webkit-linear-gradient(top, #56a7df,#2b91d7 3%,#2175af);background-image:linear-gradient(top, #56a7df,#2b91d7 3%,#2175af);border-top-color:#2175af;border-bottom-color:#195884}.x-tabbar-light .x-tab{color:#c1dff4}.x-tabbar-light .x-tab-active{color:white;border-bottom:1px solid #278bd1}.x-tabbar-light .x-tab-pressed{color:white}.x-tabbar-light.x-docked-bottom .x-tab{text-shadow:rgba(0, 0, 0, 0.5) 0 -0.08em 0}.x-tabbar-light.x-docked-bottom .x-tab .x-button-icon{background-image:none;background-color:#6cb2e3;background-image:-webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #acd4f0), color-stop(3%, #81bde7), color-stop(100%, #56a7df));background-image:-webkit-linear-gradient(top, #acd4f0,#81bde7 3%,#56a7df);background-image:linear-gradient(top, #acd4f0,#81bde7 3%,#56a7df)}.x-tabbar-light.x-docked-bottom .x-tab-active{background-image:none;background-color:#2175af;background-image:-webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #195884), color-stop(10%, #1d6699), color-stop(65%, #2175af), color-stop(100%, #2176b1));background-image:-webkit-linear-gradient(top, #195884,#1d6699 10%,#2175af 65%,#2176b1);background-image:linear-gradient(top, #195884,#1d6699 10%,#2175af 65%,#2176b1);text-shadow:rgba(255, 255, 255, 0.25) 0 0.08em 0;-webkit-box-shadow:#1d6699 0 0 0.25em inset;box-shadow:#1d6699 0 0 0.25em inset}.x-tabbar-light.x-docked-bottom .x-tab-active .x-button-icon{background-image:none;background-color:#1da2ff;background-image:-webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #6ac2ff), color-stop(3%, #37adff), color-stop(100%, #0398ff));background-image:-webkit-linear-gradient(top, #6ac2ff,#37adff 3%,#0398ff);background-image:linear-gradient(top, #6ac2ff,#37adff 3%,#0398ff)}.x-tabbar-light.x-docked-top .x-tab-active{background-image:none;background-color:#2175af;background-image:-webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #195884), color-stop(10%, #1d6699), color-stop(65%, #2175af), color-stop(100%, #2176b1));background-image:-webkit-linear-gradient(top, #195884,#1d6699 10%,#2175af 65%,#2176b1);background-image:linear-gradient(top, #195884,#1d6699 10%,#2175af 65%,#2176b1);color:white}.x-tabbar-dark{background-image:none;background-color:#0e4b75;background-image:-webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #1676b9), color-stop(3%, #11598c), color-stop(100%, #0b3c5e));background-image:-webkit-linear-gradient(top, #1676b9,#11598c 3%,#0b3c5e);background-image:linear-gradient(top, #1676b9,#11598c 3%,#0b3c5e);border-top-color:#0b3c5e;border-bottom-color:#061f31}.x-tabbar-dark .x-tab{color:#63b4ec}.x-tabbar-dark .x-tab-active{color:white;border-bottom:1px solid #105483}.x-tabbar-dark .x-tab-pressed{color:white}.x-tabbar-dark.x-docked-bottom .x-tab{text-shadow:rgba(0, 0, 0, 0.5) 0 -0.08em 0}.x-tabbar-dark.x-docked-bottom .x-tab .x-button-icon{background-image:none;background-color:#1985d0;background-image:-webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #4ca9e9), color-stop(3%, #1e93e4), color-stop(100%, #1676b9));background-image:-webkit-linear-gradient(top, #4ca9e9,#1e93e4 3%,#1676b9);background-image:linear-gradient(top, #4ca9e9,#1e93e4 3%,#1676b9)}.x-tabbar-dark.x-docked-bottom .x-tab-active{background-image:none;background-color:#0b3c5e;background-image:-webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #061f31), color-stop(10%, #092e47), color-stop(65%, #0b3c5e), color-stop(100%, #0c3e60));background-image:-webkit-linear-gradient(top, #061f31,#092e47 10%,#0b3c5e 65%,#0c3e60);background-image:linear-gradient(top, #061f31,#092e47 10%,#0b3c5e 65%,#0c3e60);text-shadow:rgba(0, 0, 0, 0.5) 0 -0.08em 0;-webkit-box-shadow:#092e47 0 0 0.25em inset;box-shadow:#092e47 0 0 0.25em inset}.x-tabbar-dark.x-docked-bottom .x-tab-active .x-button-icon{background-image:none;background-color:#50b7ff;background-image:-webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #9dd7ff), color-stop(3%, #6ac2ff), color-stop(100%, #37adff));background-image:-webkit-linear-gradient(top, #9dd7ff,#6ac2ff 3%,#37adff);background-image:linear-gradient(top, #9dd7ff,#6ac2ff 3%,#37adff)}.x-tabbar-dark.x-docked-top .x-tab-active{background-image:none;background-color:#0b3c5e;background-image:-webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #061f31), color-stop(10%, #092e47), color-stop(65%, #0b3c5e), color-stop(100%, #0c3e60));background-image:-webkit-linear-gradient(top, #061f31,#092e47 10%,#0b3c5e 65%,#0c3e60);background-image:linear-gradient(top, #061f31,#092e47 10%,#0b3c5e 65%,#0c3e60);color:white}.x-tabbar-neutral{background-image:none;background-color:#e0e0e0;background-image:-webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #ffffff), color-stop(3%, #ededed), color-stop(100%, #d3d3d3));background-image:-webkit-linear-gradient(top, #ffffff,#ededed 3%,#d3d3d3);background-image:linear-gradient(top, #ffffff,#ededed 3%,#d3d3d3);border-top-color:#d3d3d3;border-bottom-color:#bababa}.x-tabbar-neutral .x-tab{color:#7a7a7a}.x-tabbar-neutral .x-tab-active{color:black;border-bottom:1px solid #e8e8e8}.x-tabbar-neutral .x-tab-pressed{color:black}.x-tabbar-neutral.x-docked-bottom .x-tab{text-shadow:rgba(255, 255, 255, 0.25) 0 0.08em 0}.x-tabbar-neutral.x-docked-bottom .x-tab .x-button-icon{background-image:none;background-color:#adadad;background-image:-webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #d3d3d3), color-stop(3%, #bababa), color-stop(100%, #a0a0a0));background-image:-webkit-linear-gradient(top, #d3d3d3,#bababa 3%,#a0a0a0);background-image:linear-gradient(top, #d3d3d3,#bababa 3%,#a0a0a0)}.x-tabbar-neutral.x-docked-bottom .x-tab-active{background-image:none;background-color:#d3d3d3;background-image:-webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #bababa), color-stop(10%, #c7c7c7), color-stop(65%, #d3d3d3), color-stop(100%, #d5d5d5));background-image:-webkit-linear-gradient(top, #bababa,#c7c7c7 10%,#d3d3d3 65%,#d5d5d5);background-image:linear-gradient(top, #bababa,#c7c7c7 10%,#d3d3d3 65%,#d5d5d5);text-shadow:rgba(255, 255, 255, 0.25) 0 0.08em 0;-webkit-box-shadow:#c7c7c7 0 0 0.25em inset;box-shadow:#c7c7c7 0 0 0.25em inset}.x-tabbar-neutral.x-docked-bottom .x-tab-active .x-button-icon{background-image:none;background-color:#7a7a7a;background-image:-webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #a0a0a0), color-stop(3%, #878787), color-stop(100%, #6d6d6d));background-image:-webkit-linear-gradient(top, #a0a0a0,#878787 3%,#6d6d6d);background-image:linear-gradient(top, #a0a0a0,#878787 3%,#6d6d6d)}.x-tabbar-neutral.x-docked-top .x-tab-active{background-image:none;background-color:#d3d3d3;background-image:-webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #bababa), color-stop(10%, #c7c7c7), color-stop(65%, #d3d3d3), color-stop(100%, #d5d5d5));background-image:-webkit-linear-gradient(top, #bababa,#c7c7c7 10%,#d3d3d3 65%,#d5d5d5);background-image:linear-gradient(top, #bababa,#c7c7c7 10%,#d3d3d3 65%,#d5d5d5);color:black}.x-tab.x-item-disabled span.x-button-label,.x-tab.x-item-disabled .x-hasbadge span.x-badge,.x-hasbadge .x-tab.x-item-disabled span.x-badge,.x-tab.x-item-disabled .x-button-icon{filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=50);opacity:0.5}.x-tab.x-draggable{filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=70);opacity:0.7}.x-tab{-webkit-user-select:none;overflow:visible !important}.x-tab .x-button-label,.x-tab .x-hasbadge .x-badge,.x-hasbadge .x-tab .x-badge{overflow:visible !important}.x-tabbar > *{z-index:1}.x-toolbar{padding:0 0.2em;overflow:hidden;position:relative;height:2.6em}.x-toolbar > *{z-index:1}.x-toolbar.x-docked-top{border-bottom:.1em solid}.x-toolbar.x-docked-bottom{border-top:.1em solid}.x-toolbar.x-docked-left{width:7em;height:auto;padding:0.2em;border-right:.1em solid}.x-toolbar.x-docked-right{width:7em;height:auto;padding:0.2em;border-left:.1em solid}.x-title{line-height:2.1em;font-size:1.2em;text-align:center;font-weight:bold;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin:0 0.3em;max-width:100%}.x-title .x-innerhtml{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;padding:0 .3em}.x-toolbar-dark{background-image:none;background-color:#1468a2;background-image:-webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #1e93e4), color-stop(3%, #1676b9), color-stop(100%, #11598c));background-image:-webkit-linear-gradient(top, #1e93e4,#1676b9 3%,#11598c);background-image:linear-gradient(top, #1e93e4,#1676b9 3%,#11598c);border-color:black}.x-toolbar-dark .x-title{color:white;text-shadow:rgba(0, 0, 0, 0.5) 0 -0.08em 0}.x-toolbar-dark.x-docked-top{border-bottom-color:black}.x-toolbar-dark.x-docked-bottom{border-top-color:black}.x-toolbar-dark.x-docked-left{border-right-color:black}.x-toolbar-dark.x-docked-right{border-left-color:black}.x-toolbar-dark .x-button,.x-toolbar .x-toolbar-dark .x-button,.x-toolbar-dark .x-field-select .x-component-outer,.x-toolbar .x-toolbar-dark .x-field-select .x-component-outer,.x-toolbar-dark .x-field-select .x-component-outer:before,.x-toolbar .x-toolbar-dark .x-field-select .x-component-outer:before{border:1px solid #061f31;border-top-color:#092e47;color:white}.x-toolbar-dark .x-button.x-button-back:before,.x-toolbar-dark .x-button.x-button-forward:before,.x-toolbar .x-toolbar-dark .x-button.x-button-back:before,.x-toolbar .x-toolbar-dark .x-button.x-button-forward:before,.x-toolbar-dark .x-field-select .x-component-outer.x-button-back:before,.x-toolbar-dark .x-field-select .x-component-outer.x-button-forward:before,.x-toolbar .x-toolbar-dark .x-field-select .x-component-outer.x-button-back:before,.x-toolbar .x-toolbar-dark .x-field-select .x-component-outer.x-button-forward:before,.x-toolbar-dark .x-field-select .x-component-outer:before.x-button-back:before,.x-toolbar-dark .x-field-select .x-component-outer:before.x-button-forward:before,.x-toolbar .x-toolbar-dark .x-field-select .x-component-outer:before.x-button-back:before,.x-toolbar .x-toolbar-dark .x-field-select .x-component-outer:before.x-button-forward:before{background:#061f31}.x-toolbar-dark .x-button,.x-toolbar-dark .x-button.x-button-back:after,.x-toolbar-dark .x-button.x-button-forward:after,.x-toolbar .x-toolbar-dark .x-button,.x-toolbar .x-toolbar-dark .x-button.x-button-back:after,.x-toolbar .x-toolbar-dark .x-button.x-button-forward:after,.x-toolbar-dark .x-field-select .x-component-outer,.x-toolbar-dark .x-field-select .x-component-outer.x-button-back:after,.x-toolbar-dark .x-field-select .x-component-outer.x-button-forward:after,.x-toolbar .x-toolbar-dark .x-field-select .x-component-outer,.x-toolbar .x-toolbar-dark .x-field-select .x-component-outer.x-button-back:after,.x-toolbar .x-toolbar-dark .x-field-select .x-component-outer.x-button-forward:after,.x-toolbar-dark .x-field-select .x-component-outer:before,.x-toolbar-dark .x-field-select .x-component-outer:before.x-button-back:after,.x-toolbar-dark .x-field-select .x-component-outer:before.x-button-forward:after,.x-toolbar .x-toolbar-dark .x-field-select .x-component-outer:before,.x-toolbar .x-toolbar-dark .x-field-select .x-component-outer:before.x-button-back:after,.x-toolbar .x-toolbar-dark .x-field-select .x-component-outer:before.x-button-forward:after{background-image:none;background-color:#11598c;background-image:-webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #1985d0), color-stop(3%, #1468a2), color-stop(100%, #0e4b75));background-image:-webkit-linear-gradient(top, #1985d0,#1468a2 3%,#0e4b75);background-image:linear-gradient(top, #1985d0,#1468a2 3%,#0e4b75)}.x-toolbar-dark .x-button .x-button-icon.x-icon-mask,.x-toolbar .x-toolbar-dark .x-button .x-button-icon.x-icon-mask,.x-toolbar-dark .x-field-select .x-component-outer .x-button-icon.x-icon-mask,.x-toolbar .x-toolbar-dark .x-field-select .x-component-outer .x-button-icon.x-icon-mask,.x-toolbar-dark .x-field-select .x-component-outer:before .x-button-icon.x-icon-mask,.x-toolbar .x-toolbar-dark .x-field-select .x-component-outer:before .x-button-icon.x-icon-mask{background-image:none;background-color:white;background-image:-webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #ffffff), color-stop(3%, #ffffff), color-stop(100%, #e8f4fc));background-image:-webkit-linear-gradient(top, #ffffff,#ffffff 3%,#e8f4fc);background-image:linear-gradient(top, #ffffff,#ffffff 3%,#e8f4fc)}.x-toolbar-dark .x-button.x-button-pressing,.x-toolbar-dark .x-button.x-button-pressing:after,.x-toolbar-dark .x-button.x-button-pressed,.x-toolbar-dark .x-button.x-button-pressed:after,.x-toolbar-dark .x-button.x-button-active,.x-toolbar-dark .x-button.x-button-active:after,.x-toolbar .x-toolbar-dark .x-button.x-button-pressing,.x-toolbar .x-toolbar-dark .x-button.x-button-pressing:after,.x-toolbar .x-toolbar-dark .x-button.x-button-pressed,.x-toolbar .x-toolbar-dark .x-button.x-button-pressed:after,.x-toolbar .x-toolbar-dark .x-button.x-button-active,.x-toolbar .x-toolbar-dark .x-button.x-button-active:after,.x-toolbar-dark .x-field-select .x-component-outer.x-button-pressing,.x-toolbar-dark .x-field-select .x-component-outer.x-button-pressing:after,.x-toolbar-dark .x-field-select .x-component-outer.x-button-pressed,.x-toolbar-dark .x-field-select .x-component-outer.x-button-pressed:after,.x-toolbar-dark .x-field-select .x-component-outer.x-button-active,.x-toolbar-dark .x-field-select .x-component-outer.x-button-active:after,.x-toolbar .x-toolbar-dark .x-field-select .x-component-outer.x-button-pressing,.x-toolbar .x-toolbar-dark .x-field-select .x-component-outer.x-button-pressing:after,.x-toolbar .x-toolbar-dark .x-field-select .x-component-outer.x-button-pressed,.x-toolbar .x-toolbar-dark .x-field-select .x-component-outer.x-button-pressed:after,.x-toolbar .x-toolbar-dark .x-field-select .x-component-outer.x-button-active,.x-toolbar .x-toolbar-dark .x-field-select .x-component-outer.x-button-active:after,.x-toolbar-dark .x-field-select .x-component-outer:before.x-button-pressing,.x-toolbar-dark .x-field-select .x-component-outer:before.x-button-pressing:after,.x-toolbar-dark .x-field-select .x-component-outer:before.x-button-pressed,.x-toolbar-dark .x-field-select .x-component-outer:before.x-button-pressed:after,.x-toolbar-dark .x-field-select .x-component-outer:before.x-button-active,.x-toolbar-dark .x-field-select .x-component-outer:before.x-button-active:after,.x-toolbar .x-toolbar-dark .x-field-select .x-component-outer:before.x-button-pressing,.x-toolbar .x-toolbar-dark .x-field-select .x-component-outer:before.x-button-pressing:after,.x-toolbar .x-toolbar-dark .x-field-select .x-component-outer:before.x-button-pressed,.x-toolbar .x-toolbar-dark .x-field-select .x-component-outer:before.x-button-pressed:after,.x-toolbar .x-toolbar-dark .x-field-select .x-component-outer:before.x-button-active,.x-toolbar .x-toolbar-dark .x-field-select .x-component-outer:before.x-button-active:after{background-image:none;background-color:#0f517e;background-image:-webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #0a3351), color-stop(10%, #0c4267), color-stop(65%, #0f517e), color-stop(100%, #0f5280));background-image:-webkit-linear-gradient(top, #0a3351,#0c4267 10%,#0f517e 65%,#0f5280);background-image:linear-gradient(top, #0a3351,#0c4267 10%,#0f517e 65%,#0f5280)}.x-toolbar-dark .x-form-label{color:white;text-shadow:rgba(0, 0, 0, 0.5) 0 -0.08em 0}.x-toolbar-light{background-image:none;background-color:#1985d0;background-image:-webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #4ca9e9), color-stop(3%, #1e93e4), color-stop(100%, #1676b9));background-image:-webkit-linear-gradient(top, #4ca9e9,#1e93e4 3%,#1676b9);background-image:linear-gradient(top, #4ca9e9,#1e93e4 3%,#1676b9);border-color:black}.x-toolbar-light .x-title{color:white;text-shadow:rgba(0, 0, 0, 0.5) 0 -0.08em 0}.x-toolbar-light.x-docked-top{border-bottom-color:black}.x-toolbar-light.x-docked-bottom{border-top-color:black}.x-toolbar-light.x-docked-left{border-right-color:black}.x-toolbar-light.x-docked-right{border-left-color:black}.x-toolbar-light .x-button,.x-toolbar .x-toolbar-light .x-button,.x-toolbar-light .x-field-select .x-component-outer,.x-toolbar .x-toolbar-light .x-field-select .x-component-outer,.x-toolbar-light .x-field-select .x-component-outer:before,.x-toolbar .x-toolbar-light .x-field-select .x-component-outer:before{border:1px solid #0b3c5e;border-top-color:#0e4b75;color:white}.x-toolbar-light .x-button.x-button-back:before,.x-toolbar-light .x-button.x-button-forward:before,.x-toolbar .x-toolbar-light .x-button.x-button-back:before,.x-toolbar .x-toolbar-light .x-button.x-button-forward:before,.x-toolbar-light .x-field-select .x-component-outer.x-button-back:before,.x-toolbar-light .x-field-select .x-component-outer.x-button-forward:before,.x-toolbar .x-toolbar-light .x-field-select .x-component-outer.x-button-back:before,.x-toolbar .x-toolbar-light .x-field-select .x-component-outer.x-button-forward:before,.x-toolbar-light .x-field-select .x-component-outer:before.x-button-back:before,.x-toolbar-light .x-field-select .x-component-outer:before.x-button-forward:before,.x-toolbar .x-toolbar-light .x-field-select .x-component-outer:before.x-button-back:before,.x-toolbar .x-toolbar-light .x-field-select .x-component-outer:before.x-button-forward:before{background:#0b3c5e}.x-toolbar-light .x-button,.x-toolbar-light .x-button.x-button-back:after,.x-toolbar-light .x-button.x-button-forward:after,.x-toolbar .x-toolbar-light .x-button,.x-toolbar .x-toolbar-light .x-button.x-button-back:after,.x-toolbar .x-toolbar-light .x-button.x-button-forward:after,.x-toolbar-light .x-field-select .x-component-outer,.x-toolbar-light .x-field-select .x-component-outer.x-button-back:after,.x-toolbar-light .x-field-select .x-component-outer.x-button-forward:after,.x-toolbar .x-toolbar-light .x-field-select .x-component-outer,.x-toolbar .x-toolbar-light .x-field-select .x-component-outer.x-button-back:after,.x-toolbar .x-toolbar-light .x-field-select .x-component-outer.x-button-forward:after,.x-toolbar-light .x-field-select .x-component-outer:before,.x-toolbar-light .x-field-select .x-component-outer:before.x-button-back:after,.x-toolbar-light .x-field-select .x-component-outer:before.x-button-forward:after,.x-toolbar .x-toolbar-light .x-field-select .x-component-outer:before,.x-toolbar .x-toolbar-light .x-field-select .x-component-outer:before.x-button-back:after,.x-toolbar .x-toolbar-light .x-field-select .x-component-outer:before.x-button-forward:after{background-image:none;background-color:#1676b9;background-image:-webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #359ee7), color-stop(3%, #1985d0), color-stop(100%, #1468a2));background-image:-webkit-linear-gradient(top, #359ee7,#1985d0 3%,#1468a2);background-image:linear-gradient(top, #359ee7,#1985d0 3%,#1468a2)}.x-toolbar-light .x-button .x-button-icon.x-icon-mask,.x-toolbar .x-toolbar-light .x-button .x-button-icon.x-icon-mask,.x-toolbar-light .x-field-select .x-component-outer .x-button-icon.x-icon-mask,.x-toolbar .x-toolbar-light .x-field-select .x-component-outer .x-button-icon.x-icon-mask,.x-toolbar-light .x-field-select .x-component-outer:before .x-button-icon.x-icon-mask,.x-toolbar .x-toolbar-light .x-field-select .x-component-outer:before .x-button-icon.x-icon-mask{background-image:none;background-color:white;background-image:-webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #ffffff), color-stop(3%, #ffffff), color-stop(100%, #e8f4fc));background-image:-webkit-linear-gradient(top, #ffffff,#ffffff 3%,#e8f4fc);background-image:linear-gradient(top, #ffffff,#ffffff 3%,#e8f4fc)}.x-toolbar-light .x-button.x-button-pressing,.x-toolbar-light .x-button.x-button-pressing:after,.x-toolbar-light .x-button.x-button-pressed,.x-toolbar-light .x-button.x-button-pressed:after,.x-toolbar-light .x-button.x-button-active,.x-toolbar-light .x-button.x-button-active:after,.x-toolbar .x-toolbar-light .x-button.x-button-pressing,.x-toolbar .x-toolbar-light .x-button.x-button-pressing:after,.x-toolbar .x-toolbar-light .x-button.x-button-pressed,.x-toolbar .x-toolbar-light .x-button.x-button-pressed:after,.x-toolbar .x-toolbar-light .x-button.x-button-active,.x-toolbar .x-toolbar-light .x-button.x-button-active:after,.x-toolbar-light .x-field-select .x-component-outer.x-button-pressing,.x-toolbar-light .x-field-select .x-component-outer.x-button-pressing:after,.x-toolbar-light .x-field-select .x-component-outer.x-button-pressed,.x-toolbar-light .x-field-select .x-component-outer.x-button-pressed:after,.x-toolbar-light .x-field-select .x-component-outer.x-button-active,.x-toolbar-light .x-field-select .x-component-outer.x-button-active:after,.x-toolbar .x-toolbar-light .x-field-select .x-component-outer.x-button-pressing,.x-toolbar .x-toolbar-light .x-field-select .x-component-outer.x-button-pressing:after,.x-toolbar .x-toolbar-light .x-field-select .x-component-outer.x-button-pressed,.x-toolbar .x-toolbar-light .x-field-select .x-component-outer.x-button-pressed:after,.x-toolbar .x-toolbar-light .x-field-select .x-component-outer.x-button-active,.x-toolbar .x-toolbar-light .x-field-select .x-component-outer.x-button-active:after,.x-toolbar-light .x-field-select .x-component-outer:before.x-button-pressing,.x-toolbar-light .x-field-select .x-component-outer:before.x-button-pressing:after,.x-toolbar-light .x-field-select .x-component-outer:before.x-button-pressed,.x-toolbar-light .x-field-select .x-component-outer:before.x-button-pressed:after,.x-toolbar-light .x-field-select .x-component-outer:before.x-button-active,.x-toolbar-light .x-field-select .x-component-outer:before.x-button-active:after,.x-toolbar .x-toolbar-light .x-field-select .x-component-outer:before.x-button-pressing,.x-toolbar .x-toolbar-light .x-field-select .x-component-outer:before.x-button-pressing:after,.x-toolbar .x-toolbar-light .x-field-select .x-component-outer:before.x-button-pressed,.x-toolbar .x-toolbar-light .x-field-select .x-component-outer:before.x-button-pressed:after,.x-toolbar .x-toolbar-light .x-field-select .x-component-outer:before.x-button-active,.x-toolbar .x-toolbar-light .x-field-select .x-component-outer:before.x-button-active:after{background-image:none;background-color:#156eac;background-image:-webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #0f517e), color-stop(10%, #125f95), color-stop(65%, #156eac), color-stop(100%, #156fae));background-image:-webkit-linear-gradient(top, #0f517e,#125f95 10%,#156eac 65%,#156fae);background-image:linear-gradient(top, #0f517e,#125f95 10%,#156eac 65%,#156fae)}.x-toolbar-light .x-form-label{color:white;text-shadow:rgba(0, 0, 0, 0.5) 0 -0.08em 0}.x-toolbar-neutral{background-image:none;background-color:#e0e0e0;background-image:-webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #ffffff), color-stop(3%, #ededed), color-stop(100%, #d3d3d3));background-image:-webkit-linear-gradient(top, #ffffff,#ededed 3%,#d3d3d3);background-image:linear-gradient(top, #ffffff,#ededed 3%,#d3d3d3);border-color:#616161}.x-toolbar-neutral .x-title{color:#070707;text-shadow:rgba(255, 255, 255, 0.25) 0 0.08em 0}.x-toolbar-neutral.x-docked-top{border-bottom-color:#616161}.x-toolbar-neutral.x-docked-bottom{border-top-color:#616161}.x-toolbar-neutral.x-docked-left{border-right-color:#616161}.x-toolbar-neutral.x-docked-right{border-left-color:#616161}.x-toolbar-neutral .x-button,.x-toolbar .x-toolbar-neutral .x-button,.x-toolbar-neutral .x-field-select .x-component-outer,.x-toolbar .x-toolbar-neutral .x-field-select .x-component-outer,.x-toolbar-neutral .x-field-select .x-component-outer:before,.x-toolbar .x-toolbar-neutral .x-field-select .x-component-outer:before{border:1px solid #a0a0a0;border-top-color:#adadad;color:black}.x-toolbar-neutral .x-button.x-button-back:before,.x-toolbar-neutral .x-button.x-button-forward:before,.x-toolbar .x-toolbar-neutral .x-button.x-button-back:before,.x-toolbar .x-toolbar-neutral .x-button.x-button-forward:before,.x-toolbar-neutral .x-field-select .x-component-outer.x-button-back:before,.x-toolbar-neutral .x-field-select .x-component-outer.x-button-forward:before,.x-toolbar .x-toolbar-neutral .x-field-select .x-component-outer.x-button-back:before,.x-toolbar .x-toolbar-neutral .x-field-select .x-component-outer.x-button-forward:before,.x-toolbar-neutral .x-field-select .x-component-outer:before.x-button-back:before,.x-toolbar-neutral .x-field-select .x-component-outer:before.x-button-forward:before,.x-toolbar .x-toolbar-neutral .x-field-select .x-component-outer:before.x-button-back:before,.x-toolbar .x-toolbar-neutral .x-field-select .x-component-outer:before.x-button-forward:before{background:#a0a0a0}.x-toolbar-neutral .x-button,.x-toolbar-neutral .x-button.x-button-back:after,.x-toolbar-neutral .x-button.x-button-forward:after,.x-toolbar .x-toolbar-neutral .x-button,.x-toolbar .x-toolbar-neutral .x-button.x-button-back:after,.x-toolbar .x-toolbar-neutral .x-button.x-button-forward:after,.x-toolbar-neutral .x-field-select .x-component-outer,.x-toolbar-neutral .x-field-select .x-component-outer.x-button-back:after,.x-toolbar-neutral .x-field-select .x-component-outer.x-button-forward:after,.x-toolbar .x-toolbar-neutral .x-field-select .x-component-outer,.x-toolbar .x-toolbar-neutral .x-field-select .x-component-outer.x-button-back:after,.x-toolbar .x-toolbar-neutral .x-field-select .x-component-outer.x-button-forward:after,.x-toolbar-neutral .x-field-select .x-component-outer:before,.x-toolbar-neutral .x-field-select .x-component-outer:before.x-button-back:after,.x-toolbar-neutral .x-field-select .x-component-outer:before.x-button-forward:after,.x-toolbar .x-toolbar-neutral .x-field-select .x-component-outer:before,.x-toolbar .x-toolbar-neutral .x-field-select .x-component-outer:before.x-button-back:after,.x-toolbar .x-toolbar-neutral .x-field-select .x-component-outer:before.x-button-forward:after{background-image:none;background-color:#d3d3d3;background-image:-webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #fafafa), color-stop(3%, #e0e0e0), color-stop(100%, #c7c7c7));background-image:-webkit-linear-gradient(top, #fafafa,#e0e0e0 3%,#c7c7c7);background-image:linear-gradient(top, #fafafa,#e0e0e0 3%,#c7c7c7)}.x-toolbar-neutral .x-button .x-button-icon.x-icon-mask,.x-toolbar .x-toolbar-neutral .x-button .x-button-icon.x-icon-mask,.x-toolbar-neutral .x-field-select .x-component-outer .x-button-icon.x-icon-mask,.x-toolbar .x-toolbar-neutral .x-field-select .x-component-outer .x-button-icon.x-icon-mask,.x-toolbar-neutral .x-field-select .x-component-outer:before .x-button-icon.x-icon-mask,.x-toolbar .x-toolbar-neutral .x-field-select .x-component-outer:before .x-button-icon.x-icon-mask{background-image:none;background-color:black;background-image:-webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #262626), color-stop(3%, #0d0d0d), color-stop(100%, #000000));background-image:-webkit-linear-gradient(top, #262626,#0d0d0d 3%,#000000);background-image:linear-gradient(top, #262626,#0d0d0d 3%,#000000)}.x-toolbar-neutral .x-button.x-button-pressing,.x-toolbar-neutral .x-button.x-button-pressing:after,.x-toolbar-neutral .x-button.x-button-pressed,.x-toolbar-neutral .x-button.x-button-pressed:after,.x-toolbar-neutral .x-button.x-button-active,.x-toolbar-neutral .x-button.x-button-active:after,.x-toolbar .x-toolbar-neutral .x-button.x-button-pressing,.x-toolbar .x-toolbar-neutral .x-button.x-button-pressing:after,.x-toolbar .x-toolbar-neutral .x-button.x-button-pressed,.x-toolbar .x-toolbar-neutral .x-button.x-button-pressed:after,.x-toolbar .x-toolbar-neutral .x-button.x-button-active,.x-toolbar .x-toolbar-neutral .x-button.x-button-active:after,.x-toolbar-neutral .x-field-select .x-component-outer.x-button-pressing,.x-toolbar-neutral .x-field-select .x-component-outer.x-button-pressing:after,.x-toolbar-neutral .x-field-select .x-component-outer.x-button-pressed,.x-toolbar-neutral .x-field-select .x-component-outer.x-button-pressed:after,.x-toolbar-neutral .x-field-select .x-component-outer.x-button-active,.x-toolbar-neutral .x-field-select .x-component-outer.x-button-active:after,.x-toolbar .x-toolbar-neutral .x-field-select .x-component-outer.x-button-pressing,.x-toolbar .x-toolbar-neutral .x-field-select .x-component-outer.x-button-pressing:after,.x-toolbar .x-toolbar-neutral .x-field-select .x-component-outer.x-button-pressed,.x-toolbar .x-toolbar-neutral .x-field-select .x-component-outer.x-button-pressed:after,.x-toolbar .x-toolbar-neutral .x-field-select .x-component-outer.x-button-active,.x-toolbar .x-toolbar-neutral .x-field-select .x-component-outer.x-button-active:after,.x-toolbar-neutral .x-field-select .x-component-outer:before.x-button-pressing,.x-toolbar-neutral .x-field-select .x-component-outer:before.x-button-pressing:after,.x-toolbar-neutral .x-field-select .x-component-outer:before.x-button-pressed,.x-toolbar-neutral .x-field-select .x-component-outer:before.x-button-pressed:after,.x-toolbar-neutral .x-field-select .x-component-outer:before.x-button-active,.x-toolbar-neutral .x-field-select .x-component-outer:before.x-button-active:after,.x-toolbar .x-toolbar-neutral .x-field-select .x-component-outer:before.x-button-pressing,.x-toolbar .x-toolbar-neutral .x-field-select .x-component-outer:before.x-button-pressing:after,.x-toolbar .x-toolbar-neutral .x-field-select .x-component-outer:before.x-button-pressed,.x-toolbar .x-toolbar-neutral .x-field-select .x-component-outer:before.x-button-pressed:after,.x-toolbar .x-toolbar-neutral .x-field-select .x-component-outer:before.x-button-active,.x-toolbar .x-toolbar-neutral .x-field-select .x-component-outer:before.x-button-active:after{background-image:none;background-color:#cccccc;background-image:-webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #b2b2b2), color-stop(10%, #bfbfbf), color-stop(65%, #cccccc), color-stop(100%, #cdcdcd));background-image:-webkit-linear-gradient(top, #b2b2b2,#bfbfbf 10%,#cccccc 65%,#cdcdcd);background-image:linear-gradient(top, #b2b2b2,#bfbfbf 10%,#cccccc 65%,#cdcdcd)}.x-toolbar-neutral .x-form-label{color:#070707;text-shadow:rgba(255, 255, 255, 0.25) 0 0.08em 0}.x-toolbar.x-toolbar-neutral .x-toolbar-inner .x-button.x-button-pressing{background-image:-webkit-gradient(radial, 50% 50%, 0, 50% 50%, 24, color-stop(0%, rgba(122,191,239,0.7)), color-stop(100%, rgba(122,191,239,0)));background-image:-webkit-radial-gradient(rgba(122,191,239,0.7),rgba(122,191,239,0) 24px);background-image:radial-gradient(rgba(122,191,239,0.7),rgba(122,191,239,0) 24px)}.x-toolbar.x-toolbar-neutral .x-toolbar-inner .x-button.x-button-pressing .x-button-icon.x-button-mask{background-image:none;background-color:#fff;background-image:-webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #e6e6e6), color-stop(10%, #f2f2f2), color-stop(65%, #ffffff), color-stop(100%, #ffffff));background-image:-webkit-linear-gradient(top, #e6e6e6,#f2f2f2 10%,#ffffff 65%,#ffffff);background-image:linear-gradient(top, #e6e6e6,#f2f2f2 10%,#ffffff 65%,#ffffff)}.x-navigation-bar .x-container{overflow:visible}.x-spinner .x-input-el,.x-field-select .x-input-el{-webkit-text-fill-color:#000;-webkit-opacity:1}.x-spinner.x-item-disabled .x-input-el,.x-field-select.x-item-disabled .x-input-el{-webkit-text-fill-color:currentcolor}.x-toolbar .x-field-select .x-input-el{-webkit-text-fill-color:#fff}.x-toolbar .x-field-select.x-item-disabled .x-input-el{-webkit-text-fill-color:rgba(255, 255, 255, 0.6)}.x-toolbar .x-form-field-container{padding:0 .3em}.x-toolbar .x-field{width:13em;margin:.5em;min-height:0;border-bottom:0;background:transparent}.x-toolbar .x-field .x-clear-icon{background-size:50% 50%;right:-0.8em;margin-top:-1.06em}.x-toolbar .x-field-input{padding-right:1.6em !important}.x-toolbar .x-field-textarea .x-component-outer,.x-toolbar .x-field-text .x-component-outer,.x-toolbar .x-field-number .x-component-outer,.x-toolbar .x-field-search .x-component-outer{-webkit-border-radius:0.3em;border-radius:0.3em;background-color:white;-webkit-box-shadow:inset rgba(0, 0, 0, 0.5) 0 0.1em 0, inset rgba(0, 0, 0, 0.5) 0 -0.1em 0, inset rgba(0, 0, 0, 0.5) 0.1em 0 0, inset rgba(0, 0, 0, 0.5) -0.1em 0 0, inset rgba(0, 0, 0, 0.5) 0 0.15em 0.4em}.x-toolbar .x-form-label{background:transparent;border:0;padding:0;line-height:1.4em}.x-toolbar .x-form-field{height:1.6em;color:#6e6e6e;background:transparent;min-height:0;-webkit-appearance:none;padding:0em .3em;margin:0}.x-toolbar .x-form-field:focus{color:black}.x-toolbar .x-field-select .x-component-outer,.x-toolbar .x-field-search .x-component-outer{-webkit-border-radius:0.8em;border-radius:0.8em}.x-toolbar .x-field-search .x-field-input{background-position:.5em 50%}.x-toolbar .x-field-select{-webkit-box-shadow:none}.x-toolbar .x-field-select .x-form-field{height:1.4em}.x-toolbar .x-field-select{background:transparent}.x-toolbar .x-field-select .x-component-outer:after{right:.4em}.x-toolbar .x-field-select.x-item-disabled .x-component-outer:after{opacity:.6}.x-toolbar .x-field-select .x-component-outer:before{width:3em;border-left:none;-webkit-border-top-right-radius:0.8em;border-top-right-radius:0.8em;-webkit-border-bottom-right-radius:0.8em;border-bottom-right-radius:0.8em;-webkit-mask:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAABCAYAAACc0f2yAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAADJJREFUeNpi/P//PwMjIyMbAwMDOxRzAjEXFHMDMQ8a5kXC6HLcSHo5kcwEmU9TABBgAOcTBAFcRiSpAAAAAElFTkSuQmCC');-webkit-mask-position:right top;-webkit-mask-repeat:repeat-y;-webkit-mask-size:3em 0.05em}.x-toolbar .x-field-select .x-input-text{color:#fff}.x-android .x-field-search .x-field-input{padding-left:.2em !important;padding-right:2.2em !important}.x-indexbar-wrapper{-webkit-box-pack:end !important;box-pack:end !important;pointer-events:none}.x-indexbar-vertical{width:1.1em;-webkit-box-orient:vertical;box-orient:vertical;margin-right:8px}.x-indexbar-horizontal{height:1.1em;-webkit-box-orient:horizontal;box-orient:horizontal;margin-bottom:8px}.x-indexbar{pointer-events:auto;z-index:2;padding:.3em 0;min-height:0 !important;height:auto !important;-webkit-box-flex:0 !important}.x-indexbar > div{color:#155988;font-size:0.6em;text-align:center;line-height:1.1em;font-weight:bold;display:block}.x-phone.x-landscape .x-indexbar > div{font-size:0.38em;line-height:1em}.x-indexbar-pressed{-webkit-border-radius:0.55em;border-radius:0.55em;background-color:rgba(143, 155, 163, 0.8)}.x-list{position:relative;background-color:#f7f7f7}.x-list .x-list-inner{width:100%}.x-list .x-list-disclosure{position:absolute;bottom:0.44em;right:0.44em}.x-list .x-list-disclosure{overflow:visible;-webkit-mask:0 0 url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA2ZpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDpFNkNCM0JGNTZFMjI2ODExQkNGQjkwMzk3MDc3MkZFQiIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDo3M0MzQUU1QUFDQkQxMURGOEQ2MUVDMjM0MzY2NTBDQSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo3M0MzQUU1OUFDQkQxMURGOEQ2MUVDMjM0MzY2NTBDQSIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1IE1hY2ludG9zaCI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOkU3Q0IzQkY1NkUyMjY4MTFCQ0ZCOTAzOTcwNzcyRkVCIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOkU2Q0IzQkY1NkUyMjY4MTFCQ0ZCOTAzOTcwNzcyRkVCIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+uoWjuwAACh9JREFUeNrUm2toVdkVx7eJRqPRaHzFGBOjidGYaLQaX9GREXXAkloYQVpT+qFYBkcqLS0zTKFQWpwv86F0KLRfHFqnWDq0UCsMFYqlqHSwGo2v4Du+X9FoNL5P12/N3rLn9Cb33HNvrnHDujfnnHvO2f+91l57/dfaGWBe8xYEQUq/H5ilftWIVIoU2+Ov2e/jIt0inSKnRVpEnvdlR/oK8CKRt0QaRd4QyU3hXkDvFvmXyOeZHoABGXzWWJF3RL4rUuFfKC4uNmPHjjUjRozQ44kTJ+r3jRs3zNOnT013d7e5deuWuXTpknnx4oV/602RP4n8TqQ1EyadCcBlIh9YoHmcqKioMFOnTjXl5eVm1KhR5smTJwrs+fPnCohvOjpw4ECTk5Ojwt/5+fnmzp075vr16+bkyZPm1KlT/nv+KvJLkf++KsCAe89KPidmz55t5s6dawoLC839+/fNo0ePFCwgHjx4oMe0u3fv6vfw4cNNbm6uGTRokCkoKNDBycvLU+DDhg3TQTp27Jg5fPiwuXfvnnvvJyI/EunIJmCczqci1RzMmzfPLFiwQF9Ox65cuWKuXr2qZoqk0ikGa/z48WbcuHFm0qRJOihDhw41LS0tZu/evToI1sl9W2RXNgC/K/IRGp42bZpZsmSJasSZ4fnz51WbmWiDBw9W0NXV1TrvOd6zZ49pbX05nd8XwB/2FWA87a+tYzKLFi0yixcvVoCY3NmzZ8MOJ6OttLRUpwy+4dy5c2bnzp3u0h9FvifAuzMJmPm6Q+SbHGzYsEHn3P79+83Ro0fVCWWrVVZWmqVLl+rfO3bsUA8v7QuRbwjoa5l6z2/xD7KsBJs3bw7WrVsXiINh8rwSGTJkSLBmzRrtS1lZmTv/H5wnc7o3iTpnA1k69AXLli0LZAmJ1VGeQWfFEek3x3FBc684ymDLli0+6E/TBfymyDMeJmasL4jbSe4bPXp0MGvWLJX6+vpAApJAlqTYoAcMGBDU1NQEmzZtCsRxuvPvxQVM7Hubh4gnDsRJxdYsInM+kOUrkHVXj/lmAGVOBuJ909K0rBZBc3OzO4eCmuIA/jcPkEAiWLVqVVqdQjA7WWLc8TZ3ns7W1tYGstaqxuI8m8GbM2dOIKuGO3dDpCAVwCw9QUlJSbB+/XrfXGLLzJkzffMtFNko8pjjyZMnq4njFONOGRSyevVqNXF77hdRARc4U167dm0wZsyYjHhW5m0IsLFMCm0EEl0FDQ0NgZCMl2afqjBgTU1N7vg+PCUK4B9yw/Tp0wNZ6NOatxEAO/JxxC03mCWmH8eZMVBVVVVBXV2dO/ebMOCcEFhIwI/5g1j2woUL5tmzZ30dS7SLLBb5DHKxb98+jaVhXDIAKT2IAIgYnnjcto3iF6r934QBr4G+Tpkyxdy+fdt0dXVlK4DiRetEfs7BgQMHtPPE6rAm6XTkBz18+FDJC2GoDYc39ga4mQ9ZL5UMZEG74fYzC7zrzJkzSitlaqnG4MxRGvH8zZs3daBs+5YMWG6iFE+R1bA+HD6bNBCXkcfsioqKNJsBl+1JGwT9J06ciNLnz0TaRP5+8eLFMvohnlfJCVQzihLQMoMF05JnFNsAanf4dxCDoLy8XIOBKGsiyxXLjUyBQEY0FQdTGDFltMdFVAQ+MmiR4wGiONZme7w1kdNayYcsQ0rio8SdaBa2wuhnigOH8lmryGfRF5gZaSDYEvw7qVMQ/4PF+djCc7iBD9ItUTtPNoK5blu5pZtRpDMi6Cci3xfZjBNua2tTc8WZ8e7e5jWK8GhrvVhJng841+aOdY643FPSjEBubrac2cciK8hjQf6vXbumzowcWE99ACyKGzlypMNX6QNmYueTO3r8+HFWCX0KjTz1AtK1WNXx48c19TNhwgS1ykQNLFiCR4ZeAsZBqMe1SbL+2k7bIGUX2iNIIectsbjmu8INLN7yNNEHXKBrlDiFfqrdcJDydZEPXZDinG0is/YcV6EPWA+42JeJuAy390XW49hI2JNjC8cAYEGJvlJzzOvb8mztStPFeOUkS2muH2l1OxOIGsK94kZU+BdLL1W7xM/hBhYvMuv0NdzhvFoWl5q4rY6pC1iWnIULFxI+6vocbpizt8R2+IDb/egkFXaS5Ub4u496HYU64b2GYARml8j3hIKo9rCGOyh84d69id6f2gfWjAsIOgAMGaEwlwisIzaucGe+LL5/hS1RiH4Tk+5n6zGB8+9F3uaAWhZ9O3ToUK+MDqURSFkNd4lDaw976f18YPPeYp00w9DHrcxWFN6GMKxYsUKJzZEjR5LSV8B6DviLROThn3wQtuEMonhrXko6xrYLGaaHb1iwdSUlJapZ4mjMOEqsT0jZ2fmSo+xOBBgNd7icUBQK1tHRob8jJeTFrJlopGX+QYxP4qCqqkqLdlQqoyQAMGeXtbFtV6KMR7fNNmzExZPBSEYTGWm4MLy4trZWHV4iD8854t3t27frjoAkwcRHtp6lmQ46jgnjfKIWw1iXWW3IeuCb5L7WRIBpnwAY+kUBmpRKb86LDhDhXL58WcH3Ng0izPevBBPLly/XKXPw4MGUkgs4XTKunnb/kOweFnWtBGQqCZ8kL+2CibNcE2sJVq5cGQj1i1XeIRlPzcpLxhf1lpemsVNGQzWSYB7byEowIQOtjglCQOSXSmPuwo897X4sIDt6S9PS2B7Uwh4qzBAvnIn4uof593/BBPOVKRKHteE48T04N0sjfxX13kY/W0gBO12TnjFjhl+UI8PyZ3eNcix1pXTeQ5mGSqfMX3fuB6mWS3Wbg5iI1pjSLZeWlpZqldAen3JpXgkmtBZEh+M+G99ATQmx5w7hv1IFDGE+aWwNFw2lA5r6L46LEqyx9WKcU0VFRVoFOwposqKohdhz0KaauFse6o2t4eI1SYTH7RzTg2Q9SXuhdLobAPOLWwQ3tvpPebWxsdE/35zuphaCdt3nQSmTykQ6+zLoJLXgdIvsaNaB9erJWzOxi4f2jnvR/Pnz1cTTmXNxC95OZKnUGnII7LZkYFPdpviueyHOAUeGV01n61GcaYFlUKzHI3vXtvXkpNIB7Mz7ofPemDhOJ50NKalolXcSReEHvGtbowB1EieXgyNjG6JW1mEylDwIFoi9U42OkjXSNLA3oj6Ykle4g/t9R0D8LZXnxU1esWRttXM7lwwJNA6qCL2EpMO44iYIXNaFyMlFeu3t7Zq78ugeBbZz2d4RX2mBa/oFTRPLQs+ggfBlGA/gYV09hYvQR5eScRvF+Zt7iOm92JjMxU9snam3kLXPALvWYHlsoztBmgjtIGiazkMhw6ABC4+GpADa/QuA5bJ+Temn5sv/f4gSo/c5YNfYKd9kGVBdOCmO5hI1pkAC3t1uExKfmwTbFfoL4HACDlN/y5p+RZLfU/Fvs+BgbK1psLBXAjhR+qauh2unTfRdAa8N4D5pqQL+nwADAKGFDQ//Deb9AAAAAElFTkSuQmCC') no-repeat;-webkit-mask-size:1.7em;background-image:none;background-color:#006bb6;background-image:-webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #0398ff), color-stop(3%, #007ad0), color-stop(100%, #005c9d));background-image:-webkit-linear-gradient(top, #0398ff,#007ad0 3%,#005c9d);background-image:linear-gradient(top, #0398ff,#007ad0 3%,#005c9d);width:1.7em;height:1.7em}.x-list.x-list-indexed .x-list-disclosure{margin-right:1em}.x-list .x-item-selected .x-list-disclosure{background:#fff none}.x-list .x-list-item{position:relative;color:black}.x-list .x-list-item .x-list-item-label{min-height:2.6em;padding:0.65em 0.8em}.x-list .x-list-item.x-item-pressed .x-list-item-label{background:#b6e1ff none}.x-list .x-list-item.x-item-selected .x-list-item-label{background-image:none;background-color:#006bb6;background-image:-webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #0398ff), color-stop(3%, #007ad0), color-stop(100%, #005c9d));background-image:-webkit-linear-gradient(top, #0398ff,#007ad0 3%,#005c9d);background-image:linear-gradient(top, #0398ff,#007ad0 3%,#005c9d);color:white;text-shadow:rgba(0, 0, 0, 0.5) 0 -0.08em 0}.x-list-header{position:relative}.x-list-header-swap{position:absolute;left:0;width:100%;z-index:1}.x-ios .x-list-header-swap{-webkit-transform:translate3d(0, 0, 0);transform:translate3d(0, 0, 0)}.x-list-normal .x-list-header{background-image:none;background-color:#5ab5f5;background-image:-webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #a2d6f9), color-stop(3%, #72c0f6), color-stop(100%, #42abf4));background-image:-webkit-linear-gradient(top, #a2d6f9,#72c0f6 3%,#42abf4);background-image:linear-gradient(top, #a2d6f9,#72c0f6 3%,#42abf4);color:#0a6aac;text-shadow:rgba(255, 255, 255, 0.25) 0 0.08em 0;border-top:1px solid #5ab5f5;border-bottom:1px solid #0d87dc;font-weight:bold;font-size:0.8em;padding:0.2em 1.02em;text-shadow:rgba(255, 255, 255, 0.25) 0 0.08em 0}.x-list-normal .x-list-item .x-list-item-label{border-top:1px solid #dedede}.x-list-normal .x-list-item:last-child .x-list-item-label{border-bottom:1px solid #dedede}.x-list-normal .x-list-item:first-child .x-list-item-label{border-top:0}.x-list-normal .x-list-item.x-item-pressed .x-list-item-label{border-top-color:#b6e1ff;background-color:#b6e1ff}.x-list-normal .x-list-item.x-item-selected .x-list-item-label{border-top-color:#006bb6;border-bottom-color:#003e6a}.x-list-round .x-scroll-view{background-color:#EEEEEE !important}.x-list-round .x-list-disclosure{overflow:hidden;-webkit-mask:0 0 url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAD0AAAA9CAYAAAAeYmHpAAAACXBIWXMAAAsTAAALEwEAmpwYAAAKT2lDQ1BQaG90b3Nob3AgSUNDIHByb2ZpbGUAAHjanVNnVFPpFj333vRCS4iAlEtvUhUIIFJCi4AUkSYqIQkQSoghodkVUcERRUUEG8igiAOOjoCMFVEsDIoK2AfkIaKOg6OIisr74Xuja9a89+bN/rXXPues852zzwfACAyWSDNRNYAMqUIeEeCDx8TG4eQuQIEKJHAAEAizZCFz/SMBAPh+PDwrIsAHvgABeNMLCADATZvAMByH/w/qQplcAYCEAcB0kThLCIAUAEB6jkKmAEBGAYCdmCZTAKAEAGDLY2LjAFAtAGAnf+bTAICd+Jl7AQBblCEVAaCRACATZYhEAGg7AKzPVopFAFgwABRmS8Q5ANgtADBJV2ZIALC3AMDOEAuyAAgMADBRiIUpAAR7AGDIIyN4AISZABRG8lc88SuuEOcqAAB4mbI8uSQ5RYFbCC1xB1dXLh4ozkkXKxQ2YQJhmkAuwnmZGTKBNA/g88wAAKCRFRHgg/P9eM4Ors7ONo62Dl8t6r8G/yJiYuP+5c+rcEAAAOF0ftH+LC+zGoA7BoBt/qIl7gRoXgugdfeLZrIPQLUAoOnaV/Nw+H48PEWhkLnZ2eXk5NhKxEJbYcpXff5nwl/AV/1s+X48/Pf14L7iJIEyXYFHBPjgwsz0TKUcz5IJhGLc5o9H/LcL//wd0yLESWK5WCoU41EScY5EmozzMqUiiUKSKcUl0v9k4t8s+wM+3zUAsGo+AXuRLahdYwP2SycQWHTA4vcAAPK7b8HUKAgDgGiD4c93/+8//UegJQCAZkmScQAAXkQkLlTKsz/HCAAARKCBKrBBG/TBGCzABhzBBdzBC/xgNoRCJMTCQhBCCmSAHHJgKayCQiiGzbAdKmAv1EAdNMBRaIaTcA4uwlW4Dj1wD/phCJ7BKLyBCQRByAgTYSHaiAFiilgjjggXmYX4IcFIBBKLJCDJiBRRIkuRNUgxUopUIFVIHfI9cgI5h1xGupE7yAAygvyGvEcxlIGyUT3UDLVDuag3GoRGogvQZHQxmo8WoJvQcrQaPYw2oefQq2gP2o8+Q8cwwOgYBzPEbDAuxsNCsTgsCZNjy7EirAyrxhqwVqwDu4n1Y8+xdwQSgUXACTYEd0IgYR5BSFhMWE7YSKggHCQ0EdoJNwkDhFHCJyKTqEu0JroR+cQYYjIxh1hILCPWEo8TLxB7iEPENyQSiUMyJ7mQAkmxpFTSEtJG0m5SI+ksqZs0SBojk8naZGuyBzmULCAryIXkneTD5DPkG+Qh8lsKnWJAcaT4U+IoUspqShnlEOU05QZlmDJBVaOaUt2ooVQRNY9aQq2htlKvUYeoEzR1mjnNgxZJS6WtopXTGmgXaPdpr+h0uhHdlR5Ol9BX0svpR+iX6AP0dwwNhhWDx4hnKBmbGAcYZxl3GK+YTKYZ04sZx1QwNzHrmOeZD5lvVVgqtip8FZHKCpVKlSaVGyovVKmqpqreqgtV81XLVI+pXlN9rkZVM1PjqQnUlqtVqp1Q61MbU2epO6iHqmeob1Q/pH5Z/YkGWcNMw09DpFGgsV/jvMYgC2MZs3gsIWsNq4Z1gTXEJrHN2Xx2KruY/R27iz2qqaE5QzNKM1ezUvOUZj8H45hx+Jx0TgnnKKeX836K3hTvKeIpG6Y0TLkxZVxrqpaXllirSKtRq0frvTau7aedpr1Fu1n7gQ5Bx0onXCdHZ4/OBZ3nU9lT3acKpxZNPTr1ri6qa6UbobtEd79up+6Ynr5egJ5Mb6feeb3n+hx9L/1U/W36p/VHDFgGswwkBtsMzhg8xTVxbzwdL8fb8VFDXcNAQ6VhlWGX4YSRudE8o9VGjUYPjGnGXOMk423GbcajJgYmISZLTepN7ppSTbmmKaY7TDtMx83MzaLN1pk1mz0x1zLnm+eb15vft2BaeFostqi2uGVJsuRaplnutrxuhVo5WaVYVVpds0atna0l1rutu6cRp7lOk06rntZnw7Dxtsm2qbcZsOXYBtuutm22fWFnYhdnt8Wuw+6TvZN9un2N/T0HDYfZDqsdWh1+c7RyFDpWOt6azpzuP33F9JbpL2dYzxDP2DPjthPLKcRpnVOb00dnF2e5c4PziIuJS4LLLpc+Lpsbxt3IveRKdPVxXeF60vWdm7Obwu2o26/uNu5p7ofcn8w0nymeWTNz0MPIQ+BR5dE/C5+VMGvfrH5PQ0+BZ7XnIy9jL5FXrdewt6V3qvdh7xc+9j5yn+M+4zw33jLeWV/MN8C3yLfLT8Nvnl+F30N/I/9k/3r/0QCngCUBZwOJgUGBWwL7+Hp8Ib+OPzrbZfay2e1BjKC5QRVBj4KtguXBrSFoyOyQrSH355jOkc5pDoVQfujW0Adh5mGLw34MJ4WHhVeGP45wiFga0TGXNXfR3ENz30T6RJZE3ptnMU85ry1KNSo+qi5qPNo3ujS6P8YuZlnM1VidWElsSxw5LiquNm5svt/87fOH4p3iC+N7F5gvyF1weaHOwvSFpxapLhIsOpZATIhOOJTwQRAqqBaMJfITdyWOCnnCHcJnIi/RNtGI2ENcKh5O8kgqTXqS7JG8NXkkxTOlLOW5hCepkLxMDUzdmzqeFpp2IG0yPTq9MYOSkZBxQqohTZO2Z+pn5mZ2y6xlhbL+xW6Lty8elQfJa7OQrAVZLQq2QqboVFoo1yoHsmdlV2a/zYnKOZarnivN7cyzytuQN5zvn//tEsIS4ZK2pYZLVy0dWOa9rGo5sjxxedsK4xUFK4ZWBqw8uIq2Km3VT6vtV5eufr0mek1rgV7ByoLBtQFr6wtVCuWFfevc1+1dT1gvWd+1YfqGnRs+FYmKrhTbF5cVf9go3HjlG4dvyr+Z3JS0qavEuWTPZtJm6ebeLZ5bDpaql+aXDm4N2dq0Dd9WtO319kXbL5fNKNu7g7ZDuaO/PLi8ZafJzs07P1SkVPRU+lQ27tLdtWHX+G7R7ht7vPY07NXbW7z3/T7JvttVAVVN1WbVZftJ+7P3P66Jqun4lvttXa1ObXHtxwPSA/0HIw6217nU1R3SPVRSj9Yr60cOxx++/p3vdy0NNg1VjZzG4iNwRHnk6fcJ3/ceDTradox7rOEH0x92HWcdL2pCmvKaRptTmvtbYlu6T8w+0dbq3nr8R9sfD5w0PFl5SvNUyWna6YLTk2fyz4ydlZ19fi753GDborZ752PO32oPb++6EHTh0kX/i+c7vDvOXPK4dPKy2+UTV7hXmq86X23qdOo8/pPTT8e7nLuarrlca7nuer21e2b36RueN87d9L158Rb/1tWeOT3dvfN6b/fF9/XfFt1+cif9zsu72Xcn7q28T7xf9EDtQdlD3YfVP1v+3Njv3H9qwHeg89HcR/cGhYPP/pH1jw9DBY+Zj8uGDYbrnjg+OTniP3L96fynQ89kzyaeF/6i/suuFxYvfvjV69fO0ZjRoZfyl5O/bXyl/erA6xmv28bCxh6+yXgzMV70VvvtwXfcdx3vo98PT+R8IH8o/2j5sfVT0Kf7kxmTk/8EA5jz/GMzLdsAAAAgY0hSTQAAeiUAAICDAAD5/wAAgOkAAHUwAADqYAAAOpgAABdvkl/FRgAABO5JREFUeNrsm1toXFUUhr8kEDNVkzjYCwTyUCMtsfGCMBJaS7EolsDUqMUHXxQrgiBUWm94a0WpWlt9kSBGKwEh0GJpaDFEbEMJBAN9ChaUqKX1UolNG1MyWlt/H2YdmY65zJ7Z+8wE/GE/zayz1r/PXuustfbeVZIIiHbgdqANWAFcAzQALfb7GDAJXAC+AUaB48BwSKOqPJOuAe4GOoE0sKzI55wB+oADwBfAZa+sJfkYrZI+lXRe/nHent3qydaSSTdJ6pZ0SfGg23SWhXSDpJ2SphU/pk13Q7Gki/HpDmAvsJjyYhx4FDjsKljtGKR2AocqgDBmwyGzqSZE9E4A++wtVyL6gfuBjC/SSeBzIEVlYwTYAEyUSjoBDC4AwrnE1833xufy6VqgNyDhaRs+kTKba4sl/bplVb4hoAt4CBgK8Py02e6ckXUE+L5elvSRpNWSkpKqJW2UdDrQ97zDJTlJSjrrmWy3pDslXZ+nq07S1kAZ3VnjUhDpDzwp/UvSh5LWzkA2d9R71DlT2jov6XZPyrbZm11cYGrYIulIIOLt+fryA9kOjyXmCUsVC8EY8B7wY4DAtmOuQJbyOLu/SHpF0iKHQqBO0haLAb6Rmm15f+ZZ0W+SNjlWQPWSugKQ3jcT6WSgMnFU0m2OxFskHQ1QjibzffpBSzl9YxXwPLDEQWYMeAf4yaMdCeN4RUbWGTAfTgNbrSFYKL4E3vZsR2duIKuNoQNyTtIjZfbvaeNKtSXpCcKiEXgZuMNB5ndb5oMel3gqWt5xlY3LgVeBZgeZ74C3PPp3e0T61hjr3XuALUC9g8yg+bePBn1bRLo5RtI11szb5CDzhzUiuzzob45IN8Xc3Wi0z9haB5kpYBdwrETdTRHpZBnaOi3AG8BKB5mT1hwYKUFvMiJdQ3mwBngKuNrx+725RPdy6nv7xgXgZ8cAVQfcVKrialNeDvRacJp2IPwk8H6JE1020l9ZYJpwkLkL2FZiDJqMSJ+JmfBpK+y/dZC5AXgJWFqi7vGI9KkYCU8B7wIDDjL1wAtRNlUiTkWkR2Mk3QN8QuEnCxLA48BjnvSPRqSHYyJ8xPz4nIPMevNjXxiOSstEDKXl95LWOJaWN0oa8lxaJqLSMhNoeyX3M/Gmo45G4DlgtUc7hozrv8nJgUCELwEfA/sd697NHv04wv78FnBS0p8BlvVBSUsdl/V91kIO3hicoIizGwU0ALYDvzrIrLDAtcyzLYevSIQCNfu/lvSA4xtutF3NEEjNtZc14EnJE5KucyC8SNKzkv4OQHhgvr2s1zwtp/XAw8DNzHMqwHCvtZGqAgTT/3KaYdb3epzlHyQ9LWmVpKtmecsrPX+Pc9FTrk15STppm3O3SLo2z497AhF22pRHUjqQIZOSthv5JZKeCeTHMg7OZ0N3B0xLRyS9KOlYoOfvLvZsaC1w0ILMQkI/sBG4ONsf/j88NwMyZI8ejiwQwhso4HxoId3QCZu9/gpf0usK7bkV2gLOmJ/sqUDCe8y2TKECxRxyT5PdW0qWmewE2X2xvmL63q7oI7vtustldj0iY293eTGEZ0tDXUazncqLC92ms6y3daLRJqlX0lQAolP27DZfV5R8X0arJXsZLY2fy2h9ZC+jXfRppG/S+Wi3xKbVRoLshZPmnOb7uPnpCRvHAzcqg5OuSPwzAGYd6fed/rrcAAAAAElFTkSuQmCC') no-repeat;-webkit-mask-size:1.5em;background-image:none;background-color:#419cdb;background-image:-webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #81bde7), color-stop(3%, #56a7df), color-stop(100%, #2b91d7));background-image:-webkit-linear-gradient(top, #81bde7,#56a7df 3%,#2b91d7);background-image:linear-gradient(top, #81bde7,#56a7df 3%,#2b91d7);width:1.5em;height:1.5em;bottom:0.5em}.x-list-round .x-list-header{color:#777;font-size:1em;font-weight:bold;padding-left:26px;line-height:1.7em;background-image:-webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #eeeeee), color-stop(30%, rgba(238,238,238,0.9)), color-stop(100%, rgba(238,238,238,0.4)));background-image:-webkit-linear-gradient(top, #eeeeee,rgba(238,238,238,0.9) 30%,rgba(238,238,238,0.4));background-image:linear-gradient(top, #eeeeee,rgba(238,238,238,0.9) 30%,rgba(238,238,238,0.4))}.x-list-round .x-list-container{padding:13px 13px 0 13px}.x-list-round .x-list-container .x-list-header{padding-left:13px;background-image:none}.x-list-round.x-list-ungrouped .x-list-item-label,.x-list-round.x-list-grouped .x-list-item-label{border:solid #DDDDDD;border-width:1px 1px 0 1px;background:#fff}.x-list-round.x-list-ungrouped .x-list-item:first-child .x-list-item-label{-webkit-border-top-left-radius:0.4em;border-top-left-radius:0.4em;-webkit-border-top-right-radius:0.4em;border-top-right-radius:0.4em}.x-list-round.x-list-ungrouped .x-list-item:last-child{margin-bottom:13px}.x-list-round.x-list-ungrouped .x-list-item:last-child .x-list-item-label{-webkit-border-bottom-left-radius:0.4em;border-bottom-left-radius:0.4em;-webkit-border-bottom-right-radius:0.4em;border-bottom-right-radius:0.4em;border-width:1px}.x-list-round.x-list-grouped .x-list-header-item .x-list-item-label{-webkit-border-top-left-radius:0.4em;border-top-left-radius:0.4em;-webkit-border-top-right-radius:0.4em;border-top-right-radius:0.4em}.x-list-round.x-list-grouped .x-list-footer-item{margin-bottom:13px}.x-list-round.x-list-grouped .x-list-footer-item .x-list-item-label{border-width:1px;-webkit-border-bottom-left-radius:0.4em;border-bottom-left-radius:0.4em;-webkit-border-bottom-right-radius:0.4em;border-bottom-right-radius:0.4em}.x-dataview-inlineblock .x-dataview-item{display:inline-block !important}.x-dataview-nowrap .x-dataview-container{white-space:nowrap !important}.x-list-inlineblock .x-list-item{display:inline-block !important}.x-list-nowrap .x-list-inner{width:auto}.x-list-nowrap .x-list-container{white-space:nowrap !important}.x-list-paging{height:50px}.x-list-paging .x-loading-spinner{display:none;margin:auto}.x-list-paging .x-list-paging-msg{text-align:center;color:#006bb6;padding-top:10px;clear:both;-webkit-border-radius:6px;border-radius:6px}.x-list-paging.x-loading .x-loading-spinner{display:block}.x-list-paging.x-loading .x-list-paging-msg{display:none}.x-list-pullrefresh{display:-webkit-box;display:box;-webkit-box-orient:horizontal;box-orient:horizontal;-webkit-box-align:center;box-align:center;-webkit-box-pack:center;box-pack:center;position:absolute;top:-5em;left:0;width:100%;height:4.5em}.x-list-pullrefresh .x-loading-spinner{display:none}.x-list-pullrefresh-arrow{width:2.5em;height:4.5em;background:center center url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAA8CAYAAAAUufjgAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAjFJREFUeNrsmU8oREEYwOexdtNuKBfFwdVhCyfuysnFiXISS+1BLopyUpKLXETkRLaUi1LK3Q2lpPbiQLnIn03a/Hm+z86Ttv0zM++bfbOar36Hbad5v535Zp7v47iuy0wOpyoEHccRHV9L9NxPkUE/bhKCOKiOSPAdn69DsJ5I8E2HYA0QJRJ8Bb50CDYRCT7pEMQD0kwk+CByUFQEW4gE73UIhoA2IsFb4ENEMCQ5MdU1IxwygpT3oKNLMGyyYFVscdhusc8tDpu+xRG7xf95BW0O2kNiV1AgIvaQ2BzUJNgJNJYZGyUU7OG1cal4Bi68oqkDPszy2teEwJp5Cdyu/lZ1g8CwIYJ7wEF+2YmrNw90Byx3BizgKhaqizEP1wg7CLLxCEzy/CtauMeBlQDyEfNuGrgU6SyM8F9SyVgHdmRaH6tAb4XkToEp2d4M5mOK0TWMigU2koa8vJMRZPxEb2ss2LEVPMpPLlMRxBgDZjQJLgNbxb6Uab9tAn3EcifAeKkBMoLY+j0GWonk7oB+lmsFkwhidAGHBPmIeTcAnJcbKCuIMQEs+hScAzZEBqoIYuzyFVCJI36lMJ2CDfxibZeUu+EX/4uMIFP8ZyLejxkgK0hG5a8kP4IYSZbr1IuQVHmAX0HGX4VuGfZVJ6cQxPd1uoRcWqDW0SroFVzZAnJZ/h0LWhAjUUAw4XdSSsH8fExRTEgtGAOuOTETBb16Jk412e+bxOSwglYw6PgWYABvLk8P7zGJFwAAAABJRU5ErkJggg==') no-repeat;background-size:2em 3em;-webkit-transform:rotate(0deg);transform:rotate(0deg)}.x-list-pullrefresh-release .x-list-pullrefresh-arrow{-webkit-transform:rotate(-180deg);transform:rotate(-180deg)}.x-list-pullrefresh-wrap{width:20em;font-size:0.7em}.x-list-pullrefresh-message{font-weight:bold;font-size:1.3em;margin-bottom:0.1em;text-align:center}.x-list-pullrefresh-updated{text-align:center}html,body{width:100%;height:100%}.x-translatable{position:absolute !important;top:100% !important;left:100% !important;z-index:1}.x-translatable-container{position:relative}.x-translatable-wrapper{width:100%;height:100%;position:absolute;overflow:hidden}.x-translatable-stretcher{width:300%;height:300%;position:absolute;visibility:hidden;z-index:-1}.x-translatable-nested-stretcher{width:100%;height:100%;left:100%;top:100%;position:absolute;visibility:hidden;z-index:-1}.x-layout-fit,.x-layout-card{position:relative;overflow:hidden}.x-layout-fit-item,.x-layout-card-item,.x-layout-card-container{position:absolute !important;width:100%;height:100%;left:0;top:0}.x-layout-card-item{position:absolute !important;top:0;right:0;bottom:0;left:0}.x-layout-hbox,.x-layout-vbox{display:-webkit-box}.x-layout-hbox > *,.x-layout-vbox > *{-webkit-box-flex:0}.x-layout-hbox{-webkit-box-orient:horizontal}.x-layout-vbox{-webkit-box-orient:vertical}.x-layout-hbox > .x-layout-box-item{width:0 !important}.x-layout-vbox > .x-layout-box-item{height:0 !important}.x-table-inner{display:table !important;width:100%;height:100%}.x-table-inner.x-table-fixed{table-layout:fixed !important}.x-table-row{display:table-row !important}.x-table-row > *{display:table-cell !important;vertical-align:middle}.x-container,.x-body{display:-webkit-box}.x-body{overflow:hidden;-webkit-box-flex:1;min-width:100%;min-height:100%}.x-body > .x-inner,.x-container > .x-inner{-webkit-box-flex:1;min-width:100%;min-height:100%;position:relative}.x-docking-horizontal{display:-webkit-box;-webkit-box-flex:1;-webkit-box-orient:horizontal;min-width:100%;min-height:100%}.x-docking-vertical{display:-webkit-box;-webkit-box-flex:1;-webkit-box-orient:vertical;min-width:100%;min-height:100%}.x-centered{position:absolute !important;width:100%;height:100%;display:-webkit-box;-webkit-box-align:center;-webkit-box-pack:center}.x-floating{position:absolute !important}.x-centered > *{position:relative !important;-webkit-box-flex:0 !important}.x-size-change-detector{visibility:hidden;position:absolute;left:0;top:0;z-index:-1;width:100%;height:100%;overflow:hidden}.x-size-change-detector > *{visibility:hidden}.x-size-change-detector-shrink > *{width:200%;height:200%}.x-size-change-detector-expand > *{width:100000px;height:100000px}.x-scroll-view{position:relative;display:block}.x-scroll-container{position:absolute;overflow:hidden;width:100%;height:100%}.x-scroll-scroller{position:absolute;min-width:100%;min-height:100%}.x-ios .x-scroll-scroller{-webkit-transform:translate3d(0, 0, 0)}.x-scroll-stretcher{position:absolute;visibility:hidden}.x-scroll-bar-grid-wrapper{position:absolute;width:100%;height:100%}.x-scroll-bar-grid{display:table;width:100%;height:100%}.x-scroll-bar-grid > *{display:table-row}.x-scroll-bar-grid > * > *{display:table-cell}.x-scroll-bar-grid > :first-child > :first-child{width:100%;height:100%}.x-scroll-bar-grid > :first-child > :nth-child(2){padding:3px 3px 0 0}.x-scroll-bar-grid > :nth-child(2) > :first-child{padding:0 0 3px 3px}.x-scroll-bar{position:relative;overflow:hidden}.x-scroll-bar-stretcher{position:absolute;visibility:hidden;width:100%;height:100%}.x-scroll-bar-x{width:100%}.x-scroll-bar-x > .x-scroll-bar-stretcher{width:300%}.x-scroll-bar-x.active{height:6px}.x-scroll-bar-y{height:100%}.x-scroll-bar-y > .x-scroll-bar-stretcher{height:300%}.x-scroll-bar-y.active{width:6px}.x-scroll-indicator{background:#333;position:absolute;z-index:2;opacity:0.5}.x-scroll-indicator.default{-webkit-border-top-left-radius:3px;border-top-left-radius:3px;-webkit-border-top-right-radius:3px;border-top-right-radius:3px;-webkit-border-bottom-left-radius:3px;border-bottom-left-radius:3px;-webkit-border-bottom-right-radius:3px;border-bottom-right-radius:3px}.x-list-light .x-scroll-indicator,.x-dataview-light .x-scroll-indicator{background:#fff;opacity:1}.x-scroll-indicator-x{height:100%}.x-scroll-indicator-y{width:100%}.x-scroll-indicator.csstransform{background:none}.x-scroll-indicator.csstransform > *{position:absolute;background-color:#333}.x-scroll-indicator.csstransform > :nth-child(2){-webkit-transform-origin:0% 0%;background:none;content:url(data:image/bmp;base64,Qk08AAAAAAAAADYAAAAoAAAAAQAAAAEAAAABABAAAAAAAAYAAAASCwAAEgsAAAAAAAAAAAAAxhgAAAAA)}.x-scroll-indicator.csstransform.x-scroll-indicator-light > *{background-color:#eee}.x-scroll-indicator.csstransform.x-scroll-indicator-light > :nth-child(2){content:url(data:image/bmp;base64,Qk08AAAAAAAAADYAAAAoAAAAAQAAAAEAAAABABAAAAAAAAYAAAASCwAAEgsAAAAAAAAAAAAAvXcAAAAA)}.x-scroll-indicator.csstransform.x-scroll-indicator-y > *{width:100%}.x-scroll-indicator.csstransform.x-scroll-indicator-y > :first-child{height:3px;-webkit-border-top-left-radius:3px;border-top-left-radius:3px;-webkit-border-top-right-radius:3px;border-top-right-radius:3px}.x-scroll-indicator.csstransform.x-scroll-indicator-y > :nth-child(2){height:1px}.x-scroll-indicator.csstransform.x-scroll-indicator-y > :last-child{height:3px;-webkit-border-bottom-left-radius:3px;border-bottom-left-radius:3px;-webkit-border-bottom-right-radius:3px;border-bottom-right-radius:3px}.x-scroll-indicator.csstransform.x-scroll-indicator-x > *{height:100%}.x-scroll-indicator.csstransform.x-scroll-indicator-x > :first-child{width:3px;-webkit-border-top-left-radius:3px;border-top-left-radius:3px;-webkit-border-bottom-left-radius:3px;border-bottom-left-radius:3px}.x-scroll-indicator.csstransform.x-scroll-indicator-x > :nth-child(2){width:1px}.x-scroll-indicator.csstransform.x-scroll-indicator-x > :last-child{width:3px;-webkit-border-top-right-radius:3px;border-top-right-radius:3px;-webkit-border-bottom-right-radius:3px;border-bottom-right-radius:3px}.x-carousel{position:relative;overflow:hidden}.x-carousel-item{position:absolute;width:100%;height:100%}.x-carousel-item > *{position:absolute;width:100%;height:100%}.x-carousel-indicator{padding:0;-webkit-border-radius:0;border-radius:0;-webkit-box-shadow:none;background-color:transparent;background-image:none}.x-carousel-indicator{-webkit-box-flex:1;display:-webkit-box;display:box;-webkit-box-pack:center;box-pack:center;-webkit-box-align:center;box-align:center}.x-carousel-indicator span{display:block;width:0.5em;height:0.5em;-webkit-border-radius:0.25em;border-radius:0.25em;margin:0.2em}.x-carousel-indicator-horizontal{height:1.5em;width:100%}.x-carousel-indicator-vertical{-webkit-box-orient:vertical;box-orient:vertical;width:1.5em;height:100%}.x-carousel-indicator-light span{background-image:none;background-color:rgba(255, 255, 255, 0.1)}.x-carousel-indicator-light span.x-carousel-indicator-active{background-image:none;background-color:rgba(255, 255, 255, 0.3)}.x-carousel-indicator-dark span{background-image:none;background-color:rgba(0, 0, 0, 0.1)}.x-carousel-indicator-dark span.x-carousel-indicator-active{background-image:none;background-color:rgba(0, 0, 0, 0.3)}.x-form .x-scroll-container{background-color:#eeeeee}.x-form .x-scroll-container > .x-inner{padding:1em}.x-form-label{text-shadow:#fff 0 1px 1px;color:#333333;text-shadow:rgba(255, 255, 255, 0.25) 0 0.08em 0;padding:0.6em;display:none !important;background-color:#f7f7f7}.x-form-label span{font-size:.8em;font-weight:bold}.x-form-label-nowrap .x-form-label{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.x-field{min-height:2.5em;background:#fff}.x-field .x-field-input{position:relative;min-width:3.7em}.x-field .x-field-input,.x-field .x-input-el{width:100%}.x-field.x-field-labeled .x-form-label{display:block !important}.x-field:last-child{border-bottom:0}.x-label-align-left .x-component-outer,.x-label-align-right .x-component-outer{-webkit-box-flex:1;box-flex:1}.x-label-align-left:first-child .x-form-label{-webkit-border-top-left-radius:0.4em;border-top-left-radius:0.4em}.x-label-align-left:last-child .x-form-label{-webkit-border-bottom-left-radius:0.4em;border-bottom-left-radius:0.4em}.x-label-align-right{-webkit-box-direction:reverse;box-direction:reverse}.x-label-align-right:first-child .x-form-label{-webkit-border-top-right-radius:0.4em;border-top-right-radius:0.4em}.x-label-align-right:last-child{border-bottom:0}.x-label-align-right:last-child .x-form-label{-webkit-border-bottom-right-radius:0.4em;border-bottom-right-radius:0.4em}.x-label-align-top,.x-label-align-bottom{-webkit-box-orient:vertical;box-orient:vertical}.x-label-align-top:first-child .x-form-label{-webkit-border-top-left-radius:0.4em;border-top-left-radius:0.4em;-webkit-border-top-right-radius:0.4em;border-top-right-radius:0.4em}.x-label-align-bottom{-webkit-box-direction:reverse;box-direction:reverse}.x-label-align-bottom:last-child .x-form-label{-webkit-border-bottom-left-radius:0.4em;border-bottom-left-radius:0.4em;-webkit-border-bottom-right-radius:0.4em;border-bottom-right-radius:0.4em}.x-input-el{padding:.4em;min-height:2.5em;display:block;border-width:0;background:transparent;-webkit-appearance:none}.x-field-mask{position:absolute;top:0;right:0;bottom:0;left:0}.x-field-required label:after,.x-field-required .x-form-label:after{content:"*";display:inline}.x-item-disabled label:after,.x-item-disabled .x-form-label:after{color:#666 !important}.x-field-textarea textarea{min-height:6em;padding-top:.5em}.x-checkmark-base,.x-field-checkbox .x-input-el:after,.x-field-checkbox .x-input-el:checked:after,.x-select-overlay .x-item-selected .x-list-item-label:before,.x-select-overlay .x-item-selected .x-list-item-label:after{content:"";position:absolute;width:1.4em;height:1.4em;top:50%;left:auto;right:1.1em;-webkit-mask-size:1.4em;-webkit-mask-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAAE+klEQVRoBe2aS28URxRGsY0h2FmwCQuEWLHjvUNgZAQ4PMwrEkIRIGEgySKwB8QvYIvEP+ANO0CwsJAA88wGBEKBZJUVQkJCQrwJ5nxN31Z5pnpc7e4ZT9vT0peqqanquqfurVvlIW3Dw8NTJtPTPplgxdoCnugeb3m45eEJtgJTJwJPGw8cP8V6TfmC4/Z/H9uEAAZsIdqHZiMBn2UNbvigSw8M2AIAD6PtqBPpmYe+8t1NoL9GLfYf3bTKKhiWo9PoA9KV0dUgn/tRh8tXWg/Hnj0KUB8yz1JNnjXUuhFd264A/f0O7dKXpQ7EIiTPfkKuVyvrSlx3US+KPF26cMbwxeg8Gg3W4LWHFd6rUUepQprQnI/Rh9A25AtjmqseHVkK7w59UxpgYFdg7wH0CwqFpWvyrKI23GZ7OWluwgqwOnqOobVoWh4Tm97DwCpBHUFp2TiUX3v5QVMnLQzMmqAsUVWWyta3UX/TAmOcwjjk6KmE830W7GbU0ZTAGKYEJdj3yAcQ2qYw1jmsG9e0KF8122UDw/SHwFX0EYWC+fpZGG/hPcn1sqk8jGHas+dQ6KXCB6o2g91IPfKsObZpgDGsqAT1hXdpz25A7QZqZU1gBsxFSh5zbEA9yniOU5R5PSvvCnYTSsLYtdkLTGf9uKdD/gS6gI6jPndgUXXe24OKSFAK4zsoSVA+G6uAGaC758/oBrIs+Zb6rbg9up35Xpa1jffpUqEEldezysbJ0VPLjhHADOpEfUiw2gtuUtAKDiGtYNXeqDWJ7zveYQnqM3V3nqx1s2s97xmRoLzzWqMgkLLaTVQJa0ZoJe+hXjRmaMYKVlslr2dlp5wgu4PsiTyszmg5qgVr0CqvoZW2WFlKxhV5gxJsdIMKtYH+Eew6yksoNLy0soJeFzqR+vEI9gx6h9wFzFoPSlA+25g3SlChnnUNU3grkWmxRg0n+ihBnUR5w9j2bCbPGjzzR3sgbc+6gL66TV4zkTHHEqSfZSzr+94V0mbzKUF1GkSWknG5QktGyoj7qBdVeZo2S1Ch2yUNXOMVUcEJyrcQjOeP4vzQCu9BpBtOck5T70HybN4w1iJcR7ouem9QPjhfG+On7EBPUNrKhrYLWp7+FS1FCjtdKvJ6VvM/Q9o2uWC1AHq60QB6hELh0voJ+im6iHReF+FZwe5HP/g8lrXNzuEfeeFu9C9Kg8nSrr9lBZ9ljK/v37xjL5qRFSytf3K15KXy9EH0D/JN3ui2Qj1rC5AAq4FnJvoDPUSNBnTnUy4YQF1maFHlCOAYuouJFN6PkWtEo+ryrH5sL2TPVi5UFXAMrfDegxrtae3ZfWh6paFFffYCx9BKZLtQo/a0YLXIhSUo3yKlAsfQ8vSBBkALtrCjxwdqbTWBY2glst9REee0Lw/ULUEZpFuOChxD1yuRybNbUV0SlAtq9SDgGFp7ushEJlhdKuqWoAzSLYOBHeidGPkc+cIztE2wA6iuCcoFtXom4Bha4f0nGmv2FqyOnoaFscFG9rsfQusYq0T2G8qayASrbdEdOlfR/TJ72AzAaHla5/QD9BnVCucvfK/fjZXtx8WzZneu/+WBf53XOb0G6XetHjQXyfv2vKLyH7qLLqMhJn5DOW5PLmBZDfRUilloGUoD/ovvXgIrT4/rkxt4XK0fw+TtYxhT6iEt4FK7L8D4locDFqnUXSadh78Bx5bEl2CLG+8AAAAASUVORK5CYII=');margin-top:-0.7em}.x-radiomark-base,.x-field-radio .x-input-el:before,.x-field-radio .x-input-el:after{content:"";position:absolute;width:1.4em;height:1.4em;top:50%;left:auto;right:1.1em;margin-top:-0.7em;-webkit-mask-size:1.4em;-webkit-mask-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAAACXBIWXMAAAsTAAALEwEAmpwYAAAKT2lDQ1BQaG90b3Nob3AgSUNDIHByb2ZpbGUAAHjanVNnVFPpFj333vRCS4iAlEtvUhUIIFJCi4AUkSYqIQkQSoghodkVUcERRUUEG8igiAOOjoCMFVEsDIoK2AfkIaKOg6OIisr74Xuja9a89+bN/rXXPues852zzwfACAyWSDNRNYAMqUIeEeCDx8TG4eQuQIEKJHAAEAizZCFz/SMBAPh+PDwrIsAHvgABeNMLCADATZvAMByH/w/qQplcAYCEAcB0kThLCIAUAEB6jkKmAEBGAYCdmCZTAKAEAGDLY2LjAFAtAGAnf+bTAICd+Jl7AQBblCEVAaCRACATZYhEAGg7AKzPVopFAFgwABRmS8Q5ANgtADBJV2ZIALC3AMDOEAuyAAgMADBRiIUpAAR7AGDIIyN4AISZABRG8lc88SuuEOcqAAB4mbI8uSQ5RYFbCC1xB1dXLh4ozkkXKxQ2YQJhmkAuwnmZGTKBNA/g88wAAKCRFRHgg/P9eM4Ors7ONo62Dl8t6r8G/yJiYuP+5c+rcEAAAOF0ftH+LC+zGoA7BoBt/qIl7gRoXgugdfeLZrIPQLUAoOnaV/Nw+H48PEWhkLnZ2eXk5NhKxEJbYcpXff5nwl/AV/1s+X48/Pf14L7iJIEyXYFHBPjgwsz0TKUcz5IJhGLc5o9H/LcL//wd0yLESWK5WCoU41EScY5EmozzMqUiiUKSKcUl0v9k4t8s+wM+3zUAsGo+AXuRLahdYwP2SycQWHTA4vcAAPK7b8HUKAgDgGiD4c93/+8//UegJQCAZkmScQAAXkQkLlTKsz/HCAAARKCBKrBBG/TBGCzABhzBBdzBC/xgNoRCJMTCQhBCCmSAHHJgKayCQiiGzbAdKmAv1EAdNMBRaIaTcA4uwlW4Dj1wD/phCJ7BKLyBCQRByAgTYSHaiAFiilgjjggXmYX4IcFIBBKLJCDJiBRRIkuRNUgxUopUIFVIHfI9cgI5h1xGupE7yAAygvyGvEcxlIGyUT3UDLVDuag3GoRGogvQZHQxmo8WoJvQcrQaPYw2oefQq2gP2o8+Q8cwwOgYBzPEbDAuxsNCsTgsCZNjy7EirAyrxhqwVqwDu4n1Y8+xdwQSgUXACTYEd0IgYR5BSFhMWE7YSKggHCQ0EdoJNwkDhFHCJyKTqEu0JroR+cQYYjIxh1hILCPWEo8TLxB7iEPENyQSiUMyJ7mQAkmxpFTSEtJG0m5SI+ksqZs0SBojk8naZGuyBzmULCAryIXkneTD5DPkG+Qh8lsKnWJAcaT4U+IoUspqShnlEOU05QZlmDJBVaOaUt2ooVQRNY9aQq2htlKvUYeoEzR1mjnNgxZJS6WtopXTGmgXaPdpr+h0uhHdlR5Ol9BX0svpR+iX6AP0dwwNhhWDx4hnKBmbGAcYZxl3GK+YTKYZ04sZx1QwNzHrmOeZD5lvVVgqtip8FZHKCpVKlSaVGyovVKmqpqreqgtV81XLVI+pXlN9rkZVM1PjqQnUlqtVqp1Q61MbU2epO6iHqmeob1Q/pH5Z/YkGWcNMw09DpFGgsV/jvMYgC2MZs3gsIWsNq4Z1gTXEJrHN2Xx2KruY/R27iz2qqaE5QzNKM1ezUvOUZj8H45hx+Jx0TgnnKKeX836K3hTvKeIpG6Y0TLkxZVxrqpaXllirSKtRq0frvTau7aedpr1Fu1n7gQ5Bx0onXCdHZ4/OBZ3nU9lT3acKpxZNPTr1ri6qa6UbobtEd79up+6Ynr5egJ5Mb6feeb3n+hx9L/1U/W36p/VHDFgGswwkBtsMzhg8xTVxbzwdL8fb8VFDXcNAQ6VhlWGX4YSRudE8o9VGjUYPjGnGXOMk423GbcajJgYmISZLTepN7ppSTbmmKaY7TDtMx83MzaLN1pk1mz0x1zLnm+eb15vft2BaeFostqi2uGVJsuRaplnutrxuhVo5WaVYVVpds0atna0l1rutu6cRp7lOk06rntZnw7Dxtsm2qbcZsOXYBtuutm22fWFnYhdnt8Wuw+6TvZN9un2N/T0HDYfZDqsdWh1+c7RyFDpWOt6azpzuP33F9JbpL2dYzxDP2DPjthPLKcRpnVOb00dnF2e5c4PziIuJS4LLLpc+Lpsbxt3IveRKdPVxXeF60vWdm7Obwu2o26/uNu5p7ofcn8w0nymeWTNz0MPIQ+BR5dE/C5+VMGvfrH5PQ0+BZ7XnIy9jL5FXrdewt6V3qvdh7xc+9j5yn+M+4zw33jLeWV/MN8C3yLfLT8Nvnl+F30N/I/9k/3r/0QCngCUBZwOJgUGBWwL7+Hp8Ib+OPzrbZfay2e1BjKC5QRVBj4KtguXBrSFoyOyQrSH355jOkc5pDoVQfujW0Adh5mGLw34MJ4WHhVeGP45wiFga0TGXNXfR3ENz30T6RJZE3ptnMU85ry1KNSo+qi5qPNo3ujS6P8YuZlnM1VidWElsSxw5LiquNm5svt/87fOH4p3iC+N7F5gvyF1weaHOwvSFpxapLhIsOpZATIhOOJTwQRAqqBaMJfITdyWOCnnCHcJnIi/RNtGI2ENcKh5O8kgqTXqS7JG8NXkkxTOlLOW5hCepkLxMDUzdmzqeFpp2IG0yPTq9MYOSkZBxQqohTZO2Z+pn5mZ2y6xlhbL+xW6Lty8elQfJa7OQrAVZLQq2QqboVFoo1yoHsmdlV2a/zYnKOZarnivN7cyzytuQN5zvn//tEsIS4ZK2pYZLVy0dWOa9rGo5sjxxedsK4xUFK4ZWBqw8uIq2Km3VT6vtV5eufr0mek1rgV7ByoLBtQFr6wtVCuWFfevc1+1dT1gvWd+1YfqGnRs+FYmKrhTbF5cVf9go3HjlG4dvyr+Z3JS0qavEuWTPZtJm6ebeLZ5bDpaql+aXDm4N2dq0Dd9WtO319kXbL5fNKNu7g7ZDuaO/PLi8ZafJzs07P1SkVPRU+lQ27tLdtWHX+G7R7ht7vPY07NXbW7z3/T7JvttVAVVN1WbVZftJ+7P3P66Jqun4lvttXa1ObXHtxwPSA/0HIw6217nU1R3SPVRSj9Yr60cOxx++/p3vdy0NNg1VjZzG4iNwRHnk6fcJ3/ceDTradox7rOEH0x92HWcdL2pCmvKaRptTmvtbYlu6T8w+0dbq3nr8R9sfD5w0PFl5SvNUyWna6YLTk2fyz4ydlZ19fi753GDborZ752PO32oPb++6EHTh0kX/i+c7vDvOXPK4dPKy2+UTV7hXmq86X23qdOo8/pPTT8e7nLuarrlca7nuer21e2b36RueN87d9L158Rb/1tWeOT3dvfN6b/fF9/XfFt1+cif9zsu72Xcn7q28T7xf9EDtQdlD3YfVP1v+3Njv3H9qwHeg89HcR/cGhYPP/pH1jw9DBY+Zj8uGDYbrnjg+OTniP3L96fynQ89kzyaeF/6i/suuFxYvfvjV69fO0ZjRoZfyl5O/bXyl/erA6xmv28bCxh6+yXgzMV70VvvtwXfcdx3vo98PT+R8IH8o/2j5sfVT0Kf7kxmTk/8EA5jz/GMzLdsAAAAgY0hSTQAAeiUAAICDAAD5/wAAgOkAAHUwAADqYAAAOpgAABdvkl/FRgAAApRJREFUeNrs279v2kAUwPHvVe0KZUDKwGCJiQwMmbKUBbqjKP8FmfpP8A/kT8jCUrFXWdwhWwaGICEhZWBjaVhr6TrkLFmufRjbh33nPOltyPij84+7d89CSkmT4hMNi8aBP5s6sBDiK3AFDIEB0Ad6QBdoqZ8dgD2wA7bAGlgBz1LKP0bOq8x7WAjRBr4DY2AEXOY81AvgA4/ALynlW63AQggPuAGmwLeSB+U3sAR+SilfCx9NSpk71aU5A54AaTif1H+1Cp1zAewEWJwBGs8FMDkrGLgDNhVgw9wAd8bBQAeYA0GF2DADdS4dI2DgArivATSe98BFqWA1snXERtGdMsHzGmPDnJcCVg+owAJwkOVBluXVs7EAG316T3KB1aRiYRE2+p5u5QHPLMSGOTsJDHhnmi6anIZ6Sba09fANcG3xsvdaGY4XANQSb+rAWn+qLPrVEnBr8aUcz9ssl/TYoYrOWHtJq7LMyCHwSJlS7+GrAmWZOsalMqWChw4WKoc68MBB8EAH7jsI7uvAPQfBPR246yC4m1qXFkL8NbkbUVEEUsovjd1bioMPDhoPOvDeQfBeB945CN7pwFsHwVsdeO0geK0DrxwEr3TgZ943o12JF2VKBqs2A98hsB9vnUiaeDw6BP7fklDTaqtRtr2e5QPtozUt1UCydGB0l4nNMB+F+Pd4BR4sHt2H1I6flBGGJm2mRaIZ26WxsGpDPO2qzbohHoYVLQ+62/RUsBVNLcewp4Ch5m1LWUb3lC6e6EjXrjHt2FunCDj6IKu89TDLK7ZIJ148Km0uzTqnKKu5NDo5OXv7cNr0ymj7cAzuAT8MrbJ8dWwvLzKaVnwCALzFkbnP0dR3S434yMPGrZYPsGvxbwA7bHepWd7cEgAAAABJRU5ErkJggg==')}.x-field-checkbox .x-input-el{position:relative}.x-field-checkbox .x-input-el:after{background-color:#dddddd}.x-field-checkbox .x-input-el:checked:after{background-color:#006bb6}.x-field-radio .x-input-el{position:relative}.x-field-radio .x-input-el:before{background-color:#d0d0d0}.x-field-radio .x-input-el:after{background-color:#dddddd;-webkit-mask-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAAACXBIWXMAAAsTAAALEwEAmpwYAAAKT2lDQ1BQaG90b3Nob3AgSUNDIHByb2ZpbGUAAHjanVNnVFPpFj333vRCS4iAlEtvUhUIIFJCi4AUkSYqIQkQSoghodkVUcERRUUEG8igiAOOjoCMFVEsDIoK2AfkIaKOg6OIisr74Xuja9a89+bN/rXXPues852zzwfACAyWSDNRNYAMqUIeEeCDx8TG4eQuQIEKJHAAEAizZCFz/SMBAPh+PDwrIsAHvgABeNMLCADATZvAMByH/w/qQplcAYCEAcB0kThLCIAUAEB6jkKmAEBGAYCdmCZTAKAEAGDLY2LjAFAtAGAnf+bTAICd+Jl7AQBblCEVAaCRACATZYhEAGg7AKzPVopFAFgwABRmS8Q5ANgtADBJV2ZIALC3AMDOEAuyAAgMADBRiIUpAAR7AGDIIyN4AISZABRG8lc88SuuEOcqAAB4mbI8uSQ5RYFbCC1xB1dXLh4ozkkXKxQ2YQJhmkAuwnmZGTKBNA/g88wAAKCRFRHgg/P9eM4Ors7ONo62Dl8t6r8G/yJiYuP+5c+rcEAAAOF0ftH+LC+zGoA7BoBt/qIl7gRoXgugdfeLZrIPQLUAoOnaV/Nw+H48PEWhkLnZ2eXk5NhKxEJbYcpXff5nwl/AV/1s+X48/Pf14L7iJIEyXYFHBPjgwsz0TKUcz5IJhGLc5o9H/LcL//wd0yLESWK5WCoU41EScY5EmozzMqUiiUKSKcUl0v9k4t8s+wM+3zUAsGo+AXuRLahdYwP2SycQWHTA4vcAAPK7b8HUKAgDgGiD4c93/+8//UegJQCAZkmScQAAXkQkLlTKsz/HCAAARKCBKrBBG/TBGCzABhzBBdzBC/xgNoRCJMTCQhBCCmSAHHJgKayCQiiGzbAdKmAv1EAdNMBRaIaTcA4uwlW4Dj1wD/phCJ7BKLyBCQRByAgTYSHaiAFiilgjjggXmYX4IcFIBBKLJCDJiBRRIkuRNUgxUopUIFVIHfI9cgI5h1xGupE7yAAygvyGvEcxlIGyUT3UDLVDuag3GoRGogvQZHQxmo8WoJvQcrQaPYw2oefQq2gP2o8+Q8cwwOgYBzPEbDAuxsNCsTgsCZNjy7EirAyrxhqwVqwDu4n1Y8+xdwQSgUXACTYEd0IgYR5BSFhMWE7YSKggHCQ0EdoJNwkDhFHCJyKTqEu0JroR+cQYYjIxh1hILCPWEo8TLxB7iEPENyQSiUMyJ7mQAkmxpFTSEtJG0m5SI+ksqZs0SBojk8naZGuyBzmULCAryIXkneTD5DPkG+Qh8lsKnWJAcaT4U+IoUspqShnlEOU05QZlmDJBVaOaUt2ooVQRNY9aQq2htlKvUYeoEzR1mjnNgxZJS6WtopXTGmgXaPdpr+h0uhHdlR5Ol9BX0svpR+iX6AP0dwwNhhWDx4hnKBmbGAcYZxl3GK+YTKYZ04sZx1QwNzHrmOeZD5lvVVgqtip8FZHKCpVKlSaVGyovVKmqpqreqgtV81XLVI+pXlN9rkZVM1PjqQnUlqtVqp1Q61MbU2epO6iHqmeob1Q/pH5Z/YkGWcNMw09DpFGgsV/jvMYgC2MZs3gsIWsNq4Z1gTXEJrHN2Xx2KruY/R27iz2qqaE5QzNKM1ezUvOUZj8H45hx+Jx0TgnnKKeX836K3hTvKeIpG6Y0TLkxZVxrqpaXllirSKtRq0frvTau7aedpr1Fu1n7gQ5Bx0onXCdHZ4/OBZ3nU9lT3acKpxZNPTr1ri6qa6UbobtEd79up+6Ynr5egJ5Mb6feeb3n+hx9L/1U/W36p/VHDFgGswwkBtsMzhg8xTVxbzwdL8fb8VFDXcNAQ6VhlWGX4YSRudE8o9VGjUYPjGnGXOMk423GbcajJgYmISZLTepN7ppSTbmmKaY7TDtMx83MzaLN1pk1mz0x1zLnm+eb15vft2BaeFostqi2uGVJsuRaplnutrxuhVo5WaVYVVpds0atna0l1rutu6cRp7lOk06rntZnw7Dxtsm2qbcZsOXYBtuutm22fWFnYhdnt8Wuw+6TvZN9un2N/T0HDYfZDqsdWh1+c7RyFDpWOt6azpzuP33F9JbpL2dYzxDP2DPjthPLKcRpnVOb00dnF2e5c4PziIuJS4LLLpc+Lpsbxt3IveRKdPVxXeF60vWdm7Obwu2o26/uNu5p7ofcn8w0nymeWTNz0MPIQ+BR5dE/C5+VMGvfrH5PQ0+BZ7XnIy9jL5FXrdewt6V3qvdh7xc+9j5yn+M+4zw33jLeWV/MN8C3yLfLT8Nvnl+F30N/I/9k/3r/0QCngCUBZwOJgUGBWwL7+Hp8Ib+OPzrbZfay2e1BjKC5QRVBj4KtguXBrSFoyOyQrSH355jOkc5pDoVQfujW0Adh5mGLw34MJ4WHhVeGP45wiFga0TGXNXfR3ENz30T6RJZE3ptnMU85ry1KNSo+qi5qPNo3ujS6P8YuZlnM1VidWElsSxw5LiquNm5svt/87fOH4p3iC+N7F5gvyF1weaHOwvSFpxapLhIsOpZATIhOOJTwQRAqqBaMJfITdyWOCnnCHcJnIi/RNtGI2ENcKh5O8kgqTXqS7JG8NXkkxTOlLOW5hCepkLxMDUzdmzqeFpp2IG0yPTq9MYOSkZBxQqohTZO2Z+pn5mZ2y6xlhbL+xW6Lty8elQfJa7OQrAVZLQq2QqboVFoo1yoHsmdlV2a/zYnKOZarnivN7cyzytuQN5zvn//tEsIS4ZK2pYZLVy0dWOa9rGo5sjxxedsK4xUFK4ZWBqw8uIq2Km3VT6vtV5eufr0mek1rgV7ByoLBtQFr6wtVCuWFfevc1+1dT1gvWd+1YfqGnRs+FYmKrhTbF5cVf9go3HjlG4dvyr+Z3JS0qavEuWTPZtJm6ebeLZ5bDpaql+aXDm4N2dq0Dd9WtO319kXbL5fNKNu7g7ZDuaO/PLi8ZafJzs07P1SkVPRU+lQ27tLdtWHX+G7R7ht7vPY07NXbW7z3/T7JvttVAVVN1WbVZftJ+7P3P66Jqun4lvttXa1ObXHtxwPSA/0HIw6217nU1R3SPVRSj9Yr60cOxx++/p3vdy0NNg1VjZzG4iNwRHnk6fcJ3/ceDTradox7rOEH0x92HWcdL2pCmvKaRptTmvtbYlu6T8w+0dbq3nr8R9sfD5w0PFl5SvNUyWna6YLTk2fyz4ydlZ19fi753GDborZ752PO32oPb++6EHTh0kX/i+c7vDvOXPK4dPKy2+UTV7hXmq86X23qdOo8/pPTT8e7nLuarrlca7nuer21e2b36RueN87d9L158Rb/1tWeOT3dvfN6b/fF9/XfFt1+cif9zsu72Xcn7q28T7xf9EDtQdlD3YfVP1v+3Njv3H9qwHeg89HcR/cGhYPP/pH1jw9DBY+Zj8uGDYbrnjg+OTniP3L96fynQ89kzyaeF/6i/suuFxYvfvjV69fO0ZjRoZfyl5O/bXyl/erA6xmv28bCxh6+yXgzMV70VvvtwXfcdx3vo98PT+R8IH8o/2j5sfVT0Kf7kxmTk/8EA5jz/GMzLdsAAAAgY0hSTQAAeiUAAICDAAD5/wAAgOkAAHUwAADqYAAAOpgAABdvkl/FRgAABL1JREFUeNrk282LVnUUwPHPvY6OisKgia8Y4ihNkeREFi50oYSbEBduItQgcRnhJmlRtPAPCMTAlYswkzBpE1EtXPRiJGKmia+go/gGajqZL/NrMeehaTSb5977vEwdOPDA89zfOd/n97u/e37nnJullDRKsiybgHGYiNmYhwfDfjYGZ9CHftxNKf3eMJ+qBs6ybDZm4Tm8hGfxwggv/xE/43scwoWUUl9bAmdZthS9WI0XMbnkkL/hB+zDwZTSt5U4mlIqpQG4B9eQGqTXwsbq0v4WhMyxBLtxFQMNhK3pQNjaHbbzpgCjC+/EJtMM0EeB94UPXQ0FxmJ8FLtparH2hy+LGwKM9Tgej5XUJvogfFpfKTA240KLlvBIlvgFbC4NHEHB27FZpDbXq+HrmELAsRO/jiujALamV8LnvAjwUhxrs3t2JPf0MSytCxgL8SXujyLYmt4P3xeOCBgT8CFujkLYmt4MhgmPBUaG13CiTXfkenbuE8GSDWXsGBZa92AD5gZ8FXIB+3EAh3ESl+K76ejGoggXl8VJq/RZJhg24CCOPnR4iEfQe7hc0b98BO9iUR3BzaK45khFPlwOpjEPLWkswNe4V8Fy2o7eEiew3hij7G11L5gW/A0YHXgDZ0sauDjSiKeOCO9iSZ/OBlvHUODZ+Bi3S8Juqgp2CPSmktC3g212LdmRYUXc2GWW8eaqYYfNdJnlfTQYM+jE1pIh5PZGwQ6B3l4y5NyKzhxjY5PoKvgI+AU7NF52hK0i0hWMY/N4Xs2MjauI7EkpHWw0bdjYU/DyjmCcm8eDv6tEULFX82Rv2Cw6y9055mBSwUH2p5QON4s2bO0vePkkzMkxIyoDReSA5ktRmxMxI8dUjC84yOEWABe1OR5T85KHhJMtAC5jM8tLGr/UAuBSNnP/MykLPL0FPk9vJXB3C4C7ywLfjcC8iCxqAXBRmwO4m0dWoL/gIEtaAFzUZj8u5ziHWwUHWZZlWdNmOWwtK3j5LZzLcQo3Cg4yC2uaOLtrSiT5buBUHunMWu2oiKzNsqy3CbPbi7VFw/BgPJHHVB+PVEgReQYbmzC7G8NWEbkdjLdqO/WrOP0fTvGcDsa8NmAPvlOucNauSbwHwdYzNGs5Ge9HnPpfS9NeCrbJwxPxy+PoVUVdp10S8SmYlj+q8jANn6iuYaUdSi39wTTtn6qHawy2/FVZF+7DLrwVueEn4zA+Pj6viO92xW+rrBMfwprHlUvH4QNcN3pLpTW9Hizj/q0g3oNvlC+qtVLvBUPPSFse1hls6R2twGewrp4ejw68aXS2PdwM3zvq7eKZgp3ao82wnl15J6YUbUybY7Bf+c4ogL0Tvs4p1XoYKZXP2hz6TvjYXVWvZTc+bVPoO+Fbd9XdtF3YErFpO7Q0DYQvW9TRN11vjNuJVQZbgVrZpXc/fFiFzma8AjAP23C+ybM9EDa3YV7T3nkYAr4ydsYrDQYfCBv7sLKMz6Vf48mybAyexyuxxHorLOEMxNL9Ap/jp5TSg1L+Vvje0th4bj+Nl2P2nyoAP4Bf8ZXBrtijOJ9SuleJn414FS/Lss4oQM/0Vx/lfDwROj9+eiqyiVfjc60f8yL6U0p/VO3bnwMAgXFQ4e97RG4AAAAASUVORK5CYII=')}.x-field-radio .x-input-el:checked:before{background-color:#006bb6}.x-item-disabled.x-field-radio .x-input-el:checked:before{background:#84afcd}.x-item-disabled.x-field-radio .x-input-el:after{background:#eee}.x-item-disabled.x-field-checkbox .x-input-el:checked:after{background:#84afcd}.x-spinner .x-component-outer{display:-webkit-box;display:box}.x-spinner .x-component-outer > *{width:auto}.x-spinner .x-field-input{-webkit-box-flex:1}.x-spinner .x-field-input .x-input-el{-webkit-text-fill-color:#000;width:100%;text-align:center}.x-spinner .x-field-input input::-webkit-outer-spin-button,.x-spinner .x-field-input input::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}.x-spinner.x-item-disabled .x-input-el{-webkit-text-fill-color:#B3B3B3}.x-spinner.x-item-disabled .x-spinner-button{color:#aaa !important}.x-spinner.x-item-disabled .x-spinner-button,.x-toolbar .x-spinner.x-item-disabled .x-spinner-button{border:1px solid #c4c4c4;border-top-color:#d0d0d0;color:#1e1e1e}.x-spinner.x-item-disabled .x-spinner-button.x-button-back:before,.x-spinner.x-item-disabled .x-spinner-button.x-button-forward:before,.x-toolbar .x-spinner.x-item-disabled .x-spinner-button.x-button-back:before,.x-toolbar .x-spinner.x-item-disabled .x-spinner-button.x-button-forward:before{background:#c4c4c4}.x-spinner.x-item-disabled .x-spinner-button,.x-spinner.x-item-disabled .x-spinner-button.x-button-back:after,.x-spinner.x-item-disabled .x-spinner-button.x-button-forward:after,.x-toolbar .x-spinner.x-item-disabled .x-spinner-button,.x-toolbar .x-spinner.x-item-disabled .x-spinner-button.x-button-back:after,.x-toolbar .x-spinner.x-item-disabled .x-spinner-button.x-button-forward:after{background-image:none;background-color:#f7f7f7;background-image:-webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #ffffff), color-stop(3%, #ffffff), color-stop(100%, #eaeaea));background-image:-webkit-linear-gradient(top, #ffffff,#ffffff 3%,#eaeaea);background-image:linear-gradient(top, #ffffff,#ffffff 3%,#eaeaea)}.x-spinner.x-item-disabled .x-spinner-button .x-button-icon.x-icon-mask,.x-toolbar .x-spinner.x-item-disabled .x-spinner-button .x-button-icon.x-icon-mask{background-image:none;background-color:black;background-image:-webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #262626), color-stop(3%, #0d0d0d), color-stop(100%, #000000));background-image:-webkit-linear-gradient(top, #262626,#0d0d0d 3%,#000000);background-image:linear-gradient(top, #262626,#0d0d0d 3%,#000000)}.x-spinner.x-item-disabled .x-spinner-button.x-button-pressing,.x-spinner.x-item-disabled .x-spinner-button.x-button-pressing:after,.x-spinner.x-item-disabled .x-spinner-button.x-button-pressed,.x-spinner.x-item-disabled .x-spinner-button.x-button-pressed:after,.x-spinner.x-item-disabled .x-spinner-button.x-button-active,.x-spinner.x-item-disabled .x-spinner-button.x-button-active:after,.x-toolbar .x-spinner.x-item-disabled .x-spinner-button.x-button-pressing,.x-toolbar .x-spinner.x-item-disabled .x-spinner-button.x-button-pressing:after,.x-toolbar .x-spinner.x-item-disabled .x-spinner-button.x-button-pressed,.x-toolbar .x-spinner.x-item-disabled .x-spinner-button.x-button-pressed:after,.x-toolbar .x-spinner.x-item-disabled .x-spinner-button.x-button-active,.x-toolbar .x-spinner.x-item-disabled .x-spinner-button.x-button-active:after{background-image:none;background-color:#efefef;background-image:-webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #d5d5d5), color-stop(10%, #e2e2e2), color-stop(65%, #efefef), color-stop(100%, #f0f0f0));background-image:-webkit-linear-gradient(top, #d5d5d5,#e2e2e2 10%,#efefef 65%,#f0f0f0);background-image:linear-gradient(top, #d5d5d5,#e2e2e2 10%,#efefef 65%,#f0f0f0)}.x-spinner .x-spinner-button{margin-top:.25em;margin-bottom:.25em;width:2em;padding:.23em 0 .27em;font-weight:bold;text-align:center;border:1px solid #dddddd !important;-webkit-border-radius:1em;border-radius:1em}.x-spinner .x-spinner-button,.x-toolbar .x-spinner .x-spinner-button{border:1px solid #b7b7b7;border-top-color:#c4c4c4;color:#111111}.x-spinner .x-spinner-button.x-button-back:before,.x-spinner .x-spinner-button.x-button-forward:before,.x-toolbar .x-spinner .x-spinner-button.x-button-back:before,.x-toolbar .x-spinner .x-spinner-button.x-button-forward:before{background:#b7b7b7}.x-spinner .x-spinner-button,.x-spinner .x-spinner-button.x-button-back:after,.x-spinner .x-spinner-button.x-button-forward:after,.x-toolbar .x-spinner .x-spinner-button,.x-toolbar .x-spinner .x-spinner-button.x-button-back:after,.x-toolbar .x-spinner .x-spinner-button.x-button-forward:after{background-image:none;background-color:#eaeaea;background-image:-webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #ffffff), color-stop(3%, #f7f7f7), color-stop(100%, #dddddd));background-image:-webkit-linear-gradient(top, #ffffff,#f7f7f7 3%,#dddddd);background-image:linear-gradient(top, #ffffff,#f7f7f7 3%,#dddddd)}.x-spinner .x-spinner-button .x-button-icon.x-icon-mask,.x-toolbar .x-spinner .x-spinner-button .x-button-icon.x-icon-mask{background-image:none;background-color:black;background-image:-webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #262626), color-stop(3%, #0d0d0d), color-stop(100%, #000000));background-image:-webkit-linear-gradient(top, #262626,#0d0d0d 3%,#000000);background-image:linear-gradient(top, #262626,#0d0d0d 3%,#000000)}.x-spinner .x-spinner-button.x-button-pressing,.x-spinner .x-spinner-button.x-button-pressing:after,.x-spinner .x-spinner-button.x-button-pressed,.x-spinner .x-spinner-button.x-button-pressed:after,.x-spinner .x-spinner-button.x-button-active,.x-spinner .x-spinner-button.x-button-active:after,.x-toolbar .x-spinner .x-spinner-button.x-button-pressing,.x-toolbar .x-spinner .x-spinner-button.x-button-pressing:after,.x-toolbar .x-spinner .x-spinner-button.x-button-pressed,.x-toolbar .x-spinner .x-spinner-button.x-button-pressed:after,.x-toolbar .x-spinner .x-spinner-button.x-button-active,.x-toolbar .x-spinner .x-spinner-button.x-button-active:after{background-image:none;background-color:#e2e2e2;background-image:-webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #c9c9c9), color-stop(10%, #d5d5d5), color-stop(65%, #e2e2e2), color-stop(100%, #e3e3e3));background-image:-webkit-linear-gradient(top, #c9c9c9,#d5d5d5 10%,#e2e2e2 65%,#e3e3e3);background-image:linear-gradient(top, #c9c9c9,#d5d5d5 10%,#e2e2e2 65%,#e3e3e3)}.x-spinner .x-spinner-button-down{margin-left:.25em}.x-spinner .x-spinner-button-up{margin-right:.25em}.x-spinner.x-field-grouped-buttons .x-input-el{text-align:left}.x-spinner.x-field-grouped-buttons .x-spinner-button-down{margin-right:.5em}.x-android .x-spinner-button{padding:.40em 0 .11em !important}.x-phone .x-select-overlay{min-width:14em;min-height:12.5em}.x-select-overlay{min-width:18em;min-height:22em}.x-select-overlay .x-list-item-label{height:2.6em}.x-select-overlay .x-list-label{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;display:block}.x-select-overlay .x-item-selected .x-list-label{margin-right:2.6em}.x-select-overlay .x-item-selected .x-list-item-label:before{background-color:rgba(0, 0, 0, 0.3);margin-top:-0.8em}.x-select-overlay .x-item-selected .x-list-item-label:after{background-color:#dddddd}.x-slider-field .x-component-outer,.x-toggle-field .x-component-outer{padding:0.6em}.x-slider,.x-toggle{position:relative;height:2.2em;min-height:0;min-width:0}.x-slider > *,.x-toggle > *{position:absolute;width:100%;height:100%}.x-slider.x-item-disabled{opacity:.6}.x-thumb{position:absolute;height:2.2em;width:2.2em}.x-thumb:before{content:"";position:absolute;width:1.85em;height:1.85em;top:0.175em;left:0.175em;border:1px solid #919191;-webkit-border-radius:0.925em;border-radius:0.925em;background-image:none;background-color:#dddddd;background-image:-webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #ffffff), color-stop(3%, #eaeaea), color-stop(100%, #d0d0d0));background-image:-webkit-linear-gradient(top, #ffffff,#eaeaea 3%,#d0d0d0);background-image:linear-gradient(top, #ffffff,#eaeaea 3%,#d0d0d0);-webkit-background-clip:padding;background-clip:padding-box}.x-thumb.x-dragging{opacity:1}.x-thumb.x-dragging:before{background-image:none;background-color:#d0d0d0;background-image:-webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #f7f7f7), color-stop(3%, #dddddd), color-stop(100%, #c4c4c4));background-image:-webkit-linear-gradient(top, #f7f7f7,#dddddd 3%,#c4c4c4);background-image:linear-gradient(top, #f7f7f7,#dddddd 3%,#c4c4c4)}.x-slider:after{content:"";position:absolute;width:auto;height:0.8em;top:0.737em;left:0;right:0;margin:0 0.925em;background-image:none;background-color:#dddddd;background-image:-webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #c4c4c4), color-stop(10%, #d0d0d0), color-stop(65%, #dddddd), color-stop(100%, #dedede));background-image:-webkit-linear-gradient(top, #c4c4c4,#d0d0d0 10%,#dddddd 65%,#dedede);background-image:linear-gradient(top, #c4c4c4,#d0d0d0 10%,#dddddd 65%,#dedede);border:0.1em solid rgba(0, 0, 0, 0.1);border-bottom:0;-webkit-box-shadow:rgba(255, 255, 255, 0.7) 0 0.1em 0;-webkit-border-radius:0.4em;border-radius:0.4em}.x-toggle{width:4.4em;-webkit-border-radius:1.1em;border-radius:1.1em;overflow:hidden;border:1px solid #b7b7b7;background-image:none;background-color:#ddd;background-image:-webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #c4c4c4), color-stop(10%, #d0d0d0), color-stop(65%, #dddddd), color-stop(100%, #dedede));background-image:-webkit-linear-gradient(top, #c4c4c4,#d0d0d0 10%,#dddddd 65%,#dedede);background-image:linear-gradient(top, #c4c4c4,#d0d0d0 10%,#dddddd 65%,#dedede);-webkit-box-flex:0}.x-toggle .x-thumb.x-dragging{opacity:1}.x-toggle .x-thumb:before{top:0.175em}.x-toggle-on{background-image:none;background-color:#92cf00;background-image:-webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #6e9c00), color-stop(10%, #80b500), color-stop(65%, #92cf00), color-stop(100%, #94d200));background-image:-webkit-linear-gradient(top, #6e9c00,#80b500 10%,#92cf00 65%,#94d200);background-image:linear-gradient(top, #6e9c00,#80b500 10%,#92cf00 65%,#94d200)}input[type="search"]::-webkit-search-cancel-button{-webkit-appearance:none}.x-field-number input::-webkit-outer-spin-button,.x-field-number input::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}.x-field-search .x-field-input{position:relative}.x-field-search .x-field-input:before{content:"";position:absolute;width:0.86em;height:0.86em;top:50%;left:0.5em;-webkit-mask-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAAGdElEQVRoBdWaa4ycUxjHd9rpbm2bqKhiUavbVZdo0LCyLl3iHhGEkkZsKBYJX4RISHwQIYIPNJoQlUjTuCakUZ9oVGRF0GywslvqbgkpDarqsn7/6XsmM5n38pzzvtudeZL/nplznvM8z//cz5ktTU5OtuWRUqk0i/qdoAN0gcXgP+CkzIcx8APYBXbi82/SaZFSKGGILiTibnA+GADHgbkgSXZT8CF4GwyDEXxvI92r4k0Yoj1EeAG4CvSDEggRkX8VbID4lhADQXXUwxZgfAF4CGwFmgdFYQJb68HJljjy6mSSJZAZ4CLwESiKZJydb7A/CGblJZVWP5UwzueBB8AfIC7IovO0mK0B89KCzlOWSBinWoBeAkWTstiT3948xJLqxhLG2Xzw4jSRdQ0yiv/upMBD8xsI40Rzdu00k3WknyeO+aHk4urFEb4TJ/80CWEdYB4BhS1kdfswe+zpGNf80RYUIr9QSdgOdNCYCfaLcABpqFxBbymu3FIlDFkdD18B5wRYHaHOJvAeGCU4fa8IdnXUPAaoMZeDk4CvfEKFM7CrhswnbpxjZQX4C7j5Y0m1d64EXc5OWoqeFsPLwTvAYt/p/Iv+6jTb1rLKHMbYgWCjZxCb0T/e6qhWj3o6hz8HRMSRykp17l5WayfksyN8oafzTegfHOLQ1aG+blc6ZGQRdeVawB4GlWno7Pim1G9rB08AZzgrfRfdw3wdxelHvl/38K01Itc2Rf22Q8BPIIuoynXQL/SQj71DwcfA4n8nev1xjWfN0yGjD2gxsYh6432LolWHQL9F91Gj/j7oacUPFhE+11hbLxbrCFBzqWh5A4PDRqN90RZqVK9XE+ET67MSv41D9s3E0nwFX1Ndu4RFjkZpjkUxTkeEdTDIEvXqW1lKoeU0pOavXj10OsuSI1CYnaWUVC7COvpliR7f9CQzlaK5/LPBQRc6mstBIsIW0WXiO4tiDh35mIr1oS4kK2ENOctwqzPu+SX0MdDLjZWw9Pb1suyv7EPYR7cuEithLRLL6moW/0VriaVRtT1qTQkSER411Cyjc4pBL4/KEirPNRj4FZ3gXy5EWM+vWaIhtJQNf2GWYkg5dtWzui9bhuqn6OkVNUhE+ANjTZG91Kjrq6bDxHnGStqvcxHWsU5bQpZ0orCK3rDs21m2quXY6+DLTWBBNTP9wxbOKZZ4E63omLYZWG4r0nkQtOtwVASwdYeH723o9uTxS/3Ks+ytHk5/R3cI5LqIK2hEDw86XVkb+wV0Z+YiHDnWCjnu4Vj3Ug3DzhDn1NPacTX4HljJ6gFPr5e5RpZ74tFz6l0ezhWk5tFTYJFPEOjrLKxhrEazktWR8zVQ9vEVp1ttLYyplyeANQinN0ydIXBUnAOXR7nsrwAbgatrTbX3nu1s5Ul1oKgIRsZYMR/jy72gY0+u6a8OJMJX1P+C9MsaqDcPAseCHtANQkRTwHIoybZd21qR0Q2k1pZP0tNJSIubLhxJOr75egO/sjbekM/VIe0qY1RDb6p//PYl6/QniO0sF2tI2kBYRpBTgVrUOWqm9DPiGgghW+GWVBGj/UCvEM1E1sWinr4sKfa0/NgedhUwqsVITzvOUTOl6gxv0qmERRw5HOi/bHz2zb3VMHp28hremYQj0rq23QhGwFSQ0ZVPu8NvAfa3Use8kJkI1wzxxRhfDcYDAotrKF0GngYnRA17D599f7KVXcVzmoszLfUi7AxhfBG4GKwFPudhBacnmpfBStDwnzrkrQIhpDW8L3ExJqXV/wBA2Vs4WelquT9Qzy8FvdHnDlKR01RQ8OrJMaAp8TnYQUA7SBsEm6pzPXgcyI6PaCG7Hdu6VcVLUkuE5ONBR8ByDGb42sPGteBPEDcV0vK0ZZ2Z5C9oSCcZKzqfwO8OJK2FbCAunqYmrICRQaA3rLRejSvTWtGwTzc94Yj0DQS/O4C05nQd6VYhrIVMpEN6Wqv3crBngY4b582aR9DXgJCFTPt05T+AtKq2jNARzxLs/UBbnY/0onwLO97sXPuwj8cidQn8OuytAe0edjUyuluqh2vIPcNnPS1rIbOKfkRf0pKEGdqSJyFwM/AZ3j+2JGHXpZDWWf4+sMvlpaTal7e3xLYEsdQ4ITIIsras29AppxrKctRM5ZDRLUvv13GnLl1p5yjellylCb5BolvWkRQMgT6g6apXmnVgPWQrc/1/boJCaHVWyukAAAAASUVORK5CYII=');-webkit-mask-size:.86em;background-color:#ccc;-webkit-mask-repeat:no-repeat;margin-top:-0.43em}.x-field-search .x-field-input .x-form-field{margin-left:1.0em}.x-field-input .x-clear-icon{display:none;background:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAADHmlDQ1BJQ0MgUHJvZmlsZQAAeAGFVN9r01AU/tplnbDhizpnEQk+aJFuZFN0Q5y2a1e6zVrqNrchSJumbVyaxiTtfrAH2YtvOsV38Qc++QcM2YNve5INxhRh+KyIIkz2IrOemzRNJ1MDufe73/nuOSfn5F6g+XFa0xQvDxRVU0/FwvzE5BTf8gFeHEMr/GhNi4YWSiZHQA/Tsnnvs/MOHsZsdO5v36v+Y9WalQwR8BwgvpQ1xCLhWaBpXNR0E+DWie+dMTXCzUxzWKcECR9nOG9jgeGMjSOWZjQ1QJoJwgfFQjpLuEA4mGng8w3YzoEU5CcmqZIuizyrRVIv5WRFsgz28B9zg/JfsKiU6Zut5xCNbZoZTtF8it4fOX1wjOYA1cE/Xxi9QbidcFg246M1fkLNJK4RJr3n7nRpmO1lmpdZKRIlHCS8YlSuM2xp5gsDiZrm0+30UJKwnzS/NDNZ8+PtUJUE6zHF9fZLRvS6vdfbkZMH4zU+pynWf0D+vff1corleZLw67QejdX0W5I6Vtvb5M2mI8PEd1E/A0hCgo4cZCjgkUIMYZpjxKr4TBYZIkqk0ml0VHmyONY7KJOW7RxHeMlfDrheFvVbsrj24Pue3SXXjrwVhcW3o9hR7bWB6bqyE5obf3VhpaNu4Te55ZsbbasLCFH+iuWxSF5lyk+CUdd1NuaQU5f8dQvPMpTuJXYSWAy6rPBe+CpsCk+FF8KXv9TIzt6tEcuAcSw+q55TzcbsJdJM0utkuL+K9ULGGPmQMUNanb4kTZyKOfLaUAsnBneC6+biXC/XB567zF3h+rkIrS5yI47CF/VFfCHwvjO+Pl+3b4hhp9u+02TrozFa67vTkbqisXqUj9sn9j2OqhMZsrG+sX5WCCu0omNqSrN0TwADJW1Ol/MFk+8RhAt8iK4tiY+rYleQTysKb5kMXpcMSa9I2S6wO4/tA7ZT1l3maV9zOfMqcOkb/cPrLjdVBl4ZwNFzLhegM3XkCbB8XizrFdsfPJ63gJE722OtPW1huos+VqvbdC5bHgG7D6vVn8+q1d3n5H8LeKP8BqkjCtbCoV8yAAAACXBIWXMAAAsTAAALEwEAmpwYAAABbmlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNC40LjAiPgogICA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPgogICAgICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgICAgICAgICB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iPgogICAgICAgICA8ZGM6c3ViamVjdD4KICAgICAgICAgICAgPHJkZjpCYWcvPgogICAgICAgICA8L2RjOnN1YmplY3Q+CiAgICAgIDwvcmRmOkRlc2NyaXB0aW9uPgogICA8L3JkZjpSREY+CjwveDp4bXBtZXRhPgrlPw1BAAAIWklEQVRoBdVbS2hVRxiee83LmJeaRBOTCKWgtIiJoQYNFAnSRSF205AqKEJ3urDQlq7aECuuCqUUzK5gS20XBUMLlQYaH3TRoGJsaTURN0mMryQGE40mJun3He65zL2ZmTPnZZOBm3POzPz//N/MN/88k1hcXBRxh2vXrlUsLCxsWbVq1WaUV5JIJIpRZi5+0/iewvc40gdvI7S1tc3GaU8iDsBXr17dlpOTsxeGt+C3G791NiBgyzzA30De83jvffLkye/Nzc1TNrK2eSIDDJBVAHkIhh6E0a/bGmDKB10zSO9G659ubGzswXdoOoYGfOXKlVcA9BOAPAzj8kwAwqQB67+QP3nr1q0fQfv5oLoCA+7r6yvJz88/joKPAmxOUAMCyN2cn58/umPHjt4AsiIQ4P7+/ndQWBeAVgUpNAoZtPgP0HOkvr5+0o8+X4ABMAGP+xkeHSgk4aegmPIOQO++7du3D9rqtwYMp1SIYeU0wL5rq/xl5ENLT8KmdoDusSkvaZPp8uXLtXBMfyw3sLQdNpUB9K/oZsdssHi2MMHm5ub2QfH/1l9tgDAPhq8TDQ0Nn5ryGwGTxmxZKGgwKVlOaQB9AKDp0JRBS2m0aIJ9FlIrBiwRJpPJb0DvN5Roma5LSHnjZeWgdLZmxRfguxv2V2fFO59KwBxn0cAcelZkgO3V+J29cOHCkgnRkojUDKoLSI3jbF1dnVi7dq22QsbGxsSdO3e06aaE2tpasW6dfr0xMjIixsfHTSrovXeWlZV9gExfyBmXtDCni8js6ZEJZm5uTtaV8b5+/XpRVFSUEWfzQRlTRT5+/FhMTEzYqCLoDjRgjZw5AzAXAkg8KmfQvWM+K4aGhnTJLEzU1NTQiWjzZCe4MnyqwosXLwRbF+OuKlkVV1RQUNApJ2RYk1r1LKG5LCC/Y70qHj58KEdlvIMtoqrKkyxpmY0bNwrK6ALBmlilkkPlHMTwWuempQFzPYuaPewm2DxZ0/fv3xfPnj3TZmdftKF2YWGhKC8v1+ohjUlnvwGYctGQH7lyacCIPIRI3+tZUnt4eNjVt+RJSm/atMmh+JJEKYJ5dPSfnZ0Vd+/e9UNlSbOg3MFz58451EkDZmRGLh8fMzMzjkE6EdK0ulo5LDoiGzZsEKtXr9aJO/2W/TdoQCuXobu0Ut4BDDpvQ2TgbRlSm8ME+7QqQLfjeVXUhlNxqMw8qvDgwQMxPT2tSvIVB/bsp4ADGHTe60takZnU5lCFuawiVQhMU51WzqYtWx7lK2XIHDpFVmjYAB0tnZ2d6TGjJaxCytN5sa/pAluTntgNprGaIFmBYajslsMnad3a2trg9uFmOTHoO4189OiR1pvK1M7LyxOVlZVaZ3bv3j3x9OnToKYo5VD+7hxukoNm+jmiUlQfSWqzlTnMqKjKOI7N9LwErQpTU1PObCoKKsv6AXhrEkq3ypFRvHtRmx65pKREWRQpzNaNispyIQC8JcnjDzkyqvfJyUmH3ip9pHa283LzcSITNZVd3WjczUl4VZ7zRB7orTmkPH/+3Fq3qZKslRgyoqJLkvgTC2CWS2qzxWz6IiuGeekD4gqwo5hemqd4sQWOpXRQXoEOzDTb8pK3TM8l4PDTGE1pnGxw2mhaAbmi7NfMy7E6xjBNLx3pcaRsLBfy2HWQo4zvrBiOzayoOAIqdYp92LxXErBkjsNsMVWgQ9P1a1ZSaWmpSix0HMocp5ceDK0pSwEnF5xCqiYezMp1Lfu2LnBiElN/HkzymgGQR+Ya2Re56C8uVjt/d23L2ZhucuFWWNTUhm0DSd6pwMsNXW37jSeV5QWCLE8ac2wmaC75OO/WUZszMdKbFRhVAJuvu4uH81EoZcuYdjcIUt5e5RTStD1EakfotRcB+KIDGLUc6DRdriS2REVFhbbvkb6jo6OyiLN2ZpxussHpJyswCmoD41+4JzLmAOZtGUTovUiGmeoP7mZwSFEF0pYLeVVrelF7zZo1guvmsNSGDb/QNgdw6mpQt8pYmzhSmXvQukCPzL6rC2xl05w7Cq8NtnzH8t0+THp9qzPIFM+ap0G6tS30eh65kAGm7SGWz+OXENT+070WkQYMfv+Ggnk1yFegNzWdA/GMyWa5R2qbjlDovDiRCUjtL11QacAAy52yk26CzRM3A4xUJk3piW0Dx2YTtekU2ad9hoHu7u6fXJk0YEbw0hceN91E05M1zX6rm02x/nyeAzle20uGp5Z+qA07jnd0dKS3UjMA84YbgtVhGmms26ZhRXFSQZr6DdljdbY8WcWhyiYA7CXc4zoj51Xe8cCB+Bm0oLNxLWdeSe8AOwcMDXBW/8h2Z7SwlHAE7wPS94p7BeBj2WAJQgk4dZ1vH4R8XetbLrUCu0/hJk+Xyh4lYGbkuAVKtEM4spWUyoAY4nqxGai9pKYFnALdg+eHMRgVi0o0zm2M+W179uzRHjUaAdMq0PsrzJZOxGJhhEoJFox8e9euXcYLIJ6AaROv8wH0Abzqj/ojNN6vKoA9j/n6TnZDL1krwFTC63xQ/CZ+mWs8rxJiToc9p9Bn3/JqWdcM5TjsJqqevOEG6pzFb6cq/WXFAegcfsd03lhnh3ULuwpQwChqtBmFfYw4/1MpV1GIJ8q+hAqHKeqhx6TadwvLynjpC6uYThjA/2SJ9QQjVe4AyvocjvR72Q4/775bWFbe1NQ0AkfxPubfryL+axgT10SlD/rbsep5LQxY2h6qhalADrwahM2AfWjt9wC+BU/7YwdZkXPTaPFv6PiZOxU23jdTXP8VKWC5GF4g4Z0KgG7Gbwt+WwFgM57FeHLTml1gGt/8d7wxvHNmN4Dh7zp+F7nhJuuL6v0/Vc+vwPfknLsAAAAASUVORK5CYII=') no-repeat;background-position:center center;background-size:55% 55%;width:2.2em;height:2.2em;margin:.5em;margin-top:-1.1em;position:absolute;top:50%;right:-0.5em}.x-field-clearable .x-clear-icon{display:block}.x-field-clearable .x-field-input{padding-right:2.2em}.x-android .x-input-el{-webkit-text-fill-color:#000}.x-android .x-empty .x-input-el{-webkit-text-fill-color:#A9A9A9}.x-item-disabled .x-form-label span,.x-item-disabled input,.x-item-disabled .x-input-el,.x-item-disabled .x-spinner-body,.x-item-disabled select,.x-item-disabled textarea,.x-item-disabled .x-field-clear-container{color:#b3b3b3;-webkit-text-fill-color:#b3b3b3;pointer-events:none}.x-form-fieldset{margin:0 0 1.5em}.x-form-fieldset .x-form-label{border-top:1px solid white}.x-form-fieldset .x-form-fieldset-inner{border:1px solid #dddddd;background:#fff;padding:0;-webkit-border-radius:0.4em;border-radius:0.4em;overflow:hidden}.x-form-fieldset .x-field{border-bottom:1px solid #dddddd;background:transparent}.x-form-fieldset .x-field:first-child{-webkit-border-top-left-radius:0.4em;border-top-left-radius:0.4em;-webkit-border-top-right-radius:0.4em;border-top-right-radius:0.4em}.x-form-fieldset .x-field:last-child{border-bottom:0;-webkit-border-bottom-left-radius:0.4em;border-bottom-left-radius:0.4em;-webkit-border-bottom-right-radius:0.4em;border-bottom-right-radius:0.4em}.x-form-fieldset-title{text-shadow:#fff 0 1px 1px;color:#333333;margin:1em 0.7em 0.3em;color:#333333;font-weight:bold;white-space:nowrap}.x-form-fieldset-instructions{text-shadow:#fff 0 1px 1px;color:#333333;color:gray;margin:1em 0.7em 0.3em;font-size:.8em;text-align:center}.x-selectmark-base,.x-field-select .x-component-outer:after{content:"";position:absolute;width:1em;height:1em;top:50%;left:auto;right:0.7em;-webkit-mask-size:1em;-webkit-mask-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA2ZpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDpGRTdGMTE3NDA3MjA2ODExOTJDQUMyNUQwRUE4NjdEQiIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDoxQTFBMDFDQ0I5NEYxMURGQUU1RjlGMEFERUNDQTVEMCIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDoyMkRCMDIxMkI5NEUxMURGQUU1RjlGMEFERUNDQTVEMCIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1IE1hY2ludG9zaCI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjMwRTE0QzVBNDIyMjY4MTFCQ0ZCOTAzOTcwNzcyRkVCIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOkZFN0YxMTc0MDcyMDY4MTE5MkNBQzI1RDBFQTg2N0RCIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+HfrH/AAAAeVJREFUeNrs2cFHBGEUAPA3zYqIiIhOnTpFRHSKrp26RqeuEV077R/QqWtE166dOkVERHRa9hQRnZalFcv0Hk/W1Mx+38z3vvlm5j3eZW+/9+abne+9KEkSaFPMQMtCwQpWsIIVrGAFK1jBClawgo2ik/4hiqJGwLKuvfpIc5xSkWqYr5hzU1s/mRNxXTPsJ+ZqluvXlwOmSj3XBDvG3M1rpAmYYoUrFzr4ZNqTawqm2MH8Dhh7ZXJUbcAUx4FinzBnJcAUl4FhP/jIgRSYKvkYCJaO2LbNv08RMMUy5nsA4COTLy0XYIqtil9iF6aflq7AwBWuAvuQ9ZKSBgNX2ieWjtKSzeXBNZgqfe8J+4W5aXtbcg0GrvibB/BhkeuhBJhigzsghT0veh+WAlMcCGHvMOMQwcCdcIntYy6WmXhIg2PuiAvsEHO97IhHGgzckb4D8L6LmZYPMHBnhiWwXVdDPF9g4A4Vwd66nFr6BAN3ygbbw1yoMzjmjplgB5hrrufSvsHAHesZDOD2JAbxVYCBOzfIAZ9JbR6qAgN3cPwP9kZy1VIlGLiTdluCmoOBO/pnS9Bk8DzmS3pL4BMcpZEe1qX0GI/atC4dQYXRMa1MU0IX4gpWsIIVrGAFK1jBCnYUPwIMAPUPAyFL+nRdAAAAAElFTkSuQmCC');margin-top:-0.5em}.x-field-select{position:relative}.x-field-select .x-component-outer:after{background-color:#dddddd;z-index:2}.x-field-select .x-component-outer:before,.x-field-select .x-component-outer:after{pointer-events:none;position:absolute;display:block}.x-field-select .x-component-outer:before{content:"";position:absolute;width:4em;height:auto;top:0;left:auto;right:0;bottom:0;-webkit-border-top-right-radius:0.4em;border-top-right-radius:0.4em;-webkit-border-bottom-right-radius:0.4em;border-bottom-right-radius:0.4em;background:-webkit-gradient(linear, 0% 0%, 100% 0%, from(rgba(255, 255, 255, 0)), color-stop(0.5, white));z-index:1}.x-msgbox{min-width:15em;max-width:20em;padding:0.8em;margin:.5em;-webkit-box-shadow:rgba(0, 0, 0, 0.4) 0 0.1em 0.5em;-webkit-border-radius:0.3em;border-radius:0.3em;border:0.15em solid #1985d0}.x-msgbox .x-icon{margin:0 0.8em 0 0.5em;background:#fff;-webkit-mask-size:100%}.x-msgbox .x-msgbox-info{-webkit-mask-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAAHOElEQVRoBdWbXYgVZRjHXdf8ysjUQl011lbRIFEjM6Uu0iyiEDG86EItKoIuuhDJCgoioouugqKbgi4CKwulILG0mxLTUtMyTWQNPzLTPszU1cx+v+OZw9nZM3POmZl3zQd+zMz7zvs8z//MvF+z2nLhwoU+oaylpWUQvvvDYGiDdjgP1dbKRSccglNwlpxOcwxiLUULRqTCRsNUmAk3wS3QiG3hpp2wCbbDYfLzhyjMChOM0FlkNR3mw61wFeSxv2j8FayBrQjfmMdZpa1POA84UuD7cBzsHyHQtzHm58nVtpnEErgvzIB34Rj8CyGEVvs0hrGMaey+WcQ3LZhAQ+FZsG/1htBq0Z4b09jmMLRZ0U0JJsA0eAccTeOJ9Pa1OZjLtGZENywYx0tgDzit9La4pHjmYk5LGhXdkGAcLoPDcCle4SSxUbk5mduyRkSnCsaJi4IV4GARBSj6eALfR8sxunLEMUdzbU0TniiYho7ED8GvULRI/UV9cDbnrsauheXQCVnjmas5J47gaYJdSPwAIfqsPlfEnwRl/eBBOAlZROvXnGfFfUfXNQXTYCKsg38gS+B6bT6MEogfiTcKNuaIa87mPjHu2+segrnRBf8bYN+ql3jW+ntrJVNK6OJGw+VkVt+2M3c1DIrHsZ9WjPVwCxcLYQ4MqVQUf/Jjikt3VnnX4eauhoVlTZVw3QRTOhmWwjhQfCi7ppZjkjOf62FCrfomysxdDUtBTRWrCCZYK6WLYAo4aoa0JxKcu2x9CsYk1DdTrAa1LCpru9g2ese58lddD+cgT/9ppK2j8ONR7HLf9Um8B0XOCmpR04QoVmnQosDp4BHYD40kXMQ9zsPfgSI/hyNQhN+4j/34VVu/0g9b/nXbKFgJf0O8weV+rSa1tam1b3kUm0SB77sj5KUw18OhTE1qm6RWBy07t0O4S7veto8J6FLwbng+YHC1qbE0GDtnrYXeGKzsHj7NT2AejKgMJn36DODaASZEF1KbGof4hJ2vXM45cIW2nwjwKDyA0HXgDicyl4RpC5LovixHtalxnCcd4PwX0hTjcvEFRO5ICBRyoWNINXYo2Ek+5DJyP/6fgZWI9XVNs3r1aW3r1alxjIJHQqjR+Vt8L0fnpxzrmU+45pKzXsMG69U4UsHDYWCDjRq9zYFpCzwGLi5K5qyA+KQpSMHt5VtDHNQ4XMEh+s5R/L4CuxSIUKeDO8BX1pG4lrlDmlqrosCy0jxcoL+KK5PvgFbEOka8CKsgbRd0u/dDUPMJh7ArcXon/A4PwwxwyvkKkuwuKi5bwYqaDbdBNAP8wvn3kGQ+4RDdq1u8UE/YINUjv313L/35bLfo5Qte+xs5va5WXdFlrrRMImnkLCreaRxtSnE2i7q8n3VS3Jeq1HhWwY6o7k1Dmn/r3ZgSYCZ1g1Lqi6hS41EFHwC/QIQ0P5D7vbiH8Tq7DnD7Frr/qvGAgvfBnxDSNqcsOJx7Xe2FNjXuU/BeOAah1rHn8f0FJJkDlk85pKlNjXsV7KPeA34KCWUuM5OsN760qE2NJxXcBevBfhbCOnFqsB5G/72aQj8vVVuIN01tauyKFvPbuHBhEGJ6+hK/SSLaqBsPmrFfhZe9KND0q7ZtjiM+Ye0guIXzPS/atuPQflzLxlI4Go6AOys/wq+Gn6EoU5Pa1Fj6G7Dfpp0nfeT+EkXaOZx9jf+kJ+xqbAPcxy1vwhnOd8MuKMrUtB7fauz2HcsgBuuAQVCEHcLJ8RRHrr42kExpWqRPu3mYDTektGmmyhVe9x+QYJU/mVK5AHwF/QblU8nLWnyMrY6Rds69T4Kvd964tleDWhZUx6yItRBzo+7A8QcUEXQVfkZVB6x1zj3GfQ587YqIqw81qKV/dcxugsuiJ3OT/cr+lzf4S/gYXB0wfk69HwX8YRxN88aL2pu7Gib3iBcv8BpbDJ0QOch6fB0fNf+1HOVXwD2wE7L6T2rXic/FNbXVLLw4mNmfTuRMZi/tx8djUDYHPgAHlaSks5abs7mX/lrYI3a8ILqmwTB4G9xWZQ1uu7egHQbC/aBQR+88PpPamqs5D4t0xI89+nD1DTT0A9waOANJQeqVu+j4Ddx3u26vd3/WenM01zHVGuLnqYK9GXNeXg15RGcV0Wg7czPHjrjA+HVdwVWifRX/j6LNydzqii1pif8CSdc4HApPg0u1IqeQRp9i/D5zMBdzqjkT1NLS0BOOGuLYv+E6lWyFolZjcSGNXBvbHMxlQJRfI8emBEcOCeKo+xq4A+nNp20sYxq7PcqnmWMmwVEAgs4FR0Y32CGF69sYxpobxc9yzP3feMo7nJtJxDnWV2w6RPtsTnOZQn1118JH8A0ik/bWVNe33IKjEAh3qei87Ue5eeDTnwTNilfkbvgM1oHb1oMIdX2c2woTXJ0J4h3c3NyPgikwA9zjjigT7Xf3ce0XCfF8M+wAv3icQmQXx0LtP/qKurS9uZqyAAAAAElFTkSuQmCC')}.x-msgbox .x-msgbox-warning{-webkit-mask-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAAGXElEQVRoBeWa2Y8UVRSHpxEBEUYURUEdxhA2hbgQiVtgJIgbQpQQBRSi7AF88MknX30m/gP6YIiaGI34YkxcEo1xfTDikmgk7gtGAQFBHb+v6Wp6uquqq7rqtjpzkl+q6y7nnN9dzj230pXBwcGekSSjRhJZuY44wqO7NcOVSuU0bJ0PLgXnggo4CPaBr9laJ3gGl0o39jBkHdi54BawDswDEv4KPAmeAx/iyzGeYUXCIYH3kr0K7AV/AaNkM96k7GYwJqQv6g5KtmrgJNnnY0g2k36XNgNgVEjSQQnj/ETwNGgml/T+Km2nhiQcLEqzb9W9FLhvs8oNNFxJ39OzdsjbLhhhHDEiPwCc5axiJN8EZmXtkLddEMK1GbobZ5bldYj2C8A6dIzvoG/7LiH2C1bng/0gaa+2Kz9A35tC+Fb6DDMzZ+LsVtAHOpVz6LgTXZM6VZDUr1TCOGgysQiYXBSVJSi4E5Xu69KkVMJ4dRF4EJQxMxPQsw3MAaVJaYSZCY+SDWBxad719FyJrq3oHlOWztII41A/WAPOAGWJg3gHMPcuRcxzC0ttBlahKGn5GZW/aWPIYHd2TJvplG3AxsdE7aMx9bmKSrkt4Yxn5x4wM8G6jj4M/k6ot9jLw/KE+u8pXwvhVxLqsxcXPeuwdAF4ETiLSfDe25dmi/pHU/qr9y0wI01HlrpCe5iZ9RhaC24EaaIdByZWanoS62udXEUbaVsogBUijANG0Q2gXaDSjl85ksSBm5pUWSs33ji4nvMdS8eEGemxWN0IZmewrrNeJtIkS+58MQo2Y9sA15F0TBhrfsVYASTeTpzBuAgc9dOPdkvatrZz+wxAuqMMrCPCGOvF6FZgZpVFdK7dkj0viyLa2G5n7Zmxy6lmHRGm+63A2c0qzvBZKY0nU5fHl8W0X8XAqzeX5DFSVYwN9+IOkLZE45yYEldYK3M551miBsltIOncr6ltfeQiDNlxqDAqX92qqm1JGmEHL+9s+X17Ez7lCmC5CGPAQLUFSDyvjKsNWFw/93feNFffPaZc3pklM2Gc9brm7PZn1j60ocsw6dro/s47w2p3oExGMm+vTIRRqDMLgTeXPHuN5nXx5jOx/jb0h5E3ky9Du1X7DFC2qOZjU3Xra1Yjzsx60O5oabVwqsRtYDSOE/d3pwPp5yBXXpZzvP2oMnIOyu3Ao6iImAMnEc68JBMcWEL5mpqvCU1OFmeZYdM5j6G0KJtqpFYp4aQ9PC2LgpQ2xgCDqZE7VVIJM2Luu/uBN5WiYtBqyaZqs2JALCqeyVvQl5rqphJGgUTvBRIvKh47cUHLNLWTY67ZH7ncBa5vrmh8TyTMSLn8toO+xg4FfhvpW2aYMgehDMK6Zha4C98Tr6KJhOl4LbgNlDG7qKmes3HByYF1f5chrqKlwNtULLfYQho7Uu7dxJGirhPpRbeYGAEl/cD9XZYYDzaDC+MUtqRzOOJ5uBIsi+tQsGwu/XcB/wmgDAIja6582I5tZID61XDZzXeuyNbJLs0fvii9DLwHdKZsHEfnz+CnBvzGb79mlm1rHzoXtPBrLKCBS2sPCOFAROgH9L8AHgcfgD9BVFf28yV0TxrCcchLT899NPglkAMO4kdgNZgHZoDrwG5wCJRNVn2HwTZQ/f4u1/p/PCg0QL0OQs2uy9gz3VVU/eMKT+OFwWUvCGX3HXRPjya2SpgCDd8DjoAQI63Ot0FvZLjxSfl68AcIYfsYeneA6l+iomOpj4KHgKMfSn6F5MEE5Z9RfiKhrmixqeZ2MFNFowjdHk3LwRUWBJQp2Eq6ws3BbssRWaIvs9Hlv4OqCY657MsgxHJq1Ol2eQSMbVrOOvMaCLWHIx/ex0avozoeXANCi9vFpXWIkX6Dp2eygdL9tRCYa4cUE5ypGrkEfBHSUpPub3k3YhtMXF39IGTsQH1dVjjDLqVuyjSMCe1GQbNb9o9o8Hewv1sWG+x0m6yr6nONmuU81eDIcP1pOvtjlHjM4uUZEEW04fZ8Fm6Xg0pE2ExrPngMfAeGC2GX8RPAHGO0x2H9Ty0cFZL2u7OJ/QJgMuCxYYCxzkH4L0sF57z7egp4Bf0EeBv7FHwJ2eq9uE6Ywh5I28lUTKKTgcfFBOBe/z8QNvIfBkfBASDx45Ctn0RDCFM57KXbR8O/PqAjjvA/zIVqEDmIpQQAAAAASUVORK5CYII=')}.x-msgbox .x-msgbox-question{-webkit-mask-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAAFoklEQVRoBcWaXYhVVRTHu2kmZvmgTpbOqCN+PtinEYUWEkVkEfgWJCT4Ij31oi+K4Eu99hB94DTaQ1QGpUJRFiWVSDWliZCVdwJNS/u2Mj+afv/pHLhtjnf22mevOwt+c+acu/be63/2Pmvvfe5tDA0NXeJhjUbjUuodXzCO4yy4FkJrcOF3+LI4niGmP0OnXOeN3IIR2k1wvXALXF8cZ3McA+3sAh82YR+8BZ/BEeL7g2M2yyIYkeoliVsGD8JSGEkgLhc1DbvvYTv0wwDC8wxF1ZMKgUjUClBg34F6SYHl4h/qOg7PQk9qnK3lksTSuHp0IbwI6gkFlktkVT1/U//bcFtr8Cn/mwXT6ARYC03I3aNVYstr52nvU7gjRWhZJkXwPBrVMCsD6eTxLO3uhqmlAOtRU4fVmhR4CbJmz8ggLsNP2X8teVIjzWxmwdzRc7TyPHwDenY7bRNp8CGYm9KwWXDRyCGOSlinUxqtWUYJcxo8QC+rx02WJLjo5V20dBRGo5evoN37QSs5k401ef/fWUvB92AWxDxPp/D7EX4AtXt1gYK3mub/GdAFWpZGW7Jg9TJDqo+WtKpaBFUrqy+4vhP2ghYmP4OEq90poIDvg3VgHW3qXa0FlEvizZrWW/1pRUGuh5+gnJ40X+6BlTATxraWCf/n83HwOJTlY4+/UubhsL6Rzs3zcFghjU4FLQgktAmrYVLo1+4c/5vhKMSKlZ8Er2pXb9VnyUOaxoaNSk8ytJ/jREFvgX1csyayk5T7HKaDq9UWXES3BZFP14hUU42wmrmMNVFUBoRYLUbqmJ7jScYKLuCvYW2yLIJNLVY7z+fykuqPLnpVOUOZ32SjLpjnX/PxY6BettgZnL+1FJDvqApG7OXEsBnuBIvpEToMpy2F5DtqghHbQ/tbYY0CMZqEvgLqZZPlytKmRhE7hQJPgVZZVtOUp7csH5AslbhM1vEeRuwCIuyHFLESp334qzAIduMu1V5txdZBdDfBAFhWVK2+6tEDMC+2zdCvYz1Mz64g0NfgBkg1rcieQYQSVpqFd8DjnMiWg3ZOrb1l/V+LjCfgyjoxug9nAtQWcDdooWAVWfr/RdmXYXIdsSrrOqQZxtojr4LboWq/zOURTVPQdniUgPUCoZ7VvWPtyhOZlotfg6aSsrcsR2XkDTChXTuWz9zmYXpXqyj1bjdYdzW6KV/BOsQo0eUzy92x+BJhDxwHS4/KV1PPXlhmaS/W162HCfguSNnyfUi5RxBwhGN280xamnct7431nGtRscZLrO6ei2Ce36uo+0awZGa9CNyE2PRFBRWMZC6CaVTPr8TGJitt916HN8DVvAT3ErXlW4FB/PvoXQl3NS/BSlaW4XwYsR+5Ki0q9xJsiV3T0MeWAnV8vQRb6tUwHqwjwlLWEpilXmVa/S4jxhSDpqSOmNfCQ1s57V1jMrVuTOzNqX1TsvxOK4yCeVgZeibELDy0nDxG0volrMfj3EWwR6C56vR6hnPFl70er2d4ONDiBcB0TrSu1gu88pcC2tR/Au9Ck+GstyGdMRpzec1D9NfARjgBVa93NP8quT0JXV5xhPV6iZ2GiJ2gOTZmP7wLvyy/pQwFhufZBRO48sILcBZixMpHvttgfBhg7nOPpHUvgS+HmCkJt2GT792w+L9Tv78egu8hXH13ZLVyD20tZ/LPKrjIynoPnVKvynRRh+vMkRJYuzuqFZa2hrEb/7AuPc+ulluwvq/9DVIDT71R0Tcpq2AyquZWbRpSdj+6SadAdbhZVsFFlO9w1As5q+lbhv3ctNTREdWeh+AdtPw+WJaL8tUyU98w+lruib3ooG6ifhOqlpTqwZA9XOv1iCWsM/tKq2wAAXOgH5TIQoHluT7rg9llOe+j636YOVW7o+tgNSyAW0EiB+AgbIUDiNS1jti/UGZZHiXRBKIAAAAASUVORK5CYII=')}.x-msgbox .x-msgbox-error{-webkit-mask-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAAFGUlEQVRoBe2bzW8VVRxAeS0UMZggaARtGgtNU5pIRCMaEsPChrgh7FhCjHtdEBKNLvwjdOHShSHGmEiMiVFw4cL4EcFoolRSuqBiRDQqCLRS6znDzPP1dub1fbbzLv6S05m57829vzN3Pu+8VhYWFtZ0KyqVygbqHoA74QEYhnmojX4WpuFHuAZz5HSdaVei0mlhJBW7Hx6GJ+AheAwaiS/50rfwGXwNF8nPDdGx6JgwonvJ6hE4CI/DXdBOXGHlz+EEnEb803Yqq65rD7cDFSn4NvwKHh/dwLpt42A7ubpuS7I03Ad74C24DP9AN0Rr67QN27JN2+5rRb5pYRraBC+Bx9ZKiNZKO2+bM/AKbG5WuilhGtgNb4Jn0zCRlV42B3PZ3Yx0w8JUfAQmwcvKSssVtWcu5nSkUemGhKnwKFyE1diFi2SzcnMyt6ONSNcVphJvCl4ATxZZA2WdmqO59tcTLxRmRc/Ez8AvUFbJMC9zNefCM3g9YW8kvocyHbOhYLhsrua8t6iXc4VZYRQ+hJsQVlr2ZXM299E86SXCfNEb/tfhTyi7XFF+5q7DhlB6kTBfqMCzMA1FlfVKuQ66JM8LmXgoPM4XTsIs9IpYUZ466DKeyTr1TJwETztegg7BLhhICnv7jw66HErdEpuqMEvb4Um4O/kkjj+66KRbEokwW2AtS/tgBzgfS+ii077UsbpL30fhBNwLsYVOuum4pg9zz8xj4P7u2FNsoZNuY7q6S3twPwUx9i5aSeim44DC68CxKB/sYw3ddFyn8BBsg5hOVugsCt10HFJ4BGLuXfSS0HFE80HYmBQt/XOdIseJfwfvaDzBlTGy3JRyHNzngTB0HFR4KxSdnS/w2ctwFhxZKHO4t3q1eRV82gtDx60Kb4E7wk/T5d+YTnIPqnjpI73EmnNe6LjFrVJvN3VXya7VeZWUpiyV1ceciyK5Dhd9GGW5W+S2iv+Fg+72eKh3TARfX/XFZfO1h+eg6JLjbec8Z+nSS6c5/k2+/ZAXOs55WboEvqfJu/nYTvlznAHPM3UItMxh5w3BgwVJ6nhJYa+xVyFP2Gv0MYghdLzgVpmCP2IwWsZBxymFz8FlKP1xSo6thm46nlPYrp6EvyDW0E3HqwrPwinwRVSsoZuOs75l83R9Bn4G52MLnXQ7o6s9bMzAR+B+HlvopJuOt4ZpMb/CvF3ulogtdDqVOlbHpZX8DnzQd5QjltBFJ92SyHZpX6p5YB+HH2A++bS3/+igy/HULbGpCqdu7zP9BLxU9XrooItO/wX24SvTnXz6MXgjnj199NrU3HXYucQvLHCZOAzT0GuiWb7mfjjXLbfw1qD886zUiz97MGdzX5vrlldoGbEZ3gAfq7ItV/apuZpz4W8wFx2/oTwrOkh/Am5A2WXN0VwHQ4/a5brCaU+PUMm7UGZpczPHkVq5vPllhWuk3ymptLLmtqxs4pK3FfLKqND3Ni+Ct2rekK/2Lm4O5mJOm/JyzitrqIezFal4PTwNp+EmrJa0bZuDuazP8mtk2pRwViGNDMNr4BPISva2bdmmbQ9n+TQzbUk4a4BGJ8Azo/fh3RS3btuwrYms/Vambf8bD0O4jgM/CgfAXcyfFoT36BS1FIq6634A78FXSLb1YNO2MEkkgbiD9l63x2E/2Ptj0Ky8kmfhJPirWB/tZhD1/rjt6JhwbSbIe3LzBfQ22AV7YAfck+K8MQWOSIjzX8A38BNcQ9Lxto7Gv+RtOqJ9fOwKAAAAAElFTkSuQmCC')}.x-msgbox .x-title{font-size:.9em;line-height:1.4em}.x-msgbox .x-body{background:transparent !important}.x-msgbox .x-toolbar{background:transparent none;-webkit-box-shadow:none}.x-msgbox .x-toolbar.x-docked-top{border-bottom:0;height:1.3em}.x-msgbox .x-toolbar.x-docked-bottom{border-top:0}.x-msgbox .x-field{min-height:2em;background:#fff;-webkit-border-radius:0.2em;border-radius:0.2em}.x-msgbox .x-form-field{min-height:1.5em;padding-right:0 !important;-webkit-appearance:none}.x-msgbox .x-field-input{padding-right:2.2em}.x-msgbox-text{text-align:center;padding:6px 0;line-height:1.4em}.x-msgbox-buttons{padding:0.4em 0;height:auto}.x-msgbox-buttons .x-button{min-width:4.5em}.x-msgbox-buttons .x-button-normal span{opacity:.7}.x-msgbox-dark .x-msgbox-text{color:rgba(190, 224, 247, 0.9);text-shadow:rgba(0, 0, 0, 0.5) 0 -0.08em 0}.x-msgbox-dark .x-msgbox-input{background-image:none;background-color:rgba(190, 224, 247, 0.9);background-image:-webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, rgba(144,202,242,0.9)), color-stop(10%, rgba(167,213,244,0.9)), color-stop(65%, rgba(190,224,247,0.9)), color-stop(100%, rgba(192,225,247,0.9)));background-image:-webkit-linear-gradient(top, rgba(144,202,242,0.9),rgba(167,213,244,0.9) 10%,rgba(190,224,247,0.9) 65%,rgba(192,225,247,0.9));background-image:linear-gradient(top, rgba(144,202,242,0.9),rgba(167,213,244,0.9) 10%,rgba(190,224,247,0.9) 65%,rgba(192,225,247,0.9));border:0.1em solid rgba(25, 133, 208, 0.9)}.x-loading-spinner{font-size:250%;height:1em;width:1em;position:relative;-webkit-transform-origin:0.5em 0.5em}.x-loading-spinner > span,.x-loading-spinner > span:before,.x-loading-spinner > span:after{display:block;position:absolute;width:0.1em;height:0.25em;top:0;-webkit-transform-origin:0.05em 0.5em;-webkit-border-radius:0.05em;border-radius:0.05em;content:" "}.x-loading-spinner > span.x-loading-top{background-color:rgba(170, 170, 170, 0.99)}.x-loading-spinner > span.x-loading-top::after{background-color:rgba(170, 170, 170, 0.9)}.x-loading-spinner > span.x-loading-left::before{background-color:rgba(170, 170, 170, 0.8)}.x-loading-spinner > span.x-loading-left{background-color:rgba(170, 170, 170, 0.7)}.x-loading-spinner > span.x-loading-left::after{background-color:rgba(170, 170, 170, 0.6)}.x-loading-spinner > span.x-loading-bottom::before{background-color:rgba(170, 170, 170, 0.5)}.x-loading-spinner > span.x-loading-bottom{background-color:rgba(170, 170, 170, 0.4)}.x-loading-spinner > span.x-loading-bottom::after{background-color:rgba(170, 170, 170, 0.35)}.x-loading-spinner > span.x-loading-right::before{background-color:rgba(170, 170, 170, 0.3)}.x-loading-spinner > span.x-loading-right{background-color:rgba(170, 170, 170, 0.25)}.x-loading-spinner > span.x-loading-right::after{background-color:rgba(170, 170, 170, 0.2)}.x-loading-spinner > span.x-loading-top::before{background-color:rgba(170, 170, 170, 0.15)}.x-loading-spinner > span{left:50%;margin-left:-0.05em}.x-loading-spinner > span.x-loading-top{-webkit-transform:rotate(0deg);-moz-transform:rotate(0deg)}.x-loading-spinner > span.x-loading-right{-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg)}.x-loading-spinner > span.x-loading-bottom{-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg)}.x-loading-spinner > span.x-loading-left{-webkit-transform:rotate(270deg);-moz-transform:rotate(270deg)}.x-loading-spinner > span::before{-webkit-transform:rotate(30deg);-moz-transform:rotate(30deg)}.x-loading-spinner > span::after{-webkit-transform:rotate(-30deg);-moz-transform:rotate(-30deg)}.x-loading-spinner{-webkit-animation-name:x-loading-spinner-rotate;-webkit-animation-duration:.5s;-webkit-animation-iteration-count:infinite;-webkit-animation-timing-function:linear}@-webkit-keyframes x-loading-spinner-rotate{0%{-webkit-transform:rotate(0deg)}8.32%{-webkit-transform:rotate(0deg)}8.33%{-webkit-transform:rotate(30deg)}16.65%{-webkit-transform:rotate(30deg)}16.66%{-webkit-transform:rotate(60deg)}24.99%{-webkit-transform:rotate(60deg)}25%{-webkit-transform:rotate(90deg)}33.32%{-webkit-transform:rotate(90deg)}33.33%{-webkit-transform:rotate(120deg)}41.65%{-webkit-transform:rotate(120deg)}41.66%{-webkit-transform:rotate(150deg)}49.99%{-webkit-transform:rotate(150deg)}50%{-webkit-transform:rotate(180deg)}58.32%{-webkit-transform:rotate(180deg)}58.33%{-webkit-transform:rotate(210deg)}66.65%{-webkit-transform:rotate(210deg)}66.66%{-webkit-transform:rotate(240deg)}74.99%{-webkit-transform:rotate(240deg)}75%{-webkit-transform:rotate(270deg)}83.32%{-webkit-transform:rotate(270deg)}83.33%{-webkit-transform:rotate(300deg)}91.65%{-webkit-transform:rotate(300deg)}91.66%{-webkit-transform:rotate(330deg)}100%{-webkit-transform:rotate(330deg)}} diff --git a/programs/touchui/sencha-touch.js b/programs/touchui/sencha-touch.js deleted file mode 100644 index 6fce6d443..000000000 --- a/programs/touchui/sencha-touch.js +++ /dev/null @@ -1,35 +0,0 @@ -/* -This file is part of Sencha Touch 2.0 - -Copyright (c) 2011-2012 Sencha Inc - -Contact: http://www.sencha.com/contact - -GNU General Public License Usage -This file may be used under the terms of the GNU General Public License version 3.0 as -published by the Free Software Foundation and appearing in the file LICENSE included in the -packaging of this file. - -Please review the following information to ensure the GNU General Public License version 3.0 -requirements will be met: http://www.gnu.org/copyleft/gpl.html. - -If you are unsure which license is appropriate for your use, please contact the sales department -at http://www.sencha.com/contact. - -Build date: 2012-06-04 15:34:28 (d81f71da2d56f5f71419dc892fbc85685098c6b7) -*/ -/* - -This file is part of Sencha Touch 2 - -Copyright (c) 2012 Sencha Inc - -Contact: http://www.sencha.com/contact - -Commercial Usage -Licensees holding valid commercial licenses may use this file in accordance with the Commercial Software License Agreement provided with the Software or, alternatively, in accordance with the terms contained in a written agreement between you and Sencha. - -If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact. - -*/ -(function(){var global=this,objectPrototype=Object.prototype,toString=objectPrototype.toString,enumerables=true,enumerablesTest={toString:1},emptyFn=function(){},i;if(typeof Ext==="undefined"){global.Ext={}}Ext.global=global;for(i in enumerablesTest){enumerables=null}if(enumerables){enumerables=["hasOwnProperty","valueOf","isPrototypeOf","propertyIsEnumerable","toLocaleString","toString","constructor"]}Ext.enumerables=enumerables;Ext.apply=function(object,config,defaults){if(defaults){Ext.apply(object,defaults)}if(object&&config&&typeof config==="object"){var i,j,k;for(i in config){object[i]=config[i]}if(enumerables){for(j=enumerables.length;j--;){k=enumerables[j];if(config.hasOwnProperty(k)){object[k]=config[k]}}}}return object};Ext.buildSettings=Ext.apply({baseCSSPrefix:"x-",scopeResetCSS:false},Ext.buildSettings||{});Ext.apply(Ext,{emptyFn:emptyFn,baseCSSPrefix:Ext.buildSettings.baseCSSPrefix,applyIf:function(object,config){var property;if(object){for(property in config){if(object[property]===undefined){object[property]=config[property]}}}return object},iterate:function(object,fn,scope){if(Ext.isEmpty(object)){return}if(scope===undefined){scope=object}if(Ext.isIterable(object)){Ext.Array.each.call(Ext.Array,object,fn,scope)}else{Ext.Object.each.call(Ext.Object,object,fn,scope)}}});Ext.apply(Ext,{extend:function(){var objectConstructor=objectPrototype.constructor,inlineOverrides=function(o){for(var m in o){if(!o.hasOwnProperty(m)){continue}this[m]=o[m]}};return function(subclass,superclass,overrides){if(Ext.isObject(superclass)){overrides=superclass;superclass=subclass;subclass=overrides.constructor!==objectConstructor?overrides.constructor:function(){superclass.apply(this,arguments)}}var F=function(){},subclassProto,superclassProto=superclass.prototype;F.prototype=superclassProto;subclassProto=subclass.prototype=new F();subclassProto.constructor=subclass;subclass.superclass=superclassProto;if(superclassProto.constructor===objectConstructor){superclassProto.constructor=superclass}subclass.override=function(overrides){Ext.override(subclass,overrides)};subclassProto.override=inlineOverrides;subclassProto.proto=subclassProto;subclass.override(overrides);subclass.extend=function(o){return Ext.extend(subclass,o)};return subclass}}(),override:function(cls,overrides){if(cls.$isClass){return cls.override(overrides)}else{Ext.apply(cls.prototype,overrides)}}});Ext.apply(Ext,{valueFrom:function(value,defaultValue,allowBlank){return Ext.isEmpty(value,allowBlank)?defaultValue:value},typeOf:function(value){if(value===null){return"null"}var type=typeof value;if(type==="undefined"||type==="string"||type==="number"||type==="boolean"){return type}var typeToString=toString.call(value);switch(typeToString){case"[object Array]":return"array";case"[object Date]":return"date";case"[object Boolean]":return"boolean";case"[object Number]":return"number";case"[object RegExp]":return"regexp"}if(type==="function"){return"function"}if(type==="object"){if(value.nodeType!==undefined){if(value.nodeType===3){return(/\S/).test(value.nodeValue)?"textnode":"whitespace"}else{return"element"}}return"object"}},isEmpty:function(value,allowEmptyString){return(value===null)||(value===undefined)||(!allowEmptyString?value==="":false)||(Ext.isArray(value)&&value.length===0)},isArray:("isArray" in Array)?Array.isArray:function(value){return toString.call(value)==="[object Array]"},isDate:function(value){return toString.call(value)==="[object Date]"},isObject:(toString.call(null)==="[object Object]")?function(value){return value!==null&&value!==undefined&&toString.call(value)==="[object Object]"&&value.ownerDocument===undefined}:function(value){return toString.call(value)==="[object Object]"},isSimpleObject:function(value){return value instanceof Object&&value.constructor===Object},isPrimitive:function(value){var type=typeof value;return type==="string"||type==="number"||type==="boolean"},isFunction:(typeof document!=="undefined"&&typeof document.getElementsByTagName("body")==="function")?function(value){return toString.call(value)==="[object Function]"}:function(value){return typeof value==="function"},isNumber:function(value){return typeof value==="number"&&isFinite(value)},isNumeric:function(value){return !isNaN(parseFloat(value))&&isFinite(value)},isString:function(value){return typeof value==="string"},isBoolean:function(value){return typeof value==="boolean"},isElement:function(value){return value?value.nodeType===1:false},isTextNode:function(value){return value?value.nodeName==="#text":false},isDefined:function(value){return typeof value!=="undefined"},isIterable:function(value){return(value&&typeof value!=="string")?value.length!==undefined:false}});Ext.apply(Ext,{clone:function(item){if(item===null||item===undefined){return item}if(item.nodeType&&item.cloneNode){return item.cloneNode(true)}var type=toString.call(item);if(type==="[object Date]"){return new Date(item.getTime())}var i,j,k,clone,key;if(type==="[object Array]"){i=item.length;clone=[];while(i--){clone[i]=Ext.clone(item[i])}}else{if(type==="[object Object]"&&item.constructor===Object){clone={};for(key in item){clone[key]=Ext.clone(item[key])}if(enumerables){for(j=enumerables.length;j--;){k=enumerables[j];clone[k]=item[k]}}}}return clone||item},getUniqueGlobalNamespace:function(){var uniqueGlobalNamespace=this.uniqueGlobalNamespace;if(uniqueGlobalNamespace===undefined){var i=0;do{uniqueGlobalNamespace="ExtBox"+(++i)}while(Ext.global[uniqueGlobalNamespace]!==undefined);Ext.global[uniqueGlobalNamespace]=Ext;this.uniqueGlobalNamespace=uniqueGlobalNamespace}return uniqueGlobalNamespace},functionFactory:function(){var args=Array.prototype.slice.call(arguments),ln=args.length;if(ln>0){args[ln-1]="var Ext=window."+this.getUniqueGlobalNamespace()+";"+args[ln-1]}return Function.prototype.constructor.apply(Function.prototype,args)},globalEval:("execScript" in global)?function(code){global.execScript(code)}:function(code){(function(){eval(code)})()},});Ext.type=Ext.typeOf})();(function(){var a="4.1.0",b;Ext.Version=b=Ext.extend(Object,{constructor:function(d){var c=this.toNumber,f,e;if(d instanceof b){return d}this.version=this.shortVersion=String(d).toLowerCase().replace(/_/g,".").replace(/[\-+]/g,"");e=this.version.search(/([^\d\.])/);if(e!==-1){this.release=this.version.substr(e,d.length);this.shortVersion=this.version.substr(0,e)}this.shortVersion=this.shortVersion.replace(/[^\d]/g,"");f=this.version.split(".");this.major=c(f.shift());this.minor=c(f.shift());this.patch=c(f.shift());this.build=c(f.shift());return this},toNumber:function(c){c=parseInt(c||0,10);if(isNaN(c)){c=0}return c},toString:function(){return this.version},valueOf:function(){return this.version},getMajor:function(){return this.major||0},getMinor:function(){return this.minor||0},getPatch:function(){return this.patch||0},getBuild:function(){return this.build||0},getRelease:function(){return this.release||""},isGreaterThan:function(c){return b.compare(this.version,c)===1},isGreaterThanOrEqual:function(c){return b.compare(this.version,c)>=0},isLessThan:function(c){return b.compare(this.version,c)===-1},isLessThanOrEqual:function(c){return b.compare(this.version,c)<=0},equals:function(c){return b.compare(this.version,c)===0},match:function(c){c=String(c);return this.version.substr(0,c.length)===c},toArray:function(){return[this.getMajor(),this.getMinor(),this.getPatch(),this.getBuild(),this.getRelease()]},getShortVersion:function(){return this.shortVersion},gt:function(){return this.isGreaterThan.apply(this,arguments)},lt:function(){return this.isLessThan.apply(this,arguments)},gtEq:function(){return this.isGreaterThanOrEqual.apply(this,arguments)},ltEq:function(){return this.isLessThanOrEqual.apply(this,arguments)}});Ext.apply(b,{releaseValueMap:{dev:-6,alpha:-5,a:-5,beta:-4,b:-4,rc:-3,"#":-2,p:-1,pl:-1},getComponentValue:function(c){return !c?0:(isNaN(c)?this.releaseValueMap[c]||c:parseInt(c,10))},compare:function(g,f){var d,e,c;g=new b(g).toArray();f=new b(f).toArray();for(c=0;ce){return 1}}}return 0}});Ext.apply(Ext,{versions:{},lastRegisteredVersion:null,setVersion:function(d,c){Ext.versions[d]=new b(c);Ext.lastRegisteredVersion=Ext.versions[d];return this},getVersion:function(c){if(c===undefined){return Ext.lastRegisteredVersion}return Ext.versions[c]},deprecate:function(c,e,f,d){if(b.compare(Ext.getVersion(c),e)<1){f.call(d)}}});Ext.setVersion("core",a)})();Ext.String={trimRegex:/^[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u2028\u2029\u202f\u205f\u3000]+|[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u2028\u2029\u202f\u205f\u3000]+$/g,escapeRe:/('|\\)/g,formatRe:/\{(\d+)\}/g,escapeRegexRe:/([-.*+?^${}()|[\]\/\\])/g,htmlEncode:(function(){var d={"&":"&",">":">","<":"<",'"':"""},b=[],c,a;for(c in d){b.push(c)}a=new RegExp("("+b.join("|")+")","g");return function(e){return(!e)?e:String(e).replace(a,function(g,f){return d[f]})}})(),htmlDecode:(function(){var d={"&":"&",">":">","<":"<",""":'"'},b=[],c,a;for(c in d){b.push(c)}a=new RegExp("("+b.join("|")+"|&#[0-9]{1,5};)","g");return function(e){return(!e)?e:String(e).replace(a,function(g,f){if(f in d){return d[f]}else{return String.fromCharCode(parseInt(f.substr(2),10))}})}})(),urlAppend:function(b,a){if(!Ext.isEmpty(a)){return b+(b.indexOf("?")===-1?"?":"&")+a}return b},trim:function(a){return a.replace(Ext.String.trimRegex,"")},capitalize:function(a){return a.charAt(0).toUpperCase()+a.substr(1)},ellipsis:function(c,a,d){if(c&&c.length>a){if(d){var e=c.substr(0,a-2),b=Math.max(e.lastIndexOf(" "),e.lastIndexOf("."),e.lastIndexOf("!"),e.lastIndexOf("?"));if(b!==-1&&b>=(a-15)){return e.substr(0,b)+"..."}}return c.substr(0,a-3)+"..."}return c},escapeRegex:function(a){return a.replace(Ext.String.escapeRegexRe,"\\$1")},escape:function(a){return a.replace(Ext.String.escapeRe,"\\$1")},toggle:function(b,c,a){return b===c?a:c},leftPad:function(b,c,d){var a=String(b);d=d||" ";while(a.lengthH){for(C=e;C--;){F[z+C]=F[H+C]}}}if(J&&G===B){F.length=B;F.push.apply(F,I)}else{F.length=B+J;for(C=0;C-1;y--){if(A.call(z||C[y],C[y],y,C)===false){return y}}}return true},forEach:i?function(z,y,e){return z.forEach(y,e)}:function(B,z,y){var e=0,A=B.length;for(;ee){e=z}}}return e},mean:function(e){return e.length>0?a.sum(e)/e.length:undefined},sum:function(B){var y=0,e,A,z;for(e=0,A=B.length;e=c){f+=c}else{if(b*2<-c){f-=c}}}return Ext.Number.constrain(f,d,g)},toFixed:function(d,b){if(a){b=b||0;var c=Math.pow(10,b);return(Math.round(d*c)/c).toFixed(b)}return d.toFixed(b)},from:function(c,b){if(isFinite(c)){c=parseFloat(c)}return !isNaN(c)?c:b}}})();Ext.num=function(){return Ext.Number.from.apply(this,arguments)};(function(){var a=function(){};var b=Ext.Object={chain:function(d){a.prototype=d;var c=new a();a.prototype=null;return c},toQueryObjects:function(e,j,d){var c=b.toQueryObjects,h=[],f,g;if(Ext.isArray(j)){for(f=0,g=j.length;f0){h=n.split("=");v=decodeURIComponent(h[0]);m=(h[1]!==undefined)?decodeURIComponent(h[1]):"";if(!q){if(t.hasOwnProperty(v)){if(!Ext.isArray(t[v])){t[v]=[t[v]]}t[v].push(m)}else{t[v]=m}}else{g=v.match(/(\[):?([^\]]*)\]/g);s=v.match(/^([^\[]+)/);v=s[0];k=[];if(g===null){t[v]=m;continue}for(o=0,c=g.length;o0){return setTimeout(e,c)}e();return 0},createSequence:function(b,c,a){if(!c){return b}else{return function(){var d=b.apply(this,arguments);c.apply(a||this,arguments);return d}}},createBuffered:function(e,b,d,c){var a;return function(){if(!d){d=this}if(!c){c=Array.prototype.slice.call(arguments)}if(a){clearTimeout(a);a=null}a=setTimeout(function(){e.apply(d,c)},b)}},createThrottled:function(e,b,d){var f,a,c,h,g=function(){e.apply(d||this,c);f=new Date().getTime()};return function(){a=new Date().getTime()-f;c=arguments;clearTimeout(h);if(!f||(a>=b)){g()}else{h=setTimeout(g,b-a)}}},interceptBefore:function(b,a,c){var d=b[a]||Ext.emptyFn;return b[a]=function(){var e=c.apply(this,arguments);d.apply(this,arguments);return e}},interceptAfter:function(b,a,c){var d=b[a]||Ext.emptyFn;return b[a]=function(){d.apply(this,arguments);return c.apply(this,arguments)}}};Ext.defer=Ext.Function.alias(Ext.Function,"defer");Ext.pass=Ext.Function.alias(Ext.Function,"pass");Ext.bind=Ext.Function.alias(Ext.Function,"bind");Ext.JSON=new (function(){var useHasOwn=!!{}.hasOwnProperty,isNative=function(){var useNative=null;return function(){if(useNative===null){useNative=Ext.USE_NATIVE_JSON&&window.JSON&&JSON.toString()=="[object JSON]"}return useNative}}(),pad=function(n){return n<10?"0"+n:n},doDecode=function(json){return eval("("+json+")")},doEncode=function(o){if(!Ext.isDefined(o)||o===null){return"null"}else{if(Ext.isArray(o)){return encodeArray(o)}else{if(Ext.isDate(o)){return Ext.JSON.encodeDate(o)}else{if(Ext.isString(o)){return encodeString(o)}else{if(typeof o=="number"){return isFinite(o)?String(o):"null"}else{if(Ext.isBoolean(o)){return String(o)}else{if(Ext.isObject(o)){return encodeObject(o)}else{if(typeof o==="function"){return"null"}}}}}}}}return"undefined"},m={"\b":"\\b","\t":"\\t","\n":"\\n","\f":"\\f","\r":"\\r",'"':'\\"',"\\":"\\\\","\x0b":"\\u000b"},charToReplace=/[\\\"\x00-\x1f\x7f-\uffff]/g,encodeString=function(s){return'"'+s.replace(charToReplace,function(a){var c=m[a];return typeof c==="string"?c:"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)})+'"'},encodeArray=function(o){var a=["[",""],len=o.length,i;for(i=0;i0){for(d=0;d0){if(l===k){return n[l]}m=n[l];k=k.substring(l.length+1)}if(m.length>0){m+="/"}return m.replace(/\/\.\//g,"/")+k.replace(/\./g,"/")+".js"},getPrefix:function(l){var n=this.config.paths,m,k="";if(n.hasOwnProperty(l)){return l}for(m in n){if(n.hasOwnProperty(m)&&m+"."===l.substring(0,m.length+1)){if(m.length>k.length){k=m}}}return k},require:function(m,l,k,n){if(l){l.call(k)}},syncRequire:function(){},exclude:function(l){var k=this;return{require:function(o,n,m){return k.require(o,n,m,l)},syncRequire:function(o,n,m){return k.syncRequire(o,n,m,l)}}},onReady:function(n,m,o,k){var l;if(o!==false&&Ext.onDocumentReady){l=n;n=function(){Ext.onDocumentReady(l,m,k)}}n.call(m)}};Ext.apply(b,{documentHead:typeof document!="undefined"&&(document.head||document.getElementsByTagName("head")[0]),isLoading:false,queue:[],isClassFileLoaded:{},isFileLoaded:{},readyListeners:[],optionalRequires:[],requiresMap:{},numPendingFiles:0,numLoadedFiles:0,hasFileLoadError:false,classNameToFilePathMap:{},syncModeEnabled:false,scriptElements:{},refreshQueue:function(){var k=this.queue,q=k.length,n,p,l,o,m;if(q===0){this.triggerReady();return}for(n=0;nthis.numLoadedFiles){continue}l=0;do{if(a.isCreated(o[l])){f(o,l,1)}else{l++}}while(l=200&&n<300)||n==304||(n==0&&q.length>0)){Ext.globalEval(q+"\n//@ sourceURL="+l);s.call(w)}else{}u=null}},syncRequire:function(){var k=this.syncModeEnabled;if(!k){this.syncModeEnabled=true}this.require.apply(this,arguments);if(!k){this.syncModeEnabled=false}this.refreshQueue()},require:function(F,t,n,q){var v={},m={},y=this.queue,C=this.classNameToFilePathMap,A=this.isClassFileLoaded,s=[],H=[],E=[],l=[],r,G,x,w,k,p,D,B,z,u,o;if(q){q=h(q);for(B=0,u=q.length;B0){s=a.getNamesByExpression(k);for(z=0,o=s.length;z0){r=function(){var K=[],J,L,I;for(J=0,L=l.length;J0){H=a.getNamesByExpression(w);o=H.length;for(z=0;z0){if(!this.config.enabled){throw new Error("Ext.Loader is not enabled, so dependencies cannot be resolved dynamically. Missing required class"+((E.length>1)?"es":"")+": "+E.join(", "))}}else{r.call(n);return this}G=this.syncModeEnabled;if(!G){y.push({requires:E.slice(),callback:r,scope:n})}u=E.length;for(B=0;B=2){if("1496x2048" in r){e(r["1496x2048"],"(orientation: landscape)")}if("1536x2008" in r){e(r["1536x2008"],"(orientation: portrait)")}}else{if("748x1024" in r){e(r["748x1024"],"(orientation: landscape)")}if("768x1004" in r){e(r["768x1004"],"(orientation: portrait)")}}}else{if(o>=2&&Ext.os.version.gtEq("4.3")){e(r["640x920"])}else{e(r["320x460"])}}},application:function(b){var a=b.name,e,d,c;if(!b){b={}}if(!Ext.Loader.config.paths[a]){Ext.Loader.setPath(a,b.appFolder||"app")}c=Ext.Array.from(b.requires);b.requires=["Ext.app.Application"];e=b.onReady;d=b.scope;b.onReady=function(){b.requires=c;new Ext.app.Application(b);if(e){e.call(d)}};Ext.setup(b)},factoryConfig:function(a,l){var g=Ext.isSimpleObject(a);if(g&&a.xclass){var f=a.xclass;delete a.xclass;Ext.require(f,function(){Ext.factoryConfig(a,function(i){l(Ext.create(f,i))})});return}var d=Ext.isArray(a),m=[],k,j,c,e;if(g||d){if(g){for(k in a){if(a.hasOwnProperty(k)){j=a[k];if(Ext.isSimpleObject(j)||Ext.isArray(j)){m.push(k)}}}}else{for(c=0,e=a.length;c=e){l(a);return}k=m[c];j=a[k];Ext.factoryConfig(j,h)}b();return}l(a)},factory:function(b,e,a,f){var d=Ext.ClassManager,c;if(!b||b.isInstance){if(a&&a!==b){a.destroy()}return b}if(f){if(typeof b=="string"){return d.instantiateByAlias(f+"."+b)}else{if(Ext.isObject(b)&&"type" in b){return d.instantiateByAlias(f+"."+b.type,b)}}}if(b===true){return a||d.instantiate(e)}if("xtype" in b){c=d.instantiateByAlias("widget."+b.xtype,b)}else{if("xclass" in b){c=d.instantiate(b.xclass,b)}}if(c){if(a){a.destroy()}return c}if(a){return a.setConfig(b)}return d.instantiate(e,b)},deprecateClassMember:function(b,c,a,d){return this.deprecateProperty(b.prototype,c,a,d)},deprecateClassMembers:function(b,c){var d=b.prototype,e,a;for(e in c){if(c.hasOwnProperty(e)){a=c[e];this.deprecateProperty(d,e,a)}}},deprecateProperty:function(b,c,a,d){if(!d){d="'"+c+"' is deprecated"}if(a){d+=", please use '"+a+"' instead"}if(a){Ext.Object.defineProperty(b,c,{get:function(){return this[a]},set:function(e){this[a]=e},configurable:true})}},deprecatePropertyValue:function(b,a,d,c){Ext.Object.defineProperty(b,a,{get:function(){return d},configurable:true})},deprecateMethod:function(b,a,d,c){b[a]=function(){if(d){return d.apply(this,arguments)}}},deprecateClassMethod:function(a,b,h,d){if(typeof b!="string"){var g,f;for(g in b){if(b.hasOwnProperty(g)){f=b[g];Ext.deprecateClassMethod(a,g,f)}}return}var c=typeof h=="string",e;if(!d){d="'"+b+"()' is deprecated, please use '"+(c?h:h.name)+"()' instead"}if(c){e=function(){return this[h].apply(this,arguments)}}else{e=function(){return h.apply(this,arguments)}}if(b in a.prototype){Ext.Object.defineProperty(a.prototype,b,{value:null,writable:true,configurable:true})}a.addMember(b,e)},isReady:false,readyListeners:[],triggerReady:function(){var b=Ext.readyListeners,a,c,d;if(!Ext.isReady){Ext.isReady=true;for(a=0,c=b.length;a0){return b+Ext.String.capitalize(a)}return a}},function(){var a=Ext.browser=new this(Ext.global.navigator.userAgent)});Ext.define("Ext.env.OS",{requires:["Ext.Version"],statics:{names:{ios:"iOS",android:"Android",webos:"webOS",blackberry:"BlackBerry",rimTablet:"RIMTablet",mac:"MacOS",win:"Windows",linux:"Linux",bada:"Bada",other:"Other"},prefixes:{ios:"i(?:Pad|Phone|Pod)(?:.*)CPU(?: iPhone)? OS ",android:"(Android |HTC_|Silk/)",blackberry:"BlackBerry(?:.*)Version/",rimTablet:"RIM Tablet OS ",webos:"(?:webOS|hpwOS)/",bada:"Bada/"}},is:Ext.emptyFn,name:null,version:null,setFlag:function(a,b){if(typeof b=="undefined"){b=true}this.is[a]=b;this.is[a.toLowerCase()]=b;return this},constructor:function(m,b){var k=this.statics(),j=k.names,c=k.prefixes,a,h="",d,g,f,l,e;e=this.is=function(i){return this.is[i]===true};for(d in c){if(c.hasOwnProperty(d)){g=c[d];f=m.match(new RegExp("(?:"+g+")([^\\s;]+)"));if(f){a=j[d];if(f[1]&&(f[1]=="HTC_"||f[1]=="Silk/")){h=new Ext.Version("2.3")}else{h=new Ext.Version(f[f.length-1])}break}}}if(!a){a=j[(m.toLowerCase().match(/mac|win|linux/)||["other"])[0]];h=new Ext.Version("")}this.name=a;this.version=h;if(b){this.setFlag(b)}this.setFlag(a);if(h){this.setFlag(a+(h.getMajor()||""));this.setFlag(a+h.getShortVersion())}for(d in j){if(j.hasOwnProperty(d)){l=j[d];if(!e.hasOwnProperty(a)){this.setFlag(l,(a===l))}}}return this}},function(){var a=Ext.global.navigator,e=a.userAgent,b,g,d;Ext.os=b=new this(e,a.platform);g=b.name;var c=window.location.search.match(/deviceType=(Tablet|Phone)/),f=window.deviceType;if(c&&c[1]){d=c[1]}else{if(f==="iPhone"){d="Phone"}else{if(f==="iPad"){d="Tablet"}else{if(!b.is.Android&&!b.is.iOS&&/Windows|Linux|MacOS/.test(g)){d="Desktop"}else{if(b.is.iPad||b.is.Android3||(b.is.Android4&&e.search(/mobile/i)==-1)){d="Tablet"}else{d="Phone"}}}}}b.setFlag(d,true);b.deviceType=d});Ext.define("Ext.env.Feature",{requires:["Ext.env.Browser","Ext.env.OS"],constructor:function(){this.testElements={};this.has=function(a){return !!this.has[a]};return this},getTestElement:function(a,b){if(a===undefined){a="div"}else{if(typeof a!=="string"){return a}}if(b){return document.createElement(a)}if(!this.testElements[a]){this.testElements[a]=document.createElement(a)}return this.testElements[a]},isStyleSupported:function(c,b){var d=this.getTestElement(b).style,a=Ext.String.capitalize(c);if(typeof d[c]!=="undefined"||typeof d[Ext.browser.getStylePrefix(c)+a]!=="undefined"){return true}return false},isEventSupported:function(c,a){if(a===undefined){a=window}var e=this.getTestElement(a),b="on"+c.toLowerCase(),d=(b in e);if(!d){if(e.setAttribute&&e.removeAttribute){e.setAttribute(b,"");d=typeof e[b]==="function";if(typeof e[b]!=="undefined"){e[b]=undefined}e.removeAttribute(b)}}return d},getSupportedPropertyName:function(b,a){var c=Ext.browser.getVendorProperyName(a);if(c in b){return c}else{if(a in b){return a}}return null},registerTest:Ext.Function.flexSetter(function(a,b){this.has[a]=b.call(this);return this})},function(){Ext.feature=new this;var a=Ext.feature.has;Ext.feature.registerTest({Canvas:function(){var b=this.getTestElement("canvas");return !!(b&&b.getContext&&b.getContext("2d"))},Svg:function(){var b=document;return !!(b.createElementNS&&!!b.createElementNS("http://www.w3.org/2000/svg","svg").createSVGRect)},Vml:function(){var c=this.getTestElement(),b=false;c.innerHTML="";b=(c.childNodes.length===1);c.innerHTML="";return b},Touch:function(){return this.isEventSupported("touchstart")&&!(Ext.os&&Ext.os.name.match(/Windows|MacOS|Linux/))},Orientation:function(){return("orientation" in window)&&this.isEventSupported("orientationchange")},OrientationChange:function(){return this.isEventSupported("orientationchange")},DeviceMotion:function(){return this.isEventSupported("devicemotion")},Geolocation:function(){return"geolocation" in window.navigator},SqlDatabase:function(){return"openDatabase" in window},WebSockets:function(){return"WebSocket" in window},Range:function(){return !!document.createRange},CreateContextualFragment:function(){var b=!!document.createRange?document.createRange():false;return b&&!!b.createContextualFragment},History:function(){return("history" in window&&"pushState" in window.history)},CssTransforms:function(){return this.isStyleSupported("transform")},Css3dTransforms:function(){return this.has("CssTransforms")&&this.isStyleSupported("perspective")&&!Ext.os.is.Android2},CssAnimations:function(){return this.isStyleSupported("animationName")},CssTransitions:function(){return this.isStyleSupported("transitionProperty")},Audio:function(){return !!this.getTestElement("audio").canPlayType},Video:function(){return !!this.getTestElement("video").canPlayType},ClassList:function(){return"classList" in this.getTestElement()}})});Ext.define("Ext.dom.Query",{select:function(h,b){var g=[],d,f,e,c,a;b=b||document;if(typeof b=="string"){b=document.getElementById(b)}h=h.split(",");for(f=0,c=h.length;f")}else{c.push(">");if((h=d.tpl)){h.applyOut(d.tplData,c)}if((h=d.html)){c.push(h)}if((h=d.cn||d.children)){g.generateMarkup(h,c)}f=g.closeTags;c.push(f[a]||(f[a]=""))}}}return c},generateStyles:function(e,c){var b=c||[],d;for(d in e){if(e.hasOwnProperty(d)){b.push(this.decamelizeName(d),":",e[d],";")}}return c||b.join("")},markup:function(a){if(typeof a=="string"){return a}var b=this.generateMarkup(a,[]);return b.join("")},applyStyles:function(a,b){Ext.fly(a).applyStyles(b)},createContextualFragment:function(c){var f=document.createElement("div"),a=document.createDocumentFragment(),b=0,d,e;f.innerHTML=c;e=f.childNodes;d=e.length;for(;b0){this.id=b=a.id}else{a.id=b=this.mixins.identifiable.getUniqueId.call(this)}this.self.cache[b]=this}return b},setId:function(c){var a=this.id,b=this.self.cache;if(a){delete b[a]}this.dom.id=c;this.id=c;b[c]=this;return this},setHtml:function(a){this.dom.innerHTML=a},getHtml:function(){return this.dom.innerHTML},setText:function(a){this.dom.textContent=a},redraw:function(){var b=this.dom,a=b.style;a.display="none";b.offsetHeight;a.display=""},isPainted:function(){var a=this.dom;return Boolean(a&&a.offsetParent)},set:function(a,b){var e=this.dom,c,d;for(c in a){if(a.hasOwnProperty(c)){d=a[c];if(c=="style"){this.applyStyles(d)}else{if(c=="cls"){e.className=d}else{if(b!==false){if(d===undefined){e.removeAttribute(c)}else{e.setAttribute(c,d)}}else{e[c]=d}}}}}return this},is:function(a){return Ext.DomQuery.is(this.dom,a)},getValue:function(b){var a=this.dom.value;return b?parseInt(a,10):a},getAttribute:function(a,b){var c=this.dom;return c.getAttributeNS(b,a)||c.getAttribute(b+":"+a)||c.getAttribute(a)||c[a]},destroy:function(){this.isDestroyed=true;var a=Ext.Element.cache,b=this.dom;if(b&&b.parentNode&&b.tagName!="BODY"){b.parentNode.removeChild(b)}delete a[this.id];delete this.dom}},function(a){Ext.elements=Ext.cache=a.cache;this.addStatics({Fly:new Ext.Class({extend:a,constructor:function(b){this.dom=b}}),_flyweights:{},fly:function(e,c){var f=null,d=a._flyweights,b;c=c||"_global";e=Ext.getDom(e);if(e){f=d[c]||(d[c]=new a.Fly());f.dom=e;f.isSynchronized=false;b=Ext.cache[e.id];if(b&&b.isElement){b.isSynchronized=false}}return f}});Ext.get=function(b){return a.get.call(a,b)};Ext.fly=function(){return a.fly.apply(a,arguments)};Ext.ClassManager.onCreated(function(){a.mixin("observable",Ext.mixin.Observable)},null,"Ext.mixin.Observable")});Ext.dom.Element.addStatics({numberRe:/\d+$/,unitRe:/\d+(px|em|%|en|ex|pt|in|cm|mm|pc)$/i,camelRe:/(-[a-z])/gi,cssRe:/([a-z0-9-]+)\s*:\s*([^;\s]+(?:\s*[^;\s]+)*);?/gi,opacityRe:/alpha\(opacity=(.*)\)/i,propertyCache:{},defaultUnit:"px",borders:{l:"border-left-width",r:"border-right-width",t:"border-top-width",b:"border-bottom-width"},paddings:{l:"padding-left",r:"padding-right",t:"padding-top",b:"padding-bottom"},margins:{l:"margin-left",r:"margin-right",t:"margin-top",b:"margin-bottom"},addUnits:function(b,a){if(b===""||b=="auto"||b===undefined||b===null){return b||""}if(Ext.isNumber(b)||this.numberRe.test(b)){return b+(a||this.defaultUnit||"px")}else{if(!this.unitRe.test(b)){return b||""}}return b},isAncestor:function(b,d){var a=false;b=Ext.getDom(b);d=Ext.getDom(d);if(b&&d){if(b.contains){return b.contains(d)}else{if(b.compareDocumentPosition){return !!(b.compareDocumentPosition(d)&16)}else{while((d=d.parentNode)){a=d==b||a}}}}return a},parseBox:function(b){if(typeof b!="string"){b=b.toString()}var c=b.split(" "),a=c.length;if(a==1){c[1]=c[2]=c[3]=c[0]}else{if(a==2){c[2]=c[0];c[3]=c[1]}else{if(a==3){c[3]=c[1]}}}return{top:c[0]||0,right:c[1]||0,bottom:c[2]||0,left:c[3]||0}},unitizeBox:function(c,a){var b=this;c=b.parseBox(c);return b.addUnits(c.top,a)+" "+b.addUnits(c.right,a)+" "+b.addUnits(c.bottom,a)+" "+b.addUnits(c.left,a)},camelReplaceFn:function(b,c){return c.charAt(1).toUpperCase()},normalize:function(a){return this.propertyCache[a]||(this.propertyCache[a]=a.replace(this.camelRe,this.camelReplaceFn))},fromPoint:function(a,b){return Ext.get(document.elementFromPoint(a,b))},parseStyles:function(c){var a={},b=this.cssRe,d;if(c){b.lastIndex=0;while((d=b.exec(c))){a[d[1]]=d[2]}}return a}});Ext.dom.Element.addMembers({appendChild:function(a){this.dom.appendChild(Ext.getDom(a));return this},removeChild:function(a){this.dom.removeChild(Ext.getDom(a));return this},append:function(){this.appendChild.apply(this,arguments)},appendTo:function(a){Ext.getDom(a).appendChild(this.dom);return this},insertBefore:function(a){a=Ext.getDom(a);a.parentNode.insertBefore(this.dom,a);return this},insertAfter:function(a){a=Ext.getDom(a);a.parentNode.insertBefore(this.dom,a.nextSibling);return this},insertFirst:function(b){var a=Ext.getDom(b),d=this.dom,c=d.firstChild;if(!c){d.appendChild(a)}else{d.insertBefore(a,c)}return this},insertSibling:function(e,c,d){var f=this,b,a=(c||"before").toLowerCase()=="after",g;if(Ext.isArray(e)){g=f;Ext.each(e,function(h){b=Ext.fly(g,"_internal").insertSibling(h,c,d);if(a){g=b}});return b}e=e||{};if(e.nodeType||e.dom){b=f.dom.parentNode.insertBefore(Ext.getDom(e),a?f.dom.nextSibling:f.dom);if(!d){b=Ext.get(b)}}else{if(a&&!f.dom.nextSibling){b=Ext.core.DomHelper.append(f.dom.parentNode,e,!d)}else{b=Ext.core.DomHelper[a?"insertAfter":"insertBefore"](f.dom,e,!d)}}return b},replace:function(a){a=Ext.get(a);this.insertBefore(a);a.remove();return this},replaceWith:function(a){var b=this;if(a.nodeType||a.dom||typeof a=="string"){a=Ext.get(a);b.dom.parentNode.insertBefore(a,b.dom)}else{a=Ext.core.DomHelper.insertBefore(b.dom,a)}delete Ext.cache[b.id];Ext.removeNode(b.dom);b.id=Ext.id(b.dom=a);Ext.dom.Element.addToCache(b.isFlyweight?new Ext.dom.Element(b.dom):b);return b},createChild:function(b,a,c){b=b||{tag:"div"};if(a){return Ext.core.DomHelper.insertBefore(a,b,c!==true)}else{return Ext.core.DomHelper[!this.dom.firstChild?"insertFirst":"append"](this.dom,b,c!==true)}},wrap:function(b,c){var e=this.dom,f=this.self.create(b,c),d=(c)?f:f.dom,a=e.parentNode;if(a){a.insertBefore(d,e)}d.appendChild(e);return f},wrapAllChildren:function(a){var d=this.dom,b=d.childNodes,e=this.self.create(a),c=e.dom;while(b.length>0){c.appendChild(d.firstChild)}d.appendChild(c);return e},unwrapAllChildren:function(){var c=this.dom,b=c.childNodes,a=c.parentNode;if(a){while(b.length>0){a.insertBefore(c,c.firstChild)}this.destroy()}},unwrap:function(){var c=this.dom,a=c.parentNode,b;if(a){b=a.parentNode;b.insertBefore(c,a);b.removeChild(a)}else{b=document.createDocumentFragment();b.appendChild(c)}return this},insertHtml:function(b,c,a){var d=Ext.core.DomHelper.insertHtml(b,this.dom,c);return a?Ext.get(d):d}});Ext.dom.Element.override({getX:function(a){return this.getXY(a)[0]},getY:function(a){return this.getXY(a)[1]},getXY:function(){var a=window.webkitConvertPointFromNodeToPage;if(a){return function(){var b=a(this.dom,new WebKitPoint(0,0));return[b.x,b.y]}}else{return function(){var c=this.dom.getBoundingClientRect(),b=Math.round;return[b(c.left+window.pageXOffset),b(c.top+window.pageYOffset)]}}}(),getOffsetsTo:function(a){var c=this.getXY(),b=Ext.fly(a,"_internal").getXY();return[c[0]-b[0],c[1]-b[1]]},setX:function(a){return this.setXY([a,this.getY()])},setY:function(a){return this.setXY([this.getX(),a])},setXY:function(d){var b=this;if(arguments.length>1){d=[d,arguments[1]]}var c=b.translatePoints(d),a=b.dom.style;for(d in c){if(!c.hasOwnProperty(d)){continue}if(!isNaN(c[d])){a[d]=c[d]+"px"}}return b},getLeft:function(){return parseInt(this.getStyle("left"),10)||0},getRight:function(){return parseInt(this.getStyle("right"),10)||0},getTop:function(){return parseInt(this.getStyle("top"),10)||0},getBottom:function(){return parseInt(this.getStyle("bottom"),10)||0},translatePoints:function(a,g){g=isNaN(a[1])?g:a[1];a=isNaN(a[0])?a:a[0];var d=this,e=d.isStyle("position","relative"),f=d.getXY(),b=parseInt(d.getStyle("left"),10),c=parseInt(d.getStyle("top"),10);b=!isNaN(b)?b:(e?0:d.dom.offsetLeft);c=!isNaN(c)?c:(e?0:d.dom.offsetTop);return{left:(a-f[0]+b),top:(g-f[1]+c)}},setBox:function(d){var c=this,b=d.width,a=d.height,f=d.top,e=d.left;if(e!==undefined){c.setLeft(e)}if(f!==undefined){c.setTop(f)}if(b!==undefined){c.setWidth(b)}if(a!==undefined){c.setHeight(a)}return this},getBox:function(g,j){var h=this,e=h.dom,c=e.offsetWidth,k=e.offsetHeight,n,f,d,a,m,i;if(!j){n=h.getXY()}else{if(g){n=[0,0]}else{n=[parseInt(h.getStyle("left"),10)||0,parseInt(h.getStyle("top"),10)||0]}}if(!g){f={x:n[0],y:n[1],0:n[0],1:n[1],width:c,height:k}}else{d=h.getBorderWidth.call(h,"l")+h.getPadding.call(h,"l");a=h.getBorderWidth.call(h,"r")+h.getPadding.call(h,"r");m=h.getBorderWidth.call(h,"t")+h.getPadding.call(h,"t");i=h.getBorderWidth.call(h,"b")+h.getPadding.call(h,"b");f={x:n[0]+d,y:n[1]+m,0:n[0]+d,1:n[1]+m,width:c-(d+a),height:k-(m+i)}}f.left=f.x;f.top=f.y;f.right=f.x+f.width;f.bottom=f.y+f.height;return f},getPageBox:function(e){var g=this,c=g.dom,j=c.offsetWidth,f=c.offsetHeight,m=g.getXY(),k=m[1],a=m[0]+j,i=m[1]+f,d=m[0];if(!c){return new Ext.util.Region()}if(e){return new Ext.util.Region(k,a,i,d)}else{return{left:d,top:k,width:j,height:f,right:a,bottom:i}}}});Ext.dom.Element.addMembers({WIDTH:"width",HEIGHT:"height",MIN_WIDTH:"min-width",MIN_HEIGHT:"min-height",MAX_WIDTH:"max-width",MAX_HEIGHT:"max-height",TOP:"top",RIGHT:"right",BOTTOM:"bottom",LEFT:"left",VISIBILITY:1,DISPLAY:2,OFFSETS:3,SEPARATOR:"-",trimRe:/^\s+|\s+$/g,wordsRe:/\w/g,spacesRe:/\s+/,styleSplitRe:/\s*(?::|;)\s*/,transparentRe:/^(?:transparent|(?:rgba[(](?:\s*\d+\s*[,]){3}\s*0\s*[)]))$/i,classNameSplitRegex:/[\s]+/,borders:{t:"border-top-width",r:"border-right-width",b:"border-bottom-width",l:"border-left-width"},paddings:{t:"padding-top",r:"padding-right",b:"padding-bottom",l:"padding-left"},margins:{t:"margin-top",r:"margin-right",b:"margin-bottom",l:"margin-left"},defaultUnit:"px",isSynchronized:false,synchronize:function(){var g=this.dom,a={},d=g.className,f,c,e,b;if(d.length>0){f=g.className.split(this.classNameSplitRegex);for(c=0,e=f.length;c0?a:0},getWidth:function(a){var c=this.dom,b=a?(c.clientWidth-this.getPadding("lr")):c.offsetWidth;return b>0?b:0},getBorderWidth:function(a){return this.addStyles(a,this.borders)},getPadding:function(a){return this.addStyles(a,this.paddings)},applyStyles:function(d){if(d){var e=this.dom,c,b,a;if(typeof d=="function"){d=d.call()}c=typeof d;if(c=="string"){d=Ext.util.Format.trim(d).split(this.styleSplitRe);for(b=0,a=d.length;b "+a,c.dom);return b?d:Ext.get(d)},parent:function(a,b){return this.matchNode("parentNode","parentNode",a,b)},next:function(a,b){return this.matchNode("nextSibling","nextSibling",a,b)},prev:function(a,b){return this.matchNode("previousSibling","previousSibling",a,b)},first:function(a,b){return this.matchNode("nextSibling","firstChild",a,b)},last:function(a,b){return this.matchNode("previousSibling","lastChild",a,b)},matchNode:function(b,e,a,c){if(!this.dom){return null}var d=this.dom[e];while(d){if(d.nodeType==1&&(!a||Ext.DomQuery.is(d,a))){return !c?Ext.get(d):d}d=d[b]}return null},isAncestor:function(a){return this.self.isAncestor.call(this.self,this.dom,a)}});Ext.define("Ext.dom.CompositeElementLite",{alternateClassName:["Ext.CompositeElementLite","Ext.CompositeElement"],requires:["Ext.dom.Element"],statics:{importElementMethods:function(){}},constructor:function(b,a){this.elements=[];this.add(b,a);this.el=new Ext.dom.Element.Fly()},isComposite:true,getElement:function(a){return this.el.attach(a).synchronize()},transformElement:function(a){return Ext.getDom(a)},getCount:function(){return this.elements.length},add:function(c,a){var e=this.elements,b,d;if(!c){return this}if(typeof c=="string"){c=Ext.dom.Element.selectorFunction(c,a)}else{if(c.isComposite){c=c.elements}else{if(!Ext.isIterable(c)){c=[c]}}}for(b=0,d=c.length;b-1){c=Ext.getDom(c);if(a){f=this.elements[b];f.parentNode.insertBefore(c,f);Ext.removeNode(f)}Ext.Array.splice(this.elements,b,1,c)}return this},clear:function(){this.elements=[]},addElements:function(c,a){if(!c){return this}if(typeof c=="string"){c=Ext.dom.Element.selectorFunction(c,a)}var b=this.elements;Ext.each(c,function(d){b.push(Ext.get(d))});return this},first:function(){return this.item(0)},last:function(){return this.item(this.getCount()-1)},contains:function(a){return this.indexOf(a)!=-1},removeElement:function(c,e){var b=this,d=this.elements,a;Ext.each(c,function(f){if((a=(d[f]||d[f=b.indexOf(f)]))){if(e){if(a.dom){a.remove()}else{Ext.removeNode(a)}}Ext.Array.erase(d,f,1)}});return this}},function(){var a=Ext.dom.Element,d=a.prototype,c=this.prototype,b;for(b in d){if(typeof d[b]=="function"){(function(e){c[e]=c[e]||function(){return this.invoke(e,arguments)}}).call(c,b)}}c.on=c.addListener;if(Ext.DomQuery){a.selectorFunction=Ext.DomQuery.select}a.select=function(e,f){var g;if(typeof e=="string"){g=a.selectorFunction(e,f)}else{if(e.length!==undefined){g=e}else{}}return new Ext.CompositeElementLite(g)};Ext.select=function(){return a.select.apply(a,arguments)}});Ext.define("Ext.ComponentManager",{alternateClassName:"Ext.ComponentMgr",singleton:true,constructor:function(){var a={};this.all={map:a,getArray:function(){var b=[],c;for(c in a){b.push(a[c])}return b}};this.map=a},register:function(a){var b=a.getId();this.map[a.getId()]=a},unregister:function(a){delete this.map[a.getId()]},isRegistered:function(a){return this.map[a]!==undefined},get:function(a){return this.map[a]},create:function(a,c){if(a.isComponent){return a}else{if(Ext.isString(a)){return Ext.createByAlias("widget."+a)}else{var b=a.xtype||c;return Ext.createByAlias("widget."+b,a)}}},registerType:Ext.emptyFn});Ext.define("Ext.ComponentQuery",{singleton:true,uses:["Ext.ComponentManager"]},function(){var g=this,j=["var r = [],","i = 0,","it = items,","l = it.length,","c;","for (; i < l; i++) {","c = it[i];","if (c.{0}) {","r.push(c);","}","}","return r;"].join(""),e=function(o,n){return n.method.apply(this,[o].concat(n.args))},a=function(p,t){var n=[],q=0,s=p.length,r,o=t!==">";for(;q\^])\s?|\s|$)/,c=/^(#)?([\w\-]+|\*)(?:\((true|false)\))?/,b=[{re:/^\.([\w\-]+)(?:\((true|false)\))?/,method:l},{re:/^(?:[\[](?:@)?([\w\-]+)\s?(?:(=|.=)\s?['"]?(.*?)["']?)?[\]])/,method:m},{re:/^#([\w\-]+)/,method:d},{re:/^\:([\w\-]+)(?:\(((?:\{[^\}]+\})|(?:(?!\{)[^\s>\/]*?(?!\})))\))?/,method:k},{re:/^(?:\{([^\}]+)\})/,method:j}];g.Query=Ext.extend(Object,{constructor:function(n){n=n||{};Ext.apply(this,n)},execute:function(o){var q=this.operations,r=0,s=q.length,p,n;if(!o){n=Ext.ComponentManager.all.getArray()}else{if(Ext.isArray(o)){n=o}}for(;r1){for(q=0,r=s.length;q1){r=q.length;for(p=0;p]*)\>)|(?:<\/tpl>)/g,actionsRe:/\s*(elif|elseif|if|for|exec|switch|case|eval)\s*\=\s*(?:(?:["]([^"]*)["])|(?:[']([^']*)[']))\s*/g,defaultRe:/^\s*default\s*$/,elseRe:/^\s*else\s*$/});Ext.define("Ext.app.Action",{config:{scope:null,application:null,controller:null,action:null,args:[],url:undefined,data:{},title:null,beforeFilters:[],currentFilterIndex:-1},constructor:function(a){this.initConfig(a);this.getUrl()},execute:function(){this.resume()},resume:function(){var b=this.getCurrentFilterIndex()+1,c=this.getBeforeFilters(),a=this.getController(),d=c[b];if(d){this.setCurrentFilterIndex(b);d.call(a,this)}else{a[this.getAction()].apply(a,this.getArgs())}},applyUrl:function(a){if(a===null||a===undefined){a=this.urlEncode()}return a},applyController:function(a){var c=this.getApplication(),b=c.getCurrentProfile();if(Ext.isString(a)){a=c.getController(a,b?b.getNamespace():null)}return a},urlEncode:function(){var a=this.getController(),b;if(a instanceof Ext.app.Controller){b=a.$className.split(".");a=b[b.length-1]}return a+"/"+this.getAction()}});Ext.define("Ext.app.Route",{config:{conditions:{},url:null,controller:null,action:null,initialized:false},constructor:function(a){this.initConfig(a)},recognize:function(b){if(!this.getInitialized()){this.initialize()}if(this.recognizes(b)){var c=this.matchesFor(b),a=b.match(this.matcherRegex);a.shift();return Ext.applyIf(c,{controller:this.getController(),action:this.getAction(),historyUrl:b,args:a})}},initialize:function(){this.paramMatchingRegex=new RegExp(/:([0-9A-Za-z\_]*)/g);this.paramsInMatchString=this.getUrl().match(this.paramMatchingRegex)||[];this.matcherRegex=this.createMatcherRegex(this.getUrl());this.setInitialized(true)},recognizes:function(a){return this.matcherRegex.test(a)},matchesFor:function(b){var f={},e=this.paramsInMatchString,a=b.match(this.matcherRegex),d=e.length,c;a.shift();for(c=0;c0){f.timeout=setTimeout(Ext.bind(i.handleTimeout,i,[f]),l)}i.setupErrorHandling(f);i[k]=Ext.bind(i.handleResponse,i,[f],true);i.loadScript(f);return f},abort:function(b){var c=this.statics().requests,a;if(b){if(!b.id){b=c[b]}this.abort(b)}else{for(a in c){if(c.hasOwnProperty(a)){this.abort(c[a])}}}},setupErrorHandling:function(a){a.script.onerror=Ext.bind(this.handleError,this,[a])},handleAbort:function(a){a.errorType="abort";this.handleResponse(null,a)},handleError:function(a){a.errorType="error";this.handleResponse(null,a)},cleanupErrorHandling:function(a){a.script.onerror=null},handleTimeout:function(a){a.errorType="timeout";this.handleResponse(null,a)},handleResponse:function(a,b){var c=true;if(b.timeout){clearTimeout(b.timeout)}delete this[b.callbackName];delete this.statics()[b.id];this.cleanupErrorHandling(b);Ext.fly(b.script).destroy();if(b.errorType){c=false;Ext.callback(b.failure,b.scope,[b.errorType,b])}else{Ext.callback(b.success,b.scope,[a,b])}Ext.callback(b.callback,b.scope,[c,a,b.errorType,b])},createScript:function(c,d,b){var a=document.createElement("script");a.setAttribute("src",Ext.urlAppend(c,Ext.Object.toQueryString(d)));a.setAttribute("async",true);a.setAttribute("type","text/javascript");return a},loadScript:function(a){Ext.getHead().appendChild(a.script)}});Ext.define("Ext.data.Operation",{config:{synchronous:true,action:null,filters:null,sorters:null,grouper:null,start:null,limit:null,batch:null,callback:null,scope:null,resultSet:null,records:null,request:null,response:null,withCredentials:null,params:null,url:null,page:null,node:null,model:undefined,addRecords:false},started:false,running:false,complete:false,success:undefined,exception:false,error:undefined,constructor:function(a){this.initConfig(a)},applyModel:function(a){if(typeof a=="string"){a=Ext.data.ModelManager.getModel(a);if(!a){Ext.Logger.error("Model with name "+arguments[0]+" doesnt exist.")}}if(a&&!a.prototype.isModel&&Ext.isObject(a)){a=Ext.data.ModelManager.registerType(a.storeId||a.id||Ext.id(),a)}return a},getRecords:function(){var a=this.getResultSet();return this._records||(a?a.getRecords():[])},setStarted:function(){this.started=true;this.running=true},setCompleted:function(){this.complete=true;this.running=false},setSuccessful:function(){this.success=true},setException:function(a){this.exception=true;this.success=false;this.running=false;this.error=a},hasException:function(){return this.exception===true},getError:function(){return this.error},isStarted:function(){return this.started===true},isRunning:function(){return this.running===true},isComplete:function(){return this.complete===true},wasSuccessful:function(){return this.isComplete()&&this.success===true},allowWrite:function(){return this.getAction()!="read"},process:function(d,b,c,a){if(b.getSuccess()!==false){this.setResponse(a);this.setResultSet(b);this.setCompleted();this.setSuccessful()}else{return false}return this["process"+Ext.String.capitalize(d)].call(this,b,c,a)},processRead:function(d){var b=d.getRecords(),g=[],f=this.getModel(),e=b.length,c,a;for(c=0;c]+>/gi,none:function(a){return a},asText:function(a){return String(a).replace(this.stripTagsRE,"")},asUCText:function(a){return String(a).toUpperCase().replace(this.stripTagsRE,"")},asUCString:function(a){return String(a).toUpperCase()},asDate:function(a){if(!a){return 0}if(Ext.isDate(a)){return a.getTime()}return Date.parse(String(a))},asFloat:function(a){a=parseFloat(String(a).replace(/,/g,""));return isNaN(a)?0:a},asInt:function(a){a=parseInt(String(a).replace(/,/g,""),10);return isNaN(a)?0:a}});Ext.define("Ext.data.Types",{singleton:true,requires:["Ext.data.SortTypes"],stripRe:/[\$,%]/g,dashesRe:/-/g,iso8601TestRe:/\d\dT\d\d/,iso8601SplitRe:/[- :T\.Z\+]/},function(){var b=this,a=Ext.data.SortTypes;Ext.apply(b,{AUTO:{convert:function(c){return c},sortType:a.none,type:"auto"},STRING:{convert:function(c){return(c===undefined||c===null)?(this.getAllowNull()?null:""):String(c)},sortType:a.asUCString,type:"string"},INT:{convert:function(c){return(c!==undefined&&c!==null&&c!=="")?((typeof c==="number")?parseInt(c,10):parseInt(String(c).replace(b.stripRe,""),10)):(this.getAllowNull()?null:0)},sortType:a.none,type:"int"},FLOAT:{convert:function(c){return(c!==undefined&&c!==null&&c!=="")?((typeof c==="number")?c:parseFloat(String(c).replace(b.stripRe,""),10)):(this.getAllowNull()?null:0)},sortType:a.none,type:"float"},BOOL:{convert:function(c){if((c===undefined||c===null||c==="")&&this.getAllowNull()){return null}return c!=="false"&&!!c},sortType:a.none,type:"bool"},DATE:{convert:function(e){var c=this.getDateFormat(),d;if(!e){return null}if(Ext.isDate(e)){return e}if(c){if(c=="timestamp"){return new Date(e*1000)}if(c=="time"){return new Date(parseInt(e,10))}return Ext.Date.parse(e,c)}d=new Date(Date.parse(e));if(isNaN(d)){if(b.iso8601TestRe.test(e)){d=e.split(b.iso8601SplitRe);d=new Date(d[0],d[1]-1,d[2],d[3],d[4],d[5])}if(isNaN(d)){d=new Date(Date.parse(e.replace(this.dashesRe,"/")))}}return isNaN(d)?null:d},sortType:a.asDate,type:"date"}});Ext.apply(b,{BOOLEAN:this.BOOL,INTEGER:this.INT,NUMBER:this.FLOAT})});Ext.define("Ext.data.Validations",{alternateClassName:"Ext.data.validations",singleton:true,config:{presenceMessage:"must be present",lengthMessage:"is the wrong length",formatMessage:"is the wrong format",inclusionMessage:"is not included in the list of acceptable values",exclusionMessage:"is not an acceptable value",emailMessage:"is not a valid email address"},constructor:function(a){this.initConfig(a)},getMessage:function(a){var b=this["get"+a[0].toUpperCase()+a.slice(1)+"Message"];if(b){return b.call(this)}return""},emailRe:/^\s*[\w\-\+_]+(\.[\w\-\+_]+)*\@[\w\-\+_]+\.[\w\-\+_]+(\.[\w\-\+_]+)*\s*$/,presence:function(a,b){if(arguments.length===1){b=a}return !!b||b===0},length:function(b,e){if(e===undefined||e===null){return false}var d=e.length,c=b.min,a=b.max;if((c&&da)){return false}else{return true}},email:function(b,a){return Ext.data.validations.emailRe.test(a)},format:function(a,b){if(b===undefined||b===null){b=""}return !!(a.matcher&&a.matcher.test(b))},inclusion:function(a,b){return a.list&&Ext.Array.indexOf(a.list,b)!=-1},exclusion:function(a,b){return a.list&&Ext.Array.indexOf(a.list,b)==-1}});Ext.define("Ext.data.identifier.Simple",{alias:"data.identifier.simple",statics:{AUTO_ID:1},config:{prefix:"ext-record-"},constructor:function(a){this.initConfig(a)},generate:function(a){return this._prefix+this.self.AUTO_ID++}});Ext.define("Ext.data.identifier.Uuid",{extend:"Ext.data.identifier.Simple",alias:"data.identifier.uuid",isUnique:true,config:{id:undefined,salt:null,timestamp:null,version:4},applyId:function(a){if(a===undefined){return Ext.data.identifier.Uuid.Global}return a},constructor:function(){var a=this;a.callParent(arguments);a.parts=[];a.init()},reconfigure:function(a){this.setConfig(a);this.init()},generate:function(){var c=this,e=c.parts,a=c.getVersion(),b=c.getSalt(),d=c.getTimestamp();e[0]=c.toHex(d.lo,8);e[1]=c.toHex(d.hi&65535,4);e[2]=c.toHex(((d.hi>>>16)&4095)|(a<<12),4);e[3]=c.toHex(128|((c.clockSeq>>>8)&63),2)+c.toHex(c.clockSeq&255,2);e[4]=c.toHex(b.hi,4)+c.toHex(b.lo,8);if(a==4){c.init()}else{++d.lo;if(d.lo>=c.twoPow32){d.lo=0;++d.hi}}return e.join("-").toLowerCase()},init:function(){var b=this,a=b.getSalt(),c=b.getTimestamp();if(b.getVersion()==4){b.clockSeq=b.rand(0,b.twoPow14-1);if(!a){a={};b.setSalt(a)}if(!c){c={};b.setTimestamp(c)}a.lo=b.rand(0,b.twoPow32-1);a.hi=b.rand(0,b.twoPow16-1);c.lo=b.rand(0,b.twoPow32-1);c.hi=b.rand(0,b.twoPow28-1)}else{b.setSalt(b.split(b.getSalt()));b.setTimestamp(b.split(b.getTimestamp()));b.getSalt().hi|=256}},twoPow14:Math.pow(2,14),twoPow16:Math.pow(2,16),twoPow28:Math.pow(2,28),twoPow32:Math.pow(2,32),toHex:function(c,b){var a=c.toString(16);if(a.length>b){a=a.substring(a.length-b)}else{if(a.length")}for(;c");for(j in k){if(k.hasOwnProperty(j)){d.push("<",j,">",k[j],"")}}d.push("")}if(h){d.push("")}a.setXmlData(d.join(""));return a}});Ext.define("Ext.direct.RemotingMethod",{config:{name:null,params:null,formHandler:null,len:null,ordered:true},constructor:function(a){this.initConfig(a)},applyParams:function(f){if(Ext.isNumber(f)){this.setLen(f)}else{if(Ext.isArray(f)){this.setOrdered(false);var d=f.length,b=[],c,e,a;for(c=0;c0){if(a){for(c=0,d=a.length;c0){k.apply(m,l)}if(a){k.call(m,e)}if(c.length>0){k.apply(m,c)}if(b){k.call(m,e)}if(o.length>0){k.apply(m,o)}}else{for(f=0;f0){k.apply(m,l)}}if(a){k.call(m,e)}for(f=0;f0){k.apply(m,c)}}if(b){k.call(m,e)}for(f=0;f0){k.apply(m,o)}}}if(m.length===0){return this}if(!h){h=[]}d.length=0;d.push.apply(d,h);d.push(null,this);this.doFire();return this},doFire:function(){var k=this.firingListeners,c=this.firingArguments,g=c.length-2,d,f,b,o,h,n,a,j,l,e,m;this.isPausing=false;this.isPaused=false;this.isStopped=false;this.isFiring=true;for(d=0,f=k.length;d0){this.isPaused=false;this.doFire()}if(a){a.resume()}return this},isInterrupted:function(){return this.isStopped||this.isPaused},stop:function(){var a=this.connectingController;this.isStopped=true;if(a){this.connectingController=null;a.stop()}this.isFiring=false;this.listenerStacks=null;return this},pause:function(){var a=this.connectingController;this.isPausing=true;if(a){a.pause()}return this}});Ext.define("Ext.event.Event",{alternateClassName:"Ext.EventObject",isStopped:false,set:function(a,b){if(arguments.length===1&&typeof a!="string"){var c=a;for(a in c){if(c.hasOwnProperty(a)){this[a]=c[a]}}}else{this[a]=c[a]}},stopEvent:function(){return this.stopPropagation()},stopPropagation:function(){this.isStopped=true;return this}});Ext.define("Ext.event.ListenerStack",{currentOrder:"current",length:0,constructor:function(){this.listeners={before:[],current:[],after:[]};this.lateBindingMap={};return this},add:function(h,j,k,e){var a=this.lateBindingMap,g=this.getAll(e),f=g.length,b,d,c;if(typeof h=="string"&&j.isIdentifiable){c=j.getId();b=a[c];if(b){if(b[h]){return false}else{b[h]=true}}else{a[c]=b={};b[h]=true}}else{if(f>0){while(f--){d=g[f];if(d.fn===h&&d.scope===j){d.options=k;return false}}}}d=this.create(h,j,k,e);if(k&&k.prepend){delete k.prepend;g.unshift(d)}else{g.push(d)}this.length++;return true},getAt:function(b,a){return this.getAll(a)[b]},getAll:function(a){if(!a){a=this.currentOrder}return this.listeners[a]},count:function(a){return this.getAll(a).length},create:function(d,c,b,a){return{stack:this,fn:d,firingFn:false,boundFn:false,isLateBinding:typeof d=="string",scope:c,options:b||{},order:a}},remove:function(h,j,e){var g=this.getAll(e),f=g.length,b=false,a=this.lateBindingMap,d,c;if(f>0){while(f--){d=g[f];if(d.fn===h&&d.scope===j){g.splice(f,1);b=true;this.length--;if(typeof h=="string"&&j.isIdentifiable){c=j.getId();if(a[c]&&a[c][h]){delete a[c][h]}}break}}}return b}});Ext.define("Ext.event.publisher.Publisher",{targetType:"",idSelectorRegex:/^#([\w\-]+)$/i,constructor:function(){var b=this.handledEvents,a,c,e,d;a=this.handledEventsMap={};for(c=0,e=b.length;cb){this.isEnded=true;return this.getEndValue()}else{return this.getStartValue()+((a/b)*this.distance)}}});Ext.define("Ext.fx.easing.Momentum",{extend:"Ext.fx.easing.Abstract",config:{acceleration:30,friction:0,startVelocity:0},alpha:0,updateFriction:function(b){var a=Math.log(1-(b/10));this.theta=a;this.alpha=a/this.getAcceleration()},updateStartVelocity:function(a){this.velocity=a*this.getAcceleration()},updateAcceleration:function(a){this.velocity=this.getStartVelocity()*a;this.alpha=this.theta/a},getValue:function(){return this.getStartValue()-this.velocity*(1-this.getFrictionFactor())/this.theta},getFrictionFactor:function(){var a=Ext.Date.now()-this.getStartTime();return Math.exp(a*this.alpha)},getVelocity:function(){return this.getFrictionFactor()*this.velocity}});Ext.define("Ext.mixin.Mixin",{onClassExtended:function(b,e){var a=e.mixinConfig,d,f,c;if(a){d=b.superclass.mixinConfig;if(d){a=e.mixinConfig=Ext.merge({},d,a)}e.mixinId=a.id;f=a.beforeHooks;c=a.hooks||a.afterHooks;if(f||c){Ext.Function.interceptBefore(e,"onClassMixedIn",function(h){var g=this.prototype;if(f){Ext.Object.each(f,function(j,i){h.override(i,function(){if(g[j].apply(this,arguments)!==false){return this.callOverridden(arguments)}})})}if(c){Ext.Object.each(c,function(j,i){h.override(i,function(){var k=this.callOverridden(arguments);g[j].apply(this,arguments);return k})})}})}}}});Ext.define("Ext.mixin.Selectable",{extend:"Ext.mixin.Mixin",mixinConfig:{id:"selectable",hooks:{updateStore:"updateStore"}},config:{disableSelection:null,mode:"SINGLE",allowDeselect:false,lastSelected:null,lastFocused:null,deselectOnContainerClick:true},modes:{SINGLE:true,SIMPLE:true,MULTI:true},selectableEventHooks:{addrecords:"onSelectionStoreAdd",removerecords:"onSelectionStoreRemove",updaterecord:"onSelectionStoreUpdate",load:"refreshSelection",refresh:"refreshSelection"},constructor:function(){this.selected=new Ext.util.MixedCollection();this.callParent(arguments)},applyMode:function(a){a=a?a.toUpperCase():"SINGLE";return this.modes[a]?a:"SINGLE"},updateStore:function(a,c){var b=this,d=Ext.apply({},b.selectableEventHooks,{scope:b});if(c&&Ext.isObject(c)&&c.isStore){if(c.autoDestroy){c.destroy()}else{c.un(d)}}if(a){a.on(d);b.refreshSelection()}},selectAll:function(a){var e=this,c=e.getStore().getRange(),d=c.length,b=0;for(;bg){e=g;g=c;c=e}for(d=c;d<=g;d++){a.push(b.getAt(d))}this.doMultiSelect(a,h)},select:function(c,e,b){var d=this,a;if(d.getDisableSelection()){return}if(typeof c==="number"){c=[d.getStore().getAt(c)]}if(!c){return}if(d.getMode()=="SINGLE"&&c){a=c.length?c[0]:c;d.doSingleSelect(a,b)}else{d.doMultiSelect(c,e,b)}},doSingleSelect:function(a,b){var d=this,c=d.selected;if(d.getDisableSelection()){return}if(d.isSelected(a)){return}if(c.getCount()>0){d.deselect(d.getLastSelected(),b)}c.add(a);d.setLastSelected(a);d.onItemSelect(a,b);d.setLastFocused(a);if(!b){d.fireSelectionChange([a])}},doMultiSelect:function(a,j,h){if(a===null||this.getDisableSelection()){return}a=!Ext.isArray(a)?[a]:a;var f=this,b=f.selected,e=a.length,g=false,c=0,d;if(!j&&b.getCount()>0){g=true;f.deselect(f.getSelection(),true)}for(;c0},refreshSelection:function(){var b=this,a=b.getSelection();b.deselectAll(true);if(a.length){b.select(a,false,true)}},onSelectionStoreRemove:function(c,b){var g=this,e=g.selected,f=b.length,a,d;if(g.getDisableSelection()){return}for(d=0;d0)?a[0]:b;return this.fromTouch(c)},fromTouch:function(a){return new this(a.pageX,a.pageY)},from:function(a){if(!a){return new this(0,0)}if(!(a instanceof this)){return new this(a.x,a.y)}return a}},constructor:function(a,b){if(typeof a=="undefined"){a=0}if(typeof b=="undefined"){b=0}this.x=a;this.y=b;return this},clone:function(){return new this.self(this.x,this.y)},copy:function(){return this.clone.apply(this,arguments)},copyFrom:function(a){this.x=a.x;this.y=a.y;return this},toString:function(){return"Point["+this.x+","+this.y+"]"},equals:function(a){return(this.x===a.x&&this.y===a.y)},isCloseTo:function(c,b){if(typeof b=="number"){b={x:b};b.y=b.x}var a=c.x,f=c.y,e=b.x,d=b.y;return(this.x<=a+e&&this.x>=a-e&&this.y<=f+d&&this.y>=f-d)},isWithin:function(){return this.isCloseTo.apply(this,arguments)},translate:function(a,b){this.x+=a;this.y+=b;return this},roundedEquals:function(a){return(Math.round(this.x)===Math.round(a.x)&&Math.round(this.y)===Math.round(a.y))},getDistanceTo:function(b){var c=this.x-b.x,a=this.y-b.y;return Math.sqrt(c*c+a*a)},getAngleTo:function(b){var c=this.x-b.x,a=this.y-b.y;return Math.atan2(a,c)*this.radianToDegreeConstant}});Ext.define("Ext.util.Region",{statics:{getRegion:function(a){return Ext.fly(a).getPageBox(true)},from:function(a){return new this(a.top,a.right,a.bottom,a.left)}},constructor:function(d,f,a,c){var e=this;e.top=d;e[1]=d;e.right=f;e.bottom=a;e.left=c;e[0]=c},contains:function(b){var a=this;return(b.left>=a.left&&b.right<=a.right&&b.top>=a.top&&b.bottom<=a.bottom)},intersect:function(g){var f=this,d=Math.max(f.top,g.top),e=Math.min(f.right,g.right),a=Math.min(f.bottom,g.bottom),c=Math.max(f.left,g.left);if(a>d&&e>c){return new Ext.util.Region(d,e,a,c)}else{return false}},union:function(g){var f=this,d=Math.min(f.top,g.top),e=Math.max(f.right,g.right),a=Math.max(f.bottom,g.bottom),c=Math.min(f.left,g.left);return new Ext.util.Region(d,e,a,c)},constrainTo:function(b){var a=this,c=Ext.util.Numbers.constrain;a.top=c(a.top,b.top,b.bottom);a.bottom=c(a.bottom,b.top,b.bottom);a.left=c(a.left,b.left,b.right);a.right=c(a.right,b.left,b.right);return a},adjust:function(d,f,a,c){var e=this;e.top+=d;e.left+=c;e.right+=f;e.bottom+=a;return e},getOutOfBoundOffset:function(a,b){if(!Ext.isObject(a)){if(a=="x"){return this.getOutOfBoundOffsetX(b)}else{return this.getOutOfBoundOffsetY(b)}}else{b=a;var c=new Ext.util.Offset();c.x=this.getOutOfBoundOffsetX(b.x);c.y=this.getOutOfBoundOffsetY(b.y);return c}},getOutOfBoundOffsetX:function(a){if(a<=this.left){return this.left-a}else{if(a>=this.right){return this.right-a}}return 0},getOutOfBoundOffsetY:function(a){if(a<=this.top){return this.top-a}else{if(a>=this.bottom){return this.bottom-a}}return 0},isOutOfBound:function(a,b){if(!Ext.isObject(a)){if(a=="x"){return this.isOutOfBoundX(b)}else{return this.isOutOfBoundY(b)}}else{b=a;return(this.isOutOfBoundX(b.x)||this.isOutOfBoundY(b.y))}},isOutOfBoundX:function(a){return(athis.right)},isOutOfBoundY:function(a){return(athis.bottom)},restrict:function(b,d,a){if(Ext.isObject(b)){var c;a=d;d=b;if(d.copy){c=d.copy()}else{c={x:d.x,y:d.y}}c.x=this.restrictX(d.x,a);c.y=this.restrictY(d.y,a);return c}else{if(b=="x"){return this.restrictX(d,a)}else{return this.restrictY(d,a)}}},restrictX:function(b,a){if(!a){a=1}if(b<=this.left){b-=(b-this.left)*a}else{if(b>=this.right){b-=(b-this.right)*a}}return b},restrictY:function(b,a){if(!a){a=1}if(b<=this.top){b-=(b-this.top)*a}else{if(b>=this.bottom){b-=(b-this.bottom)*a}}return b},getSize:function(){return{width:this.right-this.left,height:this.bottom-this.top}},copy:function(){return new Ext.util.Region(this.top,this.right,this.bottom,this.left)},toString:function(){return"Region["+this.top+","+this.right+","+this.bottom+","+this.left+"]"},translateBy:function(a){this.left+=a.x;this.right+=a.x;this.top+=a.y;this.bottom+=a.y;return this},round:function(){this.top=Math.round(this.top);this.right=Math.round(this.right);this.bottom=Math.round(this.bottom);this.left=Math.round(this.left);return this},equals:function(a){return(this.top==a.top&&this.right==a.right&&this.bottom==a.bottom&&this.left==a.left)}});Ext.define("Ext.util.Sorter",{isSorter:true,config:{property:null,sorterFn:null,root:null,transform:null,direction:"ASC",id:undefined},constructor:function(a){this.initConfig(a)},applyId:function(a){if(!a){a=this.getProperty();if(!a){a=Ext.id(null,"ext-sorter-")}}return a},createSortFunction:function(b){var c=this,a=c.getDirection().toUpperCase()=="DESC"?-1:1;return function(e,d){return a*b.call(c,e,d)}},defaultSortFn:function(e,c){var g=this,f=g._transform,b=g._root,d,a,h=g._property;if(b!==null){e=e[b];c=c[b]}d=e[h];a=c[h];if(f){d=f(d);a=f(a)}return d>a?1:(d -1 || Ext.isDate(values) ? values : ""'}else{if(e=="#"){c="xindex"}else{if(e.substr(0,7)=="parent."){c=e}else{if((e.indexOf(".")!==-1)&&(e.indexOf("-")===-1)){c="values."+e}else{c="values['"+e+"']"}}}}if(f){c="("+c+f+")"}if(g&&this.useFormat){d=d?","+d:"";if(g.substr(0,5)!="this."){g="fm."+g+"("}else{g+="("}}else{d="";g="("+c+" === undefined ? '' : "}return g+c+d+")"},evalTpl:function($){eval($);return $},newLineRe:/\r\n|\r|\n/g,aposRe:/[']/g,intRe:/^\s*(\d+)\s*$/,tagRe:/([\w-\.\#]+)(?:\:([\w\.]*)(?:\((.*?)?\))?)?(\s?[\+\-\*\/]\s?[\d\.\+\-\*\/\(\)]+)?/},function(){var a=this.prototype;a.fnArgs="out,values,parent,xindex,xcount";a.callFn=".call(this,"+a.fnArgs+")"});Ext.define("Ext.data.Field",{requires:["Ext.data.Types","Ext.data.SortTypes"],alias:"data.field",isField:true,config:{name:null,type:"auto",convert:undefined,dateFormat:null,allowNull:true,defaultValue:undefined,mapping:null,sortType:undefined,sortDir:"ASC",allowBlank:true,persist:true,encode:null,decode:null,bubbleEvents:"action"},constructor:function(a){if(Ext.isString(a)){a={name:a}}this.initConfig(a)},applyType:function(c){var b=Ext.data.Types,a=b.AUTO;if(c){if(Ext.isString(c)){return b[c.toUpperCase()]||a}else{return c}}return a},updateType:function(a,b){var c=this.getConvert();if(b&&c===b.convert){this.setConvert(a.convert)}},applySortType:function(d){var c=Ext.data.SortTypes,a=this.getType(),b=a.sortType;if(d){if(Ext.isString(d)){return c[d]||b}else{return d}}return b},applyConvert:function(b){var a=this.getType().convert;if(b&&b!==a){this._hasCustomConvert=true;return b}else{this._hasCustomConvert=false;return a}},hasCustomConvert:function(){return this._hasCustomConvert}});Ext.define("Ext.data.identifier.Sequential",{extend:"Ext.data.identifier.Simple",alias:"data.identifier.sequential",config:{prefix:"",seed:1},constructor:function(){var a=this;a.callParent(arguments);a.parts=[a.getPrefix(),""]},generate:function(b){var c=this,d=c.parts,a=c.getSeed()+1;c.setSeed(a);d[1]=a;return d.join("")}});Ext.define("Ext.data.writer.Json",{extend:"Ext.data.writer.Writer",alternateClassName:"Ext.data.JsonWriter",alias:"writer.json",config:{rootProperty:undefined,encode:false,allowSingle:true,encodeRequest:false},applyRootProperty:function(a){if(!a&&(this.getEncode()||this.getEncodeRequest())){a="data"}return a},writeRecords:function(d,e){var a=this.getRootProperty(),f=d.getParams(),b=this.getAllowSingle(),c;if(this.getAllowSingle()&&e&&e.length==1){e=e[0]}if(this.getEncodeRequest()){c=d.getJsonData()||{};if(e&&(e.length||(b&&Ext.isObject(e)))){c[a]=e}d.setJsonData(Ext.apply(c,f||{}));d.setParams(null);d.setMethod("POST");return d}if(!e||!(e.length||(b&&Ext.isObject(e)))){return d}if(this.getEncode()){if(a){f[a]=Ext.encode(e)}else{}}else{c=d.getJsonData()||{};if(a){c[a]=e}else{c=e}d.setJsonData(c)}return d}});Ext.define("Ext.event.Dispatcher",{requires:["Ext.event.ListenerStack","Ext.event.Controller"],statics:{getInstance:function(){if(!this.instance){this.instance=new this()}return this.instance},setInstance:function(a){this.instance=a;return this}},config:{publishers:{}},wildcard:"*",constructor:function(a){this.listenerStacks={};this.activePublishers={};this.publishersCache={};this.noActivePublishers=[];this.controller=null;this.initConfig(a);return this},getListenerStack:function(e,g,c,b){var d=this.listenerStacks,f=d[e],a;b=Boolean(b);if(!f){if(b){d[e]=f={}}else{return null}}f=f[g];if(!f){if(b){d[e][g]=f={}}else{return null}}a=f[c];if(!a){if(b){f[c]=a=new Ext.event.ListenerStack()}else{return null}}return a},getController:function(d,f,c,b){var a=this.controller,e={targetType:d,target:f,eventName:c};if(!a){this.controller=a=new Ext.event.Controller()}if(a.isFiring){a=new Ext.event.Controller()}a.setInfo(e);if(b&&a!==b){a.connect(b)}return a},applyPublishers:function(c){var a,b;this.publishersCache={};for(a in c){if(c.hasOwnProperty(a)){b=c[a];this.registerPublisher(b)}}return c},registerPublisher:function(b){var a=this.activePublishers,c=b.getTargetType(),d=a[c];if(!d){a[c]=d=[]}d.push(b);b.setDispatcher(this);return this},getCachedActivePublishers:function(c,b){var a=this.publishersCache,d;if((d=a[c])&&(d=d[b])){return d}return null},cacheActivePublishers:function(c,b,d){var a=this.publishersCache;if(!a[c]){a[c]={}}a[c][b]=d;return d},getActivePublishers:function(f,b){var g,a,c,e,d;if((g=this.getCachedActivePublishers(f,b))){return g}a=this.activePublishers[f];if(a){g=[];for(c=0,e=a.length;c0}return false},addListener:function(d,e,a){var f=this.getActivePublishers(d,a),c=f.length,b;if(c>0){for(b=0;b0){for(b=0;b0){for(b=0;b0)){return true}delete d[f];if(--d.$length===0){delete this.subscribers[a]}return true},onBeforeComponentRenderedChange:function(b,d,g){var f=this.eventNames,c=g?f.painted:f.erased,e=this.getSubscribers(c),a;if(e&&e.$length>0){this.renderedQueue[d.getId()]=a=[];this.publish(e,d,c,a)}},onBeforeComponentHiddenChange:function(c,d){var f=this.eventNames,b=d?f.erased:f.painted,e=this.getSubscribers(b),a;if(e&&e.$length>0){this.hiddenQueue[c.getId()]=a=[];this.publish(e,c,b,a)}},onComponentRenderedChange:function(b,c){var d=this.renderedQueue,e=c.getId(),a;if(!d.hasOwnProperty(e)){return}a=d[e];delete d[e];if(a.length>0){this.dispatchQueue(a)}},onComponentHiddenChange:function(c){var b=this.hiddenQueue,d=c.getId(),a;if(!b.hasOwnProperty(d)){return}a=b[d];delete b[d];if(a.length>0){this.dispatchQueue(a)}},dispatchQueue:function(g){var l=this.dispatcher,a=this.targetType,b=this.eventNames,e=g.slice(),f=e.length,c,k,h,d,j;g.length=0;if(f>0){for(c=0;c0)){return true}delete c[i];c.$length--}else{if(!d.hasOwnProperty(i)||(!j&&--d[i]>0)){return true}delete d[i];d.$length--}}else{if(g===this.SELECTOR_ALL){if(j){a.all=0}else{a.all--}}else{if(!b.hasOwnProperty(g)||(!j&&--b[g]>0)){return true}delete b[g];Ext.Array.remove(b,g)}}a.$length--;return true},getElementTarget:function(a){if(a.nodeType!==1){a=a.parentNode;if(!a||a.nodeType!==1){return null}}return a},getBubblingTargets:function(b){var a=[];if(!b){return a}do{a[a.length]=b;b=b.parentNode}while(b&&b.nodeType===1);return a},dispatch:function(c,a,b){b.push(b[0].target);this.callParent(arguments)},publish:function(b,a,c){var d=this.getSubscribers(b),e;if(d.$length===0||!this.doPublish(d,b,a,c)){e=this.getSubscribers("*");if(e.$length>0){this.doPublish(e,b,a,c)}}return this},doPublish:function(f,h,x,u){var r=f.id,g=f.className,b=f.selector,p=r.$length>0,a=g.$length>0,l=b.length>0,o=f.all>0,y={},e=[u],q=false,m=this.classNameSplitRegex,v,k,t,d,z,n,c,w,s;for(v=0,k=x.length;v0){c=a.slice(0);a.length=0;for(b=0;b0){this.processEvent(this.mergeEvents(d));d.length=0}this.processEvent(e)}}if(d.length>0){this.processEvent(this.mergeEvents(d));d.length=0}}},mergeEvents:function(c){var b=[],f=c.length,a,e,d;d=c[f-1];if(f===1){return d}for(a=0;ah){for(d=0;dh){return}}for(d=0;da){this.end(d)}}},onTouchEnd:function(a){this.end(a)},start:function(){if(!this.isTracking){this.isTracking=true;this.isStarted=false}},end:function(a){if(this.isTracking){this.isTracking=false;if(this.isStarted){this.isStarted=false;this.fireEnd(a)}}}});Ext.define("Ext.event.recognizer.Pinch",{extend:"Ext.event.recognizer.MultiTouch",requiredTouchesCount:2,handledEvents:["pinchstart","pinch","pinchend"],startDistance:0,lastTouches:null,onTouchMove:function(c){if(!this.isTracking){return}var b=Array.prototype.slice.call(c.touches),d,a,f;d=b[0].point;a=b[1].point;f=d.getDistanceTo(a);if(f===0){return}if(!this.isStarted){this.isStarted=true;this.startDistance=f;this.fire("pinchstart",c,b,{touches:b,distance:f,scale:1})}else{this.fire("pinch",c,b,{touches:b,distance:f,scale:f/this.startDistance})}this.lastTouches=b},fireEnd:function(a){this.fire("pinchend",a,this.lastTouches)},fail:function(){return this.callParent(arguments)}});Ext.define("Ext.event.recognizer.Rotate",{extend:"Ext.event.recognizer.MultiTouch",requiredTouchesCount:2,handledEvents:["rotatestart","rotate","rotateend"],startAngle:0,lastTouches:null,lastAngle:null,onTouchMove:function(h){if(!this.isTracking){return}var g=Array.prototype.slice.call(h.touches),b=this.lastAngle,d,f,c,a,i,j;d=g[0].point;f=g[1].point;c=d.getAngleTo(f);if(b!==null){j=Math.abs(b-c);a=c+360;i=c-360;if(Math.abs(a-b)1){return this.fail(this.self.NOT_SINGLE_TOUCH)}}});Ext.define("Ext.event.recognizer.DoubleTap",{extend:"Ext.event.recognizer.SingleTouch",config:{maxDuration:300},handledEvents:["singletap","doubletap"],singleTapTimer:null,onTouchStart:function(a){if(this.callParent(arguments)===false){return false}this.startTime=a.time;clearTimeout(this.singleTapTimer)},onTouchMove:function(){return this.fail(this.self.TOUCH_MOVED)},onEnd:function(g){var c=this,b=this.getMaxDuration(),h=g.changedTouches[0],f=g.time,a=this.lastTapTime,d;this.lastTapTime=f;if(a){d=f-a;if(d<=b){this.lastTapTime=0;this.fire("doubletap",g,[h],{touch:h,duration:d});return}}if(f-this.startTime>b){this.fireSingleTap(g,h)}else{this.singleTapTimer=setTimeout(function(){c.fireSingleTap(g,h)},b)}},fireSingleTap:function(a,b){this.fire("singletap",a,[b],{touch:b})}});Ext.define("Ext.event.recognizer.Drag",{extend:"Ext.event.recognizer.SingleTouch",isStarted:false,startPoint:null,previousPoint:null,lastPoint:null,handledEvents:["dragstart","drag","dragend"],onTouchStart:function(b){var c,a;if(this.callParent(arguments)===false){if(this.isStarted&&this.lastMoveEvent!==null){this.onTouchEnd(this.lastMoveEvent)}return false}this.startTouches=c=b.changedTouches;this.startTouch=a=c[0];this.startPoint=a.point},onTouchMove:function(d){var c=d.changedTouches,f=c[0],a=f.point,b=d.time;if(this.lastPoint){this.previousPoint=this.lastPoint}if(this.lastTime){this.previousTime=this.lastTime}this.lastTime=b;this.lastPoint=a;this.lastMoveEvent=d;if(!this.isStarted){this.isStarted=true;this.startTime=b;this.previousTime=b;this.previousPoint=this.startPoint;this.fire("dragstart",d,this.startTouches,this.getInfo(d,this.startTouch))}else{this.fire("drag",d,c,this.getInfo(d,f))}},onTouchEnd:function(c){if(this.isStarted){var b=c.changedTouches,d=b[0],a=d.point;this.isStarted=false;this.lastPoint=a;this.fire("dragend",c,b,this.getInfo(c,d));this.startTime=0;this.previousTime=0;this.lastTime=0;this.startPoint=null;this.previousPoint=null;this.lastPoint=null;this.lastMoveEvent=null}},getInfo:function(j,i){var d=j.time,a=this.startPoint,f=this.previousPoint,b=this.startTime,k=this.previousTime,l=this.lastPoint,h=l.x-a.x,g=l.y-a.y,c={touch:i,startX:a.x,startY:a.y,previousX:f.x,previousY:f.y,pageX:l.x,pageY:l.y,deltaX:h,deltaY:g,absDeltaX:Math.abs(h),absDeltaY:Math.abs(g),previousDeltaX:l.x-f.x,previousDeltaY:l.y-f.y,time:d,startTime:b,previousTime:k,deltaTime:d-b,previousDeltaTime:d-k};return c}});Ext.define("Ext.event.recognizer.LongPress",{extend:"Ext.event.recognizer.SingleTouch",inheritableStatics:{DURATION_NOT_ENOUGH:32},config:{minDuration:1000},handledEvents:["longpress"],fireLongPress:function(a){var b=a.changedTouches[0];this.fire("longpress",a,[b],{touch:b,duration:this.getMinDuration()});this.isLongPress=true},onTouchStart:function(b){var a=this;if(this.callParent(arguments)===false){return false}this.isLongPress=false;this.timer=setTimeout(function(){a.fireLongPress(b)},this.getMinDuration())},onTouchMove:function(){return this.fail(this.self.TOUCH_MOVED)},onTouchEnd:function(){if(!this.isLongPress){return this.fail(this.self.DURATION_NOT_ENOUGH)}},fail:function(){clearTimeout(this.timer);return this.callParent(arguments)}},function(){this.override({handledEvents:["longpress","taphold"],fire:function(a){if(a==="longpress"){var b=Array.prototype.slice.call(arguments);b[0]="taphold";this.fire.apply(this,b)}return this.callOverridden(arguments)}})});Ext.define("Ext.event.recognizer.Tap",{handledEvents:["tap"],extend:"Ext.event.recognizer.SingleTouch",onTouchMove:function(){return this.fail(this.self.TOUCH_MOVED)},onTouchEnd:function(a){var b=a.changedTouches[0];this.fire("tap",a,[b])}},function(){});(function(){function b(d){var c=Array.prototype.slice.call(arguments,1);return d.replace(/\{(\d+)\}/g,function(e,f){return c[f]})}Ext.DateExtras={now:Date.now||function(){return +new Date()},getElapsed:function(d,c){return Math.abs(d-(c||new Date()))},useStrict:false,formatCodeToRegex:function(d,c){var e=a.parseCodes[d];if(e){e=typeof e=="function"?e():e;a.parseCodes[d]=e}return e?Ext.applyIf({c:e.c?b(e.c,c||"{0}"):e.c},e):{g:0,c:null,s:Ext.String.escapeRegex(d)}},parseFunctions:{MS:function(d,c){var e=new RegExp("\\/Date\\(([-+])?(\\d+)(?:[+-]\\d{4})?\\)\\/");var f=(d||"").match(e);return f?new Date(((f[1]||"")+f[2])*1):null}},parseRegexes:[],formatFunctions:{MS:function(){return"\\/Date("+this.getTime()+")\\/"}},y2kYear:50,MILLI:"ms",SECOND:"s",MINUTE:"mi",HOUR:"h",DAY:"d",MONTH:"mo",YEAR:"y",defaults:{},dayNames:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],monthNames:["January","February","March","April","May","June","July","August","September","October","November","December"],monthNumbers:{Jan:0,Feb:1,Mar:2,Apr:3,May:4,Jun:5,Jul:6,Aug:7,Sep:8,Oct:9,Nov:10,Dec:11},defaultFormat:"m/d/Y",getShortMonthName:function(c){return a.monthNames[c].substring(0,3)},getShortDayName:function(c){return a.dayNames[c].substring(0,3)},getMonthNumber:function(c){return a.monthNumbers[c.substring(0,1).toUpperCase()+c.substring(1,3).toLowerCase()]},formatCodes:{d:"Ext.String.leftPad(this.getDate(), 2, '0')",D:"Ext.Date.getShortDayName(this.getDay())",j:"this.getDate()",l:"Ext.Date.dayNames[this.getDay()]",N:"(this.getDay() ? this.getDay() : 7)",S:"Ext.Date.getSuffix(this)",w:"this.getDay()",z:"Ext.Date.getDayOfYear(this)",W:"Ext.String.leftPad(Ext.Date.getWeekOfYear(this), 2, '0')",F:"Ext.Date.monthNames[this.getMonth()]",m:"Ext.String.leftPad(this.getMonth() + 1, 2, '0')",M:"Ext.Date.getShortMonthName(this.getMonth())",n:"(this.getMonth() + 1)",t:"Ext.Date.getDaysInMonth(this)",L:"(Ext.Date.isLeapYear(this) ? 1 : 0)",o:"(this.getFullYear() + (Ext.Date.getWeekOfYear(this) == 1 && this.getMonth() > 0 ? +1 : (Ext.Date.getWeekOfYear(this) >= 52 && this.getMonth() < 11 ? -1 : 0)))",Y:"Ext.String.leftPad(this.getFullYear(), 4, '0')",y:"('' + this.getFullYear()).substring(2, 4)",a:"(this.getHours() < 12 ? 'am' : 'pm')",A:"(this.getHours() < 12 ? 'AM' : 'PM')",g:"((this.getHours() % 12) ? this.getHours() % 12 : 12)",G:"this.getHours()",h:"Ext.String.leftPad((this.getHours() % 12) ? this.getHours() % 12 : 12, 2, '0')",H:"Ext.String.leftPad(this.getHours(), 2, '0')",i:"Ext.String.leftPad(this.getMinutes(), 2, '0')",s:"Ext.String.leftPad(this.getSeconds(), 2, '0')",u:"Ext.String.leftPad(this.getMilliseconds(), 3, '0')",O:"Ext.Date.getGMTOffset(this)",P:"Ext.Date.getGMTOffset(this, true)",T:"Ext.Date.getTimezone(this)",Z:"(this.getTimezoneOffset() * -60)",c:function(){for(var j="Y-m-dTH:i:sP",g=[],f=0,d=j.length;f= 0 && y >= 0){","v = Ext.Date.add(new Date(y < 100 ? 100 : y, 0, 1, h, i, s, ms), Ext.Date.YEAR, y < 100 ? y - 100 : 0);","v = !strict? v : (strict === true && (z <= 364 || (Ext.Date.isLeapYear(v) && z <= 365))? Ext.Date.add(v, Ext.Date.DAY, z) : null);","}else if(strict === true && !Ext.Date.isValid(y, m + 1, d, h, i, s, ms)){","v = null;","}else{","v = Ext.Date.add(new Date(y < 100 ? 100 : y, m, d, h, i, s, ms), Ext.Date.YEAR, y < 100 ? y - 100 : 0);","}","}","}","if(v){","if(zz != null){","v = Ext.Date.add(v, Ext.Date.SECOND, -v.getTimezoneOffset() * 60 - zz);","}else if(o){","v = Ext.Date.add(v, Ext.Date.MINUTE, -v.getTimezoneOffset() + (sn == '+'? -1 : 1) * (hr * 60 + mn));","}","}","return v;"].join("\n");return function(l){var e=a.parseRegexes.length,m=1,f=[],k=[],j=false,d="";for(var h=0;h Ext.Date.y2kYear ? 1900 + ty : 2000 + ty;\n",s:"(\\d{1,2})"},a:{g:1,c:"if (/(am)/i.test(results[{0}])) {\nif (!h || h == 12) { h = 0; }\n} else { if (!h || h < 12) { h = (h || 0) + 12; }}",s:"(am|pm|AM|PM)"},A:{g:1,c:"if (/(am)/i.test(results[{0}])) {\nif (!h || h == 12) { h = 0; }\n} else { if (!h || h < 12) { h = (h || 0) + 12; }}",s:"(AM|PM|am|pm)"},g:function(){return a.formatCodeToRegex("G")},G:{g:1,c:"h = parseInt(results[{0}], 10);\n",s:"(\\d{1,2})"},h:function(){return a.formatCodeToRegex("H")},H:{g:1,c:"h = parseInt(results[{0}], 10);\n",s:"(\\d{2})"},i:{g:1,c:"i = parseInt(results[{0}], 10);\n",s:"(\\d{2})"},s:{g:1,c:"s = parseInt(results[{0}], 10);\n",s:"(\\d{2})"},u:{g:1,c:"ms = results[{0}]; ms = parseInt(ms, 10)/Math.pow(10, ms.length - 3);\n",s:"(\\d+)"},O:{g:1,c:["o = results[{0}];","var sn = o.substring(0,1),","hr = o.substring(1,3)*1 + Math.floor(o.substring(3,5) / 60),","mn = o.substring(3,5) % 60;","o = ((-12 <= (hr*60 + mn)/60) && ((hr*60 + mn)/60 <= 14))? (sn + Ext.String.leftPad(hr, 2, '0') + Ext.String.leftPad(mn, 2, '0')) : null;\n"].join("\n"),s:"([+-]\\d{4})"},P:{g:1,c:["o = results[{0}];","var sn = o.substring(0,1),","hr = o.substring(1,3)*1 + Math.floor(o.substring(4,6) / 60),","mn = o.substring(4,6) % 60;","o = ((-12 <= (hr*60 + mn)/60) && ((hr*60 + mn)/60 <= 14))? (sn + Ext.String.leftPad(hr, 2, '0') + Ext.String.leftPad(mn, 2, '0')) : null;\n"].join("\n"),s:"([+-]\\d{2}:\\d{2})"},T:{g:0,c:null,s:"[A-Z]{1,4}"},Z:{g:1,c:"zz = results[{0}] * 1;\nzz = (-43200 <= zz && zz <= 50400)? zz : null;\n",s:"([+-]?\\d{1,5})"},c:function(){var e=[],c=[a.formatCodeToRegex("Y",1),a.formatCodeToRegex("m",2),a.formatCodeToRegex("d",3),a.formatCodeToRegex("h",4),a.formatCodeToRegex("i",5),a.formatCodeToRegex("s",6),{c:"ms = results[7] || '0'; ms = parseInt(ms, 10)/Math.pow(10, ms.length - 3);\n"},{c:["if(results[8]) {","if(results[8] == 'Z'){","zz = 0;","}else if (results[8].indexOf(':') > -1){",a.formatCodeToRegex("P",8).c,"}else{",a.formatCodeToRegex("O",8).c,"}","}"].join("\n")}];for(var f=0,d=c.length;f0?"-":"+")+Ext.String.leftPad(Math.floor(Math.abs(e)/60),2,"0")+(d?":":"")+Ext.String.leftPad(Math.abs(e%60),2,"0")},getDayOfYear:function(f){var e=0,h=Ext.Date.clone(f),c=f.getMonth(),g;for(g=0,h.setDate(1),h.setMonth(0);g28){e=Math.min(e,Ext.Date.getLastDateOfMonth(Ext.Date.add(Ext.Date.getFirstDateOfMonth(g),"mo",h)).getDate())}i.setDate(e);i.setMonth(g.getMonth()+h);break;case Ext.Date.YEAR:i.setFullYear(g.getFullYear()+h);break}return i},between:function(d,f,c){var e=d.getTime();return f.getTime()<=e&&e<=c.getTime()}};var a=Ext.DateExtras;Ext.apply(Ext.Date,a)})();Ext.define("Ext.fx.Easing",{requires:["Ext.fx.easing.Linear"],constructor:function(a){return Ext.factory(a,Ext.fx.easing.Linear,null,"easing")}});Ext.define("Ext.fx.easing.BoundMomentum",{extend:"Ext.fx.easing.Abstract",requires:["Ext.fx.easing.Momentum","Ext.fx.easing.Bounce"],config:{momentum:null,bounce:null,minMomentumValue:0,maxMomentumValue:0,minVelocity:0.01,startVelocity:0},applyMomentum:function(a,b){return Ext.factory(a,Ext.fx.easing.Momentum,b)},applyBounce:function(a,b){return Ext.factory(a,Ext.fx.easing.Bounce,b)},updateStartTime:function(a){this.getMomentum().setStartTime(a);this.callParent(arguments)},updateStartVelocity:function(a){this.getMomentum().setStartVelocity(a)},updateStartValue:function(a){this.getMomentum().setStartValue(a)},reset:function(){this.lastValue=null;this.isBouncingBack=false;this.isOutOfBound=false;return this.callParent(arguments)},getValue:function(){var a=this.getMomentum(),j=this.getBounce(),e=a.getStartVelocity(),f=e>0?1:-1,g=this.getMinMomentumValue(),d=this.getMaxMomentumValue(),c=(f==1)?d:g,h=this.lastValue,i,b;if(e===0){return this.getStartValue()}if(!this.isOutOfBound){i=a.getValue();b=a.getVelocity();if(Math.abs(b)=g&&i<=d){return i}this.isOutOfBound=true;j.setStartTime(Ext.Date.now()).setStartVelocity(b).setStartValue(c)}i=j.getValue();if(!this.isEnded){if(!this.isBouncingBack){if(h!==null){if((f==1&&ih)){this.isBouncingBack=true}}}else{if(Math.round(i)==c){this.isEnded=true}}}this.lastValue=i;return i}});Ext.define("Ext.fx.easing.EaseIn",{extend:"Ext.fx.easing.Linear",alias:"easing.ease-in",config:{exponent:4,duration:1500},getValue:function(){var c=Ext.Date.now()-this.getStartTime(),g=this.getDuration(),b=this.getStartValue(),a=this.getEndValue(),h=this.distance,e=c/g,d=Math.pow(e,this.getExponent()),f=b+(d*h);if(c>=g){this.isEnded=true;return a}return f}});Ext.define("Ext.fx.easing.EaseOut",{extend:"Ext.fx.easing.Linear",alias:"easing.ease-out",config:{exponent:4,duration:1500},getValue:function(){var f=Ext.Date.now()-this.getStartTime(),d=this.getDuration(),b=this.getStartValue(),h=this.getEndValue(),a=this.distance,c=f/d,g=1-c,e=1-Math.pow(g,this.getExponent()),i=b+(e*a);if(f>=d){this.isEnded=true;return h}return i}});Ext.define("Ext.mixin.Filterable",{extend:"Ext.mixin.Mixin",requires:["Ext.util.Filter"],mixinConfig:{id:"filterable"},config:{filters:null,filterRoot:null},dirtyFilterFn:false,filterFn:null,filtered:false,applyFilters:function(a,b){if(!b){b=this.createFiltersCollection()}b.clear();this.filtered=false;this.dirtyFilterFn=true;if(a){this.addFilters(a)}return b},createFiltersCollection:function(){this._filters=Ext.create("Ext.util.Collection",function(a){return a.getId()});return this._filters},addFilter:function(a){this.addFilters([a])},addFilters:function(b){var a=this.getFilters();return this.insertFilters(a?a.length:0,b)},insertFilter:function(a,b){return this.insertFilters(a,[b])},insertFilters:function(h,c){if(!Ext.isArray(c)){c=[c]}var j=c.length,a=this.getFilterRoot(),d=this.getFilters(),e=[],f,g,b;if(!d){d=this.createFiltersCollection()}for(g=0;g=200&&a<300)||a==304||(a==0&&d.responseText.length>0),b=false;if(!c){switch(a){case 12002:case 12029:case 12030:case 12031:case 12152:case 13030:b=true;break}}return{success:c,isException:b}},createResponse:function(c){var g=c.xhr,a={},h,d,i,e,f,b;if(c.timedout||c.aborted){c.success=false;h=[]}else{h=g.getAllResponseHeaders().replace(this.lineBreakRe,"\n").split("\n")}d=h.length;while(d--){i=h[d];e=i.indexOf(":");if(e>=0){f=i.substr(0,e).toLowerCase();if(i.charAt(e+1)==" "){++e}a[f]=i.substr(e+1)}}c.xhr=null;delete c.xhr;b={request:c,requestId:c.id,status:g.status,statusText:g.statusText,getResponseHeader:function(j){return a[j.toLowerCase()]},getAllResponseHeaders:function(){return a},responseText:g.responseText,responseXML:g.responseXML};g=null;return b},createException:function(a){return{request:a,requestId:a.id,status:a.aborted?-1:0,statusText:a.aborted?"transaction aborted":"communication failure",aborted:a.aborted,timedout:a.timedout}}});Ext.define("Ext.Ajax",{extend:"Ext.data.Connection",singleton:true,autoAbort:false});Ext.define("Ext.data.reader.Reader",{requires:["Ext.data.ResultSet"],alternateClassName:["Ext.data.Reader","Ext.data.DataReader"],mixins:["Ext.mixin.Observable"],isReader:true,config:{idProperty:undefined,clientIdProperty:"clientId",totalProperty:"total",successProperty:"success",messageProperty:null,rootProperty:"",implicitIncludes:true,model:undefined},constructor:function(a){this.initConfig(a)},fieldCount:0,applyModel:function(a){if(typeof a=="string"){a=Ext.data.ModelManager.getModel(a);if(!a){Ext.Logger.error("Model with name "+arguments[0]+" doesnt exist.")}}if(a&&!a.prototype.isModel&&Ext.isObject(a)){a=Ext.data.ModelManager.registerType(a.storeId||a.id||Ext.id(),a)}return a},applyIdProperty:function(a){if(!a&&this.getModel()){a=this.getModel().getIdProperty()}return a},updateModel:function(a){if(a){if(!this.getIdProperty()){this.setIdProperty(a.getIdProperty())}this.buildExtractors()}},createAccessor:Ext.emptyFn,createFieldAccessExpression:function(){return"undefined"},buildExtractors:function(){if(!this.getModel()){return}var b=this,c=b.getTotalProperty(),a=b.getSuccessProperty(),d=b.getMessageProperty();if(c){b.getTotal=b.createAccessor(c)}if(a){b.getSuccess=b.createAccessor(a)}if(d){b.getMessage=b.createAccessor(d)}b.extractRecordData=b.buildRecordDataExtractor()},buildRecordDataExtractor:function(){var k=this,e=k.getModel(),g=e.getFields(),j=g.length,a=[],h=k.getModel().getClientIdProperty(),f="__field",b=["var me = this,\n"," fields = me.getModel().getFields(),\n"," idProperty = me.getIdProperty(),\n",' idPropertyIsFn = (typeof idProperty == "function"),'," value,\n"," internalId"],d,l,c,m;g=g.items;for(d=0;d=0){return Ext.functionFactory("obj","var value; try {value = obj"+(b>0?".":"")+c+"} catch(e) {}; return value;")}}return function(d){return d[c]}}}(),createFieldAccessExpression:function(g,b,c){var f=this,h=f.objectRe,e=(g.getMapping()!==null),a=e?g.getMapping():g.getName(),i,d;if(typeof a==="function"){i=b+".getMapping()("+c+", this)"}else{if(f.getUseSimpleAccessors()===true||((d=String(a).search(h))<0)){if(!e||isNaN(a)){a='"'+a+'"'}i=c+"["+a+"]"}else{i=c+(d>0?".":"")+a}}return i}});Ext.define("Ext.data.proxy.Proxy",{extend:"Ext.Evented",alias:"proxy.proxy",alternateClassName:["Ext.data.DataProxy","Ext.data.Proxy"],requires:["Ext.data.reader.Json","Ext.data.writer.Json","Ext.data.Batch","Ext.data.Operation"],config:{batchOrder:"create,update,destroy",batchActions:true,model:null,reader:{type:"json"},writer:{type:"json"}},isProxy:true,applyModel:function(a){if(typeof a=="string"){a=Ext.data.ModelManager.getModel(a);if(!a){Ext.Logger.error("Model with name "+arguments[0]+" doesnt exist.")}}if(a&&!a.prototype.isModel&&Ext.isObject(a)){a=Ext.data.ModelManager.registerType(a.storeId||a.id||Ext.id(),a)}return a},updateModel:function(b){if(b){var a=this.getReader();if(a&&!a.getModel()){a.setModel(b)}}},applyReader:function(b,a){return Ext.factory(b,Ext.data.Reader,a,"reader")},updateReader:function(a){if(a){var b=this.getModel();if(!b){b=a.getModel();if(b){this.setModel(b)}}else{a.setModel(b)}if(a.onMetaChange){a.onMetaChange=Ext.Function.createSequence(a.onMetaChange,this.onMetaChange,this)}}},onMetaChange:function(b){var a=this.getReader().getModel();if(!this.getModel()&&a){this.setModel(a)}this.fireEvent("metachange",this,b)},applyWriter:function(b,a){return Ext.factory(b,Ext.data.Writer,a,"writer")},create:Ext.emptyFn,read:Ext.emptyFn,update:Ext.emptyFn,destroy:Ext.emptyFn,onDestroy:function(){Ext.destroy(this.getReader(),this.getWriter())},batch:function(e,f){var g=this,d=g.getBatchActions(),c=this.getModel(),b,a;if(e.operations===undefined){e={operations:e,batch:{listeners:f}}}if(e.batch){if(e.batch.isBatch){e.batch.setProxy(g)}else{e.batch.proxy=g}}else{e.batch={proxy:g,listeners:e.listeners||{}}}if(!b){b=new Ext.data.Batch(e.batch)}b.on("complete",Ext.bind(g.onBatchComplete,g,[e],0));Ext.each(g.getBatchOrder().split(","),function(h){a=e.operations[h];if(a){if(d){b.add(new Ext.data.Operation({action:h,records:a,model:c}))}else{Ext.each(a,function(i){b.add(new Ext.data.Operation({action:h,records:[i],model:c}))})}}},g);b.start();return b},onBatchComplete:function(a,b){var c=a.scope||this;if(b.hasException){if(Ext.isFunction(a.failure)){Ext.callback(a.failure,c,[b,a])}}else{if(Ext.isFunction(a.success)){Ext.callback(a.success,c,[b,a])}}if(Ext.isFunction(a.callback)){Ext.callback(a.callback,c,[b,a])}}},function(){});Ext.define("Ext.data.proxy.Client",{extend:"Ext.data.proxy.Proxy",alternateClassName:"Ext.proxy.ClientProxy",clear:function(){}});Ext.define("Ext.data.proxy.Memory",{extend:"Ext.data.proxy.Client",alias:"proxy.memory",alternateClassName:"Ext.data.MemoryProxy",isMemoryProxy:true,config:{data:[]},finishOperation:function(b,f,d){if(b){var c=0,e=b.getRecords(),a=e.length;for(c;c0){if(o){h[e]=m[0].getProperty();h[b]=m[0].getDirection()}else{h[e]=n.encodeSorters(m)}}if(c&&f&&f.length>0){h[c]=n.encodeFilters(f)}return h},buildUrl:function(c){var b=this,a=b.getUrl(c);if(b.getNoCache()){a=Ext.urlAppend(a,Ext.String.format("{0}={1}",b.getCacheString(),Ext.Date.now()))}return a},getUrl:function(a){return a?a.getUrl()||this.getApi()[a.getAction()]||this._url:this._url},doRequest:function(a,c,b){},afterRequest:Ext.emptyFn});Ext.define("Ext.data.proxy.JsonP",{extend:"Ext.data.proxy.Server",alternateClassName:"Ext.data.ScriptTagProxy",alias:["proxy.jsonp","proxy.scripttag"],requires:["Ext.data.JsonP"],config:{defaultWriterType:"base",callbackKey:"callback",recordParam:"records",autoAppendParams:true},doRequest:function(a,f,b){var d=this,c=d.buildRequest(a),e=c.getParams();c.setConfig({callbackKey:d.getCallbackKey(),timeout:d.getTimeout(),scope:d,callback:d.createRequestCallback(c,a,f,b)});if(d.getAutoAppendParams()){c.setParams({})}c.setJsonP(Ext.data.JsonP.request(c.getCurrentConfig()));c.setParams(e);a.setStarted();d.lastRequest=c;return c},createRequestCallback:function(d,a,e,b){var c=this;return function(h,f,g){delete c.lastRequest;c.processResponse(h,a,d,f,e,b)}},setException:function(b,a){b.setException(b.getRequest().getJsonP().errorType)},buildUrl:function(f){var h=this,a=h.callParent(arguments),e=Ext.apply({},f.getParams()),c=e.filters,d,b,g,j;delete e.filters;if(h.getAutoAppendParams()){a=Ext.urlAppend(a,Ext.Object.toQueryString(e))}if(c&&c.length){for(g=0;g1){this.endAnimationCounter=0;this.fireEvent("animationend",this)}},applyInAnimation:function(b,a){return Ext.factory(b,Ext.fx.Animation,a)},applyOutAnimation:function(b,a){return Ext.factory(b,Ext.fx.Animation,a)},updateInAnimation:function(a){a.setScope(this)},updateOutAnimation:function(a){a.setScope(this)},onActiveItemChange:function(a,e,h,i,d){var b=this.getInAnimation(),g=this.getOutAnimation(),f,c;if(e&&h&&h.isPainted()){f=e.renderElement;c=h.renderElement;b.setElement(f);g.setElement(c);g.setOnBeforeEnd(function(j,k){if(k||Ext.Animator.hasRunningAnimations(j)){d.firingArguments[1]=null;d.firingArguments[2]=null}});g.setOnEnd(function(){d.resume()});f.dom.style.setProperty("visibility","hidden","!important");e.show();Ext.Animator.run([g,b]);d.pause()}}});Ext.define("Ext.fx.layout.card.Cover",{extend:"Ext.fx.layout.card.Style",alias:"fx.layout.card.cover",config:{reverse:null,inAnimation:{before:{"z-index":100},after:{"z-index":0},type:"slide",easing:"ease-out"},outAnimation:{easing:"ease-out",from:{opacity:0.99},to:{opacity:1},out:true}},updateReverse:function(a){this.getInAnimation().setReverse(a);this.getOutAnimation().setReverse(a)}});Ext.define("Ext.fx.layout.card.Cube",{extend:"Ext.fx.layout.card.Style",alias:"fx.layout.card.cube",config:{reverse:null,inAnimation:{type:"cube"},outAnimation:{type:"cube",out:true}}});Ext.define("Ext.fx.layout.card.Fade",{extend:"Ext.fx.layout.card.Style",alias:"fx.layout.card.fade",config:{reverse:null,inAnimation:{type:"fade",easing:"ease-out"},outAnimation:{type:"fade",easing:"ease-out",out:true}}});Ext.define("Ext.fx.layout.card.Flip",{extend:"Ext.fx.layout.card.Style",alias:"fx.layout.card.flip",config:{duration:500,inAnimation:{type:"flip",half:true,easing:"ease-out",before:{"backface-visibility":"hidden"},after:{"backface-visibility":null}},outAnimation:{type:"flip",half:true,easing:"ease-in",before:{"backface-visibility":"hidden"},after:{"backface-visibility":null},out:true}},updateDuration:function(d){var c=d/2,b=this.getInAnimation(),a=this.getOutAnimation();b.setDelay(c);b.setDuration(c);a.setDuration(c)}});Ext.define("Ext.fx.layout.card.Pop",{extend:"Ext.fx.layout.card.Style",alias:"fx.layout.card.pop",config:{duration:500,inAnimation:{type:"pop",easing:"ease-out"},outAnimation:{type:"pop",easing:"ease-in",out:true}},updateDuration:function(d){var c=d/2,b=this.getInAnimation(),a=this.getOutAnimation();b.setDelay(c);b.setDuration(c);a.setDuration(c)}});Ext.define("Ext.fx.layout.card.Reveal",{extend:"Ext.fx.layout.card.Style",alias:"fx.layout.card.reveal",config:{inAnimation:{easing:"ease-out",from:{opacity:0.99},to:{opacity:1}},outAnimation:{before:{"z-index":100},after:{"z-index":0},type:"slide",easing:"ease-out",out:true}},updateReverse:function(a){this.getInAnimation().setReverse(a);this.getOutAnimation().setReverse(a)}});Ext.define("Ext.fx.layout.card.Slide",{extend:"Ext.fx.layout.card.Style",alias:"fx.layout.card.slide",config:{inAnimation:{type:"slide",easing:"ease-out"},outAnimation:{type:"slide",easing:"ease-out",out:true}},updateReverse:function(a){this.getInAnimation().setReverse(a);this.getOutAnimation().setReverse(a)}});Ext.define("Ext.fx.layout.Card",{requires:["Ext.fx.layout.card.Slide","Ext.fx.layout.card.Cover","Ext.fx.layout.card.Reveal","Ext.fx.layout.card.Fade","Ext.fx.layout.card.Flip","Ext.fx.layout.card.Pop","Ext.fx.layout.card.Scroll"],constructor:function(b){var a=Ext.fx.layout.card.Abstract,c;if(!b){return null}if(typeof b=="string"){c=b;b={}}else{if(b.type){c=b.type}}b.elementBox=false;if(c){if(Ext.os.is.Android2){if(c!="fade"){c="scroll"}}else{if(c==="slide"&&Ext.browser.is.ChromeMobile){c="scroll"}}a=Ext.ClassManager.getByAlias("fx.layout.card."+c)}return Ext.factory(b,a)}});Ext.define("Ext.fx.runner.Css",{extend:"Ext.Evented",requires:["Ext.fx.Animation"],prefixedProperties:{transform:true,"transform-origin":true,perspective:true,"transform-style":true,transition:true,"transition-property":true,"transition-duration":true,"transition-timing-function":true,"transition-delay":true,animation:true,"animation-name":true,"animation-duration":true,"animation-iteration-count":true,"animation-direction":true,"animation-timing-function":true,"animation-delay":true},lengthProperties:{top:true,right:true,bottom:true,left:true,width:true,height:true,"max-height":true,"max-width":true,"min-height":true,"min-width":true,"margin-bottom":true,"margin-left":true,"margin-right":true,"margin-top":true,"padding-bottom":true,"padding-left":true,"padding-right":true,"padding-top":true,"border-bottom-width":true,"border-left-width":true,"border-right-width":true,"border-spacing":true,"border-top-width":true,"border-width":true,"outline-width":true,"letter-spacing":true,"line-height":true,"text-indent":true,"word-spacing":true,"font-size":true,translate:true,translateX:true,translateY:true,translateZ:true,translate3d:true},durationProperties:{"transition-duration":true,"transition-delay":true,"animation-duration":true,"animation-delay":true},angleProperties:{rotate:true,rotateX:true,rotateY:true,rotateZ:true,skew:true,skewX:true,skewY:true},lengthUnitRegex:/([a-z%]*)$/,DEFAULT_UNIT_LENGTH:"px",DEFAULT_UNIT_ANGLE:"deg",DEFAULT_UNIT_DURATION:"ms",formattedNameCache:{},constructor:function(){var a=Ext.feature.has.Css3dTransforms;if(a){this.transformMethods=["translateX","translateY","translateZ","rotate","rotateX","rotateY","rotateZ","skewX","skewY","scaleX","scaleY","scaleZ"]}else{this.transformMethods=["translateX","translateY","rotate","skewX","skewY","scaleX","scaleY"]}this.vendorPrefix=Ext.browser.getStyleDashPrefix();this.ruleStylesCache={};return this},getStyleSheet:function(){var c=this.styleSheet,a,b;if(!c){a=document.createElement("style");a.type="text/css";(document.head||document.getElementsByTagName("head")[0]).appendChild(a);b=document.styleSheets;this.styleSheet=c=b[b.length-1]}return c},applyRules:function(i){var g=this.getStyleSheet(),k=this.ruleStylesCache,j=g.cssRules,c,e,h,b,d,a,f;for(c in i){e=i[c];h=k[c];if(h===undefined){d=j.length;g.insertRule(c+"{}",d);h=k[c]=j.item(d).style}b=h.$cache;if(!b){b=h.$cache={}}for(a in e){f=this.formatValue(e[a],a);a=this.formatName(a);if(b[a]!==f){b[a]=f;if(f===null){h.removeProperty(a)}else{h.setProperty(a,f,"important")}}}}return this},applyStyles:function(d){var g,c,f,b,a,e;for(g in d){c=document.getElementById(g);if(!c){return this}f=c.style;b=d[g];for(a in b){e=this.formatValue(b[a],a);a=this.formatName(a);if(e===null){f.removeProperty(a)}else{f.setProperty(a,e,"important")}}}return this},formatName:function(b){var a=this.formattedNameCache,c=a[b];if(!c){if(this.prefixedProperties[b]){c=this.vendorPrefix+b}else{c=b}a[b]=c}return c},formatValue:function(j,b){var g=typeof j,l=this.DEFAULT_UNIT_LENGTH,e,a,d,f,c,k,h;if(g=="string"){if(this.lengthProperties[b]){h=j.match(this.lengthUnitRegex)[1];if(h.length>0){}else{return j+l}}return j}else{if(g=="number"){if(j==0){return"0"}if(this.lengthProperties[b]){return j+l}if(this.angleProperties[b]){return j+this.DEFAULT_UNIT_ANGLE}if(this.durationProperties[b]){return j+this.DEFAULT_UNIT_DURATION}}else{if(b==="transform"){e=this.transformMethods;c=[];for(d=0,f=e.length;d0)?k.join(", "):"none"}}}}return j}});Ext.define("Ext.fx.runner.CssTransition",{extend:"Ext.fx.runner.Css",listenersAttached:false,constructor:function(){this.runningAnimationsData={};return this.callParent(arguments)},attachListeners:function(){this.listenersAttached=true;this.getEventDispatcher().addListener("element","*","transitionend","onTransitionEnd",this)},onTransitionEnd:function(b){var a=b.target,c=a.id;if(c&&this.runningAnimationsData.hasOwnProperty(c)){this.refreshRunningAnimationsData(Ext.get(a),[b.browserEvent.propertyName])}},onAnimationEnd:function(g,f,d,j,n){var c=g.getId(),k=this.runningAnimationsData[c],o={},m={},b,h,e,l,a;d.un("stop","onAnimationStop",this);if(k){b=k.nameMap}o[c]=m;if(f.onBeforeEnd){f.onBeforeEnd.call(f.scope||this,g,j)}d.fireEvent("animationbeforeend",d,g,j);this.fireEvent("animationbeforeend",this,d,g,j);if(n||(!j&&!f.preserveEndState)){h=f.toPropertyNames;for(e=0,l=h.length;e0},refreshRunningAnimationsData:function(d,k,t,p){var g=d.getId(),q=this.runningAnimationsData,a=q[g];if(!a){return}var m=a.nameMap,s=a.nameList,b=a.sessions,f,h,e,u,l,c,r,o,n=false;t=Boolean(t);p=Boolean(p);if(!b){return this}f=b.length;if(f===0){return this}if(p){a.nameMap={};s.length=0;for(l=0;l");d.close();this.testElement=c=d.createElement("div");c.style.setProperty("position","absolute","!important");d.body.appendChild(c);this.testElementComputedStyle=window.getComputedStyle(c)}return c},getCssStyleValue:function(b,e){var d=this.getTestElement(),a=this.testElementComputedStyle,c=d.style;c.setProperty(b,e);e=a.getPropertyValue(b);c.removeProperty(b);return e},run:function(p){var F=this,h=this.lengthProperties,x={},E={},G={},d,s,y,e,u,I,v,q,r,a,A,z,o,B,l,t,g,C,H,k,f,w,n,c,D,b,m;if(!this.listenersAttached){this.attachListeners()}p=Ext.Array.from(p);for(A=0,o=p.length;A0){this.refreshRunningAnimationsData(d,Ext.Array.merge(I,v),true,G.replacePrevious)}c=a.nameMap;D=a.nameList;t={};for(z=0;z0){I=Ext.Array.difference(D,I);v=Ext.Array.merge(I,v);y["transition-property"]=I}E[s]=e=Ext.Object.chain(e);e["transition-property"]=v;e["transition-duration"]=G.duration;e["transition-timing-function"]=G.easing;e["transition-delay"]=G.delay;B.startTime=Date.now()}r=this.$className;this.applyStyles(x);q=function(i){if(i.data===r&&i.source===window){window.removeEventListener("message",q,false);F.applyStyles(E)}};window.addEventListener("message",q,false);window.postMessage(r,"*")},onAnimationStop:function(d){var f=this.runningAnimationsData,h,a,g,b,c,e;for(h in f){if(f.hasOwnProperty(h)){a=f[h];g=a.sessions;for(b=0,c=g.length;b component"})},reapply:function(){this.container.innerElement.addCls(this.cls);this.updatePack(this.getPack());this.updateAlign(this.getAlign())},unapply:function(){this.container.innerElement.removeCls(this.cls);this.updatePack(null);this.updateAlign(null)},doItemAdd:function(d,b){this.callParent(arguments);if(d.isInnerItem()){var c=d.getConfig(this.sizePropertyName),a=d.config;if(!c&&("flex" in a)){this.setItemFlex(d,a.flex)}}},doItemRemove:function(a){if(a.isInnerItem()){this.setItemFlex(a,null)}this.callParent(arguments)},onItemSizeChange:function(a){this.setItemFlex(a,null)},doItemCenteredChange:function(b,a){if(a){this.setItemFlex(b,null)}this.callParent(arguments)},doItemFloatingChange:function(a,b){if(b){this.setItemFlex(a,null)}this.callParent(arguments)},doItemDockedChange:function(a,b){if(b){this.setItemFlex(a,null)}this.callParent(arguments)},redrawContainer:function(){var a=this.container,b=a.renderElement.dom.parentNode;if(b&&b.nodeType!==11){a.innerElement.redraw()}},setItemFlex:function(c,a){var b=c.element,d=this.flexItemCls;if(a){b.addCls(d)}else{if(b.hasCls(d)){this.redrawContainer();b.removeCls(d)}}b.dom.style.webkitBoxFlex=a},convertPosition:function(a){if(this.positionMap.hasOwnProperty(a)){return this.positionMap[a]}return a},applyAlign:function(a){return this.convertPosition(a)},updateAlign:function(a){this.container.innerElement.dom.style.webkitBoxAlign=a},applyPack:function(a){return this.convertPosition(a)},updatePack:function(a){this.container.innerElement.dom.style.webkitBoxPack=a}});Ext.define("Ext.layout.Fit",{extend:"Ext.layout.Default",alternateClassName:"Ext.layout.FitLayout",alias:"layout.fit",cls:Ext.baseCSSPrefix+"layout-fit",itemCls:Ext.baseCSSPrefix+"layout-fit-item",constructor:function(a){this.callParent(arguments);this.apply()},apply:function(){this.container.innerElement.addCls(this.cls)},reapply:function(){this.apply()},unapply:function(){this.container.innerElement.removeCls(this.cls)},doItemAdd:function(b,a){if(b.isInnerItem()){b.addCls(this.itemCls)}this.callParent(arguments)},doItemRemove:function(a){if(a.isInnerItem()){a.removeCls(this.itemCls)}this.callParent(arguments)}});Ext.define("Ext.layout.Card",{extend:"Ext.layout.Fit",alternateClassName:"Ext.layout.CardLayout",isCard:true,requires:["Ext.fx.layout.Card"],alias:"layout.card",cls:Ext.baseCSSPrefix+"layout-card",itemCls:Ext.baseCSSPrefix+"layout-card-item",constructor:function(){this.callParent(arguments);this.container.onInitialized(this.onContainerInitialized,this)},applyAnimation:function(a){return new Ext.fx.layout.Card(a)},updateAnimation:function(b,a){if(b&&b.isAnimation){b.setLayout(this)}if(a){a.destroy()}},doItemAdd:function(b,a){if(b.isInnerItem()){b.hide()}this.callParent(arguments)},getInnerItemsContainer:function(){var a=this.innerItemsContainer;if(!a){this.innerItemsContainer=a=Ext.Element.create({className:this.cls+"-container"});this.container.innerElement.append(a)}return a},doItemRemove:function(c,a,b){this.callParent(arguments);if(!b&&c.isInnerItem()){c.show()}},onContainerInitialized:function(a){var b=a.getActiveItem();if(b){b.show()}a.on("activeitemchange","onContainerActiveItemChange",this)},onContainerActiveItemChange:function(a){this.relayEvent(arguments,"doActiveItemChange")},doActiveItemChange:function(b,c,a){if(a){a.hide()}if(c){c.show()}},doItemDockedChange:function(b,c){var a=b.element;if(c){a.removeCls(this.itemCls)}else{a.addCls(this.itemCls)}this.callParent(arguments)}});Ext.define("Ext.layout.HBox",{extend:"Ext.layout.AbstractBox",alternateClassName:"Ext.layout.HBoxLayout",alias:"layout.hbox",sizePropertyName:"width",sizeChangeEventName:"widthchange",cls:Ext.baseCSSPrefix+"layout-hbox"});Ext.define("Ext.layout.VBox",{extend:"Ext.layout.AbstractBox",alternateClassName:"Ext.layout.VBoxLayout",alias:"layout.vbox",sizePropertyName:"height",sizeChangeEventName:"heightchange",cls:Ext.baseCSSPrefix+"layout-vbox"});Ext.define("Ext.layout.Layout",{requires:["Ext.layout.Fit","Ext.layout.Card","Ext.layout.HBox","Ext.layout.VBox"],constructor:function(a,b){var c=Ext.layout.Default,d,e;if(typeof b=="string"){d=b;b={}}else{if("type" in b){d=b.type}}if(d){c=Ext.ClassManager.getByAlias("layout."+d)}return new c(a,b)}});Ext.define("Ext.mixin.Sortable",{extend:"Ext.mixin.Mixin",requires:["Ext.util.Sorter"],mixinConfig:{id:"sortable"},config:{sorters:null,defaultSortDirection:"ASC",sortRoot:null},dirtySortFn:false,sortFn:null,sorted:false,applySorters:function(a,b){if(!b){b=this.createSortersCollection()}b.clear();this.sorted=false;if(a){this.addSorters(a)}return b},createSortersCollection:function(){this._sorters=Ext.create("Ext.util.Collection",function(a){return a.getId()});return this._sorters},addSorter:function(b,a){this.addSorters([b],a)},addSorters:function(c,a){var b=this.getSorters();return this.insertSorters(b?b.length:0,c,a)},insertSorter:function(a,c,b){return this.insertSorters(a,[c],b)},insertSorters:function(e,h,a){if(!Ext.isArray(h)){h=[h]}var f=h.length,j=a||this.getDefaultSortDirection(),c=this.getSortRoot(),k=this.getSorters(),l=[],g,b,m,d;if(!k){k=this.createSortersCollection()}for(b=0;b>1;f=d(e,b[c]);if(f>=0){h=c+1}else{if(f<0){a=c-1}}}return h}});Ext.define("Ext.util.AbstractMixedCollection",{requires:["Ext.util.Filter"],mixins:{observable:"Ext.mixin.Observable"},constructor:function(b,a){var c=this;c.items=[];c.map={};c.keys=[];c.length=0;c.allowFunctions=b===true;if(a){c.getKey=a}c.mixins.observable.constructor.call(c)},allowFunctions:false,add:function(b,e){var d=this,f=e,c=b,a;if(arguments.length==1){f=c;c=d.getKey(f)}if(typeof c!="undefined"&&c!==null){a=d.map[c];if(typeof a!="undefined"){return d.replace(c,f)}d.map[c]=f}d.length++;d.items.push(f);d.keys.push(c);d.fireEvent("add",d.length-1,f,c);return f},getKey:function(a){return a.id},replace:function(c,e){var d=this,a,b;if(arguments.length==1){e=arguments[0];c=d.getKey(e)}a=d.map[c];if(typeof c=="undefined"||c===null||typeof a=="undefined"){return d.add(c,e)}b=d.indexOfKey(c);d.items[b]=e;d.map[c]=e;d.fireEvent("replace",c,a,e);return e},addAll:function(f){var e=this,d=0,b,a,c;if(arguments.length>1||Ext.isArray(f)){b=arguments.length>1?arguments:f;for(a=b.length;d=d.length){return d.add(c,f)}d.length++;Ext.Array.splice(d.items,a,0,f);if(typeof c!="undefined"&&c!==null){d.map[c]=f}Ext.Array.splice(d.keys,a,0,c);d.fireEvent("add",a,f,c);return f},remove:function(a){return this.removeAt(this.indexOf(a))},removeAll:function(a){Ext.each(a||[],function(b){this.remove(b)},this);return this},removeAt:function(a){var c=this,d,b;if(a=0){c.length--;d=c.items[a];Ext.Array.erase(c.items,a,1);b=c.keys[a];if(typeof b!="undefined"){delete c.map[b]}Ext.Array.erase(c.keys,a,1);c.fireEvent("remove",d,b);return d}return false},removeAtKey:function(a){return this.removeAt(this.indexOfKey(a))},getCount:function(){return this.length},indexOf:function(a){return Ext.Array.indexOf(this.items,a)},indexOfKey:function(a){return Ext.Array.indexOf(this.keys,a)},get:function(b){var d=this,a=d.map[b],c=a!==undefined?a:(typeof b=="number")?d.items[b]:undefined;return typeof c!="function"||d.allowFunctions?c:null},getAt:function(a){return this.items[a]},getByKey:function(a){return this.map[a]},contains:function(a){return Ext.Array.contains(this.items,a)},containsKey:function(a){return typeof this.map[a]!="undefined"},clear:function(){var a=this;a.length=0;a.items=[];a.keys=[];a.map={};a.fireEvent("clear")},first:function(){return this.items[0]},last:function(){return this.items[this.length-1]},sum:function(g,b,h,a){var c=this.extractValues(g,b),f=c.length,e=0,d;h=h||0;a=(a||a===0)?a:f-1;for(d=h;d<=a;d++){e+=c[d]}return e},collect:function(j,e,g){var k=this.extractValues(j,e),a=k.length,b={},c=[],h,f,d;for(d=0;d=a;d--){b[b.length]=c[d]}}return b},filter:function(d,c,f,a){var b=[],e;if(Ext.isString(d)){b.push(Ext.create("Ext.util.Filter",{property:d,value:c,anyMatch:f,caseSensitive:a}))}else{if(Ext.isArray(d)||d instanceof Ext.util.Filter){b=b.concat(d)}}e=function(g){var m=true,n=b.length,h;for(h=0;h=e.length||(a&&e.getAutoSort())){return e.add(d,f)}if(typeof d!="undefined"&&d!==null){if(typeof g[d]!="undefined"){e.replace(d,f);return false}g[d]=f}this.all.push(f);if(b&&this.getAutoFilter()&&this.mixins.filterable.isFiltered.call(e,f)){return null}e.length++;Ext.Array.splice(e.items,c,0,f);Ext.Array.splice(e.keys,c,0,d);e.dirtyIndices=true;return f},insertAll:function(g,d){if(g>=this.items.length||(this.sorted&&this.getAutoSort())){return this.addAll(d)}var s=this,h=this.filtered,a=this.sorted,b=this.all,m=this.items,l=this.keys,r=this.map,n=this.getAutoFilter(),o=this.getAutoSort(),t=[],j=[],f=[],c=this.mixins.filterable,e=false,k,u,p,q;if(a&&this.getAutoSort()){}if(Ext.isObject(d)){for(u in d){if(d.hasOwnProperty(u)){j.push(m[u]);t.push(u)}}}else{j=d;k=d.length;for(p=0;p=0){e=a[b];c=f[b];if(typeof c!="undefined"){delete g.map[c]}Ext.Array.erase(a,b,1);Ext.Array.erase(f,b,1);Ext.Array.remove(d,e);delete g.indices[c];g.length--;this.dirtyIndices=true;return e}return false},removeAtKey:function(a){return this.removeAt(this.indexOfKey(a))},getCount:function(){return this.length},indexOf:function(b){if(this.dirtyIndices){this.updateIndices()}var a=this.indices[this.getKey(b)];return(a===undefined)?-1:a},indexOfKey:function(b){if(this.dirtyIndices){this.updateIndices()}var a=this.indices[b];return(a===undefined)?-1:a},updateIndices:function(){var a=this.items,e=a.length,f=this.indices={},c,d,b;for(c=0;c=a;d--){b[b.length]=c[d]}}return b},findIndexBy:function(d,c,h){var g=this,f=g.keys,a=g.items,b=h||0,e=a.length;for(;b1){for(c=a.length;ba){if(d){var e=c.substr(0,a-2),b=Math.max(e.lastIndexOf(" "),e.lastIndexOf("."),e.lastIndexOf("!"),e.lastIndexOf("?"));if(b!=-1&&b>=(a-15)){return e.substr(0,b)+"..."}}return c.substr(0,a-3)+"..."}return c},escapeRegex:function(a){return a.replace(Ext.util.Format.escapeRegexRe,"\\$1")},escape:function(a){return a.replace(Ext.util.Format.escapeRe,"\\$1")},toggle:function(b,c,a){return b==c?a:c},trim:function(a){return a.replace(Ext.util.Format.trimRe,"")},leftPad:function(d,b,c){var a=String(d);c=c||" ";while(a.length/g,">").replace(/").replace(/</g,"<").replace(/"/g,'"').replace(/&/g,"&")},date:function(b,c){var a=b;if(!b){return""}if(!Ext.isDate(b)){a=new Date(Date.parse(b));if(isNaN(a)){if(this.iso8601TestRe.test(b)){a=b.split(this.iso8601SplitRe);a=new Date(a[0],a[1]-1,a[2],a[3],a[4],a[5])}if(isNaN(a)){a=new Date(Date.parse(b.replace(this.dashesRe,"/")))}}b=a}return Ext.Date.format(b,c||Ext.util.Format.defaultDateFormat)}});Ext.define("Ext.Template",{requires:["Ext.dom.Helper","Ext.util.Format"],inheritableStatics:{from:function(b,a){b=Ext.getDom(b);return new this(b.value||b.innerHTML,a||"")}},constructor:function(d){var f=this,b=arguments,a=[],c=0,e=b.length,g;f.initialConfig={};if(e>1){for(;cf)?1:((ba?1:(d0},isExpandable:function(){var a=this;if(a.get("expandable")){return !(a.isLeaf()||(a.isLoaded()&&!a.hasChildNodes()))}return false},appendChild:function(b,j,h){var f=this,c,e,d,g,a;if(Ext.isArray(b)){for(c=0,e=b.length;c0){Ext.Array.sort(d,f);for(c=0;cMath.max(c,b)||jMath.max(a,q)||eMath.max(p,n)||eMath.max(k,h)){return null}return new Ext.util.Point(j,e)},toString:function(){return this.point1.toString()+" "+this.point2.toString()}});Ext.define("Ext.util.SizeMonitor",{extend:"Ext.Evented",config:{element:null,detectorCls:Ext.baseCSSPrefix+"size-change-detector",callback:Ext.emptyFn,scope:null,args:[]},constructor:function(d){this.initConfig(d);this.doFireSizeChangeEvent=Ext.Function.bind(this.doFireSizeChangeEvent,this);var g=this,e=this.getElement().dom,b=this.getDetectorCls(),c=Ext.Element.create({classList:[b,b+"-expand"],children:[{}]},true),h=Ext.Element.create({classList:[b,b+"-shrink"],children:[{}]},true),a=function(i){g.onDetectorScroll("expand",i)},f=function(i){g.onDetectorScroll("shrink",i)};e.appendChild(c);e.appendChild(h);this.detectors={expand:c,shrink:h};this.position={expand:{left:0,top:0},shrink:{left:0,top:0}};this.listeners={expand:a,shrink:f};this.refresh();c.addEventListener("scroll",a,true);h.addEventListener("scroll",f,true)},applyElement:function(a){if(a){return Ext.get(a)}},updateElement:function(a){a.on("destroy","destroy",this)},refreshPosition:function(b){var e=this.detectors[b],a=this.position[b],d,c;a.left=d=e.scrollWidth-e.offsetWidth;a.top=c=e.scrollHeight-e.offsetHeight;e.scrollLeft=d;e.scrollTop=c},refresh:function(){this.refreshPosition("expand");this.refreshPosition("shrink")},onDetectorScroll:function(b){var c=this.detectors[b],a=this.position[b];if(c.scrollLeft!==a.left||c.scrollTop!==a.top){this.refresh();this.fireSizeChangeEvent()}},fireSizeChangeEvent:function(){clearTimeout(this.sizeChangeThrottleTimer);this.sizeChangeThrottleTimer=setTimeout(this.doFireSizeChangeEvent,1)},doFireSizeChangeEvent:function(){this.getCallback().apply(this.getScope(),this.getArgs())},destroyDetector:function(a){var c=this.detectors[a],b=this.listeners[a];c.removeEventListener("scroll",b,true);Ext.removeNode(c)},destroy:function(){this.callParent(arguments);this.destroyDetector("expand");this.destroyDetector("shrink");delete this.listeners;delete this.detectors}});Ext.define("Ext.event.publisher.ComponentSize",{extend:"Ext.event.publisher.Publisher",requires:["Ext.ComponentManager","Ext.util.SizeMonitor"],targetType:"component",handledEvents:["resize"],constructor:function(){this.callParent(arguments);this.sizeMonitors={}},subscribe:function(g){var c=g.match(this.idSelectorRegex),f=this.subscribers,a=this.sizeMonitors,d=this.dispatcher,e=this.targetType,b;if(!c){return false}if(!f.hasOwnProperty(g)){f[g]=0;d.addListener(e,g,"painted","onComponentPainted",this,null,"before");b=Ext.ComponentManager.get(c[1]);a[g]=new Ext.util.SizeMonitor({element:b.element,callback:this.onComponentSizeChange,scope:this,args:[this,g]})}f[g]++;return true},unsubscribe:function(h,b,e){var c=h.match(this.idSelectorRegex),g=this.subscribers,d=this.dispatcher,f=this.targetType,a=this.sizeMonitors;if(!c){return false}if(!g.hasOwnProperty(h)||(!e&&--g[h]>0)){return true}a[h].destroy();delete a[h];d.removeListener(f,h,"painted","onComponentPainted",this,"before");delete g[h];return true},onComponentPainted:function(b){var c=b.getObservableId(),a=this.sizeMonitors[c];a.refresh()},onComponentSizeChange:function(a,b){this.dispatcher.doDispatchEvent(this.targetType,b,"resize",[a])}});Ext.define("Ext.util.Sortable",{isSortable:true,defaultSortDirection:"ASC",requires:["Ext.util.Sorter"],initSortable:function(){var a=this,b=a.sorters;a.sorters=Ext.create("Ext.util.AbstractMixedCollection",false,function(c){return c.id||c.property});if(b){a.sorters.addAll(a.decodeSorters(b))}},sort:function(g,f,c,e){var d=this,h,b,a;if(Ext.isArray(g)){e=c;c=f;a=g}else{if(Ext.isObject(g)){e=c;c=f;a=[g]}else{if(Ext.isString(g)){h=d.sorters.get(g);if(!h){h={property:g,direction:f};a=[h]}else{if(f===undefined){h.toggle()}else{h.setDirection(f)}}}}}if(a&&a.length){a=d.decodeSorters(a);if(Ext.isString(c)){if(c==="prepend"){g=d.sorters.clone().items;d.sorters.clear();d.sorters.addAll(a);d.sorters.addAll(g)}else{d.sorters.addAll(a)}}else{d.sorters.clear();d.sorters.addAll(a)}if(e!==false){d.onBeforeSort(a)}}if(e!==false){g=d.sorters.items;if(g.length){b=function(l,k){var j=g[0].sort(l,k),n=g.length,m;for(m=1;me?1:(f0){g=f.data.items;r=g.length;for(k=0;k0){b.create=e;f=true}if(c.length>0){b.update=c;f=true}if(a.length>0){b.destroy=a;f=true}if(f&&d.fireEvent("beforesync",this,b)!==false){d.getProxy().batch({operations:b,listeners:d.getBatchListeners()})}return{added:e,updated:c,removed:a}},first:function(){return this.data.first()},last:function(){return this.data.last()},sum:function(e){var d=0,c=0,b=this.data.items,a=b.length;for(;c0){c=b[0].get(f)}for(;d0){a=c[0].get(f)}for(;da){a=e}}return a},average:function(e){var c=0,b=this.data.items,a=b.length,d=0;if(b.length>0){for(;ce){return 1}else{if(fa.data.index)?1:-1},applyFilters:function(b){var a=this;return function(c){return a.isVisible(c)}},applyProxy:function(a){},applyNode:function(a){if(a){a=Ext.data.NodeInterface.decorate(a)}return a},updateNode:function(a,c){if(c&&!c.isDestroyed){c.un({append:"onNodeAppend",insert:"onNodeInsert",remove:"onNodeRemove",load:"onNodeLoad",scope:this});c.unjoin(this)}if(a){a.on({scope:this,append:"onNodeAppend",insert:"onNodeInsert",remove:"onNodeRemove",load:"onNodeLoad"});a.join(this);var b=[];if(a.childNodes.length){b=b.concat(this.retrieveChildNodes(a))}if(this.getRootVisible()){b.push(a)}else{if(a.isLoaded()||a.isLoading()){a.set("expanded",true)}}this.data.clear();this.fireEvent("clear",this);this.suspendEvents();this.add(b);this.resumeEvents();this.fireEvent("refresh",this,this.data)}},retrieveChildNodes:function(a){var d=this.getNode(),b=this.getRecursive(),c=[],e=a;if(!a.childNodes.length||(!b&&a!==d)){return c}if(!b){return a.childNodes}while(e){if(e._added){delete e._added;if(e===a){break}else{e=e.nextSibling||e.parentNode}}else{if(e!==a){c.push(e)}if(e.firstChild){e._added=true;e=e.firstChild}else{e=e.nextSibling||e.parentNode}}}return c},isVisible:function(b){var a=b.parentNode;if(!this.getRecursive()&&a!==this.getNode()){return false}while(a){if(!a.isExpanded()){return false}if(a===this.getNode()){break}a=a.parentNode}return true}});Ext.define("Ext.data.TreeStore",{extend:"Ext.data.NodeStore",alias:"store.tree",config:{root:undefined,clearOnLoad:true,nodeParam:"node",defaultRootId:"root",defaultRootProperty:"children",recursive:true},applyProxy:function(){return Ext.data.Store.prototype.applyProxy.apply(this,arguments)},applyRoot:function(a){var b=this;a=a||{};a=Ext.apply({},a);if(!a.isModel){Ext.applyIf(a,{id:b.getStoreId()+"-"+b.getDefaultRootId(),text:"Root",allowDrag:false});a=Ext.data.ModelManager.create(a,b.getModel())}Ext.data.NodeInterface.decorate(a);a.set(a.raw);return a},handleTreeInsertionIndex:function(a,b,d,c){if(b.parentNode){b.parentNode.sort(d.getSortFn(),true,true)}return this.callParent(arguments)},handleTreeSort:function(a,b){if(this._sorting){return a}this._sorting=true;this.getNode().sort(b.getSortFn(),true,true);delete this._sorting;return this.callParent(arguments)},updateRoot:function(a,b){if(b){b.unBefore({expand:"onNodeBeforeExpand",scope:this});b.unjoin(this)}a.onBefore({expand:"onNodeBeforeExpand",scope:this});this.onNodeAppend(null,a);this.setNode(a);if(!a.isLoaded()&&!a.isLoading()&&a.isExpanded()){this.load({node:a})}this.fireEvent("rootchange",this,a,b)},getNodeById:function(a){return this.data.getByKey(a)},onNodeBeforeExpand:function(b,a,c){if(b.isLoading()){c.pause();this.on("load",function(){c.resume()},this,{single:true})}else{if(!b.isLoaded()){c.pause();this.load({node:b,callback:function(){c.resume()}})}}},onNodeAppend:function(n,c){var l=this.getProxy(),j=l.getReader(),b=this.getModel(),g=c.raw,d=[],a=j.getRootProperty(),m,h,f,k,e;if(!c.isLeaf()){m=j.getRoot(g);if(m){h=j.extractData(m);for(f=0,k=h.length;f0){this.sendRequest(b==1?a[0]:a);this.callBuffer=[]}}});Ext.define("Ext.util.TapRepeater",{requires:["Ext.DateExtras"],mixins:{observable:"Ext.mixin.Observable"},config:{el:null,accelerate:true,interval:10,delay:250,preventDefault:true,stopDefault:false,timer:0,pressCls:null},constructor:function(a){var b=this;b.initConfig(a)},updateEl:function(c,b){var a={touchstart:"onTouchStart",touchend:"onTouchEnd",tap:"eventOptions",scope:this};if(b){b.un(a)}c.on(a)},eventOptions:function(a){if(this.getPreventDefault()){a.preventDefault()}if(this.getStopDefault()){a.stopEvent()}},destroy:function(){this.clearListeners();Ext.destroy(this.el)},onTouchStart:function(c){var b=this,a=b.getPressCls();clearTimeout(b.getTimer());if(a){b.getEl().addCls(a)}b.tapStartTime=new Date();b.fireEvent("touchstart",b,c);b.fireEvent("tap",b,c);if(b.getAccelerate()){b.delay=400}b.setTimer(Ext.defer(b.tap,b.getDelay()||b.getInterval(),b,[c]))},tap:function(b){var a=this;a.fireEvent("tap",a,b);a.setTimer(Ext.defer(a.tap,a.getAccelerate()?a.easeOutExpo(Ext.Date.getElapsed(a.tapStartTime),400,-390,12000):a.getInterval(),a,[b]))},easeOutExpo:function(e,a,g,f){return(e==f)?a+g:g*(-Math.pow(2,-10*e/f)+1)+a},onTouchEnd:function(b){var a=this;clearTimeout(a.getTimer());a.getEl().removeCls(a.getPressCls());a.fireEvent("touchend",a,b)}});Ext.define("Ext.util.translatable.Abstract",{extend:"Ext.Evented",requires:["Ext.fx.easing.Linear"],config:{element:null,easing:null,easingX:null,easingY:null,fps:60},constructor:function(a){var b;this.doAnimationFrame=Ext.Function.bind(this.doAnimationFrame,this);this.x=0;this.y=0;this.activeEasingX=null;this.activeEasingY=null;this.initialConfig=a;if(a&&a.element){b=a.element;this.setElement(b)}},applyElement:function(a){if(!a){return}return Ext.get(a)},updateElement:function(a){this.initConfig(this.initialConfig);this.refresh()},factoryEasing:function(a){return Ext.factory(a,Ext.fx.easing.Linear,null,"easing")},applyEasing:function(a){if(!this.getEasingX()){this.setEasingX(this.factoryEasing(a))}if(!this.getEasingY()){this.setEasingY(this.factoryEasing(a))}},applyEasingX:function(a){return this.factoryEasing(a)},applyEasingY:function(a){return this.factoryEasing(a)},updateFps:function(a){this.animationInterval=1000/a},doTranslate:function(a,b){if(typeof a=="number"){this.x=a}if(typeof b=="number"){this.y=b}return this},translate:function(a,c,b){if(!this.getElement().dom){return}if(Ext.isObject(a)){throw new Error()}this.stopAnimation();if(b){return this.translateAnimated(a,c,b)}return this.doTranslate(a,c)},animate:function(b,a){this.activeEasingX=b;this.activeEasingY=a;this.isAnimating=true;this.animationTimer=setInterval(this.doAnimationFrame,this.animationInterval);this.fireEvent("animationstart",this,this.x,this.y);return this},translateAnimated:function(b,g,e){if(Ext.isObject(b)){throw new Error()}if(!Ext.isObject(e)){e={}}var d=Ext.Date.now(),f=e.easing,c=(typeof b=="number")?(e.easingX||this.getEasingX()||f||true):null,a=(typeof g=="number")?(e.easingY||this.getEasingY()||f||true):null;if(c){c=this.factoryEasing(c);c.setStartTime(d);c.setStartValue(this.x);c.setEndValue(b);if("duration" in e){c.setDuration(e.duration)}}if(a){a=this.factoryEasing(a);a.setStartTime(d);a.setStartValue(this.y);a.setEndValue(g);if("duration" in e){a.setDuration(e.duration)}}return this.animate(c,a)},doAnimationFrame:function(){var c=this.activeEasingX,b=this.activeEasingY,d=this.getElement(),a,e;if(!this.isAnimating||!d.dom){return}if(c===null&&b===null){this.stopAnimation();return}if(c!==null){this.x=a=Math.round(c.getValue());if(c.isEnded){this.activeEasingX=null;this.fireEvent("axisanimationend",this,"x",a)}}else{a=this.x}if(b!==null){this.y=e=Math.round(b.getValue());if(b.isEnded){this.activeEasingY=null;this.fireEvent("axisanimationend",this,"y",e)}}else{e=this.y}this.doTranslate(a,e);this.fireEvent("animationframe",this,a,e)},stopAnimation:function(){if(!this.isAnimating){return}this.activeEasingX=null;this.activeEasingY=null;this.isAnimating=false;clearInterval(this.animationTimer);this.fireEvent("animationend",this,this.x,this.y)},refresh:function(){this.translate(this.x,this.y)}});Ext.define("Ext.util.translatable.CssTransform",{extend:"Ext.util.translatable.Abstract",doTranslate:function(a,c){var b=this.getElement().dom.style;if(typeof a!="number"){a=this.x}if(typeof c!="number"){c=this.y}b.webkitTransform="translate3d("+a+"px, "+c+"px, 0px)";return this.callParent(arguments)},destroy:function(){var a=this.getElement();if(a&&!a.isDestroyed){a.dom.style.webkitTransform=null}this.callParent(arguments)}});Ext.define("Ext.util.translatable.ScrollPosition",{extend:"Ext.util.translatable.Abstract",wrapperWidth:0,wrapperHeight:0,baseCls:"x-translatable",config:{useWrapper:true},getWrapper:function(){var e=this.wrapper,c=this.baseCls,b=this.getElement(),d,a;if(!e){a=b.getParent();if(!a){return null}if(this.getUseWrapper()){e=b.wrap({className:c+"-wrapper"},true)}else{e=a.dom}e.appendChild(Ext.Element.create({className:c+"-stretcher"},true));this.nestedStretcher=d=Ext.Element.create({className:c+"-nested-stretcher"},true);b.appendChild(d);b.addCls(c);a.addCls(c+"-container");this.container=a;this.wrapper=e;this.refresh()}return e},doTranslate:function(a,c){var b=this.getWrapper();if(b){if(typeof a=="number"){b.scrollLeft=this.wrapperWidth-a}if(typeof c=="number"){b.scrollTop=this.wrapperHeight-c}}return this.callParent(arguments)},refresh:function(){var a=this.getWrapper();if(a){this.wrapperWidth=a.offsetWidth;this.wrapperHeight=a.offsetHeight;this.callParent(arguments)}},destroy:function(){var b=this.getElement(),a=this.baseCls;if(this.wrapper){if(this.getUseWrapper()){b.unwrap()}this.container.removeCls(a+"-container");b.removeCls(a);b.removeChild(this.nestedStretcher)}this.callParent(arguments)}});Ext.define("Ext.util.Translatable",{requires:["Ext.util.translatable.CssTransform","Ext.util.translatable.ScrollPosition"],constructor:function(a){var c=Ext.util.translatable,e=c.CssTransform,d=c.ScrollPosition,b;if(typeof a=="object"&&"translationMethod" in a){if(a.translationMethod==="scrollposition"){b=d}else{if(a.translationMethod==="csstransform"){b=e}}}if(!b){if(Ext.os.is.Android2||Ext.browser.is.ChromeMobile){b=d}else{b=e}}return new b(a)}});Ext.define("Ext.behavior.Translatable",{extend:"Ext.behavior.Behavior",requires:["Ext.util.Translatable"],constructor:function(){this.listeners={painted:"onComponentPainted",scope:this};this.callParent(arguments)},onComponentPainted:function(){this.translatable.refresh()},setConfig:function(c){var a=this.translatable,b=this.component;if(c){if(!a){this.translatable=a=new Ext.util.Translatable(c);a.setElement(b.renderElement);a.on("destroy","onTranslatableDestroy",this);if(b.isPainted()){this.onComponentPainted(b)}b.on(this.listeners)}else{if(Ext.isObject(c)){a.setConfig(c)}}}else{if(a){a.destroy()}}return this},getTranslatable:function(){return this.translatable},onTranslatableDestroy:function(){var a=this.component;delete this.translatable;a.un(this.listeners)},onComponentDestroy:function(){var a=this.translatable;if(a){a.destroy()}}});Ext.define("Ext.scroll.Scroller",{extend:"Ext.Evented",requires:["Ext.fx.easing.BoundMomentum","Ext.fx.easing.EaseOut","Ext.util.SizeMonitor","Ext.util.Translatable"],config:{element:null,direction:"auto",translationMethod:"auto",fps:"auto",disabled:null,directionLock:false,momentumEasing:{momentum:{acceleration:30,friction:0.5},bounce:{acceleration:30,springTension:0.3},minVelocity:1},bounceEasing:{duration:400},outOfBoundRestrictFactor:0.5,startMomentumResetTime:300,maxAbsoluteVelocity:6,containerSize:"auto",containerScrollSize:"auto",size:"auto",autoRefresh:true,initialOffset:{x:0,y:0},slotSnapSize:{x:0,y:0},slotSnapOffset:{x:0,y:0},slotSnapEasing:{duration:150}},cls:Ext.baseCSSPrefix+"scroll-scroller",containerCls:Ext.baseCSSPrefix+"scroll-container",dragStartTime:0,dragEndTime:0,isDragging:false,isAnimating:false,constructor:function(a){var b=a&&a.element;this.doAnimationFrame=Ext.Function.bind(this.doAnimationFrame,this);this.stopAnimation=Ext.Function.bind(this.stopAnimation,this);this.listeners={scope:this,touchstart:"onTouchStart",touchend:"onTouchEnd",dragstart:"onDragStart",drag:"onDrag",dragend:"onDragEnd"};this.minPosition={x:0,y:0};this.startPosition={x:0,y:0};this.size={x:0,y:0};this.position={x:0,y:0};this.velocity={x:0,y:0};this.isAxisEnabledFlags={x:false,y:false};this.flickStartPosition={x:0,y:0};this.flickStartTime={x:0,y:0};this.lastDragPosition={x:0,y:0};this.dragDirection={x:0,y:0};this.initialConfig=a;if(b){this.setElement(b)}return this},applyElement:function(a){if(!a){return}return Ext.get(a)},updateElement:function(a){this.initialize();a.addCls(this.cls);if(!this.getDisabled()){this.attachListeneners()}this.onConfigUpdate(["containerSize","size"],"refreshMaxPosition");this.on("maxpositionchange","snapToBoundary");this.on("minpositionchange","snapToBoundary");return this},getTranslatable:function(){if(!this.hasOwnProperty("translatable")){var a=this.getBounceEasing();this.translatable=new Ext.util.Translatable({translationMethod:this.getTranslationMethod(),element:this.getElement(),easingX:a.x,easingY:a.y,useWrapper:false,listeners:{animationframe:"onAnimationFrame",animationend:"onAnimationEnd",axisanimationend:"onAxisAnimationEnd",scope:this}})}return this.translatable},updateFps:function(a){if(a!=="auto"){this.getTranslatable().setFps(a)}},attachListeneners:function(){this.getContainer().on(this.listeners)},detachListeners:function(){this.getContainer().un(this.listeners)},updateDisabled:function(a){if(a){this.detachListeners()}else{this.attachListeneners()}},updateInitialOffset:function(c){if(typeof c=="number"){c={x:c,y:c}}var b=this.position,a,d;b.x=a=c.x;b.y=d=c.y;this.getTranslatable().doTranslate(-a,-d)},applyDirection:function(a){var e=this.getMinPosition(),d=this.getMaxPosition(),c,b;this.givenDirection=a;if(a==="auto"){c=d.x>e.x;b=d.y>e.y;if(c&&b){a="both"}else{if(c){a="horizontal"}else{a="vertical"}}}return a},updateDirection:function(b){var a=this.isAxisEnabledFlags;a.x=(b==="both"||b==="horizontal");a.y=(b==="both"||b==="vertical")},isAxisEnabled:function(a){this.getDirection();return this.isAxisEnabledFlags[a]},applyMomentumEasing:function(b){var a=Ext.fx.easing.BoundMomentum;return{x:Ext.factory(b,a),y:Ext.factory(b,a)}},applyBounceEasing:function(b){var a=Ext.fx.easing.EaseOut;return{x:Ext.factory(b,a),y:Ext.factory(b,a)}},applySlotSnapEasing:function(b){var a=Ext.fx.easing.EaseOut;return{x:Ext.factory(b,a),y:Ext.factory(b,a)}},getMinPosition:function(){var a=this.minPosition;if(!a){this.minPosition=a={x:0,y:0};this.fireEvent("minpositionchange",this,a)}return a},getMaxPosition:function(){var c=this.maxPosition,a,b;if(!c){a=this.getSize();b=this.getContainerSize();this.maxPosition=c={x:Math.max(0,a.x-b.x),y:Math.max(0,a.y-b.y)};this.fireEvent("maxpositionchange",this,c)}return c},refreshMaxPosition:function(){this.maxPosition=null;this.getMaxPosition()},applyContainerSize:function(b){var c=this.getContainer().dom,a,d;if(!c){return}this.givenContainerSize=b;if(b==="auto"){a=c.offsetWidth;d=c.offsetHeight}else{a=b.x;d=b.y}return{x:a,y:d}},applySize:function(b){var c=this.getElement().dom,a,d;if(!c){return}this.givenSize=b;if(b==="auto"){a=c.offsetWidth;d=c.offsetHeight}else{a=b.x;d=b.y}return{x:a,y:d}},applyContainerScrollSize:function(b){var c=this.getContainer().dom,a,d;if(!c){return}this.givenContainerScrollSize=b;if(b==="auto"){a=c.scrollWidth;d=c.scrollHeight}else{a=b.x;d=b.y}return{x:a,y:d}},updateAutoRefresh:function(b){var c=Ext.util.SizeMonitor,a;if(b){this.sizeMonitors={element:new c({element:this.getElement(),callback:this.doRefresh,scope:this}),container:new c({element:this.getContainer(),callback:this.doRefresh,scope:this})}}else{a=this.sizeMonitors;if(a){a.element.destroy();a.container.destroy()}}},applySlotSnapSize:function(a){if(typeof a=="number"){return{x:a,y:a}}return a},applySlotSnapOffset:function(a){if(typeof a=="number"){return{x:a,y:a}}return a},getContainer:function(){var a=this.container;if(!a){this.container=a=this.getElement().getParent();a.addCls(this.containerCls)}return a},doRefresh:function(){this.stopAnimation();this.getTranslatable().refresh();this.setSize(this.givenSize);this.setContainerSize(this.givenContainerSize);this.setContainerScrollSize(this.givenContainerScrollSize);this.setDirection(this.givenDirection);this.fireEvent("refresh",this)},refresh:function(){var a=this.sizeMonitors;if(a){a.element.refresh();a.container.refresh()}this.doRefresh();return this},scrollTo:function(c,h,g){var b=this.getTranslatable(),a=this.position,d=false,f,e;if(this.isAxisEnabled("x")){if(typeof c!="number"){c=a.x}else{if(a.x!==c){a.x=c;d=true}}f=-c}if(this.isAxisEnabled("y")){if(typeof h!="number"){h=a.y}else{if(a.y!==h){a.y=h;d=true}}e=-h}if(d){if(g!==undefined){b.translateAnimated(f,e,g)}else{this.fireEvent("scroll",this,a.x,a.y);b.doTranslate(f,e)}}return this},scrollToTop:function(b){var a=this.getInitialOffset();return this.scrollTo(a.x,a.y,b)},scrollToEnd:function(a){return this.scrollTo(0,this.getSize().y-this.getContainerSize().y,a)},scrollBy:function(b,d,c){var a=this.position;b=(typeof b=="number")?b+a.x:null;d=(typeof d=="number")?d+a.y:null;return this.scrollTo(b,d,c)},onTouchStart:function(){this.isTouching=true;this.stopAnimation()},onTouchEnd:function(){var a=this.position;this.isTouching=false;if(!this.isDragging&&this.snapToSlot()){this.fireEvent("scrollstart",this,a.x,a.y)}},onDragStart:function(l){var o=this.getDirection(),g=l.absDeltaX,f=l.absDeltaY,j=this.getDirectionLock(),i=this.startPosition,d=this.flickStartPosition,k=this.flickStartTime,h=this.lastDragPosition,c=this.position,b=this.dragDirection,n=c.x,m=c.y,a=Ext.Date.now();this.isDragging=true;if(j&&o!=="both"){if((o==="horizontal"&&g>f)||(o==="vertical"&&f>g)){l.stopPropagation()}else{this.isDragging=false;return}}h.x=n;h.y=m;d.x=n;d.y=m;i.x=n;i.y=m;k.x=a;k.y=a;b.x=0;b.y=0;this.dragStartTime=a;this.isDragging=true;this.fireEvent("scrollstart",this,n,m)},onAxisDrag:function(i,q){if(!this.isAxisEnabled(i)){return}var h=this.flickStartPosition,l=this.flickStartTime,j=this.lastDragPosition,e=this.dragDirection,g=this.position[i],k=this.getMinPosition()[i],o=this.getMaxPosition()[i],d=this.startPosition[i],p=j[i],n=d-q,c=e[i],m=this.getOutOfBoundRestrictFactor(),f=this.getStartMomentumResetTime(),b=Ext.Date.now(),a;if(no){a=n-o;n=o+a*m}}if(n>p){e[i]=1}else{if(nf){h[i]=g;l[i]=b}j[i]=n},onDrag:function(b){if(!this.isDragging){return}var a=this.lastDragPosition;this.onAxisDrag("x",b.deltaX);this.onAxisDrag("y",b.deltaY);this.scrollTo(a.x,a.y)},onDragEnd:function(c){var b,a;if(!this.isDragging){return}this.dragEndTime=Ext.Date.now();this.onDrag(c);this.isDragging=false;b=this.getAnimationEasing("x");a=this.getAnimationEasing("y");if(b||a){this.getTranslatable().animate(b,a)}else{this.onScrollEnd()}},getAnimationEasing:function(g){if(!this.isAxisEnabled(g)){return null}var e=this.position[g],f=this.flickStartPosition[g],k=this.flickStartTime[g],c=this.getMinPosition()[g],j=this.getMaxPosition()[g],a=this.getMaxAbsoluteVelocity(),d=null,b=this.dragEndTime,l,i,h;if(ej){d=j}}if(d!==null){l=this.getBounceEasing()[g];l.setConfig({startTime:b,startValue:-e,endValue:-d});return l}h=b-k;if(h===0){return null}i=(e-f)/(b-k);if(i===0){return null}if(i<-a){i=-a}else{if(i>a){i=a}}l=this.getMomentumEasing()[g];l.setConfig({startTime:b,startValue:-e,startVelocity:-i,minMomentumValue:-j,maxMomentumValue:0});return l},onAnimationFrame:function(c,b,d){var a=this.position;a.x=-b;a.y=-d;this.fireEvent("scroll",this,a.x,a.y)},onAxisAnimationEnd:function(a){},onAnimationEnd:function(){this.snapToBoundary();this.onScrollEnd()},stopAnimation:function(){this.getTranslatable().stopAnimation()},onScrollEnd:function(){var a=this.position;if(this.isTouching||!this.snapToSlot()){this.fireEvent("scrollend",this,a.x,a.y)}},snapToSlot:function(){var b=this.getSnapPosition("x"),a=this.getSnapPosition("y"),c=this.getSlotSnapEasing();if(b!==null||a!==null){this.scrollTo(b,a,{easingX:c.x,easingY:c.y});return true}return false},getSnapPosition:function(c){var g=this.getSlotSnapSize()[c],d=null,a,f,e,b;if(g!==0&&this.isAxisEnabled(c)){a=this.position[c];f=this.getSlotSnapOffset()[c];e=this.getMaxPosition()[c];b=(a-f)%g;if(b!==0){if(Math.abs(b)>g/2){d=a+((b>0)?g-b:b-g);if(d>e){d=a-b}}else{d=a-b}}}return d},snapToBoundary:function(){var g=this.position,c=this.getMinPosition(),f=this.getMaxPosition(),e=c.x,d=c.y,b=f.x,a=f.y,i=Math.round(g.x),h=Math.round(g.y);if(ib){i=b}}if(ha){h=a}}this.scrollTo(i,h)},destroy:function(){var b=this.getElement(),a=this.sizeMonitors;if(a){a.element.destroy();a.container.destroy()}if(b&&!b.isDestroyed){b.removeCls(this.cls);this.getContainer().removeCls(this.containerCls)}Ext.destroy(this.translatable);this.callParent(arguments)}},function(){});Ext.define("Ext.util.Draggable",{isDraggable:true,mixins:["Ext.mixin.Observable"],requires:["Ext.util.SizeMonitor","Ext.util.Translatable"],config:{cls:Ext.baseCSSPrefix+"draggable",draggingCls:Ext.baseCSSPrefix+"dragging",element:null,constraint:"container",disabled:null,direction:"both",initialOffset:{x:0,y:0},translatable:{}},DIRECTION_BOTH:"both",DIRECTION_VERTICAL:"vertical",DIRECTION_HORIZONTAL:"horizontal",defaultConstraint:{min:{x:-Infinity,y:-Infinity},max:{x:Infinity,y:Infinity}},sizeMonitor:null,containerSizeMonitor:null,constructor:function(a){var b;this.extraConstraint={};this.initialConfig=a;this.offset={x:0,y:0};this.listeners={dragstart:"onDragStart",drag:"onDrag",dragend:"onDragEnd",scope:this};if(a&&a.element){b=a.element;delete a.element;this.setElement(b)}return this},applyElement:function(a){if(!a){return}return Ext.get(a)},updateElement:function(a){a.on(this.listeners);this.sizeMonitor=new Ext.util.SizeMonitor({element:a,callback:this.doRefresh,scope:this});this.initConfig(this.initialConfig)},updateInitialOffset:function(b){if(typeof b=="number"){b={x:b,y:b}}var c=this.offset,a,d;c.x=a=b.x;c.y=d=b.y;this.getTranslatable().doTranslate(a,d)},updateCls:function(a){this.getElement().addCls(a)},applyTranslatable:function(a,b){a=Ext.factory(a,Ext.util.Translatable,b);a.setElement(this.getElement());return a},setExtraConstraint:function(a){this.extraConstraint=a||{};this.refreshConstraint();return this},addExtraConstraint:function(a){Ext.merge(this.extraConstraint,a);this.refreshConstraint();return this},applyConstraint:function(a){this.currentConstraint=a;if(!a){a=this.defaultConstraint}if(a==="container"){return Ext.merge(this.getContainerConstraint(),this.extraConstraint)}return Ext.merge({},this.extraConstraint,a)},updateConstraint:function(){this.refreshOffset()},getContainerConstraint:function(){var b=this.getContainer(),c=this.getElement();if(!b||!c.dom){return this.defaultConstraint}var h=c.dom,g=b.dom,d=h.offsetWidth,a=h.offsetHeight,f=g.offsetWidth,e=g.offsetHeight;return{min:{x:0,y:0},max:{x:f-d,y:e-a}}},getContainer:function(){var a=this.container;if(!a){a=this.getElement().getParent();if(a){this.containerSizeMonitor=new Ext.util.SizeMonitor({element:a,callback:this.doRefresh,scope:this});this.container=a;a.on("destroy","onContainerDestroy",this)}}return a},onContainerDestroy:function(){delete this.container;delete this.containerSizeMonitor},detachListeners:function(){this.getElement().un(this.listeners)},isAxisEnabled:function(a){var b=this.getDirection();if(a==="x"){return(b===this.DIRECTION_BOTH||b===this.DIRECTION_HORIZONTAL)}return(b===this.DIRECTION_BOTH||b===this.DIRECTION_VERTICAL)},onDragStart:function(a){if(this.getDisabled()){return false}var b=this.offset;this.fireAction("dragstart",[this,a,b.x,b.y],this.initDragStart)},initDragStart:function(b,c,a,d){this.dragStartOffset={x:a,y:d};this.isDragging=true;this.getElement().addCls(this.getDraggingCls())},onDrag:function(b){if(!this.isDragging){return}var a=this.dragStartOffset;this.fireAction("drag",[this,b,a.x+b.deltaX,a.y+b.deltaY],this.doDrag)},doDrag:function(b,c,a,d){b.setOffset(a,d)},onDragEnd:function(a){if(!this.isDragging){return}this.onDrag(a);this.isDragging=false;this.getElement().removeCls(this.getDraggingCls());this.fireEvent("dragend",this,a,this.offset.x,this.offset.y)},setOffset:function(i,h,b){var f=this.offset,a=this.getConstraint(),e=a.min,c=a.max,d=Math.min,g=Math.max;if(this.isAxisEnabled("x")&&typeof i=="number"){i=d(g(i,e.x),c.x)}else{i=f.x}if(this.isAxisEnabled("y")&&typeof h=="number"){h=d(g(h,e.y),c.y)}else{h=f.y}f.x=i;f.y=h;this.getTranslatable().translate(i,h,b)},getOffset:function(){return this.offset},refreshConstraint:function(){this.setConstraint(this.currentConstraint)},refreshOffset:function(){var a=this.offset;this.setOffset(a.x,a.y)},doRefresh:function(){this.refreshConstraint();this.getTranslatable().refresh();this.refreshOffset()},refresh:function(){if(this.sizeMonitor){this.sizeMonitor.refresh()}if(this.containerSizeMonitor){this.containerSizeMonitor.refresh()}this.doRefresh()},enable:function(){return this.setDisabled(false)},disable:function(){return this.setDisabled(true)},destroy:function(){var a=this.getTranslatable();Ext.destroy(this.containerSizeMonitor,this.sizeMonitor);delete this.sizeMonitor;delete this.containerSizeMonitor;var b=this.getElement();if(b&&!b.isDestroyed){b.removeCls(this.getCls())}this.detachListeners();if(a){a.destroy()}}},function(){});Ext.define("Ext.behavior.Draggable",{extend:"Ext.behavior.Behavior",requires:["Ext.util.Draggable"],constructor:function(){this.listeners={painted:"onComponentPainted",scope:this};this.callParent(arguments)},onComponentPainted:function(){this.draggable.refresh()},setConfig:function(c){var a=this.draggable,b=this.component;if(c){if(!a){b.setTranslatable(true);this.draggable=a=new Ext.util.Draggable(c);a.setTranslatable(b.getTranslatable());a.setElement(b.renderElement);a.on("destroy","onDraggableDestroy",this);if(b.isPainted()){this.onComponentPainted(b)}b.on(this.listeners)}else{if(Ext.isObject(c)){a.setConfig(c)}}}else{if(a){a.destroy()}}return this},getDraggable:function(){return this.draggable},onDraggableDestroy:function(){var a=this.component;delete this.draggable;a.un(this.listeners)},onComponentDestroy:function(){var a=this.draggable;if(a){a.destroy()}}});(function(a){Ext.define("Ext.Component",{extend:"Ext.AbstractComponent",alternateClassName:"Ext.lib.Component",mixins:["Ext.mixin.Traversable"],requires:["Ext.ComponentManager","Ext.XTemplate","Ext.dom.Element","Ext.behavior.Translatable","Ext.behavior.Draggable"],xtype:"component",observableType:"component",cachedConfig:{baseCls:null,cls:null,floatingCls:null,hiddenCls:a+"item-hidden",ui:null,margin:null,padding:null,border:null,styleHtmlCls:a+"html",styleHtmlContent:null},eventedConfig:{left:null,top:null,right:null,bottom:null,width:null,height:null,minWidth:null,minHeight:null,maxWidth:null,maxHeight:null,docked:null,centered:null,hidden:null,disabled:null},config:{style:null,html:null,draggable:null,translatable:null,renderTo:null,zIndex:null,tpl:null,enterAnimation:null,exitAnimation:null,showAnimation:null,hideAnimation:null,tplWriteMode:"overwrite",data:null,disabledCls:a+"item-disabled",contentEl:null,itemId:undefined,record:null,plugins:null},listenerOptionsRegex:/^(?:delegate|single|delay|buffer|args|prepend|element)$/,alignmentRegex:/^([a-z]+)-([a-z]+)(\?)?$/,isComponent:true,floating:false,rendered:false,dockPositions:{top:true,right:true,bottom:true,left:true},innerElement:null,element:null,template:[],constructor:function(c){var d=this,b=d.config,e;d.onInitializedListeners=[];d.initialConfig=c;if(c!==undefined&&"id" in c){e=c.id}else{if("id" in b){e=b.id}else{e=d.getId()}}d.id=e;d.setId(e);Ext.ComponentManager.register(d);d.initElement();d.initConfig(d.initialConfig);d.initialize();d.triggerInitialized();if(d.config.fullscreen){d.fireEvent("fullscreen",d)}d.fireEvent("initialize",d)},beforeInitConfig:function(b){this.beforeInitialize.apply(this,arguments)},beforeInitialize:Ext.emptyFn,initialize:Ext.emptyFn,getTemplate:function(){return this.template},getElementConfig:function(){return{reference:"element",children:this.getTemplate()}},triggerInitialized:function(){var c=this.onInitializedListeners,d=c.length,e,b;if(!this.initialized){this.initialized=true;if(d>0){for(b=0;b0){c.pressedTimeout=setTimeout(function(){delete c.pressedTimeout;if(a){a.addCls(b)}},d)}else{a.addCls(b)}}},onRelease:function(a){this.fireAction("release",[this,a],"doRelease")},doRelease:function(a,b){if(!a.getDisabled()){if(a.hasOwnProperty("pressedTimeout")){clearTimeout(a.pressedTimeout);delete a.pressedTimeout}else{a.element.removeCls(a.getPressedCls())}}},onTap:function(a){if(this.getDisabled()){return false}this.fireAction("tap",[this,a],"doTap")},doTap:function(c,d){var b=c.getHandler(),a=c.getScope()||c;if(!b){return}if(typeof b=="string"){b=a[b]}d.preventDefault();b.apply(a,arguments)}},function(){});Ext.define("Ext.Decorator",{extend:"Ext.Component",isDecorator:true,config:{component:{}},statics:{generateProxySetter:function(a){return function(c){var b=this.getComponent();b[a].call(b,c);return this}},generateProxyGetter:function(a){return function(){var b=this.getComponent();return b[a].call(b)}}},onClassExtended:function(c,e){if(!e.hasOwnProperty("proxyConfig")){return}var f=Ext.Class,i=e.proxyConfig,d=e.config;e.config=(d)?Ext.applyIf(d,i):i;var b,h,g,a;for(b in i){if(i.hasOwnProperty(b)){h=f.getConfigNameMap(b);g=h.set;a=h.get;e[g]=this.generateProxySetter(g);e[a]=this.generateProxyGetter(a)}}},applyComponent:function(a){return Ext.factory(a,Ext.Component)},updateComponent:function(a,b){if(b){if(this.isRendered()&&b.setRendered(false)){b.fireAction("renderedchange",[this,b,false],"doUnsetComponent",this,{args:[b]})}else{this.doUnsetComponent(b)}}if(a){if(this.isRendered()&&a.setRendered(true)){a.fireAction("renderedchange",[this,a,true],"doSetComponent",this,{args:[a]})}else{this.doSetComponent(a)}}},doUnsetComponent:function(a){if(a.renderElement.dom){this.innerElement.dom.removeChild(a.renderElement.dom)}},doSetComponent:function(a){if(a.renderElement.dom){this.innerElement.dom.appendChild(a.renderElement.dom)}},setRendered:function(b){var a;if(this.callParent(arguments)){a=this.getComponent();if(a){a.setRendered(b)}return true}return false},setDisabled:function(a){this.callParent(arguments);this.getComponent().setDisabled(a)},destroy:function(){Ext.destroy(this.getComponent());this.callParent()}});Ext.define("Ext.Img",{extend:"Ext.Component",xtype:["image","img"],config:{src:null,baseCls:Ext.baseCSSPrefix+"img",mode:"background"},beforeInitialize:function(){var a=this;a.onLoad=Ext.Function.bind(a.onLoad,a);a.onError=Ext.Function.bind(a.onError,a)},initialize:function(){var a=this;a.callParent();a.relayEvents(a.renderElement,"*");a.element.on({tap:"onTap",scope:a})},hide:function(){this.callParent();this.hiddenSrc=this.hiddenSrc||this.getSrc();this.setSrc(null)},show:function(){this.callParent();if(this.hiddenSrc){this.setSrc(this.hiddenSrc);delete this.hiddenSrc}},updateMode:function(a){if(a==="background"){if(this.imageElement){this.imageElement.destroy();delete this.imageElement;this.updateSrc(this.getSrc())}}else{this.imageElement=this.element.createChild({tag:"img"})}},onTap:function(a){this.fireEvent("tap",this,a)},onAfterRender:function(){this.updateSrc(this.getSrc())},updateSrc:function(a){var b=this,c;if(b.getMode()==="background"){c=this.imageObject||new Image()}else{c=b.imageElement.dom}this.imageObject=c;c.setAttribute("src",Ext.isString(a)?a:"");c.addEventListener("load",b.onLoad,false);c.addEventListener("error",b.onError,false)},detachListeners:function(){var a=this.imageObject;if(a){a.removeEventListener("load",this.onLoad,false);a.removeEventListener("error",this.onError,false)}},onLoad:function(a){this.detachListeners();if(this.getMode()==="background"){this.element.dom.style.backgroundImage='url("'+this.imageObject.src+'")'}this.fireEvent("load",this,a)},onError:function(a){this.detachListeners();this.fireEvent("error",this,a)},doSetWidth:function(b){var a=(this.getMode()==="background")?this.element:this.imageElement;a.setWidth(b);this.callParent(arguments)},doSetHeight:function(b){var a=(this.getMode()==="background")?this.element:this.imageElement;a.setHeight(b);this.callParent(arguments)},destroy:function(){this.detachListeners();Ext.destroy(this.imageObject);delete this.imageObject;this.callParent()}});Ext.define("Ext.Label",{extend:"Ext.Component",xtype:"label",config:{}});Ext.define("Ext.Map",{extend:"Ext.Component",xtype:"map",requires:["Ext.util.Geolocation"],isMap:true,config:{baseCls:Ext.baseCSSPrefix+"map",useCurrentLocation:false,map:null,geo:null,mapOptions:{}},constructor:function(){this.callParent(arguments);this.element.setVisibilityMode(Ext.Element.OFFSETS);if(!(window.google||{}).maps){this.setHtml("Google Maps API is required")}},initialize:function(){this.callParent();this.on({painted:"doResize",scope:this});this.element.on("touchstart","onTouchStart",this)},onTouchStart:function(a){a.makeUnpreventable()},applyMapOptions:function(a){return Ext.merge({},this.options,a)},updateMapOptions:function(d){var a=this,c=(window.google||{}).maps,b=this.getMap();if(c&&b){b.setOptions(d)}if(d.center&&!a.isPainted()){a.un("painted","setMapCenter",this);a.on("painted","setMapCenter",this,{delay:150,single:true,args:[d.center]})}},getMapOptions:function(){return Ext.merge({},this.options||this.getInitialConfig("mapOptions"))},updateUseCurrentLocation:function(a){this.setGeo(a);if(!a){this.renderMap()}},applyGeo:function(a){return Ext.factory(a,Ext.util.Geolocation,this.getGeo())},updateGeo:function(b,a){var c={locationupdate:"onGeoUpdate",locationerror:"onGeoError",scope:this};if(a){a.un(c)}if(b){b.on(c);b.updateLocation()}},doResize:function(){var b=(window.google||{}).maps,a=this.getMap();if(b&&a){b.event.trigger(a,"resize")}},renderMap:function(){var d=this,f=(window.google||{}).maps,b=d.element,a=d.getMapOptions(),e=d.getMap(),c;if(f){if(Ext.os.is.iPad){Ext.merge({navigationControlOptions:{style:f.NavigationControlStyle.ZOOM_PAN}},a)}a=Ext.merge({zoom:12,mapTypeId:f.MapTypeId.ROADMAP},a);if(!a.hasOwnProperty("center")){a.center=new f.LatLng(37.381592,-122.135672)}if(b.dom.firstChild){Ext.fly(b.dom.firstChild).destroy()}if(e){f.event.clearInstanceListeners(e)}d.setMap(new f.Map(b.dom,a));e=d.getMap();c=f.event;c.addListener(e,"zoom_changed",Ext.bind(d.onZoomChange,d));c.addListener(e,"maptypeid_changed",Ext.bind(d.onTypeChange,d));c.addListener(e,"center_changed",Ext.bind(d.onCenterChange,d));d.fireEvent("maprender",d,e)}},onGeoUpdate:function(a){if(a){this.setMapCenter(new google.maps.LatLng(a.getLatitude(),a.getLongitude()))}},onGeoError:Ext.emptyFn,setMapCenter:function(d){var a=this,c=a.getMap(),b=(window.google||{}).maps;if(b){if(!a.isPainted()){a.un("painted","setMapCenter",this);a.on("painted","setMapCenter",this,{delay:150,single:true,args:[d]});return}d=d||new b.LatLng(37.381592,-122.135672);if(d&&!(d instanceof b.LatLng)&&"longitude" in d){d=new b.LatLng(d.latitude,d.longitude)}if(!c){a.renderMap();c=a.getMap()}if(c&&d instanceof b.LatLng){c.panTo(d)}else{this.options=Ext.apply(this.getMapOptions(),{center:d})}}},onZoomChange:function(){var a=this.getMapOptions(),c=this.getMap(),b;b=(c&&c.getZoom)?c.getZoom():a.zoom||10;this.options=Ext.apply(a,{zoom:b});this.fireEvent("zoomchange",this,c,b)},onTypeChange:function(){var b=this.getMapOptions(),c=this.getMap(),a;a=(c&&c.getMapTypeId)?c.getMapTypeId():b.mapTypeId;this.options=Ext.apply(b,{mapTypeId:a});this.fireEvent("typechange",this,c,a)},onCenterChange:function(){var b=this.getMapOptions(),c=this.getMap(),a;a=(c&&c.getCenter)?c.getCenter():b.center;this.options=Ext.apply(b,{center:a});this.fireEvent("centerchange",this,c,a)},destroy:function(){Ext.destroy(this.getGeo());var a=this.getMap();if(a&&(window.google||{}).maps){google.maps.event.clearInstanceListeners(a)}this.callParent()}},function(){});Ext.define("Ext.Mask",{extend:"Ext.Component",xtype:"mask",config:{baseCls:Ext.baseCSSPrefix+"mask",transparent:false,top:0,left:0,right:0,bottom:0},initialize:function(){this.callParent();this.on({painted:"onPainted",erased:"onErased"})},onPainted:function(){this.element.on("*","onEvent",this)},onErased:function(){this.element.un("*","onEvent",this)},onEvent:function(b){var a=arguments[arguments.length-1];if(a.info.eventName==="tap"){this.fireEvent("tap",this,b);return false}if(b&&b.stopEvent){b.stopEvent()}return false},updateTransparent:function(a){this[a?"addCls":"removeCls"](this.getBaseCls()+"-transparent")}});Ext.define("Ext.LoadMask",{extend:"Ext.Mask",xtype:"loadmask",config:{message:"Loading...",messageCls:Ext.baseCSSPrefix+"mask-message",indicator:true,listeners:{painted:"onPainted",erased:"onErased"}},getTemplate:function(){var a=Ext.baseCSSPrefix;return[{reference:"innerElement",cls:a+"mask-inner",children:[{reference:"indicatorElement",cls:a+"loading-spinner-outer",children:[{cls:a+"loading-spinner",children:[{tag:"span",cls:a+"loading-top"},{tag:"span",cls:a+"loading-right"},{tag:"span",cls:a+"loading-bottom"},{tag:"span",cls:a+"loading-left"}]}]},{reference:"messageElement"}]}]},updateMessage:function(a){this.messageElement.setHtml(a)},updateMessageCls:function(b,a){this.messageElement.replaceCls(a,b)},updateIndicator:function(a){this[a?"removeCls":"addCls"](Ext.baseCSSPrefix+"indicator-hidden")},onPainted:function(){this.getParent().on({scope:this,resize:this.refreshPosition});this.refreshPosition()},onErased:function(){this.getParent().un({scope:this,resize:this.refreshPosition})},refreshPosition:function(){var c=this.getParent(),d=c.getScrollable(),a=(d)?d.getScroller():null,f=(a)?a.position:{x:0,y:0},e=c.element.getSize(),b=this.element.getSize();this.innerElement.setStyle({marginTop:Math.round(e.height-b.height+(f.y*2))+"px",marginLeft:Math.round(e.width-b.width+f.x)+"px"})}},function(){});Ext.define("Ext.Media",{extend:"Ext.Component",xtype:"media",config:{url:"",enableControls:Ext.os.is.Android?false:true,autoResume:false,autoPause:true,preload:true,loop:false,media:null,volume:1,muted:false},initialize:function(){var a=this;a.callParent();a.on({scope:a,activate:a.onActivate,deactivate:a.onDeactivate});a.addMediaListener({canplay:"onCanPlay",play:"onPlay",pause:"onPause",ended:"onEnd",volumechange:"onVolumeChange",timeupdate:"onTimeUpdate"})},addMediaListener:function(d,b){var c=this,e=c.media.dom,f=Ext.Function.bind;if(!Ext.isObject(d)){var a=d;d={};d[a]=b}Ext.Object.each(d,function(h,g){if(typeof g!=="function"){g=c[g]}if(typeof g=="function"){g=f(g,c);e.addEventListener(h,g)}})},onPlay:function(){this.fireEvent("play",this)},onCanPlay:function(){this.fireEvent("canplay",this)},onPause:function(){this.fireEvent("pause",this,this.getCurrentTime())},onEnd:function(){this.fireEvent("ended",this,this.getCurrentTime())},onVolumeChange:function(){this.fireEvent("volumechange",this,this.media.dom.volume)},onTimeUpdate:function(){this.fireEvent("timeupdate",this,this.getCurrentTime())},isPlaying:function(){return !Boolean(this.media.dom.paused)},onActivate:function(){var a=this;if(a.getAutoResume()&&!a.isPlaying()){a.play()}},onDeactivate:function(){var a=this;if(a.getAutoResume()&&a.isPlaying()){a.pause()}},updateUrl:function(a){var b=this.media.dom;b.src=a;if("load" in b){b.load()}if(this.isPlaying()){this.play()}},updateEnableControls:function(a){this.media.dom.controls=a?"controls":false},updateLoop:function(a){this.media.dom.loop=a?"loop":false},play:function(){var a=this.media.dom;if("play" in a){a.play();setTimeout(function(){a.play()},10)}},pause:function(){var a=this.media.dom;if("pause" in a){a.pause()}},toggle:function(){if(this.isPlaying()){this.pause()}else{this.play()}},stop:function(){var a=this;a.setCurrentTime(0);a.fireEvent("stop",a);a.pause()},updateVolume:function(a){this.media.dom.volume=a},updateMuted:function(a){this.fireEvent("mutedchange",this,a);this.media.dom.muted=a},getCurrentTime:function(){return this.media.dom.currentTime},setCurrentTime:function(a){this.media.dom.currentTime=a;return a},getDuration:function(){return this.media.dom.duration},destroy:function(){var a=this;Ext.Object.each(event,function(c,b){if(typeof b!=="function"){b=a[b]}if(typeof b=="function"){b=bind(b,a);dom.removeEventListener(c,b)}})}});Ext.define("Ext.Audio",{extend:"Ext.Media",xtype:"audio",config:{cls:Ext.baseCSSPrefix+"audio"},onActivate:function(){var a=this;a.callParent();if(Ext.os.is.Phone){a.element.show()}},onDeactivate:function(){var a=this;a.callParent();if(Ext.os.is.Phone){a.element.hide()}},template:[{reference:"media",preload:"auto",tag:"audio",cls:Ext.baseCSSPrefix+"component"}]});Ext.define("Ext.Spacer",{extend:"Ext.Component",alias:"widget.spacer",config:{},constructor:function(a){a=a||{};if(!a.width){a.flex=1}this.callParent([a])}});Ext.define("Ext.Title",{extend:"Ext.Component",xtype:"title",config:{baseCls:"x-title",title:""},updateTitle:function(a){this.setHtml(a)}});Ext.define("Ext.Video",{extend:"Ext.Media",xtype:"video",config:{posterUrl:null,cls:Ext.baseCSSPrefix+"video"},template:[{reference:"ghost",classList:[Ext.baseCSSPrefix+"video-ghost"]},{tag:"video",reference:"media",classList:[Ext.baseCSSPrefix+"media"]}],initialize:function(){var a=this;a.callParent();a.media.hide();a.onBefore({erased:"onErased",scope:a});a.ghost.on({tap:"onGhostTap",scope:a});a.media.on({pause:"onPause",scope:a});if(Ext.os.is.Android4||Ext.os.is.iPad){this.isInlineVideo=true}},applyUrl:function(a){return[].concat(a)},updateUrl:function(f){var c=this,e=c.media,g=f.length,d=e.query("source"),b=d.length,a;for(a=0;a0){a.pop().destroy()}},setActiveIndex:function(b){var e=this.indicators,d=this.activeIndex,a=e[d],f=e[b],c=this.getBaseCls();if(a){a.removeCls(c,null,"active")}if(f){f.addCls(c,null,"active")}this.activeIndex=b;return this},onTap:function(f){var g=f.touch,a=this.element.getPageBox(),d=a.left+(a.width/2),b=a.top+(a.height/2),c=this.getDirection();if((c==="horizontal"&&g.pageX>=d)||(c==="vertical"&&g.pageY>=b)){this.fireEvent("next",this)}else{this.fireEvent("previous",this)}},destroy:function(){var d=this.indicators,b,c,a;for(b=0,c=d.length;bd.bottom||a.yd.right||a.x div",scope:this})},initialize:function(){this.callParent();this.doInitialize()},updateBaseCls:function(a,b){var c=this;c.callParent([a+"-container",b])},onItemTouchStart:function(d){var b=this,c=d.getTarget(),a=b.getViewItems().indexOf(c);Ext.get(c).on({touchmove:"onItemTouchMove",scope:b,single:true});b.fireEvent("itemtouchstart",b,Ext.get(c),a,d)},onItemTouchEnd:function(d){var b=this,c=d.getTarget(),a=b.getViewItems().indexOf(c);Ext.get(c).un({touchmove:"onItemTouchMove",scope:b});b.fireEvent("itemtouchend",b,Ext.get(c),a,d)},onItemTouchMove:function(d){var b=this,c=d.getTarget(),a=b.getViewItems().indexOf(c);b.fireEvent("itemtouchmove",b,Ext.get(c),a,d)},onItemTap:function(d){var b=this,c=d.getTarget(),a=b.getViewItems().indexOf(c);b.fireEvent("itemtap",b,Ext.get(c),a,d)},onItemTapHold:function(d){var b=this,c=d.getTarget(),a=b.getViewItems().indexOf(c);b.fireEvent("itemtaphold",b,Ext.get(c),a,d)},onItemDoubleTap:function(d){var b=this,c=d.getTarget(),a=b.getViewItems().indexOf(c);b.fireEvent("itemdoubletap",b,Ext.get(c),a,d)},onItemSingleTap:function(d){var b=this,c=d.getTarget(),a=b.getViewItems().indexOf(c);b.fireEvent("itemsingletap",b,Ext.get(c),a,d)},onItemSwipe:function(d){var b=this,c=d.getTarget(),a=b.getViewItems().indexOf(c);b.fireEvent("itemswipe",b,Ext.get(c),a,d)},updateListItem:function(b,d){var c=this,a=c.dataview,e=a.prepareData(b.getData(true),a.getStore().indexOf(b),b);d.innerHTML=c.dataview.getItemTpl().apply(e)},addListItem:function(e,c){var h=this,d=h.dataview,a=d.prepareData(c.getData(true),d.getStore().indexOf(c),c),b=h.element,i=b.dom.childNodes,g=i.length,f;f=Ext.Element.create(this.getItemElementConfig(e,a));if(!g||e==g){f.appendTo(b)}else{f.insertBefore(i[e])}},getItemElementConfig:function(c,e){var b=this.dataview,d=b.getItemCls(),a=b.getBaseCls()+"-item";if(d){a+=" "+d}return{cls:a,html:b.getItemTpl().apply(e)}},doRemoveItemCls:function(a){var d=this.getViewItems(),c=d.length,b=0;for(;b=0;b--){c=a[f+b];c.parentNode.removeChild(c)}if(d.getViewItems().length==0){this.dataview.showEmptyText()}},moveItemsFromCache:function(d){var g=this,b=g.dataview,c=b.getStore(),f=d.length,e,a;if(f){b.hideEmptyText()}for(e=0;eh._tmpIndex?1:-1});for(e=0;e(?:[\s]*)|(?:\s*))([\w\-]+)$/i,handledEvents:["*"],getSubscribers:function(b,a){var d=this.subscribers,c=d[b];if(!c&&a){c=d[b]={type:{$length:0},selector:[],$length:0}}return c},subscribe:function(g,f){if(this.idSelectorRegex.test(g)){return false}var e=g.match(this.optimizedSelectorRegex),a=this.getSubscribers(f,true),k=a.type,c=a.selector,d,i,j,b,h;if(e!==null){d=e[1];i=e[2].indexOf(">")===-1;j=e[3];b=k[j];if(!b){k[j]=b={descendents:{$length:0},children:{$length:0},$length:0}}h=i?b.descendents:b.children;if(h.hasOwnProperty(d)){h[d]++;return true}h[d]=1;h.$length++;b.$length++;k.$length++}else{if(c.hasOwnProperty(g)){c[g]++;return true}c[g]=1;c.push(g)}a.$length++;return true},unsubscribe:function(g,f,k){var a=this.getSubscribers(f);if(!a){return false}var e=g.match(this.optimizedSelectorRegex),l=a.type,c=a.selector,d,i,j,b,h;k=Boolean(k);if(e!==null){d=e[1];i=e[2].indexOf(">")===-1;j=e[3];b=l[j];if(!b){return true}h=i?b.descendents:b.children;if(!h.hasOwnProperty(d)||(!k&&--h[d]>0)){return true}delete h[d];h.$length--;b.$length--;l.$length--}else{if(!c.hasOwnProperty(g)||(!k&&--c[g]>0)){return true}delete c[g];Ext.Array.remove(c,g)}if(--a.$length===0){delete this.subscribers[f]}return true},notify:function(d,a){var c=this.getSubscribers(a),e,b;if(!c||c.$length===0){return false}e=d.substr(1);b=Ext.ComponentManager.get(e);if(b){this.dispatcher.doAddListener(this.targetType,d,a,"publish",this,{args:[a,b]},"before")}},matchesSelector:function(b,a){return Ext.ComponentQuery.is(b,a)},dispatch:function(d,b,c,a){this.dispatcher.doDispatchEvent(this.targetType,d,b,c,null,a)},publish:function(g,k){var e=this.getSubscribers(g);if(!e){return}var p=arguments[arguments.length-1],o=e.type,b=e.selector,d=Array.prototype.slice.call(arguments,2,-2),l=k.xtypesChain,s,n,t,a,m,v,r,u,h,f,q,c;for(u=0,h=l.length;u0){s=e.descendents;if(s.$length>0){if(!a){a=k.getAncestorIds()}for(q=0,c=a.length;q0){if(!t){if(a){t=a[0]}else{v=k.getParent();if(v){t=v.getId()}}}if(t){if(n.hasOwnProperty(t)){this.dispatch("#"+t+" > "+f,g,d,p)}}}}}h=b.length;if(h>0){for(u=0;uf){d=e}}c.setValue(d);d=c.getValue();c.fireEvent("spin",c,d,g);c.fireEvent("spin"+g,c,d)},doSetDisabled:function(a){Ext.Component.prototype.doSetDisabled.apply(this,arguments)},setDisabled:function(){Ext.Component.prototype.setDisabled.apply(this,arguments)},reset:function(){this.setValue(this.getDefaultValue())},destroy:function(){var a=this;Ext.destroy(a.downRepeater,a.upRepeater,a.spinDownButton,a.spinUpButton);a.callParent(arguments)}},function(){});Ext.define("Ext.field.TextAreaInput",{extend:"Ext.field.Input",xtype:"textareainput",tag:"textarea"});Ext.define("Ext.field.TextArea",{extend:"Ext.field.Text",xtype:"textareafield",requires:["Ext.field.TextAreaInput"],alternateClassName:"Ext.form.TextArea",config:{ui:"textarea",autoCapitalize:false,component:{xtype:"textareainput"},maxRows:null},updateMaxRows:function(a){this.getComponent().setMaxRows(a)},doSetHeight:function(a){this.callParent(arguments);var b=this.getComponent();b.input.setHeight(a)},doSetWidth:function(b){this.callParent(arguments);var a=this.getComponent();a.input.setWidth(b)},doKeyUp:function(a){var b=a.getValue();a[b?"showClearIcon":"hideClearIcon"]()}});Ext.define("Ext.field.Url",{extend:"Ext.field.Text",xtype:"urlfield",alternateClassName:"Ext.form.Url",config:{autoCapitalize:false,component:{type:"url"}}});Ext.define("Ext.plugin.ListPaging",{extend:"Ext.Component",alias:"plugin.listpaging",config:{autoPaging:false,loadMoreText:"Load More...",noMoreRecordsText:"No More Records",loadTpl:['
','','','','',"
",'
{message}
'].join(""),loadMoreCmp:{xtype:"component",baseCls:Ext.baseCSSPrefix+"list-paging"},loadMoreCmpAdded:false,loadingCls:Ext.baseCSSPrefix+"loading",list:null,scroller:null,loading:false},init:function(c){var a=c.getScrollable().getScroller(),b=c.getStore();this.setList(c);this.setScroller(a);this.bindStore(c.getStore());if(b){this.disableDataViewMask(b)}c.updateStore=Ext.Function.createInterceptor(c.updateStore,this.bindStore,this);if(this.getAutoPaging()){a.on({scrollend:this.onScrollEnd,scope:this})}},bindStore:function(a,b){if(b){b.un({load:this.onStoreLoad,beforeload:this.onStoreBeforeLoad,scope:this})}if(a){a.on({load:this.onStoreLoad,beforeload:this.onStoreBeforeLoad,scope:this})}},disableDataViewMask:function(a){var b=this.getList();if(a.isAutoLoading()){b.setLoadingText(null)}else{a.on({load:{single:true,fn:function(){b.setLoadingText(null)}}})}},applyLoadTpl:function(a){return(Ext.isObject(a)&&a.isTemplate)?a:new Ext.XTemplate(a)},applyLoadMoreCmp:function(a){a=Ext.merge(a,{html:this.getLoadTpl().apply({cssPrefix:Ext.baseCSSPrefix,message:this.getLoadMoreText()}),listeners:{tap:{fn:this.loadNextPage,scope:this,element:"element"}}});return Ext.factory(a,Ext.Component,this.getLoadMoreCmp())},onScrollEnd:function(b,a,c){if(!this.getLoading()&&c>=b.maxPosition.y){this.loadNextPage()}},updateLoading:function(a){var b=this.getLoadMoreCmp(),c=this.getLoadingCls();if(a){b.addCls(c)}else{b.removeCls(c)}},onStoreBeforeLoad:function(a){if(a.getCount()===0){this.getLoadMoreCmp().hide()}},onStoreLoad:function(a){var d=this.addLoadMoreCmp(),b=this.getLoadTpl(),c=this.storeFullyLoaded()?this.getNoMoreRecordsText():this.getLoadMoreText();this.getLoadMoreCmp().show();this.setLoading(false);if(this.scrollY){this.getScroller().scrollTo(null,this.scrollY);delete this.scrollY}d.setHtml(b.apply({cssPrefix:Ext.baseCSSPrefix,message:c}))},addLoadMoreCmp:function(){var b=this.getList(),a=this.getLoadMoreCmp();if(!this.getLoadMoreCmpAdded()){b.add(a);b.fireEvent("loadmorecmpadded",this,b);this.setLoadMoreCmpAdded(true)}return a},storeFullyLoaded:function(){var a=this.getList().getStore(),b=a.getTotalCount();return b!==null?a.getTotalCount()<=(a.currentPage*a.getPageSize()):false},loadNextPage:function(){var a=this;if(!a.storeFullyLoaded()){a.setLoading(true);a.scrollY=a.getScroller().position.y;a.getList().getStore().nextPage({addRecords:true})}}});Ext.define("Ext.plugin.PullRefresh",{extend:"Ext.Component",alias:"plugin.pullrefresh",requires:["Ext.DateExtras"],config:{list:null,pullRefreshText:"Pull down to refresh...",releaseRefreshText:"Release to refresh...",loadingText:"Loading...",snappingAnimationDuration:150,refreshFn:null,pullTpl:['
','
','
','','','','',"
",'
','

{message}

','
Last Updated: {lastUpdated:date("m/d/Y h:iA")}
',"
","
"].join("")},isRefreshing:false,currentViewState:"",initialize:function(){this.callParent();this.on({painted:"onPainted",scope:this})},init:function(f){var d=this,b=f.getStore(),e=d.getPullTpl(),c=d.element,a=f.getScrollable().getScroller();d.setList(f);d.lastUpdated=new Date();f.insert(0,d);if(b){if(b.isAutoLoading()){f.setLoadingText(null)}else{b.on({load:{single:true,fn:function(){f.setLoadingText(null)}}})}}e.overwrite(c,{message:d.getPullRefreshText(),lastUpdated:d.lastUpdated},true);d.loadingElement=c.getFirstChild();d.messageEl=c.down(".x-list-pullrefresh-message");d.updatedEl=c.down(".x-list-pullrefresh-updated > span");d.maxScroller=a.getMaxPosition();a.on({maxpositionchange:d.setMaxScroller,scroll:d.onScrollChange,scope:d})},fetchLatest:function(){var b=this.getList().getStore(),c=b.getProxy(),a;a=Ext.create("Ext.data.Operation",{page:1,start:0,model:b.getModel(),limit:b.getPageSize(),action:"read",filters:b.getRemoteFilter()?b.getFilters():[]});c.read(a,this.onLatestFetched,this)},onLatestFetched:function(d){var j=this.getList().getStore(),b=j.getData(),c=d.getRecords(),a=c.length,g=[],h,f,e;for(e=0;ethis.maxScroller.y){this.onBounceBottom(c)}},applyPullTpl:function(a){return(Ext.isObject(a)&&a.isTemplate)?a:new Ext.XTemplate(a)},onBounceTop:function(d){var b=this,c=b.getList(),a=c.getScrollable().getScroller();if(!b.isReleased){if(!b.isRefreshing&&-d>=b.pullHeight+10){b.isRefreshing=true;b.setViewState("release");a.getContainer().onBefore({dragend:"onScrollerDragEnd",single:true,scope:b})}else{if(b.isRefreshing&&-d=1){a=Math.round((f-1)*b);e=this.applyLength(d-a);a=d-e;this.updateLength(e);g=c+a}else{g=c*f}}this.setOffset(g)},setOffset:function(c){var a=this.getAxis(),b=this.element.dom.style;c=Math.round(c);if(a==="x"){b.webkitTransform="translate3d("+c+"px, 0, 0)"}else{b.webkitTransform="translate3d(0, "+c+"px, 0)"}}});Ext.define("Ext.scroll.indicator.Default",{extend:"Ext.scroll.indicator.Abstract",config:{cls:"default"},setOffset:function(c){var b=this.getAxis(),a=this.element.dom.style;if(b==="x"){a.webkitTransform="translate3d("+c+"px, 0, 0)"}else{a.webkitTransform="translate3d(0, "+c+"px, 0)"}},applyLength:function(a){return Math.round(Math.max(0,a))},updateValue:function(f){var b=this.barLength,c=this.gapLength,d=this.getLength(),e,g,a;if(f<=0){g=0;this.updateLength(this.applyLength(d+f*b))}else{if(f>=1){a=Math.round((f-1)*b);e=this.applyLength(d-a);a=d-e;this.updateLength(e);g=c+a}else{g=c*f}}this.setOffset(g)}});Ext.define("Ext.scroll.indicator.ScrollPosition",{extend:"Ext.scroll.indicator.Abstract",config:{cls:"scrollposition"},getElementConfig:function(){var a=this.callParent(arguments);a.children.unshift({className:"x-scroll-bar-stretcher"});return a},updateValue:function(a){if(this.gapLength===0){if(a>1){a=a-1}this.setOffset(this.barLength*a)}else{this.setOffset(this.gapLength*a)}},setLength:function(e){var c=this.getAxis(),a=this.barLength,d=this.barElement.dom,b=this.element;this.callParent(arguments);if(c==="x"){d.scrollLeft=a;b.setLeft(a)}else{d.scrollTop=a;b.setTop(a)}},setOffset:function(d){var b=this.getAxis(),a=this.barLength,c=this.barElement.dom;d=a-d;if(b==="x"){c.scrollLeft=d}else{c.scrollTop=d}}});Ext.define("Ext.scroll.Indicator",{requires:["Ext.scroll.indicator.Default","Ext.scroll.indicator.ScrollPosition","Ext.scroll.indicator.CssTransform"],alternateClassName:"Ext.util.Indicator",constructor:function(a){if(Ext.os.is.Android2||Ext.browser.is.ChromeMobile){return new Ext.scroll.indicator.ScrollPosition(a)}else{if(Ext.os.is.iOS){return new Ext.scroll.indicator.CssTransform(a)}else{return new Ext.scroll.indicator.Default(a)}}}});Ext.define("Ext.scroll.View",{extend:"Ext.Evented",alternateClassName:"Ext.util.ScrollView",requires:["Ext.scroll.Scroller","Ext.scroll.Indicator"],config:{indicatorsUi:"dark",element:null,scroller:{},indicators:{x:{axis:"x"},y:{axis:"y"}},indicatorsHidingDelay:100,cls:Ext.baseCSSPrefix+"scroll-view"},processConfig:function(c){if(!c){return null}if(typeof c=="string"){c={direction:c}}c=Ext.merge({},c);var a=c.scroller,b;if(!a){c.scroller=a={}}for(b in c){if(c.hasOwnProperty(b)){if(!this.hasConfig(b)){a[b]=c[b];delete c[b]}}}return c},constructor:function(a){a=this.processConfig(a);this.useIndicators={x:true,y:true};this.doHideIndicators=Ext.Function.bind(this.doHideIndicators,this);this.initConfig(a)},setConfig:function(a){return this.callParent([this.processConfig(a)])},updateIndicatorsUi:function(a){var b=this.getIndicators();b.x.setUi(a);b.y.setUi(a)},applyScroller:function(a,b){return Ext.factory(a,Ext.scroll.Scroller,b)},applyIndicators:function(b,d){var a=Ext.scroll.Indicator,c=this.useIndicators;if(!b){b={}}if(!b.x){c.x=false;b.x={}}if(!b.y){c.y=false;b.y={}}return{x:Ext.factory(b.x,a,d&&d.x),y:Ext.factory(b.y,a,d&&d.y)}},updateIndicators:function(a){this.indicatorsGrid=Ext.Element.create({className:"x-scroll-bar-grid-wrapper",children:[{className:"x-scroll-bar-grid",children:[{children:[{},{children:[a.y.barElement]}]},{children:[{children:[a.x.barElement]},{}]}]}]})},updateScroller:function(a){a.on({scope:this,scrollstart:"onScrollStart",scroll:"onScroll",scrollend:"onScrollEnd",refresh:"refreshIndicators"})},isAxisEnabled:function(a){return this.getScroller().isAxisEnabled(a)&&this.useIndicators[a]},applyElement:function(a){if(a){return Ext.get(a)}},updateElement:function(c){var b=c.getFirstChild().getFirstChild(),a=this.getScroller();c.addCls(this.getCls());c.insertFirst(this.indicatorsGrid);a.setElement(b);this.refreshIndicators();return this},showIndicators:function(){var a=this.getIndicators();if(this.hasOwnProperty("indicatorsHidingTimer")){clearTimeout(this.indicatorsHidingTimer);delete this.indicatorsHidingTimer}if(this.isAxisEnabled("x")){a.x.show()}if(this.isAxisEnabled("y")){a.y.show()}},hideIndicators:function(){var a=this.getIndicatorsHidingDelay();if(a>0){this.indicatorsHidingTimer=setTimeout(this.doHideIndicators,a)}else{this.doHideIndicators()}},doHideIndicators:function(){var a=this.getIndicators();if(this.isAxisEnabled("x")){a.x.hide()}if(this.isAxisEnabled("y")){a.y.hide()}},onScrollStart:function(){this.onScroll.apply(this,arguments);this.showIndicators()},onScrollEnd:function(){this.hideIndicators()},onScroll:function(b,a,c){this.setIndicatorValue("x",a);this.setIndicatorValue("y",c)},setIndicatorValue:function(b,f){if(!this.isAxisEnabled(b)){return this}var a=this.getScroller(),c=a.getMaxPosition()[b],e=a.getContainerSize()[b],d;if(c===0){d=f/e;if(f>=0){d+=1}}else{if(f>c){d=1+((f-c)/e)}else{if(f<0){d=f/e}else{d=f/c}}}this.getIndicators()[b].setValue(d)},refreshIndicator:function(d){if(!this.isAxisEnabled(d)){return this}var a=this.getScroller(),b=this.getIndicators()[d],e=a.getContainerSize()[d],f=a.getSize()[d],c=e/f;b.setRatio(c);b.refresh()},refresh:function(){return this.getScroller().refresh()},refreshIndicators:function(){var a=this.getIndicators();a.x.setActive(this.isAxisEnabled("x"));a.y.setActive(this.isAxisEnabled("y"));this.refreshIndicator("x");this.refreshIndicator("y")},destroy:function(){var a=this.getElement(),b=this.getIndicators();if(a&&!a.isDestroyed){a.removeCls(this.getCls())}b.x.destroy();b.y.destroy();Ext.destroy(this.getScroller(),this.indicatorsGrid);delete this.indicatorsGrid;this.callParent(arguments)}});Ext.define("Ext.behavior.Scrollable",{extend:"Ext.behavior.Behavior",requires:["Ext.scroll.View"],constructor:function(){this.listeners={painted:"onComponentPainted",scope:this};this.callParent(arguments)},onComponentPainted:function(){this.scrollView.refresh()},setConfig:function(d){var b=this.scrollView,c=this.component,e,a;if(d){if(!b){this.scrollView=b=new Ext.scroll.View(d);b.on("destroy","onScrollViewDestroy",this);c.setUseBodyElement(true);this.scrollerElement=a=c.innerElement;this.scrollContainer=a.wrap();this.scrollViewElement=e=c.bodyElement;b.setElement(e);if(c.isPainted()){this.onComponentPainted(c)}c.on(this.listeners)}else{if(Ext.isString(d)||Ext.isObject(d)){b.setConfig(d)}}}else{if(b){b.destroy()}}return this},getScrollView:function(){return this.scrollView},onScrollViewDestroy:function(){var b=this.component,a=this.scrollerElement;if(!a.isDestroyed){this.scrollerElement.unwrap()}this.scrollContainer.destroy();b.un(this.listeners);delete this.scrollerElement;delete this.scrollView;delete this.scrollContainer},onComponentDestroy:function(){var a=this.scrollView;if(a){a.destroy()}}});Ext.define("Ext.Container",{extend:"Ext.Component",alternateClassName:"Ext.lib.Container",requires:["Ext.layout.Layout","Ext.ItemCollection","Ext.behavior.Scrollable","Ext.Mask"],xtype:"container",eventedConfig:{activeItem:0},config:{layout:null,control:{},defaults:null,items:null,autoDestroy:true,defaultType:null,scrollable:null,useBodyElement:null,masked:null,modal:null,hideOnMaskTap:null},isContainer:true,delegateListeners:{delegate:"> component",centeredchange:"onItemCenteredChange",dockedchange:"onItemDockedChange",floatingchange:"onItemFloatingChange"},constructor:function(a){var b=this;b._items=b.items=new Ext.ItemCollection();b.innerItems=[];b.onItemAdd=b.onFirstItemAdd;b.callParent(arguments)},getElementConfig:function(){return{reference:"element",className:"x-container",children:[{reference:"innerElement",className:"x-inner"}]}},applyMasked:function(a,b){b=Ext.factory(a,Ext.Mask,b);if(b){this.add(b)}return b},mask:function(a){this.setMasked(a||true)},unmask:function(){this.setMasked(false)},applyModal:function(a,b){if(!a&&!b){return}return Ext.factory(a,Ext.Mask,b)},updateModal:function(c,a){var b={painted:"refreshModalMask",erased:"destroyModalMask"};if(c){this.on(b);c.on("destroy","onModalDestroy",this);if(this.getTop()===null&&this.getBottom()===null&&this.getRight()===null&&this.getLeft()===null&&!this.getCentered()){this.setTop(0);this.setLeft(0)}if(this.isPainted()){this.refreshModalMask()}}else{if(a){a.un("destroy","onModalDestroy",this);this.un(b)}}},onModalDestroy:function(){this.setModal(null)},refreshModalMask:function(){var b=this.getModal(),a=this.getParent();if(!this.painted){this.painted=true;if(b){a.insertBefore(b,this);b.setZIndex(this.getZIndex()-1);if(this.getHideOnMaskTap()){b.on("tap","hide",this,{single:true})}}}},destroyModalMask:function(){var b=this.getModal(),a=this.getParent();if(this.painted){this.painted=false;if(b){b.un("tap","hide",this);a.remove(b,false)}}},updateZIndex:function(b){var a=this.getModal();this.callParent(arguments);if(a){a.setZIndex(b-1)}},updateBaseCls:function(a,b){var c=this,d=c.getUi();if(a){this.element.addCls(a);this.innerElement.addCls(a,null,"inner");if(d){this.element.addCls(a,null,d)}}if(b){this.element.removeCls(b);this.innerElement.removeCls(a,null,"inner");if(d){this.element.removeCls(b,null,d)}}},updateUseBodyElement:function(a){if(a){this.bodyElement=this.innerElement.wrap({cls:"x-body"});this.referenceList.push("bodyElement")}},applyItems:function(a,b){if(a){this.getDefaultType();this.getDefaults();if(this.initialized&&b.length>0){this.removeAll()}this.add(a)}},applyControl:function(c){var a,b,e,d;for(a in c){d=c[a];for(b in d){e=d[b];if(Ext.isObject(e)){e.delegate=a}}d.delegate=a;this.addListener(d)}return c},onFirstItemAdd:function(){delete this.onItemAdd;this.setLayout(new Ext.layout.Layout(this,this.getLayout()||"default"));if(this.innerHtmlElement&&!this.getHtml()){this.innerHtmlElement.destroy();delete this.innerHtmlElement}this.on(this.delegateListeners);return this.onItemAdd.apply(this,arguments)},updateDefaultType:function(a){this.defaultItemClass=Ext.ClassManager.getByAlias("widget."+a)},applyDefaults:function(a){if(a){this.factoryItem=this.factoryItemWithDefaults;return a}},factoryItem:function(a){return Ext.factory(a,this.defaultItemClass)},factoryItemWithDefaults:function(c){var b=this,d=b.getDefaults(),a;if(!d){return Ext.factory(c,b.defaultItemClass)}if(c.isComponent){a=c;if(d&&c.isInnerItem()&&!b.has(a)){a.setConfig(d,true)}}else{if(d&&!c.ignoreDefaults){if(!(c.hasOwnProperty("left")&&c.hasOwnProperty("right")&&c.hasOwnProperty("top")&&c.hasOwnProperty("bottom")&&c.hasOwnProperty("docked")&&c.hasOwnProperty("centered"))){c=Ext.mergeIf({},c,d)}}a=Ext.factory(c,b.defaultItemClass)}return a},add:function(a){var e=this,b,d,c,f;a=Ext.Array.from(a);d=a.length;for(b=0;b0&&c.isInnerItem()){f=c}}if(f){this.setActiveItem(f)}return c},doAdd:function(d){var c=this,a=c.getItems(),b;if(!a.has(d)){b=a.length;a.add(d);if(d.isInnerItem()){c.insertInner(d)}d.setParent(c);c.onItemAdd(d,b)}},remove:function(d,b){var c=this,a=c.indexOf(d),e=c.getInnerItems();if(b===undefined){b=c.getAutoDestroy()}if(a!==-1){if(!c.removingAll&&e.length>1&&d===c.getActiveItem()){c.on({activeitemchange:"doRemove",scope:c,single:true,order:"after",args:[d,a,b]});c.doResetActiveItem(e.indexOf(d))}else{c.doRemove(d,a,b);if(e.length===0){c.setActiveItem(null)}}}return c},doResetActiveItem:function(a){if(a===0){this.setActiveItem(1)}else{this.setActiveItem(0)}},doRemove:function(d,a,b){var c=this;c.items.remove(d);if(d.isInnerItem()){c.removeInner(d)}c.onItemRemove(d,a,b);d.setParent(null);if(b){d.destroy()}},removeAll:function(c,f){var a=this.items,e=a.length,b=0,d;if(c===undefined){c=this.getAutoDestroy()}f=Boolean(f);this.removingAll=true;for(;b=0;b--){c.insert(a,d[b])}return c}d=this.factoryItem(d);this.doInsert(a,d);return d},doInsert:function(d,f){var e=this,b=e.items,c=b.length,a,g;g=f.isInnerItem();if(d>c){d=c}if(b[d-1]===f){return e}a=e.indexOf(f);if(a!==-1){if(a "+a)[0]||null},down:function(a){return this.query(a)[0]||null},destroy:function(){var a=this.getModal();if(a){a.destroy()}this.removeAll(true,true);Ext.destroy(this.getScrollable(),this.bodyElement);this.callParent()}},function(){this.addMember("defaultItemClass",this)});Ext.define("Ext.Panel",{extend:"Ext.Container",requires:["Ext.util.LineSegment"],alternateClassName:"Ext.lib.Panel",xtype:"panel",isPanel:true,config:{baseCls:Ext.baseCSSPrefix+"panel",bodyPadding:null,bodyMargin:null,bodyBorder:null},getElementConfig:function(){var a=this.callParent();a.children.push({reference:"tipElement",className:"x-anchor",hidden:true});return a},applyBodyPadding:function(a){if(a===true){a=5}if(a){a=Ext.dom.Element.unitizeBox(a)}return a},updateBodyPadding:function(a){this.element.setStyle("padding",a)},applyBodyMargin:function(a){if(a===true){a=5}if(a){a=Ext.dom.Element.unitizeBox(a)}return a},updateBodyMargin:function(a){this.element.setStyle("margin",a)},applyBodyBorder:function(a){if(a===true){a=1}if(a){a=Ext.dom.Element.unitizeBox(a)}return a},updateBodyBorder:function(a){this.element.setStyle("border-width",a)},alignTo:function(m){var w=this.tipElement;w.hide();if(this.currentTipPosition){w.removeCls("x-anchor-"+this.currentTipPosition)}this.callParent(arguments);var f=Ext.util.LineSegment,d=m.isComponent?m.renderElement:m,a=this.renderElement,n=d.getPageBox(),k=a.getPageBox(),b=k.left,t=k.top,C=k.right,h=k.bottom,j=b+(k.width/2),i=t+(k.height/2),o={x:b,y:t},l={x:C,y:t},B={x:b,y:h},D={x:C,y:h},y={x:j,y:i},s=n.left+(n.width/2),q=n.top+(n.height/2),v={x:s,y:q},c=new f(y,v),g=0,A=0,e,z,r,p,x,u;w.setVisibility(false);w.show();e=w.getSize();z=e.width;r=e.height;if(c.intersects(new f(o,l))){x=Math.min(Math.max(s,b),C-(z/2));u=t;A=r+10;p="top"}else{if(c.intersects(new f(o,B))){x=b;u=Math.min(Math.max(q+(z/2),t),h);g=r+10;p="left"}else{if(c.intersects(new f(B,D))){x=Math.min(Math.max(s,b),C-(z/2));u=h;A=-r-10;p="bottom"}else{if(c.intersects(new f(l,D))){x=C;u=Math.min(Math.max(q-(z/2),t),h);g=-r-10;p="right"}}}}if(x||u){this.currentTipPosition=p;w.addCls("x-anchor-"+p);w.setLeft(x-b);w.setTop(u-t);w.setVisibility(true);this.setLeft(this.getLeft()+g);this.setTop(this.getTop()+A)}}});Ext.define("Ext.SegmentedButton",{extend:"Ext.Container",xtype:"segmentedbutton",requires:["Ext.Button"],config:{baseCls:Ext.baseCSSPrefix+"segmentedbutton",pressedCls:Ext.baseCSSPrefix+"button-pressed",allowMultiple:false,allowDepress:null,pressedButtons:[],layout:{type:"hbox",align:"stretch"},defaultType:"button"},initialize:function(){var a=this;a.callParent();a.on({delegate:"> button",scope:a,tap:"onButtonRelease"});a.onAfter({delegate:"> button",scope:a,hiddenchange:"onButtonHiddenChange"})},updateAllowMultiple:function(){if(!this.initialized&&!this.getInitialConfig().hasOwnProperty("allowDepress")){this.setAllowDepress(true)}},applyItems:function(){var e=this,f=[],d,b,c,a;e.callParent(arguments);a=this.getItems();d=a.length;for(b=0;b=0;b--){c=a.items[b];if(!c.isHidden()){c.addCls(e+"last");break}}},applyPressedButtons:function(a){var e=this,f=[],c,d,b;if(Ext.isArray(a)){d=a.length;for(b=0;bm){c.renderElement.setWidth(m)}}var j=this.spacer.renderElement.getPageBox(),k=f.getPageBox(),g=k.width-j.width,d=k.left,i=k.right,b,l,e;if(g>0){f.setWidth(j.width);b=g/2;d+=b;i-=b}l=j.left-d;e=i-j.right;if(l>0){f.setLeft(l)}else{if(e>0){f.setLeft(-e)}}f.repaint()},updateTitle:function(a){this.titleComponent.setTitle(a);if(this.isPainted()){this.refreshTitlePosition()}}});Ext.define("Ext.Toolbar",{extend:"Ext.Container",xtype:"toolbar",requires:["Ext.Button","Ext.Title","Ext.Spacer"],isToolbar:true,config:{baseCls:Ext.baseCSSPrefix+"toolbar",ui:"dark",title:null,defaultType:"button",layout:{type:"hbox",align:"center"}},constructor:function(a){a=a||{};if(a.docked=="left"||a.docked=="right"){a.layout={type:"vbox",align:"stretch"}}this.callParent([a])},applyTitle:function(a){if(typeof a=="string"){a={title:a,centered:true}}return Ext.factory(a,Ext.Title,this.getTitle())},updateTitle:function(b,a){if(b){this.add(b);this.getLayout().setItemFlex(b,1)}if(a){a.destroy()}},showTitle:function(){var a=this.getTitle();if(a){a.show()}},hideTitle:function(){var a=this.getTitle();if(a){a.hide()}}},function(){});Ext.define("Ext.MessageBox",{extend:"Ext.Sheet",requires:["Ext.Toolbar","Ext.field.Text","Ext.field.TextArea"],config:{ui:"dark",baseCls:Ext.baseCSSPrefix+"msgbox",iconCls:null,showAnimation:{type:"popIn",duration:250,easing:"ease-out"},hideAnimation:{type:"popOut",duration:250,easing:"ease-out"},zIndex:10,defaultTextHeight:75,title:null,buttons:null,message:null,prompt:null,layout:{type:"vbox",pack:"center"}},statics:{OK:{text:"OK",itemId:"ok",ui:"action"},YES:{text:"Yes",itemId:"yes",ui:"action"},NO:{text:"No",itemId:"no"},CANCEL:{text:"Cancel",itemId:"cancel"},INFO:Ext.baseCSSPrefix+"msgbox-info",WARNING:Ext.baseCSSPrefix+"msgbox-warning",QUESTION:Ext.baseCSSPrefix+"msgbox-question",ERROR:Ext.baseCSSPrefix+"msgbox-error",OKCANCEL:[{text:"Cancel",itemId:"cancel"},{text:"OK",itemId:"ok",ui:"action"}],YESNOCANCEL:[{text:"Cancel",itemId:"cancel"},{text:"No",itemId:"no"},{text:"Yes",itemId:"yes",ui:"action"}],YESNO:[{text:"No",itemId:"no"},{text:"Yes",itemId:"yes",ui:"action"}]},constructor:function(a){a=a||{};if(a.hasOwnProperty("promptConfig")){Ext.applyIf(a,{prompt:a.promptConfig});delete a.promptConfig}if(a.hasOwnProperty("multiline")||a.hasOwnProperty("multiLine")){a.prompt=a.prompt||{};Ext.applyIf(a.prompt,{multiLine:a.multiline||a.multiLine});delete a.multiline;delete a.multiLine}this.defaultAllowedConfig={};var e=["ui","showAnimation","hideAnimation","title","message","prompt","iconCls","buttons","defaultTextHeight"],d=e.length,b,c;for(b=0;b=a-c&&b<=a+c)},onDragStart:function(f){var d=this.getDirection(),b=f.absDeltaX,a=f.absDeltaY,c=this.getDirectionLock();this.isDragging=true;if(c){if((d==="horizontal"&&b>a)||(d==="vertical"&&a>b)){f.stopPropagation()}else{this.isDragging=false;return}}if(this.isAnimating){this.getActiveCarouselItem().getTranslatable().stopAnimation()}this.dragStartOffset=this.offset;this.dragDirection=0},onDrag:function(j){if(!this.isDragging){return}var k=this.dragStartOffset,l=this.getDirection(),m=l==="horizontal"?j.deltaX:j.deltaY,a=this.offset,i=this.flickStartTime,c=this.dragDirection,b=Ext.Date.now(),h=this.getActiveIndex(),f=this.getMaxItemIndex(),d=c,g;if((h===0&&m>0)||(h===f&&m<0)){m*=0.5}g=k+m;if(g>a){c=1}else{if(g300){this.flickStartOffset=a;this.flickStartTime=b}this.dragDirection=c;this.setOffset(g)},onDragEnd:function(j){if(!this.isDragging){return}this.onDrag(j);this.isDragging=false;var a=Ext.Date.now(),i=this.itemLength,g=i/2,f=this.offset,m=this.getActiveIndex(),c=this.getMaxItemIndex(),h=0,l=f-this.flickStartOffset,b=a-this.flickStartTime,k=this.getIndicator(),d;if(b>0&&Math.abs(l)>=10){d=l/b;if(Math.abs(d)>=1){if(d<0&&m0&&m>0){h=1}}}}if(h===0){if(m0&&f>g){h=1}}}if(k){k.setActiveIndex(m-h)}this.animationDirection=h;this.setOffsetAnimated(h*i)},applyAnimation:function(a){a.easing=Ext.factory(a.easing,Ext.fx.easing.EaseOut);return a},updateDirection:function(b){var a=this.getIndicator();this.currentAxis=(b==="horizontal")?"x":"y";if(a){a.setDirection(b)}},setOffset:function(e){var k=this.orderedCarouselItems,c=this.getBufferSize(),g=k[c],j=this.itemLength,d=this.currentAxis,a,h,b,f;this.offset=e;e+=this.itemOffset;if(g){g.translateAxis(d,e);for(f=1,b=0;f<=c;f++){h=k[c-f];if(h){b+=j;h.translateAxis(d,e-b)}}for(f=1,b=0;f<=c;f++){a=k[c+f];if(a){b+=j;a.translateAxis(d,e+b)}}}return this},setOffsetAnimated:function(c){var b=this.orderedCarouselItems[this.getBufferSize()],a=this.getIndicator();if(a){a.setActiveIndex(this.getActiveIndex()-this.animationDirection)}this.offset=c;c+=this.itemOffset;if(b){this.isAnimating=true;b.getTranslatable().on(this.animationListeners);b.translateAxis(this.currentAxis,c,this.getAnimation())}return this},onActiveItemAnimationFrame:function(k){var j=this.orderedCarouselItems,c=this.getBufferSize(),h=this.itemLength,d=this.currentAxis,e=k[d],g,a,f,b;for(f=1,b=0;f<=c;f++){g=j[c-f];if(g){b+=h;g.translateAxis(d,e-b)}}for(f=1,b=0;f<=c;f++){a=j[c+f];if(a){b+=h;a.translateAxis(d,e+b)}}},onActiveItemAnimationEnd:function(b){var c=this.getActiveIndex(),a=this.animationDirection,e=this.currentAxis,f=b[e],d=this.itemLength,g;this.isAnimating=false;b.un(this.animationListeners);if(a===-1){g=d+f}else{if(a===1){g=f-d}else{g=f}}g-=this.itemOffset;this.offset=g;this.setActiveItem(c-a)},refresh:function(){this.refreshSizing();this.refreshActiveItem()},refreshSizing:function(){var a=this.element,b=this.getItemLength(),c,d;if(this.getDirection()==="horizontal"){d=a.getWidth()}else{d=a.getHeight()}this.hiddenTranslation=-d;if(b===null){b=d;c=0}else{c=(d-b)/2}this.itemLength=b;this.itemOffset=c},refreshOffset:function(){this.setOffset(this.offset)},refreshActiveItem:function(){this.doSetActiveItem(this.getActiveItem())},getActiveIndex:function(){return this.activeIndex},refreshActiveIndex:function(){this.activeIndex=this.getInnerItemIndex(this.getActiveItem())},refreshCarouselItems:function(){var a=this.carouselItems,b,d,c;for(b=0,d=a.length;b0){for(f=1;f<=c;f++){h=q-f;if(h>=0){a=this.getInnerItemAt(h);b=a.getId();o[b]=a;p[b]=c-f}else{break}}}if(qb){this.setActiveItem(b)}else{this.rebuildInnerIndexes(a);this.refreshActiveItem()}}},rebuildInnerIndexes:function(n){var c=this.innerIndexToItem,g=this.innerIdToIndex,j=this.innerItems.slice(),h=j.length,b=this.getBufferSize(),d=this.getMaxItemIndex(),l=[],e,k,f,a,m;if(n===undefined){this.innerIndexToItem=c={};this.innerIdToIndex=g={};for(e=0;e=0&&e<=d){if(c.hasOwnProperty(e)){Ext.Array.remove(j,c[e]);continue}l.push(e)}}for(e=0,h=l.length;e ."+Ext.baseCSSPrefix+"data-item",scope:this})},initialize:function(){this.callParent();this.doInitialize()},onItemTouchStart:function(d){var b=this,c=d.getTarget(),a=Ext.getCmp(c.id);a.on({touchmove:"onItemTouchMove",scope:b,single:true});b.fireEvent("itemtouchstart",b,a,b.indexOf(a),d)},onItemTouchMove:function(d){var b=this,c=d.getTarget(),a=Ext.getCmp(c.id);b.fireEvent("itemtouchmove",b,a,b.indexOf(a),d)},onItemTouchEnd:function(d){var b=this,c=d.getTarget(),a=Ext.getCmp(c.id);a.un({touchmove:"onItemTouchMove",scope:b});b.fireEvent("itemtouchend",b,a,b.indexOf(a),d)},onItemTap:function(d){var b=this,c=d.getTarget(),a=Ext.getCmp(c.id);b.fireEvent("itemtap",b,a,b.indexOf(a),d)},onItemTapHold:function(d){var b=this,c=d.getTarget(),a=Ext.getCmp(c.id);b.fireEvent("itemtaphold",b,a,b.indexOf(a),d)},onItemSingleTap:function(d){var b=this,c=d.getTarget(),a=Ext.getCmp(c.id);b.fireEvent("itemsingletap",b,a,b.indexOf(a),d)},onItemDoubleTap:function(d){var b=this,c=d.getTarget(),a=Ext.getCmp(c.id);b.fireEvent("itemdoubletap",b,a,b.indexOf(a),d)},onItemSwipe:function(d){var b=this,c=d.getTarget(),a=Ext.getCmp(c.id);b.fireEvent("itemswipe",b,a,b.indexOf(a),d)},moveItemsToCache:function(j,k){var h=this,c=h.dataview,a=c.getMaxItemCache(),g=h.getViewItems(),f=h.itemCache,e=f.length,l=c.getPressedCls(),d=c.getSelectedCls(),b=k-j,m;for(;b>=0;b--){m=g[j+b];if(e!==a){h.remove(m,false);m.removeCls([l,d]);f.push(m);e++}else{m.destroy()}}if(h.getViewItems().length==0){this.dataview.showEmptyText()}},moveItemsFromCache:function(b){var l=this,e=l.dataview,m=e.getStore(),k=b.length,a=e.getDefaultType(),h=e.getItemConfig(),g=l.itemCache,f=g.length,j=[],c,n,d;if(k){e.hideEmptyText()}for(c=0;ci._tmpIndex?1:-1});for(c=0;c{text}
",pressedCls:"x-item-pressed",itemCls:null,selectedCls:"x-item-selected",triggerEvent:"itemtap",triggerCtEvent:"tap",deselectOnContainerClick:true,scrollable:true,inline:null,pressedDelay:100,loadingText:"Loading...",useComponents:null,itemConfig:{},maxItemCache:20,defaultType:"dataitem",scrollToTopOnRefresh:true},constructor:function(a){var b=this;b.hasLoadedStore=false;b.mixins.selectable.constructor.apply(b,arguments);b.callParent(arguments)},updateItemCls:function(c,b){var a=this.container;if(a){if(b){a.doRemoveItemCls(b)}if(c){a.doAddItemCls(c)}}},storeEventHooks:{beforeload:"onBeforeLoad",load:"onLoad",refresh:"refresh",addrecords:"onStoreAdd",removerecords:"onStoreRemove",updaterecord:"onStoreUpdate"},initialize:function(){this.callParent();var b=this,a;b.on(b.getTriggerCtEvent(),b.onContainerTrigger,b);a=b.container=this.add(new Ext.dataview[b.getUseComponents()?"component":"element"].Container({baseCls:this.getBaseCls()}));a.dataview=b;b.on(b.getTriggerEvent(),b.onItemTrigger,b);a.on({itemtouchstart:"onItemTouchStart",itemtouchend:"onItemTouchEnd",itemtap:"onItemTap",itemtaphold:"onItemTapHold",itemtouchmove:"onItemTouchMove",itemsingletap:"onItemSingleTap",itemdoubletap:"onItemDoubleTap",itemswipe:"onItemSwipe",scope:b});if(this.getStore()){this.refresh()}},applyInline:function(a){if(Ext.isObject(a)){a=Ext.apply({},a)}return a},updateInline:function(c,b){var a=this.getBaseCls();if(b){this.removeCls([a+"-inlineblock",a+"-nowrap"])}if(c){this.addCls(a+"-inlineblock");if(Ext.isObject(c)&&c.wrap===false){this.addCls(a+"-nowrap")}else{this.removeCls(a+"-nowrap")}}},prepareData:function(c,b,a){c.xindex=b+1;return c},onContainerTrigger:function(b){var a=this;if(b.target!=a.element.dom){return}if(a.getDeselectOnContainerClick()&&a.getStore()){a.deselectAll()}},onItemTrigger:function(b,a){this.selectWithEvent(this.getStore().getAt(a))},doAddPressedCls:function(a){var c=this,b=c.container.getViewItems()[c.getStore().indexOf(a)];if(Ext.isElement(b)){b=Ext.get(b)}if(b){b.addCls(c.getPressedCls())}},onItemTouchStart:function(b,h,d,g){var f=this,c=f.getStore(),a=c&&c.getAt(d);f.fireAction("itemtouchstart",[f,d,h,a,g],"doItemTouchStart")},doItemTouchStart:function(c,b,e,a){var d=c.getPressedDelay();if(a){if(d>0){c.pressedTimeout=Ext.defer(c.doAddPressedCls,d,c,[a])}else{c.doAddPressedCls(a)}}},onItemTouchEnd:function(b,h,d,g){var f=this,c=f.getStore(),a=c&&c.getAt(d);if(this.hasOwnProperty("pressedTimeout")){clearTimeout(this.pressedTimeout);delete this.pressedTimeout}if(a&&h){h.removeCls(f.getPressedCls())}f.fireEvent("itemtouchend",f,d,h,a,g)},onItemTouchMove:function(b,h,d,g){var f=this,c=f.getStore(),a=c&&c.getAt(d);if(f.hasOwnProperty("pressedTimeout")){clearTimeout(f.pressedTimeout);delete f.pressedTimeout}if(a&&h){h.removeCls(f.getPressedCls())}f.fireEvent("itemtouchmove",f,d,h,a,g)},onItemTap:function(b,h,d,g){var f=this,c=f.getStore(),a=c&&c.getAt(d);f.fireEvent("itemtap",f,d,h,a,g)},onItemTapHold:function(b,h,d,g){var f=this,c=f.getStore(),a=c&&c.getAt(d);f.fireEvent("itemtaphold",f,d,h,a,g)},onItemSingleTap:function(b,h,d,g){var f=this,c=f.getStore(),a=c&&c.getAt(d);f.fireEvent("itemsingletap",f,d,h,a,g)},onItemDoubleTap:function(b,h,d,g){var f=this,c=f.getStore(),a=c&&c.getAt(d);f.fireEvent("itemdoubletap",f,d,h,a,g)},onItemSwipe:function(b,h,d,g){var f=this,c=f.getStore(),a=c&&c.getAt(d);f.fireEvent("itemswipe",f,d,h,a,g)},onItemSelect:function(a,b){var c=this;if(b){c.doItemSelect(c,a)}else{c.fireAction("select",[c,a],"doItemSelect")}},doItemSelect:function(c,a){if(c.container&&!c.isDestroyed){var b=c.container.getViewItems()[c.getStore().indexOf(a)];if(Ext.isElement(b)){b=Ext.get(b)}if(b){b.removeCls(c.getPressedCls());b.addCls(c.getSelectedCls())}}},onItemDeselect:function(a,b){var c=this;if(c.container&&!c.isDestroyed){if(b){c.doItemDeselect(c,a)}else{c.fireAction("deselect",[c,a,b],"doItemDeselect")}}},doItemDeselect:function(c,a){var b=c.container.getViewItems()[c.getStore().indexOf(a)];if(Ext.isElement(b)){b=Ext.get(b)}if(b){b.removeCls([c.getPressedCls(),c.getSelectedCls()])}},updateData:function(b){var a=this.getStore();if(!a){this.setStore(Ext.create("Ext.data.Store",{data:b}))}else{a.add(b)}},applyStore:function(b){var d=this,e=Ext.apply({},d.storeEventHooks,{scope:d}),c,a;if(b){b=Ext.data.StoreManager.lookup(b);if(b&&Ext.isObject(b)&&b.isStore){b.on(e);c=b.getProxy();if(c){a=c.getReader();if(a){a.on("exception","handleException",this)}}}}return b},handleException:function(){this.setMasked(false)},updateStore:function(b,e){var d=this,f=Ext.apply({},d.storeEventHooks,{scope:d}),c,a;if(e&&Ext.isObject(e)&&e.isStore){if(e.autoDestroy){e.destroy()}else{e.un(f);c=e.getProxy();if(c){a=c.getReader();if(a){a.un("exception","handleException",this)}}}}if(b){if(b.isLoaded()){this.hasLoadedStore=true}if(b.isLoading()){d.onBeforeLoad()}if(d.container){d.refresh()}}},onBeforeLoad:function(){var b=this.getScrollable();if(b){b.getScroller().stopAnimation()}var a=this.getLoadingText();if(a){this.setMasked({xtype:"loadmask",message:a});if(b){b.getScroller().setDisabled(true)}}this.hideEmptyText()},updateEmptyText:function(c,d){var b=this,a;if(d&&b.emptyTextCmp){b.remove(b.emptyTextCmp,true);delete b.emptyTextCmp}if(c){b.emptyTextCmp=b.add({xtype:"component",cls:b.getBaseCls()+"-emptytext",html:c,hidden:true});a=b.getStore();if(a&&b.hasLoadedStore&&!a.getCount()){this.showEmptyText()}}},onLoad:function(a){var b=this.getScrollable();this.hasLoadedStore=true;this.setMasked(false);if(b){b.getScroller().setDisabled(false)}if(!a.getCount()){this.showEmptyText()}},refresh:function(){var b=this,a=b.container;if(!b.getStore()){if(!b.hasLoadedStore&&!b.getDeferEmptyText()){b.showEmptyText()}return}if(a){b.fireAction("refresh",[b],"doRefresh")}},applyItemTpl:function(a){return(Ext.isObject(a)&&a.isTemplate)?a:new Ext.XTemplate(a)},onAfterRender:function(){var a=this;a.callParent(arguments);a.updateStore(a.getStore())},getViewItems:function(){return this.container.getViewItems()},doRefresh:function(f){var a=f.container,j=f.getStore(),b=j.getRange(),e=a.getViewItems(),h=b.length,l=e.length,c=h-l,g=f.getScrollable(),d,k;if(this.getScrollToTopOnRefresh()&&g){g.getScroller().scrollToTop()}if(h<1){f.onStoreClear();return}if(c<0){a.moveItemsToCache(l+c,l-1);e=a.getViewItems();l=e.length}else{if(c>0){a.moveItemsFromCache(j.getRange(l))}}for(d=0;dh.y){c=g;break}f=g}return{current:f,next:c}},doRefreshHeaders:function(){if(!this.getGrouped()||!this.container){return false}var l=this.findGroupHeaderIndices(),f=l.length,g=this.container.getViewItems(),j=this.pinHeaderInfo={offsets:[]},a=j.offsets,h=this.getScrollable(),e,k,b,d,c;if(f){for(b=0;bd.offset)||(f&&h0&&d.offset-h<=c){var k=c-(d.offset-h);this.translateHeader(k)}else{this.translateHeader(null)}},translateHeaderTransform:function(a){this.header.renderElement.dom.style.webkitTransform=(a===null)?null:"translate3d(0px, -"+a+"px, 0px)"},translateHeaderCssPosition:function(a){this.header.renderElement.dom.style.top=(a===null)?null:"-"+Math.round(a)+"px"},setActiveGroup:function(b){var a=this,c=a.header;if(c){if(b&&b.header){if(!a.activeGroup||a.activeGroup.header!=b.header){c.show();if(c.element){c.setHtml(b.header.innerHTML)}}}else{if(c&&c.element){c.hide()}}}this.activeGroup=b},onIndex:function(o,c){var r=this,s=c.toLowerCase(),b=r.getStore(),q=b.getGroups(),f=q.length,h=r.getScrollable(),n,e,m,g,k,p;if(h){n=r.getScrollable().getScroller()}else{return}for(m=0;ms){g=e;break}else{g=e}}if(h&&g){p=r.container.getViewItems()[b.indexOf(g.children[0])];n.stopAnimation();var l=n.getContainerSize().y,j=n.getSize().y,d=j-l,a=(p.offsetTop>d)?d:p.offsetTop;n.scrollTo(0,a)}},applyOnItemDisclosure:function(a){if(Ext.isFunction(a)){return{scope:this,handler:a}}return a},handleItemDisclosure:function(f){var d=this,c=f.getTarget().parentNode,b=d.container.getViewItems().indexOf(c),a=d.getStore().getAt(b);d.fireAction("disclose",[d,a,c,b,f],"doDisclose")},doDisclose:function(f,a,d,c,g){var b=f.getOnItemDisclosure();if(b&&b.handler){b.handler.call(b.scope||f,a,d,c,g)}},findGroupHeaderIndices:function(){if(!this.getGrouped()){return[]}var h=this,k=h.getStore();if(!k){return[]}var b=h.container,d=k.getGroups(),m=d.length,g=b.getViewItems(),c=[],l=b.footerClsShortCache,e,a,f,n,j;b.doRemoveHeaders();b.doRemoveFooterCls();if(g.length){for(e=0;e class="x-list-item-leaf">'+a.getItemTextTpl(b)+""},this.getListConfig())}},function(){});Ext.define("Ext.form.FieldSet",{extend:"Ext.Container",alias:"widget.fieldset",requires:["Ext.Title"],config:{baseCls:Ext.baseCSSPrefix+"form-fieldset",title:null,instructions:null},applyTitle:function(a){if(typeof a=="string"){a={title:a}}Ext.applyIf(a,{docked:"top",baseCls:this.getBaseCls()+"-title"});return Ext.factory(a,Ext.Title,this.getTitle())},updateTitle:function(b,a){if(b){this.add(b)}if(a){this.remove(a)}},applyInstructions:function(a){if(typeof a=="string"){a={title:a}}Ext.applyIf(a,{docked:"bottom",baseCls:this.getBaseCls()+"-instructions"});return Ext.factory(a,Ext.Title,this.getInstructions())},updateInstructions:function(b,a){if(b){this.add(b)}if(a){this.remove(a)}}});Ext.define("Ext.form.Panel",{alternateClassName:"Ext.form.FormPanel",extend:"Ext.Panel",xtype:"formpanel",requires:["Ext.XTemplate","Ext.field.Checkbox","Ext.Ajax"],config:{baseCls:Ext.baseCSSPrefix+"form",standardSubmit:false,url:null,baseParams:null,submitOnAction:false,record:null,method:"post",scrollable:{translationMethod:"scrollposition"}},getElementConfig:function(){var a=this.callParent();a.tag="form";return a},initialize:function(){var a=this;a.callParent();a.element.on({submit:"onSubmit",scope:a})},updateRecord:function(c){var a,b,d;if(c&&(a=c.fields)){b=this.getValues();for(d in b){if(b.hasOwnProperty(d)&&a.containsKey(d)){c.set(d,b[d])}}}return this},setRecord:function(a){var b=this;if(a&&a.data){b.setValues(a.data)}b._record=a;return this},onSubmit:function(b){var a=this;if(b&&!a.getStandardSubmit()){b.stopEvent()}else{this.submit()}},updateSubmitOnAction:function(a){if(a){this.on({action:"onFieldAction",scope:this})}else{this.un({action:"onFieldAction",scope:this})}},onFieldAction:function(a){if(this.getSubmitOnAction()){a.blur();this.submit()}},submit:function(a){var c=this,b=c.element.dom||{},d;a=Ext.apply({url:c.getUrl()||b.action,submit:false,method:c.getMethod()||b.method||"post",autoAbort:false,params:null,waitMsg:null,headers:null,success:null,failure:null},a||{});d=c.getValues(c.getStandardSubmit()||!a.submitDisabled);return c.fireAction("beforesubmit",[c,d,a],"doBeforeSubmit")},doBeforeSubmit:function(f,h,b){var e=f.element.dom||{};if(f.getStandardSubmit()){if(b.url&&Ext.isEmpty(e.action)){e.action=b.url}var a=this.query("spinnerfield"),d=a.length,c,g;for(c=0;c1;d.doChangeView(c,a,false)},onViewRemove:function(c){var d=this,b=d.backButtonStack,a;d.endAnimation();b.pop();a=b.length>1;d.doChangeView(c,a,true)},doChangeView:function(k,c,g){var r=this,o=r.leftBox,e=o.element,f=r.titleComponent,m=f.element,n=r.getBackButton(),l=r.getTitleText(),h=r.getBackButtonText(),q=r.getAnimation()&&k.getLayout().getAnimation(),p=q&&q.isAnimation&&k.isPainted(),d,i,a,j,b;if(p){i=r.createProxy(o.element);e.setStyle("opacity","0");n.setText(h);n[c?"show":"hide"]();a=r.createProxy(f.element.getParent());m.setStyle("opacity","0");r.setTitle(l);r.refreshTitlePosition();d=r.measureView(i,a,g);j=d.left;b=d.title;r.isAnimating=true;r.animate(e,j.element);r.animate(m,b.element,function(){m.setLeft(d.titleLeft);r.isAnimating=false});if(Ext.os.is.Android2&&!this.getAndroid2Transforms()){i.ghost.destroy();a.ghost.destroy()}else{r.animate(i.ghost,j.ghost);r.animate(a.ghost,b.ghost,function(){i.ghost.destroy();a.ghost.destroy()})}}else{if(c){n.setText(h);n.show()}else{n.hide()}r.setTitle(l)}},measureView:function(e,u,k){var w=this,j=w.element,v=w.leftBox.element,p=w.titleComponent.element,l=Math.min(j.getWidth()/3,200),q=v.getWidth(),c=j.getX(),m=j.getWidth(),n=p.getX(),d=p.getLeft(),s=p.getWidth(),r=e.x,t=e.width,a=e.left,h=Ext.os.is.Android2&&!this.getAndroid2Transforms(),i,b,f,x,o,g;g=c-r-t;if(k){i=g;b=Math.min(n-t,l)}else{b=g;i=Math.min(n-c,l)}if(h){f={element:{from:{left:i,opacity:1},to:{left:0,opacity:1}}}}else{f={element:{from:{transform:{translateX:i},opacity:0},to:{transform:{translateX:0},opacity:1}},ghost:{to:{transform:{translateX:b},opacity:0}}}}g=c-n+q;if((a+s)>n){o=c-n-s}if(k){p.setLeft(0);b=c+m;if(o!==undefined){i=o}else{i=g}}else{i=m-n;if(o!==undefined){b=o}else{b=g}}if(h){x={element:{from:{left:i,opacity:1},to:{left:d,opacity:1}}}}else{x={element:{from:{transform:{translateX:i},opacity:0},to:{transform:{translateX:d},opacity:1}},ghost:{to:{transform:{translateX:b},opacity:0}}}}return{left:f,title:x,titleLeft:d}},animate:function(b,a,e){var c=this,d;b.setLeft(0);a=Ext.apply(a,{element:b,easing:"ease-in-out",duration:c.getAnimation().duration});d=new Ext.fx.Animation(a);d.on("animationend",function(){if(e){e.call(c)}},c);Ext.Animator.run(d);c.activeAnimations.push(d)},endAnimation:function(){var a=this.activeAnimations,d,b,c;if(a){c=a.length;for(b=0;b0){if(b&&b.isAnimation){b.setReverse(true)}a.setActiveItem(d-1);a.getNavigationBar().onViewRemove(a,c[d],d)}},doRemove:function(){var a=this.getLayout().getAnimation();if(a&&a.isAnimation){a.setReverse(false)}this.callParent(arguments)},onItemAdd:function(b,a){this.doItemLayoutAdd(b,a);if(!this.isItemsInitializing&&b.isInnerItem()){this.setActiveItem(b);this.getNavigationBar().onViewAdd(this,b,a)}if(this.initialized){this.fireEvent("add",this,b,a)}},reset:function(){return this.pop(this.getInnerItems().length)}});Ext.define("Ext.picker.Slot",{extend:"Ext.dataview.DataView",xtype:"pickerslot",alternateClassName:"Ext.Picker.Slot",requires:["Ext.XTemplate","Ext.data.Store","Ext.Component","Ext.data.StoreManager"],isSlot:true,config:{title:null,showTitle:true,cls:Ext.baseCSSPrefix+"picker-slot",name:null,value:null,flex:1,align:"left",displayField:"text",valueField:"value",scrollable:{direction:"vertical",indicators:false,momentumEasing:{minVelocity:2},slotSnapEasing:{duration:100}}},constructor:function(){this.selectedIndex=0;this.callParent(arguments)},applyTitle:function(a){if(a){a=Ext.create("Ext.Component",{cls:Ext.baseCSSPrefix+"picker-slot-title",docked:"top",html:a})}return a},updateTitle:function(b,a){if(b){this.add(b);this.setupBar()}if(a){this.remove(a)}},updateShowTitle:function(a){var b=this.getTitle();if(b){b[a?"show":"hide"]();this.setupBar()}},updateDisplayField:function(a){this.setItemTpl('
'+Ext.baseCSSPrefix+'picker-invalid">{'+a+"}
")},updateAlign:function(a,c){var b=this.element;b.addCls(Ext.baseCSSPrefix+"picker-"+a);b.removeCls(Ext.baseCSSPrefix+"picker-"+c)},applyData:function(d){var f=[],c=d&&d.length,a,b,e;if(d&&Ext.isArray(d)&&c){for(a=0;a0){c[0].addCls(b+"first");c[c.length-1].addCls(b+"last")}this.updateUseTitles(this.getUseTitles())},onDoneButtonTap:function(){var a=this._value,b=this.getValue(true);if(b!=a){this.fireEvent("change",this,b)}this.hide()},onCancelButtonTap:function(){this.fireEvent("cancel",this);this.hide()},onSlotPick:function(a){this.fireEvent("pick",this,this.getValue(true),a)},onShow:function(){if(!this.isHidden()){this.setValue(this._value)}},setValue:function(k,a){var f=this,d=f.getInnerItems(),e=d.length,j,h,c,b,g;if(!k){k={};for(b=0;b{'+this.getDisplayField()+":htmlEncode}",listeners:{select:this.onListSelect,itemtap:this.onListTap,scope:this}}},a))}return this.listPanel},onMaskTap:function(){if(this.getDisabled()){return false}this.showPicker();return false},showPicker:function(){var b=this.getStore();if(!b||b.getCount()===0){return}if(this.getReadOnly()){return}this.isFocused=true;if(this.getUsePicker()){var e=this.getPhonePicker(),d=this.getName(),h={};h[d]=this.record.get(this.getValueField());e.setValue(h);if(!e.getParent()){Ext.Viewport.add(e)}e.show()}else{var f=this.getTabletPicker(),g=f.down("list"),b=g.getStore(),c=b.find(this.getValueField(),this.getValue(),null,null,null,true),a=b.getAt((c==-1)?0:c);if(!f.getParent()){Ext.Viewport.add(f)}f.showBy(this.getComponent());g.select(a,null,true)}},onListSelect:function(c,a){var b=this;if(a){b.setValue(a)}},onListTap:function(){this.listPanel.hide({type:"fade",out:true,scope:this})},onPickerChange:function(d,f){var e=this,g=f[e.getName()],b=e.getStore(),c=b.find(e.getValueField(),g,null,null,null,true),a=b.getAt(c);e.setValue(a)},onChange:function(f,h,e){var g=this,b=g.getStore(),d=(b)?b.find(g.getDisplayField(),e):-1,c=g.getValueField(),a=(b)?b.getAt(d):null,e=(a)?a.get(c):null;g.fireEvent("change",g,g.getValue(),e)},updateOptions:function(b){var a=this.getStore();if(!a){this.setStore(true);a=this._store}if(!b){a.clearData()}else{a.setData(b);this.onStoreDataChanged(a)}},applyStore:function(a){if(a===true){a=Ext.create("Ext.data.Store",{fields:[this.getValueField(),this.getDisplayField()]})}if(a){a=Ext.data.StoreManager.lookup(a);a.on({scope:this,addrecords:this.onStoreDataChanged,removerecords:this.onStoreDataChanged,updaterecord:this.onStoreDataChanged,refresh:this.onStoreDataChanged})}return a},updateStore:function(a){if(a){this.onStoreDataChanged(a)}},onStoreDataChanged:function(a){var c=this.getInitialConfig(),b=this.getValue();if(Ext.isDefined(b)){this.updateValue(this.applyValue(b))}if(this.getValue()===null){if(c.hasOwnProperty("value")){this.setValue(c.value)}if(this.getValue()===null){if(a.getCount()>0){this.setValue(a.getAt(0))}}}},doSetDisabled:function(a){Ext.Component.prototype.doSetDisabled.apply(this,arguments)},setDisabled:function(){Ext.Component.prototype.setDisabled.apply(this,arguments)},reset:function(){var b=this.getStore(),a=(this.originalValue)?this.originalValue:b.getAt(0);if(b&&a){this.setValue(a)}return this},onFocus:function(a){this.fireEvent("focus",this,a);this.isFocused=true;this.showPicker()},destroy:function(){this.callParent(arguments);Ext.destroy(this.listPanel,this.picker,this.hiddenField)}});Ext.define("Ext.picker.Date",{extend:"Ext.picker.Picker",xtype:"datepicker",alternateClassName:"Ext.DatePicker",requires:["Ext.DateExtras"],config:{yearFrom:1980,yearTo:new Date().getFullYear(),monthText:"Month",dayText:"Day",yearText:"Year",slotOrder:["month","day","year"]},initialize:function(){this.callParent();this.on({scope:this,delegate:"> slot",slotpick:this.onSlotPick})},setValue:function(b,a){if(Ext.isDate(b)){b={day:b.getDate(),month:b.getMonth()+1,year:b.getFullYear()}}this.callParent([b,a])},getValue:function(k){var h={},e=this.getItems().items,d=e.length,a,g,c,f,j,b;for(b=0;bf){e=m;m=f;f=e}for(d=m;d<=f;d++){g.push({text:d,value:d})}a=this.getDaysInMonth(1,new Date().getFullYear());for(d=0;d thumb",dragstart:"onThumbDragStart",drag:"onThumbDrag",dragend:"onThumbDragEnd"});this.on({painted:"refresh",resize:"refresh"})},factoryThumb:function(){return Ext.factory(this.getThumbConfig(),Ext.slider.Thumb)},getThumbs:function(){return this.innerItems},getThumb:function(a){if(typeof a!="number"){a=0}return this.innerItems[a]},refreshOffsetValueRatio:function(){var b=this.getMaxValue()-this.getMinValue(),a=this.elementWidth-this.thumbWidth;this.offsetValueRatio=a/b},refreshElementWidth:function(){this.elementWidth=this.element.dom.offsetWidth;var a=this.getThumb(0);if(a){this.thumbWidth=a.getElementWidth()}},refresh:function(){this.refreshElementWidth();this.refreshValue()},setActiveThumb:function(b){var a=this.activeThumb;if(a&&a!==b){a.setZIndex(null)}this.activeThumb=b;b.setZIndex(2);return this},onThumbDragStart:function(a,b){if(b.absDeltaX<=b.absDeltaY){return false}else{b.stopPropagation()}if(this.getAllowThumbsOverlapping()){this.setActiveThumb(a)}this.dragStartValue=this.getValue()[this.getThumbIndex(a)];this.fireEvent("dragstart",this,a,this.dragStartValue,b)},onThumbDrag:function(c,g,a){var d=this.getThumbIndex(c),f=this.offsetValueRatio,b=this.constrainValue(a/f);g.stopPropagation();this.setIndexValue(d,b);this.fireEvent("drag",this,c,this.getValue(),g);return false},setIndexValue:function(d,g,f){var c=this.getThumb(d),b=this.getValue(),e=this.offsetValueRatio,a=c.getDraggable();a.setOffset(g*e,null,f);b[d]=g},onThumbDragEnd:function(a,f){this.refreshThumbConstraints(a);var c=this.getThumbIndex(a),d=this.getValue()[c],b=this.dragStartValue;this.fireEvent("dragend",this,a,this.getValue(),f);if(b!==d){this.fireEvent("change",this,a,d,b)}},getThumbIndex:function(a){return this.getThumbs().indexOf(a)},refreshThumbConstraints:function(d){var b=this.getAllowThumbsOverlapping(),a=d.getDraggable().getOffset().x,c=this.getThumbs(),e=this.getThumbIndex(d),g=c[e-1],h=c[e+1],f=this.thumbWidth;if(g){g.getDraggable().addExtraConstraint({max:{x:a-((b)?0:f)}})}if(h){h.getDraggable().addExtraConstraint({min:{x:a+((b)?0:f)}})}},onTap:function(j){if(this.isDisabled()){return}var k=Ext.get(j.target);if(!k||k.hasCls("x-thumb")){return}var n=j.touch.point.x,h=this.element,c=h.getX(),d=n-c-(this.thumbWidth/2),o=this.constrainValue(d/this.offsetValueRatio),r=this.getValue(),q=Infinity,m=r.length,g,f,l,p,b,a;if(m===1){p=0}else{for(g=0;g=(a/2)){e+=(c>0)?a:-a}e=Math.max(d,e);e=Math.min(f,e);return e},setThumbsCount:function(e){var a=this.getThumbs(),f=a.length,c,d,b;if(f>e){for(c=0,d=f-e;c0,b=d.getMaxValueCls(),e=d.getMinValueCls();this.element.addCls(g?b:e);this.element.removeCls(g?e:b)},toggle:function(){var a=this.getValue();this.setValue((a==1)?0:1);return this},onTap:function(){if(this.isDisabled()){return}var b=this.getValue(),c=(b==1)?0:1,a=this.getThumb(0);this.setIndexValue(0,c,this.getAnimation());this.refreshThumbConstraints(a);this.fireEvent("change",this,a,c,b)}});Ext.define("Ext.field.Toggle",{extend:"Ext.field.Slider",xtype:"togglefield",alternateClassName:"Ext.form.Toggle",requires:["Ext.slider.Toggle"],config:{cls:"x-toggle-field"},proxyConfig:{minValueCls:"x-toggle-off",maxValueCls:"x-toggle-on"},applyComponent:function(a){return Ext.factory(a,Ext.slider.Toggle)},setValue:function(a){if(a===true){a=1}this.getComponent().setValue(a);return this},getValue:function(){return(this.getComponent().getValue()==1)?1:0},toggle:function(){this.getComponent().toggle();return this}});Ext.define("Ext.tab.Tab",{extend:"Ext.Button",xtype:"tab",alternateClassName:"Ext.Tab",isTab:true,config:{baseCls:Ext.baseCSSPrefix+"tab",pressedCls:Ext.baseCSSPrefix+"tab-pressed",activeCls:Ext.baseCSSPrefix+"tab-active",active:false,title:" "},template:[{tag:"span",reference:"badgeElement",hidden:true},{tag:"span",className:Ext.baseCSSPrefix+"button-icon",reference:"iconElement",style:"visibility: hidden !important"},{tag:"span",reference:"textElement",hidden:true}],updateTitle:function(a){this.setText(a)},hideIconElement:function(){this.iconElement.dom.style.setProperty("visibility","hidden","!important")},showIconElement:function(){this.iconElement.dom.style.setProperty("visibility","visible","!important")},updateActive:function(c,b){var a=this.getActiveCls();if(c&&!b){this.element.addCls(a);this.fireEvent("activate",this)}else{if(b){this.element.removeCls(a);this.fireEvent("deactivate",this)}}}},function(){this.override({activate:function(){this.setActive(true)},deactivate:function(){this.setActive(false)}})});Ext.define("Ext.tab.Bar",{extend:"Ext.Toolbar",alternateClassName:"Ext.TabBar",xtype:"tabbar",requires:["Ext.tab.Tab"],config:{baseCls:Ext.baseCSSPrefix+"tabbar",defaultType:"tab",layout:{type:"hbox",align:"middle"}},eventedConfig:{activeTab:null},initialize:function(){var a=this;a.callParent();a.on({tap:"onTabTap",delegate:"> tab",scope:a})},onTabTap:function(a){this.setActiveTab(a)},applyActiveTab:function(b,c){if(!b&&b!==0){return}var a=this.parseActiveTab(b);if(!a){return}return a},doSetDocked:function(a){var c=this.getLayout(),b=a=="bottom"?"center":"left";if(c.isLayout){c.setPack(b)}else{c.pack=(c&&c.pack)?c.pack:b}},doSetActiveTab:function(b,a){if(b){b.setActive(true)}if(a){a.setActive(false)}},parseActiveTab:function(a){if(typeof a=="number"){return this.getInnerItems()[a]}else{if(typeof a=="string"){a=Ext.getCmp(a)}}return a}});Ext.define("Ext.tab.Panel",{extend:"Ext.Container",xtype:"tabpanel",alternateClassName:"Ext.TabPanel",requires:["Ext.tab.Bar"],config:{ui:"dark",tabBar:true,tabBarPosition:"top",layout:{type:"card",animation:{type:"slide",direction:"left"}},cls:Ext.baseCSSPrefix+"tabpanel"},delegateListeners:{delegate:"> component",centeredchange:"onItemCenteredChange",dockedchange:"onItemDockedChange",floatingchange:"onItemFloatingChange",disabledchange:"onItemDisabledChange"},initialize:function(){this.callParent();this.on({order:"before",activetabchange:"doTabChange",delegate:"> tabbar",scope:this})},applyScrollable:function(){return false},updateUi:function(a,b){this.callParent(arguments);if(this.initialized){this.getTabBar().setUi(a)}},doSetActiveItem:function(d,j){if(d){var f=this.getInnerItems(),g=f.indexOf(j),i=f.indexOf(d),e=g>i,c=this.getLayout().getAnimation(),b=this.getTabBar(),h=b.parseActiveTab(g),a=b.parseActiveTab(i);if(c&&c.setReverse){c.setReverse(e)}this.callParent(arguments);if(i!=-1){this.forcedChange=true;b.setActiveTab(i);this.forcedChange=false;if(h){h.setActive(false)}if(a){a.setActive(true)}}}},doTabChange:function(a,d){var b=this.getActiveItem(),c;this.setActiveItem(a.indexOf(d));c=this.getActiveItem();return this.forcedChange||b!==c},applyTabBar:function(a){if(a===true){a={}}if(a){Ext.applyIf(a,{ui:this.getUi(),docked:this.getTabBarPosition()})}return Ext.factory(a,Ext.tab.Bar,this.getTabBar())},updateTabBar:function(a){if(a){this.add(a);this.setTabBarPosition(a.getDocked())}},updateTabBarPosition:function(b){var a=this.getTabBar();if(a){a.setDocked(b)}},onItemAdd:function(e){var k=this;if(!e.isInnerItem()){return k.callParent(arguments)}var c=k.getTabBar(),o=e.getInitialConfig(),d=o.tab||{},g=(e.getTitle)?e.getTitle():o.title,i=(e.getIconCls)?e.getIconCls():o.iconCls,j=(e.getHidden)?e.getHidden():o.hidden,n=(e.getDisabled)?e.getDisabled():o.disabled,p=(e.getBadgeText)?e.getBadgeText():o.badgeText,b=k.getInnerItems(),h=b.indexOf(e),l=c.getItems(),a=c.getActiveTab(),m=(l.length>=b.length)&&l.getAt(h),f;if(g&&!d.title){d.title=g}if(i&&!d.iconCls){d.iconCls=i}if(j&&!d.hidden){d.hidden=j}if(n&&!d.disabled){d.disabled=n}if(p&&!d.badgeText){d.badgeText=p}f=Ext.factory(d,Ext.tab.Tab,m);if(!m){c.insert(h,f)}e.tab=f;k.callParent(arguments);if(!a&&a!==0){c.setActiveTab(c.getActiveItem())}},onItemDisabledChange:function(a,b){if(a&&a.tab){a.tab.setDisabled(b)}},onItemRemove:function(b,a){this.getTabBar().remove(b.tab,this.getAutoDestroy());this.callParent(arguments)}},function(){});Ext.define("Ext.table.Cell",{extend:"Ext.Container",xtype:"tablecell",config:{baseCls:"x-table-cell"},getElementConfig:function(){var a=this.callParent();a.children.length=0;return a}});Ext.define("Ext.table.Row",{extend:"Ext.table.Cell",xtype:"tablerow",config:{baseCls:"x-table-row",defaultType:"tablecell"}});Ext.define("Ext.table.Table",{extend:"Ext.Container",requires:["Ext.table.Row"],xtype:"table",config:{baseCls:"x-table",defaultType:"tablerow"},cachedConfig:{fixedLayout:false},fixedLayoutCls:"x-table-fixed",updateFixedLayout:function(a){this.innerElement[a?"addCls":"removeCls"](this.fixedLayoutCls)}});Ext.define("Ext.viewport.Default",{extend:"Ext.Container",xtype:"viewport",PORTRAIT:"portrait",LANDSCAPE:"landscape",requires:["Ext.LoadMask"],config:{autoMaximize:false,autoBlurInput:true,preventPanning:true,preventZooming:false,autoRender:true,layout:"card",width:"100%",height:"100%"},isReady:false,isViewport:true,isMaximizing:false,id:"ext-viewport",isInputRegex:/^(input|textarea|select|a)$/i,focusedElement:null,fullscreenItemCls:Ext.baseCSSPrefix+"fullscreen",constructor:function(a){var b=Ext.Function.bind;this.doPreventPanning=b(this.doPreventPanning,this);this.doPreventZooming=b(this.doPreventZooming,this);this.doBlurInput=b(this.doBlurInput,this);this.maximizeOnEvents=["ready","orientationchange"];this.orientation=this.determineOrientation();this.windowWidth=this.getWindowWidth();this.windowHeight=this.getWindowHeight();this.windowOuterHeight=this.getWindowOuterHeight();if(!this.stretchHeights){this.stretchHeights={}}this.callParent([a]);if(this.supportsOrientation()){this.addWindowListener("orientationchange",b(this.onOrientationChange,this))}else{this.addWindowListener("resize",b(this.onResize,this))}document.addEventListener("focus",b(this.onElementFocus,this),true);document.addEventListener("blur",b(this.onElementBlur,this),true);Ext.onDocumentReady(this.onDomReady,this);this.on("ready",this.onReady,this,{single:true});this.getEventDispatcher().addListener("component","*","fullscreen","onItemFullscreenChange",this);return this},onDomReady:function(){this.isReady=true;this.updateSize();this.fireEvent("ready",this)},onReady:function(){if(this.getAutoRender()){this.render()}},onElementFocus:function(a){this.focusedElement=a.target},onElementBlur:function(){this.focusedElement=null},render:function(){if(!this.rendered){var a=Ext.getBody(),b=Ext.baseCSSPrefix,h=[],d=Ext.os,g=d.name.toLowerCase(),f=Ext.browser.name.toLowerCase(),e=d.version.getMajor(),c=this.getOrientation();this.renderTo(a);h.push(b+d.deviceType.toLowerCase());if(d.is.iPad){h.push(b+"ipad")}h.push(b+g);h.push(b+f);if(e){h.push(b+g+"-"+e)}if(d.is.BlackBerry){h.push(b+"bb")}if(Ext.browser.is.Standalone){h.push(b+"standalone")}h.push(b+c);a.addCls(h)}},applyAutoBlurInput:function(a){var b=(Ext.feature.has.Touch)?"touchstart":"mousedown";if(a){this.addWindowListener(b,this.doBlurInput,false)}else{this.removeWindowListener(b,this.doBlurInput,false)}return a},applyAutoMaximize:function(a){if(Ext.browser.is.WebView){a=false}if(a){this.on("ready","doAutoMaximizeOnReady",this,{single:true});this.on("orientationchange","doAutoMaximizeOnOrientationChange",this)}else{this.un("ready","doAutoMaximizeOnReady",this);this.un("orientationchange","doAutoMaximizeOnOrientationChange",this)}return a},applyPreventPanning:function(a){if(a){this.addWindowListener("touchmove",this.doPreventPanning,false)}else{this.removeWindowListener("touchmove",this.doPreventPanning,false)}return a},applyPreventZooming:function(a){var b=(Ext.feature.has.Touch)?"touchstart":"mousedown";if(a){this.addWindowListener(b,this.doPreventZooming,false)}else{this.removeWindowListener(b,this.doPreventZooming,false)}return a},doAutoMaximizeOnReady:function(){var a=arguments[arguments.length-1];a.pause();this.isMaximizing=true;this.on("maximize",function(){this.isMaximizing=false;this.updateSize();a.resume();this.fireEvent("ready",this)},this,{single:true});this.maximize()},doAutoMaximizeOnOrientationChange:function(){var a=arguments[arguments.length-1],b=a.firingArguments;a.pause();this.isMaximizing=true;this.on("maximize",function(){this.isMaximizing=false;this.updateSize();b[1]=this.windowWidth;b[2]=this.windowHeight;a.resume()},this,{single:true});this.maximize()},doBlurInput:function(b){var a=b.target,c=this.focusedElement;if(c&&!this.isInputRegex.test(a.tagName)){delete this.focusedElement;c.blur()}},doPreventPanning:function(a){a.preventDefault()},doPreventZooming:function(b){if("button" in b&&b.button!==0){return}var a=b.target;if(a&&a.nodeType===1&&!this.isInputRegex.test(a.tagName)){b.preventDefault()}},addWindowListener:function(b,c,a){window.addEventListener(b,c,Boolean(a))},removeWindowListener:function(b,c,a){window.removeEventListener(b,c,Boolean(a))},doAddListener:function(a,d,c,b){if(a==="ready"&&this.isReady&&!this.isMaximizing){d.call(c);return this}this.mixins.observable.doAddListener.apply(this,arguments)},supportsOrientation:function(){return Ext.feature.has.Orientation},onResize:function(){var c=this.windowWidth,f=this.windowHeight,e=this.getWindowWidth(),a=this.getWindowHeight(),d=this.getOrientation(),b=this.determineOrientation();if((c!==e||f!==a)&&d!==b){this.fireOrientationChangeEvent(b,d)}},onOrientationChange:function(){var b=this.getOrientation(),a=this.determineOrientation();if(a!==b){this.fireOrientationChangeEvent(a,b)}},fireOrientationChangeEvent:function(b,c){var a=Ext.baseCSSPrefix;Ext.getBody().replaceCls(a+c,a+b);this.orientation=b;this.updateSize();this.fireEvent("orientationchange",this,b,this.windowWidth,this.windowHeight)},updateSize:function(b,a){this.windowWidth=b!==undefined?b:this.getWindowWidth();this.windowHeight=a!==undefined?a:this.getWindowHeight();return this},waitUntil:function(h,e,g,a,f){if(!a){a=50}if(!f){f=2000}var c=this,b=0;setTimeout(function d(){b+=a;if(h.call(c)===true){if(e){e.call(c)}}else{if(b>=f){if(g){g.call(c)}}else{setTimeout(d,a)}}},a)},maximize:function(){this.fireMaximizeEvent()},fireMaximizeEvent:function(){this.updateSize();this.fireEvent("maximize",this)},doSetHeight:function(a){Ext.getBody().setHeight(a);this.callParent(arguments)},doSetWidth:function(a){Ext.getBody().setWidth(a);this.callParent(arguments)},scrollToTop:function(){window.scrollTo(0,-1)},getWindowWidth:function(){return window.innerWidth},getWindowHeight:function(){return window.innerHeight},getWindowOuterHeight:function(){return window.outerHeight},getWindowOrientation:function(){return window.orientation},getOrientation:function(){return this.orientation},getSize:function(){return{width:this.windowWidth,height:this.windowHeight}},determineOrientation:function(){var b=this.PORTRAIT,a=this.LANDSCAPE;if(this.supportsOrientation()){if(this.getWindowOrientation()%180===0){return b}return a}else{if(this.getWindowHeight()>=this.getWindowWidth()){return b}return a}},onItemFullscreenChange:function(a){a.addCls(this.fullscreenItemCls);this.add(a)}});Ext.define("Ext.viewport.Android",{extend:"Ext.viewport.Default",constructor:function(){this.on("orientationchange","doFireOrientationChangeEvent",this,{prepend:true});this.on("orientationchange","hideKeyboardIfNeeded",this,{prepend:true});return this.callParent(arguments)},getDummyInput:function(){var a=this.dummyInput,c=this.focusedElement,b=Ext.fly(c).getPageBox();if(!a){this.dummyInput=a=document.createElement("input");a.style.position="absolute";a.style.opacity="0";document.body.appendChild(a)}a.style.left=b.left+"px";a.style.top=b.top+"px";a.style.display="";return a},doBlurInput:function(c){var b=c.target,d=this.focusedElement,a;if(d&&!this.isInputRegex.test(b.tagName)){a=this.getDummyInput();delete this.focusedElement;a.focus();setTimeout(function(){a.style.display="none"},100)}},hideKeyboardIfNeeded:function(){var a=arguments[arguments.length-1],b=this.focusedElement;if(b){delete this.focusedElement;a.pause();if(Ext.os.version.lt("4")){b.style.display="none"}else{b.blur()}setTimeout(function(){b.style.display="";a.resume()},1000)}},doFireOrientationChangeEvent:function(){var a=arguments[arguments.length-1];this.orientationChanging=true;a.pause();this.waitUntil(function(){return this.getWindowOuterHeight()!==this.windowOuterHeight},function(){this.windowOuterHeight=this.getWindowOuterHeight();this.updateSize();a.firingArguments[1]=this.windowWidth;a.firingArguments[2]=this.windowHeight;a.resume();this.orientationChanging=false},function(){});return this},applyAutoMaximize:function(a){a=this.callParent(arguments);this.on("add","fixSize",this,{single:true});if(!a){this.on("ready","fixSize",this,{single:true});this.onAfter("orientationchange","doFixSize",this)}else{this.un("ready","fixSize",this);this.unAfter("orientationchange","doFixSize",this)}},fixSize:function(){this.doFixSize()},doFixSize:function(){this.setHeight(this.getWindowHeight())},getActualWindowOuterHeight:function(){return Math.round(this.getWindowOuterHeight()/window.devicePixelRatio)},maximize:function(){var c=this.stretchHeights,b=this.orientation,a;a=c[b];if(!a){c[b]=a=this.getActualWindowOuterHeight()}if(!this.addressBarHeight){this.addressBarHeight=a-this.getWindowHeight()}this.setHeight(a);var d=Ext.Function.bind(this.isHeightMaximized,this,[a]);this.scrollToTop();this.waitUntil(d,this.fireMaximizeEvent,this.fireMaximizeEvent)},isHeightMaximized:function(a){this.scrollToTop();return this.getWindowHeight()===a}},function(){if(!Ext.os.is.Android){return}var a=Ext.os.version,b=Ext.browser.userAgent,c=/(htc|desire|incredible|ADR6300)/i.test(b)&&a.lt("2.3");if(c){this.override({constructor:function(d){if(!d){d={}}d.autoMaximize=false;this.watchDogTick=Ext.Function.bind(this.watchDogTick,this);setInterval(this.watchDogTick,1000);return this.callParent([d])},watchDogTick:function(){this.watchDogLastTick=Ext.Date.now()},doPreventPanning:function(){var e=Ext.Date.now(),f=this.watchDogLastTick,d=e-f;if(d>=2000){return}return this.callParent(arguments)},doPreventZooming:function(){var e=Ext.Date.now(),f=this.watchDogLastTick,d=e-f;if(d>=2000){return}return this.callParent(arguments)}})}if(a.match("2")){this.override({onReady:function(){this.addWindowListener("resize",Ext.Function.bind(this.onWindowResize,this));this.callParent(arguments)},scrollToTop:function(){document.body.scrollTop=100},onWindowResize:function(){var e=this.windowWidth,g=this.windowHeight,f=this.getWindowWidth(),d=this.getWindowHeight();if(this.getAutoMaximize()&&!this.isMaximizing&&!this.orientationChanging&&window.scrollY===0&&e===f&&d=g-this.addressBarHeight)||!this.focusedElement)){this.scrollToTop()}},fixSize:function(){var d=this.getOrientation(),f=window.outerHeight,g=window.outerWidth,e;if(d==="landscape"&&(f=g)){e=this.getActualWindowOuterHeight()}else{e=this.getWindowHeight()}this.waitUntil(function(){return e>this.getWindowHeight()},this.doFixSize,this.doFixSize,50,1000)}})}else{if(a.gtEq("3.1")){this.override({isHeightMaximized:function(d){this.scrollToTop();return this.getWindowHeight()===d-1}})}else{if(a.match("3")){this.override({isHeightMaximized:function(){this.scrollToTop();return true}})}}}if(a.gtEq("4")){this.override({doBlurInput:Ext.emptyFn})}});Ext.define("Ext.viewport.Ios",{extend:"Ext.viewport.Default",isFullscreen:function(){return this.isHomeScreen()},isHomeScreen:function(){return window.navigator.standalone===true},constructor:function(){this.callParent(arguments);if(this.getAutoMaximize()&&!this.isFullscreen()){this.addWindowListener("touchstart",Ext.Function.bind(this.onTouchStart,this))}},maximize:function(){if(this.isFullscreen()){return this.callParent()}var c=this.stretchHeights,b=this.orientation,d=this.getWindowHeight(),a=c[b];if(window.scrollY>0){this.scrollToTop();if(!a){c[b]=a=this.getWindowHeight()}this.setHeight(a);this.fireMaximizeEvent()}else{if(!a){a=this.getScreenHeight()}this.setHeight(a);this.waitUntil(function(){this.scrollToTop();return d!==this.getWindowHeight()},function(){if(!c[b]){a=c[b]=this.getWindowHeight();this.setHeight(a)}this.fireMaximizeEvent()},function(){a=c[b]=this.getWindowHeight();this.setHeight(a);this.fireMaximizeEvent()},50,1000)}},getScreenHeight:function(){return window.screen[this.orientation===this.PORTRAIT?"height":"width"]},onElementFocus:function(){if(this.getAutoMaximize()&&!this.isFullscreen()){clearTimeout(this.scrollToTopTimer)}this.callParent(arguments)},onElementBlur:function(){if(this.getAutoMaximize()&&!this.isFullscreen()){this.scrollToTopTimer=setTimeout(this.scrollToTop,500)}this.callParent(arguments)},onTouchStart:function(){if(this.focusedElement===null){this.scrollToTop()}},scrollToTop:function(){window.scrollTo(0,0)}},function(){if(!Ext.os.is.iOS){return}if(Ext.os.version.lt("3.2")){this.override({constructor:function(){var a=this.stretchHeights={};a[this.PORTRAIT]=416;a[this.LANDSCAPE]=268;return this.callOverridden(arguments)}})}if(Ext.os.version.lt("5")){this.override({fieldMaskClsTest:"-field-mask",doPreventZooming:function(b){var a=b.target;if(a&&a.nodeType===1&&!this.isInputRegex.test(a.tagName)&&a.className.indexOf(this.fieldMaskClsTest)==-1){b.preventDefault()}}})}if(Ext.os.is.iPad){this.override({isFullscreen:function(){return true}})}});Ext.define("Ext.viewport.Viewport",{requires:["Ext.viewport.Ios","Ext.viewport.Android"],constructor:function(b){var c=Ext.os.name,d,a;switch(c){case"Android":d="Android";break;case"iOS":d="Ios";break;default:d="Default"}a=Ext.create("Ext.viewport."+d,b);return a}});Ext.define("Ext.event.recognizer.Swipe",{extend:"Ext.event.recognizer.SingleTouch",handledEvents:["swipe"],inheritableStatics:{MAX_OFFSET_EXCEEDED:16,MAX_DURATION_EXCEEDED:17,DISTANCE_NOT_ENOUGH:18},config:{minDistance:80,maxOffset:35,maxDuration:1000},onTouchStart:function(a){if(this.callParent(arguments)===false){return false}var b=a.changedTouches[0];this.startTime=a.time;this.isHorizontal=true;this.isVertical=true;this.startX=b.pageX;this.startY=b.pageY},onTouchMove:function(f){var h=f.changedTouches[0],b=h.pageX,g=h.pageY,c=Math.abs(b-this.startX),a=Math.abs(g-this.startY),d=f.time;if(d-this.startTime>this.getMaxDuration()){return this.fail(this.self.MAX_DURATION_EXCEEDED)}if(this.isVertical&&c>this.getMaxOffset()){this.isVertical=false}if(this.isHorizontal&&a>this.getMaxOffset()){this.isHorizontal=false}if(!this.isHorizontal&&!this.isVertical){return this.fail(this.self.MAX_OFFSET_EXCEEDED)}},onTouchEnd:function(i){if(this.onTouchMove(i)===false){return false}var h=i.changedTouches[0],l=h.pageX,j=h.pageY,g=l-this.startX,f=j-this.startY,c=Math.abs(g),b=Math.abs(f),m=this.getMinDistance(),d=i.time-this.startTime,k,a;if(this.isVertical&&bc){return this.fail(this.self.MAX_DURATION_EXCEEDED)}if(a>b){return this.fail(this.self.MAX_OFFSET_EXCEEDED)}},onTouchEnd:function(f){if(this.onTouchMove(f)!==false){var i=f.changedTouches[0],a=i.pageX,b=a-this.startX,h=Math.abs(b),d=f.time-this.startTime,g=this.getMinDistance(),c;if(h} + */ +function flags(files) { + var list = [ + "--warning_level", "VERBOSE", + // The project ships its own copy of the externs of the browser, so the + // ones bundled with the compiler are not used, to avoid duplicates. + "--env", "CUSTOM", + "--language_in", "ECMASCRIPT3", + "--language_out", "ECMASCRIPT3", + "--use_types_for_optimization", + "--hide_warnings_for=synthetic", + "--summary_detail_level", "3" + ]; + closure.errorGroupsFor(closure.version).forEach(function (group) { + if (!withTests || group !== "reportUnknownTypes") { + list.push("--jscomp_error", group); + } + }); + closure.offGroups.forEach(function (group) { + list.push("--jscomp_off", group); + }); + // The order matters: an extern may use a type declared in a previous one. + externs.forEach(function (name) { + list.push("--externs", path.join(rootDir, "webodf/tools", name)); + }); + // Declarations of the libraries packaged with webodf.js. + fs.readdirSync(path.join(rootDir, "webodf/externs")).forEach(function (name) { + list.push("--externs", path.join(rootDir, "webodf/externs", name)); + }); + list.push("--js", files.version); + sources.libraryFiles().forEach(function (file) { + list.push("--js", file); + }); + list.push("--js", files.css); + if (withTests) { + sources.testFiles().forEach(function (file) { + list.push("--js", file); + }); + } + return list; +} + +/** + * Arguments of a second pass, over the libraries packaged with webodf.js, JSZip + * for now. Their types are not checked, as they are not written for the + * compiler, but their jsdoc has to parse, as the target "simplecompiled.js" of + * the build with cmake compiles them: without this pass, a broken annotation is + * only reported by the build with cmake. + * @return {!Array.} + */ +function packagedFlags() { + var list = [ + "--compilation_level", "WHITESPACE_ONLY", + "--warning_level", "VERBOSE", + "--env", "CUSTOM", + "--language_in", "ECMASCRIPT3", + "--language_out", "ECMASCRIPT3", + "--js_output_file", "/dev/null" + ]; + // The same groups as the build with cmake, so that a broken annotation is + // an error and not a warning. reportUnknownTypes is left out, as are the + // checks of the types: WHITESPACE_ONLY does not run them. + closure.errorGroupsFor(closure.version).forEach(function (group) { + if (group !== "reportUnknownTypes") { + list.push("--jscomp_error", group); + } + }); + closure.offGroups.forEach(function (group) { + list.push("--jscomp_off", group); + }); + fs.readdirSync(path.join(rootDir, "webodf/lib/externs")).forEach(function (name) { + list.push("--js", path.join(rootDir, "webodf/lib/externs", name)); + }); + return list; +} + +closure.jar().then(function (jarPath) { + var files = writeGeneratedFiles(), + flagFile = path.join(files.dir, "flagfile.txt"), + args, + result; + args = flags(files); + // The compiler splits a flag file on the white space, so a path holding a + // space would be read as two arguments. + args.forEach(function (arg) { + if (/\s/.test(arg)) { + throw new Error(`The path "${arg}" holds a space, that the flag` + + " file of the compiler does not support."); + } + }); + fs.writeFileSync(flagFile, args.join("\n")); + result = child.spawnSync("java", [ + "-jar", jarPath, + "--flagfile", flagFile, + "--define", "IS_COMPILED_CODE=true", + // Only the checks are needed: the library is built by scripts/build.js. + "--checks_only" + ], {stdio: "inherit"}); + fs.rmSync(files.dir, {recursive: true, force: true}); + if (result.error) { + console.error("Unable to run java, that the closure compiler needs: " + + result.error.message); + process.exit(1); + } + if (result.status === 0 && withTests) { + result = child.spawnSync("java", + ["-jar", jarPath].concat(packagedFlags()), {stdio: "inherit"}); + } + process.exit(result.status === null ? 1 : result.status); +}).catch(function (err) { + console.error(String(err)); + process.exit(1); +}); diff --git a/scripts/cmake/keep-products.cmake b/scripts/cmake/keep-products.cmake new file mode 100644 index 000000000..05a495a6a --- /dev/null +++ b/scripts/cmake/keep-products.cmake @@ -0,0 +1,18 @@ +# Take out of the directory of the products what another build left there. +# +# The products carry the version of the sources they were built from, so a +# second build writes files of another name beside the ones of the first, and +# a reader is left to tell which is which. Only the products of this build are +# kept: DIR is the directory, KEEP the names to leave in it. +# The names come as one string parted by commas: a list of cmake is parted by +# ";", which the shell that runs the build would read as the end of a command. +string(REPLACE "," ";" KEEP "${KEEP}") +file(GLOB WEBODF_THERE ${DIR}/*) +foreach (WEBODF_ONE ${WEBODF_THERE}) + get_filename_component(WEBODF_NAME ${WEBODF_ONE} NAME) + list(FIND KEEP ${WEBODF_NAME} WEBODF_WANTED) + if (WEBODF_WANTED EQUAL -1) + message(STATUS "Taking away the product of another build: ${WEBODF_NAME}") + file(REMOVE_RECURSE ${WEBODF_ONE}) + endif () +endforeach () diff --git a/scripts/coverage.js b/scripts/coverage.js new file mode 100644 index 000000000..79b9db7c8 --- /dev/null +++ b/scripts/coverage.js @@ -0,0 +1,77 @@ +"use strict"; + +/** + * Run the tests of scripts/test.js under c8 and report the coverage of the + * library, as the target "instrumented" of the original build did with + * JSCoverage. + * + * c8 does not read nor rewrite the sources: V8 records the coverage while it + * runs and c8 only converts it, so the version of ECMAScript of the library + * does not matter. The bundle the tests run is a concatenation, so it is + * written with a source map that brings the coverage back to the files. + * + * Usage: node scripts/coverage.js [--reporter=text|html|lcov ...] + */ + +var fs = require("fs"), + os = require("os"), + path = require("path"), + child = require("child_process"), + bundle = require("./lib/bundle.js"), + sources = require("./lib/sources.js"), + rootDir = path.resolve(__dirname, ".."), + reportDir = path.join(rootDir, "coverage"); + +function main() { + var dir = fs.mkdtempSync(path.join(os.tmpdir(), "webodf-coverage-")), + bundlePath = path.join(dir, "tests.js"), + built = bundle.buildWithMap(true), + reporters = process.argv.slice(2), + c8, + result; + + fs.writeFileSync(bundlePath, `${built.code} +//# sourceMappingURL=tests.js.map +`); + fs.writeFileSync(bundlePath + ".map", JSON.stringify(built.map)); + fs.writeFileSync(path.join(dir, "run.js"), + `require(${JSON.stringify(path.join(__dirname, "lib/dom.js"))}).install(); +require(${JSON.stringify(bundlePath)}); +`); + fs.symlinkSync(path.join(rootDir, "node_modules"), path.join(dir, "node_modules")); + + try { + c8 = require.resolve("c8/bin/c8.js"); + } catch (ignore) { + console.error("c8 was not found: run \"npm install\" first."); + process.exit(1); + } + + if (reporters.length === 0) { + reporters = ["--reporter=text", "--reporter=html"]; + } + + // The tests run from their own directory, as they read their documents from + // it, so the sources of the library are outside of it: without + // --allowExternal c8 drops them all. The exclusions are applied once the + // source map is resolved, as the coverage is recorded on the bundle. + result = child.spawnSync(process.execPath, [c8].concat(reporters, [ + "--report-dir", reportDir, + "--allowExternal", + "--exclude-after-remap", + "--exclude", "**/webodf/tests/**", + "--exclude", "**/lib/externs/**", + process.execPath, path.join(dir, "run.js") + ]), { + cwd: sources.testsDir, + stdio: "inherit" + }); + + fs.rmSync(dir, {recursive: true, force: true}); + if (result.status === 0) { + console.log("The report is in " + reportDir + "."); + } + process.exit(result.status === null ? 1 : result.status); +} + +main(); diff --git a/scripts/doc.js b/scripts/doc.js new file mode 100644 index 000000000..9d3447aed --- /dev/null +++ b/scripts/doc.js @@ -0,0 +1,63 @@ +"use strict"; + +/** + * Generate the documentation of the api from the jsdoc annotations of the + * sources, with jsdoc, that replaces the abandoned jsdoc-toolkit 2.4.0 of the + * original build. + * + * The build with cmake runs this script as well, on the library and on the + * editors, so that both builds read the same tool. + * + * Usage: node scripts/doc.js [--output path] [--source file ...] + */ + +var fs = require("fs"), + os = require("os"), + path = require("path"), + child = require("child_process"), + sources = require("./lib/sources.js"), + rootDir = path.resolve(__dirname, ".."), + args = process.argv.slice(2), + outputIndex = args.indexOf("--output"), + outputDir = outputIndex === -1 + ? path.join(rootDir, "dist/docs") + : path.resolve(args[outputIndex + 1]), + sourceIndex = args.indexOf("--source"), + // Everything after --source is a file to read, so that the editors give + // their own where the library gives the classes of its manifest. + sourceFiles = sourceIndex === -1 + ? null + : args.slice(sourceIndex + 1).map(function (name) { + return path.resolve(name); + }), + // The file of javascript of jsdoc, run by node, rather than the script of + // "node_modules/.bin": that one is a shell script on unix and a ".cmd" on + // windows, and neither name holds on the other system. + jsdoc = path.join(rootDir, "node_modules/jsdoc/jsdoc.js"), + configDir = fs.mkdtempSync(path.join(os.tmpdir(), "webodf-doc-")), + configPath = path.join(configDir, "jsdoc.json"), + result; + +// By default jsdoc skips any path with a part starting with an underscore, +// which silently drops every source when the project is checked out in such a +// directory. +fs.writeFileSync(configPath, JSON.stringify({ + source: {includePattern: ".+\\.js$", excludePattern: ""}, + opts: {template: "templates/default"} +})); + +result = child.spawnSync(process.execPath, [ + jsdoc, + "--configure", configPath, + "--destination", outputDir +].concat(sourceFiles || sources.libraryFiles()), {stdio: "inherit"}); +fs.rmSync(configDir, {recursive: true, force: true}); + +if (result.error || !fs.existsSync(jsdoc)) { + console.error("jsdoc is missing, run: npm install"); + process.exit(1); +} +if (result.status === 0) { + console.log("Documentation written in " + outputDir); +} +process.exit(result.status === null ? 1 : result.status); diff --git a/scripts/lib/bundle.js b/scripts/lib/bundle.js new file mode 100644 index 000000000..e948d5ef7 --- /dev/null +++ b/scripts/lib/bundle.js @@ -0,0 +1,144 @@ +"use strict"; + +/** + * Concatenation of the sources of the library, with or without its tests. The + * order of the files comes from their dependencies, see lib/sources.js. + */ + +var fs = require("fs"), + path = require("path"), + sources = require("./sources.js"), + generated = require("./generated.js"); + +var BASE64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +/** + * The parts of the bundle, in the order they are concatenated. Each one keeps + * the name of the file it comes from, so that a source map can be built. + * + * @param {boolean} withTests + * @return {!Array.<{name: string, text: string}>} + */ +function parts(withTests) { + var result = [{name: "webodfversion.js", text: generated.versionSource()}]; + sources.libraryFiles().forEach(function (file) { + result.push({name: file, text: fs.readFileSync(file, "utf8")}); + }); + result.push({name: "webodf.css.js", text: generated.cssSource()}); + result.push({ + name: path.join(sources.libDir, "externs/JSZip.js"), + text: fs.readFileSync(path.join(sources.libDir, "externs/JSZip.js"), "utf8") + }); + if (withTests) { + sources.testFiles().forEach(function (file) { + result.push({name: file, text: fs.readFileSync(file, "utf8")}); + }); + } + return result; +} + +/** + * @param {!Array.<{name: string, text: string}>} list + * @return {string} + */ +function concatenate(list) { + // IS_COMPILED_CODE tells the runtime that all the classes are already + // loaded, so that loadClass() does not fetch them one by one. The + // replacement keeps the line count, so that the source map stays valid. + return list.map(function (part) { + return part.text; + }).join("\n").replace("var IS_COMPILED_CODE = false;", + "var IS_COMPILED_CODE = true;"); +} + +/** + * @param {number} value + * @return {string} + */ +function encodeVlq(value) { + var vlq = value < 0 ? ((-value) << 1) + 1 : (value << 1), + result = "", + digit; + do { + digit = vlq & 31; + vlq = vlq >>> 5; + if (vlq > 0) { + digit = digit | 32; + } + result += BASE64.charAt(digit); + } while (vlq > 0); + return result; +} + +/** + * @param {boolean} withTests + * @return {string} + */ +function build(withTests) { + return concatenate(parts(withTests)); +} + +/** + * Build the bundle and the source map that maps it back to the sources. + * + * The bundle is a plain concatenation, so every line of the output comes from + * exactly one line of one source, at the same column: one segment per line is + * enough, and the mappings are only the deltas of the index of the source. + * + * @param {boolean} withTests + * @return {{code: string, map: !Object}} + */ +function buildWithMap(withTests) { + var list = parts(withTests), + names = [], + contents = [], + mappings = [], + previousIndex = 0, + previousLine = 0; + list.forEach(function (part, index) { + var lines = part.text.split("\n").length, + line = 1; + names.push(part.name); + contents.push(part.text); + // A segment holds four fields: the column of the output, the index of + // the source, its line and its column. The index and the line are + // running values, shared by the whole map: the first line of a part has + // to bring the line back to zero, the next ones only move it by one. + mappings.push("A" + encodeVlq(index - previousIndex) + + encodeVlq(-previousLine) + "A"); + while (line < lines) { + mappings.push("AACA"); + line += 1; + } + previousIndex = index; + previousLine = lines - 1; + }); + return { + code: concatenate(list), + map: { + version: 3, + sources: names, + sourcesContent: contents, + names: [], + mappings: mappings.join(";") + } + }; +} + +/** + * @return {string} + */ +function library() { + return build(false); +} + +/** + * @return {string} + */ +function withTests() { + return build(true); +} + +exports.library = library; +exports.withTests = withTests; +exports.buildWithMap = buildWithMap; diff --git a/scripts/lib/closure.js b/scripts/lib/closure.js new file mode 100644 index 000000000..34016081d --- /dev/null +++ b/scripts/lib/closure.js @@ -0,0 +1,101 @@ +"use strict"; + +/** + * The closure compiler is used as a type checker only: the library is built by + * scripts/build.js, without it. The jar is published on maven central and is + * cached in the directory ".tools", so that a build does not download it again. + */ + +var fs = require("fs"), + path = require("path"), + https = require("https"), + rootDir = path.resolve(__dirname, "../.."), + toolsDir = path.join(rootDir, ".tools"), + version = process.env.CLOSURE_VERSION || "v20260804", + jarName = "closure-compiler-" + version + ".jar", + jarPath = path.join(toolsDir, jarName), + jarUrl = `https://repo1.maven.org/maven2/com/google/javascript/closure-compiler/${version}/${jarName}`; + +/** + * Path of the jar, downloaded when missing. The environment variable + * CLOSURE_JAR overrides it, to use a local copy or another version. + * @return {!Promise} + */ +function jar() { + if (process.env.CLOSURE_JAR) { + return Promise.resolve(process.env.CLOSURE_JAR); + } + if (fs.existsSync(jarPath)) { + return Promise.resolve(jarPath); + } + if (!fs.existsSync(toolsDir)) { + fs.mkdirSync(toolsDir); + } + console.log("Downloading " + jarUrl); + return new Promise(function (resolve, reject) { + var file = fs.createWriteStream(jarPath + ".part"); + https.get(jarUrl, function (response) { + if (response.statusCode !== 200) { + reject(new Error("Unable to download the closure compiler: " + + response.statusCode)); + return; + } + response.pipe(file); + file.on("finish", function () { + file.close(function () { + fs.renameSync(jarPath + ".part", jarPath); + resolve(jarPath); + }); + }); + }).on("error", reject); + }); +} + +// Checks of the original build. The groups removed in the compilers newer +// than 2016 are marked, so that the list is ready when it is updated: +// ambiguousFunctionDecl, checkEventfulObjectDisposal, es3, fileoverviewTags, +// internetExplorerChecks, missingGetCssName, newCheckTypes, undefinedNames, +// unusedPrivateMembers and useOfGoogBase. +// Groups removed from the compilers newer than 2016. +var removedGroups = [ + "ambiguousFunctionDecl", "checkEventfulObjectDisposal", "es3", + "fileoverviewTags", "internetExplorerChecks", "missingGetCssName", + "newCheckTypes", "undefinedNames", "unusedPrivateMembers", "useOfGoogBase" +]; + +var errorGroups = [ + "accessControls", "ambiguousFunctionDecl", "checkEventfulObjectDisposal", + "checkRegExp", "checkTypes", "checkVars", + "conformanceViolations", "const", "constantProperty", "deprecated", + "deprecatedAnnotations", "duplicateMessage", "es3", "es5Strict", + "externsValidation", "fileoverviewTags", "globalThis", + "internetExplorerChecks", "invalidCasts", "misplacedTypeAnnotation", + "missingGetCssName", "missingProperties", "missingProvide", + "missingReturn", "msgDescriptions", "newCheckTypes", "reportUnknownTypes", + "strictModuleDepCheck", "suspiciousCode", "typeInvalidation", + "undefinedNames", "undefinedVars", "underscore", "unknownDefines", + "unusedLocalVariables", "unusedPrivateMembers", "useOfGoogBase", + "uselessCode", "visibility" +]; + +var offGroups = ["missingRequire", "nonStandardJsDocs"]; + +/** + * Groups of checks to report as errors, without the ones the given version of + * the compiler does not know any more. + * @return {!Array.} + */ +function errorGroupsFor(compilerVersion) { + if (compilerVersion === "v20160911") { + return errorGroups; + } + return errorGroups.filter(function (group) { + return removedGroups.indexOf(group) === -1; + }); +} + +exports.jar = jar; +exports.version = version; +exports.errorGroupsFor = errorGroupsFor; +exports.errorGroups = errorGroups; +exports.offGroups = offGroups; diff --git a/scripts/lib/dom.js b/scripts/lib/dom.js new file mode 100644 index 000000000..96d036363 --- /dev/null +++ b/scripts/lib/dom.js @@ -0,0 +1,43 @@ +"use strict"; + +/** + * A dom close to the one of a browser, from jsdom, added to the globals of + * node. The package xmldom, that the library uses by default, has no range and + * no tree walker, so the tests that walk a document are skipped without this. + */ + +/** + * Add the interfaces of a dom to the global object, when jsdom is installed. + * @return {boolean} true when the dom is available + */ +function install() { + var JSDOM, window, names; + try { + JSDOM = require("jsdom").JSDOM; + } catch (ignore) { + return false; + } + window = new JSDOM("").window; + names = [ + "DOMParser", "XMLSerializer", "Node", "NodeFilter", "Element", "Attr", + "Document", "DocumentFragment", "Text", "Range", "NodeIterator", + "TreeWalker", "XPathResult", "DOMException", "getComputedStyle" + ]; + names.forEach(function (name) { + if (window[name] !== undefined) { + global[name] = window[name]; + } + }); + // The document and the window of the page, that some tests use to build + // their fixtures. + global.document = window.document; + global.window = window; + // The packaged libraries, JSZip for now, attach themselves to the window + // when there is one, while the library reads them as a global. The same + // object is shared, so that both see the same packages. + global.externs = {}; + window.externs = global.externs; + return true; +} + +exports.install = install; diff --git a/scripts/lib/generated.js b/scripts/lib/generated.js new file mode 100644 index 000000000..b64df527d --- /dev/null +++ b/scripts/lib/generated.js @@ -0,0 +1,61 @@ +"use strict"; + +/** + * Files that are generated at build time: the version of the library and its + * css, embedded as a javascript string. + */ + +var fs = require("fs"), + path = require("path"), + child = require("child_process"), + rootDir = path.resolve(__dirname, "../.."), + cssPath = path.join(rootDir, "webodf/webodf.css"); + +/** + * Version of the library, from the last tag of the repository, as the build of + * cmake did. Only the tags that name a version are taken, so a tag of work in + * progress does not become the version. It falls back on the version of the + * package when git is not available, for example when building from an + * archive, or when no version is tagged yet. + * @return {string} + */ +function version() { + try { + return child.execSync("git describe --tags --match \"v[0-9]*\"", { + cwd: rootDir, + stdio: ["ignore", "pipe", "ignore"] + }).toString().trim(); + } catch (ignore) { + return "v" + JSON.parse( + fs.readFileSync(path.join(rootDir, "package.json"), "utf8") + ).version; + } +} + +/** + * Declaration of the version, that runtime.js exposes as webodf.Version. + * @return {string} + */ +function versionSource() { + return "var /**@const{!string}*/webodf_version = \"" + version() + "\";\n"; +} + +/** + * The css of the viewer as a javascript string, inserted in the page by + * OdfCanvas. Comments, indentation and line breaks are removed to keep the + * string small. + * @return {string} + */ +function cssSource() { + var css = fs.readFileSync(cssPath, "utf8") + .replace(/\/\*([\r\n]|.)*?\*\//g, "") + .replace(/(^\s*)|(\s*$)/gm, "") + .replace(/\r?\n/g, "") + .replace(/\\/g, "\\\\") + .replace(/'/g, "\\'"); + return "var webodf_css = '" + css + "';\n"; +} + +exports.version = version; +exports.versionSource = versionSource; +exports.cssSource = cssSource; diff --git a/scripts/lib/rhino.js b/scripts/lib/rhino.js new file mode 100644 index 000000000..549676f37 --- /dev/null +++ b/scripts/lib/rhino.js @@ -0,0 +1,54 @@ +"use strict"; + +/** + * Rhino runs javascript on a java virtual machine. It is used to run the tests + * in a second engine, besides node. The jar is published on maven central and + * is cached in the directory ".tools". + */ + +var fs = require("fs"), + path = require("path"), + https = require("https"), + rootDir = path.resolve(__dirname, "../.."), + toolsDir = path.join(rootDir, ".tools"), + version = process.env.RHINO_VERSION || "1.9.1", + jarName = "rhino-all-" + version + ".jar", + jarPath = path.join(toolsDir, jarName), + jarUrl = `https://repo1.maven.org/maven2/org/mozilla/rhino-all/${version}/${jarName}`; + +/** + * Path of the jar, downloaded when missing. The environment variable RHINO_JAR + * overrides it, to use a local copy or another version. + * @return {!Promise} + */ +function jar() { + if (process.env.RHINO_JAR) { + return Promise.resolve(process.env.RHINO_JAR); + } + if (fs.existsSync(jarPath)) { + return Promise.resolve(jarPath); + } + if (!fs.existsSync(toolsDir)) { + fs.mkdirSync(toolsDir); + } + console.log("Downloading " + jarUrl); + return new Promise(function (resolve, reject) { + var file = fs.createWriteStream(jarPath + ".part"); + https.get(jarUrl, function (response) { + if (response.statusCode !== 200) { + reject(new Error("Unable to download Rhino: " + response.statusCode)); + return; + } + response.pipe(file); + file.on("finish", function () { + file.close(function () { + fs.renameSync(jarPath + ".part", jarPath); + resolve(jarPath); + }); + }); + }).on("error", reject); + }); +} + +exports.jar = jar; +exports.version = version; diff --git a/scripts/lib/sources.js b/scripts/lib/sources.js new file mode 100644 index 000000000..87a6dfe15 --- /dev/null +++ b/scripts/lib/sources.js @@ -0,0 +1,169 @@ +"use strict"; + +/** + * Ordered list of the source files of the library, from the dependencies + * declared in "webodf/lib/manifest.json". The files are concatenated in this + * order, so a file must come after all the files it uses. + */ + +var fs = require("fs"), + path = require("path"), + libDir = path.resolve(__dirname, "../../webodf/lib"), + testsDir = path.resolve(__dirname, "../../webodf/tests"), + manifestPath = path.join(libDir, "manifest.json"); + +/** + * Convert a class name of the manifest into the path of its file. + * @param {string} name for example "odf.OdfCanvas" + * @return {string} + */ +function fileOfClass(name) { + return path.join(libDir, name.replace(/\./g, "/") + ".js"); +} + +/** + * Sort the classes of the manifest so that each one comes after the classes it + * depends on. The dependencies of the manifest are not ordered, so the sort is + * stable on the names to get the same output on every run. + * @return {!Array.} + */ +function sortedClasses() { + var manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8")), + names = Object.keys(manifest).sort(), + done = {}, + visiting = {}, + sorted = []; + + function visit(name, from) { + if (done[name]) { + return; + } + if (visiting[name]) { + throw new Error("Circular dependency on " + name + " from " + from); + } + if (!manifest.hasOwnProperty(name)) { + throw new Error("Unknown dependency " + name + " of " + from); + } + visiting[name] = true; + manifest[name].slice().sort().forEach(function (dependency) { + visit(dependency, name); + }); + visiting[name] = false; + done[name] = true; + sorted.push(name); + } + + names.forEach(function (name) { + visit(name, "manifest"); + }); + return sorted; +} + +// Classes of the manifest that no other class may use: the command line tools +// run in node only and the Relax NG validator is not used by the viewer. +var unusedClasses = [ + "odf.CommandLineTools", + "xmldom.RelaxNG", + "xmldom.RelaxNG2", + "xmldom.RelaxNGParser" +]; + +// Classes added by the build itself, after the library: the packaged JSZip is +// used at call time only, so its position does not matter. +var appendedClasses = ["externs.JSZip"]; + +var excludedClasses = unusedClasses.concat(appendedClasses); + +/** + * Files of the library, in the order they must be concatenated. The runtime + * comes first, since every other file uses it. + * @return {!Array.} + */ +function libraryFiles() { + var manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8")), + files = [path.join(libDir, "runtime.js")]; + // An unused class must be used by no other one, else its file would be + // missing from the library while something needs it. + Object.keys(manifest).forEach(function (name) { + if (excludedClasses.indexOf(name) !== -1) { + return; + } + manifest[name].forEach(function (dependency) { + if (unusedClasses.indexOf(dependency) !== -1) { + throw new Error(`${name} uses ${dependency}, that is excluded` + + " from the library."); + } + }); + }); + sortedClasses().forEach(function (name) { + var file = fileOfClass(name); + if (excludedClasses.indexOf(name) === -1 && files.indexOf(file) === -1) { + files.push(file); + } + }); + return files; +} + +// Tests, in the order of the original build: the helpers come first and +// "tests.js", that runs them all, comes last. +var testFileNames = [ + "webodfcore/UnitTester.js", + "webodfcore/ZipTests.js", + "webodfcore/Base64Tests.js", + "webodfcore/CursorTests.js", + "webodfcore/DomUtilsTests.js", + "webodfcore/EventSubscriptionsTests.js", + "webodfcore/PositionIteratorTests.js", + "webodfcore/RuntimeTests.js", + "webodfcore/StepIteratorTests.js", + "gui/DirectFormattingControllerTests.js", + "gui/GuiStepUtilsTests.js", + "gui/MetadataControllerTests.js", + "gui/SelectionControllerTests.js", + "gui/StyleSummaryTests.js", + "gui/TextControllerTests.js", + "gui/ImageControllerTests.js", + "gui/TrivialUndoManagerTests.js", + "gui/UndoStateRulesTests.js", + "odf/StyleParseUtilsTests.js", + "odf/StyleTreeTests.js", + "odf/StyleCacheTests.js", + "odf/FormattingTests.js", + "odf/LayoutTests.js", + "odf/ListStyleToCssTests.js", + "odf/ObjectNameGeneratorTests.js", + "odf/MaliciousDocumentTests.js", + "odf/OdfContainerTests.js", + "odf/OdfContainerSafetyTests.js", + "odf/OdfUtilsTests.js", + "odf/OfficeDocumentsTests.js", + "odf/PageLayoutTests.js", + "odf/StyleInfoTests.js", + "odf/TextStyleApplicatorTests.js", + "ops/OperationTestHelper.js", + "ops/OdtDocumentTests.js", + "ops/OperationTests.js", + "ops/SessionTests.js", + "ops/OdtStepsTranslatorTests.js", + "ops/TransformationTests.js", + "ops/TransformerTests.js", + "xmldom/LSSerializerTests.js", + "xmldom/XPathTests.js", + "tests.js" +]; + +/** + * Files of the tests, in the order they must be concatenated. + * @return {!Array.} + */ +function testFiles() { + return testFileNames.map(function (name) { + return path.join(testsDir, name); + }); +} + +exports.libDir = libDir; +exports.testsDir = testsDir; +exports.testFiles = testFiles; +exports.fileOfClass = fileOfClass; +exports.libraryFiles = libraryFiles; diff --git a/scripts/test-browser.js b/scripts/test-browser.js new file mode 100644 index 000000000..d9672eb3b --- /dev/null +++ b/scripts/test-browser.js @@ -0,0 +1,164 @@ +"use strict"; + +/** + * Run the tests in a browser, the only engine providing a layout and a css + * parser as strict as the ones the last suites need. The files of the tests are + * served over http, since a page loaded from a file cannot read them. + * + * The browser is not downloaded: set WEBODF_BROWSER to the path of a chromium, + * or install one, for example with "npx playwright install chromium". + * + * Usage: node scripts/test-browser.js + */ + +var fs = require("fs"), + http = require("http"), + path = require("path"), + bundle = require("./lib/bundle.js"), + sources = require("./lib/sources.js"), + browserPaths = [ + process.env.WEBODF_BROWSER, + "/usr/bin/chromium", + "/usr/bin/chromium-browser", + "/usr/bin/google-chrome", + "/opt/google/chrome/chrome" + ], + // A text file is served as bytes: a browser strips the byte order mark when + // it decodes the answer as utf-8, and the tests read the raw bytes. + types = { + ".html": "text/html", + ".js": "application/javascript", + ".css": "text/css", + ".xml": "application/xml", + ".odt": "application/vnd.oasis.opendocument.text", + ".ods": "application/vnd.oasis.opendocument.spreadsheet", + ".odp": "application/vnd.oasis.opendocument.presentation", + ".png": "image/png" + }, + page = `tests` + + `
` + + ``; + +/** + * The first browser found among the usual paths. + * @return {?string} + */ +function findBrowser() { + var found = null; + browserPaths.forEach(function (candidate) { + if (!found && candidate && fs.existsSync(candidate)) { + found = candidate; + } + }); + return found; +} + +/** + * Serve the page of the tests, their bundle and the documents they read. + * @param {string} code the bundle + * @return {!http.Server} + */ +function serve(code) { + return http.createServer(function (request, response) { + var name = request.url.split("?")[0], + file, + data; + if (name === "/") { + response.writeHead(200, {"Content-Type": "text/html"}); + response.end(page); + return; + } + if (name === "/tests.js") { + response.writeHead(200, {"Content-Type": "application/javascript"}); + response.end(code); + return; + } + file = path.join(sources.testsDir, path.normalize(name).replace(/^(\.\.[/\\])+/, "")); + // The tests write and delete files, as they do with node, so the + // server answers PUT and DELETE besides GET. + if (request.method === "PUT") { + data = []; + request.on("data", function (chunk) { + data.push(chunk); + }); + request.on("end", function () { + try { + fs.writeFileSync(file, Buffer.concat(data)); + response.writeHead(200); + } catch (ignore) { + response.writeHead(500); + } + response.end(); + }); + return; + } + if (request.method === "DELETE") { + try { + fs.unlinkSync(file); + response.writeHead(200); + } catch (ignore) { + response.writeHead(404); + } + response.end(); + return; + } + try { + data = fs.readFileSync(file); + } catch (ignore) { + console.error("404 " + name); + response.writeHead(404); + response.end(); + return; + } + response.writeHead(200, { + "Content-Type": types[path.extname(file)] || "application/octet-stream", + // The tests write a file then read it back at once. + "Cache-Control": "no-store" + }); + response.end(data); + }); +} + +function main() { + var browser = findBrowser(), + server; + if (!browser) { + console.error("No browser found. Set WEBODF_BROWSER to the path of a" + + " chromium, or install one with: npx playwright install chromium"); + process.exit(1); + } + server = serve(bundle.withTests()); + server.listen(0, "127.0.0.1", function () { + var url = "http://127.0.0.1:" + server.address().port + "/"; + require("playwright-core").chromium.launch({ + executablePath: browser, + args: ["--no-sandbox"] + }).then(function (instance) { + return instance.newPage().then(function (browserPage) { + // The suite tells it is over by logging its number of failures. + var done = new Promise(function (resolve) { + browserPage.on("console", function (message) { + var text = message.text(); + console.log(text); + if (text.indexOf("Number of failed asserts: ") === 0) { + resolve(parseInt(text.substr(26), 10)); + } + }); + }); + browserPage.goto(url); + return done.then(function (failed) { + return instance.close().then(function () { + server.close(); + process.exit(failed); + }); + }); + }); + }).catch(function (err) { + console.error(String(err)); + server.close(); + process.exit(1); + }); + }); +} + +main(); diff --git a/scripts/test-extension.js b/scripts/test-extension.js new file mode 100644 index 000000000..a9a1124d8 --- /dev/null +++ b/scripts/test-extension.js @@ -0,0 +1,170 @@ +"use strict"; + +/** + * Run the add-on of Chrome and check that it shows a document. + * + * The package is built here, from "programs/opendocumentviewer-webext" and the library + * of "dist", then a document is served over http and the browser is asked for + * it. The add-on has to send it to its viewer, that has to draw it. + * + * This catches what neither the linter nor the compiler sees: a rule Chrome + * refuses is dropped without a word, and the document is downloaded as if the + * add-on were not installed. + * + * Firefox is not driven here: it takes web-ext, that is not a dependency of + * this project, see "README-Building.md". + * + * Usage: node scripts/test-extension.js + */ + +var fs = require("fs"), + os = require("os"), + http = require("http"), + path = require("path"), + rootDir = path.resolve(__dirname, ".."), + extensionDir = path.join(rootDir, "programs/opendocumentviewer-webext"), + libraryPath = path.join(rootDir, "dist/webodf.js"), + // A document that is versioned, so that the test needs no other build. + documentPath = path.join(rootDir, "programs/benchmark/1page.odt"), + browserPaths = [ + process.env.WEBODF_BROWSER, + "/usr/bin/chromium", + "/usr/bin/chromium-browser", + "/usr/bin/google-chrome", + "/opt/google/chrome/chrome" + ], + port = 8734; + +/** + * The first browser found among the usual paths. + * @return {?string} + */ +function findBrowser() { + var found = null; + browserPaths.forEach(function (candidate) { + if (!found && candidate && fs.existsSync(candidate)) { + found = candidate; + } + }); + return found; +} + +/** + * Write the package of Chrome in a directory of its own. + * @return {string} + */ +function buildPackage() { + var dir = fs.mkdtempSync(path.join(os.tmpdir(), "webodf-extension-")), + manifest = fs.readFileSync( + path.join(extensionDir, "manifest-chrome.json.in"), + "utf8" + ); + fs.mkdirSync(path.join(dir, "skin/default"), {recursive: true}); + fs.writeFileSync(path.join(dir, "manifest.json"), + manifest.replace("@WEBODF_MANIFEST_VERSION@", "0.0.0")); + ["background-chrome.js", "viewer.html", "viewer.css", "viewer.js"].forEach( + function (name) { + fs.copyFileSync(path.join(extensionDir, name), path.join(dir, name)); + } + ); + fs.copyFileSync(path.join(extensionDir, "skin/default/icon.png"), + path.join(dir, "skin/default/icon.png")); + fs.copyFileSync(libraryPath, path.join(dir, "webodf.js")); + return dir; +} + +/** + * Serve the document under a type that says nothing, so that the add-on has to + * read the name of the file, as it does on a server that knows no ODF. + * @return {!http.Server} + */ +function serve() { + var body = fs.readFileSync(documentPath); + return http.createServer(function (request, response) { + response.writeHead(200, { + "Content-Type": "application/octet-stream", + "Content-Length": body.length + }); + response.end(body); + }).listen(port); +} + +function main() { + var browser = findBrowser(), + playwright, + server, + directory; + if (!browser) { + console.error("No browser found. Set WEBODF_BROWSER to the path of a" + + " chromium, or install one with: npx playwright install chromium"); + process.exit(1); + } + if (!fs.existsSync(libraryPath)) { + console.error("The library is missing: run \"npm run build\" first."); + process.exit(1); + } + playwright = require("playwright-core"); + directory = buildPackage(); + server = serve(); + return playwright.chromium.launchPersistentContext( + fs.mkdtempSync(path.join(os.tmpdir(), "webodf-profile-")), + { + executablePath: browser, + headless: true, + args: [ + "--no-sandbox", + "--disable-gpu", + "--disable-extensions-except=" + directory, + "--load-extension=" + directory + ] + } + ).then(function (context) { + // The rule is written by the service worker once it is installed, so + // the document is only asked for after it has run. The worker may + // already be there, and the event would then never come. + return (context.serviceWorkers().length > 0 + ? Promise.resolve() + : context.waitForEvent("serviceworker", {timeout: 30000})) + .then(function () { + return context.serviceWorkers()[0].evaluate(function () { + return chrome.declarativeNetRequest.getDynamicRules(); + }); + }) + .then(function (rules) { + if (!rules || rules.length === 0) { + throw new Error("the add-on wrote no rule to redirect with"); + } + return context.newPage(); + }) + .then(function (page) { + return page.goto("http://127.0.0.1:" + port + "/1page.odt", { + waitUntil: "load", + timeout: 30000 + }).then(function () { + return page.waitForSelector("#odf *", {timeout: 30000}); + }).then(function () { + return page.url(); + }); + }) + .then(function (url) { + return context.close().then(function () { + return url; + }); + }); + }).then(function (url) { + server.close(); + fs.rmSync(directory, {recursive: true, force: true}); + if (url.indexOf("viewer.html") === -1) { + console.error("The add-on did not send the document to its viewer," + + ` the page is at ${url}`); + process.exit(1); + } + console.log("The add-on showed the document: " + url.slice(0, 80)); + }).catch(function (err) { + server.close(); + console.error("The add-on did not show the document: " + String(err)); + process.exit(1); + }); +} + +main(); diff --git a/scripts/test-rhino.js b/scripts/test-rhino.js new file mode 100644 index 000000000..fcf88a550 --- /dev/null +++ b/scripts/test-rhino.js @@ -0,0 +1,42 @@ +"use strict"; + +/** + * Run the tests that do not need a browser with Rhino, on a java virtual + * machine, as a second engine besides node. + * + * Usage: node scripts/test-rhino.js + */ + +var fs = require("fs"), + os = require("os"), + path = require("path"), + child = require("child_process"), + bundle = require("./lib/bundle.js"), + rhino = require("./lib/rhino.js"), + sources = require("./lib/sources.js"); + +rhino.jar().then(function (jarPath) { + var dir = fs.mkdtempSync(path.join(os.tmpdir(), "webodf-rhino-")), + bundlePath = path.join(dir, "tests.js"), + result; + fs.writeFileSync(bundlePath, bundle.withTests()); + // The tests read their documents from the current directory. + // The dom of java is implemented by internal classes that the module system + // does not export since java 9, and Rhino reaches them by reflection. + result = child.spawnSync("java", [ + "--add-opens", "java.xml/com.sun.org.apache.xerces.internal.dom=ALL-UNNAMED", + "-jar", jarPath, "-f", bundlePath + ], { + cwd: sources.testsDir, + stdio: "inherit" + }); + fs.rmSync(dir, {recursive: true, force: true}); + if (result.error) { + console.error("Unable to run java, that Rhino needs: " + result.error.message); + process.exit(1); + } + process.exit(result.status === null ? 1 : result.status); +}).catch(function (err) { + console.error(String(err)); + process.exit(1); +}); diff --git a/scripts/test.js b/scripts/test.js new file mode 100644 index 000000000..07c903711 --- /dev/null +++ b/scripts/test.js @@ -0,0 +1,45 @@ +"use strict"; + +/** + * Run the tests that do not need a browser, as the target "simplenodetest" of + * the original build did: the library and the tests are concatenated, then the + * result is run with node from the directory of the tests, that holds the + * documents they read. + * + * Usage: node scripts/test.js + */ + +var fs = require("fs"), + os = require("os"), + path = require("path"), + child = require("child_process"), + bundle = require("./lib/bundle.js"), + sources = require("./lib/sources.js"), + rootDir = path.resolve(__dirname, ".."); + +function main() { + var dir = fs.mkdtempSync(path.join(os.tmpdir(), "webodf-test-")), + bundlePath = path.join(dir, "tests.js"), + result; + // The bundle is loaded by a small script that adds a dom to the globals + // first, so that the tests walking a document are not skipped. + fs.writeFileSync(bundlePath, bundle.withTests()); + fs.writeFileSync(path.join(dir, "run.js"), + `require(${JSON.stringify(path.join(__dirname, "lib/dom.js"))}).install(); +require(${JSON.stringify(bundlePath)}); +`); + // Some tests write a file in "out", as the targets of the build with cmake + // do: the directory is made here too, since a fresh checkout has none. + fs.mkdirSync(path.join(sources.testsDir, "out"), {recursive: true}); + // The tests read their documents from the current directory, and require() + // resolves the dependencies from the directory of the bundle. + fs.symlinkSync(path.join(rootDir, "node_modules"), path.join(dir, "node_modules")); + result = child.spawnSync(process.execPath, [path.join(dir, "run.js")], { + cwd: sources.testsDir, + stdio: "inherit" + }); + fs.rmSync(dir, {recursive: true, force: true}); + process.exit(result.status === null ? 1 : result.status); +} + +main(); diff --git a/webodf/CMakeLists.txt b/webodf/CMakeLists.txt index a3deff67d..998000363 100644 --- a/webodf/CMakeLists.txt +++ b/webodf/CMakeLists.txt @@ -38,15 +38,15 @@ set(EXTERNS_LIST string(REPLACE ";" " " EXTERNS "${EXTERNS_LIST}") set(TESTJSFILES - tests/core/UnitTester.js - tests/core/ZipTests.js - tests/core/Base64Tests.js - tests/core/CursorTests.js - tests/core/DomUtilsTests.js - tests/core/EventSubscriptionsTests.js - tests/core/PositionIteratorTests.js - tests/core/RuntimeTests.js - tests/core/StepIteratorTests.js + tests/webodfcore/UnitTester.js + tests/webodfcore/ZipTests.js + tests/webodfcore/Base64Tests.js + tests/webodfcore/CursorTests.js + tests/webodfcore/DomUtilsTests.js + tests/webodfcore/EventSubscriptionsTests.js + tests/webodfcore/PositionIteratorTests.js + tests/webodfcore/RuntimeTests.js + tests/webodfcore/StepIteratorTests.js tests/gui/DirectFormattingControllerTests.js tests/gui/GuiStepUtilsTests.js tests/gui/MetadataControllerTests.js @@ -57,13 +57,16 @@ set(TESTJSFILES tests/gui/TrivialUndoManagerTests.js tests/gui/UndoStateRulesTests.js tests/odf/StyleParseUtilsTests.js + tests/odf/StyleTreeTests.js tests/odf/StyleCacheTests.js tests/odf/FormattingTests.js tests/odf/LayoutTests.js + tests/odf/PageLayoutTests.js tests/odf/ListStyleToCssTests.js tests/odf/ObjectNameGeneratorTests.js tests/odf/MaliciousDocumentTests.js tests/odf/OdfContainerTests.js + tests/odf/OfficeDocumentsTests.js tests/odf/OdfContainerSafetyTests.js tests/odf/OdfUtilsTests.js tests/odf/StyleInfoTests.js @@ -105,8 +108,6 @@ SET(CLOSURE_ERROR_OPTION_LIST # "--new_type_inf" "--hide_warnings_for=synthetic" "--jscomp_error accessControls" - "--jscomp_error ambiguousFunctionDecl" - "--jscomp_error checkEventfulObjectDisposal" "--jscomp_error checkRegExp" "--jscomp_error checkTypes" "--jscomp_error checkVars" @@ -116,32 +117,24 @@ SET(CLOSURE_ERROR_OPTION_LIST "--jscomp_error deprecated" "--jscomp_error deprecatedAnnotations" "--jscomp_error duplicateMessage" - "--jscomp_error es3" "--jscomp_error es5Strict" "--jscomp_error externsValidation" - "--jscomp_error fileoverviewTags" "--jscomp_error globalThis" - "--jscomp_error internetExplorerChecks" "--jscomp_error invalidCasts" "--jscomp_error misplacedTypeAnnotation" - "--jscomp_error missingGetCssName" "--jscomp_error missingProperties" "--jscomp_error missingProvide" "--jscomp_off missingRequire" "--jscomp_error missingReturn" "--jscomp_error msgDescriptions" - "--jscomp_error newCheckTypes" "--jscomp_off nonStandardJsDocs" "--jscomp_error strictModuleDepCheck" "--jscomp_error suspiciousCode" "--jscomp_error typeInvalidation" - "--jscomp_error undefinedNames" "--jscomp_error undefinedVars" "--jscomp_error underscore" "--jscomp_error unknownDefines" "--jscomp_error unusedLocalVariables" - "--jscomp_error unusedPrivateMembers" - "--jscomp_error useOfGoogBase" "--jscomp_error uselessCode" "--jscomp_error visibility") string(REPLACE ";" " " CLOSURE_ERROR_OPTIONS "${CLOSURE_ERROR_OPTION_LIST}") @@ -162,8 +155,19 @@ file (APPEND ${CMAKE_CURRENT_BINARY_DIR}/cc-noTestFiles.txt " --js ${CMAKE_CURRE # this requires a separate file as other targets don't want to compile the test files execute_process(COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/cc-noTestFiles.txt ${CMAKE_CURRENT_BINARY_DIR}/cc-withTestFiles.txt) +# A third flagfile, for the file that is run rather than checked: it carries +# jszip, which the strict compilation refuses as it is minified, and it carries +# it before the tests. The compiler puts a file named on the command line after +# the ones of a flagfile, and jszip writes itself into the scope when it is +# read: a test that opens a zip reads it at once in an engine that has no loop +# of events, rhino among them. +execute_process(COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/cc-noTestFiles.txt ${CMAKE_CURRENT_BINARY_DIR}/cc-runTestFiles.txt) +file (APPEND ${CMAKE_CURRENT_BINARY_DIR}/cc-runTestFiles.txt + " --js ${CMAKE_CURRENT_SOURCE_DIR}/lib/externs/JSZip.js") + foreach(JSFILE ${TESTJSFILES}) file (APPEND ${CMAKE_CURRENT_BINARY_DIR}/cc-withTestFiles.txt " --js ${CMAKE_CURRENT_SOURCE_DIR}/${JSFILE}") + file (APPEND ${CMAKE_CURRENT_BINARY_DIR}/cc-runTestFiles.txt " --js ${CMAKE_CURRENT_SOURCE_DIR}/${JSFILE}") endforeach(JSFILE ${TESTJSFILES}) # nonStandardJsDocs is not used because we use @licstart @licend and @source @@ -171,6 +175,16 @@ endforeach(JSFILE ${TESTJSFILES}) # -XX:+TieredCompilation reduces compilation time by about 30% set(SHARED_CLOSURE_ARGS -XX:+TieredCompilation -jar ${CLOSURE_JAR}) +# What the closure compiler writes is written by java alone: a machine +# without it builds the library and every product, and only the checks of the +# types are left out, see "find_package(Java ...)" in the root. +if (WEBODF_HAS_JAVA) + set(WEBODF_TYPE_CHECK webodf-compiled.js) +else () + set(WEBODF_TYPE_CHECK "") +endif () +if (WEBODF_HAS_JAVA) + # Do quick syntax check on javascript files of webodf lib and webodf tests. # This compile uses no optimization and runs simple syntax tests. That is why # this target is mentioned early in the list of dependencies. @@ -180,8 +194,7 @@ add_custom_command( COMMAND ${Java_JAVA_EXECUTABLE} ARGS ${SHARED_CLOSURE_ARGS} --js ${HEADERCOMPILED_FILE} - --flagfile ${CMAKE_CURRENT_BINARY_DIR}/cc-withTestFiles.txt - --js ${CMAKE_CURRENT_SOURCE_DIR}/lib/externs/*.js + --flagfile ${CMAKE_CURRENT_BINARY_DIR}/cc-runTestFiles.txt --compilation_level WHITESPACE_ONLY --formatting PRETTY_PRINT --js_output_file simplecompiled.js- @@ -231,21 +244,36 @@ add_custom_command( DEPENDS ClosureCompiler ${LIBJSFILES} tools/externs.js webodf.css.js-target ) -# Include non-Closure Compiler compatible libraries with no further recompilation +endif () + +# The library is written by the build with node, so that both builds produce +# the same file. The closure compiler still reads the sources, in +# "webodf-compiled.js" above, but only to check their types: it compiles with +# SIMPLE_OPTIMIZATIONS, that neither folds the definition IS_COMPILED_CODE nor +# drops what it makes unreachable, so it kept the loader of the classes and +# the runner of the scripts, that read a file with eval(), inside a library +# that never calls them. add_custom_command( OUTPUT webodf.js - COMMAND ${Java_JAVA_EXECUTABLE} - ARGS ${SHARED_CLOSURE_ARGS} - --js ${HEADERCOMPILED_FILE} - --js webodf-compiled.js - --js ${CMAKE_CURRENT_SOURCE_DIR}/lib/externs/*.js - --compilation_level WHITESPACE_ONLY - --js_output_file webodf.js- - COMMAND ${CMAKE_COMMAND} ARGS -E rename webodf.js- webodf.js - DEPENDS ClosureCompiler webodf-compiled.js ${HEADERCOMPILED_FILE} + COMMAND ${NODE} ARGS ${CMAKE_SOURCE_DIR}/scripts/build.js + --output ${CMAKE_CURRENT_BINARY_DIR}/webodf.js + --header ${HEADERCOMPILED_FILE} + DEPENDS ${NODE} ${WEBODF_TYPE_CHECK} manifest.json-target + ${CMAKE_SOURCE_DIR}/scripts/build.js + ${CMAKE_SOURCE_DIR}/scripts/lib/bundle.js + ${CMAKE_SOURCE_DIR}/scripts/lib/sources.js + ${CMAKE_SOURCE_DIR}/scripts/lib/generated.js + ${HEADERCOMPILED_FILE} + ${LIBJSFILES} + # The css of the viewer is written into the library as a string, so a + # change of it is a change of the library: without this, the packages + # were built again with the css of the build before. + ${CMAKE_CURRENT_SOURCE_DIR}/webodf.css + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} ) add_custom_target(webodf.js-target DEPENDS webodf.js) +if (WEBODF_HAS_JAVA) # Compile the debug version of WebODF. This is also published as a product. add_custom_command( OUTPUT webodf-debug-compiled.js @@ -279,63 +307,51 @@ add_custom_command( ) add_custom_target(webodf-debug.js-target DEPENDS webodf-debug.js) -add_custom_target(syntaxcheck ALL - DEPENDS simplecompiled.js-target webodf.js-target - compiled.js-target -) +endif () + +if (WEBODF_HAS_JAVA) + add_custom_target(syntaxcheck ALL + DEPENDS simplecompiled.js-target webodf.js-target + compiled.js-target + ) +else () + add_custom_target(syntaxcheck ALL DEPENDS webodf.js-target) +endif () -# rhino tests are disabled for now: rhino runtime lacks (wrappers for) -# essential necessities such as Uint8Array, Node and NodeFilter -# RHINOTEST is only defined here and hence undefined to disable rhinotest -if (RHINOTEST) -add_custom_target(rhinotest - COMMAND ${Java_JAVA_EXECUTABLE} -jar ${RHINO} - -debug lib/runtime.js tests/tests.js - DEPENDS Rhino - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} - SOURCES ${LIBJSFILES} -) -add_custom_target(simplerhinotest - COMMAND ${Java_JAVA_EXECUTABLE} -jar ${RHINO} - ${CMAKE_CURRENT_BINARY_DIR}/simplecompiled.js - DEPENDS Rhino simplecompiled.js-target - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/tests -) -endif (RHINOTEST) +# The documentation is written by the build with node, with jsdoc 4, as the +# jsdoc-toolkit 2.4.0 of 2011 the build downloaded is abandoned. add_custom_command( OUTPUT docs/index.html - COMMAND ${Java_JAVA_EXECUTABLE} - ARGS -jar ${JSDOCDIR}/jsrun.jar - ${JSDOCDIR}/app/run.js -d=${CMAKE_CURRENT_BINARY_DIR}/docs - -t=${JSDOCDIR}/templates/jsdoc ${LIBJSFILES} - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} - DEPENDS ${LIBJSFILES} JsDoc + COMMAND ${NODE} ARGS ${CMAKE_SOURCE_DIR}/scripts/doc.js + --output ${CMAKE_CURRENT_BINARY_DIR}/docs + DEPENDS ${NODE} ${LIBJSFILES} manifest.json-target + ${CMAKE_SOURCE_DIR}/scripts/doc.js + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} ) add_custom_target(doc DEPENDS docs/index.html) -add_custom_command( - OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/simplenodetest.timestamp - COMMAND ${NODE} ${CMAKE_CURRENT_BINARY_DIR}/simplecompiled.js - COMMAND ${TOUCHFILE} ${CMAKE_CURRENT_BINARY_DIR}/simplenodetest.timestamp - DEPENDS ${NODE} simplecompiled.js copy_node_modules - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/tests -) -add_custom_target(simplenodetest ALL DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/simplenodetest.timestamp) - -if (NOT APPLE) +# The suite is run from the file the compiler wrote, which java writes: a +# machine without java runs the suite from the sources alone, in node and in +# the webengine of qt. +if (WEBODF_HAS_JAVA) add_custom_command( - OUTPUT instrumented/index.html - COMMAND ${JSCOVERAGE} - ARGS --exclude=node_modules - ${CMAKE_CURRENT_SOURCE_DIR} - ${CMAKE_CURRENT_BINARY_DIR}/instrumented - DEPENDS ${LIBJSFILES} JSCoverage + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/simplenodetest.timestamp + # This suite alone runs in the sources, as it reads the library the + # compiler wrote and the documents the sources hold: what it writes + # goes to "tests/out/", that the sources ignore. + COMMAND ${CMAKE_COMMAND} -E make_directory + ${CMAKE_CURRENT_SOURCE_DIR}/tests/out + COMMAND ${NODE} ${CMAKE_CURRENT_BINARY_DIR}/simplecompiled.js + COMMAND ${TOUCHFILE} ${CMAKE_CURRENT_BINARY_DIR}/simplenodetest.timestamp + DEPENDS ${NODE} simplecompiled.js copy_node_modules + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/tests ) - add_custom_target(instrumented DEPENDS instrumented/index.html) -endif(NOT APPLE) - -add_custom_target(webodf.js-tests DEPENDS syntaxcheck simplenodetest) + add_custom_target(test-simplenode ALL DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/simplenodetest.timestamp) + add_custom_target(webodf.js-tests DEPENDS syntaxcheck test-simplenode) +else () + add_custom_target(webodf.js-tests DEPENDS syntaxcheck) +endif () add_custom_command( OUTPUT ${WEBODFJS_DIR} diff --git a/webodf/README b/webodf/README index a4dde2fee..7efa5d8b1 100644 --- a/webodf/README +++ b/webodf/README @@ -1,7 +1,7 @@ This directory contains the javacript code to WebODF as well as applications that use it. The applications are browser applications, commandline applications and unit tests. lib/ -lib/core/ Parts of WebODF that also work on JavaScript runtimes without a +lib/webodfcore/ Parts of WebODF that also work on JavaScript runtimes without a document window. lib/gui/ Parts of WebODF that require a domtree diff --git a/webodf/compare.js b/webodf/compare.js index e58d34595..e53a08183 100644 --- a/webodf/compare.js +++ b/webodf/compare.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global runtime, odf*/ diff --git a/webodf/externs/JSZip.js b/webodf/externs/JSZip.js index 56f7d5737..f30fff1eb 100644 --- a/webodf/externs/JSZip.js +++ b/webodf/externs/JSZip.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global externs*/ diff --git a/webodf/filelister.js b/webodf/filelister.js index 7d98d6121..3049105f7 100644 --- a/webodf/filelister.js +++ b/webodf/filelister.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global XMLHttpRequest*/ diff --git a/webodf/httpserver.js b/webodf/httpserver.js index 8c92bd13a..8386d3e52 100644 --- a/webodf/httpserver.js +++ b/webodf/httpserver.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global require, console, process, Buffer, unescape*/ diff --git a/webodf/lib/HeaderCompiled.js b/webodf/lib/HeaderCompiled.js index 4c5e9c799..f907629e3 100644 --- a/webodf/lib/HeaderCompiled.js +++ b/webodf/lib/HeaderCompiled.js @@ -3,6 +3,7 @@ * This is a generated file. DO NOT EDIT. * * Copyright (C) 2010-2015 KO GmbH + * Copyright (C) 2026 Daniel Berthereau * * @licstart * This file is the compiled version of the WebODF library. @@ -21,6 +22,6 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ diff --git a/webodf/lib/externs/JSZip.js b/webodf/lib/externs/JSZip.js index 4114ee5aa..a27df51bb 100644 --- a/webodf/lib/externs/JSZip.js +++ b/webodf/lib/externs/JSZip.js @@ -1,7 +1,7 @@ /** * @license * @licstart -JSZip - A Javascript class for generating and reading zip files +JSZip v2.6.1 - A Javascript class for generating and reading zip files (c) 2009-2014 Stuart Knightley @@ -9,14 +9,76 @@ Dual licenced under the MIT license or GPLv3. See https://raw.github.com/Stuk/js JSZip uses the library pako released under the MIT license : https://github.com/nodeca/pako/blob/master/LICENSE + +The wrapper of the distribution is replaced, so that the class is attached to +the shared object "externs" instead of the global scope, and so that the +modules it holds do not see the commonjs of node: the library of webodf is +loaded with require(), and jszip would then export itself to it. + +The scope it is attached to is read from "globalThis" first: the "global" of +rhino is an object of its shell and not the scope the scripts share, so the +class was written where nothing could read it, and every test of the zip +failed there. * @licend */ !function(e){ - // if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.JSZip=e()} - var globalScope = typeof window !== "undefined" ? window : (typeof global !== "undefined" ? global : {}), + var globalScope = typeof globalThis !== "undefined" ? globalThis : (typeof window !== "undefined" ? window : (typeof global !== "undefined" ? global : {})), externs = globalScope.externs || (globalScope.externs = {}); externs.JSZip = e(); -}(function(){var define,module,exports;if(define||module||exports){console.log("");}return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o= 0; --i) { + if (this.data[i] === sig0 && this.data[i + 1] === sig1 && this.data[i + 2] === sig2 && this.data[i + 3] === sig3) { + return i - this.zero; + } + } + + return -1; +}; +/** + * @see DataReader.readData + */ +ArrayReader.prototype.readData = function(size) { + this.checkOffset(size); + if(size === 0) { + return []; + } + var result = this.data.slice(this.zero + this.index, this.zero + this.index + size); + this.index += size; + return result; +}; +module.exports = ArrayReader; + +},{"./dataReader":6}],2:[function(require,module,exports){ 'use strict'; // private property var _keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; @@ -88,7 +150,7 @@ exports.decode = function(input, utf8) { }; -},{}],2:[function(_dereq_,module,exports){ +},{}],3:[function(require,module,exports){ 'use strict'; function CompressedObject() { this.compressedSize = 0; @@ -118,11 +180,11 @@ CompressedObject.prototype = { }; module.exports = CompressedObject; -},{}],3:[function(_dereq_,module,exports){ +},{}],4:[function(require,module,exports){ 'use strict'; exports.STORE = { magic: "\x00\x00", - compress: function(content) { + compress: function(content, compressionOptions) { return content; // no compression }, uncompress: function(content) { @@ -131,12 +193,12 @@ exports.STORE = { compressInputType: null, uncompressInputType: null }; -exports.DEFLATE = _dereq_('./flate'); +exports.DEFLATE = require('./flate'); -},{"./flate":8}],4:[function(_dereq_,module,exports){ +},{"./flate":9}],5:[function(require,module,exports){ 'use strict'; -var utils = _dereq_('./utils'); +var utils = require('./utils'); var table = [ 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, @@ -237,14 +299,15 @@ module.exports = function crc32(input, crc) { }; // vim: set shiftwidth=4 softtabstop=4: -},{"./utils":21}],5:[function(_dereq_,module,exports){ +},{"./utils":22}],6:[function(require,module,exports){ 'use strict'; -var utils = _dereq_('./utils'); +var utils = require('./utils'); function DataReader(data) { this.data = null; // type : see implementation this.length = 0; this.index = 0; + this.zero = 0; } DataReader.prototype = { /** @@ -261,7 +324,7 @@ DataReader.prototype = { * @throws {Error} an Error if the index is out of bounds. */ checkIndex: function(newIndex) { - if (this.length < newIndex || newIndex < 0) { + if (this.length < this.zero + newIndex || newIndex < 0) { throw new Error("End of data reached (data length = " + this.length + ", asked index = " + (newIndex) + "). Corrupted zip ?"); } }, @@ -346,7 +409,7 @@ DataReader.prototype = { }; module.exports = DataReader; -},{"./utils":21}],6:[function(_dereq_,module,exports){ +},{"./utils":22}],7:[function(require,module,exports){ 'use strict'; exports.base64 = false; exports.binary = false; @@ -354,11 +417,14 @@ exports.dir = false; exports.createFolders = false; exports.date = null; exports.compression = null; +exports.compressionOptions = null; exports.comment = null; +exports.unixPermissions = null; +exports.dosPermissions = null; -},{}],7:[function(_dereq_,module,exports){ +},{}],8:[function(require,module,exports){ 'use strict'; -var utils = _dereq_('./utils'); +var utils = require('./utils'); /** * @deprecated @@ -463,26 +529,28 @@ exports.isRegExp = function (object) { }; -},{"./utils":21}],8:[function(_dereq_,module,exports){ +},{"./utils":22}],9:[function(require,module,exports){ 'use strict'; var USE_TYPEDARRAY = (typeof Uint8Array !== 'undefined') && (typeof Uint16Array !== 'undefined') && (typeof Uint32Array !== 'undefined'); -var pako = _dereq_("pako"); +var pako = require("pako"); exports.uncompressInputType = USE_TYPEDARRAY ? "uint8array" : "array"; exports.compressInputType = USE_TYPEDARRAY ? "uint8array" : "array"; exports.magic = "\x08\x00"; -exports.compress = function(input) { - return pako.deflateRaw(input); +exports.compress = function(input, compressionOptions) { + return pako.deflateRaw(input, { + level : compressionOptions.level || -1 // default compression + }); }; exports.uncompress = function(input) { return pako.inflateRaw(input); }; -},{"pako":24}],9:[function(_dereq_,module,exports){ +},{"pako":25}],10:[function(require,module,exports){ 'use strict'; -var base64 = _dereq_('./base64'); +var base64 = require('./base64'); /** Usage: @@ -530,16 +598,16 @@ function JSZip(data, options) { return newObj; }; } -JSZip.prototype = _dereq_('./object'); -JSZip.prototype.load = _dereq_('./load'); -JSZip.support = _dereq_('./support'); -JSZip.defaults = _dereq_('./defaults'); +JSZip.prototype = require('./object'); +JSZip.prototype.load = require('./load'); +JSZip.support = require('./support'); +JSZip.defaults = require('./defaults'); /** * @deprecated * This namespace will be removed in a future version without replacement. */ -JSZip.utils = _dereq_('./deprecatedPublicUtils'); +JSZip.utils = require('./deprecatedPublicUtils'); JSZip.base64 = { /** @@ -557,16 +625,24 @@ JSZip.base64 = { return base64.decode(input); } }; -JSZip.compressions = _dereq_('./compressions'); +JSZip.compressions = require('./compressions'); module.exports = JSZip; -},{"./base64":1,"./compressions":3,"./defaults":6,"./deprecatedPublicUtils":7,"./load":10,"./object":13,"./support":17}],10:[function(_dereq_,module,exports){ +},{"./base64":2,"./compressions":4,"./defaults":7,"./deprecatedPublicUtils":8,"./load":11,"./object":14,"./support":18}],11:[function(require,module,exports){ 'use strict'; -var base64 = _dereq_('./base64'); -var ZipEntries = _dereq_('./zipEntries'); +var base64 = require('./base64'); +var utf8 = require('./utf8'); +var utils = require('./utils'); +var ZipEntries = require('./zipEntries'); module.exports = function(data, options) { var files, zipEntries, i, input; - options = options || {}; + options = utils.extend(options || {}, { + base64: false, + checkCRC32: false, + optimizedBinaryString : false, + createFolders: false, + decodeFileName: utf8.utf8decode + }); if (options.base64) { data = base64.decode(data); } @@ -575,12 +651,14 @@ module.exports = function(data, options) { files = zipEntries.files; for (i = 0; i < files.length; i++) { input = files[i]; - this.file(input.fileName, input.decompressed, { + this.file(input.fileNameStr, input.decompressed, { binary: true, optimizedBinaryString: true, date: input.date, dir: input.dir, - comment : input.fileComment.length ? input.fileComment : null, + comment : input.fileCommentStr.length ? input.fileCommentStr : null, + unixPermissions : input.unixPermissions, + dosPermissions : input.dosPermissions, createFolders: options.createFolders }); } @@ -591,24 +669,31 @@ module.exports = function(data, options) { return this; }; -},{"./base64":1,"./zipEntries":22}],11:[function(_dereq_,module,exports){ +},{"./base64":2,"./utf8":21,"./utils":22,"./zipEntries":23}],12:[function(require,module,exports){ (function (Buffer){ 'use strict'; module.exports = function(data, encoding){ - return new Buffer(data, encoding); + // Patched: new Buffer() is deprecated since node 10. A number is a size + // to allocate, that alloc() also fills with zeroes, anything else is the + // content to copy. + return typeof data === "number" + ? Buffer.alloc(data) + : Buffer.from(data, encoding); }; module.exports.test = function(b){ return Buffer.isBuffer(b); }; + }).call(this,(typeof Buffer !== "undefined" ? Buffer : undefined)) -},{}],12:[function(_dereq_,module,exports){ +},{}],13:[function(require,module,exports){ 'use strict'; -var Uint8ArrayReader = _dereq_('./uint8ArrayReader'); +var Uint8ArrayReader = require('./uint8ArrayReader'); function NodeBufferReader(data) { this.data = data; this.length = this.data.length; this.index = 0; + this.zero = 0; } NodeBufferReader.prototype = new Uint8ArrayReader(); @@ -617,26 +702,26 @@ NodeBufferReader.prototype = new Uint8ArrayReader(); */ NodeBufferReader.prototype.readData = function(size) { this.checkOffset(size); - var result = this.data.slice(this.index, this.index + size); + var result = this.data.slice(this.zero + this.index, this.zero + this.index + size); this.index += size; return result; }; module.exports = NodeBufferReader; -},{"./uint8ArrayReader":18}],13:[function(_dereq_,module,exports){ +},{"./uint8ArrayReader":19}],14:[function(require,module,exports){ 'use strict'; -var support = _dereq_('./support'); -var utils = _dereq_('./utils'); -var crc32 = _dereq_('./crc32'); -var signature = _dereq_('./signature'); -var defaults = _dereq_('./defaults'); -var base64 = _dereq_('./base64'); -var compressions = _dereq_('./compressions'); -var CompressedObject = _dereq_('./compressedObject'); -var nodeBuffer = _dereq_('./nodeBuffer'); -var utf8 = _dereq_('./utf8'); -var StringWriter = _dereq_('./stringWriter'); -var Uint8ArrayWriter = _dereq_('./uint8ArrayWriter'); +var support = require('./support'); +var utils = require('./utils'); +var crc32 = require('./crc32'); +var signature = require('./signature'); +var defaults = require('./defaults'); +var base64 = require('./base64'); +var compressions = require('./compressions'); +var CompressedObject = require('./compressedObject'); +var nodeBuffer = require('./nodeBuffer'); +var utf8 = require('./utf8'); +var StringWriter = require('./stringWriter'); +var Uint8ArrayWriter = require('./uint8ArrayWriter'); /** * Returns the raw data of a ZipObject, decompress the content if necessary. @@ -725,6 +810,8 @@ var ZipObject = function(name, data, options) { this.dir = options.dir; this.date = options.date; this.comment = options.comment; + this.unixPermissions = options.unixPermissions; + this.dosPermissions = options.dosPermissions; this._data = data; this.options = options; @@ -797,24 +884,6 @@ var decToHex = function(dec, bytes) { return hex; }; -/** - * Merge the objects passed as parameters into a new one. - * @private - * @param {...Object} var_args All objects to merge. - * @return {Object} a new object with the data of the others. - */ -var extend = function() { - var result = {}, i, attr; - for (i = 0; i < arguments.length; i++) { // arguments is not enumerable in some browsers - for (attr in arguments[i]) { - if (arguments[i].hasOwnProperty(attr) && typeof result[attr] === "undefined") { - result[attr] = arguments[i][attr]; - } - } - } - return result; -}; - /** * Transforms the (incomplete) options from the user into the complete * set of options to create a file. @@ -827,7 +896,7 @@ var prepareFileAttrs = function(o) { if (o.base64 === true && (o.binary === null || o.binary === undefined)) { o.binary = true; } - o = extend(o, defaults); + o = utils.extend(o, defaults); o.date = o.date || new Date(); if (o.compression !== null) o.compression = o.compression.toUpperCase(); @@ -849,6 +918,23 @@ var fileAdd = function(name, data, o) { o = prepareFileAttrs(o); + if (typeof o.unixPermissions === "string") { + o.unixPermissions = parseInt(o.unixPermissions, 8); + } + + // UNX_IFDIR 0040000 see zipinfo.c + if (o.unixPermissions && (o.unixPermissions & 0x4000)) { + o.dir = true; + } + // Bit 4 Directory + if (o.dosPermissions && (o.dosPermissions & 0x0010)) { + o.dir = true; + } + + if (o.dir) { + name = forceTrailingSlash(name); + } + if (o.createFolders && (parent = parentFolder(name))) { folderAdd.call(this, parent, true); } @@ -857,6 +943,7 @@ var fileAdd = function(name, data, o) { o.base64 = false; o.binary = false; data = null; + dataType = null; } else if (dataType === "string") { if (o.binary && !o.base64) { @@ -901,6 +988,20 @@ var parentFolder = function (path) { return (lastSlash > 0) ? path.substring(0, lastSlash) : ""; }; + +/** + * Returns the path with a slash at the end. + * @private + * @param {string} path the path to check. + * @return {string} the path with a trailing slash. + */ +var forceTrailingSlash = function(path) { + // Check the name ends with a / + if (path.slice(-1) != "/") { + path += "/"; // IE doesn't like substr(-1) + } + return path; +}; /** * Add a (sub) folder in the current folder. * @private @@ -910,13 +1011,10 @@ var parentFolder = function (path) { * @return {Object} the new folder. */ var folderAdd = function(name, createFolders) { - // Check the name ends with a / - if (name.slice(-1) != "/") { - name += "/"; // IE doesn't like substr(-1) - } - createFolders = (typeof createFolders !== 'undefined') ? createFolders : false; + name = forceTrailingSlash(name); + // Does this folder already exist? if (!this.files[name]) { fileAdd.call(this, name, null, { @@ -931,9 +1029,10 @@ var folderAdd = function(name, createFolders) { * Generate a JSZip.CompressedObject for a given zipOject. * @param {ZipObject} file the object to read. * @param {JSZip.compression} compression the compression to use. + * @param {Object} compressionOptions the options to use when compressing. * @return {JSZip.CompressedObject} the compressed result. */ -var generateCompressedObjectFrom = function(file, compression) { +var generateCompressedObjectFrom = function(file, compression, compressionOptions) { var result = new CompressedObject(), content; @@ -953,7 +1052,7 @@ var generateCompressedObjectFrom = function(file, compression) { else { content = file._data.getContent(); // need to decompress / recompress - result.compressedContent = compression.compress(utils.transformTo(compression.compressInputType, content)); + result.compressedContent = compression.compress(utils.transformTo(compression.compressInputType, content), compressionOptions); } } else { @@ -965,7 +1064,7 @@ var generateCompressedObjectFrom = function(file, compression) { } result.uncompressedSize = content.length; result.crc32 = crc32(content); - result.compressedContent = compression.compress(utils.transformTo(compression.compressInputType, content)); + result.compressedContent = compression.compress(utils.transformTo(compression.compressInputType, content), compressionOptions); } result.compressedSize = result.compressedContent.length; @@ -974,17 +1073,74 @@ var generateCompressedObjectFrom = function(file, compression) { return result; }; + + + +/** + * Generate the UNIX part of the external file attributes. + * @param {Object} unixPermissions the unix permissions or null. + * @param {boolean} isDir true if the entry is a directory, false otherwise. + * @return {number} a 32 bit integer. + * + * adapted from http://unix.stackexchange.com/questions/14705/the-zip-formats-external-file-attribute : + * + * TTTTsstrwxrwxrwx0000000000ADVSHR + * ^^^^____________________________ file type, see zipinfo.c (UNX_*) + * ^^^_________________________ setuid, setgid, sticky + * ^^^^^^^^^________________ permissions + * ^^^^^^^^^^______ not used ? + * ^^^^^^ DOS attribute bits : Archive, Directory, Volume label, System file, Hidden, Read only + */ +var generateUnixExternalFileAttr = function (unixPermissions, isDir) { + + var result = unixPermissions; + if (!unixPermissions) { + // I can't use octal values in strict mode, hence the hexa. + // 040775 => 0x41fd + // 0100664 => 0x81b4 + result = isDir ? 0x41fd : 0x81b4; + } + + return (result & 0xFFFF) << 16; +}; + +/** + * Generate the DOS part of the external file attributes. + * @param {Object} dosPermissions the dos permissions or null. + * @param {boolean} isDir true if the entry is a directory, false otherwise. + * @return {number} a 32 bit integer. + * + * Bit 0 Read-Only + * Bit 1 Hidden + * Bit 2 System + * Bit 3 Volume Label + * Bit 4 Directory + * Bit 5 Archive + */ +var generateDosExternalFileAttr = function (dosPermissions, isDir) { + + // the dir flag is already set for compatibility + + return (dosPermissions || 0) & 0x3F; +}; + /** * Generate the various parts used in the construction of the final zip file. * @param {string} name the file name. * @param {ZipObject} file the file content. * @param {JSZip.CompressedObject} compressedObject the compressed object. * @param {number} offset the current offset from the start of the zip file. + * @param {string} platform let's pretend we are this platform (change platform dependents fields) + * @param {Function} encodeFileName the function to encode the file name / comment. * @return {object} the zip parts. */ -var generateZipParts = function(name, file, compressedObject, offset) { - var utfEncodedFileName = utils.transformTo("string", utf8.utf8encode(file.name)), +var generateZipParts = function(name, file, compressedObject, offset, platform, encodeFileName) { + var data = compressedObject.compressedContent, + useCustomEncoding = encodeFileName !== utf8.utf8encode, + encodedFileName = utils.transformTo("string", encodeFileName(file.name)), + utfEncodedFileName = utils.transformTo("string", utf8.utf8encode(file.name)), comment = file.comment || "", + encodedComment = utils.transformTo("string", encodeFileName(comment)), utfEncodedComment = utils.transformTo("string", utf8.utf8encode(comment)), useUTF8ForFileName = utfEncodedFileName.length !== file.name.length, useUTF8ForComment = utfEncodedComment.length !== comment.length, @@ -1011,6 +1167,20 @@ var generateZipParts = function(name, file, compressedObject, offset) { date = o.date; } + var extFileAttr = 0; + var versionMadeBy = 0; + if (dir) { + // dos or unix, we set the dos dir flag + extFileAttr |= 0x00010; + } + if(platform === "UNIX") { + versionMadeBy = 0x031E; // UNIX, version 3.0 + extFileAttr |= generateUnixExternalFileAttr(file.unixPermissions, dir); + } else { // DOS or other, fallback to DOS + versionMadeBy = 0x0014; // DOS, version 2.0 + extFileAttr |= generateDosExternalFileAttr(file.dosPermissions, dir); + } + // date // @see http://www.delorie.com/djgpp/doc/rbinter/it/52/13.html // @see http://www.delorie.com/djgpp/doc/rbinter/it/65/16.html @@ -1042,7 +1212,7 @@ var generateZipParts = function(name, file, compressedObject, offset) { // Version decToHex(1, 1) + // NameCRC32 - decToHex(crc32(utfEncodedFileName), 4) + + decToHex(crc32(encodedFileName), 4) + // UnicodeName utfEncodedFileName; @@ -1061,7 +1231,7 @@ var generateZipParts = function(name, file, compressedObject, offset) { // Version decToHex(1, 1) + // CommentCRC32 - decToHex(this.crc32(utfEncodedComment), 4) + + decToHex(this.crc32(encodedComment), 4) + // UnicodeName utfEncodedComment; @@ -1080,7 +1250,7 @@ var generateZipParts = function(name, file, compressedObject, offset) { header += "\x0A\x00"; // general purpose bit flag // set bit 11 if utf8 - header += (useUTF8ForFileName || useUTF8ForComment) ? "\x00\x08" : "\x00\x00"; + header += !useCustomEncoding && (useUTF8ForFileName || useUTF8ForComment) ? "\x00\x08" : "\x00\x00"; // compression method header += compressedObject.compressionMethod; // last mod file time @@ -1094,34 +1264,34 @@ var generateZipParts = function(name, file, compressedObject, offset) { // uncompressed size header += decToHex(compressedObject.uncompressedSize, 4); // file name length - header += decToHex(utfEncodedFileName.length, 2); + header += decToHex(encodedFileName.length, 2); // extra field length header += decToHex(extraFields.length, 2); - var fileRecord = signature.LOCAL_FILE_HEADER + header + utfEncodedFileName + extraFields; + var fileRecord = signature.LOCAL_FILE_HEADER + header + encodedFileName + extraFields; var dirRecord = signature.CENTRAL_FILE_HEADER + // version made by (00: DOS) - "\x14\x00" + + decToHex(versionMadeBy, 2) + // file header (common to file and central directory) header + // file comment length - decToHex(utfEncodedComment.length, 2) + + decToHex(encodedComment.length, 2) + // disk number start "\x00\x00" + // internal file attributes TODO "\x00\x00" + // external file attributes - (dir === true ? "\x10\x00\x00\x00" : "\x00\x00\x00\x00") + + decToHex(extFileAttr, 4) + // relative offset of local header decToHex(offset, 4) + // file name - utfEncodedFileName + + encodedFileName + // extra field extraFields + // file comment - utfEncodedComment; + encodedComment; return { fileRecord: fileRecord, @@ -1161,7 +1331,7 @@ var out = { } file = this.files[filename]; // return a new object, don't let the user mess with our internal objects :) - fileClone = new ZipObject(file.name, file._data, extend(file.options)); + fileClone = new ZipObject(file.name, file._data, utils.extend(file.options)); relativePath = filename.slice(this.root.length, filename.length); if (filename.slice(0, this.root.length) === this.root && // the file is in the current root search(relativePath, fileClone)) { // and the file matches the function @@ -1175,7 +1345,7 @@ var out = { * Add a file to the zip file, or search a file. * @param {string|RegExp} name The name of the file to add (if data is defined), * the name of the file to find (if no data) or a regex to match files. - * @param {string|ArrayBuffer|Uint8Array|Buffer} data The file data, either raw or base64 encoded + * @param {String|ArrayBuffer|Uint8Array|Buffer} data The file data, either raw or base64 encoded * @param {Object} o File options * @return {JSZip|Object|Array} this JSZip object (when adding a file), * a file (when searching by string) or an array of files (when searching by regex). @@ -1203,7 +1373,7 @@ var out = { /** * Add a directory to the zip file, or search. - * @param {string|RegExp} arg The name of the directory to add, or a regex to search folders. + * @param {String|RegExp} arg The name of the directory to add, or a regex to search folders. * @return {JSZip} an object with the new directory as the root, or an array containing matching folders. */ folder: function(arg) { @@ -1268,20 +1438,37 @@ var out = { * @return {string|Uint8Array|ArrayBuffer|Buffer|Blob} the zip file */ generate: function(options) { - options = extend(options || {}, { + options = utils.extend(options || {}, { base64: true, compression: "STORE", + compressionOptions : null, type: "base64", - comment: null + platform: "DOS", + comment: null, + mimeType: 'application/zip', + encodeFileName: utf8.utf8encode }); utils.checkSupport(options.type); + // accept nodejs `process.platform` + if( + options.platform === 'darwin' || + options.platform === 'freebsd' || + options.platform === 'linux' || + options.platform === 'sunos' + ) { + options.platform = "UNIX"; + } + if (options.platform === 'win32') { + options.platform = "DOS"; + } + var zipData = [], localDirLength = 0, centralDirLength = 0, writer, i, - utfEncodedComment = utils.transformTo("string", this.utf8encode(options.comment || this.comment || "")); + encodedComment = utils.transformTo("string", options.encodeFileName(options.comment || this.comment || "")); // first, generate all the zip parts. for (var name in this.files) { @@ -1295,10 +1482,11 @@ var out = { if (!compression) { throw new Error(compressionName + " is not a valid compression method !"); } + var compressionOptions = file.options.compressionOptions || options.compressionOptions || {}; - var compressedObject = generateCompressedObjectFrom.call(this, file, compression); + var compressedObject = generateCompressedObjectFrom.call(this, file, compression, compressionOptions); - var zipPart = generateZipParts.call(this, name, file, compressedObject, localDirLength); + var zipPart = generateZipParts.call(this, name, file, compressedObject, localDirLength, options.platform, options.encodeFileName); localDirLength += zipPart.fileRecord.length + compressedObject.compressedSize; centralDirLength += zipPart.dirRecord.length; zipData.push(zipPart); @@ -1321,9 +1509,9 @@ var out = { // offset of start of central directory with respect to the starting disk number decToHex(localDirLength, 4) + // .ZIP file comment length - decToHex(utfEncodedComment.length, 2) + + decToHex(encodedComment.length, 2) + // .ZIP file comment - utfEncodedComment; + encodedComment; // we have all the parts (and the total length) @@ -1356,7 +1544,7 @@ var out = { case "nodebuffer" : return utils.transformTo(options.type.toLowerCase(), zip); case "blob" : - return utils.arrayBuffer2Blob(utils.transformTo("arraybuffer", zip)); + return utils.arrayBuffer2Blob(utils.transformTo("arraybuffer", zip), options.mimeType); // case "zip is a string" case "base64" : return (options.base64) ? base64.encode(zip) : zip; @@ -1392,7 +1580,7 @@ var out = { }; module.exports = out; -},{"./base64":1,"./compressedObject":2,"./compressions":3,"./crc32":4,"./defaults":6,"./nodeBuffer":11,"./signature":14,"./stringWriter":16,"./support":17,"./uint8ArrayWriter":19,"./utf8":20,"./utils":21}],14:[function(_dereq_,module,exports){ +},{"./base64":2,"./compressedObject":3,"./compressions":4,"./crc32":5,"./defaults":7,"./nodeBuffer":12,"./signature":15,"./stringWriter":17,"./support":18,"./uint8ArrayWriter":20,"./utf8":21,"./utils":22}],15:[function(require,module,exports){ 'use strict'; exports.LOCAL_FILE_HEADER = "PK\x03\x04"; exports.CENTRAL_FILE_HEADER = "PK\x01\x02"; @@ -1401,10 +1589,10 @@ exports.ZIP64_CENTRAL_DIRECTORY_LOCATOR = "PK\x06\x07"; exports.ZIP64_CENTRAL_DIRECTORY_END = "PK\x06\x06"; exports.DATA_DESCRIPTOR = "PK\x07\x08"; -},{}],15:[function(_dereq_,module,exports){ +},{}],16:[function(require,module,exports){ 'use strict'; -var DataReader = _dereq_('./dataReader'); -var utils = _dereq_('./utils'); +var DataReader = require('./dataReader'); +var utils = require('./utils'); function StringReader(data, optimizedBinaryString) { this.data = data; @@ -1413,19 +1601,20 @@ function StringReader(data, optimizedBinaryString) { } this.length = this.data.length; this.index = 0; + this.zero = 0; } StringReader.prototype = new DataReader(); /** * @see DataReader.byteAt */ StringReader.prototype.byteAt = function(i) { - return this.data.charCodeAt(i); + return this.data.charCodeAt(this.zero + i); }; /** * @see DataReader.lastIndexOfSignature */ StringReader.prototype.lastIndexOfSignature = function(sig) { - return this.data.lastIndexOf(sig); + return this.data.lastIndexOf(sig) - this.zero; }; /** * @see DataReader.readData @@ -1433,16 +1622,16 @@ StringReader.prototype.lastIndexOfSignature = function(sig) { StringReader.prototype.readData = function(size) { this.checkOffset(size); // this will work because the constructor applied the "& 0xff" mask. - var result = this.data.slice(this.index, this.index + size); + var result = this.data.slice(this.zero + this.index, this.zero + this.index + size); this.index += size; return result; }; module.exports = StringReader; -},{"./dataReader":5,"./utils":21}],16:[function(_dereq_,module,exports){ +},{"./dataReader":6,"./utils":22}],17:[function(require,module,exports){ 'use strict'; -var utils = _dereq_('./utils'); +var utils = require('./utils'); /** * An object to write any content to a string. @@ -1471,7 +1660,7 @@ StringWriter.prototype = { module.exports = StringWriter; -},{"./utils":21}],17:[function(_dereq_,module,exports){ +},{"./utils":22}],18:[function(require,module,exports){ (function (Buffer){ 'use strict'; exports.base64 = true; @@ -1509,40 +1698,19 @@ else { } }).call(this,(typeof Buffer !== "undefined" ? Buffer : undefined)) -},{}],18:[function(_dereq_,module,exports){ +},{}],19:[function(require,module,exports){ 'use strict'; -var DataReader = _dereq_('./dataReader'); +var ArrayReader = require('./arrayReader'); function Uint8ArrayReader(data) { if (data) { this.data = data; this.length = this.data.length; this.index = 0; + this.zero = 0; } } -Uint8ArrayReader.prototype = new DataReader(); -/** - * @see DataReader.byteAt - */ -Uint8ArrayReader.prototype.byteAt = function(i) { - return this.data[i]; -}; -/** - * @see DataReader.lastIndexOfSignature - */ -Uint8ArrayReader.prototype.lastIndexOfSignature = function(sig) { - var sig0 = sig.charCodeAt(0), - sig1 = sig.charCodeAt(1), - sig2 = sig.charCodeAt(2), - sig3 = sig.charCodeAt(3); - for (var i = this.length - 4; i >= 0; --i) { - if (this.data[i] === sig0 && this.data[i + 1] === sig1 && this.data[i + 2] === sig2 && this.data[i + 3] === sig3) { - return i; - } - } - - return -1; -}; +Uint8ArrayReader.prototype = new ArrayReader(); /** * @see DataReader.readData */ @@ -1552,16 +1720,16 @@ Uint8ArrayReader.prototype.readData = function(size) { // in IE10, when using subarray(idx, idx), we get the array [0x00] instead of []. return new Uint8Array(0); } - var result = this.data.subarray(this.index, this.index + size); + var result = this.data.subarray(this.zero + this.index, this.zero + this.index + size); this.index += size; return result; }; module.exports = Uint8ArrayReader; -},{"./dataReader":5}],19:[function(_dereq_,module,exports){ +},{"./arrayReader":1}],20:[function(require,module,exports){ 'use strict'; -var utils = _dereq_('./utils'); +var utils = require('./utils'); /** * An object to write any content to an Uint8Array. @@ -1596,12 +1764,12 @@ Uint8ArrayWriter.prototype = { module.exports = Uint8ArrayWriter; -},{"./utils":21}],20:[function(_dereq_,module,exports){ +},{"./utils":22}],21:[function(require,module,exports){ 'use strict'; -var utils = _dereq_('./utils'); -var support = _dereq_('./support'); -var nodeBuffer = _dereq_('./nodeBuffer'); +var utils = require('./utils'); +var support = require('./support'); +var nodeBuffer = require('./nodeBuffer'); /** * The following functions come from pako, from pako/lib/utils/strings @@ -1704,7 +1872,7 @@ var utf8border = function(buf, max) { // convert array to string var buf2string = function (buf) { - var i, out, c, c_len; + var str, i, out, c, c_len; var len = buf.length; // Reserve max possible length (2 words per char) @@ -1805,11 +1973,11 @@ exports.utf8decode = function utf8decode(buf) { }; // vim: set shiftwidth=4 softtabstop=4: -},{"./nodeBuffer":11,"./support":17,"./utils":21}],21:[function(_dereq_,module,exports){ +},{"./nodeBuffer":12,"./support":18,"./utils":22}],22:[function(require,module,exports){ 'use strict'; -var support = _dereq_('./support'); -var compressions = _dereq_('./compressions'); -var nodeBuffer = _dereq_('./nodeBuffer'); +var support = require('./support'); +var compressions = require('./compressions'); +var nodeBuffer = require('./nodeBuffer'); /** * Convert a string to a "binary string" : a string containing only char codes between 0 and 255. * @param {string} str the string to transform. @@ -1822,13 +1990,14 @@ exports.string2binary = function(str) { } return result; }; -exports.arrayBuffer2Blob = function(buffer) { +exports.arrayBuffer2Blob = function(buffer, mimeType) { exports.checkSupport("blob"); + mimeType = mimeType || 'application/zip'; try { // Blob constructor return new Blob([buffer], { - type: "application/zip" + type: mimeType }); } catch (e) { @@ -1838,7 +2007,7 @@ exports.arrayBuffer2Blob = function(buffer) { var Builder = window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder || window.MSBlobBuilder; var builder = new Builder(); builder.append(buffer); - return builder.getBlob('application/zip'); + return builder.getBlob(mimeType); } catch (e) { @@ -2131,17 +2300,36 @@ exports.isRegExp = function (object) { return Object.prototype.toString.call(object) === "[object RegExp]"; }; +/** + * Merge the objects passed as parameters into a new one. + * @private + * @param {...Object} var_args All objects to merge. + * @return {Object} a new object with the data of the others. + */ +exports.extend = function() { + var result = {}, i, attr; + for (i = 0; i < arguments.length; i++) { // arguments is not enumerable in some browsers + for (attr in arguments[i]) { + if (arguments[i].hasOwnProperty(attr) && typeof result[attr] === "undefined") { + result[attr] = arguments[i][attr]; + } + } + } + return result; +}; + -},{"./compressions":3,"./nodeBuffer":11,"./support":17}],22:[function(_dereq_,module,exports){ +},{"./compressions":4,"./nodeBuffer":12,"./support":18}],23:[function(require,module,exports){ 'use strict'; -var StringReader = _dereq_('./stringReader'); -var NodeBufferReader = _dereq_('./nodeBufferReader'); -var Uint8ArrayReader = _dereq_('./uint8ArrayReader'); -var utils = _dereq_('./utils'); -var sig = _dereq_('./signature'); -var ZipEntry = _dereq_('./zipEntry'); -var support = _dereq_('./support'); -var jszipProto = _dereq_('./object'); +var StringReader = require('./stringReader'); +var NodeBufferReader = require('./nodeBufferReader'); +var Uint8ArrayReader = require('./uint8ArrayReader'); +var ArrayReader = require('./arrayReader'); +var utils = require('./utils'); +var sig = require('./signature'); +var ZipEntry = require('./zipEntry'); +var support = require('./support'); +var jszipProto = require('./object'); // class ZipEntries {{{ /** * All the entries in the zip file. @@ -2168,6 +2356,20 @@ ZipEntries.prototype = { throw new Error("Corrupted zip or bug : unexpected signature " + "(" + utils.pretty(signature) + ", expected " + utils.pretty(expectedSignature) + ")"); } }, + /** + * Check if the given signature is at the given index. + * @param {number} askedIndex the index to check. + * @param {string} expectedSignature the signature to expect. + * @return {boolean} true if the signature is here, false otherwise. + */ + isSignature: function(askedIndex, expectedSignature) { + var currentIndex = this.reader.index; + this.reader.setIndex(askedIndex); + var signature = this.reader.readString(4); + var result = signature === expectedSignature; + this.reader.setIndex(currentIndex); + return result; + }, /** * Read the end of the central directory. */ @@ -2183,10 +2385,12 @@ ZipEntries.prototype = { // warning : the encoding depends of the system locale // On a linux machine with LANG=en_US.utf8, this field is utf8 encoded. // On a windows machine, this field is encoded with the localized windows code page. - this.zipComment = this.reader.readString(this.zipCommentLength); + var zipComment = this.reader.readData(this.zipCommentLength); + var decodeParamType = support.uint8array ? "uint8array" : "array"; // To get consistent behavior with the generation part, we will assume that - // this is utf8 encoded. - this.zipComment = jszipProto.utf8decode(this.zipComment); + // this is utf8 encoded unless specified otherwise. + var decodeContent = utils.transformTo(decodeParamType, zipComment); + this.zipComment = this.loadOptions.decodeFileName(decodeContent); }, /** * Read the end of the Zip 64 central directory. @@ -2244,6 +2448,7 @@ ZipEntries.prototype = { this.checkSignature(sig.LOCAL_FILE_HEADER); file.readLocalPart(this.reader); file.handleUTF8(); + file.processAttributes(); } }, /** @@ -2260,16 +2465,41 @@ ZipEntries.prototype = { file.readCentralPart(this.reader); this.files.push(file); } + + if (this.centralDirRecords !== this.files.length) { + if (this.centralDirRecords !== 0 && this.files.length === 0) { + // We expected some records but couldn't find ANY. + // This is really suspicious, as if something went wrong. + throw new Error("Corrupted zip or bug: expected " + this.centralDirRecords + " records in central dir, got " + this.files.length); + } else { + // We found some records but not all. + // Something is wrong but we got something for the user: no error here. + // console.warn("expected", this.centralDirRecords, "records in central dir, got", this.files.length); + } + } }, /** * Read the end of central directory. */ readEndOfCentral: function() { var offset = this.reader.lastIndexOfSignature(sig.CENTRAL_DIRECTORY_END); - if (offset === -1) { - throw new Error("Corrupted zip : can't find end of central directory"); + if (offset < 0) { + // Check if the content is a truncated zip or complete garbage. + // A "LOCAL_FILE_HEADER" is not required at the beginning (auto + // extractible zip for example) but it can give a good hint. + // If an ajax request was used without responseType, we will also + // get unreadable data. + var isGarbage = !this.isSignature(0, sig.LOCAL_FILE_HEADER); + + if (isGarbage) { + throw new Error("Can't find end of central directory : is this a zip file ? " + + "If it is, see http://stuk.github.io/jszip/documentation/howto/read_zip.html"); + } else { + throw new Error("Corrupted zip : can't find end of central directory"); + } } this.reader.setIndex(offset); + var endOfCentralDirOffset = offset; this.checkSignature(sig.CENTRAL_DIRECTORY_END); this.readBlockEndOfCentral(); @@ -2298,7 +2528,7 @@ ZipEntries.prototype = { // should look for a zip64 EOCD locator offset = this.reader.lastIndexOfSignature(sig.ZIP64_CENTRAL_DIRECTORY_LOCATOR); - if (offset === -1) { + if (offset < 0) { throw new Error("Corrupted zip : can't find the ZIP64 end of central directory locator"); } this.reader.setIndex(offset); @@ -2306,21 +2536,55 @@ ZipEntries.prototype = { this.readBlockZip64EndOfCentralLocator(); // now the zip64 EOCD record + if (!this.isSignature(this.relativeOffsetEndOfZip64CentralDir, sig.ZIP64_CENTRAL_DIRECTORY_END)) { + // console.warn("ZIP64 end of central directory not where expected."); + this.relativeOffsetEndOfZip64CentralDir = this.reader.lastIndexOfSignature(sig.ZIP64_CENTRAL_DIRECTORY_END); + if (this.relativeOffsetEndOfZip64CentralDir < 0) { + throw new Error("Corrupted zip : can't find the ZIP64 end of central directory"); + } + } this.reader.setIndex(this.relativeOffsetEndOfZip64CentralDir); this.checkSignature(sig.ZIP64_CENTRAL_DIRECTORY_END); this.readBlockZip64EndOfCentral(); } + + var expectedEndOfCentralDirOffset = this.centralDirOffset + this.centralDirSize; + if (this.zip64) { + expectedEndOfCentralDirOffset += 20; // end of central dir 64 locator + expectedEndOfCentralDirOffset += 12 /* should not include the leading 12 bytes */ + this.zip64EndOfCentralSize; + } + + var extraBytes = endOfCentralDirOffset - expectedEndOfCentralDirOffset; + + if (extraBytes > 0) { + // console.warn(extraBytes, "extra bytes at beginning or within zipfile"); + if (this.isSignature(endOfCentralDirOffset, sig.CENTRAL_FILE_HEADER)) { + // The offsets seem wrong, but we have something at the specified offset. + // So… we keep it. + } else { + // the offset is wrong, update the "zero" of the reader + // this happens if data has been prepended (crx files for example) + this.reader.zero = extraBytes; + } + } else if (extraBytes < 0) { + throw new Error("Corrupted zip: missing " + Math.abs(extraBytes) + " bytes."); + } }, prepareReader: function(data) { var type = utils.getTypeOf(data); + utils.checkSupport(type); if (type === "string" && !support.uint8array) { this.reader = new StringReader(data, this.loadOptions.optimizedBinaryString); } else if (type === "nodebuffer") { this.reader = new NodeBufferReader(data); } - else { + else if (support.uint8array) { this.reader = new Uint8ArrayReader(utils.transformTo("uint8array", data)); + } else if (support.array) { + this.reader = new ArrayReader(utils.transformTo("array", data)); + } else { + throw new Error("Unexpected error: unsupported type '" + type + "'"); } }, /** @@ -2337,12 +2601,17 @@ ZipEntries.prototype = { // }}} end of ZipEntries module.exports = ZipEntries; -},{"./nodeBufferReader":12,"./object":13,"./signature":14,"./stringReader":15,"./support":17,"./uint8ArrayReader":18,"./utils":21,"./zipEntry":23}],23:[function(_dereq_,module,exports){ +},{"./arrayReader":1,"./nodeBufferReader":13,"./object":14,"./signature":15,"./stringReader":16,"./support":18,"./uint8ArrayReader":19,"./utils":22,"./zipEntry":24}],24:[function(require,module,exports){ 'use strict'; -var StringReader = _dereq_('./stringReader'); -var utils = _dereq_('./utils'); -var CompressedObject = _dereq_('./compressedObject'); -var jszipProto = _dereq_('./object'); +var StringReader = require('./stringReader'); +var utils = require('./utils'); +var CompressedObject = require('./compressedObject'); +var jszipProto = require('./object'); +var support = require('./support'); + +var MADE_BY_DOS = 0x00; +var MADE_BY_UNIX = 0x03; + // class ZipEntry {{{ /** * An entry in the zip file. @@ -2436,7 +2705,7 @@ ZipEntry.prototype = { // Unfortunately, this lead also to some issues : http://seclists.org/fulldisclosure/2009/Sep/394 this.fileNameLength = reader.readInt(2); localExtraFieldsLength = reader.readInt(2); // can't be sure this will be the same as the central dir - this.fileName = reader.readString(this.fileNameLength); + this.fileName = reader.readData(this.fileNameLength); reader.skip(localExtraFieldsLength); if (this.compressedSize == -1 || this.uncompressedSize == -1) { @@ -2445,7 +2714,7 @@ ZipEntry.prototype = { compression = utils.findCompression(this.compressionMethod); if (compression === null) { // no compression found - throw new Error("Corrupted zip : compression " + utils.pretty(this.compressionMethod) + " unknown (inner file : " + this.fileName + ")"); + throw new Error("Corrupted zip : compression " + utils.pretty(this.compressionMethod) + " unknown (inner file : " + utils.transformTo("string", this.fileName) + ")"); } this.decompressed = new CompressedObject(); this.decompressed.compressedSize = this.compressedSize; @@ -2469,7 +2738,7 @@ ZipEntry.prototype = { * @param {DataReader} reader the reader to use. */ readCentralPart: function(reader) { - this.versionMadeBy = reader.readString(2); + this.versionMadeBy = reader.readInt(2); this.versionNeeded = reader.readInt(2); this.bitFlag = reader.readInt(2); this.compressionMethod = reader.readString(2); @@ -2489,14 +2758,41 @@ ZipEntry.prototype = { throw new Error("Encrypted zip are not supported"); } - this.fileName = reader.readString(this.fileNameLength); + this.fileName = reader.readData(this.fileNameLength); this.readExtraFields(reader); this.parseZIP64ExtraField(reader); - this.fileComment = reader.readString(this.fileCommentLength); + this.fileComment = reader.readData(this.fileCommentLength); + }, + + /** + * Parse the external file attributes and get the unix/dos permissions. + */ + processAttributes: function () { + this.unixPermissions = null; + this.dosPermissions = null; + var madeBy = this.versionMadeBy >> 8; + + // Check if we have the DOS directory flag set. + // We look for it in the DOS and UNIX permissions + // but some unknown platform could set it as a compatibility flag. + this.dir = this.externalFileAttributes & 0x0010 ? true : false; + + if(madeBy === MADE_BY_DOS) { + // first 6 bits (0 to 5) + this.dosPermissions = this.externalFileAttributes & 0x3F; + } + + if(madeBy === MADE_BY_UNIX) { + this.unixPermissions = (this.externalFileAttributes >> 16) & 0xFFFF; + // the octal permissions are in (this.unixPermissions & 0x01FF).toString(8); + } - // warning, this is true only for zip with madeBy == DOS (plateform dependent feature) - this.dir = this.externalFileAttributes & 0x00000010 ? true : false; + // fail safe : if the name ends with a / it probably means a folder + if (!this.dir && this.fileNameStr.slice(-1) === '/') { + this.dir = true; + } }, + /** * Parse the ZIP64 extra field and merge the info in the current ZipEntry. * @param {DataReader} reader the reader to use. @@ -2553,17 +2849,25 @@ ZipEntry.prototype = { * Apply an UTF8 transformation if needed. */ handleUTF8: function() { + var decodeParamType = support.uint8array ? "uint8array" : "array"; if (this.useUTF8()) { - this.fileName = jszipProto.utf8decode(this.fileName); - this.fileComment = jszipProto.utf8decode(this.fileComment); + this.fileNameStr = jszipProto.utf8decode(this.fileName); + this.fileCommentStr = jszipProto.utf8decode(this.fileComment); } else { var upath = this.findExtraFieldUnicodePath(); if (upath !== null) { - this.fileName = upath; + this.fileNameStr = upath; + } else { + var fileNameByteArray = utils.transformTo(decodeParamType, this.fileName); + this.fileNameStr = this.loadOptions.decodeFileName(fileNameByteArray); } + var ucomment = this.findExtraFieldUnicodeComment(); if (ucomment !== null) { - this.fileComment = ucomment; + this.fileCommentStr = ucomment; + } else { + var commentByteArray = utils.transformTo(decodeParamType, this.fileComment); + this.fileCommentStr = this.loadOptions.decodeFileName(commentByteArray); } } }, @@ -2618,31 +2922,33 @@ ZipEntry.prototype = { }; module.exports = ZipEntry; -},{"./compressedObject":2,"./object":13,"./stringReader":15,"./utils":21}],24:[function(_dereq_,module,exports){ +},{"./compressedObject":3,"./object":14,"./stringReader":16,"./support":18,"./utils":22}],25:[function(require,module,exports){ // Top level file is just a mixin of submodules & constants 'use strict'; -var assign = _dereq_('./lib/utils/common').assign; +var assign = require('./lib/utils/common').assign; -var deflate = _dereq_('./lib/deflate'); -var inflate = _dereq_('./lib/inflate'); -var constants = _dereq_('./lib/zlib/constants'); +var deflate = require('./lib/deflate'); +var inflate = require('./lib/inflate'); +var constants = require('./lib/zlib/constants'); var pako = {}; assign(pako, deflate, inflate, constants); module.exports = pako; -},{"./lib/deflate":25,"./lib/inflate":26,"./lib/utils/common":27,"./lib/zlib/constants":30}],25:[function(_dereq_,module,exports){ + +},{"./lib/deflate":26,"./lib/inflate":27,"./lib/utils/common":28,"./lib/zlib/constants":31}],26:[function(require,module,exports){ 'use strict'; -var zlib_deflate = _dereq_('./zlib/deflate.js'); -var utils = _dereq_('./utils/common'); -var strings = _dereq_('./utils/strings'); -var msg = _dereq_('./zlib/messages'); -var zstream = _dereq_('./zlib/zstream'); +var zlib_deflate = require('./zlib/deflate'); +var utils = require('./utils/common'); +var strings = require('./utils/strings'); +var msg = require('./zlib/messages'); +var ZStream = require('./zlib/zstream'); +var toString = Object.prototype.toString; /* Public constants ==========================================================*/ /* ===========================================================================*/ @@ -2652,6 +2958,7 @@ var Z_FINISH = 4; var Z_OK = 0; var Z_STREAM_END = 1; +var Z_SYNC_FLUSH = 2; var Z_DEFAULT_COMPRESSION = -1; @@ -2681,7 +2988,9 @@ var Z_DEFLATED = 8; * * Compressed result, generated by default [[Deflate#onData]] * and [[Deflate#onEnd]] handlers. Filled after you push last chunk - * (call [[Deflate#push]] with `Z_FINISH` / `true` param). + * (call [[Deflate#push]] with `Z_FINISH` / `true` param) or if you + * push a chunk with explicit flush (call [[Deflate#push]] with + * `Z_SYNC_FLUSH` param). **/ /** @@ -2711,6 +3020,7 @@ var Z_DEFLATED = 8; * - `windowBits` * - `memLevel` * - `strategy` + * - `dictionary` * * [http://zlib.net/manual.html#Advanced](http://zlib.net/manual.html#Advanced) * for more information on these. @@ -2748,7 +3058,8 @@ var Z_DEFLATED = 8; * console.log(deflate.result); * ``` **/ -var Deflate = function(options) { +function Deflate(options) { + if (!(this instanceof Deflate)) return new Deflate(options); this.options = utils.assign({ level: Z_DEFAULT_COMPRESSION, @@ -2775,7 +3086,7 @@ var Deflate = function(options) { this.ended = false; // used to avoid multiple onEnd() calls this.chunks = []; // chunks of compressed data - this.strm = new zstream(); + this.strm = new ZStream(); this.strm.avail_out = 0; var status = zlib_deflate.deflateInit2( @@ -2794,19 +3105,41 @@ var Deflate = function(options) { if (opt.header) { zlib_deflate.deflateSetHeader(this.strm, opt.header); } -}; + + if (opt.dictionary) { + var dict; + // Convert data if needed + if (typeof opt.dictionary === 'string') { + // If we need to compress text, change encoding to utf8. + dict = strings.string2buf(opt.dictionary); + } else if (toString.call(opt.dictionary) === '[object ArrayBuffer]') { + dict = new Uint8Array(opt.dictionary); + } else { + dict = opt.dictionary; + } + + status = zlib_deflate.deflateSetDictionary(this.strm, dict); + + if (status !== Z_OK) { + throw new Error(msg[status]); + } + + this._dict_set = true; + } +} /** * Deflate#push(data[, mode]) -> Boolean - * - data (Uint8Array|Array|String): input data. Strings will be converted to - * utf8 byte sequence. + * - data (Uint8Array|Array|ArrayBuffer|String): input data. Strings will be + * converted to utf8 byte sequence. * - mode (Number|Boolean): 0..6 for corresponding Z_NO_FLUSH..Z_TREE modes. * See constants. Skipped or `false` means Z_NO_FLUSH, `true` meansh Z_FINISH. * * Sends input data to deflate pipe, generating [[Deflate#onData]] calls with * new compressed chunks. Returns `true` on success. The last data block must have - * mode Z_FINISH (or `true`). That flush internal pending buffers and call - * [[Deflate#onEnd]]. + * mode Z_FINISH (or `true`). That will flush internal pending buffers and call + * [[Deflate#onEnd]]. For interim explicit flushes (without ending the stream) you + * can use mode Z_SYNC_FLUSH, keeping the compression context. * * On fail call [[Deflate#onEnd]] with error code and return false. * @@ -2824,7 +3157,7 @@ var Deflate = function(options) { * push(chunk, true); // push last chunk * ``` **/ -Deflate.prototype.push = function(data, mode) { +Deflate.prototype.push = function (data, mode) { var strm = this.strm; var chunkSize = this.options.chunkSize; var status, _mode; @@ -2837,6 +3170,8 @@ Deflate.prototype.push = function(data, mode) { if (typeof data === 'string') { // If we need to compress text, change encoding to utf8. strm.input = strings.string2buf(data); + } else if (toString.call(data) === '[object ArrayBuffer]') { + strm.input = new Uint8Array(data); } else { strm.input = data; } @@ -2857,7 +3192,7 @@ Deflate.prototype.push = function(data, mode) { this.ended = true; return false; } - if (strm.avail_out === 0 || (strm.avail_in === 0 && _mode === Z_FINISH)) { + if (strm.avail_out === 0 || (strm.avail_in === 0 && (_mode === Z_FINISH || _mode === Z_SYNC_FLUSH))) { if (this.options.to === 'string') { this.onData(strings.buf2binstring(utils.shrinkBuf(strm.output, strm.next_out))); } else { @@ -2874,6 +3209,13 @@ Deflate.prototype.push = function(data, mode) { return status === Z_OK; } + // callback interim results if Z_SYNC_FLUSH. + if (_mode === Z_SYNC_FLUSH) { + this.onEnd(Z_OK); + strm.avail_out = 0; + return true; + } + return true; }; @@ -2887,7 +3229,7 @@ Deflate.prototype.push = function(data, mode) { * By default, stores data blocks in `chunks[]` property and glue * those in `onEnd`. Override this handler, if you need another behaviour. **/ -Deflate.prototype.onData = function(chunk) { +Deflate.prototype.onData = function (chunk) { this.chunks.push(chunk); }; @@ -2897,11 +3239,12 @@ Deflate.prototype.onData = function(chunk) { * - status (Number): deflate status. 0 (Z_OK) on success, * other if not. * - * Called once after you tell deflate that input stream complete - * or error happenned. By default - join collected chunks, + * Called once after you tell deflate that the input stream is + * complete (Z_FINISH) or should be flushed (Z_SYNC_FLUSH) + * or if an error happened. By default - join collected chunks, * free memory and fill `results` / `err` properties. **/ -Deflate.prototype.onEnd = function(status) { +Deflate.prototype.onEnd = function (status) { // On success - join if (status === Z_OK) { if (this.options.to === 'string') { @@ -2921,7 +3264,7 @@ Deflate.prototype.onEnd = function(status) { * - data (Uint8Array|Array|String): input data to compress. * - options (Object): zlib deflate options. * - * Compress `data` with deflate alrorythm and `options`. + * Compress `data` with deflate algorithm and `options`. * * Supported options are: * @@ -2929,6 +3272,7 @@ Deflate.prototype.onEnd = function(status) { * - windowBits * - memLevel * - strategy + * - dictionary * * [http://zlib.net/manual.html#Advanced](http://zlib.net/manual.html#Advanced) * for more information on these. @@ -2995,18 +3339,20 @@ exports.Deflate = Deflate; exports.deflate = deflate; exports.deflateRaw = deflateRaw; exports.gzip = gzip; -},{"./utils/common":27,"./utils/strings":28,"./zlib/deflate.js":32,"./zlib/messages":37,"./zlib/zstream":39}],26:[function(_dereq_,module,exports){ + +},{"./utils/common":28,"./utils/strings":29,"./zlib/deflate":33,"./zlib/messages":38,"./zlib/zstream":40}],27:[function(require,module,exports){ 'use strict'; -var zlib_inflate = _dereq_('./zlib/inflate.js'); -var utils = _dereq_('./utils/common'); -var strings = _dereq_('./utils/strings'); -var c = _dereq_('./zlib/constants'); -var msg = _dereq_('./zlib/messages'); -var zstream = _dereq_('./zlib/zstream'); -var gzheader = _dereq_('./zlib/gzheader'); +var zlib_inflate = require('./zlib/inflate'); +var utils = require('./utils/common'); +var strings = require('./utils/strings'); +var c = require('./zlib/constants'); +var msg = require('./zlib/messages'); +var ZStream = require('./zlib/zstream'); +var GZheader = require('./zlib/gzheader'); +var toString = Object.prototype.toString; /** * class Inflate @@ -3027,7 +3373,9 @@ var gzheader = _dereq_('./zlib/gzheader'); * * Uncompressed result, generated by default [[Inflate#onData]] * and [[Inflate#onEnd]] handlers. Filled after you push last chunk - * (call [[Inflate#push]] with `Z_FINISH` / `true` param). + * (call [[Inflate#push]] with `Z_FINISH` / `true` param) or if you + * push a chunk with explicit flush (call [[Inflate#push]] with + * `Z_SYNC_FLUSH` param). **/ /** @@ -3052,6 +3400,7 @@ var gzheader = _dereq_('./zlib/gzheader'); * on bad params. Supported options: * * - `windowBits` + * - `dictionary` * * [http://zlib.net/manual.html#Advanced](http://zlib.net/manual.html#Advanced) * for more information on these. @@ -3084,7 +3433,8 @@ var gzheader = _dereq_('./zlib/gzheader'); * console.log(inflate.result); * ``` **/ -var Inflate = function(options) { +function Inflate(options) { + if (!(this instanceof Inflate)) return new Inflate(options); this.options = utils.assign({ chunkSize: 16384, @@ -3122,7 +3472,7 @@ var Inflate = function(options) { this.ended = false; // used to avoid multiple onEnd() calls this.chunks = []; // chunks of compressed data - this.strm = new zstream(); + this.strm = new ZStream(); this.strm.avail_out = 0; var status = zlib_inflate.inflateInit2( @@ -3134,21 +3484,22 @@ var Inflate = function(options) { throw new Error(msg[status]); } - this.header = new gzheader(); + this.header = new GZheader(); zlib_inflate.inflateGetHeader(this.strm, this.header); -}; +} /** * Inflate#push(data[, mode]) -> Boolean - * - data (Uint8Array|Array|String): input data + * - data (Uint8Array|Array|ArrayBuffer|String): input data * - mode (Number|Boolean): 0..6 for corresponding Z_NO_FLUSH..Z_TREE modes. * See constants. Skipped or `false` means Z_NO_FLUSH, `true` meansh Z_FINISH. * * Sends input data to inflate pipe, generating [[Inflate#onData]] calls with * new output chunks. Returns `true` on success. The last data block must have - * mode Z_FINISH (or `true`). That flush internal pending buffers and call - * [[Inflate#onEnd]]. + * mode Z_FINISH (or `true`). That will flush internal pending buffers and call + * [[Inflate#onEnd]]. For interim explicit flushes (without ending the stream) you + * can use mode Z_SYNC_FLUSH, keeping the decompression context. * * On fail call [[Inflate#onEnd]] with error code and return false. * @@ -3166,11 +3517,17 @@ var Inflate = function(options) { * push(chunk, true); // push last chunk * ``` **/ -Inflate.prototype.push = function(data, mode) { +Inflate.prototype.push = function (data, mode) { var strm = this.strm; var chunkSize = this.options.chunkSize; + var dictionary = this.options.dictionary; var status, _mode; var next_out_utf8, tail, utf8str; + var dict; + + // Flag to properly process Z_BUF_ERROR on testing inflate call + // when we check that all output data was flushed. + var allowBufError = false; if (this.ended) { return false; } _mode = (mode === ~~mode) ? mode : ((mode === true) ? c.Z_FINISH : c.Z_NO_FLUSH); @@ -3179,6 +3536,8 @@ Inflate.prototype.push = function(data, mode) { if (typeof data === 'string') { // Only binary strings can be decompressed on practice strm.input = strings.binstring2buf(data); + } else if (toString.call(data) === '[object ArrayBuffer]') { + strm.input = new Uint8Array(data); } else { strm.input = data; } @@ -3195,6 +3554,25 @@ Inflate.prototype.push = function(data, mode) { status = zlib_inflate.inflate(strm, c.Z_NO_FLUSH); /* no bad return value */ + if (status === c.Z_NEED_DICT && dictionary) { + // Convert data if needed + if (typeof dictionary === 'string') { + dict = strings.string2buf(dictionary); + } else if (toString.call(dictionary) === '[object ArrayBuffer]') { + dict = new Uint8Array(dictionary); + } else { + dict = dictionary; + } + + status = zlib_inflate.inflateSetDictionary(this.strm, dict); + + } + + if (status === c.Z_BUF_ERROR && allowBufError === true) { + status = c.Z_OK; + allowBufError = false; + } + if (status !== c.Z_STREAM_END && status !== c.Z_OK) { this.onEnd(status); this.ended = true; @@ -3202,7 +3580,7 @@ Inflate.prototype.push = function(data, mode) { } if (strm.next_out) { - if (strm.avail_out === 0 || status === c.Z_STREAM_END || (strm.avail_in === 0 && _mode === c.Z_FINISH)) { + if (strm.avail_out === 0 || status === c.Z_STREAM_END || (strm.avail_in === 0 && (_mode === c.Z_FINISH || _mode === c.Z_SYNC_FLUSH))) { if (this.options.to === 'string') { @@ -3223,11 +3601,24 @@ Inflate.prototype.push = function(data, mode) { } } } - } while ((strm.avail_in > 0) && status !== c.Z_STREAM_END); + + // When no more input data, we should check that internal inflate buffers + // are flushed. The only way to do it when avail_out = 0 - run one more + // inflate pass. But if output data not exists, inflate return Z_BUF_ERROR. + // Here we set flag to process this error properly. + // + // NOTE. Deflate does not return error in this case and does not needs such + // logic. + if (strm.avail_in === 0 && strm.avail_out === 0) { + allowBufError = true; + } + + } while ((strm.avail_in > 0 || strm.avail_out === 0) && status !== c.Z_STREAM_END); if (status === c.Z_STREAM_END) { _mode = c.Z_FINISH; } + // Finalize on the last chunk. if (_mode === c.Z_FINISH) { status = zlib_inflate.inflateEnd(this.strm); @@ -3236,6 +3627,13 @@ Inflate.prototype.push = function(data, mode) { return status === c.Z_OK; } + // callback interim results if Z_SYNC_FLUSH. + if (_mode === c.Z_SYNC_FLUSH) { + this.onEnd(c.Z_OK); + strm.avail_out = 0; + return true; + } + return true; }; @@ -3249,7 +3647,7 @@ Inflate.prototype.push = function(data, mode) { * By default, stores data blocks in `chunks[]` property and glue * those in `onEnd`. Override this handler, if you need another behaviour. **/ -Inflate.prototype.onData = function(chunk) { +Inflate.prototype.onData = function (chunk) { this.chunks.push(chunk); }; @@ -3259,11 +3657,12 @@ Inflate.prototype.onData = function(chunk) { * - status (Number): inflate status. 0 (Z_OK) on success, * other if not. * - * Called once after you tell inflate that input stream complete - * or error happenned. By default - join collected chunks, + * Called either after you tell inflate that the input stream is + * complete (Z_FINISH) or should be flushed (Z_SYNC_FLUSH) + * or if an error happened. By default - join collected chunks, * free memory and fill `results` / `err` properties. **/ -Inflate.prototype.onEnd = function(status) { +Inflate.prototype.onEnd = function (status) { // On success - join if (status === c.Z_OK) { if (this.options.to === 'string') { @@ -3361,7 +3760,7 @@ exports.inflate = inflate; exports.inflateRaw = inflateRaw; exports.ungzip = inflate; -},{"./utils/common":27,"./utils/strings":28,"./zlib/constants":30,"./zlib/gzheader":33,"./zlib/inflate.js":35,"./zlib/messages":37,"./zlib/zstream":39}],27:[function(_dereq_,module,exports){ +},{"./utils/common":28,"./utils/strings":29,"./zlib/constants":31,"./zlib/gzheader":34,"./zlib/inflate":36,"./zlib/messages":38,"./zlib/zstream":40}],28:[function(require,module,exports){ 'use strict'; @@ -3376,7 +3775,7 @@ exports.assign = function (obj /*from1, from2, from3, ...*/) { var source = sources.shift(); if (!source) { continue; } - if (typeof(source) !== 'object') { + if (typeof source !== 'object') { throw new TypeError(source + 'must be non-object'); } @@ -3403,28 +3802,28 @@ exports.shrinkBuf = function (buf, size) { var fnTyped = { arraySet: function (dest, src, src_offs, len, dest_offs) { if (src.subarray && dest.subarray) { - dest.set(src.subarray(src_offs, src_offs+len), dest_offs); + dest.set(src.subarray(src_offs, src_offs + len), dest_offs); return; } // Fallback to ordinary array - for(var i=0; i= 252 ? 6 : i >= 248 ? 5 : i >= 240 ? 4 : i >= 224 ? 3 : i >= 192 ? 2 : 1); +for (var q = 0; q < 256; q++) { + _utf8len[q] = (q >= 252 ? 6 : q >= 248 ? 5 : q >= 240 ? 4 : q >= 224 ? 3 : q >= 192 ? 2 : 1); } -_utf8len[254]=_utf8len[254]=1; // Invalid sequence start +_utf8len[254] = _utf8len[254] = 1; // Invalid sequence start // convert string to array (typed, when possible) @@ -3501,8 +3901,8 @@ exports.string2buf = function (str) { // count binary size for (m_pos = 0; m_pos < str_len; m_pos++) { c = str.charCodeAt(m_pos); - if ((c & 0xfc00) === 0xd800 && (m_pos+1 < str_len)) { - c2 = str.charCodeAt(m_pos+1); + if ((c & 0xfc00) === 0xd800 && (m_pos + 1 < str_len)) { + c2 = str.charCodeAt(m_pos + 1); if ((c2 & 0xfc00) === 0xdc00) { c = 0x10000 + ((c - 0xd800) << 10) + (c2 - 0xdc00); m_pos++; @@ -3515,10 +3915,10 @@ exports.string2buf = function (str) { buf = new utils.Buf8(buf_len); // convert - for (i=0, m_pos = 0; i < buf_len; m_pos++) { + for (i = 0, m_pos = 0; i < buf_len; m_pos++) { c = str.charCodeAt(m_pos); - if ((c & 0xfc00) === 0xd800 && (m_pos+1 < str_len)) { - c2 = str.charCodeAt(m_pos+1); + if ((c & 0xfc00) === 0xd800 && (m_pos + 1 < str_len)) { + c2 = str.charCodeAt(m_pos + 1); if ((c2 & 0xfc00) === 0xdc00) { c = 0x10000 + ((c - 0xd800) << 10) + (c2 - 0xdc00); m_pos++; @@ -3558,7 +3958,7 @@ function buf2binstring(buf, len) { } var result = ''; - for(var i=0; i < len; i++) { + for (var i = 0; i < len; i++) { result += String.fromCharCode(buf[i]); } return result; @@ -3566,15 +3966,15 @@ function buf2binstring(buf, len) { // Convert byte array to binary string -exports.buf2binstring = function(buf) { +exports.buf2binstring = function (buf) { return buf2binstring(buf, buf.length); }; // Convert binary string (typed, when possible) -exports.binstring2buf = function(str) { +exports.binstring2buf = function (str) { var buf = new utils.Buf8(str.length); - for(var i=0, len=buf.length; i < len; i++) { + for (var i = 0, len = buf.length; i < len; i++) { buf[i] = str.charCodeAt(i); } return buf; @@ -3589,16 +3989,16 @@ exports.buf2string = function (buf, max) { // Reserve max possible length (2 words per char) // NB: by unknown reasons, Array is significantly faster for // String.fromCharCode.apply than Uint16Array. - var utf16buf = new Array(len*2); + var utf16buf = new Array(len * 2); - for (out=0, i=0; i 4) { utf16buf[out++] = 0xfffd; i += c_len-1; continue; } + if (c_len > 4) { utf16buf[out++] = 0xfffd; i += c_len - 1; continue; } // apply mask on first byte c &= c_len === 2 ? 0x1f : c_len === 3 ? 0x0f : 0x07; @@ -3630,14 +4030,14 @@ exports.buf2string = function (buf, max) { // // buf[] - utf8 bytes array // max - length limit (mandatory); -exports.utf8border = function(buf, max) { +exports.utf8border = function (buf, max) { var pos; max = max || buf.length; if (max > buf.length) { max = buf.length; } // go back from last position, until start of sequence found - pos = max-1; + pos = max - 1; while (pos >= 0 && (buf[pos] & 0xC0) === 0x80) { pos--; } // Fuckup - very small and broken sequence, @@ -3651,7 +4051,7 @@ exports.utf8border = function(buf, max) { return (pos + _utf8len[buf[pos]] > max) ? pos : max; }; -},{"./common":27}],29:[function(_dereq_,module,exports){ +},{"./common":28}],30:[function(require,module,exports){ 'use strict'; // Note: adler32 takes 12% for level 0 and 2% for level 6. @@ -3659,9 +4059,9 @@ exports.utf8border = function(buf, max) { // Small size is preferable. function adler32(adler, buf, len, pos) { - var s1 = (adler & 0xffff) |0 - , s2 = ((adler >>> 16) & 0xffff) |0 - , n = 0; + var s1 = (adler & 0xffff) |0, + s2 = ((adler >>> 16) & 0xffff) |0, + n = 0; while (len !== 0) { // Set limit ~ twice less than 5552, to keep @@ -3684,7 +4084,11 @@ function adler32(adler, buf, len, pos) { module.exports = adler32; -},{}],30:[function(_dereq_,module,exports){ + +},{}],31:[function(require,module,exports){ +'use strict'; + + module.exports = { /* Allowed flush values; see deflate() and inflate() below for details */ @@ -3732,7 +4136,8 @@ module.exports = { Z_DEFLATED: 8 //Z_NULL: null // Use -1 or null inline, depending on var type }; -},{}],31:[function(_dereq_,module,exports){ + +},{}],32:[function(require,module,exports){ 'use strict'; // Note: we can't get significant speed boost here. @@ -3744,10 +4149,10 @@ module.exports = { function makeTable() { var c, table = []; - for(var n =0; n < 256; n++){ + for (var n = 0; n < 256; n++) { c = n; - for(var k =0; k < 8; k++){ - c = ((c&1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1)); + for (var k = 0; k < 8; k++) { + c = ((c & 1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1)); } table[n] = c; } @@ -3760,12 +4165,12 @@ var crcTable = makeTable(); function crc32(crc, buf, len, pos) { - var t = crcTable - , end = pos + len; + var t = crcTable, + end = pos + len; - crc = crc ^ (-1); + crc ^= -1; - for (var i = pos; i < end; i++ ) { + for (var i = pos; i < end; i++) { crc = (crc >>> 8) ^ t[(crc ^ buf[i]) & 0xFF]; } @@ -3774,14 +4179,15 @@ function crc32(crc, buf, len, pos) { module.exports = crc32; -},{}],32:[function(_dereq_,module,exports){ + +},{}],33:[function(require,module,exports){ 'use strict'; -var utils = _dereq_('../utils/common'); -var trees = _dereq_('./trees'); -var adler32 = _dereq_('./adler32'); -var crc32 = _dereq_('./crc32'); -var msg = _dereq_('./messages'); +var utils = require('../utils/common'); +var trees = require('./trees'); +var adler32 = require('./adler32'); +var crc32 = require('./crc32'); +var msg = require('./messages'); /* Public constants ==========================================================*/ /* ===========================================================================*/ @@ -3854,7 +4260,7 @@ var D_CODES = 30; /* number of distance codes */ var BL_CODES = 19; /* number of codes used to transfer the bit lengths */ -var HEAP_SIZE = 2*L_CODES + 1; +var HEAP_SIZE = 2 * L_CODES + 1; /* maximum heap size */ var MAX_BITS = 15; /* All codes must not exceed MAX_BITS bits */ @@ -3920,7 +4326,7 @@ function flush_pending(strm) { } -function flush_block_only (s, last) { +function flush_block_only(s, last) { trees._tr_flush_block(s, (s.block_start >= 0 ? s.block_start : -1), s.strstart - s.block_start, last); s.block_start = s.strstart; flush_pending(s.strm); @@ -3960,6 +4366,7 @@ function read_buf(strm, buf, start, size) { strm.avail_in -= len; + // zmemcpy(buf, strm->next_in, len); utils.arraySet(buf, strm.input, strm.next_in, len, start); if (strm.state.wrap === 1) { strm.adler = adler32(strm.adler, buf, len, start); @@ -4190,7 +4597,7 @@ function fill_window(s) { //#endif while (s.insert) { /* UPDATE_HASH(s, s->ins_h, s->window[str + MIN_MATCH-1]); */ - s.ins_h = ((s.ins_h << s.hash_shift) ^ s.window[str + MIN_MATCH-1]) & s.hash_mask; + s.ins_h = ((s.ins_h << s.hash_shift) ^ s.window[str + MIN_MATCH - 1]) & s.hash_mask; s.prev[str & s.w_mask] = s.head[s.ins_h]; s.head[s.ins_h] = str; @@ -4454,7 +4861,7 @@ function deflate_fast(s, flush) { /***/ } } - s.insert = ((s.strstart < (MIN_MATCH-1)) ? s.strstart : MIN_MATCH-1); + s.insert = ((s.strstart < (MIN_MATCH - 1)) ? s.strstart : MIN_MATCH - 1); if (flush === Z_FINISH) { /*** FLUSH_BLOCK(s, 1); ***/ flush_block_only(s, true); @@ -4517,10 +4924,10 @@ function deflate_slow(s, flush) { */ s.prev_length = s.match_length; s.prev_match = s.match_start; - s.match_length = MIN_MATCH-1; + s.match_length = MIN_MATCH - 1; if (hash_head !== 0/*NIL*/ && s.prev_length < s.max_lazy_match && - s.strstart - hash_head <= (s.w_size-MIN_LOOKAHEAD)/*MAX_DIST(s)*/) { + s.strstart - hash_head <= (s.w_size - MIN_LOOKAHEAD)/*MAX_DIST(s)*/) { /* To simplify the code, we prevent matches with the string * of window index 0 (in particular we have to avoid a match * of the string with itself at the start of the input file). @@ -4534,7 +4941,7 @@ function deflate_slow(s, flush) { /* If prev_match is also MIN_MATCH, match_start is garbage * but we will ignore the current match anyway. */ - s.match_length = MIN_MATCH-1; + s.match_length = MIN_MATCH - 1; } } /* If there was a match at the previous step and the current @@ -4548,13 +4955,13 @@ function deflate_slow(s, flush) { /***_tr_tally_dist(s, s.strstart - 1 - s.prev_match, s.prev_length - MIN_MATCH, bflush);***/ - bflush = trees._tr_tally(s, s.strstart - 1- s.prev_match, s.prev_length - MIN_MATCH); + bflush = trees._tr_tally(s, s.strstart - 1 - s.prev_match, s.prev_length - MIN_MATCH); /* Insert in hash table all strings up to the end of the match. * strstart-1 and strstart are already inserted. If there is not * enough lookahead, the last two strings are not inserted in * the hash table. */ - s.lookahead -= s.prev_length-1; + s.lookahead -= s.prev_length - 1; s.prev_length -= 2; do { if (++s.strstart <= max_insert) { @@ -4566,7 +4973,7 @@ function deflate_slow(s, flush) { } } while (--s.prev_length !== 0); s.match_available = 0; - s.match_length = MIN_MATCH-1; + s.match_length = MIN_MATCH - 1; s.strstart++; if (bflush) { @@ -4585,7 +4992,7 @@ function deflate_slow(s, flush) { */ //Tracevv((stderr,"%c", s->window[s->strstart-1])); /*** _tr_tally_lit(s, s.window[s.strstart-1], bflush); ***/ - bflush = trees._tr_tally(s, 0, s.window[s.strstart-1]); + bflush = trees._tr_tally(s, 0, s.window[s.strstart - 1]); if (bflush) { /*** FLUSH_BLOCK_ONLY(s, 0) ***/ @@ -4610,11 +5017,11 @@ function deflate_slow(s, flush) { if (s.match_available) { //Tracevv((stderr,"%c", s->window[s->strstart-1])); /*** _tr_tally_lit(s, s.window[s.strstart-1], bflush); ***/ - bflush = trees._tr_tally(s, 0, s.window[s.strstart-1]); + bflush = trees._tr_tally(s, 0, s.window[s.strstart - 1]); s.match_available = 0; } - s.insert = s.strstart < MIN_MATCH-1 ? s.strstart : MIN_MATCH-1; + s.insert = s.strstart < MIN_MATCH - 1 ? s.strstart : MIN_MATCH - 1; if (flush === Z_FINISH) { /*** FLUSH_BLOCK(s, 1); ***/ flush_block_only(s, true); @@ -4794,13 +5201,13 @@ function deflate_huff(s, flush) { * exclude worst case performance for pathological files. Better values may be * found for specific files. */ -var Config = function (good_length, max_lazy, nice_length, max_chain, func) { +function Config(good_length, max_lazy, nice_length, max_chain, func) { this.good_length = good_length; this.max_lazy = max_lazy; this.nice_length = nice_length; this.max_chain = max_chain; this.func = func; -}; +} var configuration_table; @@ -4950,8 +5357,8 @@ function DeflateState() { // Use flat array of DOUBLE size, with interleaved fata, // because JS does not support effective this.dyn_ltree = new utils.Buf16(HEAP_SIZE * 2); - this.dyn_dtree = new utils.Buf16((2*D_CODES+1) * 2); - this.bl_tree = new utils.Buf16((2*BL_CODES+1) * 2); + this.dyn_dtree = new utils.Buf16((2 * D_CODES + 1) * 2); + this.bl_tree = new utils.Buf16((2 * BL_CODES + 1) * 2); zero(this.dyn_ltree); zero(this.dyn_dtree); zero(this.bl_tree); @@ -4961,11 +5368,11 @@ function DeflateState() { this.bl_desc = null; /* desc. for bit length tree */ //ush bl_count[MAX_BITS+1]; - this.bl_count = new utils.Buf16(MAX_BITS+1); + this.bl_count = new utils.Buf16(MAX_BITS + 1); /* number of codes at each bit length for an optimal tree */ //int heap[2*L_CODES+1]; /* heap used to build the Huffman trees */ - this.heap = new utils.Buf16(2*L_CODES+1); /* heap used to build the Huffman trees */ + this.heap = new utils.Buf16(2 * L_CODES + 1); /* heap used to build the Huffman trees */ zero(this.heap); this.heap_len = 0; /* number of elements in the heap */ @@ -4974,7 +5381,7 @@ function DeflateState() { * The same heap array is used to build all trees. */ - this.depth = new utils.Buf16(2*L_CODES+1); //uch depth[2*L_CODES+1]; + this.depth = new utils.Buf16(2 * L_CODES + 1); //uch depth[2*L_CODES+1]; zero(this.depth); /* Depth of each subtree used as tie breaker for trees of equal frequency */ @@ -5140,9 +5547,16 @@ function deflateInit2(strm, level, method, windowBits, memLevel, strategy) { s.lit_bufsize = 1 << (memLevel + 6); /* 16K elements by default */ s.pending_buf_size = s.lit_bufsize * 4; + + //overlay = (ushf *) ZALLOC(strm, s->lit_bufsize, sizeof(ush)+2); + //s->pending_buf = (uchf *) overlay; s.pending_buf = new utils.Buf8(s.pending_buf_size); - s.d_buf = s.lit_bufsize >> 1; + // It is offset from `s.pending_buf` (size is `s.lit_bufsize * 2`) + //s->d_buf = overlay + s->lit_bufsize/sizeof(ush); + s.d_buf = 1 * s.lit_bufsize; + + //s->l_buf = s->pending_buf + (1+sizeof(ush))*s->lit_bufsize; s.l_buf = (1 + 2) * s.lit_bufsize; s.level = level; @@ -5311,7 +5725,7 @@ function deflate(strm, flush) { put_byte(s, val); } while (val !== 0); - if (s.gzhead.hcrc && s.pending > beg){ + if (s.gzhead.hcrc && s.pending > beg) { strm.adler = crc32(strm.adler, s.pending_buf, s.pending - beg, beg); } if (val === 0) { @@ -5515,12 +5929,94 @@ function deflateEnd(strm) { return status === BUSY_STATE ? err(strm, Z_DATA_ERROR) : Z_OK; } + /* ========================================================================= - * Copy the source state to the destination state + * Initializes the compression dictionary from the given byte + * sequence without producing any compressed output. */ -//function deflateCopy(dest, source) { -// -//} +function deflateSetDictionary(strm, dictionary) { + var dictLength = dictionary.length; + + var s; + var str, n; + var wrap; + var avail; + var next; + var input; + var tmpDict; + + if (!strm/*== Z_NULL*/ || !strm.state/*== Z_NULL*/) { + return Z_STREAM_ERROR; + } + + s = strm.state; + wrap = s.wrap; + + if (wrap === 2 || (wrap === 1 && s.status !== INIT_STATE) || s.lookahead) { + return Z_STREAM_ERROR; + } + + /* when using zlib wrappers, compute Adler-32 for provided dictionary */ + if (wrap === 1) { + /* adler32(strm->adler, dictionary, dictLength); */ + strm.adler = adler32(strm.adler, dictionary, dictLength, 0); + } + + s.wrap = 0; /* avoid computing Adler-32 in read_buf */ + + /* if dictionary would fill window, just replace the history */ + if (dictLength >= s.w_size) { + if (wrap === 0) { /* already empty otherwise */ + /*** CLEAR_HASH(s); ***/ + zero(s.head); // Fill with NIL (= 0); + s.strstart = 0; + s.block_start = 0; + s.insert = 0; + } + /* use the tail */ + // dictionary = dictionary.slice(dictLength - s.w_size); + tmpDict = new utils.Buf8(s.w_size); + utils.arraySet(tmpDict, dictionary, dictLength - s.w_size, s.w_size, 0); + dictionary = tmpDict; + dictLength = s.w_size; + } + /* insert dictionary into window and hash */ + avail = strm.avail_in; + next = strm.next_in; + input = strm.input; + strm.avail_in = dictLength; + strm.next_in = 0; + strm.input = dictionary; + fill_window(s); + while (s.lookahead >= MIN_MATCH) { + str = s.strstart; + n = s.lookahead - (MIN_MATCH - 1); + do { + /* UPDATE_HASH(s, s->ins_h, s->window[str + MIN_MATCH-1]); */ + s.ins_h = ((s.ins_h << s.hash_shift) ^ s.window[str + MIN_MATCH - 1]) & s.hash_mask; + + s.prev[str & s.w_mask] = s.head[s.ins_h]; + + s.head[s.ins_h] = str; + str++; + } while (--n); + s.strstart = str; + s.lookahead = MIN_MATCH - 1; + fill_window(s); + } + s.strstart += s.lookahead; + s.block_start = s.strstart; + s.insert = s.lookahead; + s.lookahead = 0; + s.match_length = s.prev_length = MIN_MATCH - 1; + s.match_available = 0; + strm.next_in = next; + strm.input = input; + strm.avail_in = avail; + s.wrap = wrap; + return Z_OK; +} + exports.deflateInit = deflateInit; exports.deflateInit2 = deflateInit2; @@ -5529,18 +6025,19 @@ exports.deflateResetKeep = deflateResetKeep; exports.deflateSetHeader = deflateSetHeader; exports.deflate = deflate; exports.deflateEnd = deflateEnd; +exports.deflateSetDictionary = deflateSetDictionary; exports.deflateInfo = 'pako deflate (from Nodeca project)'; /* Not implemented exports.deflateBound = deflateBound; exports.deflateCopy = deflateCopy; -exports.deflateSetDictionary = deflateSetDictionary; exports.deflateParams = deflateParams; exports.deflatePending = deflatePending; exports.deflatePrime = deflatePrime; exports.deflateTune = deflateTune; */ -},{"../utils/common":27,"./adler32":29,"./crc32":31,"./messages":37,"./trees":38}],33:[function(_dereq_,module,exports){ + +},{"../utils/common":28,"./adler32":30,"./crc32":32,"./messages":38,"./trees":39}],34:[function(require,module,exports){ 'use strict'; @@ -5581,7 +6078,8 @@ function GZheader() { } module.exports = GZheader; -},{}],34:[function(_dereq_,module,exports){ + +},{}],35:[function(require,module,exports){ 'use strict'; // See state defs from inflate.js @@ -5636,7 +6134,8 @@ module.exports = function inflate_fast(strm, start) { var wsize; /* window size or zero if not using window */ var whave; /* valid bytes in the window */ var wnext; /* window write index */ - var window; /* allocated sliding window, if wsize != 0 */ + // Use `s_window` instead `window`, avoid conflict with instrumentation tools + var s_window; /* allocated sliding window, if wsize != 0 */ var hold; /* local strm.hold */ var bits; /* local strm.bits */ var lcode; /* local strm.lencode */ @@ -5670,7 +6169,7 @@ module.exports = function inflate_fast(strm, start) { wsize = state.wsize; whave = state.whave; wnext = state.wnext; - window = state.window; + s_window = state.window; hold = state.hold; bits = state.bits; lcode = state.lencode; @@ -5788,13 +6287,13 @@ module.exports = function inflate_fast(strm, start) { //#endif } from = 0; // window index - from_source = window; + from_source = s_window; if (wnext === 0) { /* very common case */ from += wsize - op; if (op < len) { /* some from window */ len -= op; do { - output[_out++] = window[from++]; + output[_out++] = s_window[from++]; } while (--op); from = _out - dist; /* rest from output */ from_source = output; @@ -5806,14 +6305,14 @@ module.exports = function inflate_fast(strm, start) { if (op < len) { /* some from end of window */ len -= op; do { - output[_out++] = window[from++]; + output[_out++] = s_window[from++]; } while (--op); from = 0; if (wnext < len) { /* some from start of window */ op = wnext; len -= op; do { - output[_out++] = window[from++]; + output[_out++] = s_window[from++]; } while (--op); from = _out - dist; /* rest from output */ from_source = output; @@ -5825,7 +6324,7 @@ module.exports = function inflate_fast(strm, start) { if (op < len) { /* some from window */ len -= op; do { - output[_out++] = window[from++]; + output[_out++] = s_window[from++]; } while (--op); from = _out - dist; /* rest from output */ from_source = output; @@ -5908,15 +6407,15 @@ module.exports = function inflate_fast(strm, start) { return; }; -},{}],35:[function(_dereq_,module,exports){ +},{}],36:[function(require,module,exports){ 'use strict'; -var utils = _dereq_('../utils/common'); -var adler32 = _dereq_('./adler32'); -var crc32 = _dereq_('./crc32'); -var inflate_fast = _dereq_('./inffast'); -var inflate_table = _dereq_('./inftrees'); +var utils = require('../utils/common'); +var adler32 = require('./adler32'); +var crc32 = require('./crc32'); +var inflate_fast = require('./inffast'); +var inflate_table = require('./inftrees'); var CODES = 0; var LENS = 1; @@ -6003,7 +6502,7 @@ var MAX_WBITS = 15; var DEF_WBITS = MAX_WBITS; -function ZSWAP32(q) { +function zswap32(q) { return (((q >>> 24) & 0xff) + ((q >>> 8) & 0xff00) + ((q & 0xff00) << 8) + @@ -6196,13 +6695,13 @@ function fixedtables(state) { while (sym < 280) { state.lens[sym++] = 7; } while (sym < 288) { state.lens[sym++] = 8; } - inflate_table(LENS, state.lens, 0, 288, lenfix, 0, state.work, {bits: 9}); + inflate_table(LENS, state.lens, 0, 288, lenfix, 0, state.work, { bits: 9 }); /* distance table */ sym = 0; while (sym < 32) { state.lens[sym++] = 5; } - inflate_table(DISTS, state.lens, 0, 32, distfix, 0, state.work, {bits: 5}); + inflate_table(DISTS, state.lens, 0, 32, distfix, 0, state.work, { bits: 5 }); /* do this just once */ virgin = false; @@ -6244,7 +6743,7 @@ function updatewindow(strm, src, end, copy) { /* copy state->wsize or less output bytes into the circular window */ if (copy >= state.wsize) { - utils.arraySet(state.window,src, end - state.wsize, state.wsize, 0); + utils.arraySet(state.window, src, end - state.wsize, state.wsize, 0); state.wnext = 0; state.whave = state.wsize; } @@ -6254,11 +6753,11 @@ function updatewindow(strm, src, end, copy) { dist = copy; } //zmemcpy(state->window + state->wnext, end - copy, dist); - utils.arraySet(state.window,src, end - copy, dist, state.wnext); + utils.arraySet(state.window, src, end - copy, dist, state.wnext); copy -= dist; if (copy) { //zmemcpy(state->window, end - copy, copy); - utils.arraySet(state.window,src, end - copy, copy, 0); + utils.arraySet(state.window, src, end - copy, copy, 0); state.wnext = copy; state.whave = state.wsize; } @@ -6295,7 +6794,7 @@ function inflate(strm, flush) { var n; // temporary var for NEED_BITS var order = /* permutation of code lengths */ - [16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15]; + [ 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 ]; if (!strm || !strm.state || !strm.output || @@ -6622,7 +7121,7 @@ function inflate(strm, flush) { state.head.hcrc = ((state.flags >> 9) & 1); state.head.done = true; } - strm.adler = state.check = 0 /*crc32(0L, Z_NULL, 0)*/; + strm.adler = state.check = 0; state.mode = TYPE; break; case DICTID: @@ -6634,7 +7133,7 @@ function inflate(strm, flush) { bits += 8; } //===// - strm.adler = state.check = ZSWAP32(hold); + strm.adler = state.check = zswap32(hold); //=== INITBITS(); hold = 0; bits = 0; @@ -6826,7 +7325,7 @@ function inflate(strm, flush) { state.lencode = state.lendyn; state.lenbits = 7; - opts = {bits: state.lenbits}; + opts = { bits: state.lenbits }; ret = inflate_table(CODES, state.lens, 0, 19, state.lencode, 0, state.work, opts); state.lenbits = opts.bits; @@ -6957,7 +7456,7 @@ function inflate(strm, flush) { concerning the ENOUGH constants, which depend on those values */ state.lenbits = 9; - opts = {bits: state.lenbits}; + opts = { bits: state.lenbits }; ret = inflate_table(LENS, state.lens, 0, state.nlen, state.lencode, 0, state.work, opts); // We have separate tables & no pointers. 2 commented lines below not needed. // state.next_index = opts.table_index; @@ -6974,7 +7473,7 @@ function inflate(strm, flush) { //state.distcode.copy(state.codes); // Switch to use dynamic table state.distcode = state.distdyn; - opts = {bits: state.distbits}; + opts = { bits: state.distbits }; ret = inflate_table(DISTS, state.lens, state.nlen, state.ndist, state.distcode, 0, state.work, opts); // We have separate tables & no pointers. 2 commented lines below not needed. // state.next_index = opts.table_index; @@ -7022,7 +7521,7 @@ function inflate(strm, flush) { } state.back = 0; for (;;) { - here = state.lencode[hold & ((1 << state.lenbits) -1)]; /*BITS(state.lenbits)*/ + here = state.lencode[hold & ((1 << state.lenbits) - 1)]; /*BITS(state.lenbits)*/ here_bits = here >>> 24; here_op = (here >>> 16) & 0xff; here_val = here & 0xffff; @@ -7041,7 +7540,7 @@ function inflate(strm, flush) { last_val = here_val; for (;;) { here = state.lencode[last_val + - ((hold & ((1 << (last_bits + last_op)) -1))/*BITS(last.bits + last.op)*/ >> last_bits)]; + ((hold & ((1 << (last_bits + last_op)) - 1))/*BITS(last.bits + last.op)*/ >> last_bits)]; here_bits = here >>> 24; here_op = (here >>> 16) & 0xff; here_val = here & 0xffff; @@ -7098,7 +7597,7 @@ function inflate(strm, flush) { bits += 8; } //===// - state.length += hold & ((1 << state.extra) -1)/*BITS(state.extra)*/; + state.length += hold & ((1 << state.extra) - 1)/*BITS(state.extra)*/; //--- DROPBITS(state.extra) ---// hold >>>= state.extra; bits -= state.extra; @@ -7111,7 +7610,7 @@ function inflate(strm, flush) { /* falls through */ case DIST: for (;;) { - here = state.distcode[hold & ((1 << state.distbits) -1)];/*BITS(state.distbits)*/ + here = state.distcode[hold & ((1 << state.distbits) - 1)];/*BITS(state.distbits)*/ here_bits = here >>> 24; here_op = (here >>> 16) & 0xff; here_val = here & 0xffff; @@ -7130,7 +7629,7 @@ function inflate(strm, flush) { last_val = here_val; for (;;) { here = state.distcode[last_val + - ((hold & ((1 << (last_bits + last_op)) -1))/*BITS(last.bits + last.op)*/ >> last_bits)]; + ((hold & ((1 << (last_bits + last_op)) - 1))/*BITS(last.bits + last.op)*/ >> last_bits)]; here_bits = here >>> 24; here_op = (here >>> 16) & 0xff; here_val = here & 0xffff; @@ -7174,7 +7673,7 @@ function inflate(strm, flush) { bits += 8; } //===// - state.offset += hold & ((1 << state.extra) -1)/*BITS(state.extra)*/; + state.offset += hold & ((1 << state.extra) - 1)/*BITS(state.extra)*/; //--- DROPBITS(state.extra) ---// hold >>>= state.extra; bits -= state.extra; @@ -7268,8 +7767,8 @@ function inflate(strm, flush) { } _out = left; - // NB: crc32 stored as signed 32-bit int, ZSWAP32 returns signed too - if ((state.flags ? hold : ZSWAP32(hold)) !== state.check) { + // NB: crc32 stored as signed 32-bit int, zswap32 returns signed too + if ((state.flags ? hold : zswap32(hold)) !== state.check) { strm.msg = 'incorrect data check'; state.mode = BAD; break; @@ -7391,6 +7890,41 @@ function inflateGetHeader(strm, head) { return Z_OK; } +function inflateSetDictionary(strm, dictionary) { + var dictLength = dictionary.length; + + var state; + var dictid; + var ret; + + /* check state */ + if (!strm /* == Z_NULL */ || !strm.state /* == Z_NULL */) { return Z_STREAM_ERROR; } + state = strm.state; + + if (state.wrap !== 0 && state.mode !== DICT) { + return Z_STREAM_ERROR; + } + + /* check for correct dictionary identifier */ + if (state.mode === DICT) { + dictid = 1; /* adler32(0, null, 0)*/ + /* dictid = adler32(dictid, dictionary, dictLength); */ + dictid = adler32(dictid, dictionary, dictLength, 0); + if (dictid !== state.check) { + return Z_DATA_ERROR; + } + } + /* copy dictionary to window using updatewindow(), which will amend the + existing dictionary if appropriate */ + ret = updatewindow(strm, dictionary, dictLength, dictLength); + if (ret) { + state.mode = MEM; + return Z_MEM_ERROR; + } + state.havedict = 1; + // Tracev((stderr, "inflate: dictionary set\n")); + return Z_OK; +} exports.inflateReset = inflateReset; exports.inflateReset2 = inflateReset2; @@ -7400,6 +7934,7 @@ exports.inflateInit2 = inflateInit2; exports.inflate = inflate; exports.inflateEnd = inflateEnd; exports.inflateGetHeader = inflateGetHeader; +exports.inflateSetDictionary = inflateSetDictionary; exports.inflateInfo = 'pako inflate (from Nodeca project)'; /* Not implemented @@ -7407,16 +7942,16 @@ exports.inflateCopy = inflateCopy; exports.inflateGetDictionary = inflateGetDictionary; exports.inflateMark = inflateMark; exports.inflatePrime = inflatePrime; -exports.inflateSetDictionary = inflateSetDictionary; exports.inflateSync = inflateSync; exports.inflateSyncPoint = inflateSyncPoint; exports.inflateUndermine = inflateUndermine; */ -},{"../utils/common":27,"./adler32":29,"./crc32":31,"./inffast":34,"./inftrees":36}],36:[function(_dereq_,module,exports){ + +},{"../utils/common":28,"./adler32":30,"./crc32":32,"./inffast":35,"./inftrees":37}],37:[function(require,module,exports){ 'use strict'; -var utils = _dereq_('../utils/common'); +var utils = require('../utils/common'); var MAXBITS = 15; var ENOUGH_LENS = 852; @@ -7472,8 +8007,8 @@ module.exports = function inflate_table(type, lens, lens_index, codes, table, ta var base_index = 0; // var shoextra; /* extra bits table to use */ var end; /* use base and extra for symbol > end */ - var count = new utils.Buf16(MAXBITS+1); //[MAXBITS+1]; /* number of codes of each length */ - var offs = new utils.Buf16(MAXBITS+1); //[MAXBITS+1]; /* offsets in table for each length */ + var count = new utils.Buf16(MAXBITS + 1); //[MAXBITS+1]; /* number of codes of each length */ + var offs = new utils.Buf16(MAXBITS + 1); //[MAXBITS+1]; /* offsets in table for each length */ var extra = null; var extra_index = 0; @@ -7609,18 +8144,20 @@ module.exports = function inflate_table(type, lens, lens_index, codes, table, ta // poor man optimization - use if-else instead of switch, // to avoid deopts in old v8 if (type === CODES) { - base = extra = work; /* dummy value--not used */ - end = 19; + base = extra = work; /* dummy value--not used */ + end = 19; + } else if (type === LENS) { - base = lbase; - base_index -= 257; - extra = lext; - extra_index -= 257; - end = 256; + base = lbase; + base_index -= 257; + extra = lext; + extra_index -= 257; + end = 256; + } else { /* DISTS */ - base = dbase; - extra = dext; - end = -1; + base = dbase; + extra = dext; + end = -1; } /* initialize opts for loop */ @@ -7640,7 +8177,7 @@ module.exports = function inflate_table(type, lens, lens_index, codes, table, ta return 1; } - var i=0; + var i = 0; /* process all codes and make table entries */ for (;;) { i++; @@ -7739,13 +8276,13 @@ module.exports = function inflate_table(type, lens, lens_index, codes, table, ta return 0; }; -},{"../utils/common":27}],37:[function(_dereq_,module,exports){ +},{"../utils/common":28}],38:[function(require,module,exports){ 'use strict'; module.exports = { - '2': 'need dictionary', /* Z_NEED_DICT 2 */ - '1': 'stream end', /* Z_STREAM_END 1 */ - '0': '', /* Z_OK 0 */ + 2: 'need dictionary', /* Z_NEED_DICT 2 */ + 1: 'stream end', /* Z_STREAM_END 1 */ + 0: '', /* Z_OK 0 */ '-1': 'file error', /* Z_ERRNO (-1) */ '-2': 'stream error', /* Z_STREAM_ERROR (-2) */ '-3': 'data error', /* Z_DATA_ERROR (-3) */ @@ -7753,11 +8290,12 @@ module.exports = { '-5': 'buffer error', /* Z_BUF_ERROR (-5) */ '-6': 'incompatible version' /* Z_VERSION_ERROR (-6) */ }; -},{}],38:[function(_dereq_,module,exports){ + +},{}],39:[function(require,module,exports){ 'use strict'; -var utils = _dereq_('../utils/common'); +var utils = require('../utils/common'); /* Public constants ==========================================================*/ /* ===========================================================================*/ @@ -7811,7 +8349,7 @@ var D_CODES = 30; var BL_CODES = 19; /* number of codes used to transfer the bit lengths */ -var HEAP_SIZE = 2*L_CODES + 1; +var HEAP_SIZE = 2 * L_CODES + 1; /* maximum heap size */ var MAX_BITS = 15; @@ -7840,6 +8378,7 @@ var REPZ_3_10 = 17; var REPZ_11_138 = 18; /* repeat a zero length 11-138 times (7 bits of repeat count) */ +/* eslint-disable comma-spacing,array-bracket-spacing */ var extra_lbits = /* extra bits for each length code */ [0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0]; @@ -7851,6 +8390,8 @@ var extra_blbits = /* extra bits for each bit length code */ var bl_order = [16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15]; +/* eslint-enable comma-spacing,array-bracket-spacing */ + /* The lengths of the bit length codes are sent in order of decreasing * probability, to avoid transmitting the lengths for unused bit length codes. */ @@ -7864,7 +8405,7 @@ var bl_order = var DIST_CODE_LEN = 512; /* see definition of array dist_code below */ // !!!! Use flat array insdead of structure, Freq = i*2, Len = i*2+1 -var static_ltree = new Array((L_CODES+2) * 2); +var static_ltree = new Array((L_CODES + 2) * 2); zero(static_ltree); /* The static literal tree. Since the bit lengths are imposed, there is no * need for the L_CODES extra codes used during heap construction. However @@ -7885,7 +8426,7 @@ zero(_dist_code); * the 15 bit distances. */ -var _length_code = new Array(MAX_MATCH-MIN_MATCH+1); +var _length_code = new Array(MAX_MATCH - MIN_MATCH + 1); zero(_length_code); /* length code for each normalized match length (0 == MIN_MATCH) */ @@ -7898,7 +8439,7 @@ zero(base_dist); /* First normalized distance for each code (0 = distance of 1) */ -var StaticTreeDesc = function (static_tree, extra_bits, extra_base, elems, max_length) { +function StaticTreeDesc(static_tree, extra_bits, extra_base, elems, max_length) { this.static_tree = static_tree; /* static tree or NULL */ this.extra_bits = extra_bits; /* extra bits for each code or NULL */ @@ -7908,7 +8449,7 @@ var StaticTreeDesc = function (static_tree, extra_bits, extra_base, elems, max_l // show if `static_tree` has data or dummy - needed for monomorphic objects this.has_stree = static_tree && static_tree.length; -}; +} var static_l_desc; @@ -7916,11 +8457,11 @@ var static_d_desc; var static_bl_desc; -var TreeDesc = function(dyn_tree, stat_desc) { +function TreeDesc(dyn_tree, stat_desc) { this.dyn_tree = dyn_tree; /* the dynamic tree */ this.max_code = 0; /* largest code with non zero frequency */ this.stat_desc = stat_desc; /* the corresponding static tree */ -}; +} @@ -7933,7 +8474,7 @@ function d_code(dist) { * Output a short LSB first on the stream. * IN assertion: there is enough room in pendingBuf. */ -function put_short (s, w) { +function put_short(s, w) { // put_byte(s, (uch)((w) & 0xff)); // put_byte(s, (uch)((ush)(w) >> 8)); s.pending_buf[s.pending++] = (w) & 0xff; @@ -7959,7 +8500,7 @@ function send_bits(s, value, length) { function send_code(s, c, tree) { - send_bits(s, tree[c*2]/*.Code*/, tree[c*2 + 1]/*.Len*/); + send_bits(s, tree[c * 2]/*.Code*/, tree[c * 2 + 1]/*.Len*/); } @@ -8031,16 +8572,16 @@ function gen_bitlen(s, desc) /* In a first pass, compute the optimal bit lengths (which may * overflow in the case of the bit length tree). */ - tree[s.heap[s.heap_max]*2 + 1]/*.Len*/ = 0; /* root of the heap */ + tree[s.heap[s.heap_max] * 2 + 1]/*.Len*/ = 0; /* root of the heap */ - for (h = s.heap_max+1; h < HEAP_SIZE; h++) { + for (h = s.heap_max + 1; h < HEAP_SIZE; h++) { n = s.heap[h]; - bits = tree[tree[n*2 +1]/*.Dad*/ * 2 + 1]/*.Len*/ + 1; + bits = tree[tree[n * 2 + 1]/*.Dad*/ * 2 + 1]/*.Len*/ + 1; if (bits > max_length) { bits = max_length; overflow++; } - tree[n*2 + 1]/*.Len*/ = bits; + tree[n * 2 + 1]/*.Len*/ = bits; /* We overwrite tree[n].Dad which is no longer needed */ if (n > max_code) { continue; } /* not a leaf node */ @@ -8048,12 +8589,12 @@ function gen_bitlen(s, desc) s.bl_count[bits]++; xbits = 0; if (n >= base) { - xbits = extra[n-base]; + xbits = extra[n - base]; } f = tree[n * 2]/*.Freq*/; s.opt_len += f * (bits + xbits); if (has_stree) { - s.static_len += f * (stree[n*2 + 1]/*.Len*/ + xbits); + s.static_len += f * (stree[n * 2 + 1]/*.Len*/ + xbits); } } if (overflow === 0) { return; } @@ -8063,10 +8604,10 @@ function gen_bitlen(s, desc) /* Find the first bit length which could increase: */ do { - bits = max_length-1; + bits = max_length - 1; while (s.bl_count[bits] === 0) { bits--; } s.bl_count[bits]--; /* move one leaf down the tree */ - s.bl_count[bits+1] += 2; /* move one overflow item as its brother */ + s.bl_count[bits + 1] += 2; /* move one overflow item as its brother */ s.bl_count[max_length]--; /* The brother of the overflow item also moves one step up, * but this does not affect bl_count[max_length] @@ -8084,10 +8625,10 @@ function gen_bitlen(s, desc) while (n !== 0) { m = s.heap[--h]; if (m > max_code) { continue; } - if (tree[m*2 + 1]/*.Len*/ !== bits) { + if (tree[m * 2 + 1]/*.Len*/ !== bits) { // Trace((stderr,"code %d bits %d->%d\n", m, tree[m].Len, bits)); - s.opt_len += (bits - tree[m*2 + 1]/*.Len*/)*tree[m*2]/*.Freq*/; - tree[m*2 + 1]/*.Len*/ = bits; + s.opt_len += (bits - tree[m * 2 + 1]/*.Len*/) * tree[m * 2]/*.Freq*/; + tree[m * 2 + 1]/*.Len*/ = bits; } n--; } @@ -8108,7 +8649,7 @@ function gen_codes(tree, max_code, bl_count) // int max_code; /* largest code with non zero frequency */ // ushf *bl_count; /* number of codes at each bit length */ { - var next_code = new Array(MAX_BITS+1); /* next code value for each bit length */ + var next_code = new Array(MAX_BITS + 1); /* next code value for each bit length */ var code = 0; /* running code value */ var bits; /* bit index */ var n; /* code index */ @@ -8117,7 +8658,7 @@ function gen_codes(tree, max_code, bl_count) * without bit reversal. */ for (bits = 1; bits <= MAX_BITS; bits++) { - next_code[bits] = code = (code + bl_count[bits-1]) << 1; + next_code[bits] = code = (code + bl_count[bits - 1]) << 1; } /* Check that the bit counts in bl_count are consistent. The last code * must be all ones. @@ -8127,10 +8668,10 @@ function gen_codes(tree, max_code, bl_count) //Tracev((stderr,"\ngen_codes: max_code %d ", max_code)); for (n = 0; n <= max_code; n++) { - var len = tree[n*2 + 1]/*.Len*/; + var len = tree[n * 2 + 1]/*.Len*/; if (len === 0) { continue; } /* Now reverse the bits */ - tree[n*2]/*.Code*/ = bi_reverse(next_code[len]++, len); + tree[n * 2]/*.Code*/ = bi_reverse(next_code[len]++, len); //Tracecv(tree != static_ltree, (stderr,"\nn %3d %c l %2d c %4x (%x) ", // n, (isgraph(n) ? n : ' '), len, tree[n].Code, next_code[len]-1)); @@ -8147,7 +8688,7 @@ function tr_static_init() { var length; /* length value */ var code; /* code value */ var dist; /* distance index */ - var bl_count = new Array(MAX_BITS+1); + var bl_count = new Array(MAX_BITS + 1); /* number of codes at each bit length for an optimal tree */ // do check in _tr_init() @@ -8164,9 +8705,9 @@ function tr_static_init() { /* Initialize the mapping length (0..255) -> length code (0..28) */ length = 0; - for (code = 0; code < LENGTH_CODES-1; code++) { + for (code = 0; code < LENGTH_CODES - 1; code++) { base_length[code] = length; - for (n = 0; n < (1< dist code (0..29) */ dist = 0; - for (code = 0 ; code < 16; code++) { + for (code = 0; code < 16; code++) { base_dist[code] = dist; - for (n = 0; n < (1<>= 7; /* from now on, all distances are divided by 128 */ - for ( ; code < D_CODES; code++) { + for (; code < D_CODES; code++) { base_dist[code] = dist << 7; - for (n = 0; n < (1<<(extra_dbits[code]-7)); n++) { + for (n = 0; n < (1 << (extra_dbits[code] - 7)); n++) { _dist_code[256 + dist++] = code; } } @@ -8202,22 +8743,22 @@ function tr_static_init() { n = 0; while (n <= 143) { - static_ltree[n*2 + 1]/*.Len*/ = 8; + static_ltree[n * 2 + 1]/*.Len*/ = 8; n++; bl_count[8]++; } while (n <= 255) { - static_ltree[n*2 + 1]/*.Len*/ = 9; + static_ltree[n * 2 + 1]/*.Len*/ = 9; n++; bl_count[9]++; } while (n <= 279) { - static_ltree[n*2 + 1]/*.Len*/ = 7; + static_ltree[n * 2 + 1]/*.Len*/ = 7; n++; bl_count[7]++; } while (n <= 287) { - static_ltree[n*2 + 1]/*.Len*/ = 8; + static_ltree[n * 2 + 1]/*.Len*/ = 8; n++; bl_count[8]++; } @@ -8225,18 +8766,18 @@ function tr_static_init() { * tree construction to get a canonical Huffman tree (longest code * all ones) */ - gen_codes(static_ltree, L_CODES+1, bl_count); + gen_codes(static_ltree, L_CODES + 1, bl_count); /* The static distance tree is trivial: */ for (n = 0; n < D_CODES; n++) { - static_dtree[n*2 + 1]/*.Len*/ = 5; - static_dtree[n*2]/*.Code*/ = bi_reverse(n, 5); + static_dtree[n * 2 + 1]/*.Len*/ = 5; + static_dtree[n * 2]/*.Code*/ = bi_reverse(n, 5); } // Now data ready and we can init static trees - static_l_desc = new StaticTreeDesc(static_ltree, extra_lbits, LITERALS+1, L_CODES, MAX_BITS); + static_l_desc = new StaticTreeDesc(static_ltree, extra_lbits, LITERALS + 1, L_CODES, MAX_BITS); static_d_desc = new StaticTreeDesc(static_dtree, extra_dbits, 0, D_CODES, MAX_BITS); - static_bl_desc =new StaticTreeDesc(new Array(0), extra_blbits, 0, BL_CODES, MAX_BL_BITS); + static_bl_desc = new StaticTreeDesc(new Array(0), extra_blbits, 0, BL_CODES, MAX_BL_BITS); //static_init_done = true; } @@ -8249,11 +8790,11 @@ function init_block(s) { var n; /* iterates over tree elements */ /* Initialize the trees. */ - for (n = 0; n < L_CODES; n++) { s.dyn_ltree[n*2]/*.Freq*/ = 0; } - for (n = 0; n < D_CODES; n++) { s.dyn_dtree[n*2]/*.Freq*/ = 0; } - for (n = 0; n < BL_CODES; n++) { s.bl_tree[n*2]/*.Freq*/ = 0; } + for (n = 0; n < L_CODES; n++) { s.dyn_ltree[n * 2]/*.Freq*/ = 0; } + for (n = 0; n < D_CODES; n++) { s.dyn_dtree[n * 2]/*.Freq*/ = 0; } + for (n = 0; n < BL_CODES; n++) { s.bl_tree[n * 2]/*.Freq*/ = 0; } - s.dyn_ltree[END_BLOCK*2]/*.Freq*/ = 1; + s.dyn_ltree[END_BLOCK * 2]/*.Freq*/ = 1; s.opt_len = s.static_len = 0; s.last_lit = s.matches = 0; } @@ -8302,8 +8843,8 @@ function copy_block(s, buf, len, header) * the subtrees have equal frequency. This minimizes the worst case length. */ function smaller(tree, n, m, depth) { - var _n2 = n*2; - var _m2 = m*2; + var _n2 = n * 2; + var _m2 = m * 2; return (tree[_n2]/*.Freq*/ < tree[_m2]/*.Freq*/ || (tree[_n2]/*.Freq*/ === tree[_m2]/*.Freq*/ && depth[n] <= depth[m])); } @@ -8324,7 +8865,7 @@ function pqdownheap(s, tree, k) while (j <= s.heap_len) { /* Set j to the smallest of the two sons: */ if (j < s.heap_len && - smaller(tree, s.heap[j+1], s.heap[j], s.depth)) { + smaller(tree, s.heap[j + 1], s.heap[j], s.depth)) { j++; } /* Exit if v is smaller than both sons */ @@ -8360,7 +8901,7 @@ function compress_block(s, ltree, dtree) if (s.last_lit !== 0) { do { - dist = (s.pending_buf[s.d_buf + lx*2] << 8) | (s.pending_buf[s.d_buf + lx*2 + 1]); + dist = (s.pending_buf[s.d_buf + lx * 2] << 8) | (s.pending_buf[s.d_buf + lx * 2 + 1]); lc = s.pending_buf[s.l_buf + lx]; lx++; @@ -8370,7 +8911,7 @@ function compress_block(s, ltree, dtree) } else { /* Here, lc is the match length - MIN_MATCH */ code = _length_code[lc]; - send_code(s, code+LITERALS+1, ltree); /* send the length code */ + send_code(s, code + LITERALS + 1, ltree); /* send the length code */ extra = extra_lbits[code]; if (extra !== 0) { lc -= base_length[code]; @@ -8432,7 +8973,7 @@ function build_tree(s, desc) s.depth[n] = 0; } else { - tree[n*2 + 1]/*.Len*/ = 0; + tree[n * 2 + 1]/*.Len*/ = 0; } } @@ -8448,7 +8989,7 @@ function build_tree(s, desc) s.opt_len--; if (has_stree) { - s.static_len -= stree[node*2 + 1]/*.Len*/; + s.static_len -= stree[node * 2 + 1]/*.Len*/; } /* node is 0 or 1 so it does not have extra bits */ } @@ -8479,7 +9020,7 @@ function build_tree(s, desc) /* Create a new node father of n and m */ tree[node * 2]/*.Freq*/ = tree[n * 2]/*.Freq*/ + tree[m * 2]/*.Freq*/; s.depth[node] = (s.depth[n] >= s.depth[m] ? s.depth[n] : s.depth[m]) + 1; - tree[n*2 + 1]/*.Dad*/ = tree[m*2 + 1]/*.Dad*/ = node; + tree[n * 2 + 1]/*.Dad*/ = tree[m * 2 + 1]/*.Dad*/ = node; /* and insert the new node in the heap */ s.heap[1/*SMALLEST*/] = node++; @@ -8512,7 +9053,7 @@ function scan_tree(s, tree, max_code) var prevlen = -1; /* last emitted length */ var curlen; /* length of current code */ - var nextlen = tree[0*2 + 1]/*.Len*/; /* length of next code */ + var nextlen = tree[0 * 2 + 1]/*.Len*/; /* length of next code */ var count = 0; /* repeat count of the current code */ var max_count = 7; /* max repeat count */ @@ -8522,11 +9063,11 @@ function scan_tree(s, tree, max_code) max_count = 138; min_count = 3; } - tree[(max_code+1)*2 + 1]/*.Len*/ = 0xffff; /* guard */ + tree[(max_code + 1) * 2 + 1]/*.Len*/ = 0xffff; /* guard */ for (n = 0; n <= max_code; n++) { curlen = nextlen; - nextlen = tree[(n+1)*2 + 1]/*.Len*/; + nextlen = tree[(n + 1) * 2 + 1]/*.Len*/; if (++count < max_count && curlen === nextlen) { continue; @@ -8537,13 +9078,13 @@ function scan_tree(s, tree, max_code) } else if (curlen !== 0) { if (curlen !== prevlen) { s.bl_tree[curlen * 2]/*.Freq*/++; } - s.bl_tree[REP_3_6*2]/*.Freq*/++; + s.bl_tree[REP_3_6 * 2]/*.Freq*/++; } else if (count <= 10) { - s.bl_tree[REPZ_3_10*2]/*.Freq*/++; + s.bl_tree[REPZ_3_10 * 2]/*.Freq*/++; } else { - s.bl_tree[REPZ_11_138*2]/*.Freq*/++; + s.bl_tree[REPZ_11_138 * 2]/*.Freq*/++; } count = 0; @@ -8578,7 +9119,7 @@ function send_tree(s, tree, max_code) var prevlen = -1; /* last emitted length */ var curlen; /* length of current code */ - var nextlen = tree[0*2 + 1]/*.Len*/; /* length of next code */ + var nextlen = tree[0 * 2 + 1]/*.Len*/; /* length of next code */ var count = 0; /* repeat count of the current code */ var max_count = 7; /* max repeat count */ @@ -8592,7 +9133,7 @@ function send_tree(s, tree, max_code) for (n = 0; n <= max_code; n++) { curlen = nextlen; - nextlen = tree[(n+1)*2 + 1]/*.Len*/; + nextlen = tree[(n + 1) * 2 + 1]/*.Len*/; if (++count < max_count && curlen === nextlen) { continue; @@ -8607,15 +9148,15 @@ function send_tree(s, tree, max_code) } //Assert(count >= 3 && count <= 6, " 3_6?"); send_code(s, REP_3_6, s.bl_tree); - send_bits(s, count-3, 2); + send_bits(s, count - 3, 2); } else if (count <= 10) { send_code(s, REPZ_3_10, s.bl_tree); - send_bits(s, count-3, 3); + send_bits(s, count - 3, 3); } else { send_code(s, REPZ_11_138, s.bl_tree); - send_bits(s, count-11, 7); + send_bits(s, count - 11, 7); } count = 0; @@ -8657,13 +9198,13 @@ function build_bl_tree(s) { * requires that at least 4 bit length codes be sent. (appnote.txt says * 3 but the actual value used is 4.) */ - for (max_blindex = BL_CODES-1; max_blindex >= 3; max_blindex--) { - if (s.bl_tree[bl_order[max_blindex]*2 + 1]/*.Len*/ !== 0) { + for (max_blindex = BL_CODES - 1; max_blindex >= 3; max_blindex--) { + if (s.bl_tree[bl_order[max_blindex] * 2 + 1]/*.Len*/ !== 0) { break; } } /* Update opt_len to include the bit length tree and counts */ - s.opt_len += 3*(max_blindex+1) + 5+5+4; + s.opt_len += 3 * (max_blindex + 1) + 5 + 5 + 4; //Tracev((stderr, "\ndyn trees: dyn %ld, stat %ld", // s->opt_len, s->static_len)); @@ -8686,19 +9227,19 @@ function send_all_trees(s, lcodes, dcodes, blcodes) //Assert (lcodes <= L_CODES && dcodes <= D_CODES && blcodes <= BL_CODES, // "too many codes"); //Tracev((stderr, "\nbl counts: ")); - send_bits(s, lcodes-257, 5); /* not +255 as stated in appnote.txt */ - send_bits(s, dcodes-1, 5); - send_bits(s, blcodes-4, 4); /* not -3 as stated in appnote.txt */ + send_bits(s, lcodes - 257, 5); /* not +255 as stated in appnote.txt */ + send_bits(s, dcodes - 1, 5); + send_bits(s, blcodes - 4, 4); /* not -3 as stated in appnote.txt */ for (rank = 0; rank < blcodes; rank++) { //Tracev((stderr, "\nbl code %2d ", bl_order[rank])); - send_bits(s, s.bl_tree[bl_order[rank]*2 + 1]/*.Len*/, 3); + send_bits(s, s.bl_tree[bl_order[rank] * 2 + 1]/*.Len*/, 3); } //Tracev((stderr, "\nbl tree: sent %ld", s->bits_sent)); - send_tree(s, s.dyn_ltree, lcodes-1); /* literal tree */ + send_tree(s, s.dyn_ltree, lcodes - 1); /* literal tree */ //Tracev((stderr, "\nlit tree: sent %ld", s->bits_sent)); - send_tree(s, s.dyn_dtree, dcodes-1); /* distance tree */ + send_tree(s, s.dyn_dtree, dcodes - 1); /* distance tree */ //Tracev((stderr, "\ndist tree: sent %ld", s->bits_sent)); } @@ -8726,7 +9267,7 @@ function detect_data_type(s) { /* Check for non-textual ("black-listed") bytes. */ for (n = 0; n <= 31; n++, black_mask >>>= 1) { - if ((black_mask & 1) && (s.dyn_ltree[n*2]/*.Freq*/ !== 0)) { + if ((black_mask & 1) && (s.dyn_ltree[n * 2]/*.Freq*/ !== 0)) { return Z_BINARY; } } @@ -8783,7 +9324,7 @@ function _tr_stored_block(s, buf, stored_len, last) //ulg stored_len; /* length of input block */ //int last; /* one if this is the last block for a file */ { - send_bits(s, (STORED_BLOCK<<1)+(last ? 1 : 0), 3); /* send block type */ + send_bits(s, (STORED_BLOCK << 1) + (last ? 1 : 0), 3); /* send block type */ copy_block(s, buf, stored_len, true); /* with header */ } @@ -8793,7 +9334,7 @@ function _tr_stored_block(s, buf, stored_len, last) * This takes 10 bits, of which 7 may remain in the bit buffer. */ function _tr_align(s) { - send_bits(s, STATIC_TREES<<1, 3); + send_bits(s, STATIC_TREES << 1, 3); send_code(s, END_BLOCK, static_ltree); bi_flush(s); } @@ -8838,8 +9379,8 @@ function _tr_flush_block(s, buf, stored_len, last) max_blindex = build_bl_tree(s); /* Determine the best encoding. Compute the block lengths in bytes. */ - opt_lenb = (s.opt_len+3+7) >>> 3; - static_lenb = (s.static_len+3+7) >>> 3; + opt_lenb = (s.opt_len + 3 + 7) >>> 3; + static_lenb = (s.static_len + 3 + 7) >>> 3; // Tracev((stderr, "\nopt %lu(%lu) stat %lu(%lu) stored %lu lit %u ", // opt_lenb, s->opt_len, static_lenb, s->static_len, stored_len, @@ -8852,7 +9393,7 @@ function _tr_flush_block(s, buf, stored_len, last) opt_lenb = static_lenb = stored_len + 5; /* force a stored block */ } - if ((stored_len+4 <= opt_lenb) && (buf !== -1)) { + if ((stored_len + 4 <= opt_lenb) && (buf !== -1)) { /* 4: two words for the lengths */ /* The test buf != NULL is only necessary if LIT_BUFSIZE > WSIZE. @@ -8865,12 +9406,12 @@ function _tr_flush_block(s, buf, stored_len, last) } else if (s.strategy === Z_FIXED || static_lenb === opt_lenb) { - send_bits(s, (STATIC_TREES<<1) + (last ? 1 : 0), 3); + send_bits(s, (STATIC_TREES << 1) + (last ? 1 : 0), 3); compress_block(s, static_ltree, static_dtree); } else { - send_bits(s, (DYN_TREES<<1) + (last ? 1 : 0), 3); - send_all_trees(s, s.l_desc.max_code+1, s.d_desc.max_code+1, max_blindex+1); + send_bits(s, (DYN_TREES << 1) + (last ? 1 : 0), 3); + send_all_trees(s, s.l_desc.max_code + 1, s.d_desc.max_code + 1, max_blindex + 1); compress_block(s, s.dyn_ltree, s.dyn_dtree); } // Assert (s->compressed_len == s->bits_sent, "bad compressed size"); @@ -8905,7 +9446,7 @@ function _tr_tally(s, dist, lc) if (dist === 0) { /* lc is the unmatched char */ - s.dyn_ltree[lc*2]/*.Freq*/++; + s.dyn_ltree[lc * 2]/*.Freq*/++; } else { s.matches++; /* Here, lc is the match length - MIN_MATCH */ @@ -8914,7 +9455,7 @@ function _tr_tally(s, dist, lc) // (ush)lc <= (ush)(MAX_MATCH-MIN_MATCH) && // (ush)d_code(dist) < (ush)D_CODES, "_tr_tally: bad match"); - s.dyn_ltree[(_length_code[lc]+LITERALS+1) * 2]/*.Freq*/++; + s.dyn_ltree[(_length_code[lc] + LITERALS + 1) * 2]/*.Freq*/++; s.dyn_dtree[d_code(dist) * 2]/*.Freq*/++; } @@ -8941,7 +9482,7 @@ function _tr_tally(s, dist, lc) // } //#endif - return (s.last_lit === s.lit_bufsize-1); + return (s.last_lit === s.lit_bufsize - 1); /* We avoid equality with lit_bufsize because of wraparound at 64K * on 16 bit machines and because stored blocks are restricted to * 64K-1 bytes. @@ -8953,7 +9494,8 @@ exports._tr_stored_block = _tr_stored_block; exports._tr_flush_block = _tr_flush_block; exports._tr_tally = _tr_tally; exports._tr_align = _tr_align; -},{"../utils/common":27}],39:[function(_dereq_,module,exports){ + +},{"../utils/common":28}],40:[function(require,module,exports){ 'use strict'; @@ -8983,6 +9525,6 @@ function ZStream() { } module.exports = ZStream; -},{}]},{},[9]) -(9) -}); + +},{}]},{},[10])(10) +}); \ No newline at end of file diff --git a/webodf/lib/gui/AnnotationController.js b/webodf/lib/gui/AnnotationController.js index 298c33487..a2091dc38 100644 --- a/webodf/lib/gui/AnnotationController.js +++ b/webodf/lib/gui/AnnotationController.js @@ -18,15 +18,15 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ -/*global runtime, core, gui, odf, ops, Node*/ +/*global runtime, webodfcore, gui, odf, ops, Node*/ /** * @constructor - * @implements {core.Destroyable} + * @implements {webodfcore.Destroyable} * @param {!ops.Session} session * @param {!gui.SessionConstraints} sessionConstraints * @param {!string} inputMemberId @@ -36,10 +36,10 @@ gui.AnnotationController = function AnnotationController(session, sessionConstra var odtDocument = session.getOdtDocument(), isAnnotatable = false, - eventNotifier = new core.EventNotifier([gui.AnnotationController.annotatableChanged]), + eventNotifier = new webodfcore.EventNotifier([gui.AnnotationController.annotatableChanged]), odfUtils = odf.OdfUtils, /**@const*/ - NEXT = core.StepDirection.NEXT; + NEXT = webodfcore.StepDirection.NEXT; /** * @return {undefined} diff --git a/webodf/lib/gui/AnnotationViewManager.js b/webodf/lib/gui/AnnotationViewManager.js index 2daa7eeca..a9e6a8b17 100644 --- a/webodf/lib/gui/AnnotationViewManager.js +++ b/webodf/lib/gui/AnnotationViewManager.js @@ -1,5 +1,6 @@ /** * Copyright (C) 2012-2013 KO GmbH + * Copyright (C) 2026 Daniel Berthereau * * @licstart * This file is part of WebODF. @@ -18,11 +19,11 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ -/*global runtime, gui, core, odf, Node*/ +/*global runtime, gui, webodfcore, odf, Node*/ /*jslint sub: true*/ /** @@ -30,7 +31,7 @@ * This needs to be fixed soon. */ -/*jslint emptyblock:true*/ +/*jslint emptyblock:true, unparam:true*/ /** * Abstraction of document canvas that can have annotations. * @class @@ -46,7 +47,14 @@ gui.AnnotatableCanvas.prototype.getZoomLevel = function () {"use strict"; }; * @return {Element} */ gui.AnnotatableCanvas.prototype.getSizer = function () {"use strict"; }; -/*jslint emptyblock:false*/ +/** + * Where the page that holds a place of the drawn document begins and ends + * across, or nothing when the document is drawn as one run of text. + * @param {!number} x from the left edge of the window + * @return {?{left:!number,right:!number}} + */ +gui.AnnotatableCanvas.prototype.pageBoxAt = function (x) {"use strict"; }; +/*jslint emptyblock:false, unparam:false*/ /** * A GUI class for wrapping Annotation nodes inside html wrappers, positioning @@ -225,12 +233,27 @@ gui.AnnotationViewManager = function AnnotationViewManager(canvas, odfFragment, connectorAngle = 0, previousAnnotation = annotations[annotations.indexOf(annotation) - 1], previousRect, + /**@type{!number}*/ + laneWidth, + /**@type{?{left:!number,right:!number}}*/ + page, zoomLevel = canvas.getZoomLevel(); - annotationNote.style.left = - (annotationsPane.getBoundingClientRect().left - - annotationWrapper.getBoundingClientRect().left) / zoomLevel + 'px'; - annotationNote.style.width = annotationsPane.getBoundingClientRect().width / zoomLevel + 'px'; + laneWidth = annotationsPane.getBoundingClientRect().width / zoomLevel; + // The notes stand in one pane beside the whole text, and beside each + // page when the pages stand beside one another: the page a note + // belongs to is the page the line it is written at stands on. + page = canvas.pageBoxAt( + annotationWrapper.getBoundingClientRect().left + ); + annotationNote.style.left = page + ? page.right - ((annotationWrapper.getBoundingClientRect().left + - odfFragment.getBoundingClientRect().left) / zoomLevel) + + 'px' + : (annotationsPane.getBoundingClientRect().left + - annotationWrapper.getBoundingClientRect().left) + / zoomLevel + 'px'; + annotationNote.style.width = laneWidth + 'px'; connectorHorizontal.style.width = parseFloat(annotationNote.style.left) diff --git a/webodf/lib/gui/Avatar.js b/webodf/lib/gui/Avatar.js index 2dd66ad48..13139748c 100644 --- a/webodf/lib/gui/Avatar.js +++ b/webodf/lib/gui/Avatar.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global gui*/ @@ -29,7 +29,7 @@ * It will always keep a relative distance to that element, so automatically * move around with the parent element. * @constructor - * @implements {core.Destroyable} + * @implements {webodfcore.Destroyable} * @param {!Element} parentElement * @param {boolean} avatarInitiallyVisible Sets the initial visibility of the avatar */ diff --git a/webodf/lib/gui/BlacklistNamespaceNodeFilter.js b/webodf/lib/gui/BlacklistNamespaceNodeFilter.js index e48f976d0..4e46319fb 100644 --- a/webodf/lib/gui/BlacklistNamespaceNodeFilter.js +++ b/webodf/lib/gui/BlacklistNamespaceNodeFilter.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global gui, NodeFilter*/ diff --git a/webodf/lib/gui/Caret.js b/webodf/lib/gui/Caret.js index 891fdcfd0..24f24c742 100644 --- a/webodf/lib/gui/Caret.js +++ b/webodf/lib/gui/Caret.js @@ -18,11 +18,11 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ -/*global core, gui, odf, ops, runtime, Node*/ +/*global webodfcore, gui, odf, ops, runtime, Node*/ /** * Class that represents a caret in a document. @@ -31,7 +31,7 @@ * Blinking is done by switching the color of the border from transparent to * the member color and back. * @constructor - * @implements {core.Destroyable} + * @implements {webodfcore.Destroyable} * @param {!ops.OdtCursor} cursor * @param {!gui.Viewport} viewport * @param {boolean} avatarInitiallyVisible Sets the initial visibility of the caret's avatar @@ -58,13 +58,13 @@ gui.Caret = function Caret(cursor, viewport, avatarInitiallyVisible, blinkOnRang /**@type{!Range}*/ caretSizerRange, canvas = cursor.getDocument().getCanvas(), - domUtils = core.DomUtils, + domUtils = webodfcore.DomUtils, guiStepUtils = new gui.GuiStepUtils(), - /**@type{!core.StepIterator}*/ + /**@type{!webodfcore.StepIterator}*/ stepIterator, - /**@type{!core.ScheduledTask}*/ + /**@type{!webodfcore.ScheduledTask}*/ redrawTask, - /**@type{!core.ScheduledTask}*/ + /**@type{!webodfcore.ScheduledTask}*/ blinkTask, /**@type{boolean}*/ shouldResetBlink = false, @@ -464,7 +464,7 @@ gui.Caret = function Caret(cursor, viewport, avatarInitiallyVisible, blinkOnRang */ this.destroy = function (callback) { var cleanup = [redrawTask.destroy, blinkTask.destroy, avatar.destroy, destroy]; - core.Async.destroyAll(cleanup, callback); + webodfcore.Async.destroyAll(cleanup, callback); }; function init() { @@ -494,8 +494,8 @@ gui.Caret = function Caret(cursor, viewport, avatarInitiallyVisible, blinkOnRang stepIterator = odtDocument.createStepIterator(cursor.getNode(), 0, positionFilters, odtDocument.getRootNode()); - redrawTask = core.Task.createRedrawTask(updateCaret); - blinkTask = core.Task.createTimeoutTask(blinkCaret, BLINK_PERIOD_MS); + redrawTask = webodfcore.Task.createRedrawTask(updateCaret); + blinkTask = webodfcore.Task.createTimeoutTask(blinkCaret, BLINK_PERIOD_MS); redrawTask.triggerImmediate(); } init(); diff --git a/webodf/lib/gui/CaretManager.js b/webodf/lib/gui/CaretManager.js index 299a3828d..e51c33b7b 100644 --- a/webodf/lib/gui/CaretManager.js +++ b/webodf/lib/gui/CaretManager.js @@ -18,11 +18,11 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ -/*global runtime, core, gui, ops*/ +/*global runtime, webodfcore, gui, ops*/ /** @@ -32,7 +32,7 @@ * caret's current focus, and ensure the caret stays visible after every local * operation. * @constructor - * @implements {core.Destroyable} + * @implements {webodfcore.Destroyable} * @param {!gui.SessionController} sessionController * @param {!gui.Viewport} viewport */ @@ -123,7 +123,6 @@ gui.CaretManager = function CaretManager(sessionController, viewport) { // if local input member, then let controller listen on caret span if (memberid === sessionController.getInputMemberId()) { - runtime.log("Starting to track input on new cursor of " + memberid); odtDocument.subscribe(ops.OdtDocument.signalProcessingBatchEnd, caret.ensureVisible); odtDocument.subscribe(ops.Document.signalCursorMoved, caret.refreshCursorBlinking); @@ -165,7 +164,7 @@ gui.CaretManager = function CaretManager(sessionController, viewport) { sessionController.getSelectionController().setCaretXPositionLocator(null); odtDocument.unsubscribe(ops.Document.signalCursorRemoved, removeCaret); carets = {}; - core.Async.destroyAll(caretCleanup, callback); + webodfcore.Async.destroyAll(caretCleanup, callback); }; function init() { diff --git a/webodf/lib/gui/Clipboard.js b/webodf/lib/gui/Clipboard.js index 86bcedf44..5ccafcf38 100644 --- a/webodf/lib/gui/Clipboard.js +++ b/webodf/lib/gui/Clipboard.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global gui, runtime*/ diff --git a/webodf/lib/gui/ClosestXOffsetScanner.js b/webodf/lib/gui/ClosestXOffsetScanner.js index 2efb796cb..633138933 100644 --- a/webodf/lib/gui/ClosestXOffsetScanner.js +++ b/webodf/lib/gui/ClosestXOffsetScanner.js @@ -18,11 +18,11 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ -/*global core, gui*/ +/*global webodfcore, gui*/ /** * Finds the closest step to the specified offset, but comparing the left and right edges of the diff --git a/webodf/lib/gui/CommonConstraints.js b/webodf/lib/gui/CommonConstraints.js index a86e00394..b5a6609f9 100644 --- a/webodf/lib/gui/CommonConstraints.js +++ b/webodf/lib/gui/CommonConstraints.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global gui*/ diff --git a/webodf/lib/gui/DirectFormattingController.js b/webodf/lib/gui/DirectFormattingController.js index 05d34ebb6..e39d3bf62 100644 --- a/webodf/lib/gui/DirectFormattingController.js +++ b/webodf/lib/gui/DirectFormattingController.js @@ -18,15 +18,15 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ -/*global core, ops, odf, gui, runtime*/ +/*global webodfcore, ops, odf, gui, runtime*/ /** * @constructor - * @implements {core.Destroyable} + * @implements {webodfcore.Destroyable} * @param {!ops.Session} session * @param {!gui.SessionConstraints} sessionConstraints * @param {!gui.SessionContext} sessionContext @@ -48,9 +48,9 @@ gui.DirectFormattingController = function DirectFormattingController( var self = this, odtDocument = session.getOdtDocument(), - utils = new core.Utils(), + utils = new webodfcore.Utils(), odfUtils = odf.OdfUtils, - eventNotifier = new core.EventNotifier([ + eventNotifier = new webodfcore.EventNotifier([ gui.DirectFormattingController.enabledChanged, gui.DirectFormattingController.textStylingChanged, gui.DirectFormattingController.paragraphStylingChanged @@ -58,13 +58,13 @@ gui.DirectFormattingController = function DirectFormattingController( /**@const*/ textns = odf.Namespaces.textns, /**@const*/ - NEXT = core.StepDirection.NEXT, + NEXT = webodfcore.StepDirection.NEXT, /**@type{?odf.Formatting.StyleData}*/ directCursorStyleProperties = null, // cached text settings /**@type{!gui.DirectFormattingController.SelectionInfo}*/ lastSignalledSelectionInfo, - /**@type {!core.LazyProperty.} */ + /**@type {!webodfcore.LazyProperty.} */ selectionInfoCache; /** @@ -884,9 +884,9 @@ gui.DirectFormattingController = function DirectFormattingController( odtDocument.subscribe(ops.OdtDocument.signalOperationEnd, clearCursorStyle); odtDocument.subscribe(ops.OdtDocument.signalProcessingBatchEnd, emitSelectionChanges); sessionConstraints.subscribe(gui.CommonConstraints.EDIT.REVIEW_MODE, forceSelectionInfoRefresh); - selectionInfoCache = new core.LazyProperty(getSelectionInfo); + selectionInfoCache = new webodfcore.LazyProperty(getSelectionInfo); // Using a function rather than calling selectionInfoCache.value() directly because Closure Compiler's generics - // inference is quite limited and does not seem to recognise the core.LazyProperty type interface correctly + // inference is quite limited and does not seem to recognise the webodfcore.LazyProperty type interface correctly // within the function that creates the instance. Everywhere else in this file has no issues with it though... lastSignalledSelectionInfo = getCachedSelectionInfo(); diff --git a/webodf/lib/gui/EditInfoHandle.js b/webodf/lib/gui/EditInfoHandle.js index 0b5167e44..0b0e50ada 100644 --- a/webodf/lib/gui/EditInfoHandle.js +++ b/webodf/lib/gui/EditInfoHandle.js @@ -18,15 +18,15 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ -/*global core, gui*/ +/*global webodfcore, gui*/ /** * @constructor - * @implements {core.Destroyable} + * @implements {webodfcore.Destroyable} * @param {!Element} parentElement */ gui.EditInfoHandle = function EditInfoHandle(parentElement) { @@ -42,7 +42,7 @@ gui.EditInfoHandle = function EditInfoHandle(parentElement) { function renderEdits() { var i, infoDiv, colorSpan, authorSpan, timeSpan; - core.DomUtils.removeAllChildNodes(handle); + webodfcore.DomUtils.removeAllChildNodes(handle); for (i = 0; i < edits.length; i += 1) { infoDiv = document.createElementNS(htmlns, 'div'); infoDiv.className = "editInfo"; diff --git a/webodf/lib/gui/EditInfoMarker.js b/webodf/lib/gui/EditInfoMarker.js index 40b4d232d..fa714c8d0 100644 --- a/webodf/lib/gui/EditInfoMarker.js +++ b/webodf/lib/gui/EditInfoMarker.js @@ -18,15 +18,15 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ -/*global core, gui, runtime*/ +/*global webodfcore, gui, runtime*/ /** * @constructor - * @implements {core.Destroyable} + * @implements {webodfcore.Destroyable} * @param {!ops.EditInfo} editInfo marker takes ownership * @param {boolean} initialVisibility Sets the initial edit info marker visibility */ diff --git a/webodf/lib/gui/EventManager.js b/webodf/lib/gui/EventManager.js index 8b5920c86..04ff35c53 100644 --- a/webodf/lib/gui/EventManager.js +++ b/webodf/lib/gui/EventManager.js @@ -18,11 +18,11 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ -/*global runtime, gui, core, Node */ +/*global runtime, gui, webodfcore, Node */ /** * Event wiring and management abstraction layer @@ -30,7 +30,7 @@ * class provides a mechanism for returning event focus back to the SessionController when it has been lost to * an external source. * @constructor - * @implements {core.Destroyable} + * @implements {webodfcore.Destroyable} * @param {!ops.OdtDocument} odtDocument */ gui.EventManager = function EventManager(odtDocument) { @@ -79,7 +79,7 @@ gui.EventManager = function EventManager(odtDocument) { function EventDelegate(eventName) { var self = this, recentEvents = [], - subscribers = new core.EventNotifier([eventName]); + subscribers = new webodfcore.EventNotifier([eventName]); /** * @param {!Element|!Window} eventTarget @@ -203,7 +203,7 @@ gui.EventManager = function EventManager(odtDocument) { function CompoundEvent(eventName, dependencies, eventProxy) { var /**@type{!Object}*/ cachedState = {}, - subscribers = new core.EventNotifier([eventName]); + subscribers = new webodfcore.EventNotifier([eventName]); /** * @param {!Event} event diff --git a/webodf/lib/gui/GuiStepUtils.js b/webodf/lib/gui/GuiStepUtils.js index 95c19ff6f..9d13855b9 100644 --- a/webodf/lib/gui/GuiStepUtils.js +++ b/webodf/lib/gui/GuiStepUtils.js @@ -18,11 +18,11 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ -/*global runtime, odf, gui, core, Node*/ +/*global runtime, odf, gui, webodfcore, Node*/ /** * Helper functions to retrieve information about an ODF document using a step iterator @@ -32,8 +32,8 @@ gui.GuiStepUtils = function GuiStepUtils() { "use strict"; var odfUtils = odf.OdfUtils, stepUtils = new odf.StepUtils(), - domUtils = core.DomUtils, - NEXT = core.StepDirection.NEXT, + domUtils = webodfcore.DomUtils, + NEXT = webodfcore.StepDirection.NEXT, LEFT_TO_RIGHT = gui.StepInfo.VisualDirection.LEFT_TO_RIGHT, RIGHT_TO_LEFT = gui.StepInfo.VisualDirection.RIGHT_TO_LEFT; @@ -41,7 +41,7 @@ gui.GuiStepUtils = function GuiStepUtils() { * Returns the client rectangle for the content bounds at the step iterator's current position. * Note, if the selected content is really collapsed whitespace, this function will return null. * - * @param {!core.StepIterator} stepIterator + * @param {!webodfcore.StepIterator} stepIterator * @return {?ClientRect} */ function getContentRect(stepIterator) { @@ -81,8 +81,8 @@ gui.GuiStepUtils = function GuiStepUtils() { * Advance the step iterator in the specified direction until an accepted step is identified * by a token scanner. * - * @param {!core.StepIterator} stepIterator - * @param {!core.StepDirection} direction + * @param {!webodfcore.StepIterator} stepIterator + * @param {!webodfcore.StepDirection} direction * @param {!Array.} scanners * @return {!boolean} Return true if a step was found that satisfied one of the scanners */ @@ -92,7 +92,7 @@ gui.GuiStepUtils = function GuiStepUtils() { rightRect, previousRect, nextRect, - /**@type{?core.StepIterator.StepSnapshot}*/ + /**@type{?webodfcore.StepIterator.StepSnapshot}*/ destinationToken, // Just in case no destination is found, the iterator will reset back to the initial position initialToken = stepIterator.snapshot(), diff --git a/webodf/lib/gui/HyperlinkClickHandler.js b/webodf/lib/gui/HyperlinkClickHandler.js index fa0881a04..bf85f5aed 100644 --- a/webodf/lib/gui/HyperlinkClickHandler.js +++ b/webodf/lib/gui/HyperlinkClickHandler.js @@ -18,15 +18,15 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global runtime, gui, odf, xmldom */ /** * @constructor - * @implements {core.Destroyable} + * @implements {webodfcore.Destroyable} * @param {!function():!HTMLElement} getContainer Fetch the surrounding HTML container * @param {!gui.KeyboardHandler} keyDownHandler * @param {!gui.KeyboardHandler} keyUpHandler diff --git a/webodf/lib/gui/HyperlinkController.js b/webodf/lib/gui/HyperlinkController.js index 2755f2ff1..ff4569044 100644 --- a/webodf/lib/gui/HyperlinkController.js +++ b/webodf/lib/gui/HyperlinkController.js @@ -18,16 +18,16 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ -/*global runtime, core, gui, Node, ops, odf */ +/*global runtime, webodfcore, gui, Node, ops, odf */ /** * @constructor - * @implements {core.Destroyable} - * @implements {core.EventSource} + * @implements {webodfcore.Destroyable} + * @implements {webodfcore.EventSource} * @param {!ops.Session} session * @param {!gui.SessionConstraints} sessionConstraints * @param {!gui.SessionContext} sessionContext @@ -43,7 +43,7 @@ gui.HyperlinkController = function HyperlinkController( var odfUtils = odf.OdfUtils, odtDocument = session.getOdtDocument(), - eventNotifier = new core.EventNotifier([ + eventNotifier = new webodfcore.EventNotifier([ gui.HyperlinkController.enabledChanged ]), isEnabled = false; diff --git a/webodf/lib/gui/HyperlinkTooltipView.js b/webodf/lib/gui/HyperlinkTooltipView.js index 5c923ae14..6dc12387d 100644 --- a/webodf/lib/gui/HyperlinkTooltipView.js +++ b/webodf/lib/gui/HyperlinkTooltipView.js @@ -18,21 +18,21 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ -/*global runtime, gui, odf, core */ +/*global runtime, gui, odf, webodfcore */ /** * @constructor - * @implements {core.Destroyable} + * @implements {webodfcore.Destroyable} * @param {!odf.OdfCanvas} odfCanvas * @param {!function():!number} getActiveModifier Get the currently active hyperlink click handler modifier */ gui.HyperlinkTooltipView = function HyperlinkTooltipView(odfCanvas, getActiveModifier) { "use strict"; - var domUtils = core.DomUtils, + var domUtils = webodfcore.DomUtils, odfUtils = odf.OdfUtils, /**@type{!Window}*/ window = /**@type{!Window}*/(runtime.getWindow()), diff --git a/webodf/lib/gui/IOSSafariSupport.js b/webodf/lib/gui/IOSSafariSupport.js index 3b9e47ceb..cfab791ca 100644 --- a/webodf/lib/gui/IOSSafariSupport.js +++ b/webodf/lib/gui/IOSSafariSupport.js @@ -18,17 +18,17 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ -/*global runtime, gui, core*/ +/*global runtime, gui, webodfcore*/ /** * Drop-in workaround for several bugs/quirks * found in iOS Safari. * @constructor - * @implements {core.Destroyable} + * @implements {webodfcore.Destroyable} * @param {!gui.EventManager} eventManager */ gui.IOSSafariSupport = function (eventManager) { diff --git a/webodf/lib/gui/ImageController.js b/webodf/lib/gui/ImageController.js index 7971c88f4..f3480904e 100644 --- a/webodf/lib/gui/ImageController.js +++ b/webodf/lib/gui/ImageController.js @@ -18,11 +18,11 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ -/*global runtime, core, gui, odf, ops */ +/*global runtime, webodfcore, gui, odf, ops */ /** * @constructor @@ -54,7 +54,7 @@ gui.ImageController = function ImageController( odtDocument = session.getOdtDocument(), odfUtils = odf.OdfUtils, formatting = odtDocument.getFormatting(), - eventNotifier = new core.EventNotifier([ + eventNotifier = new webodfcore.EventNotifier([ gui.HyperlinkController.enabledChanged ]), isEnabled = false; @@ -289,7 +289,7 @@ gui.ImageController = function ImageController( styleName, pageContentSize, imageSize, - cssUnits = new core.CSSUnits(); + cssUnits = new webodfcore.CSSUnits(); runtime.assert(widthInPx > 0 && heightInPx > 0, "Both width and height of the image should be greater than 0px."); imageSize = { diff --git a/webodf/lib/gui/ImageSelector.js b/webodf/lib/gui/ImageSelector.js index 0f5c82fd1..b5f64c89c 100644 --- a/webodf/lib/gui/ImageSelector.js +++ b/webodf/lib/gui/ImageSelector.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global runtime, gui, odf */ diff --git a/webodf/lib/gui/InputMethodEditor.js b/webodf/lib/gui/InputMethodEditor.js index b374bfeea..3f45fbc0e 100644 --- a/webodf/lib/gui/InputMethodEditor.js +++ b/webodf/lib/gui/InputMethodEditor.js @@ -18,11 +18,11 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ -/*global runtime, gui, core, ops, Node, odf*/ +/*global runtime, gui, webodfcore, ops, Node, odf*/ (function () { "use strict"; @@ -37,7 +37,7 @@ * text as if it occurred via a normal composition event * * @constructor - * @implements {core.Destroyable} + * @implements {webodfcore.Destroyable} * @param {!gui.EventManager} eventManager */ function DetectSafariCompositionError(eventManager) { @@ -93,7 +93,7 @@ * - On Chrome, using Option+char will include the following keypress in the composition event * * @constructor - * @implements {core.Destroyable} + * @implements {webodfcore.Destroyable} * @param {!string} inputMemberId * @param {!gui.EventManager} eventManager */ @@ -106,12 +106,12 @@ doc = /**@type{!Document}*/(eventTrap.ownerDocument), /**@type{!Element}*/ compositionElement, - /**@type{!core.ScheduledTask}*/ + /**@type{!webodfcore.ScheduledTask}*/ processUpdates, pendingEvent = false, /**@type{string}*/ pendingData = "", - events = new core.EventNotifier([gui.InputMethodEditor.signalCompositionStart, + events = new webodfcore.EventNotifier([gui.InputMethodEditor.signalCompositionStart, gui.InputMethodEditor.signalCompositionEnd]), lastCompositionData, /**@type{!odf.TextSerializer}*/ @@ -295,7 +295,7 @@ eventManager.unsubscribe('keypress', flushEvent); eventManager.unsubscribe('focus', synchronizeWindowSelection); - core.Async.destroyAll(cleanup, callback); + webodfcore.Async.destroyAll(cleanup, callback); }; function init() { @@ -322,7 +322,7 @@ compositionElement = doc.createElement('span'); compositionElement.setAttribute('id', 'composer'); - processUpdates = core.Task.createTimeoutTask(synchronizeWindowSelection, 1); + processUpdates = webodfcore.Task.createTimeoutTask(synchronizeWindowSelection, 1); cleanup.push(processUpdates.destroy); } diff --git a/webodf/lib/gui/KeyboardHandler.js b/webodf/lib/gui/KeyboardHandler.js index f48913a88..f2559cf4a 100644 --- a/webodf/lib/gui/KeyboardHandler.js +++ b/webodf/lib/gui/KeyboardHandler.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global gui, runtime */ diff --git a/webodf/lib/gui/LineBoundaryScanner.js b/webodf/lib/gui/LineBoundaryScanner.js index 493c02372..379f242e2 100644 --- a/webodf/lib/gui/LineBoundaryScanner.js +++ b/webodf/lib/gui/LineBoundaryScanner.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global gui*/ @@ -42,8 +42,8 @@ gui.LineBoundaryScanner = function () { * no overlap, or either of the rectangles is 0 height, this will * return 0. * - * @param {!core.SimpleClientRect} rect1 - * @param {!core.SimpleClientRect} rect2 + * @param {!webodfcore.SimpleClientRect} rect1 + * @param {!webodfcore.SimpleClientRect} rect2 * @return {!number} */ function verticalOverlapPercent(rect1, rect2) { @@ -61,21 +61,21 @@ gui.LineBoundaryScanner = function () { * Returns true if the amount of overlap between the known line rectangle and the visible next rectangle * is below the specified MIN_OVERLAP_THRESHOLD. If there is no known line rectangle, this will return false. * - * @param {!core.SimpleClientRect} nextRect Client rect of next step (by direction) + * @param {!webodfcore.SimpleClientRect} nextRect Client rect of next step (by direction) * @return {!boolean} */ function isLineBoundary(nextRect) { if (lineRect) { // TODO this logic will fail if the caret is between a subscript & superscript char as the overlap will be 0 - return verticalOverlapPercent(/**@type{!core.SimpleClientRect}*/(lineRect), nextRect) <= MIN_OVERLAP_THRESHOLD; + return verticalOverlapPercent(/**@type{!webodfcore.SimpleClientRect}*/(lineRect), nextRect) <= MIN_OVERLAP_THRESHOLD; } return false; } /** - * @param {!core.SimpleClientRect} rect1 - * @param {!core.SimpleClientRect} rect2 - * @return {!core.SimpleClientRect} + * @param {!webodfcore.SimpleClientRect} rect1 + * @param {!webodfcore.SimpleClientRect} rect2 + * @return {!webodfcore.SimpleClientRect} */ function combineRects(rect1, rect2) { return { @@ -87,14 +87,14 @@ gui.LineBoundaryScanner = function () { } /** - * @param {?core.SimpleClientRect} originalRect - * @param {?core.SimpleClientRect} newRect - * @return {?core.SimpleClientRect} + * @param {?webodfcore.SimpleClientRect} originalRect + * @param {?webodfcore.SimpleClientRect} newRect + * @return {?webodfcore.SimpleClientRect} */ function growRect(originalRect, newRect) { if (originalRect && newRect) { - return combineRects(/**@type{!core.SimpleClientRect}*/(originalRect), - /**@type{!core.SimpleClientRect}*/(newRect)); + return combineRects(/**@type{!webodfcore.SimpleClientRect}*/(originalRect), + /**@type{!webodfcore.SimpleClientRect}*/(newRect)); } return originalRect || newRect; } @@ -111,7 +111,7 @@ gui.LineBoundaryScanner = function () { // Can only detect line boundaries when the next rectangle is visible. An invisible next-rect // indicates the next step does not have any visible content attached, so it's location on screen // is impossible to determine accurately. - var isOverLineBoundary = nextRect && isLineBoundary(/**@type{!core.SimpleClientRect}*/(nextRect)); + var isOverLineBoundary = nextRect && isLineBoundary(/**@type{!webodfcore.SimpleClientRect}*/(nextRect)); if (previousRect && (!nextRect || isOverLineBoundary)) { // Detect a possible line wrap point in one of two ways: diff --git a/webodf/lib/gui/MetadataController.js b/webodf/lib/gui/MetadataController.js index 93d0aeea8..28dc26ddd 100644 --- a/webodf/lib/gui/MetadataController.js +++ b/webodf/lib/gui/MetadataController.js @@ -18,15 +18,15 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ -/*global gui, runtime, core, ops, odf*/ +/*global gui, runtime, webodfcore, ops, odf*/ /** * @constructor - * @implements {core.Destroyable} + * @implements {webodfcore.Destroyable} * @param {!ops.Session} session * @param {!string} inputMemberId */ @@ -34,7 +34,7 @@ gui.MetadataController = function MetadataController(session, inputMemberId) { "use strict"; var odtDocument = session.getOdtDocument(), - eventNotifier = new core.EventNotifier([gui.MetadataController.signalMetadataChanged]), + eventNotifier = new webodfcore.EventNotifier([gui.MetadataController.signalMetadataChanged]), /** @const @type {!Array.} */ readonlyProperties = [ "dc:creator", diff --git a/webodf/lib/gui/MimeDataExporter.js b/webodf/lib/gui/MimeDataExporter.js index 78e727c1e..61d9ac62b 100644 --- a/webodf/lib/gui/MimeDataExporter.js +++ b/webodf/lib/gui/MimeDataExporter.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global gui, runtime, odf*/ diff --git a/webodf/lib/gui/OdfFieldView.js b/webodf/lib/gui/OdfFieldView.js index 25117b18d..27e1a2dd9 100644 --- a/webodf/lib/gui/OdfFieldView.js +++ b/webodf/lib/gui/OdfFieldView.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global gui, odf*/ @@ -28,7 +28,7 @@ * Show ODF fields in an ODT document. * * @constructor - * @implements {core.Destroyable} + * @implements {webodfcore.Destroyable} * @param {!odf.OdfCanvas} odfCanvas */ gui.OdfFieldView = function(odfCanvas) { diff --git a/webodf/lib/gui/OdfTextBodyNodeFilter.js b/webodf/lib/gui/OdfTextBodyNodeFilter.js index 5ecf5b1cd..fff3aecf9 100644 --- a/webodf/lib/gui/OdfTextBodyNodeFilter.js +++ b/webodf/lib/gui/OdfTextBodyNodeFilter.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global gui, NodeFilter, odf, Node*/ diff --git a/webodf/lib/gui/ParagraphBoundaryScanner.js b/webodf/lib/gui/ParagraphBoundaryScanner.js index 0f59d5f4a..19a412e60 100644 --- a/webodf/lib/gui/ParagraphBoundaryScanner.js +++ b/webodf/lib/gui/ParagraphBoundaryScanner.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global gui, odf*/ diff --git a/webodf/lib/gui/PasteController.js b/webodf/lib/gui/PasteController.js index 671fd2e49..80c4f1255 100644 --- a/webodf/lib/gui/PasteController.js +++ b/webodf/lib/gui/PasteController.js @@ -18,17 +18,17 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ -/*global runtime, gui, ops, odf, core*/ +/*global runtime, gui, ops, odf, webodfcore*/ /** * Provides a method to paste text at the current cursor * position, and processes the input string to understand * special structuring such as paragraph splits. - * @implements {core.Destroyable} + * @implements {webodfcore.Destroyable} * @param {!ops.Session} session * @param {!gui.SessionConstraints} sessionConstraints * @param {!gui.SessionContext} sessionContext @@ -43,7 +43,7 @@ gui.PasteController = function PasteController(session, sessionConstraints, sess /**@const*/ textns = odf.Namespaces.textns, /**@const*/ - NEXT = core.StepDirection.NEXT, + NEXT = webodfcore.StepDirection.NEXT, odfUtils = odf.OdfUtils; /** diff --git a/webodf/lib/gui/SelectionController.js b/webodf/lib/gui/SelectionController.js index 42f155cf3..d2dc1a5d6 100644 --- a/webodf/lib/gui/SelectionController.js +++ b/webodf/lib/gui/SelectionController.js @@ -18,22 +18,22 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ -/*global runtime, core, gui, odf, ops, Node */ +/*global runtime, webodfcore, gui, odf, ops, Node */ /** * @constructor - * @implements {core.Destroyable} + * @implements {webodfcore.Destroyable} * @param {!ops.Session} session * @param {!string} inputMemberId */ gui.SelectionController = function SelectionController(session, inputMemberId) { "use strict"; var odtDocument = session.getOdtDocument(), - domUtils = core.DomUtils, + domUtils = webodfcore.DomUtils, odfUtils = odf.OdfUtils, baseFilter = odtDocument.getPositionFilter(), guiStepUtils = new gui.GuiStepUtils(), @@ -42,12 +42,12 @@ gui.SelectionController = function SelectionController(session, inputMemberId) { caretXPositionLocator = null, /**@type{!number|undefined}*/ lastXPosition, - /**@type{!core.ScheduledTask}*/ + /**@type{!webodfcore.ScheduledTask}*/ resetLastXPositionTask, TRAILING_SPACE = odf.WordBoundaryFilter.IncludeWhitespace.TRAILING, LEADING_SPACE = odf.WordBoundaryFilter.IncludeWhitespace.LEADING, - PREVIOUS = core.StepDirection.PREVIOUS, - NEXT = core.StepDirection.NEXT, + PREVIOUS = webodfcore.StepDirection.PREVIOUS, + NEXT = webodfcore.StepDirection.NEXT, // Number of milliseconds to keep the user's last up/down caret X position for /**@const*/ UPDOWN_NAVIGATION_RESET_DELAY_MS = 2000; @@ -66,7 +66,7 @@ gui.SelectionController = function SelectionController(session, inputMemberId) { /** * Create a new step iterator with the base Odt filter, and a root filter for the current input member. * The step iterator subtree is set to the root of the current cursor node - * @return {!core.StepIterator} + * @return {!webodfcore.StepIterator} */ function createKeyboardStepIterator() { var cursor = odtDocument.getCursor(inputMemberId), @@ -80,7 +80,7 @@ gui.SelectionController = function SelectionController(session, inputMemberId) { * @param {!Node} node * @param {!number} offset * @param {!odf.WordBoundaryFilter.IncludeWhitespace} includeWhitespace - * @return {!core.StepIterator} + * @return {!webodfcore.StepIterator} */ function createWordBoundaryStepIterator(node, offset, includeWhitespace) { var wordBoundaryFilter = new odf.WordBoundaryFilter(odtDocument, includeWhitespace), @@ -266,7 +266,7 @@ gui.SelectionController = function SelectionController(session, inputMemberId) { * This function will assert if no valid step is found within the supplied root. * * @param {!Node} root Root to contain iteration within - * @param {!Array.} filters Position filters + * @param {!Array.} filters Position filters * @param {!Range} range Range to modify * @param {!boolean} modifyStart Set to true to modify the start container & offset. If false, the end * container and offset will be modified instead. @@ -368,7 +368,7 @@ gui.SelectionController = function SelectionController(session, inputMemberId) { this.selectRange = selectRange; /** - * @param {!core.StepDirection} direction + * @param {!webodfcore.StepDirection} direction * @param {!boolean} extend * @return {undefined} */ @@ -428,7 +428,7 @@ gui.SelectionController = function SelectionController(session, inputMemberId) { }; /** - * @param {!core.StepDirection} direction PREVIOUS for upwards NEXT for downwards + * @param {!webodfcore.StepDirection} direction PREVIOUS for upwards NEXT for downwards * @param {!boolean} extend * @return {undefined} */ @@ -515,7 +515,7 @@ gui.SelectionController = function SelectionController(session, inputMemberId) { this.extendSelectionDown = extendSelectionDown; /** - * @param {!core.StepDirection} direction + * @param {!webodfcore.StepDirection} direction * @param {!boolean} extend * @return {undefined} */ @@ -536,7 +536,7 @@ gui.SelectionController = function SelectionController(session, inputMemberId) { } /** - * @param {!core.StepDirection} direction + * @param {!webodfcore.StepDirection} direction * @param {!boolean} extend whether extend the selection instead of moving the cursor * @return {undefined} */ @@ -623,7 +623,7 @@ gui.SelectionController = function SelectionController(session, inputMemberId) { this.extendSelectionToLineEnd = extendSelectionToLineEnd; /** - * @param {!core.StepDirection} direction + * @param {!webodfcore.StepDirection} direction * @param {!boolean} extend True to extend the selection * @param {!function(!Node):Node} getContainmentNode Returns a node container for the supplied node. * Usually this will be something like the parent paragraph or root the supplied node is within @@ -765,11 +765,11 @@ gui.SelectionController = function SelectionController(session, inputMemberId) { */ this.destroy = function (callback) { odtDocument.unsubscribe(ops.OdtDocument.signalOperationStart, resetLastXPosition); - core.Async.destroyAll([resetLastXPositionTask.destroy], callback); + webodfcore.Async.destroyAll([resetLastXPositionTask.destroy], callback); }; function init() { - resetLastXPositionTask = core.Task.createTimeoutTask(function() { + resetLastXPositionTask = webodfcore.Task.createTimeoutTask(function() { lastXPosition = undefined; }, UPDOWN_NAVIGATION_RESET_DELAY_MS); odtDocument.subscribe(ops.OdtDocument.signalOperationStart, resetLastXPosition); diff --git a/webodf/lib/gui/SelectionView.js b/webodf/lib/gui/SelectionView.js index bc807e0d5..4eb40448e 100644 --- a/webodf/lib/gui/SelectionView.js +++ b/webodf/lib/gui/SelectionView.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global gui*/ diff --git a/webodf/lib/gui/SelectionViewManager.js b/webodf/lib/gui/SelectionViewManager.js index 2f508a6fc..72fc0f964 100644 --- a/webodf/lib/gui/SelectionViewManager.js +++ b/webodf/lib/gui/SelectionViewManager.js @@ -18,17 +18,17 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ -/*global runtime, core, gui, ops*/ +/*global runtime, webodfcore, gui, ops*/ /** * The Selection View Manager is responsible for managing SelectionView objects * and attaching/detaching them to cursors. * @constructor - * @implements {core.Destroyable} + * @implements {webodfcore.Destroyable} * @param {!function(new:gui.SelectionView, !(ops.OdtCursor|gui.ShadowCursor))} SelectionView */ gui.SelectionViewManager = function SelectionViewManager(SelectionView) { diff --git a/webodf/lib/gui/SessionConstraints.js b/webodf/lib/gui/SessionConstraints.js index 983a5371f..77bf836b0 100644 --- a/webodf/lib/gui/SessionConstraints.js +++ b/webodf/lib/gui/SessionConstraints.js @@ -18,11 +18,11 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ -/*global core, runtime, gui*/ +/*global webodfcore, runtime, gui*/ /** * This class allows handling of 'constraints', which are modelled @@ -35,7 +35,7 @@ gui.SessionConstraints = function SessionConstraints() { "use strict"; var constraints = {}, - constraintNotifier = new core.EventNotifier(); + constraintNotifier = new webodfcore.EventNotifier(); /** * Register a constraint. diff --git a/webodf/lib/gui/SessionContext.js b/webodf/lib/gui/SessionContext.js index 35a89df83..7ff274d76 100644 --- a/webodf/lib/gui/SessionContext.js +++ b/webodf/lib/gui/SessionContext.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global runtime, gui, odf */ diff --git a/webodf/lib/gui/SessionController.js b/webodf/lib/gui/SessionController.js index 278875607..8cf845293 100644 --- a/webodf/lib/gui/SessionController.js +++ b/webodf/lib/gui/SessionController.js @@ -18,11 +18,11 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ -/*global runtime, core, gui, Node, ops, odf */ +/*global runtime, webodfcore, gui, Node, ops, odf */ /** * @constructor @@ -51,11 +51,11 @@ gui.SessionControllerOptions = function () { (function () { "use strict"; - var /**@const*/FILTER_ACCEPT = core.PositionFilter.FilterResult.FILTER_ACCEPT; + var /**@const*/FILTER_ACCEPT = webodfcore.PositionFilter.FilterResult.FILTER_ACCEPT; /** * @constructor - * @implements {core.Destroyable} + * @implements {webodfcore.Destroyable} * @param {!ops.Session} session * @param {!string} inputMemberId * @param {!ops.OdtCursor} shadowCursor @@ -66,7 +66,7 @@ gui.SessionControllerOptions = function () { odtDocument = session.getOdtDocument(), sessionConstraints = new gui.SessionConstraints(), sessionContext = new gui.SessionContext(session, inputMemberId), - domUtils = core.DomUtils, + domUtils = webodfcore.DomUtils, odfUtils = odf.OdfUtils, mimeDataExporter = new gui.MimeDataExporter(), clipboard = new gui.Clipboard(mimeDataExporter), @@ -77,7 +77,7 @@ gui.SessionControllerOptions = function () { clickStartedWithinCanvas = false, objectNameGenerator = new odf.ObjectNameGenerator(odtDocument.getOdfCanvas().odfContainer(), inputMemberId), isMouseMoved = false, - /**@type{core.PositionFilter}*/ + /**@type{webodfcore.PositionFilter}*/ mouseDownRootFilter = null, handleMouseClickTimeoutId, undoManager = null, @@ -92,9 +92,9 @@ gui.SessionControllerOptions = function () { imageController = new gui.ImageController(session, sessionConstraints, sessionContext, inputMemberId, objectNameGenerator), imageSelector = new gui.ImageSelector(odtDocument.getOdfCanvas()), shadowCursorIterator = odtDocument.createPositionIterator(odtDocument.getRootNode()), - /**@type{!core.ScheduledTask}*/ + /**@type{!webodfcore.ScheduledTask}*/ drawShadowCursorTask, - /**@type{!core.ScheduledTask}*/ + /**@type{!webodfcore.ScheduledTask}*/ redrawRegionSelectionTask, pasteController = new gui.PasteController(session, sessionConstraints, sessionContext, inputMemberId), inputMethodEditor = new gui.InputMethodEditor(inputMemberId, eventManager), @@ -1132,12 +1132,17 @@ gui.SessionControllerOptions = function () { } runtime.clearTimeout(handleMouseClickTimeoutId); - core.Async.destroyAll(destroyCallbacks, callback); + webodfcore.Async.destroyAll(destroyCallbacks, callback); }; function init() { - drawShadowCursorTask = core.Task.createRedrawTask(updateShadowCursor); - redrawRegionSelectionTask = core.Task.createRedrawTask(redrawRegionSelection); + // The canvas follows a link on a plain click, which is what a + // reader of a document asks for; an editor asks for a key beside + // the click and answers for it here, so the canvas is told to + // leave the links alone. + odtDocument.getOdfCanvas().setHyperlinksActive(false); + drawShadowCursorTask = webodfcore.Task.createRedrawTask(updateShadowCursor); + redrawRegionSelectionTask = webodfcore.Task.createRedrawTask(redrawRegionSelection); keyDownHandler.bind(keyCode.Left, modifier.None, rangeSelectionOnly(selectionController.moveCursorToLeft)); keyDownHandler.bind(keyCode.Right, modifier.None, rangeSelectionOnly(selectionController.moveCursorToRight)); diff --git a/webodf/lib/gui/SessionView.js b/webodf/lib/gui/SessionView.js index d2b5dd484..64b44ed8e 100644 --- a/webodf/lib/gui/SessionView.js +++ b/webodf/lib/gui/SessionView.js @@ -1,5 +1,6 @@ /** * Copyright (C) 2012-2013 KO GmbH + * Copyright (C) 2026 Daniel Berthereau * * @licstart * This file is part of WebODF. @@ -18,11 +19,11 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ -/*global Node, document, runtime, gui, ops, core */ +/*global Node, document, runtime, gui, ops, webodfcore */ /** * @constructor @@ -70,7 +71,7 @@ gui.SessionViewOptions = function () { * be matched with the memberids for which CSS is generated, to generate the same CSS * for shadow cursors. * @constructor - * @implements {core.Destroyable} + * @implements {webodfcore.Destroyable} * @param {!gui.SessionViewOptions} viewOptions * @param {string} localMemberId * @param {!ops.Session} session @@ -90,8 +91,10 @@ gui.SessionViewOptions = function () { odtDocument, /**@type{!odf.OdfCanvas}*/ odfCanvas, - /**@type{!core.ScheduledTask}*/ + /**@type{!webodfcore.ScheduledTask}*/ highlightRefreshTask, + /**@type{!webodfcore.ScheduledTask}*/ + numberingRefreshTask, showEditInfoMarkers = configOption(viewOptions.editInfoMarkersInitiallyVisible, true), showCaretAvatars = configOption(viewOptions.caretAvatarsInitiallyVisible, true), blinkOnRangeSelect = configOption(viewOptions.caretBlinksOnRangeSelect, true); @@ -352,7 +355,6 @@ gui.SessionViewOptions = function () { caret.setAvatarImageUrl(properties.imageUrl); caret.setColor(properties.color); } - runtime.log("+++ View here +++ eagerly created an Caret for '" + memberId + "'! +++"); } /** @@ -401,6 +403,18 @@ gui.SessionViewOptions = function () { function onParagraphChanged(info) { highlightEdit(info.paragraphElement, info.memberId, info.timeStamp); highlightRefreshTask.trigger(); + // An item added to a list, or a heading added to the text, moves + // the numbers of everything that follows it: the labels are + // written again once the edits of the moment are done, and not + // at each one of them. + numberingRefreshTask.trigger(); + } + + /** + * @return {undefined} + */ + function refreshNumbering() { + odfCanvas.refreshNumbering(); } /** @@ -421,7 +435,7 @@ gui.SessionViewOptions = function () { // TODO: Move such handling into AnnotationViewManager if (annotationConstraintStyles.hasChildNodes()) { - core.DomUtils.removeAllChildNodes(annotationConstraintStyles); + webodfcore.DomUtils.removeAllChildNodes(annotationConstraintStyles); } if (sessionConstraints.getState(gui.CommonConstraints.EDIT.ANNOTATIONS.ONLY_DELETE_OWN) === true) { @@ -453,6 +467,9 @@ gui.SessionViewOptions = function () { odtDocument.unsubscribe(ops.OdtDocument.signalParagraphChanged, selectionViewManager.rerenderSelectionViews); odtDocument.unsubscribe(ops.OdtDocument.signalTableAdded, selectionViewManager.rerenderSelectionViews); + odtDocument.unsubscribe( + ops.OdtDocument.signalParagraphStyleModified, + numberingRefreshTask.trigger); odtDocument.unsubscribe(ops.OdtDocument.signalParagraphStyleModified, selectionViewManager.rerenderSelectionViews); sessionConstraints.unsubscribe(gui.CommonConstraints.EDIT.ANNOTATIONS.ONLY_DELETE_OWN, processConstraints); @@ -482,15 +499,23 @@ gui.SessionViewOptions = function () { * @return {undefined} */ this.destroy = function (callback) { - var cleanup = [highlightRefreshTask.destroy, destroy]; + var cleanup = [highlightRefreshTask.destroy, + numberingRefreshTask.destroy, destroy]; odtDocument.unsubscribe(ops.OdtDocument.signalAnnotationAdded, onAnnotationAdded); - core.Async.destroyAll(cleanup, callback); + webodfcore.Async.destroyAll(cleanup, callback); }; function init() { odtDocument = session.getOdtDocument(); odfCanvas = odtDocument.getOdfCanvas(); + // The tasks are made before anything is subscribed to them: the + // trigger of a task that is not there yet is undefined, and the + // subscription would throw. + highlightRefreshTask = webodfcore.Task.createRedrawTask(refreshHighlights); + numberingRefreshTask = + webodfcore.Task.createRedrawTask(refreshNumbering); + odtDocument.subscribe(ops.OdtDocument.signalAnnotationAdded, onAnnotationAdded); odtDocument.subscribe(ops.Document.signalMemberAdded, renderMemberData); odtDocument.subscribe(ops.Document.signalMemberUpdated, renderMemberData); @@ -501,6 +526,10 @@ gui.SessionViewOptions = function () { odtDocument.subscribe(ops.OdtDocument.signalParagraphChanged, selectionViewManager.rerenderSelectionViews); odtDocument.subscribe(ops.OdtDocument.signalTableAdded, selectionViewManager.rerenderSelectionViews); + // A paragraph that is made a heading, or a heading that is made + // a paragraph, is a number more or a number less. + odtDocument.subscribe(ops.OdtDocument.signalParagraphStyleModified, + numberingRefreshTask.trigger); odtDocument.subscribe(ops.OdtDocument.signalParagraphStyleModified, selectionViewManager.rerenderSelectionViews); sessionConstraints.subscribe(gui.CommonConstraints.EDIT.ANNOTATIONS.ONLY_DELETE_OWN, processConstraints); @@ -515,7 +544,6 @@ gui.SessionViewOptions = function () { annotationConstraintStyles = newStyleSheet(); processConstraints(); - highlightRefreshTask = core.Task.createRedrawTask(refreshHighlights); } init(); }; diff --git a/webodf/lib/gui/ShadowCursor.js b/webodf/lib/gui/ShadowCursor.js index db8275235..5c7e841f0 100644 --- a/webodf/lib/gui/ShadowCursor.js +++ b/webodf/lib/gui/ShadowCursor.js @@ -18,11 +18,11 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ -/*global core, gui, ops, runtime*/ +/*global webodfcore, gui, ops, runtime*/ /** * @class diff --git a/webodf/lib/gui/SingleScrollViewport.js b/webodf/lib/gui/SingleScrollViewport.js index db36f3502..535219c55 100644 --- a/webodf/lib/gui/SingleScrollViewport.js +++ b/webodf/lib/gui/SingleScrollViewport.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global gui*/ @@ -38,9 +38,9 @@ gui.SingleScrollViewport = function(scrollPane) { /** * Pad the client rect with the supplied margin - * @param {!core.SimpleClientRect} clientRect - * @param {!core.SimpleClientRect} margin - * @return {!core.SimpleClientRect} + * @param {!webodfcore.SimpleClientRect} clientRect + * @param {!webodfcore.SimpleClientRect} margin + * @return {!webodfcore.SimpleClientRect} */ function shrinkClientRectByMargin(clientRect, margin) { return { @@ -52,7 +52,7 @@ gui.SingleScrollViewport = function(scrollPane) { } /** - * @param {!core.SimpleClientRect} clientRect + * @param {!webodfcore.SimpleClientRect} clientRect * @return {!number} */ function height(clientRect) { @@ -60,7 +60,7 @@ gui.SingleScrollViewport = function(scrollPane) { } /** - * @param {!core.SimpleClientRect} clientRect + * @param {!webodfcore.SimpleClientRect} clientRect * @return {!number} */ function width(clientRect) { @@ -68,7 +68,7 @@ gui.SingleScrollViewport = function(scrollPane) { } /** - * @param {?core.SimpleClientRect} clientRect + * @param {?webodfcore.SimpleClientRect} clientRect * @param {!boolean=} alignWithTop * @return {undefined} */ @@ -77,18 +77,18 @@ gui.SingleScrollViewport = function(scrollPane) { horizontalScrollbarWidth = scrollPane.offsetWidth - scrollPane.clientWidth, nonNullClientRect, scrollPaneRect = scrollPane.getBoundingClientRect(), - /**@type{!core.SimpleClientRect}*/ + /**@type{!webodfcore.SimpleClientRect}*/ paneRect; if (!clientRect || !scrollPaneRect) { return; } - nonNullClientRect = /**@type{!core.SimpleClientRect}*/(clientRect); + nonNullClientRect = /**@type{!webodfcore.SimpleClientRect}*/(clientRect); // Visible area is slightly different from the BCR // See https://developer.mozilla.org/en-US/docs/Web/API/CSS_Object_Model/Determining_the_dimensions_of_elements - paneRect = shrinkClientRectByMargin(/**@type{!core.SimpleClientRect}*/(scrollPaneRect), { + paneRect = shrinkClientRectByMargin(/**@type{!webodfcore.SimpleClientRect}*/(scrollPaneRect), { top: VIEW_PADDING_PX, bottom: verticalScrollbarHeight + VIEW_PADDING_PX, left: VIEW_PADDING_PX, diff --git a/webodf/lib/gui/StyleSummary.js b/webodf/lib/gui/StyleSummary.js index 86d4c19b3..67aadc984 100644 --- a/webodf/lib/gui/StyleSummary.js +++ b/webodf/lib/gui/StyleSummary.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global runtime, gui*/ diff --git a/webodf/lib/gui/SvgSelectionView.js b/webodf/lib/gui/SvgSelectionView.js index cd8499747..d64dd363b 100644 --- a/webodf/lib/gui/SvgSelectionView.js +++ b/webodf/lib/gui/SvgSelectionView.js @@ -18,17 +18,17 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ -/*global Node, NodeFilter, gui, odf, ops, runtime, core*/ +/*global Node, NodeFilter, gui, odf, ops, runtime, webodfcore*/ /** * A GUI class that attaches to a cursor and renders it's selection * as an SVG polygon. * @constructor - * @implements {core.Destroyable} + * @implements {webodfcore.Destroyable} * @implements {gui.SelectionView} * @param {!ops.OdtCursor} cursor */ @@ -47,7 +47,7 @@ gui.SvgSelectionView = function SvgSelectionView(cursor) { handle1 = doc.createElementNS(svgns, 'circle'), handle2 = doc.createElementNS(svgns, 'circle'), odfUtils = odf.OdfUtils, - domUtils = core.DomUtils, + domUtils = webodfcore.DomUtils, /**@type{!gui.ZoomHelper}*/ zoomHelper = document.getCanvas().getZoomHelper(), /**@type{boolean}*/ @@ -59,7 +59,7 @@ gui.SvgSelectionView = function SvgSelectionView(cursor) { FILTER_REJECT = NodeFilter.FILTER_REJECT, /**@const*/ HANDLE_RADIUS = 8, - /**@type{!core.ScheduledTask}*/ + /**@type{!webodfcore.ScheduledTask}*/ renderTask; /** @@ -290,7 +290,7 @@ gui.SvgSelectionView = function SvgSelectionView(cursor) { grownRect = null, currentRect, range = doc.createRange(), - /**@type{!core.PositionFilter}*/ + /**@type{!webodfcore.PositionFilter}*/ rootFilter, odfNodeFilter = new odf.OdfNodeFilter(), treeWalker; @@ -650,14 +650,14 @@ gui.SvgSelectionView = function SvgSelectionView(cursor) { * @param {function(!Error=)} callback */ this.destroy = function (callback) { - core.Async.destroyAll([renderTask.destroy, destroy], callback); + webodfcore.Async.destroyAll([renderTask.destroy, destroy], callback); }; function init() { var editinfons = 'urn:webodf:names:editinfo', memberid = cursor.getMemberId(); - renderTask = core.Task.createRedrawTask(rerender); + renderTask = webodfcore.Task.createRedrawTask(rerender); addOverlay(); overlay.setAttributeNS(editinfons, 'editinfo:memberid', memberid); sizer.classList.add('webodf-virtualSelections'); diff --git a/webodf/lib/gui/TextController.js b/webodf/lib/gui/TextController.js index 58ab4372f..0ce217956 100644 --- a/webodf/lib/gui/TextController.js +++ b/webodf/lib/gui/TextController.js @@ -18,15 +18,15 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ -/*global core, ops, gui, odf, runtime*/ +/*global webodfcore, ops, gui, odf, runtime*/ /** * @constructor - * @implements {core.Destroyable} + * @implements {webodfcore.Destroyable} * @param {!ops.Session} session * @param {!gui.SessionConstraints} sessionConstraints * @param {!gui.SessionContext} sessionContext @@ -46,7 +46,7 @@ gui.TextController = function TextController( var odtDocument = session.getOdtDocument(), odfUtils = odf.OdfUtils, - domUtils = core.DomUtils, + domUtils = webodfcore.DomUtils, /** * @const * @type {!boolean} @@ -61,7 +61,7 @@ gui.TextController = function TextController( /** @const */ textns = odf.Namespaces.textns, /**@const*/ - NEXT = core.StepDirection.NEXT; + NEXT = webodfcore.StepDirection.NEXT; /** * @return {undefined} @@ -292,7 +292,7 @@ gui.TextController = function TextController( * The iterator is constrained within the root element for the current cursor position so * iteration will stop once the root is entirely walked in the requested direction * @param {!Element} cursorNode - * @return {!core.StepIterator} + * @return {!webodfcore.StepIterator} */ function createStepIterator(cursorNode) { var cursorRoot = odtDocument.getRootElement(cursorNode), diff --git a/webodf/lib/gui/TrivialUndoManager.js b/webodf/lib/gui/TrivialUndoManager.js index b3d4de3a4..58e0d937a 100644 --- a/webodf/lib/gui/TrivialUndoManager.js +++ b/webodf/lib/gui/TrivialUndoManager.js @@ -18,11 +18,11 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ -/*global gui,ops,core,runtime*/ +/*global gui,ops,webodfcore,runtime*/ (function() { "use strict"; @@ -126,7 +126,7 @@ function StateTransition(undoRules, initialOps, editOpsPossible) { gui.TrivialUndoManager = function TrivialUndoManager(defaultRules) { var self = this, cursorns = 'urn:webodf:names:cursor', - domUtils = core.DomUtils, + domUtils = webodfcore.DomUtils, /**@type{?Element}*/ initialDoc, /**@type{!StateTransition}*/ @@ -142,7 +142,7 @@ gui.TrivialUndoManager = function TrivialUndoManager(defaultRules) { undoStateTransitions = [], /**@type{!Array.}*/ redoStateTransitions = [], - eventNotifier = new core.EventNotifier([ + eventNotifier = new webodfcore.EventNotifier([ gui.UndoManager.signalUndoStackChanged, gui.UndoManager.signalUndoStateCreated, gui.UndoManager.signalUndoStateModified, @@ -249,7 +249,10 @@ gui.TrivialUndoManager = function TrivialUndoManager(defaultRules) { moveCursor = {}, requiredAddOps = {}, remainingAddOps, - ops, + // Not named "ops": it would shadow the namespace of the same name, + // used by the annotations of this function. + /**@type{!Array.}*/ + operations, stateTransition = undoStateTransitions.pop(); document.getMemberIds().forEach(function (memberid) { @@ -285,9 +288,9 @@ gui.TrivialUndoManager = function TrivialUndoManager(defaultRules) { } while (stateTransition && remainingAddOps > 0) { - ops = stateTransition.getOperations(); - ops.reverse(); // Want the LAST move/add operation seen - ops.forEach(processOp); + operations = stateTransition.getOperations(); + operations.reverse(); // Want the LAST move/add operation seen + operations.forEach(processOp); stateTransition = undoStateTransitions.pop(); } diff --git a/webodf/lib/gui/UndoManager.js b/webodf/lib/gui/UndoManager.js index f7979e02f..9335495de 100644 --- a/webodf/lib/gui/UndoManager.js +++ b/webodf/lib/gui/UndoManager.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global ops, gui*/ diff --git a/webodf/lib/gui/UndoStateRules.js b/webodf/lib/gui/UndoStateRules.js index 0e6c9dae9..7b6981ced 100644 --- a/webodf/lib/gui/UndoStateRules.js +++ b/webodf/lib/gui/UndoStateRules.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global gui, runtime*/ diff --git a/webodf/lib/gui/Viewport.js b/webodf/lib/gui/Viewport.js index 6f5093c6b..fea694526 100644 --- a/webodf/lib/gui/Viewport.js +++ b/webodf/lib/gui/Viewport.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global gui*/ @@ -39,7 +39,7 @@ gui.Viewport = function Viewport() { "use strict"; }; * rect will be shown in the view as possible whilst ensuring the top is still * on screen. Similar logic applies if the clientRect width is too large. * - * @param {?core.SimpleClientRect} clientRect + * @param {?webodfcore.SimpleClientRect} clientRect * @param {!boolean=} alignWithTop Align the clientRect to the top of the viewport. If unspecified or false, the * view will scroll only as much as required to bring the clientRect into view. * @return {undefined} diff --git a/webodf/lib/gui/VisualStepScanner.js b/webodf/lib/gui/VisualStepScanner.js index f05b42943..66c69106a 100644 --- a/webodf/lib/gui/VisualStepScanner.js +++ b/webodf/lib/gui/VisualStepScanner.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global gui*/ @@ -36,7 +36,7 @@ gui.StepInfo = function() { "use strict"; }; * whether the text block is right-to-left or left to right. * * For left-to-right languages, this maps onto - * core.StepDirection as: + * webodfcore.StepDirection as: * LEFT_TO_RIGHT = NEXT * RIGHT_TO_LEFT = PREV * @@ -48,7 +48,7 @@ gui.StepInfo.VisualDirection = { }; /** - * @type {!core.StepIterator.StepSnapshot} + * @type {!webodfcore.StepIterator.StepSnapshot} */ gui.StepInfo.prototype.token; @@ -65,7 +65,7 @@ gui.StepInfo.prototype.offset = function() { "use strict"; }; /** * The direction of iteration from previous to next rect. * - * @type {!core.StepDirection} + * @type {!webodfcore.StepDirection} */ gui.StepInfo.prototype.direction; @@ -88,7 +88,7 @@ gui.VisualStepScanner = function() { "use strict"; }; /** * Token for the last step accepted by this scanner - * @type {?core.StepIterator.StepSnapshot|undefined} + * @type {?webodfcore.StepIterator.StepSnapshot|undefined} */ gui.VisualStepScanner.prototype.token; diff --git a/webodf/lib/gui/ZoomHelper.js b/webodf/lib/gui/ZoomHelper.js index d53335a00..696ddc0cd 100644 --- a/webodf/lib/gui/ZoomHelper.js +++ b/webodf/lib/gui/ZoomHelper.js @@ -18,11 +18,11 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ -/*global runtime, core, gui*/ +/*global runtime, webodfcore, gui*/ (function () { "use strict"; @@ -68,7 +68,7 @@ * on the sizer element. It also provides some methods to set, get, and * subscribe to the current zoom level. * @constructor - * @implements {core.Destroyable} + * @implements {webodfcore.Destroyable} */ gui.ZoomHelper = function () { var /**@type{!HTMLElement}*/ @@ -88,7 +88,7 @@ offsetParent, /**@type{!HTMLElement}*/ parentElement, - events = new core.EventNotifier([gui.ZoomHelper.signalZoomChanged]), + events = new webodfcore.EventNotifier([gui.ZoomHelper.signalZoomChanged]), /**@const*/ gestures = { NONE: 0, @@ -106,6 +106,16 @@ * @type{!boolean} */ requiresCustomScrollBars = runtime.getWindow().hasOwnProperty('ontouchstart'), + /** + * Whether ZoomHelper handles touch gestures itself. When false, no + * touch listeners are attached and pinch-zoom/pan is left to the + * host (e.g. the native WebView viewport). Programmatic zoom via + * setZoomLevel still works. Hosts that embed the canvas in a + * natively-scrolling container disable this so two zoom systems do + * not fight over the same gestures. + * @type{!boolean} + */ + gesturesEnabled = true, /**@type{?string}*/ parentOverflow = ""; @@ -119,19 +129,16 @@ * @return {undefined} */ function applyCSSTransform(x, y, scale, is3D) { - var transformCommand; - - if (is3D) { - transformCommand = 'translate3d(' + x + 'px, ' + y + 'px, 0) scale3d(' + scale + ', ' + scale + ', 1)'; - } else { - transformCommand = 'translate(' + x + 'px, ' + y + 'px) scale(' + scale + ')'; - } - - zoomableElement.style.WebkitTransform = transformCommand; - zoomableElement.style.MozTransform = transformCommand; - zoomableElement.style.msTransform = transformCommand; - zoomableElement.style.OTransform = transformCommand; - zoomableElement.style.transform = transformCommand; + /*jslint unparam: true*/ + // Scale the page with the CSS `zoom` property rather than a CSS + // transform. A transform promotes the (very tall) page onto a + // composited GPU layer, which mobile WebViews rasterise in tiles; + // tiles far from the viewport are left unpainted, so the white page + // background shows up only in patches and flickers while zooming. + // `zoom` rescales the layout box itself, so the page stays a normal + // painted element (like the RTF preview) with a solid background. + // Panning (x/y) is handled by native scrolling, so it is ignored. + zoomableElement.style.zoom = scale; } /** @@ -461,7 +468,7 @@ * @return {undefined} */ function registerGestureListeners() { - if (offsetParent) { + if (offsetParent && gesturesEnabled) { // There is no reliable way of detecting if the browser // supports these touch events. Therefore the only thing // we can do is simply attach listeners to these events @@ -484,6 +491,24 @@ } } + /** + * Enable or disable ZoomHelper's own touch-gesture handling + * (pinch-zoom and pan). When disabled, any attached listeners are + * removed and none are re-attached on subsequent setZoomableElement + * calls, leaving gestures to the host. Programmatic setZoomLevel is + * unaffected. + * @param {!boolean} enabled + * @return {undefined} + */ + this.setGestureSupportEnabled = function (enabled) { + gesturesEnabled = enabled; + if (enabled) { + registerGestureListeners(); + } else { + unregisterGestureListeners(); + } + }; + /** * @param {!function(!Error=)} callback, passing an error object in case of error * @return {undefined} diff --git a/webodf/lib/manifest.json b/webodf/lib/manifest.json index 9ccf9d5ea..180325fb0 100644 --- a/webodf/lib/manifest.json +++ b/webodf/lib/manifest.json @@ -1,61 +1,4 @@ { - "core.Async": [ - ], - "core.Base64": [ - ], - "core.CSSUnits": [ - ], - "core.Cursor": [ - "core.DomUtils" - ], - "core.Destroyable": [ - ], - "core.DomUtils": [ - ], - "core.EventNotifier": [ - "core.EventSource" - ], - "core.EventSource": [ - ], - "core.EventSubscriptions": [ - "core.Async", - "core.EventNotifier", - "core.Task" - ], - "core.LazyProperty": [ - ], - "core.LoopWatchDog": [ - ], - "core.NodeFilterChain": [ - ], - "core.PositionFilter": [ - "core.PositionIterator" - ], - "core.PositionFilterChain": [ - "core.PositionFilter" - ], - "core.PositionIterator": [ - ], - "core.ScheduledTask": [ - "core.Destroyable" - ], - "core.StepIterator": [ - "core.PositionFilter", - "core.enums" - ], - "core.Task": [ - "core.ScheduledTask" - ], - "core.Utils": [ - ], - "core.Zip": [ - "core.Base64", - "externs.JSZip" - ], - "core.enums": [ - ], - "core.typedefs": [ - ], "externs.JSZip": [ ], "gui.AnnotationController": [ @@ -68,7 +11,7 @@ "odf.OdfUtils" ], "gui.Avatar": [ - "core.Destroyable" + "webodfcore.Destroyable" ], "gui.BlacklistNamespaceNodeFilter": [ ], @@ -90,15 +33,15 @@ "gui.CommonConstraints": [ ], "gui.DirectFormattingController": [ - "core.LazyProperty", "gui.CommonConstraints", "gui.SessionConstraints", "gui.SessionContext", - "gui.StyleSummary" + "gui.StyleSummary", + "webodfcore.LazyProperty" ], "gui.EditInfoHandle": [ - "core.Destroyable", - "core.DomUtils" + "webodfcore.Destroyable", + "webodfcore.DomUtils" ], "gui.EditInfoMarker": [ "gui.EditInfoHandle", @@ -113,9 +56,9 @@ "odf.StepUtils" ], "gui.HyperlinkClickHandler": [ - "core.Destroyable", "gui.KeyboardHandler", "odf.OdfUtils", + "webodfcore.Destroyable", "xmldom.XPath" ], "gui.HyperlinkController": [ @@ -143,8 +86,8 @@ "gui.KeyboardHandler": [ ], "gui.LineBoundaryScanner": [ - "core.typedefs", - "gui.VisualStepScanner" + "gui.VisualStepScanner", + "webodfcore.typedefs" ], "gui.MetadataController": [ "ops.Session" @@ -183,7 +126,7 @@ "gui.SelectionView" ], "gui.SessionConstraints": [ - "core.EventNotifier" + "webodfcore.EventNotifier" ], "gui.SessionContext": [ "ops.Session" @@ -218,11 +161,11 @@ "odf.Formatting" ], "gui.SvgSelectionView": [ - "core.Async", - "core.Task", "gui.SelectionView", "odf.OdfNodeFilter", - "odf.OdfUtils" + "odf.OdfUtils", + "webodfcore.Async", + "webodfcore.Task" ], "gui.TextController": [ "gui.CommonConstraints", @@ -230,9 +173,9 @@ "gui.SessionContext" ], "gui.TrivialUndoManager": [ - "core.DomUtils", "gui.UndoManager", - "gui.UndoStateRules" + "gui.UndoStateRules", + "webodfcore.DomUtils" ], "gui.UndoManager": [ "ops.Operation" @@ -241,14 +184,14 @@ "ops.Operation" ], "gui.Viewport": [ - "core.typedefs" + "webodfcore.typedefs" ], "gui.VisualStepScanner": [ - "core.StepIterator" + "webodfcore.StepIterator" ], "gui.ZoomHelper": [ - "core.Destroyable", - "core.EventNotifier" + "webodfcore.Destroyable", + "webodfcore.EventNotifier" ], "odf.CollapsingRules": [ "odf.OdfUtils" @@ -261,41 +204,41 @@ "odf.OdfUtils" ], "odf.Formatting": [ - "core.CSSUnits", - "core.Utils", "odf.OdfContainer", - "odf.OdfUtils" + "odf.OdfUtils", + "webodfcore.CSSUnits", + "webodfcore.Utils" ], "odf.GraphicProperties": [ "odf.StyleParseUtils" ], "odf.ListStylesToCss": [ - "core.CSSUnits", "odf.OdfUtils", - "odf.StyleTree" + "odf.StyleTree", + "webodfcore.CSSUnits" ], "odf.Namespaces": [ ], "odf.ObjectNameGenerator": [ - "core.Utils", - "odf.OdfContainer" + "odf.OdfContainer", + "webodfcore.Utils" ], "odf.OdfCanvas": [ - "core.Async", - "core.Task", "gui.AnnotationViewManager", "gui.SingleScrollViewport", "odf.FontLoader", "odf.Formatting", "odf.ListStylesToCss", - "odf.Style2CSS", - "ops.Canvas" + "odf.TextLayout", + "ops.Canvas", + "webodfcore.Async", + "webodfcore.Task" ], "odf.OdfContainer": [ - "core.DomUtils", - "core.Zip", "odf.OdfNodeFilter", "odf.StyleInfo", + "webodfcore.DomUtils", + "webodfcore.Zip", "xmldom.LSSerializer" ], "odf.OdfNodeFilter": [ @@ -305,24 +248,31 @@ "odf.Namespaces" ], "odf.OdfUtils": [ - "core.DomUtils", - "odf.OdfSchema" + "odf.OdfSchema", + "webodfcore.DomUtils" + ], + "odf.PageFurniture": [ + "odf.PageMeasure", + "odf.Style2CSS" ], "odf.PageLayoutProperties": [ "odf.StyleParseUtils" ], + "odf.PageMeasure": [ + "odf.OdfContainer" + ], "odf.ParagraphProperties": [ "odf.StyleParseUtils" ], "odf.StepUtils": [ - "core.StepIterator" + "webodfcore.StepIterator" ], "odf.Style2CSS": [ - "core.CSSUnits", + "odf.OdfContainer", "odf.OdfUtils", "odf.StyleParseUtils", "odf.StyleTree", - "xmldom.XPath" + "webodfcore.CSSUnits" ], "odf.StyleCache": [ "odf.GraphicProperties", @@ -341,6 +291,9 @@ "odf.StyleTree": [ "odf.Namespaces" ], + "odf.TextLayout": [ + "odf.PageFurniture" + ], "odf.TextProperties": [ "odf.StyleParseUtils" ], @@ -349,9 +302,9 @@ "xmldom.LSSerializerFilter" ], "odf.TextStyleApplicator": [ - "core.LoopWatchDog", "odf.Formatting", - "odf.ObjectNameGenerator" + "odf.ObjectNameGenerator", + "webodfcore.LoopWatchDog" ], "odf.WordBoundaryFilter": [ "ops.OdtDocument" @@ -360,8 +313,8 @@ "gui.ZoomHelper" ], "ops.Document": [ - "core.PositionFilter", - "ops.Canvas" + "ops.Canvas", + "webodfcore.PositionFilter" ], "ops.EditInfo": [ "ops.OdtDocument" @@ -369,12 +322,10 @@ "ops.Member": [ ], "ops.OdtCursor": [ - "core.Cursor", - "ops.Document" + "ops.Document", + "webodfcore.Cursor" ], "ops.OdtDocument": [ - "core.NodeFilterChain", - "core.PositionFilterChain", "gui.BlacklistNamespaceNodeFilter", "gui.OdfTextBodyNodeFilter", "odf.OdfCanvas", @@ -383,13 +334,15 @@ "ops.OdtCursor", "ops.OdtStepsTranslator", "ops.Operation", - "ops.TextPositionFilter" + "ops.TextPositionFilter", + "webodfcore.NodeFilterChain", + "webodfcore.PositionFilterChain" ], "ops.OdtStepsTranslator": [ - "core.PositionFilter", - "core.enums", "odf.OdfUtils", - "ops.StepsCache" + "ops.StepsCache", + "webodfcore.PositionFilter", + "webodfcore.enums" ], "ops.OpAddAnnotation": [ "ops.OdtDocument" @@ -521,17 +474,74 @@ "ops.TrivialOperationRouter" ], "ops.StepsCache": [ - "core.DomUtils", - "core.LoopWatchDog", - "core.PositionIterator" + "webodfcore.DomUtils", + "webodfcore.LoopWatchDog", + "webodfcore.PositionIterator" ], "ops.TextPositionFilter": [ - "core.PositionFilter", - "odf.OdfUtils" + "odf.OdfUtils", + "webodfcore.PositionFilter" ], "ops.TrivialOperationRouter": [ "ops.OperationRouter" ], + "webodfcore.Async": [ + ], + "webodfcore.Base64": [ + ], + "webodfcore.CSSUnits": [ + ], + "webodfcore.Cursor": [ + "webodfcore.DomUtils" + ], + "webodfcore.Destroyable": [ + ], + "webodfcore.DomUtils": [ + ], + "webodfcore.EventNotifier": [ + "webodfcore.EventSource" + ], + "webodfcore.EventSource": [ + ], + "webodfcore.EventSubscriptions": [ + "webodfcore.Async", + "webodfcore.EventNotifier", + "webodfcore.Task" + ], + "webodfcore.LazyProperty": [ + ], + "webodfcore.LoopWatchDog": [ + ], + "webodfcore.NodeFilterChain": [ + ], + "webodfcore.PositionFilter": [ + "webodfcore.PositionIterator" + ], + "webodfcore.PositionFilterChain": [ + "webodfcore.PositionFilter" + ], + "webodfcore.PositionIterator": [ + ], + "webodfcore.ScheduledTask": [ + "webodfcore.Destroyable" + ], + "webodfcore.StepIterator": [ + "webodfcore.PositionFilter", + "webodfcore.enums" + ], + "webodfcore.Task": [ + "webodfcore.ScheduledTask" + ], + "webodfcore.Utils": [ + ], + "webodfcore.Zip": [ + "externs.JSZip", + "webodfcore.Base64" + ], + "webodfcore.enums": [ + ], + "webodfcore.typedefs": [ + ], "xmldom.LSSerializer": [ "xmldom.LSSerializerFilter" ], diff --git a/webodf/lib/odf/CollapsingRules.js b/webodf/lib/odf/CollapsingRules.js index 765fea603..1fcf38f8f 100644 --- a/webodf/lib/odf/CollapsingRules.js +++ b/webodf/lib/odf/CollapsingRules.js @@ -18,11 +18,11 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ -/*global odf, core, Node, NodeFilter*/ +/*global odf, webodfcore, Node, NodeFilter*/ /** * Defines a set of rules for how elements can be collapsed based on whether they contain ODT content (e.g., @@ -33,7 +33,7 @@ odf.CollapsingRules = function CollapsingRules(rootNode) { "use strict"; var odfUtils = odf.OdfUtils, - domUtils = core.DomUtils; + domUtils = webodfcore.DomUtils; /** * Returns NodeFilter value if a given node is odf node or a text node that has a odf parent. diff --git a/webodf/lib/odf/CommandLineTools.js b/webodf/lib/odf/CommandLineTools.js index 424fa8be2..5fc33b575 100644 --- a/webodf/lib/odf/CommandLineTools.js +++ b/webodf/lib/odf/CommandLineTools.js @@ -18,11 +18,11 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ -/*global runtime, core, odf*/ +/*global runtime, webodfcore, odf*/ /** * @constructor @@ -60,7 +60,7 @@ odf.CommandLineTools = function CommandLineTools() { this.render = function (inputfilepath, document, callback) { var body = document.getElementsByTagName("body")[0], odfcanvas; - core.DomUtils.removeAllChildNodes(body); + webodfcore.DomUtils.removeAllChildNodes(body); odfcanvas = new odf.OdfCanvas(body); odfcanvas.addListener("statereadychange", function (err) { callback(err); diff --git a/webodf/lib/odf/FontLoader.js b/webodf/lib/odf/FontLoader.js index 8da4455a1..b50144815 100644 --- a/webodf/lib/odf/FontLoader.js +++ b/webodf/lib/odf/FontLoader.js @@ -18,11 +18,11 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ -/*global runtime, odf, core, document, xmldom*/ +/*global runtime, odf, webodfcore, document, xmldom*/ /*jslint sub: true*/ @@ -30,7 +30,7 @@ "use strict"; var xpath = xmldom.XPath, odfUtils = odf.OdfUtils, - base64 = new core.Base64(); + base64 = new webodfcore.Base64(); /** * @param {!Element} fontFaceDecls diff --git a/webodf/lib/odf/Formatting.js b/webodf/lib/odf/Formatting.js index f6e46ab68..7c58ff2e2 100644 --- a/webodf/lib/odf/Formatting.js +++ b/webodf/lib/odf/Formatting.js @@ -1,5 +1,6 @@ /** * Copyright (C) 2012-2013 KO GmbH + * Copyright (C) 2026 Daniel Berthereau * * @licstart * This file is part of WebODF. @@ -18,11 +19,11 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ -/*global Node, odf, runtime, core*/ +/*global Node, odf, runtime, webodfcore*/ /** * @constructor @@ -38,15 +39,11 @@ odf.Formatting = function Formatting() { /**@const*/ stylens = odf.Namespaces.stylens, /**@const*/ - textns = odf.Namespaces.textns, - /**@const*/ - numberns = odf.Namespaces.numberns, - /**@const*/ fons = odf.Namespaces.fons, odfUtils = odf.OdfUtils, - domUtils = core.DomUtils, - utils = new core.Utils(), - cssUnits = new core.CSSUnits(), + domUtils = webodfcore.DomUtils, + utils = new webodfcore.Utils(), + cssUnits = new webodfcore.CSSUnits(), // TODO: needs to be extended. Possibly created together with CSS from sone default description? /**@const @type {!Object.}*/ @@ -206,38 +203,10 @@ odf.Formatting = function Formatting() { * @return {?Element} */ function getStyleElement(styleName, family, styleElements) { - var node, - nodeStyleName, - styleListElement, - i; - - styleElements = styleElements || [odfContainer.rootElement.automaticStyles, odfContainer.rootElement.styles]; - for (i = 0; i < styleElements.length; i += 1) { - styleListElement = /**@type{!Element}*/(styleElements[i]); - node = styleListElement.firstElementChild; - while (node) { - nodeStyleName = node.getAttributeNS(stylens, 'name'); - if (node.namespaceURI === stylens - && node.localName === "style" - && node.getAttributeNS(stylens, 'family') === family - && nodeStyleName === styleName) { - return node; - } - if (family === "list-style" - && node.namespaceURI === textns - && node.localName === "list-style" - && nodeStyleName === styleName) { - return node; - } - if (family === "data" - && node.namespaceURI === numberns - && nodeStyleName === styleName) { - return node; - } - node = node.nextElementSibling; - } - } - return null; + return styleInfo.getStyleElement(styleName, family, styleElements || [ + odfContainer.rootElement.automaticStyles, + odfContainer.rootElement.styles + ]); } this.getStyleElement = getStyleElement; @@ -661,12 +630,55 @@ odf.Formatting = function Formatting() { } /** - * Gets the width and height of content area in pixels. + * The page layout of the first master page, that a document follows when + * no style names one: a text has one master page in all but the rarest + * cases. + * @return {?Element} + */ + function firstPageLayoutElement() { + var masterStyles = odfContainer.rootElement.masterStyles, + automaticStyles = odfContainer.rootElement.automaticStyles, + /**@type{!NodeList}*/ + masterPages, + /**@type{!NodeList}*/ + layouts, + layoutName = "", + /**@type{!Element}*/ + layout, + i; + // A document holds no master styles and no automatic styles of its + // own where it was written by hand or by a program that wrote none: + // the page is then the one of the defaults. + if (!masterStyles || !automaticStyles) { + return null; + } + masterPages = masterStyles.getElementsByTagNameNS(stylens, + "master-page"); + layouts = automaticStyles.getElementsByTagNameNS(stylens, + "page-layout"); + if (masterPages.length > 0) { + layoutName = /**@type{!Element}*/(masterPages.item(0)) + .getAttributeNS(stylens, "page-layout-name") || ""; + } + for (i = 0; i < layouts.length; i += 1) { + layout = /**@type{!Element}*/(layouts.item(i)); + if (layoutName === "" + || layout.getAttributeNS(stylens, "name") === layoutName) { + return layout; + } + } + return null; + } + /** + * The geometry of a page, in pixels: the area a text is written in, and + * the distance from the left and the top edge of the sheet to it. What the + * standard calls the page is the sheet, margins included, and + * "page-content" is that area, see the values of "style:horizontal-rel". * @param {string} styleName * @param {string} styleFamily - * @return {!{width: number, height: number}} Available content size in pixels + * @return {!odf.Formatting.PageGeometry} */ - this.getContentSize = function(styleName, styleFamily) { + function readPageGeometry(styleName, styleFamily) { var pageLayoutElement, props, defaultOrientedPageWidth, @@ -684,11 +696,17 @@ odf.Formatting = function Formatting() { paddingTop, paddingBottom; - pageLayoutElement = getPageLayoutStyleElement(styleName, styleFamily); - if (!pageLayoutElement) { + // Without a style to follow, the page is the one of the first master + // page, which is the only one a text has in all but the rarest cases. + pageLayoutElement = styleName + ? getPageLayoutStyleElement(styleName, styleFamily) + : firstPageLayoutElement(); + if (!pageLayoutElement && odfContainer.rootElement.styles) { pageLayoutElement = domUtils.getDirectChild(odfContainer.rootElement.styles, stylens, "default-page-layout"); } - props = domUtils.getDirectChild(pageLayoutElement, stylens, "page-layout-properties"); + props = pageLayoutElement + ? domUtils.getDirectChild(pageLayoutElement, stylens, "page-layout-properties") + : null; if (props) { // set page's default width and height based on print orientation if (props.getAttributeNS(stylens, "print-orientation") === "landscape") { @@ -732,11 +750,42 @@ odf.Formatting = function Formatting() { } return { width: pageWidth - marginLeft - marginRight - paddingLeft - paddingRight, - height: pageHeight - marginTop - marginBottom - paddingTop - paddingBottom + height: pageHeight - marginTop - marginBottom - paddingTop - paddingBottom, + left: marginLeft + paddingLeft, + top: marginTop + paddingTop }; + } + /** + * The width and the height of the area a text is written in, in pixels. + * @param {string} styleName + * @param {string} styleFamily + * @return {!{width: number, height: number}} Available content size in pixels + */ + this.getContentSize = function (styleName, styleFamily) { + var geometry = readPageGeometry(styleName, styleFamily); + return {width: geometry.width, height: geometry.height}; + }; + /** + * The distance from the left and the top edge of the sheet to the area a + * text is written in, in pixels, that a frame placed against the sheet is + * written from. + * @param {string} styleName + * @param {string} styleFamily + * @return {!odf.Formatting.PageGeometry} + */ + this.getPageMargins = function (styleName, styleFamily) { + return readPageGeometry(styleName, styleFamily); }; }; +/**@typedef{{ + width:!number, + height:!number, + left:!number, + top:!number +}}*/ +odf.Formatting.PageGeometry; + /**@typedef{{ name:!string, family:!string, diff --git a/webodf/lib/odf/GraphicProperties.js b/webodf/lib/odf/GraphicProperties.js index ef37f2f2b..10ddfe617 100644 --- a/webodf/lib/odf/GraphicProperties.js +++ b/webodf/lib/odf/GraphicProperties.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global runtime, odf*/ diff --git a/webodf/lib/odf/ListStylesToCss.js b/webodf/lib/odf/ListStylesToCss.js index 7ca91ac86..4683c1aa4 100644 --- a/webodf/lib/odf/ListStylesToCss.js +++ b/webodf/lib/odf/ListStylesToCss.js @@ -1,5 +1,6 @@ /** * Copyright (C) 2010-2014 KO GmbH + * Copyright (C) 2026 Daniel Berthereau * * @licstart * This file is part of WebODF. @@ -18,11 +19,11 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ -/*global odf, core, runtime*/ +/*global odf, webodfcore, runtime*/ (function () { "use strict"; @@ -47,12 +48,29 @@ listCounterIdSuffix = "webodf-listLevel", /**@const @type{!Object.}*/ - stylemap = { - '1': 'decimal', - 'a': 'lower-latin', - 'A': 'upper-latin', - 'i': 'lower-roman', - 'I': 'upper-roman' + /**@const + @type{!RegExp}*/ + // Symbol/dingbat fonts whose glyphs are encoded as plain ASCII letters. + // They are proprietary and cannot be bundled, so on a device without + // them the bullet shows up as a stray letter (e.g. Wingdings "n" => a + // black square renders as the literal "n"). + symbolFontRe = /wingding|webding|stardings|starsymbol|opensymbol|monotype sorts|zapf\s*dingbats|dingbats/i, + /**@const + @type{!Object.}*/ + // The handful of dingbat code points commonly used as list bullets, + // mapped to Unicode look-alikes that render in any normal font. + symbolBulletMap = { + "n": "■", // black square (Wingdings) + "o": "□", // white square + "p": "❑", // shadowed square + "l": "●", // black circle + "m": "○", // white circle + "u": "◆", // black diamond + "v": "❖", // black diamond minus white X + "§": "▪", // small black square + "ü": "✓", // check mark + "û": "☐", // ballot box + "Ø": "➔" // heavy wide-headed rightwards arrow }; /** @@ -231,7 +249,13 @@ // lists that actually have list items will all start with the counter value of 2 which is not desirable. // To fix this we apply another CSS rule here that overrides the counter increment rule above and // prevents incrementing the counter on the FIRST list item that has content (AKA a visible list label). - newRule = 'text|list[webodfhelper|counter-id="' + newListSelectorId + '"]'; + // A list that was cut where a page ends is written again on + // the page that follows, and what follows the cut is no + // first item of a list: the counter is held back on the + // first item of a list, and never on the first item of what + // is left of one, see "webodfhelper:continued" in + // "TextLayout.js". + newRule = 'text|list[webodfhelper|counter-id="' + newListSelectorId + '"]:not([webodfhelper|continued])'; newRule += ' > text|list-item:first-child > :not(text|list):first-child:before'; newRule += '{'; // Due to https://bugs.webkit.org/show_bug.cgi?id=84985 a value of "none" is ignored by some version of WebKit @@ -259,8 +283,12 @@ } // Apply the counter increment to EVERY list item in this list that has content (AKA a visible list label) + // An item that was cut where a page ends is written again on the + // page that follows, and what is left of it is no new item of + // the list: it carries the number of the item it is the end of, + // and never one of its own. newRule = 'text|list[webodfhelper|counter-id="' + newListSelectorId + '"]'; - newRule += ' > text|list-item > :not(text|list):first-child:before'; + newRule += ' > text|list-item:not([webodfhelper|continued]) > :not(text|list):first-child:before'; newRule += '{'; newRule += contentRule; newRule += 'counter-increment: ' + newListCounterId + ';'; @@ -342,12 +370,57 @@ }; } + /** + * A number written as the format of a level asks: "1" for the numbers, + * "a" and "A" for the letters, "i" and "I" for the numbers of Rome. + * @param {!number} value + * @param {!string} format + * @return {!string} + */ + function writtenAs(value, format) { + var letters = "abcdefghijklmnopqrstuvwxyz", + romans = [[1000, "m"], [900, "cm"], [500, "d"], [400, "cd"], + [100, "c"], [90, "xc"], [50, "l"], [40, "xl"], [10, "x"], + [9, "ix"], [5, "v"], [4, "iv"], [1, "i"]], + /**@type{!string}*/ + written = "", + /**@type{!number}*/ + left = value, + /**@type{!number}*/ + i; + if (format === "a" || format === "A") { + // A number past the letters is written with as many letters as + // it takes, "aa" after "z", which is how an office writes it. + while (left > 0) { + left -= 1; + written = letters.charAt(left % 26) + written; + left = Math.floor(left / 26); + } + return format === "A" + ? written.toUpperCase() + : written; + } + if (format === "i" || format === "I") { + for (i = 0; i < romans.length; i += 1) { + while (left >= romans[i][0]) { + written += romans[i][1]; + left -= romans[i][0]; + } + } + return format === "I" + ? written.toUpperCase() + : written; + } + return String(value); + } /** * @constructor */ odf.ListStyleToCss = function ListStyleToCss() { - var cssUnits = new core.CSSUnits(), + var cssUnits = new webodfcore.CSSUnits(), + /**@type{(!Object.|undefined)}*/ + lastListStyles, odfUtils = odf.OdfUtils; /** @@ -389,48 +462,17 @@ } /** - * Gets the CSS content for a numbered list - * @param {!Element} node + * The content of the label of a numbered list. + * + * The label itself is worked out once and written on the element, + * see "numberLists": the counters of css are of the page and not of + * the document wherever a page holds its own styles, and a page that + * holds its own styles is laid out for much less. * @return {!string} */ - function getNumberRule(node) { - var style = node.getAttributeNS(stylens, "num-format"), - /**@type{!string}*/ - suffix = node.getAttributeNS(stylens, "num-suffix") || "", - /**@type{!string}*/ - prefix = node.getAttributeNS(stylens, "num-prefix") || "", - /**@type{!string}*/ - content = "", - textLevel = node.getAttributeNS(textns, "level"), - displayLevels = node.getAttributeNS(textns, "display-levels"); - if (prefix) { - // Content needs to be on a new line if it contains slashes due to a bug in older versions of webkit - // E.g., the one used in the qt runtime tests - https://bugs.webkit.org/show_bug.cgi?id=35010 - content += '"' + escapeCSSString(prefix) + '"\n'; - } - if (stylemap.hasOwnProperty(style)) { - textLevel = textLevel ? parseInt(textLevel, 10) : 1; - displayLevels = displayLevels ? parseInt(displayLevels, 10) : 1; - - // as we might want to display a subset of the counters - // we assume a different counter for each list level - // and concatenate them for multi level lists - // https://wiki.openoffice.org/wiki/Number_labels - while (displayLevels > 0) { - content += " counter(" + (textLevel - displayLevels + 1) + listCounterIdSuffix + "," + stylemap[style] + ")"; - if (displayLevels > 1) { - content += '"."'; - } - displayLevels -= 1; - } - } else if (style) { - content += ' "' + style + '"'; - } else { - content += ' ""'; - } - return 'content:' + content + ' "' + escapeCSSString(suffix) + '"'; + function getNumberRule() { + return "content: attr(odf-list-label);"; } - /** * Gets the CSS content for a image bullet list * @return {!string} @@ -445,7 +487,33 @@ * @return {!string} */ function getBulletRule(node) { - var bulletChar = node.getAttributeNS(textns, "bullet-char"); + var bulletChar = node.getAttributeNS(textns, "bullet-char"), + /**@type{?Element}*/ + textProperties = node.getElementsByTagNameNS(stylens, "text-properties")[0], + /**@type{?string}*/ + fontName = textProperties + ? (textProperties.getAttributeNS(fons, "font-family") + || textProperties.getAttributeNS(stylens, "font-name")) + : null, + rule; + if (fontName && symbolFontRe.test(fontName)) { + // Remap a known dingbat glyph, fall back to a plain disc for the + // rest, and render it in a normal font instead of the missing + // symbol font. + if (symbolBulletMap.hasOwnProperty(bulletChar)) { + bulletChar = symbolBulletMap[bulletChar]; + } else if (bulletChar && bulletChar.charCodeAt(0) < 0x2000) { + // A glyph code (ASCII/Latin-1) with no mapping: use a disc. + bulletChar = "•"; + } + rule = 'content: "' + escapeCSSString(bulletChar) + '"'; + rule += '; font-family: sans-serif'; + // The Unicode look-alike in a text font reads smaller than the + // symbol it replaces (its visible disc is well under 1em), so + // nudge it up to read as a proper bullet rather than a dot. + rule += '; font-size: 1.5em'; + return rule; + } return 'content: "' + escapeCSSString(bulletChar) + '"'; } @@ -462,7 +530,7 @@ followedBy; if (node.localName === "list-level-style-number") { - contentRule = getNumberRule(node); + contentRule = getNumberRule(); } else if (node.localName === "list-level-style-image") { contentRule = getImageRule(); } else if (node.localName === "list-level-style-bullet") { @@ -615,6 +683,20 @@ listItemRule += '}'; appendRule(styleSheet, listItemRule); + if (listLevelPositionSpaceMode !== "label-alignment") { + // In label-width-and-position mode the list level alone fixes the + // indent (leftOffset above, plus the hanging label below). A + // presentation paragraph exported from PowerPoint also carries + // the same indent as fo:margin-left / fo:text-indent on its own + // paragraph style; applying both stacks them and pushes the text + // ~twice as far from the bullet. Neutralise the paragraph's own + // horizontal indent for list paragraphs so only the list level + // counts. + listItemRule = selector + ' > text|list-item > :not(text|list)'; + listItemRule += '{ margin-left: 0; text-indent: 0; }'; + appendRule(styleSheet, listItemRule); + } + // insert the list label before every immediate child of the list-item, except for lists listItemRule = selector + ' > text|list-item > :not(text|list):first-child:before'; listItemRule += '{'; @@ -666,7 +748,16 @@ var lists = odfBody.getElementsByTagNameNS(textns, "list"), listCounter = new UniqueListCounter(styleSheet), list, - previousList, + /** + * The last list of each style, that a list which continues + * the numbering carries on from: the standard says the list + * that goes before it, and what goes before it is the last + * list written in the same style and not whatever list was + * written last, which is another list of another style as + * often as not. + * @type{!Object.} + */ + previousOfStyle = {}, continueNumbering, continueListXmlId, xmlId, @@ -687,6 +778,14 @@ // lists that have no text:style-name attribute defined but do have a parent list that defines a // style name will inherit that style and will be handled correctly as any text:list with a style defined // will have CSS rules applied to its child text:list elements + // A list may name a style that is nowhere in the document, and + // one that is written by another program often does. It is then + // drawn without a style of its own, rather than stopping the + // reading of the whole document. + if (styleName && !listStyles[styleName]) { + runtime.log("DEBUG: no list style named " + styleName + "."); + styleName = null; + } if (styleName) { continueNumbering = list.getAttributeNS(textns, "continue-numbering"); continueListXmlId = list.getAttributeNS(textns, "continue-list"); @@ -701,20 +800,369 @@ // lists with different styles cannot be continued // https://tools.oasis-open.org/issues/browse/OFFICE-3558 - if (continueNumbering && !continueListXmlId && isMatchingListStyle(previousList, styleName)) { - listCounter.createCounterRules(contentRules, list, previousList); + if (continueNumbering && !continueListXmlId + && previousOfStyle.hasOwnProperty(styleName)) { + listCounter.createCounterRules(contentRules, list, + previousOfStyle[styleName]); } else if (continueListXmlId && isMatchingListStyle(listsWithXmlId[continueListXmlId], styleName)) { listCounter.createCounterRules(contentRules, list, listsWithXmlId[continueListXmlId]); } else { listCounter.createCounterRules(contentRules, list); } - previousList = list; + previousOfStyle[styleName] = list; } } listCounter.initialiseCreatedCounters(); } + /** + * The level of a list style that answers for a level of a list. + * @param {!Element} style the "text:list-style" + * @param {!number} level from one + * @return {?Element} + */ + function levelOfStyle(style, level) { + var node = style.firstElementChild; + while (node) { + if (node.namespaceURI === textns + && String(node.getAttributeNS(textns, "level")) + === String(level)) { + return node; + } + node = node.nextElementSibling; + } + return null; + } + /** + * Write on one item of a list the label it carries. + * @param {!Element} item + * @param {!Element} style the "text:list-style" + * @param {!number} level from one + * @param {!Array.} held the number each level stands at + * @param {!Element=} where what carries the label, the first child + * of the item by default + * @return {undefined} + */ + function labelOne(item, style, level, held, where) { + var rule = levelOfStyle(style, level), + /**@type{!string}*/ + written = "", + /**@type{!number}*/ + shows, + /**@type{!number}*/ + start, + /**@type{?Element}*/ + own, + /**@type{!string}*/ + format, + /**@type{!number}*/ + l; + if (!rule || (rule.localName !== "list-level-style-number" + && rule.localName !== "outline-level-style")) { + return; + } + start = parseInt(item.getAttributeNS(textns, "start-value") + || rule.getAttributeNS(textns, "start-value") || "", 10); + if (isNaN(start)) { + held[level - 1] = (held[level - 1] || 0) + 1; + } else { + held[level - 1] = start; + } + held.length = level; + shows = parseInt(rule.getAttributeNS(textns, "display-levels") + || "1", 10); + if (isNaN(shows) || shows < 1) { + shows = 1; + } + written += rule.getAttributeNS(stylens, "num-prefix") || ""; + for (l = level - shows + 1; l <= level; l += 1) { + own = levelOfStyle(style, l); + format = own + ? own.getAttributeNS(stylens, "num-format") || "1" + : "1"; + written += writtenAs(held[l - 1] || 1, format); + if (l < level) { + written += "."; + } + } + written += rule.getAttributeNS(stylens, "num-suffix") || ""; + (where || /**@type{!Element}*/(item.firstElementChild)).setAttribute( + "odf-list-label", + written + ); + } + /** + * Write on every item of every list the label it carries. + * + * The numbers of the lists were counted by the counters of css, + * which are of the page and not of the document wherever a page + * holds its own styles: they are counted here instead, once, in the + * order the document is written, and written on the elements as + * "odf-list-label", which the rules of the labels read. + * + * What the standard says of the numbering is answered for: the + * format of each level and the levels a label shows, the prefix and + * the suffix of a level, the number a level starts at, and a list + * that carries on the numbering of the last list of its style. + * @param {!Element} odfBody + * @param {(!Object.|undefined)} listStyles + * @return {undefined} + */ + function numberLists(odfBody, listStyles) { + var lists = odfBody.getElementsByTagNameNS(textns, "list"), + /**@type{!Object.>}*/ + counts = {}, + /**@type{!number}*/ + i; + /** + * Whether a list stands inside another list. + * @param {!Element} list + * @return {!boolean} + */ + function standsInAList(list) { + var walk = /**@type{?Element}*/(list.parentElement); + while (walk) { + if (walk.namespaceURI === textns + && walk.localName === "list") { + return true; + } + walk = /**@type{?Element}*/(walk.parentElement); + } + return false; + } + /** + * The style a list is written in, its own or the one of the list + * it stands in. + * @param {!Element} list + * @return {!string} + */ + function styleOfList(list) { + var walk = /**@type{?Element}*/(list), + /**@type{!string}*/ + name = ""; + while (walk && !name) { + if (walk.namespaceURI === textns + && walk.localName === "list") { + name = walk.getAttributeNS(textns, "style-name") || ""; + } + walk = /**@type{?Element}*/(walk.parentElement); + } + return listStyles && listStyles.hasOwnProperty(name) + ? name + : ""; + } + /** + * Write the labels of one list and of the lists it holds, in the + * order the document is written: a list that stands under an + * item is numbered where that item stands and not once the whole + * of the list it stands in is numbered, or it would carry the + * number of the last item of it. + * @param {!Element} list + * @param {!Element} style the "text:list-style" + * @param {!number} level from one + * @param {!Array.} held the number each level stands at + * @return {undefined} + */ + function numberOne(list, style, level, held) { + var /**@type{?Element}*/ + item = list.firstElementChild, + /**@type{?Element}*/ + under; + while (item) { + if (item.namespaceURI === textns + && item.localName === "list-item") { + if (item.firstElementChild + && item.firstElementChild.localName + !== "list") { + labelOne(item, style, level, held); + } + under = item.firstElementChild; + while (under) { + if (under.namespaceURI === textns + && under.localName === "list") { + numberOne(under, style, level + 1, held); + } + under = under.nextElementSibling; + } + } + item = item.nextElementSibling; + } + } + if (!listStyles) { + return; + } + /** + * Write the labels of a list that stands in no other list, and + * of everything it holds. + * @param {!Element} list + * @return {undefined} + */ + function numberWhole(list) { + var name = styleOfList(list), + /**@type{!Array.}*/ + held; + if (!name || standsInAList(list) || !listStyles) { + return; + } + held = counts.hasOwnProperty(name) + ? counts[name] + : []; + counts[name] = held; + // A list that does not carry on the numbering of the last + // list of its style begins where its style says. + if (!list.getAttributeNS(textns, "continue-numbering") + && !list.getAttributeNS(textns, "continue-list")) { + held.length = 0; + } + numberOne(list, listStyles[name].element, 1, held); + } + for (i = 0; i < lists.length; i += 1) { + numberWhole(/**@type{!Element}*/(lists.item(i))); + } + } + /** + * Write on every heading the number of its chapter. + * + * A document numbers its chapters with an outline style, that names + * a way of writing the number of each level: it is not a list, and + * the headings that carry those numbers stand in no list. A heading + * that stands in a list is numbered by the list and left alone here. + * @param {!Element} odfBody + * @param {(!Object.|undefined)} listStyles + * @return {undefined} + */ + function numberHeadings(odfBody, listStyles) { + var headings = odfBody.getElementsByTagNameNS(textns, "h"), + /**@type{?Element}*/ + outline = null, + /**@type{!Array.}*/ + held = [], + /**@type{!number}*/ + level, + /**@type{?Element}*/ + parent, + /**@type{!Element}*/ + heading, + /**@type{!number}*/ + i; + if (!listStyles) { + return; + } + Object.keys(listStyles).forEach(function (name) { + if (!outline + && listStyles[name].element.localName + === "outline-style") { + outline = listStyles[name].element; + } + }); + if (!outline) { + return; + } + for (i = 0; i < headings.length; i += 1) { + heading = /**@type{!Element}*/(headings.item(i)); + parent = heading.parentElement; + level = parseInt(heading.getAttributeNS(textns, + "outline-level") || "1", 10); + if (isNaN(level) || level < 1) { + level = 1; + } + // A heading of a list is numbered by its list, and one that + // the document writes as the head of a list carries no + // number at all. + if (parent && parent.namespaceURI === textns + && parent.localName === "list-item") { + parent = null; + } else if (heading.getAttributeNS(textns, "is-list-header") + !== "true") { + labelOne(heading, /**@type{!Element}*/(outline), level, + held, heading); + } + } + } + /** + * Hold the number of a heading away from what it names. + * + * A level of an outline says how wide its label stands and how far + * it stands from the text — "text:min-label-width" and + * "text:min-label-distance" — and a document that says neither + * writes a suffix instead: without any of the three the number of a + * chapter and its name run into one another, "1.2Terminology". + * @param {!CSSStyleSheet} styleSheet + * @param {(!Object.|undefined)} listStyles + * @return {undefined} + */ + function spaceHeadings(styleSheet, listStyles) { + var /**@type{?Element}*/ + outline = null, + /**@type{?Element}*/ + node, + /**@type{!function(!Element):undefined}*/ + spaceOneLevel; + if (!listStyles) { + return; + } + Object.keys(listStyles).forEach(function (name) { + if (!outline + && listStyles[name].element.localName + === "outline-style") { + outline = listStyles[name].element; + } + }); + if (!outline) { + return; + } + spaceOneLevel = function (/**@type{!Element}*/ level) { + var /**@type{?Element}*/ + props = level.firstElementChild, + /**@type{!string}*/ + which = level.getAttributeNS(textns, "level") || "", + /**@type{!string}*/ + width = props + ? props.getAttributeNS(textns, "min-label-width") || "" + : "", + /**@type{!string}*/ + gap = props + ? props.getAttributeNS(textns, "min-label-distance") + || "" + : "", + /**@type{!string}*/ + rule = ""; + if (!which) { + return; + } + if (width) { + rule += "min-width:" + convertToPxValue(width) + "px;"; + } + if (gap) { + rule += "padding-right:" + convertToPxValue(gap) + "px;"; + } + if (rule) { + appendRule(styleSheet, + 'text|h[odf-list-label][text|outline-level="' + which + + '"]:before{' + rule + '}'); + } + }; + node = /**@type{!Element}*/(outline).firstElementChild; + while (node) { + spaceOneLevel(node); + node = node.nextElementSibling; + } + } + /** + * Write the labels of the lists and of the headings again. + * + * The numbers are worked out once, as the document is drawn: an + * editor that adds an item to a list, or a heading to the text, asks + * for them to be worked out again, and only for that, as the rules + * of the styles have not changed. + * @param {!Element} odfBody + * @return {undefined} + */ + this.renumber = function (odfBody) { + numberLists(odfBody, lastListStyles); + numberHeadings(odfBody, lastListStyles); + }; /** * Creates CSS styles from the given ODF list styles and applies them to the stylesheet * @param {!CSSStyleSheet} styleSheet @@ -738,6 +1186,13 @@ } applyContentBasedStyles(styleSheet, odfBody, styleFamilyTree); + numberLists(odfBody, styleFamilyTree); + numberHeadings(odfBody, styleFamilyTree); + lastListStyles = styleFamilyTree; + appendRule(styleSheet, 'text|h[odf-list-label]:before' + + '{content: attr(odf-list-label); white-space: pre;' + + 'display: inline-block;}'); + spaceHeadings(styleSheet, styleFamilyTree); }; }; }()); diff --git a/webodf/lib/odf/Namespaces.js b/webodf/lib/odf/Namespaces.js index a3e342af1..230899625 100644 --- a/webodf/lib/odf/Namespaces.js +++ b/webodf/lib/odf/Namespaces.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global odf*/ diff --git a/webodf/lib/odf/ObjectNameGenerator.js b/webodf/lib/odf/ObjectNameGenerator.js index d52938d04..6a4aa5eb1 100644 --- a/webodf/lib/odf/ObjectNameGenerator.js +++ b/webodf/lib/odf/ObjectNameGenerator.js @@ -18,11 +18,11 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ -/*global odf, runtime, core, Node*/ +/*global odf, runtime, webodfcore, Node*/ /** * Helper object for generating unique object names. Each name is only reported once per instance, @@ -50,7 +50,7 @@ odf.ObjectNameGenerator = function ObjectNameGenerator(odfContainer, memberId) { var stylens = odf.Namespaces.stylens, drawns = odf.Namespaces.drawns, xlinkns = odf.Namespaces.xlinkns, - utils = new core.Utils(), + utils = new webodfcore.Utils(), memberIdHash = utils.hashString(memberId), styleNameGenerator = null, frameNameGenerator = null, diff --git a/webodf/lib/odf/OdfCanvas.js b/webodf/lib/odf/OdfCanvas.js index 26ae070ad..dc543b47d 100644 --- a/webodf/lib/odf/OdfCanvas.js +++ b/webodf/lib/odf/OdfCanvas.js @@ -1,5 +1,6 @@ /** * Copyright (C) 2012-2013 KO GmbH + * Copyright (C) 2026 Daniel Berthereau * * @licstart * This file is part of WebODF. @@ -18,12 +19,13 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ -/*global runtime, odf, xmldom, webodf_css, core, gui */ -/*jslint sub: true*/ +/*global runtime, odf, xmldom, webodf_css, webodfcore, gui, atob, Uint8Array */ +/*jslint sub: true, bitwise: true, continue: true, regexp: true, + unparam: true*/ (function () { "use strict"; @@ -36,14 +38,29 @@ function LoadingQueue() { var /**@type{!Array.}*/ queue = [], - taskRunning = false; + taskRunning = false, + /**@type{?Function}*/ + drained = null, + /**@type{?Function}*/ + waiting = null, + /**@type{!boolean}*/ + destroyed = false, + /**@type{!number}*/ + timeoutId = -1; /** * @param {!Function} task * @return {undefined} */ function run(task) { taskRunning = true; - runtime.setTimeout(function () { + timeoutId = runtime.setTimeout(function () { + // The queue outlives the canvas by the time of one timeout: a + // task that runs after the canvas was destroyed draws into a + // document that was taken apart, and holds it from being + // collected. + if (destroyed) { + return; + } try { task(); } catch (/**@type{Error}*/e) { @@ -52,6 +69,10 @@ taskRunning = false; if (queue.length > 0) { run(queue.pop()); + } else if (drained) { + waiting = drained; + drained = null; + waiting(); } }, 10); } @@ -61,6 +82,32 @@ this.clearQueue = function () { queue.length = 0; }; + /** + * Drop what is waiting and what was about to run. + * @return {undefined} + */ + this.destroy = function () { + destroyed = true; + queue.length = 0; + if (timeoutId !== -1) { + runtime.clearTimeout(timeoutId); + timeoutId = -1; + } + }; + /** + * Do something once nothing is left to load: the images of a + * document land after it is drawn, and what is drawn from the size + * of what it holds is drawn again then. + * @param {!Function} task + * @return {undefined} + */ + this.whenDrained = function (task) { + if (!taskRunning && queue.length === 0) { + task(); + } else { + drained = task; + } + }; /** * @param {!Function} loadingTask * @return {undefined} @@ -74,7 +121,7 @@ } /** * @constructor - * @implements {core.Destroyable} + * @implements {webodfcore.Destroyable} * @param {!HTMLStyleElement} css */ function PageSwitcher(css) { @@ -92,11 +139,15 @@ // corresponding to a master page in #shadowContent, and in the same order. // So, when showing a page, also make it's master page (behind it) visible. sheet.insertRule('#shadowContent draw|page {display:none;}', 0); - sheet.insertRule('office|presentation draw|page {display:none;}', 1); + // A drawing is a run of pages, as a presentation is, and is + // read one page at a time in the same way. + sheet.insertRule("office|presentation draw|page," + + " office|drawing draw|page {display:none;}", 1); sheet.insertRule("#shadowContent draw|page:nth-of-type(" + position + ") {display:block;}", 2); - sheet.insertRule("office|presentation draw|page:nth-of-type(" + - position + ") {display:block;}", 3); + sheet.insertRule("office|presentation draw|page:nth-of-type(" + + position + "), office|drawing draw|page:nth-of-type(" + + position + ") {display:block;}", 3); } /** * @return {undefined} @@ -169,12 +220,22 @@ /**@const@type {!string}*/stylens = odf.Namespaces.stylens, /**@const@type {!string}*/svgns = odf.Namespaces.svgns, /**@const@type {!string}*/tablens = odf.Namespaces.tablens, + /**@const@type {!string}*/chartns = odf.Namespaces.chartns, + /**@const@type {!string}*/mathns = odf.Namespaces.mathns, /**@const@type {!string}*/textns = odf.Namespaces.textns, /**@const@type {!string}*/xlinkns = odf.Namespaces.xlinkns, /**@const@type {!string}*/presentationns = odf.Namespaces.presentationns, /**@const@type {!string}*/webodfhelperns = "urn:webodf:names:helper", + /**@type{?MutationObserver}*/ + styleNameWatcher = null, xpath = xmldom.XPath, - domUtils = core.DomUtils; + domUtils = webodfcore.DomUtils, + odfUtils = odf.OdfUtils, + styleInfo = new odf.StyleInfo(), + textLayout = new odf.TextLayout(), + /**@const@type{!Array.}*/ + chartPalette = ["#4f81bd", "#c0504d", "#9bbb59", "#8064a2", "#4bacc6", + "#f79646", "#5da5da", "#faa43a", "#60bd68", "#f17cb0"]; /** * @param {!HTMLStyleElement} style @@ -191,10 +252,13 @@ /** * A new styles.xml has been loaded. Update the live document with it. + * + * What wrote the labels of the lists and of the headings is answered, so + * that an editor may ask for them to be written again. * @param {!odf.OdfContainer} odfcontainer * @param {!odf.Formatting} formatting * @param {!HTMLStyleElement} stylesxmlcss - * @return {undefined} + * @return {!odf.ListStyleToCss} **/ function handleStyles(odfcontainer, formatting, stylesxmlcss) { // update the css translation of the styles @@ -217,7 +281,7 @@ styleSheet, styleTree, odfcontainer.rootElement.body); - + return list2css; } /** @@ -295,37 +359,136 @@ } } } + /** + * Calculate the horizontal position on a page at which a frame should + * be positioned. + * @param {!string} x svg:x + * @param {?string} horizontalPos style:horizontal-pos + * @param {?string} horizontalRel style:horizontal-rel + * @param {!odf.Formatting.PageGeometry} margins + * @return {!number} + */ + function getHorizontalOffset(x, horizontalPos, horizontalRel, margins) { + var nx = 0; + if (horizontalPos === null || horizontalPos === "from-left" + || horizontalPos === "from-inside") { + nx = odfUtils.convertToPx(x || 0); + } + // What the standard calls the page is the sheet, margins included, + // where "page-content" is the area a text is written in. A frame is + // laid out inside that area, so a distance from the edge of the sheet + // is written as the distance from the area, less the margin, and it is + // read from the page layout rather than from the rectangles of the + // moment: nothing is drawn yet when this is written. + if (horizontalRel === "page") { + nx -= margins.left; + } + return nx; + } + /** + * Calculate the vertical position on a page at which a frame should + * be positioned. + * @param {!string} y svg:y + * @param {?string} verticalPos style:vertical-pos + * @param {?string} verticalRel style:vertical-rel + * @param {!odf.Formatting.PageGeometry} margins + * @return {!number} + */ + function getVerticalOffset(y, verticalPos, verticalRel, margins) { + var ny = 0; + if (verticalPos === null || verticalPos === "from-top") { + ny = odfUtils.convertToPx(y || 0); + } + if (verticalRel === "page") { + ny -= margins.top; + } + return ny; + } /** + * @param {!odf.ODFDocumentElement} odfnode * @param {string} styleid * @param {!Element} frame * @param {!CSSStyleSheet} stylesheet + * @param {!odf.Formatting.PageGeometry} margins * @return {undefined} **/ - function setDrawElementPosition(styleid, frame, stylesheet) { + function setDrawElementPosition(odfnode, styleid, frame, stylesheet, margins) { frame.setAttributeNS(webodfhelperns, 'styleid', styleid); var rule, - anchor = frame.getAttributeNS(textns, 'anchor-type'), + anchor = frame.getAttributeNS(textns, 'anchor-type') || "paragraph", + styleName = frame.getAttributeNS(drawns, "style-name"), x = frame.getAttributeNS(svgns, 'x'), y = frame.getAttributeNS(svgns, 'y'), width = frame.getAttributeNS(svgns, 'width'), height = frame.getAttributeNS(svgns, 'height'), minheight = frame.getAttributeNS(fons, 'min-height'), - minwidth = frame.getAttributeNS(fons, 'min-width'); + minwidth = frame.getAttributeNS(fons, 'min-width'), + transform = frame.getAttributeNS(drawns, 'transform'), + tm, + angle, + tx, + ty, + properties; + + // A shape positioned with draw:transform carries no svg:x/y, so it would + // otherwise get display:block and stack in the document flow at the left. + // LibreOffice writes "rotate (a) translate (x y)": the shape's local + // origin (top-left, pre-rotation) lands at (x, y) and is rotated about + // that origin. ODF rotation is counter-clockwise, so negate it for CSS. + if (!x && !y && transform) { + tm = /translate\s*\(\s*([0-9.eE+\-]+[a-z%]*)\s+([0-9.eE+\-]+[a-z%]*)\s*\)/.exec(transform); + angle = /rotate\s*\(\s*([0-9.eE+\-]+)\s*\)/.exec(transform); + if (tm) { + tx = tm[1]; + ty = tm[2]; + rule = 'position: absolute;' + + 'left: ' + tx + ';' + + 'top: ' + ty + ';' + + 'transform-origin: 0 0;'; + if (angle) { + rule += 'transform: rotate(' + + (-parseFloat(angle[1]) * 180 / Math.PI).toFixed(4) + 'deg);'; + } + if (width) { rule += 'width: ' + width + ';'; } + if (height) { rule += 'height: ' + height + ';'; } + rule = 'draw|' + frame.localName + '[webodfhelper|styleid="' + styleid + '"] {' + + rule + '}'; + stylesheet.insertRule(rule, stylesheet.cssRules.length); + return; + } + } + properties = styleInfo.getStyleProperties(odfnode.styles, + odfnode.automaticStyles, styleName, "graphic", + { + style: { + "vertical-pos": null, + "vertical-rel": null, + "horizontal-pos": null, + "horizontal-rel": null + } + })["style"]; if (anchor === "as-char") { rule = 'display: inline-block;'; - } else if (anchor || x || y) { + } else if (x || y || frame.hasAttributeNS(textns, 'anchor-type') + || properties["horizontal-rel"] || properties["vertical-rel"]) { + // Where the frame says where it goes, it goes there: the offset is + // read from the style, that tells what the position is relative to, + // the page or the paragraph, see "getHorizontalOffset". A frame + // that names no place of its own but says what it is placed + // against, "style:horizontal-rel", is placed all the same: it is + // how a frame is written flush to a corner of the sheet. + x = getHorizontalOffset(x, properties["horizontal-pos"], + properties["horizontal-rel"], margins); + y = getVerticalOffset(y, properties["vertical-pos"], + properties["vertical-rel"], margins); rule = 'position: absolute;'; + rule += 'left: ' + x + 'px;'; + rule += 'top: ' + y + 'px;'; } else if (width || height || minheight || minwidth) { rule = 'display: block;'; } - if (x) { - rule += 'left: ' + x + ';'; - } - if (y) { - rule += 'top: ' + y + ';'; - } if (width) { rule += 'width: ' + width + ';'; } @@ -361,6 +524,253 @@ } return ""; } + + /** + * @param {!Uint8Array} data + * @param {!number} offset + * @return {!number} + */ + function readInt16LE(data, offset) { + var value = data[offset] | (data[offset + 1] << 8); + return value & 0x8000 ? value - 0x10000 : value; + } + + /** + * @param {!Uint8Array} data + * @param {!number} offset + * @return {!number} + */ + function readUInt16LE(data, offset) { + return data[offset] | (data[offset + 1] << 8); + } + + /** + * @param {!Uint8Array} data + * @param {!number} offset + * @return {!number} + */ + function readUInt32LE(data, offset) { + return (data[offset] | (data[offset + 1] << 8) | (data[offset + 2] << 16) + | (data[offset + 3] << 24)) >>> 0; + } + + /** + * @param {!Uint8Array} data + * @param {!number} offset + * @return {!string} + */ + function readColorRef(data, offset) { + return "rgb(" + data[offset] + "," + data[offset + 1] + "," + data[offset + 2] + ")"; + } + + /** + * @param {!string} text + * @return {!string} + */ + function escapeXml(text) { + return text.replace(/&/g, "&").replace(//g, ">") + .replace(/"/g, """); + } + + /** + * Convert the subset of placeable WMF used by some PowerPoint-exported ODP + * clipart to SVG so Chromium/WebView can display it. + * @param {!Uint8Array} data + * @return {?string} + */ + function wmfToSvgUrl(data) { + var PLACEABLE_KEY = 0x9AC6CDD7, + META_EOF = 0x0000, + META_SETWINDOWORG = 0x020B, + META_SETWINDOWEXT = 0x020C, + META_SELECTOBJECT = 0x012D, + META_DELETEOBJECT = 0x01F0, + META_CREATEPENINDIRECT = 0x02FA, + META_CREATEBRUSHINDIRECT = 0x02FC, + META_POLYGON = 0x0324, + META_SETPOLYFILLMODE = 0x0106, + offset = 0, + viewLeft, + viewTop, + viewRight, + viewBottom, + viewWidth, + viewHeight, + windowOrgX, + windowOrgY, + windowExtX, + windowExtY, + svgWidth, + svgHeight, + /**@type{!Array.}*/objects = [], + /**@type{!{type:!string,stroke:(!string|undefined),width:(!number|undefined),fill:(!string|undefined)}}*/pen = {type: "pen", stroke: "none", width: 0, fill: undefined}, + /**@type{!{type:!string,stroke:(!string|undefined),width:(!number|undefined),fill:(!string|undefined)}}*/brush = {type: "brush", fill: "none", stroke: undefined, width: undefined}, + fillRule = "evenodd", + body = "", + /**@type{!number}*/recordSize = 0, + /**@type{!number}*/fn = 0, + /**@type{!number}*/params = 0, + /**@type{!number}*/i = 0, + /**@type{!number}*/count = 0, + /**@type{!Array.}*/points = [], + /**@type{!number}*/style = 0, + /**@type{!string}*/color = "", + /**@type{!number}*/width = 0, + /**@type{!number}*/objectIndex = 0, + /**@type{?{type:!string,stroke:(!string|undefined),width:(!number|undefined),fill:(!string|undefined)}}*/selected = null; + + /** + * @param {!{type:!string,stroke:(!string|undefined),width:(!number|undefined),fill:(!string|undefined)}} o + * @return {undefined} + */ + function addObject(o) { + var /**@type{!number}*/slot; + for (slot = 0; slot < objects.length; slot += 1) { + if (!objects[slot]) { + objects[slot] = o; + return; + } + } + objects.push(o); + } + + /** + * @param {!number} value + * @return {!string} + */ + function fmt(value) { + return String(Math.round(value * 100) / 100); + } + + /** + * @param {!number} x + * @return {!number} + */ + function mapX(x) { + return (x - windowOrgX) * svgWidth / windowExtX; + } + + /** + * @param {!number} y + * @return {!number} + */ + function mapY(y) { + return (y - windowOrgY) * svgHeight / windowExtY; + } + + if (data.length < 40 || readUInt32LE(data, 0) !== PLACEABLE_KEY) { + return null; + } + viewLeft = readInt16LE(data, 6); + viewTop = readInt16LE(data, 8); + viewRight = readInt16LE(data, 10); + viewBottom = readInt16LE(data, 12); + viewWidth = Math.max(1, viewRight - viewLeft); + viewHeight = Math.max(1, viewBottom - viewTop); + windowOrgX = viewLeft; + windowOrgY = viewTop; + windowExtX = viewWidth; + windowExtY = viewHeight; + svgWidth = viewWidth; + svgHeight = viewHeight; + offset = 22 + 18; + while (offset + 6 <= data.length) { + recordSize = readUInt32LE(data, offset); + fn = readUInt16LE(data, offset + 4); + params = offset + 6; + if (recordSize < 3 || offset + recordSize * 2 > data.length) { + return null; + } + if (fn === META_EOF) { + break; + } + if (fn === META_CREATEBRUSHINDIRECT) { + style = readUInt16LE(data, params); + color = readColorRef(data, params + 2); + addObject({type: "brush", fill: style === 0 ? color : "none"}); + } else if (fn === META_CREATEPENINDIRECT) { + style = readUInt16LE(data, params); + width = Math.abs(readInt16LE(data, params + 2)); + color = readColorRef(data, params + 6); + addObject({type: "pen", stroke: style === 5 ? "none" : color, width: Math.max(1, width)}); + } else if (fn === META_SELECTOBJECT) { + objectIndex = readUInt16LE(data, params); + selected = objects[objectIndex]; + if (selected) { + if (selected.type === "pen") { + pen = selected; + } else if (selected.type === "brush") { + brush = selected; + } + } + } else if (fn === META_DELETEOBJECT) { + objects[readUInt16LE(data, params)] = null; + } else if (fn === META_SETPOLYFILLMODE) { + fillRule = readUInt16LE(data, params) === 2 ? "nonzero" : "evenodd"; + } else if (fn === META_SETWINDOWORG) { + windowOrgY = readInt16LE(data, params); + windowOrgX = readInt16LE(data, params + 2); + } else if (fn === META_SETWINDOWEXT) { + windowExtY = readInt16LE(data, params); + windowExtX = readInt16LE(data, params + 2); + if (windowExtX === 0 || windowExtY === 0) { + return null; + } + svgWidth = Math.abs(windowExtX); + svgHeight = Math.abs(windowExtY); + } else if (fn === META_POLYGON) { + count = readUInt16LE(data, params); + if (params + 2 + count * 4 > offset + recordSize * 2) { + return null; + } + points = []; + for (i = 0; i < count; i += 1) { + points.push(fmt(mapX(readInt16LE(data, params + 2 + i * 4))) + "," + + fmt(mapY(readInt16LE(data, params + 4 + i * 4)))); + } + body += ''; + } + offset += recordSize * 2; + } + if (!body) { + return null; + } + return "data:image/svg+xml;charset=utf-8," + encodeURIComponent( + '' + body + ""); + } + + /** + * @param {!string} url + * @return {?string} + */ + function wmfDataUrlToSvgUrl(url) { + var base64Index = url.indexOf("base64,"), + binary, + data, + i; + if (base64Index < 0 || typeof atob !== "function") { + return null; + } + binary = atob(url.substring(base64Index + 7)); + data = new Uint8Array(binary.length); + for (i = 0; i < binary.length; i += 1) { + data[i] = binary.charCodeAt(i) & 0xff; + } + return wmfToSvgUrl(data); + } + + /** + * @param {!string} url + * @return {!string} + */ + function cssUrl(url) { + return 'url("' + url.replace(/\\/g, "\\\\").replace(/"/g, '\\"') + .replace(/\r/g, "").replace(/\n/g, "") + '")'; + } + /** * @param {string} id * @param {!odf.OdfContainer} container @@ -379,16 +789,26 @@ function callback(url) { var rule; if (url) { // if part cannot be loaded, url is null - rule = "background-image: url(" + url + ");"; + rule = "background-image: " + cssUrl(url) + ";"; rule = 'draw|image[webodfhelper|styleid="' + id + '"] {' + rule + '}'; - stylesheet.insertRule(rule, stylesheet.cssRules.length); + try { + stylesheet.insertRule(rule, stylesheet.cssRules.length); + } catch (/**@type{*}*/e) { + runtime.log('image css problem: ' + String(e)); + } } } /** * @param {!odf.OdfPart} p */ function onchange(p) { - callback(p.url); + var convertedUrl = null; + try { + convertedUrl = /\.wmf$/i.test(p.name) && p.url ? wmfDataUrlToSvgUrl(p.url) : null; + } catch (/**@type{*}*/e) { + runtime.log('wmf conversion problem: ' + String(e)); + } + callback(convertedUrl || p.url); } // look for a office:binary-data if (url) { @@ -404,6 +824,2093 @@ callback(url); } } + /** + * Find a of the drawing-page family by name, searching both + * the document's automatic styles and the common styles. + * @param {!odf.ODFDocumentElement} rootElement the ODF root element + * @param {?string} name + * @return {?Element} + */ + function findDrawingPageStyle(rootElement, name) { + var /**@type{!Array.}*/ + roots = [rootElement.automaticStyles, rootElement.styles], + i, + /**@type{?Element}*/ + node; + if (!name) { + return null; + } + for (i = 0; i < roots.length; i += 1) { + node = roots[i] && roots[i].firstElementChild; + while (node) { + if (node.namespaceURI === stylens && node.localName === "style" + && node.getAttributeNS(stylens, "family") === "drawing-page" + && node.getAttributeNS(stylens, "name") === name) { + return /**@type{!Element}*/(node); + } + node = node.nextElementSibling; + } + } + return null; + } + /** + * Resolve a reference to the href of the image part. + * @param {!odf.ODFDocumentElement} rootElement the ODF root element + * @param {?string} name value of draw:fill-image-name + * @return {?string} + */ + function findFillImageHref(rootElement, name) { + var images, + i; + if (!name) { + return null; + } + images = domUtils.getElementsByTagNameNS(rootElement.styles, drawns, "fill-image"); + for (i = 0; i < images.length; i += 1) { + if (images[i].getAttributeNS(drawns, "name") === name) { + return images[i].getAttributeNS(xlinkns, "href"); + } + } + return null; + } + /** + * Find a graphic-family by name (automatic or common styles). + * @param {!odf.ODFDocumentElement} rootElement + * @param {?string} name + * @return {?Element} + */ + function findGraphicStyle(rootElement, name) { + var /**@type{!Array.}*/ + roots = [rootElement.automaticStyles, rootElement.styles], + i, + /**@type{?Element}*/ + node; + if (!name) { + return null; + } + for (i = 0; i < roots.length; i += 1) { + node = roots[i] && roots[i].firstElementChild; + while (node) { + if (node.namespaceURI === stylens && node.localName === "style" + && node.getAttributeNS(stylens, "family") === "graphic" + && node.getAttributeNS(stylens, "name") === name) { + return /**@type{!Element}*/(node); + } + node = node.nextElementSibling; + } + } + return null; + } + + /** + * @param {!odf.ODFDocumentElement} rootElement + * @param {?string} styleName + * @param {!string} propertyName + * @return {?string} + */ + function findDrawingPagePresentationProperty(rootElement, styleName, propertyName) { + var style = findDrawingPageStyle(rootElement, styleName), + props, + value; + while (style) { + props = domUtils.getDirectChild(style, stylens, "drawing-page-properties"); + if (props) { + value = props.getAttributeNS(presentationns, propertyName); + if (value) { + return value; + } + } + style = findDrawingPageStyle(rootElement, + style.getAttributeNS(stylens, "parent-style-name")); + } + return null; + } + + /** + * @param {!odf.ODFDocumentElement} rootElement + * @param {!Element} pageElement + * @param {!Element} masterPageElement + * @param {!Element} element + * @return {!boolean} + */ + function shouldCloneMasterPageElement(rootElement, pageElement, masterPageElement, element) { + var presentationClass = element.getAttributeNS(presentationns, "class"), + displayProperty, + pageValue, + masterValue, + pageStyleName, + masterStyleName; + if (element.getAttributeNS(presentationns, "placeholder") === "true") { + return false; + } + if (!presentationClass) { + return true; + } + if (presentationClass === "page-number") { + displayProperty = "display-page-number"; + } else if (presentationClass === "date-time") { + displayProperty = "display-date-time"; + } else if (presentationClass === "header") { + displayProperty = "display-header"; + } else if (presentationClass === "footer") { + displayProperty = "display-footer"; + } else { + return true; + } + pageStyleName = pageElement.getAttributeNS(drawns, "style-name"); + pageValue = findDrawingPagePresentationProperty(rootElement, pageStyleName, displayProperty); + if (pageValue === "false") { + return false; + } + if (pageValue === "true") { + return true; + } + masterStyleName = masterPageElement.getAttributeNS(drawns, "style-name"); + masterValue = findDrawingPagePresentationProperty(rootElement, masterStyleName, displayProperty); + return masterValue !== "false"; + } + /** + * Resolve one attribute from a graphic style, following parent-style-name. + * @param {!odf.ODFDocumentElement} rootElement + * @param {?string} styleName + * @param {string} ns + * @param {string} attr + * @return {?string} + */ + function resolveGraphicProperty(rootElement, styleName, ns, attr) { + var name = styleName, + depth = 0, + style, + gp, + value; + while (name && depth < 16) { + style = findGraphicStyle(rootElement, name); + if (!style) { + return null; + } + gp = domUtils.getElementsByTagNameNS(style, stylens, "graphic-properties")[0]; + if (gp) { + value = gp.getAttributeNS(ns, attr); + if (value) { + return value; + } + } + name = style.getAttributeNS(stylens, "parent-style-name"); + depth += 1; + } + return null; + } + /** + * Find a draw:marker definition by name. + * @param {!odf.ODFDocumentElement} rootElement + * @param {?string} name + * @return {?Element} + */ + function findDrawMarker(rootElement, name) { + var /**@type{!Array.}*/ + roots = [rootElement.automaticStyles, rootElement.styles], + i, + /**@type{?Element}*/ + root, + markers, + /**@type{!Element}*/marker, + j; + if (!name) { + return null; + } + for (i = 0; i < roots.length; i += 1) { + root = roots[i]; + markers = root ? domUtils.getElementsByTagNameNS(root, drawns, "marker") : []; + for (j = 0; j < markers.length; j += 1) { + marker = /**@type{!Element}*/(markers[j]); + if (marker.getAttributeNS(drawns, "name") === name) { + return marker; + } + } + } + return null; + } + /** + * Resolve the effective fo:clip of a graphic style (following + * parent-style-name). fo:clip is otherwise dropped, so cropped images + * (a common case for "fill frame" pictures) render uncropped. + * @param {!odf.ODFDocumentElement} rootElement + * @param {?string} styleName + * @return {?string} + */ + function resolveGraphicClip(rootElement, styleName) { + var name = styleName, + depth = 0, + style, + gp, + clip; + while (name && depth < 16) { + style = findGraphicStyle(rootElement, name); + if (!style) { + return null; + } + gp = domUtils.getElementsByTagNameNS(style, stylens, "graphic-properties")[0]; + if (gp) { + clip = gp.getAttributeNS(fons, "clip"); + if (clip) { + return clip; + } + } + name = style.getAttributeNS(stylens, "parent-style-name"); + depth += 1; + } + return null; + } + /** + * Parse an ODF length ("7.112cm", "-3pt") into value + unit. + * @param {?string} value + * @return {!{v: !number, u: !string}} + */ + function parseLength(value) { + var m = /^(-?[0-9.]+)([a-z%]*)$/.exec((value || "").trim()); + return m ? { v: parseFloat(m[1]), u: m[2] || "" } : { v: 0, u: "" }; + } + /** + * Apply an image crop (fo:clip) to a draw:image. The frame's svg:width/height + * is the visible (cropped) size; fo:clip gives the inset cut from each edge. + * Reproduce it with background-size/position so only the kept region shows. + * @param {!Element} image + * @param {!string} id + * @param {!odf.OdfContainer} container + * @param {!CSSStyleSheet} stylesheet + * @return {undefined} + */ + function applyImageClip(image, id, container, stylesheet) { + var /**@type{!Element}*/frame = /**@type{!Element}*/(image.parentNode), + /**@type{?string}*/clip, + /**@type{?Array.}*/m, + /**@type{!Array.}*/parts, + /**@type{!{v: !number, u: !string}}*/top, + /**@type{!{v: !number, u: !string}}*/right, + /**@type{!{v: !number, u: !string}}*/bottom, + /**@type{!{v: !number, u: !string}}*/left, + /**@type{!{v: !number, u: !string}}*/fw, + /**@type{!{v: !number, u: !string}}*/fh, + /**@type{!string}*/unit, + /**@type{!string}*/rule; + if (!frame || frame.localName !== "frame") { + return; + } + clip = resolveGraphicClip(container.rootElement, + frame.getAttributeNS(drawns, "style-name")); + if (!clip) { + return; + } + m = /rect\(([^)]*)\)/.exec(clip); + if (!m) { + return; + } + parts = m[1].split(","); + if (parts.length < 4) { + return; + } + top = parseLength(parts[0]); + right = parseLength(parts[1]); + bottom = parseLength(parts[2]); + left = parseLength(parts[3]); + if (top.v === 0 && right.v === 0 && bottom.v === 0 && left.v === 0) { + return; // no actual crop + } + fw = parseLength(frame.getAttributeNS(svgns, "width")); + fh = parseLength(frame.getAttributeNS(svgns, "height")); + if (!fw.v || !fh.v) { + return; + } + unit = fw.u || "cm"; + image.setAttributeNS(webodfhelperns, "clipid", id); + rule = 'draw|image[webodfhelper|clipid="' + id + '"] {' + + "background-size: " + (fw.v + left.v + right.v) + unit + " " + + (fh.v + top.v + bottom.v) + unit + ";" + + "background-position: " + (-left.v) + unit + " " + (-top.v) + unit + ";" + + "}"; + stylesheet.insertRule(rule, stylesheet.cssRules.length); + } + /** + * Load the image part and apply it as the background of every draw:page that + * uses the given drawing-page style. Mirrors setImage: the part is loaded + * asynchronously and the CSS rule is added once its data URL is available. + * The rule reuses the same selector that Style2CSS generates for the + * drawing-page style, but lives in the (later) position stylesheet so it wins + * over the "background: none" that Style2CSS emits when the page background + * is visible. + * @param {!odf.OdfContainer} container + * @param {!string} selector + * @param {!string} href + * @param {?string} repeat value of style:repeat + * @param {!CSSStyleSheet} stylesheet + * @return {undefined} + */ + function setPageBackgroundForSelector(container, selector, href, repeat, stylesheet) { + var part; + /** + * @param {?string} url + */ + function callback(url) { + var rule; + if (!url) { // if part cannot be loaded, url is null + return; + } + rule = selector + " {" + + "background-image: url(" + url + ");" + + "background-repeat: " + (repeat === "repeat" ? "repeat" : "no-repeat") + ";"; + if (repeat === "stretch") { + // ODF "stretch" scales the bitmap to fill the page. + rule += "background-size: 100% 100%;"; + } + rule += "}"; + stylesheet.insertRule(rule, stylesheet.cssRules.length); + } + try { + part = container.getPart(href); + part.onchange = function (/**@type{!odf.OdfPart}*/p) { + callback(p.url); + }; + part.load(); + } catch (/**@type{*}*/e) { + runtime.log('slight problem: ' + String(e)); + } + } + /** + * @param {!odf.OdfContainer} container + * @param {!string} styleName + * @param {!string} href + * @param {?string} repeat value of style:repeat + * @param {!CSSStyleSheet} stylesheet + * @return {undefined} + */ + function setPageBackground(container, styleName, href, repeat, stylesheet) { + setPageBackgroundForSelector(container, + 'draw|page[draw|style-name="' + styleName + '"]', href, repeat, stylesheet); + } + /** + * Render bitmap page fills (draw:fill="bitmap") as draw:page backgrounds. + * WebODF otherwise ignores bitmap fills, but presentations are commonly + * exported with each slide as a single full-bleed background image, which + * would then render blank. + * @param {!odf.OdfContainer} container + * @param {!Element} odfbody + * @param {!CSSStyleSheet} stylesheet + * @return {undefined} + */ + function loadPageBackgrounds(container, odfbody, stylesheet) { + var rootElement = container.rootElement, + pages = domUtils.getElementsByTagNameNS(odfbody, drawns, "page"), + seen = {}, + i; + /** + * @param {?string} styleName + * @return {undefined} + */ + function addBackgroundOfPage(styleName) { + var style, props = null, href, repeat; + if (!styleName || seen.hasOwnProperty(styleName)) { + return; + } + seen[styleName] = true; + // Walk the parent-style chain until a fill is specified. + style = findDrawingPageStyle(rootElement, styleName); + while (style) { + props = domUtils.getDirectChild(style, stylens, "drawing-page-properties"); + if (props && props.getAttributeNS(drawns, "fill")) { + break; + } + props = null; + style = findDrawingPageStyle(rootElement, + style.getAttributeNS(stylens, "parent-style-name")); + } + if (!props || props.getAttributeNS(drawns, "fill") !== "bitmap") { + return; + } + href = findFillImageHref(rootElement, + props.getAttributeNS(drawns, "fill-image-name")); + if (!href) { + return; + } + repeat = props.getAttributeNS(stylens, "repeat"); + setPageBackground(container, styleName, href, repeat, stylesheet); + } + + for (i = 0; i < pages.length; i += 1) { + addBackgroundOfPage(pages[i].getAttributeNS(drawns, "style-name")); + } + } + /** + * Apply each master page's own drawing-page fill to its rendered clone in + * #shadowContent. WebODF's default graphic style paints every draw:page with + * the template's default shape fill, and the master clones carry no + * drawing-page style of their own, so without this the master background + * shows that stray fill (e.g. a cyan deck default) instead of the master's + * real background (e.g. white). Scoped to #shadowContent so it only affects + * the master layer, never the slide page (which must stay transparent so the + * master background objects remain visible). + * @param {!odf.OdfContainer} container + * @param {!CSSStyleSheet} stylesheet + * @return {undefined} + */ + function loadMasterPageBackgrounds(container, stylesheet) { + var rootElement = container.rootElement, + masterStyles = rootElement.masterStyles, + master = masterStyles && masterStyles.firstElementChild, + selectorPrefix = '#shadowContent draw|page[draw|master-page-name="'; + /** + * @param {?string} name + * @param {?string} drawStyleName + * @return {undefined} + */ + function addBackgroundOfMasterPage(name, drawStyleName) { + var style = findDrawingPageStyle(rootElement, drawStyleName), + props = null, + fill, + color, + selector = selectorPrefix + name + '"]'; + while (style) { + props = domUtils.getDirectChild(style, stylens, "drawing-page-properties"); + if (props && props.getAttributeNS(drawns, "fill")) { + break; + } + props = null; + style = findDrawingPageStyle(rootElement, + style.getAttributeNS(stylens, "parent-style-name")); + } + // A page is white when nothing says otherwise, as a sheet of + // paper is: a master that names no fill, or names none, is painted + // white rather than left clear. The white used to come from the box + // that holds the slides, which is now clear between two of them so + // that the ground of the reader is seen there. + fill = props && props.getAttributeNS(drawns, "fill"); + if (fill === "solid") { + color = props.getAttributeNS(drawns, "fill-color"); + if (color) { + stylesheet.insertRule(selector + " {background-color: " + color + + "; background-image: none;}", stylesheet.cssRules.length); + } + } else if (!fill || fill === "none") { + stylesheet.insertRule(selector + " {background-color: white;}", + stylesheet.cssRules.length); + } + // Bitmap master fills are intentionally not painted here: the + // slide's own draw:page already carries its fill (see + // loadPageBackgrounds), and painting the master clone's bitmap + // as well makes the two overlap and ghost. + } + + while (master) { + if (master.namespaceURI === stylens && master.localName === "master-page") { + addBackgroundOfMasterPage(master.getAttributeNS(stylens, "name"), + master.getAttributeNS(drawns, "style-name")); + } + master = master.nextElementSibling; + } + } + + /** + * @param {!odf.OdfContainer} container + * @param {!CSSStyleSheet} stylesheet + * @return {undefined} + */ + function loadHiddenDrawLayers(container, stylesheet) { + var rootElement = container.rootElement, + roots = [rootElement.styles, rootElement.automaticStyles, rootElement.masterStyles], + i, + layers, + j, + layerName; + for (i = 0; i < roots.length; i += 1) { + if (!roots[i]) { + continue; + } + layers = domUtils.getElementsByTagNameNS(roots[i], drawns, "layer"); + for (j = 0; j < layers.length; j += 1) { + if (layers[j].getAttributeNS(drawns, "display") === "none") { + layerName = layers[j].getAttributeNS(drawns, "name"); + if (layerName) { + stylesheet.insertRule('[draw|layer="' + layerName.replace(/\\/g, "\\\\").replace(/"/g, '\\"') + + '"] {display: none !important; visibility: hidden !important;}', + stylesheet.cssRules.length); + } + } + } + } + } + + /** + * Build an evaluation context for the formulas (draw:equation) and modifiers + * (draw:modifiers) of a draw:enhanced-geometry, exposing a resolver for the + * predefined identifiers, the modifiers ($N) and the named equations (?fN). + * @param {!Element} geometry + * @return {!{evaluate:function((!string|undefined)):!number,viewBox:!Array.,stretchX:!number,stretchY:!number}} + */ + function createShapeContext(geometry) { + var viewBoxAttr = (geometry.getAttributeNS(svgns, "viewBox") || "0 0 21600 21600").trim().split(/\s+/), + /**@type{!Array.}*/ + vb = [parseFloat(viewBoxAttr[0]) || 0, parseFloat(viewBoxAttr[1]) || 0, + parseFloat(viewBoxAttr[2]) || 21600, parseFloat(viewBoxAttr[3]) || 21600], + modifiersAttr = (geometry.getAttributeNS(drawns, "modifiers") || "").trim(), + /**@type{!Array.}*/ + modifiers = modifiersAttr ? modifiersAttr.split(/\s+/).map(parseFloat) : [], + /**@type{!Object.}*/ + equations = {}, + /**@type{!Object.}*/ + memo = {}, + /**@type{!Object.}*/ + inProgress = {}, + /**@type{!Object.}*/ + identifiers = { + width: vb[2], height: vb[3], left: vb[0], top: vb[1], + right: vb[0] + vb[2], bottom: vb[1] + vb[3], + logwidth: vb[2], logheight: vb[3], + xstretch: 0, ystretch: 0, hasstroke: 1, hasfill: 1, pi: Math.PI + }, + node = geometry.firstElementChild, + /**@type{!{evaluate:function((!string|undefined)):!number,viewBox:!Array.,stretchX:!number,stretchY:!number}}*/ctx, + // An equation may name another one, that names it back, so these + // two are declared before the functions that call them. + /**@type{!function((!string|undefined)):!number}*/resolve, + /**@type{!function(!string):!number}*/evaluateEquation; + + while (node) { + if (node.namespaceURI === drawns && node.localName === "equation") { + equations[node.getAttributeNS(drawns, "name")] = node.getAttributeNS(drawns, "formula") || "0"; + } + node = node.nextElementSibling; + } + + /** + * @param {!Array.} tokens + * @return {!number} + */ + function parseExpression(tokens) { + // The grammar is recursive: parseAddition is declared here, so that + // the functions it calls may call it back in turn. + var /**@type{!number}*/pos = 0, + /**@type{!function():!number}*/parseAddition; + + /** + * @return {(!string|undefined)} + */ + function peek() { return tokens[pos]; } + /** + * @return {(!string|undefined)} + */ + function next() { pos += 1; return tokens[pos - 1]; } + + /** + * Read the arguments of a function call, until the parenthesis. + * @return {!Array.} + */ + function parseArguments() { + var /**@type{!Array.}*/args = [parseAddition()]; + while (peek() === ",") { + next(); + args.push(parseAddition()); + } + next(); // ")" + return args; + } + + /** + * @param {!string} name + * @param {!Array.} args + * @return {!number} + */ + function callFunction(name, args) { + switch (name) { + case "abs": return Math.abs(args[0]); + case "sqrt": return Math.sqrt(args[0]); + case "sin": return Math.sin(args[0]); + case "cos": return Math.cos(args[0]); + case "tan": return Math.tan(args[0]); + case "atan": return Math.atan(args[0]); + case "atan2": return Math.atan2(args[0], args[1]); + case "min": return Math.min(args[0], args[1]); + case "max": return Math.max(args[0], args[1]); + case "mod": return Math.sqrt(args[0] * args[0] + args[1] * args[1] + args[2] * args[2]); + case "if": return args[0] > 0 ? args[1] : args[2]; + default: return 0; + } + } + + /** + * @return {!number} + */ + function parsePrimary() { + var /**@type{(!string|undefined)}*/token = next(), + /**@type{!number}*/value; + if (token === "(") { + value = parseAddition(); + next(); // ")" + return value; + } + if (token === "-") { + return -parsePrimary(); + } + if (token === "+") { + return parsePrimary(); + } + // function call: identifier followed by "(" + if (token !== undefined && /^[a-z]+$/.test(token) && peek() === "(") { + next(); // "(" + return callFunction(token, parseArguments()); + } + return resolve(token); + } + + /** + * @return {!number} + */ + function parseMultiplication() { + var value = parsePrimary(), op; + while (peek() === "*" || peek() === "/") { + op = next(); + if (op === "*") { + value *= parsePrimary(); + } else { + value /= parsePrimary(); + } + } + return value; + } + + /** + * @return {!number} + */ + function parseAdditionInner() { + var value = parseMultiplication(), op; + while (peek() === "+" || peek() === "-") { + op = next(); + if (op === "+") { + value += parseMultiplication(); + } else { + value -= parseMultiplication(); + } + } + return value; + } + + // hoisted reference so parsePrimary can recurse into the top rule + /** + * @return {!number} + */ + parseAddition = parseAdditionInner; + + return parseAddition(); + } + + /** + * Resolve a single value token to a number. + * @param {(!string|undefined)} token + * @return {!number} + */ + resolve = function (token) { + var /**@type{!number}*/value; + if (token === undefined) { + return 0; + } + if (token.charAt(0) === "?") { + return evaluateEquation(token.substr(1)); + } + if (token.charAt(0) === "$") { + value = modifiers[parseInt(token.substr(1), 10)]; + return isNaN(value) ? 0 : value; + } + if (identifiers.hasOwnProperty(token)) { + return identifiers[token]; + } + value = parseFloat(token); + return isNaN(value) ? 0 : value; + }; + + /** + * Tokenize a formula/path value expression. + * @param {!string} expression + * @return {!Array.} + */ + function tokenize(expression) { + var tokens = expression.match(/\?[a-zA-Z0-9]+|\$[0-9]+|[a-z]+|[0-9]*\.?[0-9]+|[()+\-*\/,]/g); + return tokens || []; + } + + /** + * @param {!string} name + * @return {!number} + */ + evaluateEquation = function (name) { + var result; + if (memo.hasOwnProperty(name)) { + return memo[name]; + } + if (inProgress[name] || !equations.hasOwnProperty(name)) { + return 0; // cycle or missing reference + } + inProgress[name] = true; + result = parseExpression(tokenize(equations[name])); + inProgress[name] = false; + memo[name] = result; + return result; + }; + + ctx = { + evaluate: resolve, + viewBox: vb, + stretchX: NaN, + stretchY: NaN + }; + // The stretch point keeps a shape's corners a fixed size while only the + // straight edges between them grow when the shape's box is not square + // (e.g. a rounded-rectangle "pill"). NaN when not specified. + ctx.stretchX = parseFloat(geometry.getAttributeNS(drawns, "path-stretchpoint-x")); + ctx.stretchY = parseFloat(geometry.getAttributeNS(drawns, "path-stretchpoint-y")); + return ctx; + } + /** + * Map enhanced-path points so that, when the SVG is stretched to a non-square + * box, the corners around the stretch point keep their (circular) size and + * the straight edges absorb the extra length. Only handles paths built from + * straight/quadrant commands (M/L/X/Y); returns null for anything else (arcs, + * curves) so the caller falls back to a plain stretch. + * @param {!Array.} tokens + * @param {!{evaluate:function(!string):!number}} ctx + * @param {!number} stretchPoint coordinate of the stretch point on the axis + * @param {!number} viewBoxSize the axis length of the viewBox + * @param {!number} targetSize the widened axis length (matches the box aspect) + * @param {!boolean} horizontal stretch the x axis (else the y axis) + * @return {?Array.} + */ + function stretchedPoints(tokens, ctx, stretchPoint, viewBoxSize, targetSize, horizontal) { + var commandRe = /^[MLCZNFSTUABWVXYQ]$/, + allowed = {M: 1, L: 1, X: 1, Y: 1, Z: 1, N: 1, F: 1, S: 1}, + /**@type{!Array.}*/raw = [], + /**@type{!number}*/pos = 0, + /**@type{!number}*/n = 0, + /**@type{!number}*/i = 0, + /**@type{?string}*/cur = null, + /**@type{!boolean}*/prevOnPoint = false, + /**@type{!number}*/offset = targetSize - viewBoxSize, + /**@type{(!string|undefined)}*/cmd, + /**@type{!number}*/coord = 0, + /**@type{!number}*/j = 0, + /**@type{!number}*/c = 0; + while (pos < tokens.length) { + cmd = tokens[pos]; + if (!commandRe.test(cmd)) { pos += 1; continue; } + if (!allowed[cmd]) { return null; } + pos += 1; + while (pos < tokens.length && !commandRe.test(tokens[pos])) { + raw.push({ x: ctx.evaluate(tokens[pos]), y: ctx.evaluate(tokens[pos + 1]) }); + pos += 2; + } + } + n = raw.length; + // Decide which side of the stretch point each coordinate belongs to. A + // point exactly on the stretch point inherits the running side, except a + // run of two consecutive on-point coordinates is the zero-length segment + // that becomes the stretched straight edge, so the side flips there. + for (i = 0; i < n; i += 1) { + coord = horizontal ? raw[i].x : raw[i].y; + if (coord < stretchPoint - 0.5) { + cur = "L"; + prevOnPoint = false; + } else if (coord > stretchPoint + 0.5) { + cur = "R"; + prevOnPoint = false; + } else { + if (cur === null) { + // First point: take the side of the next off-point coordinate. + cur = "L"; + for (j = i + 1; j < n; j += 1) { + c = horizontal ? raw[j].x : raw[j].y; + if (c < stretchPoint - 0.5) { cur = "L"; break; } + if (c > stretchPoint + 0.5) { cur = "R"; break; } + } + } else if (prevOnPoint) { + cur = cur === "L" ? "R" : "L"; + } + prevOnPoint = true; + } + if (cur === "R") { + if (horizontal) { raw[i].x += offset; } else { raw[i].y += offset; } + } + } + return raw; + } + /** + * Append SVG arc command(s) tracing an elliptical segment to the path data. + * Splits arcs of 180 degrees or more so a single SVG arc never has an + * ambiguous large-arc/sweep combination, and handles full ellipses. + * @param {!Array.} d path-data fragments (mutated) + * @param {!number} cx + * @param {!number} cy + * @param {!number} rx + * @param {!number} ry + * @param {!number} startAngle radians + * @param {!number} endAngle radians + * @param {!boolean} connectWithMove start a new subpath instead of a line + * @return {undefined} + */ + function appendEllipticalArc(d, cx, cy, rx, ry, startAngle, endAngle, connectWithMove) { + var sweep = endAngle - startAngle, + steps, + i, + a1, + x0 = cx + rx * Math.cos(startAngle), + y0 = cy + ry * Math.sin(startAngle), + x1, + y1, + sweepFlag = sweep >= 0 ? 1 : 0, + absSweep = Math.abs(sweep); + d.push((connectWithMove ? "M" : "L") + x0.toFixed(2) + " " + y0.toFixed(2)); + // Break the arc into pieces no larger than (just under) 180 degrees. + steps = Math.max(1, Math.ceil(absSweep / (Math.PI - 0.001))); + for (i = 1; i <= steps; i += 1) { + a1 = startAngle + sweep * i / steps; + x1 = cx + rx * Math.cos(a1); + y1 = cy + ry * Math.sin(a1); + d.push("A" + rx.toFixed(2) + " " + ry.toFixed(2) + " 0 0 " + sweepFlag + " " + + x1.toFixed(2) + " " + y1.toFixed(2)); + } + } + /** + * Translate a draw:enhanced-path into one or more SVG sub-paths. Each entry + * has its own fill/stroke flags (the path "F"/"S" commands disable fill and + * stroke for the sub-paths that follow within the same geometry). + * @param {!string} path + * @param {!{evaluate:function(!string):!number}} ctx + * @param {?Array.=} mappedPts pre-computed, + * stretch-corrected points consumed in order for M/L/X/Y commands + * @return {!Array.} + */ + function parseEnhancedPath(path, ctx, mappedPts) { + var /**@type{!Array.}*/ + tokens = path.match(/[A-Za-z]|\?[a-zA-Z0-9]+|\$[0-9]+|[a-z]+|-?[0-9]*\.?[0-9]+/g) || [], + commandRe = /^[MLCZNFSTUABWVXYQ]$/, + /**@type{!Array.}*/ + subPaths = [], + /**@type{!Array.}*/ + d = [], + /**@type{!boolean}*/ + fill = true, + /**@type{!boolean}*/ + stroke = true, + /**@type{!number}*/ + curX = 0, + /**@type{!number}*/ + curY = 0, + /**@type{!number}*/ + pos = 0, + /**@type{!number}*/ + ptIdx = 0, + /**@type{(!string|undefined)}*/ + command, + count = 0; + + /** + * @return {undefined} + */ + function flush() { + if (d.length) { + subPaths.push({ d: d.join(" "), fill: fill, stroke: stroke }); + } + d = []; + } + /** + * @return {!number} + */ + function val() { + var t = tokens[pos] || "0"; + pos += 1; + return ctx.evaluate(t); + } + /** + * @return {!boolean} + */ + function hasValue() { + return pos < tokens.length && !commandRe.test(tokens[pos]); + } + // Read one (x,y) point: from the stretch-corrected list when present, + // otherwise straight from the token stream. + /** + * @return {!Array.} + */ + function nextPoint() { + var /**@type{!{x:!number,y:!number}}*/p; + if (mappedPts) { + p = mappedPts[ptIdx]; + ptIdx += 1; + pos += 2; + return [p.x, p.y]; + } + return [val(), val()]; + } + // Elliptical-quadrant helper (X/Y). Draws a 90-degree arc from the + // current point to (tx,ty); axis is whichever of the two the tangent + // starts along. + /** + * @param {!number} tx + * @param {!number} ty + * @param {!boolean} startAlongX + * @return {undefined} + */ + function quadrant(tx, ty, startAlongX) { + // The radii span the rectangle between the current point and the + // target; only the centre differs between X (tangent starts + // horizontal) and Y (tangent starts vertical). + var cx = startAlongX ? curX : tx, + cy = startAlongX ? ty : curY, + rx = Math.abs(tx - curX), + ry = Math.abs(ty - curY), + a0 = Math.atan2((curY - cy) / (ry || 1), (curX - cx) / (rx || 1)), + a1 = Math.atan2((ty - cy) / (ry || 1), (tx - cx) / (rx || 1)), + diff = a1 - a0; + // Choose the short way round (a quadrant is always 90 degrees). + if (diff > Math.PI) { a1 -= 2 * Math.PI; } + if (diff < -Math.PI) { a1 += 2 * Math.PI; } + appendEllipticalArc(d, cx, cy, rx, ry, a0, a1, false); + curX = tx; + curY = ty; + } + // Arc-to helper (A/W). Ellipse bounding box (x1,y1)-(x2,y2); the segment + // runs from the direction of (x3,y3) to that of (x4,y4). + /** + * @param {!number} x1 + * @param {!number} y1 + * @param {!number} x2 + * @param {!number} y2 + * @param {!number} x3 + * @param {!number} y3 + * @param {!number} x4 + * @param {!number} y4 + * @param {!boolean} clockwise + * @param {!boolean} connectWithMove + * @return {undefined} + */ + function arcTo(x1, y1, x2, y2, x3, y3, x4, y4, clockwise, connectWithMove) { + var cx = (x1 + x2) / 2, + cy = (y1 + y2) / 2, + rx = Math.abs(x2 - x1) / 2, + ry = Math.abs(y2 - y1) / 2, + a0 = Math.atan2((y3 - cy) / (ry || 1), (x3 - cx) / (rx || 1)), + a1 = Math.atan2((y4 - cy) / (ry || 1), (x4 - cx) / (rx || 1)); + if (clockwise) { + while (a1 <= a0) { a1 += 2 * Math.PI; } + } else { + while (a1 >= a0) { a1 -= 2 * Math.PI; } + } + appendEllipticalArc(d, cx, cy, rx, ry, a0, a1, connectWithMove); + curX = cx + rx * Math.cos(a1); + curY = cy + ry * Math.sin(a1); + } + + /** + * Read the next point and make it the current one. + * @return {undefined} + */ + function moveToNextPoint() { + var p = nextPoint(); + curX = p[0]; + curY = p[1]; + } + /** + * Read the next point and draw a quadrant towards it. + * @param {!boolean} startAlongX + * @return {undefined} + */ + function quadrantToNextPoint(startAlongX) { + var p = nextPoint(); + quadrant(p[0], p[1], startAlongX); + } + /** + * Read an angle-ellipse and append it, from the current command. + * @return {undefined} + */ + function appendAngleEllipse() { + var ecx = val(), ecy = val(), erx = val(), ery = val(), + a0 = val() / 65536 * Math.PI / 180, + a1 = val() / 65536 * Math.PI / 180; + appendEllipticalArc(d, ecx, ecy, erx, ery, a0, a1, command === "U"); + curX = ecx + erx * Math.cos(a1); + curY = ecy + ery * Math.sin(a1); + } + + while (pos < tokens.length) { + command = tokens[pos]; + if (!commandRe.test(command)) { + break; // malformed + } + pos += 1; + switch (command) { + case "M": + moveToNextPoint(); + d.push("M" + curX + " " + curY); + while (hasValue()) { + moveToNextPoint(); + d.push("L" + curX + " " + curY); + } + break; + case "L": + while (hasValue()) { + moveToNextPoint(); + d.push("L" + curX + " " + curY); + } + break; + case "C": + while (hasValue()) { + d.push("C" + val() + " " + val() + " " + val() + " " + val() + " "); + curX = val(); curY = val(); + d[d.length - 1] += curX + " " + curY; + } + break; + case "Q": + while (hasValue()) { + d.push("Q" + val() + " " + val() + " "); + curX = val(); curY = val(); + d[d.length - 1] += curX + " " + curY; + } + break; + case "X": + while (hasValue()) { + quadrantToNextPoint(true); + } + break; + case "Y": + while (hasValue()) { + quadrantToNextPoint(false); + } + break; + case "A": // arcto, counter-clockwise, connect with line + case "B": // arc, counter-clockwise, connect with move + while (hasValue()) { + arcTo(val(), val(), val(), val(), val(), val(), val(), val(), false, command === "B"); + } + break; + case "W": // clockwise arcto, connect with line + case "V": // clockwise arc, connect with move + while (hasValue()) { + arcTo(val(), val(), val(), val(), val(), val(), val(), val(), true, command === "V"); + } + break; + case "T": // angle-ellipse-to, connect with line + case "U": // angle-ellipse, connect with move + while (hasValue()) { + appendAngleEllipse(); + } + break; + case "Z": + d.push("Z"); + break; + case "N": + flush(); + break; + case "F": + fill = false; + break; + case "S": + stroke = false; + break; + default: + break; + } + count += 1; + if (count > 100000) { + break; // safety + } + } + flush(); + return subPaths; + } + /** + * Render a draw:custom-shape's enhanced geometry as an inline SVG image set + * as the element background, so the actual shape outline (banner, callout, + * rounded rectangle, ...) is drawn instead of WebODF's default filled + * bounding box. The bounding-box fill/border that Style2CSS applies is + * suppressed at the same time. + * @param {!Element} shape + * @param {!Element} geometry + * @param {!string} shapeId + * @param {!CSSStyleSheet} stylesheet + * @return {undefined} + */ + /** + * Translate a computed CSS gradient (as emitted by Style2CSS for a + * draw:fill="gradient" shape) into an SVG gradient plus a fill + * reference, so a custom shape can be filled with it. Returns null when the + * value is not a gradient we can parse. Keeping Style2CSS as the single + * source of gradient semantics, this only re-expresses it for SVG. + * @param {string} css e.g. "linear-gradient(63deg, rgb(..) 0%, rgb(..) 100%)" + * @param {string} id + * @return {?{def: !string, ref: !string}} + */ + function cssGradientToSvg(css, id) { + var isRadial = css.indexOf('radial-gradient') !== -1, + gid = id + "_grad", + stopRe = /(rgba?\([^)]*\)|#[0-9a-fA-F]{3,8})\s*(-?[0-9.]+)%/g, + stops = [], + angleMatch = css.match(/(-?[0-9.]+)deg/), + angle = angleMatch ? parseFloat(angleMatch[1]) : 180, + rad, + dx, + dy, + def, + m, + i; + m = stopRe.exec(css); + while (m) { + stops.push(''); + m = stopRe.exec(css); + } + if (stops.length < 2) { + return null; + } + if (isRadial) { + def = ''; + } else { + // CSS gradient angle: 0deg = "to top", growing clockwise. Convert to + // an axis vector in objectBoundingBox space (y grows downward). + rad = angle * Math.PI / 180; + dx = Math.sin(rad) / 2; + dy = -Math.cos(rad) / 2; + def = ''; + } + for (i = 0; i < stops.length; i += 1) { + def += stops[i]; + } + def += isRadial ? '' : ''; + return { def: '' + def + '', ref: 'url(#' + gid + ')' }; + } + /** + * Built-in geometry for the common OOXML preset shapes whose ODF + * enhanced-path LibreOffice writes degenerately (a single point for an + * ellipse, bare movetos for a cloud, etc.) or whose formula evaluation is + * unreliable. Returns SVG sub-paths in a normalised 0..100 viewBox (stretched + * to the shape box), or null for types handled fine by the enhanced-path. + * Adjustment handles are ignored (OOXML defaults are used) — enough for a + * faithful preview. Polygons (triangle/diamond/pentagon/...) are intentionally + * absent: their enhanced-path renders correctly already. + * @param {?string} type value of draw:type + * @param {?ClientRect} rect bounding box of the shape, used for the ratio + * @return {?{vb: !Array., subPaths: !Array.}} + */ + function buildPresetShape(type, rect) { + var /**@type{!Array.}*/sub, + /**@type{!number}*/aspect, + /**@type{!number}*/headX, + /**@type{!number}*/bodyTop, + /**@type{!number}*/bodyBottom; + switch (type) { + case "ooxml-ellipse": + sub = [{ d: "M 0 50 A 50 50 0 1 1 100 50 A 50 50 0 1 1 0 50 Z", + fill: true, stroke: true }]; + break; + case "ooxml-roundRect": + sub = [{ d: "M 16.7 0 L 83.3 0 A 16.7 16.7 0 0 1 100 16.7 L 100 83.3" + + " A 16.7 16.7 0 0 1 83.3 100 L 16.7 100 A 16.7 16.7 0 0 1 0 83.3" + + " L 0 16.7 A 16.7 16.7 0 0 1 16.7 0 Z", fill: true, stroke: true }]; + break; + case "ooxml-donut": + // outer ring clockwise, inner circle counter-clockwise; even-odd + // fill leaves the centre hole transparent. + sub = [{ d: "M 0 50 A 50 50 0 1 1 100 50 A 50 50 0 1 1 0 50 Z" + + " M 25 50 A 25 25 0 1 0 75 50 A 25 25 0 1 0 25 50 Z", + fill: true, stroke: true, fillRule: "evenodd" }]; + break; + case "ooxml-arc": + // OOXML default arc: a quarter ellipse (top to right), open (no fill). + sub = [{ d: "M 50 0 A 50 50 0 0 1 100 50", fill: false, stroke: true }]; + break; + case "ooxml-leftRightArrow": + aspect = rect && rect.height > 0 ? rect.width / rect.height : 1; + headX = aspect > 0 ? Math.max(12, Math.min(35, 50 / aspect)) : 25; + bodyTop = 25; + bodyBottom = 75; + sub = [{ d: "M 0 50 L " + headX + " 0 L " + headX + " " + bodyTop + + " L " + (100 - headX) + " " + bodyTop + " L " + (100 - headX) + + " 0 L 100 50 L " + (100 - headX) + " 100 L " + (100 - headX) + + " " + bodyBottom + " L " + headX + " " + bodyBottom + + " L " + headX + " 100 Z", fill: true, stroke: true }]; + break; + case "ooxml-cloud": + // Approximate cloud: a ring of circular bumps. Adjustment-free. + sub = [{ d: "M 22 66 A 16 16 0 0 1 10 40 A 15 15 0 0 1 26 20" + + " A 18 18 0 0 1 56 12 A 16 16 0 0 1 82 22 A 15 15 0 0 1 92 46" + + " A 15 15 0 0 1 78 68 A 18 18 0 0 1 48 74 A 16 16 0 0 1 22 66 Z", + fill: true, stroke: true }]; + break; + default: + return null; + } + return { vb: [0, 0, 100, 100], subPaths: sub }; + } + /** + * Render a draw:custom-shape as an svg background. + * + * The shape is built from its enhanced geometry, or from a known preset + * when the geometry is not usable. + * @param {!Element} shape + * @param {!Element} geometry + * @param {!string} shapeId + * @param {!CSSStyleSheet} stylesheet + * @return {undefined} + */ + function renderCustomShape(shape, geometry, shapeId, stylesheet) { + var window = runtime.getWindow(), + computed = window && window.getComputedStyle(shape, null), + /**@type{?ClientRect}*/ + rect = shape.getBoundingClientRect ? shape.getBoundingClientRect() : null, + /**@type{?{vb: !Array., subPaths: !Array.}}*/preset = null, + ctx, + /**@type{?string}*/path = null, + /**@type{!Array.}*/tokens = [], + /**@type{?Array.}*/mappedPts = null, + /**@type{!Array.}*/subPaths = [], + /**@type{!string}*/fillColor = "", + /**@type{!string}*/strokeColor = "", + /**@type{!number}*/strokeWidth = 1, + /**@type{!string}*/paths = "", + /**@type{!string}*/svg = "", + /**@type{!number}*/i = 0, + /**@type{!{d:!string,fill:!boolean,stroke:!boolean,fillRule:(!string|undefined)}}*/sp, + /**@type{!Array.}*/vb = [0, 0, 100, 100], + /**@type{!number}*/vbW = 100, + /**@type{!number}*/vbH = 100, + /**@type{!number}*/realAspect = 1, + /**@type{!number}*/viewBoxAspect = 1, + /**@type{!number}*/target = 0, + /**@type{!boolean}*/mirrorH = false, + /**@type{!boolean}*/mirrorV = false, + /**@type{!string}*/mirrorTransform = "", + /**@type{?string}*/fillImage = null, + /**@type{?Array.}*/hatchColor = null, + /**@type{?{def:!string,ref:!string}}*/gradient = null, + gradientDefs = "", + fillRef, + dropShadow, + shadowColorMatch, + shadowOffsets; + if (!computed) { + return; + } + fillColor = computed.backgroundColor; + if (!fillColor || fillColor === "rgba(0, 0, 0, 0)" || fillColor === "transparent") { + fillColor = "none"; + } + // A draw:fill="gradient" shape carries its gradient as a CSS + // background-image (from Style2CSS). Bake it into the SVG so the shape + // is actually filled with the gradient rather than left unfilled. + fillImage = computed.backgroundImage; + fillRef = fillColor; + if (fillImage && fillImage.indexOf("repeating-linear-gradient") !== -1) { + // Hatch fill. A faithful per-shape SVG hatch pattern would need + // viewBox-unit conversion under non-uniform stretch and hatch fills + // are rare, so approximate the custom shape with the hatch line + // colour. Plain (non-custom) shapes keep the real CSS hatch pattern. + hatchColor = fillImage.match(/rgba?\([^)]*\)|#[0-9a-fA-F]{3,8}/); + if (hatchColor) { + fillRef = hatchColor[0]; + } + } else if (fillImage && fillImage.indexOf("gradient") !== -1) { + gradient = cssGradientToSvg(fillImage, shapeId); + if (gradient) { + gradientDefs = gradient.def; + fillRef = gradient.ref; + } + } + strokeColor = computed.borderTopStyle === "none" ? "none" : computed.borderTopColor; + // Approximate the stroke width in viewBox units (the SVG is stretched to + // the shape box via preserveAspectRatio="none"). + strokeWidth = 0; + if (strokeColor !== "none" && rect && rect.width > 0) { + strokeWidth = (parseFloat(computed.borderTopWidth) || 1) / rect.width; + } + + // Prefer built-in geometry for the OOXML presets LibreOffice exports with + // a degenerate enhanced-path (ellipse, roundRect, donut, cloud, arc, + // leftRightArrow). Otherwise drive the shape from its enhanced-path. + preset = buildPresetShape(geometry.getAttributeNS(drawns, "type"), rect); + if (preset) { + vb = preset.vb; + vbW = vb[2]; + vbH = vb[3]; + subPaths = preset.subPaths; + } else { + ctx = createShapeContext(geometry); + vb = ctx.viewBox; + vbW = vb[2]; + vbH = vb[3]; + path = geometry.getAttributeNS(drawns, "enhanced-path") || ""; + // When the shape's box is not as square as its viewBox, keep the + // corners around the stretch point circular by widening (or + // heightening) the viewBox and remapping the path points instead of + // scaling uniformly. + if (rect && rect.width > 0 && rect.height > 0) { + realAspect = rect.width / rect.height; + viewBoxAspect = vbW / vbH; + tokens = path.match(/[A-Za-z]|\?[a-zA-Z0-9]+|\$[0-9]+|[a-z]+|-?[0-9]*\.?[0-9]+/g) || []; + if (!isNaN(ctx.stretchX) && realAspect > viewBoxAspect * 1.02) { + target = vbH * realAspect; + mappedPts = stretchedPoints(tokens, ctx, ctx.stretchX, vbW, target, true); + if (mappedPts) { vbW = target; } + } else if (!isNaN(ctx.stretchY) && realAspect < viewBoxAspect / 1.02) { + target = vbW / realAspect; + mappedPts = stretchedPoints(tokens, ctx, ctx.stretchY, vbH, target, false); + if (mappedPts) { vbH = target; } + } + } + subPaths = parseEnhancedPath(path, ctx, mappedPts); + if (!subPaths.length) { + return; + } + // Some ooxml presets (e.g. flowChartDecision) declare + // svg:viewBox="0 0 0 0" and use small literal path coordinates (0..2) + // instead of viewBox-scale formulas. createShapeContext then falls + // back to the 21600 default and the path would be drawn + // microscopically. When the path clearly does not span that box, + // derive the viewBox from the path's own bounds. + (function () { + var /**@type{!number}*/j = 0, + /**@type{!number}*/k = 0, + /**@type{!Array.}*/coords = [], + /**@type{!number}*/x = 0, + /**@type{!number}*/y = 0, + minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity; + for (j = 0; j < subPaths.length; j += 1) { + coords = subPaths[j].d.match(/-?[0-9]*\.?[0-9]+/g) || []; + for (k = 0; k + 1 < coords.length; k += 2) { + x = parseFloat(coords[k]); + y = parseFloat(coords[k + 1]); + if (x < minX) { minX = x; } + if (x > maxX) { maxX = x; } + if (y < minY) { minY = y; } + if (y > maxY) { maxY = y; } + } + } + if (maxX > minX && maxY > minY + && (maxX - minX) < vbW / 2 && (maxY - minY) < vbH / 2) { + vb[0] = minX; + vb[1] = minY; + vbW = maxX - minX; + vbH = maxY - minY; + } + }()); + vb = [vb[0], vb[1], vbW, vbH]; + } + for (i = 0; i < subPaths.length; i += 1) { + sp = subPaths[i]; + paths += '' + paths + ""; + } + + svg = '' + gradientDefs + paths + ""; + + shape.setAttributeNS(webodfhelperns, "shapeid", shapeId); + // A draw:shadow maps to box-shadow in Style2CSS, but box-shadow follows + // the element's rectangular border-box, so for a non-rectangular shape + // its straight edges poke out from behind the outline. Re-express it as a + // filter: drop-shadow, which casts from the SVG background's alpha (the + // real shape outline). + dropShadow = ""; + if (computed.boxShadow && computed.boxShadow !== "none") { + shadowColorMatch = computed.boxShadow.match(/rgba?\([^)]*\)|#[0-9a-fA-F]{3,8}/); + shadowOffsets = computed.boxShadow.replace(/rgba?\([^)]*\)/, "").match(/-?[0-9.]+px/g) || []; + if (shadowColorMatch && shadowOffsets.length >= 3) { + dropShadow = "filter: drop-shadow(" + shadowOffsets[0] + " " + + shadowOffsets[1] + " " + shadowOffsets[2] + " " + + shadowColorMatch[0] + ");"; + } + } + stylesheet.insertRule('draw|custom-shape[webodfhelper|shapeid="' + shapeId + '"] {' + + "background-image: url(\"data:image/svg+xml;charset=utf-8," + encodeURIComponent(svg) + "\");" + + "background-repeat: no-repeat;" + + "background-size: 100% 100%;" + + "background-color: transparent;" + + "border: none;" + + "box-shadow: none;" + + dropShadow + + "}", stylesheet.cssRules.length); + } + /** + * Render all draw:custom-shape elements that carry an enhanced geometry. + * @param {!Element} odfbody + * @param {!CSSStyleSheet} stylesheet + * @return {undefined} + */ + function loadCustomShapes(odfbody, stylesheet) { + var shapes = domUtils.getElementsByTagNameNS(odfbody, drawns, "custom-shape"), + i, + geometry; + for (i = 0; i < shapes.length; i += 1) { + geometry = domUtils.getDirectChild(shapes[i], drawns, "enhanced-geometry"); + if (geometry && geometry.getAttributeNS(drawns, "enhanced-path")) { + try { + renderCustomShape(shapes[i], geometry, "shape" + i, stylesheet); + } catch (/**@type{*}*/e) { + runtime.log("could not render custom shape: " + String(e)); + } + } + } + } + + // ---- Embedded chart rendering ------------------------------------------ + // WebODF does not render charts. ODF embeds each chart as a sub-document + // (draw:object -> "/content.xml" with a ) whose data lives + // in a local table. We parse that and draw a static SVG (bar/line/pie/ring), + // which is enough for a preview. + + + /** + * @param {!string} s + * @return {!string} + */ + function svgEsc(s) { + return String(s).replace(/&/g, "&").replace(//g, ">").replace(/"/g, """); + } + + /** + * Map every chart-family style name to its fill/stroke colour. + * @param {!Element} chartRoot + * @return {!Object.} + */ + function collectChartStyleColors(chartRoot) { + var styles = domUtils.getElementsByTagNameNS(chartRoot, stylens, "style"), + map = {}, + i, + s, + name, + gp; + for (i = 0; i < styles.length; i += 1) { + s = styles[i]; + if (s.getAttributeNS(stylens, "family") !== "chart") { + continue; + } + name = s.getAttributeNS(stylens, "name"); + gp = domUtils.getElementsByTagNameNS(s, stylens, "graphic-properties")[0]; + if (name && gp) { + map[name] = { + fill: gp.getAttributeNS(drawns, "fill-color") || null, + stroke: gp.getAttributeNS(svgns, "stroke-color") || null + }; + } + } + return map; + } + + /** + * @param {!Element} rowEl + * @return {!Array.} + */ + function cellsOfRow(rowEl) { + return domUtils.getElementsByTagNameNS(rowEl, tablens, "table-cell"); + } + /** + * Extract the categories and the series of a chart from its data table. + * @param {!Element} table of the chart + * @param {!Element} plotArea of the chart + * @param {!Object.} colors colour of each chart style + * @return {!{categories:!Array.,series:!Array.,pointColors:!Array.}>}} + */ + function parseChartTable(table, plotArea, colors) { + var headerRows = domUtils.getElementsByTagNameNS(table, tablens, "table-header-rows")[0], + /**@type{!Array.}*/ + headerCells = headerRows + ? cellsOfRow(domUtils.getElementsByTagNameNS(headerRows, tablens, "table-row")[0]) + : [], + /**@type{!Array.}*/ + bodyRows = domUtils.getElementsByTagNameNS( + domUtils.getElementsByTagNameNS(table, tablens, "table-rows")[0] || table, + tablens, "table-row"), + seriesEls = domUtils.getElementsByTagNameNS(plotArea, chartns, "series"), + /**@type{!Array.}*/categories = [], + /**@type{!Array.,pointColors:!Array.}>}*/series = [], + /**@type{!number}*/i = 0, + /**@type{!number}*/j = 0, + /**@type{!number}*/k = 0, + /**@type{!Array.}*/cells = [], + /**@type{!Element}*/sEl, + /**@type{!Array.}*/pts = [], + /**@type{!Array.}*/pointColors = [], + /**@type{!Array.}*/vals = [], + /**@type{(!Element|undefined)}*/cell, + /**@type{(!Element|undefined)}*/header, + /**@type{?string}*/styleName = null; + // categories = first column of the body rows + for (i = 0; i < bodyRows.length; i += 1) { + cells = cellsOfRow(bodyRows[i]); + categories.push(cells[0] ? (cells[0].textContent || "").trim() : ""); + } + for (j = 0; j < seriesEls.length; j += 1) { + sEl = seriesEls[j]; + styleName = sEl.getAttributeNS(chartns, "style-name"); + header = headerCells[j + 1]; + // per-point colours (pie/ring give each slice its own style) + pointColors = []; + pts = domUtils.getElementsByTagNameNS(sEl, chartns, "data-point"); + for (i = 0; i < pts.length; i += 1) { + if (pts[i].getAttributeNS(chartns, "style-name") + && colors[pts[i].getAttributeNS(chartns, "style-name")]) { + pointColors.push(colors[pts[i].getAttributeNS(chartns, "style-name")].fill + || chartPalette[pointColors.length % chartPalette.length]); + } + } + vals = []; + for (k = 0; k < bodyRows.length; k += 1) { + cell = cellsOfRow(bodyRows[k])[j + 1]; + vals.push(cell ? parseFloat(cell.getAttributeNS(officens, "value")) || 0 : 0); + } + series.push({ + label: header ? (header.textContent || "").trim() + : ("Series " + (j + 1)), + color: (colors[styleName] && (colors[styleName].fill || colors[styleName].stroke)) + || chartPalette[j % chartPalette.length], + values: vals, + pointColors: pointColors + }); + } + return { categories: categories, series: series }; + } + /** + * Parse a chart sub-document into a simple model. + * @param {!Document} doc + * @return {?{type:!string,title:!string,hasLegend:!boolean,categories:!Array., + * series:!Array.,pointColors:!Array.}>}} + */ + function parseChart(doc) { + var root = doc && doc.documentElement, + chart = root && domUtils.getElementsByTagNameNS(root, chartns, "chart")[0], + plotArea, + titleEl, + cls, + table, + data; + if (!chart || !root) { + return null; + } + plotArea = domUtils.getElementsByTagNameNS(chart, chartns, "plot-area")[0]; + titleEl = domUtils.getElementsByTagNameNS(chart, chartns, "title")[0]; + cls = (chart.getAttributeNS(chartns, "class") || "").replace("chart:", ""); + table = domUtils.getElementsByTagNameNS(chart, tablens, "table")[0]; + if (!plotArea || !table) { + return null; + } + data = parseChartTable(table, plotArea, collectChartStyleColors(root)); + return { type: cls, title: titleEl ? (titleEl.textContent || "").trim() : "", + hasLegend: domUtils.getElementsByTagNameNS(chart, chartns, "legend").length > 0, + categories: data.categories, series: data.series }; + } + + /** + * Prefer the data maximum when it already falls on a readable axis step. + * @param {!number} maxVal + * @return {?{max:!number,step:!number}} + */ + function exactAxis(maxVal) { + var steps = [1, 2, 2.5, 4, 5, 10], + pow = Math.pow(10, Math.floor(Math.log(maxVal) / Math.LN10)), + scales = [pow / 10, pow, pow * 10], + i, + j, + step, + intervals; + if (maxVal <= 0) { + return null; + } + for (i = 0; i < scales.length; i += 1) { + for (j = 0; j < steps.length; j += 1) { + step = steps[j] * scales[i]; + intervals = maxVal / step; + if (intervals >= 3 && intervals <= 6 + && Math.abs(intervals - Math.round(intervals)) < 1e-9) { + return { max: maxVal, step: step }; + } + } + } + return null; + } + + /** + * A "nice" axis maximum and step for a value range 0..maxVal. + * @param {!number} maxVal + * @param {!boolean} preferExactMax + * @return {!{max:!number,step:!number}} + */ + function niceAxis(maxVal, preferExactMax) { + var /**@type{?{max:!number,step:!number}}*/exact, + target = (maxVal <= 0 ? 1 : maxVal) * (preferExactMax ? 1 : 1.05), + rough = target / 5, + pow = Math.pow(10, Math.floor(Math.log(rough) / Math.LN10)), + n = rough / pow, + step = (n <= 1 ? 1 : n <= 2 ? 2 : n <= 5 ? 5 : 10) * pow; + if (preferExactMax) { + exact = exactAxis(maxVal); + if (exact) { + return exact; + } + } + return { max: Math.ceil(target / step) * step, step: step }; + } + + /** + * Render the parsed chart model to an SVG string at the given cm size. + * @param {!{type:!string,title:!string,hasLegend:!boolean,categories:!Array.,series:!Array.,pointColors:!Array.}>}} c parsed chart model + * @param {!number} wcm + * @param {!number} hcm + * @return {!string} + */ + function buildChartSvg(c, wcm, hcm) { + var /**@type{!number}*/W = Math.max(120, Math.round(wcm * 40)), + /**@type{!number}*/H = Math.max(90, Math.round(hcm * 40)), + /**@type{!string}*/body = "", + /**@type{!number}*/legendW = 0; + /** + * @param {!number} x @param {!number} y @param {!string} t + * @param {!string} anchor @param {!number} size @param {string=} weight + * @return {!string} + */ + function text(x, y, t, anchor, size, weight) { + return '' + svgEsc(t) + ''; + } + /** + * Legend items of a pie or ring chart, one per category. + * @param {!{type:!string,title:!string,hasLegend:!boolean,categories:!Array.,series:!Array.,pointColors:!Array.}>}} c + * @param {!Array.} pcols colour of each slice, may be sparse + * @return {!Array.} + */ + function pieLegendItems(c, pcols) { + var /**@type{!Array.}*/items = [], + /**@type{!number}*/m = 0; + for (m = 0; m < c.categories.length; m += 1) { + items.push({ label: c.categories[m], + color: pcols[m] || chartPalette[m % chartPalette.length] }); + } + return items; + } + /** + * Legend at the right; returns its width and appends to body. + * @param {!Array.} items + * @param {!number} top + * @return {undefined} + */ + function drawLegend(items, top) { + var maxLen = 0, k, ly; + for (k = 0; k < items.length; k += 1) { + maxLen = Math.max(maxLen, items[k].label.length); + } + legendW = 22 + maxLen * 6.2; + for (k = 0; k < items.length; k += 1) { + ly = top + k * 18; + body += ''; + body += text(W - legendW + 16, ly + 10, items[k].label, "start", 11); + } + } + + /** + * Draw the legend of the series and return the maximum value. + * + * The legend is drawn only when the chart requires it. The maximum + * value of all the series is used to scale the axis. + * @param {!{type:!string,title:!string,hasLegend:!boolean,categories:!Array.,series:!Array.,pointColors:!Array.}>}} c + * @param {!number} top + * @return {!number} + */ + function drawSeriesLegend(c, top) { + var /**@type{!Array.}*/items = [], + /**@type{!number}*/maxVal = 0, + /**@type{!number}*/s = 0, + /**@type{!number}*/k = 0; + for (s = 0; s < c.series.length; s += 1) { + for (k = 0; k < c.series[s].values.length; k += 1) { + maxVal = Math.max(maxVal, c.series[s].values[k]); + } + items.push({ label: c.series[s].label, color: c.series[s].color }); + } + if (c.hasLegend) { + drawLegend(items, top); + } + return maxVal; + } + if (c.title) { + body += text(W / 2, 18, c.title, "middle", 14, "bold"); + } + + if (c.type === "circle" || c.type === "ring") { + // pie / doughnut: one series, slice per category + (function () { + var /**@type{!Array.}*/vals = c.series[0] ? c.series[0].values : [], + /**@type{!Array.}*/pcols = c.series[0] ? c.series[0].pointColors : [], + total = 0, k = 0, ang0 = -Math.PI / 2, ang1 = 0, frac = 0, + cx = 0, cy = 0, r = 0, rIn = 0, mid = 0, lx = 0, ly = 0, + x0 = 0, y0 = 0, x1 = 0, y1 = 0, large = 0, + /**@type{!string}*/col = ""; + for (k = 0; k < vals.length; k += 1) { total += vals[k]; } + if (c.hasLegend) { + drawLegend(pieLegendItems(c, pcols), 30); + } + r = Math.min((W - legendW - 20) / 2, (H - 36) / 2) - 4; + cx = (W - legendW) / 2; + cy = 28 + (H - 28) / 2; + rIn = c.type === "ring" ? r * 0.55 : 0; + if (total <= 0) { return; } + for (k = 0; k < vals.length; k += 1) { + frac = vals[k] / total; + ang1 = ang0 + frac * 2 * Math.PI; + x0 = cx + r * Math.cos(ang0); y0 = cy + r * Math.sin(ang0); + x1 = cx + r * Math.cos(ang1); y1 = cy + r * Math.sin(ang1); + large = (ang1 - ang0) > Math.PI ? 1 : 0; + col = pcols[k] || chartPalette[k % chartPalette.length]; + if (rIn > 0) { + body += ''; + } else { + body += ''; + } + mid = (ang0 + ang1) / 2; + lx = cx + (rIn > 0 ? (r + rIn) / 2 : r * 0.6) * Math.cos(mid); + ly = cy + (rIn > 0 ? (r + rIn) / 2 : r * 0.6) * Math.sin(mid); + body += text(lx, ly + 4, String(vals[k]), "middle", 11); + ang0 = ang1; + } + }()); + } else { + // bar / line: shared cartesian axes + (function () { + var mt = c.title ? 26 : 10, + // The legend is drawn first because it sets its own width, + // that the plot area has to leave free on the right. + maxVal = drawSeriesLegend(c, mt + 4), + ml = 30, + mb = 20, + mr = legendW + 6, + axis = niceAxis(maxVal, c.type === "line"), + plotW = W - ml - mr, + plotH = H - mt - mb, + x0 = ml, + y0 = mt + plotH, + k = 0, s = 0, + nCat = c.categories.length, gi = 0, catW = 0, t = 0, gx = 0, gy = 0, + nSer = c.series.length, bw = 0, bx = 0, vh = 0, px = 0, py = 0, + /**@type{!string}*/pts = ""; + // y gridlines + labels + for (t = 0; t <= axis.max + 1e-9; t += axis.step) { + gy = y0 - (t / axis.max) * plotH; + body += ''; + body += text(x0 - 4, gy + 4, + String(Math.round(t * 100) / 100), "end", 10); + } + // axis lines + body += ''; + catW = plotW / (nCat || 1); + // category labels + for (gi = 0; gi < nCat; gi += 1) { + gx = x0 + catW * (gi + 0.5); + body += text(gx, y0 + 14, c.categories[gi], "middle", 10); + } + if (c.type === "line") { + for (s = 0; s < nSer; s += 1) { + pts = ""; + for (k = 0; k < c.series[s].values.length; k += 1) { + px = x0 + catW * (k + 0.5); + py = y0 - (c.series[s].values[k] / axis.max) * plotH; + pts += (k ? " " : "") + px + "," + py; + } + body += ''; + for (k = 0; k < c.series[s].values.length; k += 1) { + px = x0 + catW * (k + 0.5); + py = y0 - (c.series[s].values[k] / axis.max) * plotH; + body += ''; + } + } + } else { + // grouped bars + bw = (catW * 0.7) / (nSer || 1); + for (gi = 0; gi < nCat; gi += 1) { + for (s = 0; s < nSer; s += 1) { + vh = (c.series[s].values[gi] / axis.max) * plotH; + bx = x0 + catW * gi + catW * 0.15 + s * bw; + body += ''; + body += text(bx + bw * 0.46, y0 - vh - 3, + String(Math.round(c.series[s].values[gi])), "middle", 10); + } + } + } + }()); + } + return '' + + '' + + body + ''; + } + /** + * Parse the viewBox of a draw:marker, with the default used by ODF. + * @param {!Element} marker + * @return {!Array.} + */ + function parseMarkerViewBox(marker) { + var vbAttr = (marker.getAttributeNS(svgns, "viewBox") || "0 0 20 20").trim().split(/\s+/); + return [parseFloat(vbAttr[0]) || 0, parseFloat(vbAttr[1]) || 0, + parseFloat(vbAttr[2]) || 20, parseFloat(vbAttr[3]) || 20]; + } + /** + * @param {!odf.ODFDocumentElement} rootElement + * @param {?string} name + * @param {!string} id + * @param {!string} orient + * @param {!number} width + * @param {!string} color + * @param {!boolean} centered + * @return {!string} + */ + function buildLineMarker(rootElement, name, id, orient, width, color, centered) { + var marker = findDrawMarker(rootElement, name), + /**@type{?string}*/d = marker && marker.getAttributeNS(svgns, "d"), + /**@type{!Array.}*/vb = marker ? parseMarkerViewBox(marker) : [], + /**@type{!number}*/vbW = vb[2] || 20, + /**@type{!number}*/vbH = vb[3] || 20, + /**@type{!number}*/height = width * (vbH / vbW), + /**@type{!number}*/refX = centered ? vbW / 2 : vbW, + /**@type{!number}*/refY = vbH / 2; + if (!marker || !width || !d) { + return ""; + } + return ''; + } + /** + * Render a draw:line connector. WebODF positions frames/shapes from + * svg:x/y/width/height, but a line carries svg:x1/y1/x2/y2 and no geometry, + * so it is otherwise invisible. Draw it as an absolutely positioned element + * spanning the segment's bounding box with an inline SVG line; the stroke + * uses non-scaling-stroke so it keeps its width under the box stretch. + * @param {!Element} line + * @param {!string} lineId + * @param {!odf.ODFDocumentElement} rootElement + * @param {!CSSStyleSheet} stylesheet + * @param {?{borderTopStyle:!string,borderTopColor:!string,borderTopWidth:!string,color:!string}} computed + * what the line is drawn with, read before any rule was + * written, see "loadLines" + * @return {undefined} + */ + function renderLine(line, lineId, rootElement, stylesheet, computed) { + var x1 = parseLength(line.getAttributeNS(svgns, "x1")), + y1 = parseLength(line.getAttributeNS(svgns, "y1")), + x2 = parseLength(line.getAttributeNS(svgns, "x2")), + y2 = parseLength(line.getAttributeNS(svgns, "y2")), + styleName = line.getAttributeNS(drawns, "style-name"), + unit = x1.u || "cm", + strokeColor, + strokeWidth, + minThick, + markerStartName, + markerEndName, + markerStartWidth, + markerEndWidth, + markerStart, + markerEnd, + markerDefs = "", + markerAttrs = "", + left = Math.min(x1.v, x2.v), + top = Math.min(y1.v, y2.v), + w = Math.abs(x2.v - x1.v), + h = Math.abs(y2.v - y1.v), + svg, + rule; + if (!computed) { + return; + } + strokeColor = computed.borderTopStyle === "none" ? "none" : computed.borderTopColor; + if (!strokeColor || strokeColor === "none") { + // a line with no explicit stroke still needs to be visible + strokeColor = computed.color || "#000000"; + } + strokeWidth = parseFloat(computed.borderTopWidth) || 1; + markerStartName = resolveGraphicProperty(rootElement, styleName, drawns, "marker-start"); + markerEndName = resolveGraphicProperty(rootElement, styleName, drawns, "marker-end"); + markerStartWidth = parseLength(resolveGraphicProperty(rootElement, styleName, + drawns, "marker-start-width")); + markerEndWidth = parseLength(resolveGraphicProperty(rootElement, styleName, + drawns, "marker-end-width")); + markerStart = buildLineMarker(rootElement, markerStartName, lineId + "start", + "auto-start-reverse", markerStartWidth.v, strokeColor, + resolveGraphicProperty(rootElement, styleName, drawns, "marker-start-center") === "true"); + markerEnd = buildLineMarker(rootElement, markerEndName, lineId + "end", + "auto", markerEndWidth.v, strokeColor, + resolveGraphicProperty(rootElement, styleName, drawns, "marker-end-center") === "true"); + if (markerStart) { + markerDefs += markerStart; + markerAttrs += ' marker-start="url(#' + lineId + 'start)"'; + } + if (markerEnd) { + markerDefs += markerEnd; + markerAttrs += ' marker-end="url(#' + lineId + 'end)"'; + } + // Give a horizontal/vertical line (zero extent on one axis) enough box to + // paint the stroke, and recentre it on the true segment. + minThick = (strokeWidth / 37.8) * 4; // px -> cm, a few stroke widths + if (h < minThick) { + top -= (minThick - h) / 2; + h = minThick; + } + if (w < minThick) { + left -= (minThick - w) / 2; + w = minThick; + } + svg = '' + (markerDefs ? "" + markerDefs + "" : "") + + '"; + line.setAttributeNS(webodfhelperns, "lineid", lineId); + rule = 'draw|line[webodfhelper|lineid="' + lineId + '"] {' + + 'position: absolute;' + + 'left: ' + left + unit + ';' + + 'top: ' + top + unit + ';' + + 'width: ' + w + unit + ';' + + 'height: ' + h + unit + ';' + + 'background-image: url("data:image/svg+xml;charset=utf-8,' + encodeURIComponent(svg) + '");' + + 'background-repeat: no-repeat;' + + 'background-size: 100% 100%;' + // the stroke is drawn by the SVG; suppress the CSS border that + // draw:stroke="solid" maps onto the element (it would show as a + // doubled line along the thin box edges). + + 'border: none;' + + '}'; + stylesheet.insertRule(rule, stylesheet.cssRules.length); + } + /** + * Render all draw:line connectors. + * @param {!Element} odfbody + * @param {!odf.ODFDocumentElement} rootElement + * @param {!CSSStyleSheet} stylesheet + * @return {undefined} + */ + function loadLines(odfbody, rootElement, stylesheet) { + var lines = domUtils.getElementsByTagNameNS(odfbody, drawns, "line"), + /**@type{!Array.}*/ + styles = [], + /**@type{?CSSStyleDeclaration}*/ + computed, + window = runtime.getWindow(), + i; + // The style each line is drawn with is read for all of them before + // any rule is written: a rule that is written tells the browser to + // work out the style of every element of the document again, and + // reading the style of the next line makes it do so at once. Seven + // lines in a document of twenty thousand nodes cost seven seconds + // that way, and one reading answers for all of them. + for (i = 0; i < lines.length; i += 1) { + computed = window + ? window.getComputedStyle(lines[i], null) + : null; + styles.push(computed + ? { + borderTopStyle: String(computed.borderTopStyle), + borderTopColor: String(computed.borderTopColor), + borderTopWidth: String(computed.borderTopWidth), + color: String(computed.color) + } + : null); + } + for (i = 0; i < lines.length; i += 1) { + try { + renderLine(lines[i], "line" + i, rootElement, stylesheet, + styles[i]); + } catch (/**@type{*}*/e) { + runtime.log("could not render line: " + String(e)); + } + } + } + /** + * Keep the classes of the styles right while a document is written in. + * + * An editor writes the name of another style on a paragraph, and the + * class it carries is written anew: what is drawn answers to the class + * and no longer to the name. + * @param {!Element} root + * @return {undefined} + */ + function watchStyleNames(root) { + var window = runtime.getWindow(), + /**@type{!Array.}*/ + watched = [], + /**@type{!number}*/ + i; + if (!window || !window.MutationObserver) { + return; + } + if (styleNameWatcher) { + styleNameWatcher.disconnect(); + } + for (i = 0; i < odf.Style2CSS.styleNameAttributes.length; i += 1) { + watched.push(odf.Style2CSS.styleNameAttributes[i].prefix + + ":style-name"); + } + styleNameWatcher = new window.MutationObserver(function (records) { + records.forEach(function (record) { + // An element alone carries an attribute, so what answers + // here is one; the number is written rather than "Node", + // that the sources of this file do not name. + if (record.target.nodeType === 1) { + odf.Style2CSS.stampOne( + /**@type{!Element}*/(record.target) + ); + } + }); + }); + styleNameWatcher.observe(root, { + attributes: true, + subtree: true, + attributeFilter: watched + }); + } /** * @param {!Element} odfbody * @return {undefined} @@ -448,6 +2955,148 @@ node.getAttributeNS(tablens, "number-rows-spanned")); } } + /** + * A bare (no table:style-name) inherits its style + * from the row's table:default-cell-style-name, else the column's. + * WebODF only styles cells via their own table:style-name, so without + * this the cell borders/fills (which presentation tables put on the + * default cell style) are lost. Copy the resolved default onto the cell + * so the existing style rules apply. + * @param {!Element} table + * @return {undefined} + */ + function applyDefaultCellStyles(table) { + var /**@type{!Array.}*/columnDefaults = [], + /**@type{?string}*/rowDefault = null, + /**@type{?string}*/styleName = null; + /** + * @param {!Element} parent scans table-column descendants in order + * @return {undefined} + */ + function collectColumns(parent) { + var c = parent.firstElementChild, + rep, + def, + j; + while (c) { + if (c.namespaceURI === tablens && c.localName === "table-column") { + def = c.getAttributeNS(tablens, "default-cell-style-name"); + rep = parseInt(c.getAttributeNS(tablens, "number-columns-repeated"), 10) || 1; + for (j = 0; j < rep; j += 1) { + columnDefaults.push(def || ""); + } + } else if (c.namespaceURI === tablens + && (c.localName === "table-columns" + || c.localName === "table-header-columns")) { + collectColumns(c); + } + c = c.nextElementSibling; + } + } + /** + * @param {!Element} parent styles cells across the row groups + * @return {undefined} + */ + function walkRows(parent) { + var /**@type{?Element}*/r = parent.firstElementChild, + /**@type{?Element}*/c, + /**@type{!number}*/rep, + /**@type{!number}*/idx; + while (r) { + if (r.namespaceURI === tablens && r.localName === "table-row") { + rowDefault = r.getAttributeNS(tablens, "default-cell-style-name"); + idx = 0; + c = r.firstElementChild; + while (c) { + if (c.namespaceURI === tablens + && (c.localName === "table-cell" + || c.localName === "covered-table-cell")) { + if (c.localName === "table-cell" + && !c.getAttributeNS(tablens, "style-name")) { + styleName = rowDefault || columnDefaults[idx]; + if (styleName) { + c.setAttributeNS(tablens, "table:style-name", styleName); + } + } + rep = parseInt(c.getAttributeNS(tablens, "number-columns-repeated"), 10) || 1; + idx += rep; + } + c = c.nextElementSibling; + } + } else if (r.namespaceURI === tablens + && (r.localName === "table-rows" + || r.localName === "table-header-rows")) { + walkRows(r); + } + r = r.nextElementSibling; + } + } + collectColumns(table); + walkRows(table); + } + + /** + * @param {!Element} node + * @return {!boolean} true if node is inside an office:spreadsheet + */ + function isInSpreadsheet(node) { + var n = node.parentNode; + while (n) { + if (n.namespaceURI === officens && n.localName === "spreadsheet") { + return true; + } + n = n.parentNode; + } + return false; + } + + /** + * A spreadsheet collapses a run of trailing empty columns into a single + * cell carrying table:number-columns-repeated, which WebODF renders as + * one cell. A fully empty row then ends up just one cell wide, so any + * per-cell painting (e.g. the gridline background) stops after the first + * column. Pad every row out to the table's column count with empty cells + * so the grid fills the whole used range. Spreadsheet-only: other tables + * are already rectangular (and do not nest here, so the column/row + * descendant scan stays within this table). + * @param {!Element} table + * @return {undefined} + */ + function padRowsToColumnCount(table) { + var doc = table.ownerDocument, + ncols = domUtils.getElementsByTagNameNS(table, tablens, 'table-column').length, + rows, + row, + count, + c; + if (!ncols) { + return; + } + rows = domUtils.getElementsByTagNameNS(table, tablens, 'table-row'); + for (row = 0; row < rows.length; row += 1) { + count = 0; + c = rows[row].firstElementChild; + while (c) { + if (c.namespaceURI === tablens + && (c.localName === "table-cell" + || c.localName === "covered-table-cell")) { + count += 1; + } + c = c.nextElementSibling; + } + while (count < ncols) { + rows[row].appendChild(doc.createElementNS(tablens, 'table:table-cell')); + count += 1; + } + } + } + + domUtils.getElementsByTagNameNS(odffragment, tablens, 'table').forEach(function (table) { + applyDefaultCellStyles(/**@type{!Element}*/(table)); + if (isInSpreadsheet(/**@type{!Element}*/(table))) { + padRowsToColumnCount(/**@type{!Element}*/(table)); + } + }); tableCells = domUtils.getElementsByTagNameNS(odffragment, tablens, 'table-cell'); for (i = 0; i < tableCells.length; i += 1) { node = /**@type{!Element}*/(tableCells[i]); @@ -471,6 +3120,53 @@ }); } + /** + * PowerPoint-exported ODP can contain empty list items that are not shown by + * PowerPoint, but WebODF's list CSS still paints their label. + * @param {!Element} odffragment + * @param {!CSSStyleSheet} stylesheet + * @return {undefined} + */ + function hideEmptyListItems(odffragment, stylesheet) { + var listItems = domUtils.getElementsByTagNameNS(odffragment, textns, "list-item"), + i, + item, + child, + hasNestedList, + hasText, + firstNonListChild; + + for (i = 0; i < listItems.length; i += 1) { + item = /**@type{!Element}*/(listItems[i]); + hasNestedList = false; + hasText = false; + firstNonListChild = null; + child = item.firstElementChild; + while (child) { + if (child.namespaceURI === textns && child.localName === "list") { + hasNestedList = true; + } else { + if (!firstNonListChild) { + firstNonListChild = child; + } + if (child.textContent.replace(/\s/g, "")) { + hasText = true; + } + } + child = child.nextElementSibling; + } + if (!hasText && !hasNestedList) { + item.setAttributeNS(webodfhelperns, "emptylistitem", "true"); + } else if (!hasText && firstNonListChild) { + firstNonListChild.setAttributeNS(webodfhelperns, "emptylistlabel", "true"); + } + } + stylesheet.insertRule('text|list-item[webodfhelper|emptylistitem="true"] {display: none !important;}', + stylesheet.cssRules.length); + stylesheet.insertRule('text|list-item > [webodfhelper|emptylistlabel="true"]:first-child:before ' + + '{content: none !important;}', stylesheet.cssRules.length); + } + /** * Expand ODF spaces of the form to N consecutive * elements. This makes things simpler for WebODF during @@ -522,12 +3218,14 @@ }); } /** - * @param {!Element} odfbody + * @param {!odf.ODFDocumentElement} odfnode * @param {!CSSStyleSheet} stylesheet + * @param {!odf.Formatting.PageGeometry} margins * @return {undefined} **/ - function modifyDrawElements(odfbody, stylesheet) { + function modifyDrawElements(odfnode, stylesheet, margins) { var node, + odfbody = odfnode.body, /**@type{!Array.}*/ drawElements = [], i; @@ -551,7 +3249,8 @@ // adjust all the frame positions for (i = 0; i < drawElements.length; i += 1) { node = drawElements[i]; - setDrawElementPosition('frame' + String(i), node, stylesheet); + setDrawElementPosition(odfnode, 'frame' + String(i), node, stylesheet, + margins); } formatParagraphAnchors(odfbody); } @@ -560,11 +3259,12 @@ * @param {!odf.Formatting} formatting * @param {!odf.OdfContainer} odfContainer * @param {!Element} shadowContent - * @param {!Element} odfbody + * @param {!odf.ODFDocumentElement} odfnode * @param {!CSSStyleSheet} stylesheet + * @param {!odf.Formatting.PageGeometry} margins * @return {undefined} **/ - function cloneMasterPages(formatting, odfContainer, shadowContent, odfbody, stylesheet) { + function cloneMasterPages(formatting, odfContainer, shadowContent, odfnode, stylesheet, margins) { var masterPageName, masterPageElement, styleId, @@ -577,7 +3277,7 @@ elementToClone, document = odfContainer.rootElement.ownerDocument; - element = odfbody.firstElementChild; + element = odfnode.body.firstElementChild; // no master pages to expect? if (!(element && element.namespaceURI === officens && (element.localName === "presentation" || element.localName === "drawing"))) { @@ -600,9 +3300,12 @@ elementToClone = masterPageElement.firstElementChild; i = 0; while (elementToClone) { - if (elementToClone.getAttributeNS(presentationns, 'placeholder') !== 'true') { + if (shouldCloneMasterPageElement(odfContainer.rootElement, /**@type{!Element}*/(element), masterPageElement, + /**@type{!Element}*/(elementToClone))) { clonedElement = /**@type{!Element}*/(elementToClone.cloneNode(true)); clonedPageElement.appendChild(clonedElement); + setDrawElementPosition(odfnode, styleId + '_' + i, clonedElement, + stylesheet, margins); } elementToClone = elementToClone.nextElementSibling; i += 1; @@ -613,7 +3316,8 @@ // Position all elements clonedDrawElements = domUtils.getElementsByTagNameNS(clonedPageElement, drawns, '*'); for (i = 0; i < clonedDrawElements.length; i += 1) { - setDrawElementPosition(styleId + '_' + i, clonedDrawElements[i], stylesheet); + setDrawElementPosition(odfnode, styleId + '_' + i, clonedDrawElements[i], + stylesheet, margins); } // Append the cloned master page to the "Shadow Content" element outside the main ODF dom @@ -630,7 +3334,8 @@ setContainerValue(clonedPageElement, presentationns, 'footer', getHeaderFooter(odfContainer, /**@type{!Element}*/(element), 'footer')); // Now call setDrawElementPosition on this new page to set the proper dimensions - setDrawElementPosition(styleId, clonedPageElement, stylesheet); + setDrawElementPosition(odfnode, styleId, clonedPageElement, stylesheet, + margins); // Add a custom attribute with the style name of the normal page, so the CSS rules created for the styles of the normal page // to display/hide frames of certain classes from the master page can address the cloned master page belonging to that normal page // Cmp. addDrawPageFrameDisplayRules in Style2CSS @@ -797,7 +3502,7 @@ * @constructor * @implements {gui.AnnotatableCanvas} * @implements {ops.Canvas} - * @implements {core.Destroyable} + * @implements {webodfcore.Destroyable} * @param {!HTMLElement} element Put and ODF Canvas inside this element. * @param {!gui.Viewport=} viewport Viewport used for scrolling elements and ranges into view */ @@ -830,19 +3535,54 @@ stylesxmlcss, /**@type{!HTMLStyleElement}*/ positioncss, + /**@type{!HTMLDivElement}*/ shadowContent, + /**@type{!number}*/ + autofitCounter = 0, + /**@type{?HTMLDivElement}*/ + pagesDiv = null, + /**@type{!boolean}*/ + paginated = false, + /**@type{!boolean}*/ + hyperlinksActive = true, /**@type{!Object.>}*/ eventHandlers = {}, waitingForDoneTimeoutId, - /**@type{!core.ScheduledTask}*/redrawContainerTask, + /**@type{!webodfcore.ScheduledTask}*/redrawContainerTask, shouldRefreshCss = false, + /**@type{!Object.}*/ + formulas = {}, + /**@type{?odf.ListStyleToCss}*/ + numbering = null, shouldRerenderAnnotations = false, loadingQueue = new LoadingQueue(), + /** + * How wide a note of an annotation is drawn, four centimetres, + * where the pane cannot be read for it: see "webodf.css". + * @const + * @type{!number} + */ + noteLaneWidth = 151, + /** + * Whether the fonts of the reader have landed: the pages are + * laid out again when they do, and once only. + * @type{!boolean} + */ + fontsWereReady = false, /**@type{!gui.ZoomHelper}*/ zoomHelper = new gui.ZoomHelper(), /**@type{!gui.Viewport}*/ canvasViewport = viewport || new gui.SingleScrollViewport(/**@type{!HTMLElement}*/(element.parentNode)); + /** + * Return a unique id used to bind an autofit css rule to a shape. + * @return {!string} + */ + function nextAutofitId() { + autofitCounter += 1; + return "autofit" + autofitCounter; + } + /** * Load all the images that are inside an odf element. * @param {!odf.OdfContainer} container @@ -873,6 +3613,149 @@ for (i = 0; i < images.length; i += 1) { node = /**@type{!Element}*/(images.item(i)); loadImage('image' + String(i), container, node, stylesheet); + applyImageClip(node, 'imageclip' + String(i), container, stylesheet); + } + } + /** + * Draw the formulas a text holds. + * + * A formula is a document of its own inside the package, written in + * MathML: it is read and put in the element that stands for it, which + * the browser draws, as it draws MathML of its own. An office writes + * an image of the formula beside it, under "ObjectReplacements", but + * a package need not hold one, and this document holds none. + * @param {!odf.OdfContainer} container + * @param {!Element} odffragment + * @return {undefined} + */ + function loadFormulas(container, odffragment) { + var objects = odffragment.getElementsByTagNameNS(drawns, "object"), + /**@type{!number}*/ + i; + /** + * @param {!Element} object + * @return {undefined} + */ + function loadFormula(object) { + var href = object.getAttributeNS(xlinkns, "href"), + /**@type{!string}*/ + path; + if (!href + || object.getElementsByTagNameNS(mathns, + "math").length > 0) { + return; + } + if (formulas.hasOwnProperty(href)) { + // The formula was read already: a copy of it is put in + // this one too, as an element that was cut in two is + // written on two pages and each holds the formula. + object.appendChild( + object.ownerDocument.importNode(formulas[href], true) + ); + return; + } + path = href.replace(/^\.?\//, "").replace(/\/$/, "") + + "/content.xml"; + container.getPartData(path, function (err, data) { + var /**@type{?Document}*/ + formula = null; + if (err || !data) { + return; + } + try { + formula = runtime.parseXML( + runtime.byteArrayToString(data, "utf8") + ); + } catch (e) { + formula = null; + } + if (!formula || !formula.documentElement + || formula.documentElement.namespaceURI + !== mathns) { + return; + } + formulas[href] = formula.documentElement; + while (object.firstChild) { + object.removeChild(object.firstChild); + } + object.appendChild( + object.ownerDocument.importNode( + formula.documentElement, + true + ) + ); + object.setAttributeNS(webodfhelperns, + "webodfhelper:formula", "true"); + }); + } + for (i = 0; i < objects.length; i += 1) { + loadFormula(/**@type{!Element}*/(objects.item(i))); + } + } + + /** + * Render embedded chart objects (draw:object -> chart sub-document) as + * static SVG backgrounds on the object element. + * @param {!odf.OdfContainer} container + * @param {!Element} odffragment + * @param {!CSSStyleSheet} stylesheet + * @return {undefined} + */ + function loadCharts(container, odffragment, stylesheet) { + var objects = odffragment.getElementsByTagNameNS(drawns, 'object'), + /**@type{!number}*/i; + /** + * @param {!Element} object + * @param {!string} chartId + * @return {undefined} + */ + function loadChart(object, chartId) { + var href = object.getAttributeNS(xlinkns, 'href'), + /**@type{!string}*/path, + /**@type{!Element}*/frame = /**@type{!Element}*/(object.parentNode); + if (!href) { + return; + } + path = href.replace(/^\.?\//, "").replace(/\/$/, "") + "/content.xml"; + container.getPartData(path, function (err, data) { + var /**@type{?Document}*/chartDoc, + model, + /**@type{!string}*/svg, + /**@type{!{v: !number, u: !string}}*/w, + /**@type{!{v: !number, u: !string}}*/h, + /**@type{!string}*/rule; + if (err || !data) { + return; + } + try { + chartDoc = runtime.parseXML(runtime.byteArrayToString(data, "utf8")); + if (!chartDoc) { + return; + } + model = parseChart(chartDoc); + if (!model || !model.series.length) { + return; + } + w = parseLength(frame.getAttributeNS(svgns, "width")); + h = parseLength(frame.getAttributeNS(svgns, "height")); + svg = buildChartSvg(model, w.v || 12, h.v || 7); + object.setAttributeNS(webodfhelperns, "chartid", chartId); + rule = 'draw|object[webodfhelper|chartid="' + chartId + '"] {' + + 'display: block; width: 100%; height: 100%;' + + 'background-image: url("data:image/svg+xml;charset=utf-8,' + + encodeURIComponent(svg) + '");' + + 'background-repeat: no-repeat;' + + 'background-position: center;' + + 'background-size: 100% 100%;' + + '}'; + stylesheet.insertRule(rule, stylesheet.cssRules.length); + } catch (/**@type{*}*/e) { + runtime.log("could not render chart: " + String(e)); + } + }); + } + for (i = 0; i < objects.length; i += 1) { + loadChart(/**@type{!Element}*/(objects.item(i)), 'chart' + String(i)); } } /** @@ -944,8 +3827,7 @@ */ function fixContainerSize() { var minHeight, - odfdoc = sizer.firstChild, - zoomLevel = zoomHelper.getZoomLevel(); + odfdoc = sizer.firstChild; if (!odfdoc) { return; @@ -968,8 +3850,13 @@ } } - element.style.width = Math.round(zoomLevel * sizer.offsetWidth) + "px"; - element.style.height = Math.round(zoomLevel * sizer.offsetHeight) + "px"; + // The sizer is now scaled with the CSS `zoom` property (see + // gui.ZoomHelper), which rescales its layout box, so its offset + // dimensions already reflect the zoom. Let the inline-block canvas + // element shrink-wrap the floated sizer instead of pinning it to a + // transform-scaled size, so the white page box matches exactly. + element.style.width = ""; + element.style.height = ""; // Re-apply inline-block to canvas element on resizing. // Chrome tends to forget this property after a relayout element.style.display = "inline-block"; @@ -980,7 +3867,8 @@ */ function redrawContainer() { if (shouldRefreshCss) { - handleStyles(odfcontainer, formatting, stylesxmlcss); + numbering = handleStyles(odfcontainer, formatting, + stylesxmlcss); shouldRefreshCss = false; // different styles means different layout, thus different sizes } @@ -993,6 +3881,393 @@ fixContainerSize(); } + /** + * Collect the names of graphic styles (resolving style inheritance) + * that request presentation text autofit, i.e. carry + * style:shrink-to-fit="true" or draw:fit-to-size="shrink-to-fit". + * @param {!odf.OdfContainer} container + * @return {?Object.} null when no style asks for it + */ + function collectAutofitStyleNames(container) { + var rootElement = container.rootElement, + /**@type{!Array.}*/ + roots = [rootElement.automaticStyles, rootElement.styles], + /**@type{!Object.}*/directly = {}, + /**@type{!Object.}*/parent = {}, + /**@type{!Array.}*/names = [], + /**@type{?Object.}*/result = null, + /**@type{?Element}*/root, + /**@type{!number}*/r, + styles, + /**@type{!number}*/j, + s, + /**@type{?string}*/name, + gp; + + /** + * @param {string} n + * @param {!number} depth + * @return {!boolean} + */ + function inherits(n, depth) { + if (!n || depth > 32) { + return false; + } + if (directly[n] === true) { + return true; + } + return inherits(parent[n] || '', depth + 1); + } + + for (r = 0; r < roots.length; r += 1) { + root = roots[r]; + if (!root) { + continue; + } + styles = domUtils.getElementsByTagNameNS(root, stylens, 'style'); + for (j = 0; j < styles.length; j += 1) { + s = styles[j]; + if (s.getAttributeNS(stylens, 'family') !== 'graphic') { + continue; + } + name = s.getAttributeNS(stylens, 'name'); + if (!name) { + continue; + } + names.push(name); + parent[name] = s.getAttributeNS(stylens, 'parent-style-name') || ''; + gp = domUtils.getElementsByTagNameNS(s, stylens, 'graphic-properties')[0]; + if (gp && (gp.getAttributeNS(stylens, 'shrink-to-fit') === 'true' + || gp.getAttributeNS(drawns, 'fit-to-size') === 'shrink-to-fit')) { + directly[name] = true; + } + } + } + for (j = 0; j < names.length; j += 1) { + if (inherits(names[j], 0)) { + if (!result) { + result = {}; + } + result[names[j]] = true; + } + } + return result; + } + + /** + * Presentation text autofit ("shrink text on overflow"). WebODF gives + * every placeholder a fixed height, so a paragraph that is too tall just + * overflows its frame. For frames whose graphic style asks for + * shrink-to-fit, scale the text box down (CSS zoom) so it fits. Frames + * that already fit, and decks that never request autofit, are untouched + * (the ratio is measured from rendered geometry, so it is independent of + * the canvas zoom applied afterwards). + * @param {!Element} odfbody + * @param {!odf.OdfContainer} container + * @param {!CSSStyleSheet} css + * @return {undefined} + */ + function shrinkAutofitText(odfbody, container, css) { + var autofit = collectAutofitStyleNames(container), + frames, + shapes, + i, + j, + frame, + shape, + textbox, + paragraphs, + r, + minTop, + maxBottom, + frameHeight, + textHeight, + scale, + id; + if (!autofit) { + return; + } + frames = domUtils.getElementsByTagNameNS(odfbody, drawns, 'frame'); + for (i = 0; i < frames.length; i += 1) { + frame = frames[i]; + if (autofit[frame.getAttributeNS(drawns, 'style-name')] !== true) { + continue; + } + textbox = domUtils.getElementsByTagNameNS(frame, drawns, 'text-box')[0]; + if (!textbox) { + continue; + } + frameHeight = frame.getBoundingClientRect().height; + textHeight = textbox.getBoundingClientRect().height; + if (frameHeight > 0 && textHeight > frameHeight + 1) { + // zoom < 1 shrinks the text; clamp so it never disappears. + scale = Math.max(0.3, (frameHeight / textHeight) * 0.97); + id = nextAutofitId(); + textbox.setAttributeNS(webodfhelperns, 'autofitid', id); + css.insertRule('draw|text-box[webodfhelper|autofitid="' + id + + '"] { zoom: ' + scale + '; }', css.cssRules.length); + } + } + + shapes = domUtils.getElementsByTagNameNS(odfbody, drawns, 'custom-shape'); + for (i = 0; i < shapes.length; i += 1) { + shape = shapes[i]; + if (autofit[shape.getAttributeNS(drawns, 'style-name')] !== true) { + continue; + } + paragraphs = domUtils.getElementsByTagNameNS(shape, textns, 'p'); + if (!paragraphs.length) { + continue; + } + frameHeight = shape.getBoundingClientRect().height; + minTop = Infinity; + maxBottom = -Infinity; + for (j = 0; j < paragraphs.length; j += 1) { + r = paragraphs[j].getBoundingClientRect(); + if (r.height > 0) { + minTop = Math.min(minTop, r.top); + maxBottom = Math.max(maxBottom, r.bottom); + } + } + textHeight = maxBottom > minTop ? maxBottom - minTop : 0; + if (frameHeight > 0 && textHeight > frameHeight + 1) { + scale = Math.max(0.3, (frameHeight / textHeight) * 0.97); + id = nextAutofitId(); + shape.setAttributeNS(webodfhelperns, 'autofitid', id); + css.insertRule('draw|custom-shape[webodfhelper|autofitid="' + id + + '"] > text|p { zoom: ' + scale + '; }', css.cssRules.length); + } + } + } + + /** + * Break a text over pages, when the reader asked for it. + * + * The pages are boxes that float to the right of the text, of the + * height a page has in the document: the text flows around them, so it + * breaks where a page ends, and the gap between two pages is a box of + * its own. Nothing of the text is moved, which is what lets a selection + * and a search cross a page. + * + * Only a text is broken over pages: a presentation is drawn as the + * slides it is made of, and a spreadsheet as its tables. + * @param {!odf.OdfContainer} container + * @param {!odf.ODFDocumentElement} odfnode + * @return {undefined} + */ + function drawPages(container, odfnode) { + var /**@type{?FontFaceSet}*/ + fonts, + /**@type{!Array.}*/ + askedFonts = []; + if (pagesDiv && pagesDiv.parentNode) { + pagesDiv.parentNode.removeChild(pagesDiv); + } + pagesDiv = null; + if (!paginated + || !domUtils.getDirectChild(odfnode.body, officens, 'text')) { + // A text drawn as one run is drawn on a ground of its own, as + // it was before the pages were drawn. A presentation keeps a + // clear ground, so that the gap between two slides is seen. + if (sizer + && domUtils.getDirectChild(odfnode.body, officens, + 'text')) { + sizer.style.background = "white"; + } + return; + } + // Each page paints itself from here on, so the box that holds them + // is left clear and the ground of the reader is seen between two + // of them, as it is between two slides. + if (sizer) { + sizer.style.background = ""; + } + pagesDiv = /**@type{!HTMLDivElement}*/(doc.createElementNS(element.namespaceURI, 'div')); + pagesDiv.className = 'webodf-pages'; + // The header and the footer of each page are laid inside it. + pagesDiv.style.position = 'relative'; + container.getContentElement().parentNode.insertBefore(pagesDiv, + container.getContentElement()); + // A lane is left beside each page for the notes of the + // annotations when the pages stand beside one another, as the + // pane beside the whole text has nowhere to stand there. + textLayout.setNoteLane(allowAnnotations + ? noteLaneWidth + : 0); + fonts = doc.fonts || null; + // A text is broken into pages once the fonts and the images of + // the document have landed: a line is of another width and a + // frame of another height until then, so the pages would be + // broken twice, and a document of a thousand pages is not broken + // twice for nothing. + if (fonts && fonts.ready && String(fonts.status) !== "loaded" + && !fontsWereReady) { + fonts.ready.then(function () { + fontsWereReady = true; + if (paginated && pagesDiv && pagesDiv.parentNode) { + loadingQueue.whenDrained(function () { + if (pagesDiv) { + textLayout.layout(odfnode, pagesDiv, 100); + } + }); + } + }); + return; + } + // A engine may say its fonts are ready before it draws with + // them: the first pages are then broken with the letters of + // another font and hold a line too many, which is only seen when + // the pages are all broken. The pages are set right as soon as + // the fonts have truly landed, and not at the end of the whole. + // The engine is asked to load the fonts the document names, and + // not only to say when the ones it knows of are ready: an engine + // may hold a font ready before it draws with it, and the pages + // would be broken with the letters of another font. A font that + // never comes is waited on for a moment and no longer, as a + // document is better drawn late than never. + if (fonts && fonts.load) { + askedFonts = Object.keys(formatting.getFontMap()).map( + function (name) { + var family = formatting.getFontMap()[name]; + return fonts.load("12px " + (family || name)) + .then(null, function () { + return null; + }); + } + ); + } + if (fonts && fonts.ready) { + // A font that lands after the pages are broken, which an + // engine may do whatever it was asked: the pages already + // drawn are set right as the next slice of them is drawn, + // see "TextLayout.js". + fonts.ready.then(function () { + textLayout.fontsChanged(); + }); + } + /** + * Break the text into pages, once what it is drawn with is + * there. + * @return {undefined} + */ + function breakPages() { + if (!paginated || !pagesDiv || !pagesDiv.parentNode) { + return; + } + textLayout.layout(odfnode, + /**@type{!HTMLDivElement}*/(pagesDiv), 100); + // The pages are read once they are drawn: what is drawn + // after them, and the fonts it asks for, may make a page + // hold one line less, and they are broken again then, once. + runtime.setTimeout(function () { + // A text that is still being broken sets its own pages + // right when it is done, so nothing is asked of it here. + if (paginated && pagesDiv && pagesDiv.parentNode + && !textLayout.isBreaking() + && !textLayout.pagesFit(odfnode)) { + textLayout.repair(odfnode, + /**@type{!HTMLDivElement}*/(pagesDiv)); + } + }, 0); + } + // The text is broken once the fonts it is written in are loaded: + // a line is of another width until then, and the pages would be + // broken twice. A font that never comes is waited on for three + // seconds and no longer. + loadingQueue.whenDrained(function () { + var waited = false; + /** + * @return {undefined} + */ + function once() { + if (!waited) { + waited = true; + breakPages(); + } + } + if (askedFonts.length > 0 && runtime.getWindow().Promise) { + runtime.setTimeout(once, 3000); + runtime.getWindow().Promise.all(askedFonts).then(once, + once); + return; + } + once(); + }); + } + /** + * Draw a text over pages, or as one run of text, which is how it is + * drawn until a reader asks otherwise. A reader that asks for it once a + * document is drawn has it drawn again. + * @param {!boolean} enable + * @return {undefined} + */ + this.setPaginated = function (enable) { + if (paginated === enable) { + return; + } + paginated = enable; + if (odfcontainer && odfcontainer.state === odf.OdfContainer.DONE) { + drawPages(odfcontainer, odfcontainer.rootElement); + } + }; + /** + * Whether the first page stands on its own, on the right of the + * first row, as the first page of a book does. + * @param {!boolean} alone + * @return {undefined} + */ + this.setFirstPageOnItsOwn = function (alone) { + textLayout.setFirstPageOnItsOwn(alone); + if (paginated && odfcontainer + && odfcontainer.state === odf.OdfContainer.DONE) { + drawPages(odfcontainer, odfcontainer.rootElement); + } + }; + /** + * Write the labels of the lists and of the headings again. + * + * The numbers are worked out as the document is drawn: an editor + * that adds an item to a list, or a heading to the text, asks for + * them to be worked out again. The rules of the styles are left + * alone, so this is the work of the labels alone. + * @return {undefined} + */ + this.refreshNumbering = function () { + if (numbering && odfcontainer + && odfcontainer.state === odf.OdfContainer.DONE) { + numbering.renumber(odfcontainer.rootElement.body); + } + }; + /** + * How many pages stand side by side on a row, see "TextLayout.js". + * @param {!number} pages + * @return {undefined} + */ + this.setPagesPerRow = function (pages) { + textLayout.setPagesPerRow(pages); + if (paginated && odfcontainer + && odfcontainer.state === odf.OdfContainer.DONE) { + drawPages(odfcontainer, odfcontainer.rootElement); + } + }; + /** + * The way a text is drawn over pages, see "TextLayout.js". + * @param {!string} mode + * @return {undefined} + */ + this.setPageMode = function (mode) { + textLayout.setPageMode(mode); + if (paginated && odfcontainer + && odfcontainer.state === odf.OdfContainer.DONE) { + drawPages(odfcontainer, odfcontainer.rootElement); + } + }; + /** + * @return {!boolean} + */ + this.isPaginated = function () { + return paginated; + }; /** * A new content.xml has been loaded. Update the live document with it. * @param {!odf.OdfContainer} container @@ -1000,13 +4275,38 @@ * @return {undefined} **/ function handleContent(container, odfnode) { - var css = /**@type{!CSSStyleSheet}*/(positioncss.sheet); + var css = /**@type{!CSSStyleSheet}*/(positioncss.sheet), + /**@type{!odf.Formatting.PageGeometry}*/ + margins; // only append the content at the end domUtils.removeAllChildNodes(element); sizer = /**@type{!HTMLDivElement}*/(doc.createElementNS(element.namespaceURI, 'div')); sizer.style.display = "inline-block"; - sizer.style.background = "white"; + // A text is drawn on one page, white and without an end, so the box + // that holds it is white. A presentation is drawn as a slide under + // another, and the box is left clear between two of them, so that + // the ground of the reader is seen there and each slide is read on + // its own. A slide paints itself from its page style. + if (container.getDocumentType() !== "presentation" + && container.getDocumentType() !== "drawing") { + sizer.style.background = "white"; + } + // The #shadowContent master-page overlay is position:absolute with + // top/left:0, so it must be anchored to the sizer (which carries the + // slide flow and the zoom). Without an explicit position the sizer is + // static, so the overlay instead resolves against a higher ancestor: + // any padding/header the host page puts around the canvas then shifts + // every master shape relative to its slide text. Make the sizer the + // containing block so the overlay always lines up with the slides. + sizer.style.position = "relative"; + // The document stands against the left of the box it is drawn + // in, and the box itself is what a reader puts in the middle of + // its window: a reader that lays its own text in the middle would + // otherwise set the document in the middle of the box, and a row + // of two pages, which is wider than the document, would hang off + // to the right of it. + sizer.style.textAlign = "left"; // When the window is shrunk such that the // canvas container has a horizontal scrollbar, // zooming out seems to not make the scrollable @@ -1026,24 +4326,73 @@ // styled, so we will populate this in the ODF body first. Once the // styling is handled, it can then be lifted out of the // ODF body and placed beside it, to not pollute the ODF dom. - shadowContent = doc.createElementNS(element.namespaceURI, 'div'); + shadowContent = /**@type{!HTMLDivElement}*/(doc.createElementNS(element.namespaceURI, 'div')); shadowContent.id = "shadowContent"; shadowContent.style.position = 'absolute'; shadowContent.style.top = 0; shadowContent.style.left = 0; container.getContentElement().appendChild(shadowContent); - modifyDrawElements(odfnode.body, css); - cloneMasterPages(formatting, container, shadowContent, odfnode.body, css); + // The classes of the styles are written by "Style2CSS.js", that + // the rules are read against; they are kept right here while a + // document is written in. + watchStyleNames(odfnode); + + // The margins of the page, that a frame placed against the sheet is + // written from, see "getHorizontalOffset". + margins = formatting.getPageMargins("", "paragraph"); + modifyDrawElements(odfnode, css, margins); + drawPages(container, odfnode); + cloneMasterPages(formatting, container, shadowContent, odfnode, css, + margins); modifyTables(odfnode.body, element.namespaceURI); modifyLineBreakElements(odfnode.body); + hideEmptyListItems(odfnode.body, css); expandSpaceElements(odfnode.body); expandTabElements(odfnode.body); + // A tab of the text is laid at the stop the document writes, + // see "TextLayout.js": a table of contents is written from the + // left and the numbers of its pages against a stop on the right. + textLayout.layOutTabs(odfnode); loadImages(container, odfnode.body, css); + loadCharts(container, odfnode.body, css); + loadFormulas(container, odfnode.body); loadVideos(container, odfnode.body); + // WebODF's default graphic style lists "page" among its tags, so it + // leaks a fill/border onto every draw:page. Reset it (low specificity) + // so pages are transparent by default and the white canvas sizer + // shows through; named page fills and the bitmap rules below have + // higher specificity and still win. Without this a master-page clone + // shows that stray fill instead of being see-through, and (for decks + // whose slides are full-bleed bitmaps) the clone's leftover fill + // ghosts against the slide on devices that render the absolutely + // positioned clone slightly off the in-flow slide. + css.insertRule('draw|page {background-color: transparent; border: 0;}', + css.cssRules.length); + loadHiddenDrawLayers(container, css); + loadPageBackgrounds(container, odfnode.body, css); + loadMasterPageBackgrounds(container, css); + loadCustomShapes(odfnode.body, css); + loadLines(odfnode.body, container.rootElement, css); sizer.insertBefore(shadowContent, sizer.firstChild); zoomHelper.setZoomableElement(sizer); + // A reader is told when the pages are all drawn, so that it can + // scale the document anew: a row of two pages is twice as wide as + // one, and is only that wide once the last page is drawn. + textLayout.whenDrawn(function () { + // An element that was cut in two is written on two pages, + // and the copy of it that holds a formula may have been made + // before the formula was read: what is empty is filled from + // what was read already. + if (odfcontainer) { + loadFormulas(odfcontainer, odfnode.body); + } + fireEvent("pagesdrawn", []); + }); + + // Once the slide is laid out, shrink any overflowing autofit text. + shrinkAutofitText(odfnode.body, container, css); } /** @@ -1097,7 +4446,8 @@ formatting.setOdfContainer(odfcontainer); handleFonts(odfcontainer, fontcss); - handleStyles(odfcontainer, formatting, stylesxmlcss); + numbering = handleStyles(odfcontainer, formatting, + stylesxmlcss); // do content last, because otherwise the document is constantly // updated whenever the css changes handleContent(odfcontainer, odfnode); @@ -1113,17 +4463,17 @@ if (odfcontainer.state === odf.OdfContainer.DONE) { callback(); } else { - // so the ODF is not done yet. take care that we'll - // do the work once it is done: - - // FIXME: use callback registry instead of replacing the onchange - runtime.log("WARNING: refreshOdf called but ODF was not DONE."); - + // The document is still being read, which is the ordinary + // course of things for a document of some size: the drawing + // waits for it and asks again, without a word, as a wait is not + // a fault. Only a document that never comes is told of. waitingForDoneTimeoutId = runtime.setTimeout(function later_cb() { if (odfcontainer.state === odf.OdfContainer.DONE) { callback(); + } else if (odfcontainer.state === odf.OdfContainer.INVALID) { + runtime.log("The document was not read: it is not an " + + "OpenDocument, or it is broken."); } else { - runtime.log("will be back later..."); waitingForDoneTimeoutId = runtime.setTimeout(later_cb, 500); } }, 100); @@ -1165,6 +4515,21 @@ odfcontainer = container; refreshOdf(suppressEvent === true); }; + /** + * The word a reader is given while a document is read. + * + * The address of the document is not written beside it: a reader + * asked for the document and knows which one, and the address of a + * viewer is one of its own, "odf:/document", which says nothing. + * @param {!Document} doc + * @return {!Element} + */ + function loadingMessage(doc) { + var box = doc.createElementNS(element.namespaceURI, 'div'); + box.className = 'webodf-loading'; + box.appendChild(doc.createTextNode(runtime.tr('Loading the document…'))); + return box; + } /** * @param {string} url * @return {undefined} @@ -1173,11 +4538,10 @@ // clean up loadingQueue.clearQueue(); - // FIXME: We need to support parametrized strings, because - // drop-in word replacements are inadequate for translations; - // see http://techbase.kde.org/Development/Tutorials/Localization/i18n_Mistakes#Pitfall_.232:_Word_Puzzles domUtils.removeAllChildNodes(element); - element.appendChild(element.ownerDocument.createTextNode(runtime.tr('Loading') + url + '...')); + element.appendChild(loadingMessage( + /**@type{!Document}*/(element.ownerDocument) + )); element.removeAttribute('style'); // open the odf container odfcontainer = new odf.OdfContainer(url, function (container) { @@ -1265,6 +4629,13 @@ allowAnnotations = allow; showAnnotationRemoveButton = showRemoveButton; if (odfcontainer) { + // The pages are drawn again: a lane is left beside each + // of them for the notes when the pages stand beside one + // another, and it is not left when there are none. + if (paginated + && odfcontainer.state === odf.OdfContainer.DONE) { + drawPages(odfcontainer, odfcontainer.rootElement); + } handleAnnotations(odfcontainer.rootElement); } } @@ -1315,15 +4686,50 @@ this.getZoomLevel = function () { return zoomHelper.getZoomLevel(); }; + /** + * Where the page that holds a place of the drawn document begins and + * ends across, in the pixels the document is written in, or nothing + * when it is drawn as one run of text: a note of an annotation is + * drawn in the lane beside the page it belongs to. + * @param {!number} x from the left edge of the window + * @return {?{left:!number,right:!number}} + */ + this.pageBoxAt = function (x) { + var ground = odfcontainer && odfcontainer.rootElement.body + ? odfcontainer.rootElement.body.getBoundingClientRect() + : null, + zoomLevel = zoomHelper.getZoomLevel(); + if (!ground || !paginated) { + return null; + } + return textLayout.pageAt((x - ground.left) / zoomLevel); + }; + /** + * How wide and how tall what a reader is shown at once is, at the + * size the document was written at: the whole of the document when + * it is drawn as one run of text, and one page when the pages stand + * beside one another. + * @return {!{width:!number,height:!number}} + */ + function fittingSize() { + var zoomLevel = zoomHelper.getZoomLevel(), + page = paginated + ? textLayout.pageSize() + : null; + return page || { + width: element.offsetWidth / zoomLevel, + height: element.offsetHeight / zoomLevel + }; + } /** * @param {!number} width * @param {!number} height * @return {undefined} */ this.fitToContainingElement = function (width, height) { - var zoomLevel = zoomHelper.getZoomLevel(), - realWidth = element.offsetWidth / zoomLevel, - realHeight = element.offsetHeight / zoomLevel, + var size = fittingSize(), + realWidth = size.width, + realHeight = size.height, zoom; zoom = width / realWidth; @@ -1337,8 +4743,7 @@ * @return {undefined} */ this.fitToWidth = function (width) { - var realWidth = element.offsetWidth / zoomHelper.getZoomLevel(); - zoomHelper.setZoomLevel(width / realWidth); + zoomHelper.setZoomLevel(width / fittingSize().width); }; /** * @param {!number} width @@ -1346,11 +4751,10 @@ * @return {undefined} */ this.fitSmart = function (width, height) { - var realWidth, realHeight, newScale, - zoomLevel = zoomHelper.getZoomLevel(); - - realWidth = element.offsetWidth / zoomLevel; - realHeight = element.offsetHeight / zoomLevel; + var size = fittingSize(), + realWidth = size.width, + realHeight = size.height, + newScale; newScale = width / realWidth; if (height !== undefined) { @@ -1366,8 +4770,7 @@ * @return {undefined} */ this.fitToHeight = function (height) { - var realHeight = element.offsetHeight / zoomHelper.getZoomLevel(); - zoomHelper.setZoomLevel(height / realHeight); + zoomHelper.setZoomLevel(height / fittingSize().height); }; /** * @return {undefined} @@ -1418,8 +4821,9 @@ // TODO: frameid and imageid generation here is better brought in sync with that for the images on loading of a odf file. var frameName = frame.getAttributeNS(drawns, 'name'), fc = frame.firstElementChild; - setDrawElementPosition(frameName, frame, - /**@type{!CSSStyleSheet}*/(positioncss.sheet)); + setDrawElementPosition(odfcontainer.rootElement, frameName, frame, + /**@type{!CSSStyleSheet}*/(positioncss.sheet), + formatting.getPageMargins("", "paragraph")); if (fc) { setImage(frameName + 'img', odfcontainer, fc, /**@type{!CSSStyleSheet}*/( positioncss.sheet)); @@ -1434,10 +4838,21 @@ cleanup = [pageSwitcher.destroy, redrawContainerTask.destroy]; runtime.clearTimeout(waitingForDoneTimeoutId); - // TODO: anything to clean with annotationViewManager? + // Nothing of the document is left wrapped or highlighted, and the + // manager is dropped with it: it holds every annotation of the + // document, and the elements it wrapped them in. + if (annotationViewManager) { + annotationViewManager.forgetAnnotations(); + annotationViewManager = null; + } if (annotationsPane && annotationsPane.parentNode) { annotationsPane.parentNode.removeChild(annotationsPane); } + // The pages a text was broken over, that hang beside the content. + if (pagesDiv && pagesDiv.parentNode) { + pagesDiv.parentNode.removeChild(pagesDiv); + } + pagesDiv = null; zoomHelper.destroy(function () { if (sizer) { @@ -1452,17 +4867,115 @@ head.removeChild(stylesxmlcss); head.removeChild(positioncss); - // TODO: loadingQueue, make sure it is empty - core.Async.destroyAll(cleanup, callback); + // What was waiting to be loaded is dropped: a task that runs + // after this holds a document that no one reads any more. + loadingQueue.destroy(); + webodfcore.Async.destroyAll(cleanup, callback); }; + /** + * The link a node stands in, if any. + * @param {?Node} node + * @return {?Element} + */ + function linkOf(node) { + var walk = node; + while (walk && walk !== element) { + if (walk.nodeType === 1 + && /**@type{!Element}*/(walk).namespaceURI === textns + && /**@type{!Element}*/(walk).localName === "a") { + return /**@type{!Element}*/(walk); + } + walk = walk.parentNode; + } + return null; + } + /** + * The place of the document a name stands for: a bookmark, or the + * mark of a reference, as the entries of a table of contents name. + * @param {!string} name + * @return {?Element} + */ + function markNamed(name) { + var wanted = ["bookmark-start", "bookmark", "reference-mark-start", + "reference-mark"], + /**@type{!NodeList}*/ + marks, + /**@type{!number}*/ + i, + /**@type{!number}*/ + k; + if (!odfcontainer) { + return null; + } + for (k = 0; k < wanted.length; k += 1) { + marks = odfcontainer.rootElement.getElementsByTagNameNS(textns, + wanted[k]); + for (i = 0; i < marks.length; i += 1) { + if (/**@type{!Element}*/(marks.item(i)).getAttributeNS( + textns, + "name" + ) === name) { + return /**@type{!Element}*/(marks.item(i)); + } + } + } + return null; + } + /** + * Follow a link of the document: a place of the document is shown, + * and anything else is opened as a browser opens it. + * + * A reader of a document follows a link by clicking it, as a reader + * of a page does; an editor asks for a key beside the click, and + * takes this over, see "SessionController.js". + * @param {!Event} e + * @return {undefined} + */ + function followLink(e) { + var link = hyperlinksActive + ? linkOf(/**@type{?Node}*/(e.target)) + : null, + /**@type{!string}*/ + url = link + ? link.getAttributeNS(xlinkns, "href") || "" + : "", + /**@type{?Element}*/ + mark; + if (!url) { + return; + } + if (url.charAt(0) === "#") { + mark = markNamed(url.substring(1)); + if (mark) { + mark.scrollIntoView(true); + } + } else if (/^\s*(javascript|data):/.test(url)) { + runtime.log("WARN: potentially malicious URL ignored"); + } else { + runtime.getWindow().open(url); + } + if (e.preventDefault) { + e.preventDefault(); + } + } + /** + * Whether a click follows the links of the document, which a reader + * does and an editor does its own way. + * @param {!boolean} active + * @return {undefined} + */ + this.setHyperlinksActive = function (active) { + hyperlinksActive = active; + }; function init() { + listenEvent(element, "click", followLink); webodfcss = addWebODFStyleSheet(doc); pageSwitcher = new PageSwitcher(addStyleSheet(doc)); fontcss = addStyleSheet(doc); stylesxmlcss = addStyleSheet(doc); positioncss = addStyleSheet(doc); - redrawContainerTask = core.Task.createRedrawTask(redrawContainer); + redrawContainerTask = webodfcore.Task.createRedrawTask(redrawContainer); zoomHelper.subscribe(gui.ZoomHelper.signalZoomChanged, fixContainerSize); } diff --git a/webodf/lib/odf/OdfContainer.js b/webodf/lib/odf/OdfContainer.js index 6d9568191..c01c551fb 100644 --- a/webodf/lib/odf/OdfContainer.js +++ b/webodf/lib/odf/OdfContainer.js @@ -1,5 +1,6 @@ /** * Copyright (C) 2012-2013 KO GmbH + * Copyright (C) 2026 Daniel Berthereau * * @licstart * This file is part of WebODF. @@ -18,16 +19,16 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ -/*global Node, NodeFilter, runtime, core, xmldom, odf, DOMParser, document, webodf */ +/*global Node, NodeFilter, runtime, webodfcore, xmldom, odf, DOMParser, document, webodf */ (function () { "use strict"; var styleInfo = new odf.StyleInfo(), - domUtils = core.DomUtils, + domUtils = webodfcore.DomUtils, /**@const @type{!string}*/ officens = "urn:oasis:names:tc:opendocument:xmlns:office:1.0", @@ -47,13 +48,33 @@ /**@const @type{!string}*/ automaticStylePrefix = Date.now() + "_webodf_", - base64 = new core.Base64(), + base64 = new webodfcore.Base64(), /**@const @type{!string}*/ documentStylesScope = "document-styles", /**@const @type{!string}*/ - documentContentScope = "document-content"; + documentContentScope = "document-content", + /**@type{?Document}*/ + elementOwnerDocument = null; + + /** + * Document used to create the elements of an odf container. + * + * A browser uses the document of the page, but node has no global document, + * so an empty one is created once from dom implementation of the runtime. + * @return {!Document} + */ + function ownerDocument() { + if (String(typeof document) !== "undefined") { + return document; + } + if (!elementOwnerDocument) { + elementOwnerDocument = runtime.getDOMImplementation() + .createDocument(officens, "office:document", null); + } + return /**@type{!Document}*/(elementOwnerDocument); + } /** * Return the position the node should get according to the ODF flat format. @@ -238,7 +259,7 @@ * @param {string} name * @param {string} mimetype * @param {!odf.OdfContainer} container - * @param {core.Zip} zip + * @param {webodfcore.Zip} zip */ odf.OdfPart = function OdfPart(name, mimetype, container, zip) { var self = this; @@ -310,7 +331,7 @@ */ odf.OdfContainer = function OdfContainer(urlOrType, onstatereadychange) { var self = this, - /**@type {!core.Zip}*/ + /**@type {!webodfcore.Zip}*/ zip, /**@type {!Object.}*/ partMimetypes = {}, @@ -352,6 +373,26 @@ } // private functions + /** + * Next element of a tree, in document order, or null at the end. + * + * The dom of a browser has createNodeIterator for this, but the package + * xmldom, used outside of a browser, does not provide it. + * @param {!Element} element + * @param {!Element} rootElement + * @return {?Element} + */ + function nextElementInTree(element, rootElement) { + var node = element; + if (node.firstElementChild) { + return node.firstElementChild; + } + // The loop stops on a node out of the tree too, that has no parent. + while (node && node !== rootElement && !node.nextElementSibling) { + node = /**@type{!Element}*/(node.parentNode); + } + return !node || node === rootElement ? null : node.nextElementSibling; + } /** * Iterates through the subtree of rootElement and adds annotation-end * elements as direct properties of the corresponding annotation elements. @@ -361,14 +402,14 @@ * @return {undefined} */ function linkAnnotationStartAndEndElements(rootElement) { - var document = rootElement.ownerDocument, - /** @type {!Object.} */ + var /** @type {!Object.} */ annotationStarts = {}, - n, name, annotationStart, - // TODO: optimize by using a filter rejecting subtrees without annotations possible - nodeIterator = document.createNodeIterator(rootElement, NodeFilter.SHOW_ELEMENT, null, false); + /**@type{?Element}*/ + n = rootElement, + name, + annotationStart; - n = /**@type{?Element}*/(nodeIterator.nextNode()); + // TODO: optimize by using a filter rejecting subtrees without annotations possible while (n) { if (n.namespaceURI === officens) { if (n.localName === "annotation") { @@ -399,7 +440,7 @@ } } } - n = /**@type{?Element}*/(nodeIterator.nextNode()); + n = nextElementInTree(n, rootElement); } } @@ -430,7 +471,7 @@ meta = root.meta; if (!meta) { - root.meta = meta = document.createElementNS(officens, "meta"); + root.meta = meta = ownerDocument().createElementNS(officens, "meta"); setChild(root, meta); } @@ -653,12 +694,25 @@ * @return {undefined} */ function setRootElement(root) { + var doc = /**@type{!Document}*/(root.ownerDocument); + /** + * The child of the root of that name, made empty where the + * document holds none: a document written by hand, or by a + * program that wrote no styles, holds none of them, and a reader + * that looks for a style in nothing at all throws. + * @param {!string} name + * @return {!Element} + */ + function childOrEmpty(name) { + return domUtils.getDirectChild(root, officens, name) + || doc.createElementNS(officens, name); + } contentElement = null; self.rootElement = /**@type{!odf.ODFDocumentElement}*/(root); root.fontFaceDecls = domUtils.getDirectChild(root, officens, 'font-face-decls'); - root.styles = domUtils.getDirectChild(root, officens, 'styles'); - root.automaticStyles = domUtils.getDirectChild(root, officens, 'automatic-styles'); - root.masterStyles = domUtils.getDirectChild(root, officens, 'master-styles'); + root.styles = childOrEmpty('styles'); + root.automaticStyles = childOrEmpty('automatic-styles'); + root.masterStyles = childOrEmpty('master-styles'); root.body = domUtils.getDirectChild(root, officens, 'body'); root.meta = domUtils.getDirectChild(root, officens, 'meta'); root.settings = domUtils.getDirectChild(root, officens, 'settings'); @@ -817,7 +871,11 @@ * @return {undefined} */ function removeElements(xmldoc, localName, allowedNamespaces) { - var elements = domUtils.getElementsByTagName(xmldoc, localName), + // The elements are searched by local name in every namespace: in a + // xml document, getElementsByTagName matches the qualified name, so + // it would miss an element written with a prefix, like + // . + var elements = domUtils.getElementsByTagNameNS(xmldoc, "*", localName), element, i; for (i = 0; i < elements.length; i += 1) { @@ -964,7 +1022,7 @@ * @return {!Node} */ function createManifestEntry(fullPath, mediaType) { - var element = document.createElementNS(manifestns, 'manifest:file-entry'); + var element = ownerDocument().createElementNS(manifestns, 'manifest:file-entry'); element.setAttributeNS(manifestns, 'manifest:full-path', fullPath); element.setAttributeNS(manifestns, 'manifest:media-type', mediaType); return element; @@ -1063,7 +1121,7 @@ * @return {!Element} */ function createElement(type) { - var original = document.createElementNS( + var original = ownerDocument().createElementNS( type.namespaceURI, type.localName ), @@ -1111,9 +1169,13 @@ body; if (!contentElement) { body = self.rootElement.body; + // A drawing is written as a run of pages, as a + // presentation is, and is drawn the same way: the canvas + // reads both, see "OdfCanvas.js". contentElement = domUtils.getDirectChild(body, officens, "text") || domUtils.getDirectChild(body, officens, "presentation") - || domUtils.getDirectChild(body, officens, "spreadsheet"); + || domUtils.getDirectChild(body, officens, "spreadsheet") + || domUtils.getDirectChild(body, officens, "drawing"); } if (!contentElement) { throw "Could not find content element in ."; @@ -1244,17 +1306,17 @@ /** * @param {!string} type * @param {!boolean=} isTemplate Default value is false. - * @return {!core.Zip} + * @return {!webodfcore.Zip} */ function createEmptyDocument(type, isTemplate) { - var emptyzip = new core.Zip("", null), + var emptyzip = new webodfcore.Zip("", null), mimetype = "application/vnd.oasis.opendocument." + type + (isTemplate === true ? "-template" : ""), data = runtime.byteArrayFromString( mimetype, "utf8" ), root = self.rootElement, - content = document.createElementNS(officens, type); + content = ownerDocument().createElementNS(officens, type); emptyzip.save("mimetype", data, false, new Date()); /** * @param {!string} memberName variant of the real local name which allows dot notation @@ -1266,7 +1328,7 @@ if (!realLocalName) { realLocalName = memberName; } - element = document.createElementNS(officens, realLocalName); + element = ownerDocument().createElementNS(officens, realLocalName); root[memberName] = element; root.appendChild(element); } @@ -1409,9 +1471,15 @@ zip = createEmptyDocument("spreadsheet", true); } else { url = /**@type{!string}*/(urlOrType); - zip = new core.Zip(url, function (err, zipobject) { + zip = new webodfcore.Zip(url, function (err, zipobject) { zip = zipobject; if (err) { + // A document that was read whole is not read again as a + // flat xml because a late answer says it failed: the + // second answer of one request would undo the first. + if (self.state === OdfContainer.DONE) { + return; + } loadFromXML(url, function (xmlerr) { if (err) { zip.error = err + "\n" + xmlerr; diff --git a/webodf/lib/odf/OdfNodeFilter.js b/webodf/lib/odf/OdfNodeFilter.js index 14ffc4382..e07fa137f 100644 --- a/webodf/lib/odf/OdfNodeFilter.js +++ b/webodf/lib/odf/OdfNodeFilter.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global Node, NodeFilter, odf*/ diff --git a/webodf/lib/odf/OdfSchema.js b/webodf/lib/odf/OdfSchema.js index da0e52a20..deca90386 100644 --- a/webodf/lib/odf/OdfSchema.js +++ b/webodf/lib/odf/OdfSchema.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global odf, runtime*/ @@ -43,7 +43,8 @@ UNKNOWN = "uncategorized", // Uncategorized element // Basic list of containers originally created using tools/odfRng2Config.js // List has been hand-updated however, and should not simply be discarded! - /**@type{!Array.>}*/ + // Each entry is a pair: the name of the element and its category. + /**@type{!Array.>}*/ containers = [ ["config:config-item" , UNKNOWN], ["form:item" , OBJECT], diff --git a/webodf/lib/odf/OdfUtils.js b/webodf/lib/odf/OdfUtils.js index ba94567f9..7d93f78f8 100644 --- a/webodf/lib/odf/OdfUtils.js +++ b/webodf/lib/odf/OdfUtils.js @@ -18,11 +18,11 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ -/*global Node, runtime, odf, NodeFilter, core*/ +/*global Node, runtime, odf, NodeFilter, webodfcore*/ /** * @constructor @@ -39,7 +39,7 @@ odf.OdfUtilsImpl = function OdfUtilsImpl() { /**@const @type{!string}*/ xlinkns = odf.Namespaces.xlinkns, - domUtils = core.DomUtils, + domUtils = webodfcore.DomUtils, // only add odf element namespaces here. // Namespaces solely used for attributes are excluded. eg. fo, xlink & xml odfNodeNamespaceMap = [ @@ -1054,6 +1054,71 @@ odf.OdfUtilsImpl = function OdfUtilsImpl() { return fontFamilyName; }; /*jslint regexp: false*/ + /** + * Convert a unit in a string to number of pixels at 96 dpi. + * If the input value has unit 'px' or is a number, the number is taken as + * is. Other allowed unit: cm, mm, pt, pc. + * @param {!string|!number} val + * @return {!number} + */ + function convertToPx(val) { + var n = -1, length, unit; + if (typeof val === "number") { + n = val; + } else { + length = parseLength(val); + unit = length && length.unit; + if (unit === "px") { + n = length.value; + } else if (unit === "cm") { + n = length.value / 2.54 * 96; + } else if (unit === "mm") { + n = length.value / 25.4 * 96; + } else if (unit === "in") { + n = length.value * 96; + } else if (unit === "pt") { + n = length.value / 0.75; + } else if (unit === "pc") { + n = length.value * 16; + } else { + // TODO: handle catching of this exception in the callers + throw "Unit " + unit + " not supported by this function."; + } + } + return n; + } + this.convertToPx = convertToPx; + /** + * @param {!string|!number} a + * @param {!string|!number} b + * @return {!number} + */ + this.sumLengths = function (a, b) { + var an = convertToPx(a), + bn = convertToPx(b); + return an + bn; + }; + /** + * Find the page, slide or spreadsheet for a node and return the rect for it. + * The page, slide or spreadsheet should be an ancestor to the node. + * @param {!Element} node + * @return {?ClientRect} + */ + this.getPageRect = function (node) { + var p = node; + // The page is the sheet, "office:body", and not the area a text is + // written in: the standard tells one from the other, "page" and + // "page-content" of "style:horizontal-rel". + while (p && !((p.namespaceURI === odf.Namespaces.officens + && (p.localName === "body" + || p.localName === "spreadsheet")) + || (p.namespaceURI === odf.Namespaces.drawns + && p.localName === "page"))) { + p = p.parentElement; + } + p = p || node; + return domUtils.getBoundingClientRect(p); + }; }; /** diff --git a/webodf/lib/odf/PageFurniture.js b/webodf/lib/odf/PageFurniture.js new file mode 100644 index 000000000..84cd5a751 --- /dev/null +++ b/webodf/lib/odf/PageFurniture.js @@ -0,0 +1,860 @@ +/** + * Copyright (C) 2014 KO GmbH + * Copyright (C) 2026 Daniel Berthereau + * + * @licstart + * This file is part of WebODF. + * + * WebODF is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License (GNU AGPL) + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * WebODF is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with WebODF. If not, see . + * @licend + * + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ + */ + + + + +/*global odf, runtime, Node*/ + +/** + * What is drawn around the text of a page: the header and the foot, the notes + * of the foot, the shapes of the master page, and the tabs of a line. + * + * Nothing here holds the state of a layout: what a function needs of the + * document is given to it, and what it needs of the layout — where the stops + * of a paragraph are read, how the names of the styles are taken off a copy — + * is given to it as a function to call. What breaks a text into pages is in + * "TextLayout.js". + * @constructor + */ +odf.PageFurnitureImpl = function PageFurnitureImpl() { + "use strict"; + var /**@const@type{!string}*/ + textns = odf.Namespaces.textns, + /**@const@type{!string}*/ + webodfhelperns = "urn:webodf:names:helper"; + /** + * Write the runs of spaces of a header as the spaces they stand for. + * + * The canvas does that for the text of the document alone, and a header + * is written in the styles of the document, so a "text:s" is still an + * element here and nothing of it is seen: "Page 1 of 809" was read + * "Page 1of 809". + * @param {!Element} box a header or a footer, drawn for a page + * @return {undefined} + */ + function expandSpaces(box) { + var doc = box.ownerDocument, + spaces = box.getElementsByTagNameNS(textns, "s"), + /**@type{!Array.}*/ + found = [], + i; + for (i = 0; i < spaces.length; i += 1) { + found.push(/**@type{!Element}*/(spaces.item(i))); + } + found.forEach(function (space) { + var count = parseInt(space.getAttributeNS(textns, "c"), 10), + run = ""; + if (!(count > 1)) { + count = 1; + } + while (count > 0) { + run += "\u00a0"; + count -= 1; + } + if (space.parentNode) { + space.parentNode.replaceChild(doc.createTextNode(run), space); + } + }); + } + /** + * Bring the tabs of a paragraph up to be children of the paragraph. + * + * A document may write a tab deep in the spans that carry the styles of + * a line, and the parts of the line are told apart at the paragraph: the + * spans around a tab are cut in two, so the tab stands between them and + * each part keeps the styles it was written with. + * @param {!Element} paragraph + * @return {undefined} + */ + function raiseTabs(paragraph) { + var tabs = paragraph.getElementsByTagNameNS(textns, "tab"), + /**@type{!Array.}*/ + found = [], + i; + for (i = 0; i < tabs.length; i += 1) { + found.push(/**@type{!Element}*/(tabs.item(i))); + } + found.forEach(function (tab) { + var /**@type{?Node}*/ + parent = tab.parentNode, + /**@type{!Element}*/ + tail, + /**@type{?Node}*/ + node; + while (parent && parent !== paragraph) { + tail = /**@type{!Element}*/( + /**@type{!Element}*/(parent).cloneNode(false) + ); + node = tab.nextSibling; + while (node) { + tail.appendChild(node); + node = tab.nextSibling; + } + if (parent.parentNode) { + parent.parentNode.insertBefore(tab, parent.nextSibling); + if (tail.firstChild) { + parent.parentNode.insertBefore(tail, + tab.nextSibling); + } + } + parent = tab.parentNode; + } + }); + } + /** + * The parts of a paragraph, that is what stands between its tabs. + * + * The nodes of the paragraph are taken out of it and put in a span for + * each part, the first of them holding what stands before the first tab. + * @param {!Element} paragraph + * @param {!Document} doc + * @param {!string} htmlns + * @return {!Array.} + */ + function partsOfParagraph(paragraph, doc, htmlns) { + var /**@type{!Array.}*/ + parts = [], + /**@type{!HTMLElement}*/ + part = /**@type{!HTMLElement}*/(doc.createElementNS(htmlns, + "span")), + /**@type{?Node}*/ + node = paragraph.firstChild, + /**@type{?Node}*/ + next, + /**@type{!Array.}*/ + pieces, + /**@type{!number}*/ + i; + parts.push(part); + while (node) { + next = node.nextSibling; + if (node.nodeType === Node.ELEMENT_NODE + && /**@type{!Element}*/(node).namespaceURI === textns + && /**@type{!Element}*/(node).localName === "tab") { + paragraph.removeChild(node); + part = /**@type{!HTMLElement}*/(doc.createElementNS(htmlns, + "span")); + parts.push(part); + } else if (node.nodeType === Node.TEXT_NODE + && String(node.textContent).indexOf("\t") !== -1) { + // The canvas writes a "text:tab" as a tab of a terminal + // before a text is drawn, so a tab is a letter here. + pieces = String(node.textContent).split("\t"); + paragraph.removeChild(node); + for (i = 0; i < pieces.length; i += 1) { + if (i > 0) { + part = /**@type{!HTMLElement}*/(doc.createElementNS( + htmlns, + "span" + )); + parts.push(part); + } + if (pieces[i] !== "") { + part.appendChild(doc.createTextNode(pieces[i])); + } + } + } else { + part.appendChild(node); + } + node = next; + } + return parts; + } + /** + * Set the parts of a paragraph against the tab stops it is written with. + * + * A tab is drawn as a tab of a terminal otherwise, that walks to the next + * stop of eight letters, so a title, a date and a number of a page ran + * into one another. Each part is laid where its stop says instead, and + * against it as its stop asks: the standard calls that "style:type" of a + * "style:tab-stop". + * @param {!odf.ODFDocumentElement} odfroot + * @param {!Element} paragraph + * @param {!function(!odf.ODFDocumentElement,!Element): + * !Array.} stopsFor the stops the + * style of a paragraph writes + * @return {undefined} + */ + function layOutTabStops(odfroot, paragraph, stopsFor) { + var doc = /**@type{!Document}*/(paragraph.ownerDocument), + htmlns = "http://www.w3.org/1999/xhtml", + stops = stopsFor(odfroot, paragraph), + /**@type{!Array.}*/ + parts, + /**@type{!HTMLElement}*/ + line; + if (stops.length === 0) { + return; + } + raiseTabs(paragraph); + if (!paragraph.getElementsByTagNameNS(textns, "tab").length + && String(paragraph.textContent).indexOf("\t") === -1) { + // A line without a tab is written as it stands: nothing of it is + // moved, and nothing of it can be lost. + return; + } + parts = partsOfParagraph(paragraph, doc, htmlns); + if (parts.length < 2) { + // Nothing was told apart, so what was taken from the paragraph + // is put back where it was. + while (parts[0].firstChild) { + paragraph.appendChild(parts[0].firstChild); + } + return; + } + // The parts are laid in a box of the page rather than in the + // paragraph: a style set on an element of the document is not the + // canvas's to set, and the document is read as broken when it is. + line = /**@type{!HTMLElement}*/(doc.createElementNS(htmlns, "div")); + line.style.position = "relative"; + // The stops of the line are written on it: a tab goes to the first + // stop past what stands before it, which is known once the line is + // drawn and measured, see "spreadLines". + line.setAttributeNS(webodfhelperns, "webodfhelper:stops", + stops.map(function (stop) { + return String(Math.round(stop.at)) + ":" + stop.type; + }).join(",")); + paragraph.appendChild(line); + // The line of the paragraph holds its own height from here on: the + // zero width space that keeps an empty paragraph from collapsing + // would add a line of the size of the style of the paragraph, and a + // footer of eight points would stand ten points apart. + paragraph.setAttributeNS(webodfhelperns, "webodfhelper:laidout", + "true"); + parts.forEach(function (piece, index) { + var /**@type{?odf.PageMeasure.TabStop}*/ + stop = index === 0 ? null : stops[index - 1]; + piece.style.position = "absolute"; + piece.style.whiteSpace = "pre"; + if (!stop) { + piece.style.left = "0"; + line.appendChild(piece); + return; + } + // A stop is written from the left edge of the text of the page, + // and a document may put one where the page is no longer wide + // enough for it: the last stop is then the right edge itself. + piece.style.left = "min(" + stop.at + "px, 100%)"; + if (stop.type === "center") { + piece.style.transform = "translateX(-50%)"; + } else if (stop.type === "right") { + piece.style.transform = "translateX(-100%)"; + } + line.appendChild(piece); + }); + + } + /** + * Lay the tabs of a paragraph of the text at their stops. + * + * A header is drawn in a box of its own and every part of it is laid at + * its stop; a paragraph of the text keeps the first of its parts in the + * flow of the text, so that the line holds its height and its words wrap + * as they would: an entry of a table of contents is written from the + * left, and the number of the page it names is laid against the stop on + * the right. + * + * Nothing is measured here: the stops are written where the document + * says, and the parts are laid there by the browser. + * @param {!odf.ODFDocumentElement} odfroot + * @param {!Element} paragraph + * @param {!function(!odf.ODFDocumentElement,!Element): + * !Array.} stopsFor the stops the + * style of a paragraph writes + * @return {undefined} + */ + function layOutTabsInText(odfroot, paragraph, stopsFor) { + var doc = /**@type{!Document}*/(paragraph.ownerDocument), + htmlns = "http://www.w3.org/1999/xhtml", + stops = stopsFor(odfroot, paragraph), + /**@type{!Array.}*/ + parts, + /**@type{!HTMLElement}*/ + line; + if (stops.length === 0 + || paragraph.hasAttributeNS(webodfhelperns, "laidout")) { + return; + } + raiseTabs(paragraph); + if (!paragraph.getElementsByTagNameNS(textns, "tab").length + && String(paragraph.textContent).indexOf("\t") === -1) { + return; + } + parts = partsOfParagraph(paragraph, doc, htmlns); + if (parts.length < 2) { + while (parts[0].firstChild) { + paragraph.appendChild(parts[0].firstChild); + } + return; + } + line = /**@type{!HTMLElement}*/(doc.createElementNS(htmlns, "div")); + line.style.position = "relative"; + paragraph.appendChild(line); + paragraph.setAttributeNS(webodfhelperns, "webodfhelper:laidout", + "true"); + parts.forEach(function (piece, index) { + var /**@type{?odf.PageMeasure.TabStop}*/ + stop = index === 0 + ? null + : stops[Math.min(index, stops.length) - 1], + /**@type{?odf.PageMeasure.TabStop}*/ + before = index > 1 + ? stops[Math.min(index - 1, stops.length) - 1] + : null; + if (!stop) { + // The first part is written as the text is: it holds the + // height of the line and wraps where the page ends. + line.appendChild(piece); + return; + } + if (stop.type === "left" || stop.type === "char") { + // A part that begins at a stop on the left is written in the + // flow of the line, and what stands before it is held to the + // width of its own slot: a label longer than its slot pushes + // what follows to the right, as a tab of a text does, where + // a part laid against the stop would be written over it. + /**@type{!HTMLElement}*/(parts[index - 1]).style.display = + "inline-block"; + /**@type{!HTMLElement}*/(parts[index - 1]).style.minWidth = + Math.max(0, stop.at - (before + ? before.at + : 0)) + "px"; + piece.style.whiteSpace = "pre-wrap"; + line.appendChild(piece); + return; + } + piece.style.position = "absolute"; + piece.style.whiteSpace = "pre"; + piece.style.top = "0"; + piece.style.left = "min(" + stop.at + "px, 100%)"; + if (stop.type === "center") { + piece.style.transform = "translateX(-50%)"; + } else { + piece.style.transform = "translateX(-100%)"; + } + line.appendChild(piece); + }); + } + /** + * The tab stops a line was drawn with, as it carries them. + * @param {!HTMLElement} line + * @return {!Array.} + */ + function stopsOfLine(line) { + var written = line.getAttributeNS(webodfhelperns, "stops") || "", + /**@type{!Array.}*/ + stops = []; + if (!written) { + return stops; + } + written.split(",").forEach(function (one) { + var parts = one.split(":"); + if (parts.length === 2) { + stops.push({at: parseFloat(parts[0]), type: parts[1]}); + } + }); + return stops; + } + /** + * Push the parts of the lines apart where they would be written over. + * + * A part is laid at its stop, and a part that runs past the next stop + * would be written over by the one that follows it: the one that follows + * is pushed to the right, as a tab of a text does, so nothing is hidden. + * A part is never pushed off the page: a line too full for its stops is + * drawn tight rather than half of it lost. + * + * The parts of every page are read before any of them is moved: a read + * that follows a write asks the browser to lay the whole page out again, + * and a document of a hundred pages would be drawn a hundred times over. + * @param {!Array.} boxes the headers and the footers drawn + * @return {undefined} + */ + function spreadLines(boxes) { + var /**@type{!Array.>}*/ + lines = [], + /**@type{!Array.>}*/ + rects = [], + /**@type{!Array.}*/ + room = [], + /**@type{!Array.}*/ + boxOf = []; + boxes.forEach(function (box) { + var found = box.getElementsByTagName("div"), + l, + line, + parts, + pieces, + i; + for (l = 0; l < found.length; l += 1) { + line = /**@type{!HTMLElement}*/(found.item(l)); + parts = line.children; + pieces = []; + for (i = 0; i < parts.length; i += 1) { + pieces.push(/**@type{!HTMLElement}*/(parts.item(i))); + } + lines.push(pieces); + boxOf.push(line); + } + }); + lines.forEach(function (pieces, l) { + room.push(boxOf[l].getBoundingClientRect().right); + rects.push(pieces.map(function (piece) { + return piece.getBoundingClientRect(); + })); + }); + // Every part of a line is laid at a place of its own and none of them + // holds the line open, so the line is given the height of the tallest + // one: a line of a footer written in eight points is eight points + // tall, and not the ten points of the style of its paragraph. + rects.forEach(function (found, l) { + var /**@type{!number}*/ + tall = 0; + found.forEach(function (rect) { + tall = Math.max(tall, rect.height); + }); + boxOf[l].style.height = tall + "px"; + }); + lines.forEach(function (pieces, l) { + var /**@type{!number}*/ + edge = 0, + /**@type{!number}*/ + right = room[l], + /**@type{!number}*/ + gap = 4, + /**@type{!number}*/ + left = boxOf[l].getBoundingClientRect().left, + stops = stopsOfLine(boxOf[l]), + /**@type{!number}*/ + next = 0; + pieces.forEach(function (piece, i) { + var rect = rects[l][i], + /**@type{?odf.PageMeasure.TabStop}*/ + stop, + /**@type{!number}*/ + at, + push; + push = 0; + if (i > 0 && stops.length > 0) { + // A tab goes to the first stop past what stands before + // it, and not to the stop of its own number: a line whose + // words run past the stop in the middle goes on to the + // one on the right, as an office writes it. + while (next < stops.length + && left + stops[next].at < edge + gap) { + next += 1; + } + if (next < stops.length) { + stop = stops[next]; + // The stop is taken by the tab whether anything is + // written after it or not: a line that holds two + // tabs and nothing between them writes what follows + // against the second stop. + next += 1; + at = left + stop.at; + if (stop.type === "right") { + at -= rect.width; + } else if (stop.type === "center") { + at -= rect.width / 2; + } + // A stop written for a page of another size stands + // past the edge of this one: what it holds is drawn + // against the edge instead, as an office draws it, + // rather than in the margin or off the page. + at = Math.min(at, right - rect.width); + push = at - rect.left; + } + } + if (rect.width === 0) { + // Nothing is drawn of it, so nothing is moved and + // nothing stands in the way of what follows. + return; + } + if (edge > 0 && rect.left + push < edge + gap) { + // A blank is left between two parts that were pushed + // together, so the two are still read as two. + push = Math.min(edge + gap - rect.left, + Math.max(0, right - rect.right)); + } + if (push !== 0) { + // The part is already drawn against a stop, and is only + // moved from where it stands. + piece.style.transform = piece.style.transform + + " translateX(" + push + "px)"; + } + edge = rect.right + push; + }); + }); + } + /** + * Whether a line holds a field, that is written anew on every page. + * @param {!Element} paragraph + * @param {!Object.} meta + * @return {!boolean} + */ + function holdsAField(paragraph, meta) { + var names = ["page-number", "page-count"].concat(Object.keys(meta)), + /**@type{!number}*/ + i; + for (i = 0; i < names.length; i += 1) { + if (paragraph.getElementsByTagNameNS(textns, names[i]).length > 0) { + return true; + } + } + return false; + } + /** + * How far an element is from the top of the page it is drawn in. + * @param {!Element} element + * @return {!number} + */ + function getTop(element) { + var he = /**@type{!HTMLElement}*/(element), + top = he.offsetTop || 0; + if (he.offsetParent) { + top += getTop(/**@type{!Element}*/(he.offsetParent)); + } + return top; + } + /** + * The room a header or a foot takes once it is written. + * + * A document says how tall its furniture is, and writes in it what it + * writes: a foot of two lines in a box of one is drawn of two all the + * same, and the text of the page is left that much less room. The + * height is measured on a copy drawn out of sight, of the width the + * page gives it. + * @param {!odf.ODFDocumentElement} odfroot + * @param {?Element} source or + * @param {!number} width of the text area, in pixels + * @param {!function(!odf.ODFDocumentElement,!Element):undefined} unstamp + * takes off a copy the names of the styles the canvas + * stamped on it, so the copy is drawn in its own style + * @return {!number} + */ + function roomTaken(odfroot, source, width, unstamp) { + var doc = /**@type{!Document}*/(odfroot.ownerDocument), + htmlns = doc.documentElement.namespaceURI, + /**@type{!Element}*/ + box, + /**@type{!number}*/ + height; + if (!source || width <= 0) { + return 0; + } + box = /**@type{!Element}*/(doc.createElementNS(htmlns, "div")); + /**@type{!HTMLElement}*/(box).style.position = "absolute"; + /**@type{!HTMLElement}*/(box).style.visibility = "hidden"; + /**@type{!HTMLElement}*/(box).style.left = "-10000px"; + /**@type{!HTMLElement}*/(box).style.top = "0"; + /**@type{!HTMLElement}*/(box).style.width = width + "px"; + box.appendChild(doc.importNode(source, true)); + unstamp(odfroot, box); + // The name of the style is the one of the document again, so the + // attribute the rules of that style are written against is written + // anew: it carried the name the canvas stamped, that names nothing + // any more, and a header drawn without its style is drawn of the + // size of a text and no longer of eight points. + odf.Style2CSS.stampStyleClasses(box); + expandSpaces(box); + odfroot.body.appendChild(box); + height = box.getBoundingClientRect().height; + odfroot.body.removeChild(box); + return height; + } + /** + * The margins of a page, once the furniture is written inside them. + * + * A header and a foot are written between the edge of the page and its + * text, so the text is left that much less room: the height the + * document gives them, or the height what they hold takes where that is + * the greater, and the space that parts them from the text where it + * does not give way. The room a header takes is the room it takes on + * every page: the pages of a document are of one height, and a title + * page that carries no header is drawn with the same text area as the + * others. + * @param {!odf.ODFDocumentElement} odfroot + * @param {!odf.PageMeasure.PageDimensions} dims + * @param {!function(!odf.ODFDocumentElement,!Element):undefined} unstamp + * takes off a copy the names of the styles the canvas + * stamped on it + * @return {!odf.PageMeasure.PageDimensions} + */ + function roomForFurniture(odfroot, dims, unstamp) { + var width = dims.pageWidth - dims.marginLeft - dims.marginRight, + /**@type{?Element}*/ + head = dims.otherPages.header || dims.otherPages.headerLeft + || dims.otherPages.headerFirst || dims.firstPage.header + || dims.firstPage.headerLeft || dims.firstPage.headerFirst, + /**@type{?Element}*/ + foot = dims.otherPages.footer || dims.otherPages.footerLeft + || dims.otherPages.footerFirst || dims.firstPage.footer + || dims.firstPage.footerLeft || dims.firstPage.footerFirst; + if (head) { + dims.marginTop += Math.max(dims.header.height, + roomTaken(odfroot, head, width, unstamp)) + dims.header.gap; + } + if (foot) { + dims.marginBottom += Math.max(dims.footer.height, + roomTaken(odfroot, foot, width, unstamp)) + dims.footer.gap; + } + return dims; + } + /** + * A box that holds the shapes of one page, of the size of the sheet. + * @param {!Document} doc + * @param {?string} htmlns + * @param {!odf.PageMeasure.PageDimensions} dims + * @param {!number} left where the page begins across + * @param {!number} top where the page begins + * @return {!HTMLDivElement} + */ + function pageShapesBox(doc, htmlns, dims, left, top) { + var box = /**@type{!HTMLDivElement}*/(doc.createElementNS(htmlns, + "div")); + box.className = "webodf-pageShapes"; + box.style.position = "absolute"; + box.style.left = left + "px"; + box.style.top = top + "px"; + box.style.width = dims.pageWidth + "px"; + box.style.height = dims.pageHeight + "px"; + return box; + } + /** + * @param {!odf.PageMeasure.PageShape} shape + * @return {!boolean} + */ + function behindTheText(shape) { + return shape.background; + } + /** + * @param {!odf.PageMeasure.PageShape} shape + * @return {!boolean} + */ + function overTheText(shape) { + return !shape.background; + } + /** + * Take away the boxes of a former layout, wherever they were put. + * @param {!Element} root + * @return {undefined} + */ + function removeBoxes(root) { + var boxes = root.getElementsByTagName("div"), + /**@type{!Element}*/ + box, + /**@type{!number}*/ + i; + for (i = boxes.length - 1; i >= 0; i -= 1) { + box = /**@type{!Element}*/(boxes.item(i)); + if (box.className === "webodf-pageShapes" + || box.className === "webodf-pageFurniture" + || box.className === "webodf-pageSheet") { + box.parentNode.removeChild(box); + } + } + } + /** + * Take the notes of the foot of a page away, and the room they took. + * @param {!Element} box the page + * @return {undefined} + */ + function clearNotes(box) { + var area = box.lastElementChild; + if (area && area.className === "webodf-pageNotes") { + box.removeChild(area); + } + /**@type{!HTMLElement}*/(box).style.paddingBottom = ""; + } + /** + * The notes of the foot of the page that are called for on a page. + * @param {!Element} box the page + * @return {!Array.} + */ + function notesOf(box) { + var found = box.getElementsByTagNameNS(textns, "note"), + /**@type{!Array.}*/ + notes = [], + /**@type{!Element}*/ + note, + /**@type{!number}*/ + i; + for (i = 0; i < found.length; i += 1) { + note = /**@type{!Element}*/(found.item(i)); + if (note.getAttributeNS(textns, "note-class") === "footnote") { + notes.push(note); + } + } + return notes; + } + /** + * Draw the notes called for on a page at the foot of it. + * + * A note is written at the foot of the page its number stands on, as an + * office writes it, and the text of the page is that much shorter: the + * room the notes take is the padding at the foot of the box, so what + * crosses into it is moved to the page that follows, see "trimPages". + * + * What is drawn is a copy: the note itself is left where it stands in the + * text, as the document is the reader's to show and not to write. + * @param {!Element} box the page + * @return {undefined} + */ + function layNotes(box) { + var doc = /**@type{!Document}*/(box.ownerDocument), + htmlns = /**@type{!string}*/(doc.documentElement.namespaceURI), + notes = notesOf(box), + /**@type{!HTMLElement}*/ + area, + /**@type{!HTMLElement}*/ + rule, + /**@type{!number}*/ + tall; + clearNotes(box); + if (notes.length === 0) { + return; + } + area = /**@type{!HTMLElement}*/(doc.createElementNS(htmlns, "div")); + area.className = "webodf-pageNotes"; + // The notes stand in the padding at the foot of the box, against the + // text and not against the paper: the padding of the box is the + // margin of the page, which the notes are no part of. + area.style.position = "absolute"; + area.style.bottom = "0"; + area.style.left = /**@type{!HTMLElement}*/(box).style.paddingLeft; + area.style.right = /**@type{!HTMLElement}*/(box).style.paddingRight; + // A line stands between the text and the notes, as an office draws + // it: a quarter of the width of the text, and no wider. + rule = /**@type{!HTMLElement}*/(doc.createElementNS(htmlns, "div")); + rule.style.width = "25%"; + rule.style.borderTop = "1px solid currentColor"; + rule.style.marginBottom = "0.2em"; + area.appendChild(rule); + notes.forEach(function (note) { + var line = /**@type{!HTMLElement}*/(doc.createElementNS(htmlns, + "div")), + number = /**@type{!HTMLElement}*/(doc.createElementNS(htmlns, + "span")), + citation = note.getElementsByTagNameNS(textns, + "note-citation").item(0), + body = note.getElementsByTagNameNS(textns, + "note-body").item(0), + /**@type{?Node}*/ + walk; + number.className = "webodf-pageNoteNumber"; + number.style.marginRight = "0.5em"; + number.appendChild(doc.createTextNode(citation + ? String(citation.textContent) + : "")); + // The body itself is not moved and not copied: what it holds is, + // as the rule that hides a body of a note would hide the copy of + // it as well. + walk = body + ? body.firstChild + : null; + while (walk) { + line.appendChild(walk.cloneNode(true)); + walk = walk.nextSibling; + } + // The number of the note is written at the head of the first + // line of it and not above it, in the letters of the note: it is + // put inside what was copied, and not before it. + if (line.firstElementChild) { + line.firstElementChild.insertBefore(number, + line.firstElementChild.firstChild); + } else { + line.insertBefore(number, line.firstChild); + } + area.appendChild(line); + }); + box.appendChild(area); + tall = area.getBoundingClientRect().height; + /**@type{!HTMLElement}*/(box).style.paddingBottom = tall + "px"; + } + /** + * How far the foot of a page reaches into the text of it. + * + * The foot is drawn under the text, in the margin: where it is taller + * than the margin left for it, it stands over the last lines, and this + * says by how much. + * @param {!Element} box the page + * @param {?Element} furniture what is drawn around the page + * @return {!number} nothing where the foot stands clear of the text + */ + function footOver(box, furniture) { + var bottom = box.getBoundingClientRect().bottom, + /**@type{!number}*/ + top = 0, + /**@type{!NodeList}*/ + parts, + /**@type{!ClientRect}*/ + rect, + /**@type{!number}*/ + i; + if (!furniture) { + return 0; + } + parts = furniture.getElementsByTagName("*"); + top = bottom; + for (i = 0; i < parts.length; i += 1) { + rect = /**@type{!Element}*/(parts.item(i)) + .getBoundingClientRect(); + // Only what is drawn under the middle of the page is the foot of + // it: a header stands over the text and takes nothing from it + // here. + if (rect.height > 0 + && rect.top > bottom - box.getBoundingClientRect().height + / 2) { + top = Math.min(top, rect.top); + } + } + return Math.max(0, Math.round(bottom - top)); + } + this.roomTaken = roomTaken; + this.roomForFurniture = roomForFurniture; + this.spreadLines = spreadLines; + this.getTop = getTop; + this.pageShapesBox = pageShapesBox; + this.behindTheText = behindTheText; + this.overTheText = overTheText; + this.removeBoxes = removeBoxes; + this.clearNotes = clearNotes; + this.footOver = footOver; + this.layNotes = layNotes; + this.notesOf = notesOf; + this.stopsOfLine = stopsOfLine; + this.layOutTabStops = layOutTabStops; + this.partsOfParagraph = partsOfParagraph; + this.raiseTabs = raiseTabs; + this.expandSpaces = expandSpaces; + this.holdsAField = holdsAField; + this.layOutTabsInText = layOutTabsInText; +}; + +/** + * @type {!odf.PageFurnitureImpl} + */ +odf.PageFurniture = new odf.PageFurnitureImpl(); diff --git a/webodf/lib/odf/PageLayoutProperties.js b/webodf/lib/odf/PageLayoutProperties.js index 041cc7c56..1d25b31ad 100644 --- a/webodf/lib/odf/PageLayoutProperties.js +++ b/webodf/lib/odf/PageLayoutProperties.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global runtime, odf*/ diff --git a/webodf/lib/odf/PageMeasure.js b/webodf/lib/odf/PageMeasure.js new file mode 100644 index 000000000..b0e333b6f --- /dev/null +++ b/webodf/lib/odf/PageMeasure.js @@ -0,0 +1,661 @@ +/** + * Copyright (C) 2014 KO GmbH + * Copyright (C) 2026 Daniel Berthereau + * + * @licstart + * This file is part of WebODF. + * + * WebODF is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License (GNU AGPL) + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * WebODF is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with WebODF. If not, see . + * @licend + * + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ + */ + + + + +/*global odf, runtime, Node*/ + +/** + * What a page holds and where it ends: the measures a layout leans on, and + * the cuts it makes by them. + * + * Nothing here knows of the layout that is under way: each function is given + * the page and the nodes it works on and answers of those alone. What breaks + * a text into pages, and what is drawn around them, is in "TextLayout.js". + * @constructor + */ +odf.PageMeasureImpl = function PageMeasureImpl() { + "use strict"; + var /**@const@type{!string}*/ + drawns = odf.Namespaces.drawns, + /**@const@type{!string}*/ + tablens = odf.Namespaces.tablens, + /**@const@type{!string}*/ + textns = odf.Namespaces.textns, + /**@const@type{!string}*/ + webodfhelperns = "urn:webodf:names:helper"; + /** + * How many lines a paragraph is written over. + * + * The lines of a paragraph are the rectangles of the text it holds, of + * which there may be more than one to a line where the line holds text + * of more than one style: those that begin at the same height are of + * one line. + * @param {!Element} element + * @return {!number} + */ + function linesOf(element) { + var doc = /**@type{!Document}*/(element.ownerDocument), + range = doc.createRange(), + /**@type{!Object.}*/ + tops = {}, + /**@type{!ClientRectList}*/ + rects, + /**@type{!number}*/ + i; + range.selectNodeContents(element); + rects = range.getClientRects(); + for (i = 0; i < rects.length; i += 1) { + tops[String(Math.round(rects.item(i).top))] = true; + } + return Object.keys(tops).length; + } + /** + * The number a style asks for, or the one that stands for none. + * @param {!string} said + * @param {!number} fallback + * @return {!number} + */ + function askedNumber(said, fallback) { + var read = parseInt(said, 10); + return isNaN(read) || read < 1 + ? fallback + : read; + } + /** + * Whether a paragraph that was cut in two is cut where an office would + * cut it. + * + * An office leaves no line of a paragraph alone at the foot of a page, + * nor alone at the head of the next: "fo:orphans" says how many lines + * are kept at the foot and "fo:widows" how many at the head, and + * "fo:keep-together" says that the paragraph is not cut at all. They are + * read from the style the browser worked out, as the sheet of the + * document carries them under the names css gives them. + * @param {!Element} element what is left on the page + * @param {!number} whole how many lines it held before it was cut + * @return {!boolean} + */ + function cutWhereAnOfficeWould(element, whole) { + var style = runtime.getWindow().getComputedStyle(element), + /**@type{!number}*/ + head, + /**@type{!number}*/ + tail; + if (!style) { + return true; + } + if (style.getPropertyValue("break-inside") === "avoid") { + return false; + } + // The lines an office keeps together are the lines of a paragraph: a + // section, a list or a table of contents is cut wherever a paragraph + // of it may be cut, and the rule of the orphans is read of that + // paragraph and not of the whole of what holds it. + if (element.namespaceURI !== textns + || (element.localName !== "p" && element.localName !== "h")) { + return true; + } + head = linesOf(element); + tail = whole - head; + if (tail < 1) { + return true; + } + return head >= askedNumber(style.getPropertyValue("orphans"), 2) + && tail >= askedNumber(style.getPropertyValue("widows"), 2); + } + /** + * Whether a paragraph is written on the page of the one that follows it. + * + * "fo:keep-with-next" says it of a heading, that an office never leaves + * alone at the foot of a page. It is read from the style the browser + * worked out, under the name css gives it. + * @param {!Element} element + * @return {!boolean} + */ + function keepsWithWhatFollows(element) { + var style = runtime.getWindow().getComputedStyle(element); + return style + ? style.getPropertyValue("break-after") === "avoid" + : false; + } + /** + * Whether anything that is seen is written on a page before a node. + * @param {!Element} box + * @param {!Node} node + * @return {!boolean} + */ + function anythingBefore(box, node) { + var doc = /**@type{!Document}*/(box.ownerDocument), + range; + if (!box.firstChild || box.firstChild === node) { + return false; + } + range = doc.createRange(); + range.setStartBefore(/**@type{!Node}*/(box.firstChild)); + range.setEndBefore(node); + return range.getBoundingClientRect().height > 1; + } + /** + * Whether a page holds anything that is seen. + * + * A page may hold the leftover of what was cut at the end of the page + * before it and draw nothing of it — an empty table of contents, a + * section that holds no more text: what asks to be written on a new page + * is written on this one all the same, as the page is empty to a reader. + * @param {!Element} box + * @return {!boolean} + */ + function holdsSomething(box) { + var doc = /**@type{!Document}*/(box.ownerDocument), + range; + if (!box.firstChild) { + return false; + } + range = doc.createRange(); + range.setStartBefore(/**@type{!Node}*/(box.firstChild)); + range.setEndAfter(/**@type{!Node}*/(box.lastChild)); + return range.getBoundingClientRect().height > 1; + } + /** + * Write a page in the columns its layout asks for. + * @param {!Element} box the page + * @param {!odf.PageMeasure.PageDimensions} dims + * @return {undefined} + */ + function setColumns(box, dims) { + if (dims.columnCount > 1) { + /**@type{!HTMLElement}*/(box).style.setProperty("column-count", + String(dims.columnCount)); + /**@type{!HTMLElement}*/(box).style.setProperty("column-gap", + dims.columnGap + "px"); + } + } + /** + * Whether a page is written in more than one column. + * @param {!Element} box the page + * @return {!boolean} + */ + function holdsColumns(box) { + var count = /**@type{!HTMLElement}*/(box).style + .getPropertyValue("column-count"); + return count !== "" && count !== "1" && count !== "auto"; + } + /** + * Where the text of a page ends. + * + * The notes of the foot of a page are drawn in the padding at the foot of + * the box, so the text ends where that padding begins and not at the edge + * of the box. + * @param {!Element} box the page + * @return {!number} + */ + function edgeOf(box) { + return box.getBoundingClientRect().bottom + - (parseFloat(/**@type{!HTMLElement}*/(box).style.paddingBottom) + || 0); + } + /** + * Where the last column of a page ends. + * + * A page written in columns is filled from the foot of one column to the + * head of the next, so what does not fit stands to the right of the last + * column and not under the text. + * @param {!Element} box the page + * @return {!number} + */ + function rightEdgeOf(box) { + return box.getBoundingClientRect().right + - (parseFloat(/**@type{!HTMLElement}*/(box).style.paddingRight) + || 0); + } + /** + * A range that holds one node. + * @param {!Document} doc + * @param {!Node} node + * @return {!Range} + */ + function rangeOf(doc, node) { + var range = doc.createRange(); + range.selectNode(node); + return range; + } + /** + * The first thing on a page that crosses the end of it, if any. + * + * The last thing on a page is not always the lowest of them: a frame set + * against the page stands where the page says, and it may stand high on + * a page it was written at the foot of. + * @param {!Element} box the page + * @return {?Element} + */ + function firstOverflowing(box) { + var edge = edgeOf(box), + /**@type{!boolean}*/ + columns = holdsColumns(box), + /**@type{!number}*/ + side = rightEdgeOf(box), + /**@type{?Element}*/ + node = box.firstElementChild, + /**@type{!ClientRect}*/ + rect; + while (node) { + rect = node.getBoundingClientRect(); + if (node.className !== "webodf-pageNotes" + && (rect.height > 0 || rect.width > 0) + && (columns + ? rect.right > side + 1 + : rect.bottom > edge + 1)) { + return node; + } + node = node.nextElementSibling; + } + return null; + } + /** + * Whether an element is drawn out of the flow of the text. + * + * A frame anchored to the page is set against the page and stands where + * the page says, so it is not read with what follows the text: the + * browser is asked, and only of the few elements that may be so. + * @param {!Element} element + * @return {!boolean} + */ + function isOutOfFlow(element) { + var style; + if (element.namespaceURI !== drawns) { + return false; + } + style = runtime.getWindow().getComputedStyle(element); + return style + ? style.position === "absolute" || style.position === "fixed" + : false; + } + /** + * How many of the nodes a page was given stand within it. + * + * The nodes are read where they stand and none of them is moved, so the + * page is laid out once for the whole of the reading: what is read after + * something is written is laid out anew, and taking the nodes back one + * at a time lays the page out once for each of them. + * @param {!Element} box + * @param {!Array.} added the nodes that were written on the page + * @return {!number} how many of them the page holds + */ + function firstOver(box, added) { + var doc = /**@type{!Document}*/(box.ownerDocument), + edge = box.getBoundingClientRect().bottom, + range = doc.createRange(), + /**@type{!ClientRect}*/ + rect, + /**@type{!number}*/ + i; + for (i = 0; i < added.length; i += 1) { + range.setStartBefore(added[i]); + range.setEndAfter(added[i]); + rect = range.getBoundingClientRect(); + if ((rect.height > 0 || rect.width > 0) && rect.bottom > edge + 1) { + return i; + } + } + return added.length; + } + /** + * Whether what a box holds is taller than the box. + * @param {!Element} box + * @param {?Node=} from the first node to read, the head of the box by + * default: what stands before it was read already. + * @return {!boolean} + */ + function overflows(box, from) { + var doc = /**@type{!Document}*/(box.ownerDocument), + /**@type{!number}*/ + edge = edgeOf(box), + /**@type{!boolean}*/ + columns = holdsColumns(box), + /**@type{!number}*/ + side = rightEdgeOf(box), + /**@type{?Node}*/ + last = box.lastElementChild + && box.lastElementChild.className === "webodf-pageNotes" + ? box.lastElementChild.previousSibling + : box.lastChild, + /**@type{?Node}*/ + node = from || box.firstChild, + range = doc.createRange(), + /**@type{!ClientRect}*/ + rect; + // Every child is read and not the last of them alone: a frame set + // against the page stands where the page says, so the last child of + // a page is not always the lowest of them. + // + // What was measured before and held is not measured again: a node + // that is written after another does not move it, so a page that is + // filled a few nodes at a time is read from the first of them and + // not from the head of the page, which would read a page of a + // hundred nodes a hundred times over. + if (!node) { + return false; + } + // The nodes are read in one measure and not one by one: a range that + // holds them all answers with the rectangle that holds what they are + // drawn as, which is the one the page is read against. A page filled + // by chunks of sixty-four nodes is measured once for the chunk and + // not sixty-four times. + if (!last) { + return false; + } + range.setStartBefore(node); + range.setEndAfter(last); + rect = range.getBoundingClientRect(); + if ((rect.height > 0 || rect.width > 0) + && (columns + ? rect.right > side + 1 + : rect.bottom > edge + 1)) { + return true; + } + // What is drawn out of the flow of the text — a frame set against the + // page — stands where the page says and is none of the range: those + // are read on their own, and there are few of them. + while (node) { + if (node.nodeType === Node.ELEMENT_NODE + && isOutOfFlow(/**@type{!Element}*/(node))) { + rect = /**@type{!Element}*/(node).getBoundingClientRect(); + if ((rect.height > 0 || rect.width > 0) + && rect.bottom > edge + 1) { + return true; + } + } + node = node.nextSibling; + } + return false; + } + /** + * Cut a text node where the page ends, and answer what is left of it. + * @param {!Element} box the page + * @param {!Text} node + * @param {!boolean} alone whether the page holds nothing else + * @return {?Text} what did not fit, or nothing if all of it fits + */ + function cutText(box, node, alone) { + var doc = /**@type{!Document}*/(box.ownerDocument), + range = doc.createRange(), + bottom = box.getBoundingClientRect().bottom, + /**@type{!number}*/ + low = 0, + /**@type{!number}*/ + high = node.length, + /**@type{!number}*/ + middle; + // The letter the page ends at is looked for by halving: the text up + // to it is measured, and the half that holds the end is kept. + while (low + 1 < high) { + middle = Math.floor((low + high) / 2); + range.setStart(node, 0); + range.setEnd(node, middle); + if (range.getBoundingClientRect().bottom <= bottom) { + low = middle; + } else { + high = middle; + } + } + if (low >= node.length) { + return null; + } + if (low === 0 && !alone) { + // Not a word of it fits on what is left of the page, so all of + // it is written on the next one: a page that holds nothing else + // keeps a letter of it all the same, or the text would be sent + // from page to page for ever. + return node.splitText(0); + } + // The cut is made at the last blank before the letter, so that a + // word is not written half on one page and half on the next. Where + // there is no blank to cut at, the whole of it is written on the + // page that follows: a page that ended with the "A" of "Appendix" + // and gave "ppendix" to the next is no page of a document. A page + // that holds nothing else cuts all the same, or the text would be + // sent from page to page for ever. + middle = String(node.data).lastIndexOf(" ", low); + if (middle <= 0) { + // Nothing of the word fits: the whole of it is written on the + // page that follows, unless the page holds nothing that is seen + // before it, where a page would else be left empty and the word + // sent on for ever. + return anythingBefore(box, node) + ? node.splitText(0) + : node.splitText(Math.max(1, low)); + } + return node.splitText(middle + 1); + } + /** + * How many nodes a node holds, counted no further than a bound. + * @param {!Node} node + * @param {!number} bound + * @return {!number} + */ + function nodesIn(node, bound) { + var count = 1, + /**@type{?Node}*/ + child = node.firstChild; + while (child && count < bound) { + count += nodesIn(child, bound - count); + child = child.nextSibling; + } + return count; + } + /** + * Take out of an element everything past the first so many nodes. + * + * A document holds elements that hold thousands of nodes: a table of + * contents is one of them, and a page of it was written by measuring the + * whole of what was left, page after page. The element is parted in two + * beforehand, by counting and not by measuring, so that a page is drawn + * against what a page may hold and not against everything that is left + * to write. + * + * What is taken out is put in a copy of the element, as a cut does, so + * that it is written in the style it was written in. + * @param {!Element} element + * @param {!number} bound how many nodes are left in it + * @return {?Element} what was taken out, or null where it held less + */ + function splitOff(element, bound) { + var count = 1, + /**@type{?Node}*/ + node = element.firstChild, + /**@type{!Element}*/ + tail = /**@type{!Element}*/(element.cloneNode(false)), + /**@type{?Element}*/ + inner, + /**@type{?Node}*/ + next; + tail.setAttributeNS(webodfhelperns, "webodfhelper:continued", "true"); + while (node && count < bound) { + count += nodesIn(node, bound - count); + if (count >= bound && node.firstChild + && node.nodeType === Node.ELEMENT_NODE) { + // The bound falls inside this one: what it holds past the + // bound is taken out of it in the same way. + inner = splitOff(/**@type{!Element}*/(node), bound); + if (inner) { + tail.appendChild(inner); + } + node = node.nextSibling; + break; + } + node = node.nextSibling; + } + while (node) { + next = node.nextSibling; + tail.appendChild(node); + node = next; + } + return tail.firstChild + ? tail + : null; + } + /** + * Whether what was cut off an element holds nothing worth a page. + * + * An element that is cut may leave a copy that holds no word and no + * shape of its own: the empty paragraph of a table of contents, for one. + * It is thrown away rather than written, as it would take the room of + * its spacings on the page that follows and leave a page half empty. + * @param {?Element} tail what was cut off + * @return {!boolean} + */ + function holdsNothing(tail) { + if (!tail) { + return true; + } + if (String(tail.textContent).trim() !== "") { + return false; + } + // A frame, an image or a shape is drawn although it holds no word. + return tail.getElementsByTagNameNS(drawns, "frame").length === 0 + && tail.getElementsByTagNameNS(drawns, "image").length === 0 + && tail.getElementsByTagNameNS(tablens, "table").length === 0; + } + /** + * Take the spacings from what a page ends on and that holds nothing. + * + * Cutting a paragraph between two of its words may leave nothing of it on + * the page it was cut on: the empty paragraph is drawn as its spacings + * and no more, and in a box that lays its children in a column those + * spacings are not folded into the edge of the box, so the page is a few + * pixels too full and the line above is moved on for nothing. + * @param {!Element} box the page + * @return {undefined} + */ + function unspaceEmptyTail(box) { + var node = box.lastElementChild, + /**@type{!number}*/ + guard = 8; + // The last thing on the page is looked for, and then everything it + // stands in that holds nothing either: a paragraph left empty by a + // cut holds the box its tabs were laid in, which is empty as well. + while (node && node.lastElementChild && guard > 0) { + node = node.lastElementChild; + guard -= 1; + } + guard = 8; + while (node && node !== box && guard > 0 + && node.getBoundingClientRect().height === 0) { + // An element of the document is of no namespace of html, where + // the browser leaves a "style" attribute alone: it is marked + // instead, and the rule of the mark takes its spacings away. + node.setAttributeNS(webodfhelperns, "webodfhelper:spaceless", + "true"); + node = node.parentElement; + guard -= 1; + } + } + this.rangeOf = rangeOf; + this.edgeOf = edgeOf; + this.rightEdgeOf = rightEdgeOf; + this.linesOf = linesOf; + this.askedNumber = askedNumber; + this.cutWhereAnOfficeWould = cutWhereAnOfficeWould; + this.keepsWithWhatFollows = keepsWithWhatFollows; + this.anythingBefore = anythingBefore; + this.holdsSomething = holdsSomething; + this.holdsNothing = holdsNothing; + this.isOutOfFlow = isOutOfFlow; + this.holdsColumns = holdsColumns; + this.setColumns = setColumns; + this.nodesIn = nodesIn; + this.splitOff = splitOff; + this.firstOverflowing = firstOverflowing; + this.firstOver = firstOver; + this.overflows = overflows; + this.cutText = cutText; + this.unspaceEmptyTail = unspaceEmptyTail; +}; + +/** + * @type {!odf.PageMeasureImpl} + */ +odf.PageMeasure = new odf.PageMeasureImpl(); + +/**@typedef{{ + node:!Element, + background:!boolean, + order:!number +}}*/ +odf.PageMeasure.PageShape; +/**@typedef{{ + at:!number, + type:!string +}}*/ +odf.PageMeasure.TabStop; +/**@typedef{{ + text:!Element, + root:!odf.ODFDocumentElement, + doc:!Document, + htmlns:?string, + plan:!Object, + waiting:!Array., + chunk:!number, + slice:!number, + page:!number, + top:!number, + sheet:!CSSStyleSheet, + heightRule:!number +}}*/ +odf.PageMeasure.Filling; + +/**@typedef{{ + shapes:!Array., + header:?Element, + footer:?Element, + headerLeft:?Element, + footerLeft:?Element, + headerFirst:?Element, + footerFirst:?Element +}}*/ +odf.PageMeasure.PageFurniture; +/**@typedef{{ + height:!number, + gap:!number +}}*/ +odf.PageMeasure.PageArea; +/**@typedef{{ + pageWidth:!number, + pageHeight:!number, + marginTop:!number, + marginBottom:!number, + marginLeft:!number, + marginRight:!number, + pageSeparation:!number, + columnCount:!number, + columnGap:!number, + header:!odf.PageMeasure.PageArea, + footer:!odf.PageMeasure.PageArea, + firstPage:!odf.PageMeasure.PageFurniture, + otherPages:!odf.PageMeasure.PageFurniture +}}*/ +odf.PageMeasure.PageDimensions; diff --git a/webodf/lib/odf/ParagraphProperties.js b/webodf/lib/odf/ParagraphProperties.js index d3c814a39..ef0e54a1c 100644 --- a/webodf/lib/odf/ParagraphProperties.js +++ b/webodf/lib/odf/ParagraphProperties.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global runtime, odf*/ diff --git a/webodf/lib/odf/StepUtils.js b/webodf/lib/odf/StepUtils.js index 8664f4714..0d6745e7d 100644 --- a/webodf/lib/odf/StepUtils.js +++ b/webodf/lib/odf/StepUtils.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global runtime, odf, Node*/ @@ -36,7 +36,7 @@ odf.StepUtils = function StepUtils() { * the content element immediately to the left of the step. Will return undefined if there is no content to the left * of the step. * - * @param {!core.StepIterator} stepIterator + * @param {!webodfcore.StepIterator} stepIterator * @return {!{container: !Node, startOffset: !number, endOffset: !number}|undefined} */ function getContentBounds(stepIterator) { diff --git a/webodf/lib/odf/Style2CSS.js b/webodf/lib/odf/Style2CSS.js index 8c66901c3..1ee857298 100644 --- a/webodf/lib/odf/Style2CSS.js +++ b/webodf/lib/odf/Style2CSS.js @@ -1,5 +1,6 @@ /** * Copyright (C) 2012-2013 KO GmbH + * Copyright (C) 2026 Daniel Berthereau * * @licstart * This file is part of WebODF. @@ -18,11 +19,11 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ -/*global odf, runtime, xmldom, core, document*/ +/*global odf, runtime, xmldom, webodfcore, document*/ /** * @constructor @@ -54,10 +55,13 @@ odf.Style2CSS = function Style2CSS() { /**@const @type{!string}*/ presentationns = odf.Namespaces.presentationns, + /**@const + @type{!string}*/ + loextns = "urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0", /**@const * @type {!string}*/ webodfhelperns = "urn:webodf:names:helper", - domUtils = core.DomUtils, + domUtils = webodfcore.DomUtils, styleParseUtils = new odf.StyleParseUtils(), /**@const @@ -157,7 +161,13 @@ odf.Style2CSS = function Style2CSS() { [ fons, 'margin-right', 'margin-right' ], [ fons, 'margin-top', 'margin-top' ], [ fons, 'margin-bottom', 'margin-bottom' ], - [ fons, 'border', 'border' ] + [ fons, 'border', 'border' ], + // The lines an office keeps together where a paragraph is cut by + // the end of a page: css names them the same and takes the same + // numbers, and reads them in a text that is broken into columns, + // which is how a page is broken here. + [ fons, 'orphans', 'orphans' ], + [ fons, 'widows', 'widows' ] ], /**@const @@ -211,6 +221,9 @@ odf.Style2CSS = function Style2CSS() { /**@const @type{!Array.>}*/ + pageBackgroundPropertySimpleMapping = [ + [ fons, 'background-color', 'background-color' ] + ], pageContentPropertySimpleMapping = [ [ fons, 'background-color', 'background-color' ], [ fons, 'padding', 'padding' ], @@ -263,10 +276,11 @@ odf.Style2CSS = function Style2CSS() { fontFaceDeclsMap = {}, utils = odf.OdfUtils, documentType, + /**@type{!odf.ODFDocumentElement}*/ odfRoot, defaultFontSize, xpath = xmldom.XPath, - cssUnits = new core.CSSUnits(); + cssUnits = new webodfcore.CSSUnits(); /** * @param {!string} family @@ -281,19 +295,30 @@ odf.Style2CSS = function Style2CSS() { return null; } - // If there is no name, it is a default style, in which case style-name shall be used without a value + // The style a name stands for is read from an attribute of its own + // and not from the value of the attribute that names it: a browser + // sorts the rules by the attribute they read, and tries on an + // element only those that read an attribute it carries, where every + // rule reading the same attribute is tried on every element that + // carries it. A document of twenty thousand paragraphs and two + // thousand styles is read forty million times over that way, which + // is some seconds; by the name of the attribute it is read once by + // element, some tens of milliseconds. The attributes are written on + // the elements by "odf.Style2CSS.stampStyleClasses". + // + // A class would do the same and is not read at all here: the + // attribute of a class answers to css for the elements of html and + // of svg alone, and never for those of OpenDocument. + // + // Where there is no name it is a default style, that answers for + // every element of the family. if (name) { - namepart = '[' + prefix + '|style-name="' + name + '"]'; + namepart = '[' + odf.Style2CSS.classOfStyle(prefix, name) + ']'; } else { namepart = ''; } if (prefix === 'presentation') { prefix = 'draw'; - if (name) { - namepart = '[presentation|style-name="' + name + '"]'; - } else { - namepart = ''; - } } selector = prefix + '|' + familytagnames[family].join( namepart + ',' + prefix + '|' @@ -376,6 +401,32 @@ odf.Style2CSS = function Style2CSS() { return parentStyleNode; } + /** + * Resolve the effective draw:fill for a graphic-properties element by + * walking up the parent style chain. ODF inherits style attributes + * independently, so a style may override draw:fill-color while inheriting + * the fill type (e.g. "solid") from an ancestor. Returns the nearest + * ancestor's draw:fill, or "" if none specifies it. + * @param {!Element} props graphic-properties element + * @return {!string} + */ + function getInheritedFill(props) { + var parentStyle = getParentStyleNode(/**@type{!Element}*/(props.parentNode)), + properties, + fill; + while (parentStyle) { + properties = domUtils.getDirectChild(parentStyle, stylens, 'graphic-properties'); + if (properties) { + fill = properties.getAttributeNS(drawns, 'fill'); + if (fill) { + return fill; + } + } + parentStyle = getParentStyleNode(parentStyle); + } + return ''; + } + /** * Margins can be a percentage of the parent style. Resolve to * absolute value. @@ -502,12 +553,12 @@ odf.Style2CSS = function Style2CSS() { rule += applySimpleMapping(props, textPropertySimpleMapping); value = props.getAttributeNS(stylens, 'text-underline-style'); - if (value === 'solid') { - textDecorationLine += ' underline'; + if (value === 'solid' || value === 'dotted' || value === 'wave' || value === 'dash') { + textDecorationLine = ' underline'; } value = props.getAttributeNS(stylens, 'text-line-through-style'); if (value === 'solid') { - textDecorationLine += ' line-through'; + textDecorationLine = ' line-through'; } if (textDecorationLine.length) { @@ -515,24 +566,44 @@ odf.Style2CSS = function Style2CSS() { rule += 'text-decoration:' + textDecorationLine + ';\n'; // CSS3 text-decoration shorthand rule += 'text-decoration-line:' + textDecorationLine + ';\n'; - // CSS3 text-decoration shorthand - FF - rule += '-moz-text-decoration-line:' + textDecorationLine + ';\n'; } value = props.getAttributeNS(stylens, 'text-line-through-type'); switch (value) { case 'double': - textDecorationStyle += ' double'; + textDecorationStyle = ' double'; break; case 'single': - textDecorationStyle += ' single'; + textDecorationStyle = ' single'; break; } + + value = props.getAttributeNS(stylens, 'text-underline-type'); + if (value === 'double') { + textDecorationStyle = ' double'; + } + + else { + value = props.getAttributeNS(stylens, 'text-underline-style'); + switch (value) { + case 'solid': + textDecorationStyle = ' solid'; + break; + case 'dotted': + textDecorationStyle = ' dotted'; + break; + case 'wave': + textDecorationStyle = ' wavy'; + break; + case 'dash': + textDecorationStyle = ' dashed'; + break; + } + } + if (textDecorationStyle) { // CSS3 rule += 'text-decoration-style:' + textDecorationStyle + ';\n'; - // CSS3 text-decoration shorthand - FF - rule += '-moz-text-decoration-style:' + textDecorationStyle + ';\n'; } fontName = props.getAttributeNS(stylens, 'font-name') @@ -585,7 +656,7 @@ odf.Style2CSS = function Style2CSS() { * @return {!string} */ function getParagraphProperties(props) { - var rule = '', bgimage, url, lineHeight; + var rule = '', bgimage, url, lineHeight, keepWithNext, keepTogether; rule += applySimpleMapping(props, paragraphPropertySimpleMapping); bgimage = domUtils.getDirectChild(props, stylens, 'background-image'); if (bgimage) { @@ -597,6 +668,22 @@ odf.Style2CSS = function Style2CSS() { } } + // A paragraph that keeps with the one after it is not left alone at + // the foot of a page, and one that keeps together is not cut at all. + // "always" is the word of the standard, "auto" its own opposite. + keepWithNext = props.getAttributeNS(fons, 'keep-with-next'); + if (keepWithNext === 'always') { + rule += 'break-after: avoid;'; + } else if (keepWithNext === 'auto') { + rule += 'break-after: auto;'; + } + keepTogether = props.getAttributeNS(fons, 'keep-together'); + if (keepTogether === 'always') { + rule += 'break-inside: avoid;'; + } else if (keepTogether === 'auto') { + rule += 'break-inside: auto;'; + } + lineHeight = props.getAttributeNS(fons, 'line-height'); if (lineHeight && lineHeight !== 'normal') { lineHeight = utils.parseFoLineHeight(lineHeight); @@ -642,28 +729,169 @@ odf.Style2CSS = function Style2CSS() { } /** - * @param {string} n + * @param {?string} n * @return {boolean} */ function isNumber(n) { return !isNaN(parseFloat(n)); } + /** + * Find a named fill definition ( or ) inside the + * document's or . ODF references + * these by name from the graphic properties (draw:fill-gradient-name etc.). + * @param {!string} localName + * @param {?string} name + * @return {?Element} + */ + function findFillDef(localName, name) { + var roots, i, defs, j; + if (!name) { + return null; + } + roots = [odfRoot.styles, odfRoot.automaticStyles]; + for (i = 0; i < roots.length; i += 1) { + if (roots[i]) { + defs = domUtils.getElementsByTagNameNS(roots[i], drawns, localName); + for (j = 0; j < defs.length; j += 1) { + if (defs[j].getAttributeNS(drawns, 'name') === name) { + return defs[j]; + } + } + } + } + return null; + } + + /** + * Parse an ODF angle value to degrees. ODF allows a unit ("30deg", + * "1.5rad", "100grad") or a bare number, which is legacy 1/10 of a degree. + * @param {?string} value + * @return {!number} + */ + function parseOdfAngle(value) { + var n; + if (!value) { + return 0; + } + value = value.trim(); + if (/deg$/.test(value)) { + return parseFloat(value); + } + if (/grad$/.test(value)) { + return parseFloat(value) * 0.9; + } + if (/rad$/.test(value)) { + return parseFloat(value) * 180 / Math.PI; + } + n = parseFloat(value); + return isNaN(n) ? 0 : n / 10; + } + + /** + * Produce an rgba() colour from an {r,g,b} triple, scaled by an ODF + * intensity percentage (gradients darken towards black at <100%). + * @param {!{r:number,g:number,b:number}} rgb + * @param {?string} intensity + * @param {!number} alpha + * @return {!string} + */ + function scaledRgba(rgb, intensity, alpha) { + var f = isNumber(intensity) ? parseFloat(intensity) / 100 : 1; + return 'rgba(' + Math.round(rgb.r * f) + ',' + Math.round(rgb.g * f) + + ',' + Math.round(rgb.b * f) + ',' + alpha + ')'; + } + + /** + * Convert an ODF definition to a CSS gradient image. + * @param {!Element} grad + * @param {!number} alpha + * @return {!string} a CSS value, or "" if unrenderable + */ + function gradientToCss(grad, alpha) { + var style = grad.getAttributeNS(drawns, 'style') || 'linear', + startC = hexToRgb(grad.getAttributeNS(drawns, 'start-color') || ''), + endC = hexToRgb(grad.getAttributeNS(drawns, 'end-color') || ''), + borderAttr = grad.getAttributeNS(drawns, 'border'), + border = isNumber(borderAttr) + ? Math.max(0, Math.min(100, parseFloat(borderAttr))) + : 0, + start, + end, + cssAngle; + if (!startC || !endC) { + return ''; + } + start = scaledRgba(startC, grad.getAttributeNS(drawns, 'start-intensity'), alpha); + end = scaledRgba(endC, grad.getAttributeNS(drawns, 'end-intensity'), alpha); + if (style === 'radial' || style === 'ellipsoid') { + return 'radial-gradient(ellipse at center, ' + start + ' ' + + border + '%, ' + end + ' 100%)'; + } + if (style === 'square' || style === 'rectangular') { + return 'radial-gradient(circle at center, ' + start + ' ' + + border + '%, ' + end + ' 100%)'; + } + // ODF angles run counter-clockwise from the positive x-axis; CSS + // gradient angles run clockwise from "to top". (90 - odf) maps between + // the two so a vertical ODF gradient stays vertical. + cssAngle = ((90 - parseOdfAngle(grad.getAttributeNS(drawns, 'angle'))) % 360 + 360) % 360; + if (style === 'axial') { + // axial: end colour at both edges, start colour through the centre + return 'linear-gradient(' + cssAngle + 'deg, ' + end + ' 0%, ' + + start + ' 50%, ' + end + ' 100%)'; + } + return 'linear-gradient(' + cssAngle + 'deg, ' + start + ' ' + + border + '%, ' + end + ' 100%)'; + } + + /** + * Convert an ODF definition to a stack of CSS + * repeating-linear-gradients (one per hatch direction). + * @param {!Element} hatch + * @return {!string} + */ + function hatchToCss(hatch) { + var hstyle = hatch.getAttributeNS(drawns, 'style') || 'single', + color = hatch.getAttributeNS(drawns, 'color') || '#000000', + distance = hatch.getAttributeNS(drawns, 'distance') || '0.1cm', + rotation = parseOdfAngle(hatch.getAttributeNS(drawns, 'rotation')), + layers = []; + /** + * @param {!number} angle + * @return {!string} + */ + function line(angle) { + var a = ((-angle) % 360 + 360) % 360; + return 'repeating-linear-gradient(' + a + 'deg, ' + color + ' 0, ' + + color + ' 1px, transparent 1px, transparent ' + distance + ')'; + } + layers.push(line(rotation)); + if (hstyle === 'double' || hstyle === 'triple') { + layers.push(line(rotation + 90)); + } + if (hstyle === 'triple') { + layers.push(line(rotation + 45)); + } + return layers.join(', '); + } + /** * @param {!Element} props * @return {string} */ function getGraphicProperties(props) { - var rule = '', alpha, bgcolor, fill; + var rule = '', alpha, bgcolor, fill, vAlign, justify, def, css, + shadowColor, shadowAlpha; rule += applySimpleMapping(props, graphicPropertySimpleMapping); alpha = props.getAttributeNS(drawns, 'opacity'); + alpha = isNumber(alpha) ? parseFloat(alpha) / 100 : 1; fill = props.getAttributeNS(drawns, 'fill'); bgcolor = props.getAttributeNS(drawns, 'fill-color'); - if (fill === 'solid' || fill === 'hatch') { + if (fill === 'solid') { if (bgcolor && bgcolor !== 'none') { - alpha = isNumber(alpha) ? parseFloat(alpha) / 100 : 1; bgcolor = hexToRgb(bgcolor); if (bgcolor) { rule += "background-color: rgba(" @@ -675,8 +903,99 @@ odf.Style2CSS = function Style2CSS() { } else { rule += "background: none;"; } + } else if (fill === 'gradient') { + def = findFillDef('gradient', props.getAttributeNS(drawns, 'fill-gradient-name')); + css = def && gradientToCss(def, alpha); + if (css) { + rule += "background-image: " + css + ";"; + } + } else if (fill === 'hatch') { + def = findFillDef('hatch', props.getAttributeNS(drawns, 'fill-hatch-name')); + if (def) { + // a solid hatch paints fill-color behind the hatch lines + if (props.getAttributeNS(drawns, 'fill-hatch-solid') === 'true' + && bgcolor && bgcolor !== 'none') { + bgcolor = hexToRgb(bgcolor); + if (bgcolor) { + rule += "background-color: rgba(" + bgcolor.r + "," + + bgcolor.g + "," + bgcolor.b + "," + alpha + ");"; + } + } + rule += "background-image: " + hatchToCss(def) + ";"; + } else if (bgcolor && bgcolor !== 'none') { + bgcolor = hexToRgb(bgcolor); + if (bgcolor) { + rule += "background-color: rgba(" + bgcolor.r + "," + + bgcolor.g + "," + bgcolor.b + "," + alpha + ");"; + } + } } else if (fill === "none") { rule += "background: none;"; + } else if (!fill && bgcolor && bgcolor !== 'none' + && getInheritedFill(props) === 'solid') { + // This style has no draw:fill of its own but does override + // draw:fill-color, and inherits a solid fill from an ancestor. ODF + // inherits style attributes independently, so the effective fill is + // the parent's "solid". WebODF emits one CSS rule per style and + // relies on the cascade for inheritance, so without this the parent + // rule's fill-color wins and the child's colour override is + // silently lost (e.g. an #ff9966 shape that inherits fill="solid" + // from a #00b8ff parent ended up rendered in the parent's blue). + // The inherited-fill check keeps shapes that override fill-color but + // inherit fill="none" transparent rather than painting them. + bgcolor = hexToRgb(bgcolor); + if (bgcolor) { + rule += "background-color: rgba(" + + bgcolor.r + "," + + bgcolor.g + "," + + bgcolor.b + "," + + alpha + ");"; + } + } + + // Drop shadow on shapes/frames/pictures. WebODF otherwise ignores + // draw:shadow entirely (only fo:text-shadow on text was handled). + // loext:shadow-blur (a LibreOffice extension) gives a soft shadow when + // present; without it ODF shadows are hard-edged (blur 0). + if (props.getAttributeNS(drawns, 'shadow') === 'visible') { + shadowColor = hexToRgb(props.getAttributeNS(drawns, 'shadow-color') || '#808080') + || {r: 128, g: 128, b: 128}; + shadowAlpha = props.getAttributeNS(drawns, 'shadow-opacity'); + shadowAlpha = isNumber(shadowAlpha) ? parseFloat(shadowAlpha) / 100 : 1; + rule += 'box-shadow: ' + + (props.getAttributeNS(drawns, 'shadow-offset-x') || '0.1cm') + ' ' + + (props.getAttributeNS(drawns, 'shadow-offset-y') || '0.1cm') + ' ' + + (props.getAttributeNS(loextns, 'shadow-blur') || '0') + ' ' + + 'rgba(' + shadowColor.r + ',' + shadowColor.g + ',' + + shadowColor.b + ',' + shadowAlpha + ');'; + } + + // Position the shape's text vertically inside its (fixed-height) box. + // WebODF otherwise always renders text at the top, so a bottom- or + // middle-aligned title (common in presentation placeholders) sits too + // high. Implemented with a flex column whose main-axis alignment maps + // from draw:textarea-vertical-align. "top" is the default and is left + // alone so non-text frames (images) are unaffected. + vAlign = props.getAttributeNS(drawns, 'textarea-vertical-align'); + if (vAlign === 'middle') { + justify = 'center'; + } else if (vAlign === 'bottom') { + justify = 'flex-end'; + } else if (vAlign === 'justify') { + justify = 'space-between'; + } + if (justify) { + rule += 'display: flex; flex-direction: column; justify-content: ' + justify + ';'; + } + + // A frame sent to the background (style:run-through="background", used + // e.g. for a page-sized backdrop image) sits behind the body text in + // ODF. WebODF otherwise paints frames in document order, so such a frame + // declared last in the markup covers all the text positioned before it. + // A negative z-index drops it below the (positioned) text frames while + // keeping it above the page's own background. + if (props.getAttributeNS(stylens, 'run-through') === 'background') { + rule += 'z-index: -1;'; } return rule; @@ -686,9 +1005,24 @@ odf.Style2CSS = function Style2CSS() { * @return {string} */ function getDrawingPageProperties(props) { - var rule = ''; + var rule = '', fill, def, css, alpha; rule += applySimpleMapping(props, graphicPropertySimpleMapping); + fill = props.getAttributeNS(drawns, 'fill'); + alpha = props.getAttributeNS(drawns, 'opacity'); + alpha = isNumber(alpha) ? parseFloat(alpha) / 100 : 1; + if (fill === 'gradient') { + def = findFillDef('gradient', props.getAttributeNS(drawns, 'fill-gradient-name')); + css = def && gradientToCss(def, alpha); + if (css) { + rule += "background-image: " + css + ";"; + } + } else if (fill === 'hatch') { + def = findFillDef('hatch', props.getAttributeNS(drawns, 'fill-hatch-name')); + if (def) { + rule += "background-image: " + hatchToCss(def) + ";"; + } + } if (props.getAttributeNS(presentationns, 'background-visible') === 'true') { rule += "background: none;"; } @@ -726,8 +1060,37 @@ odf.Style2CSS = function Style2CSS() { * @return {string} */ function getTableProperties(props) { - var rule = '', borderModel; + var rule = '', borderModel, align; rule += applySimpleMapping(props, tablePropertySimpleMapping); + // Where the table stands between the margins of the page, as + // "table:align" says. A table set against the margins takes the + // width of the text and not the width its style writes, which is + // the width it had when it was written; one that stands on a side + // or in the middle keeps its width and is moved by its margins. + align = props.getAttributeNS(tablens, 'align'); + if (align === 'margins') { + rule += 'width:100%;'; + } else if (align === 'center') { + rule += 'margin-left:auto;margin-right:auto;'; + } else if (align === 'right') { + rule += 'margin-left:auto;margin-right:0;'; + } else if (align === 'left') { + rule += 'margin-left:0;margin-right:auto;'; + } + // A table of a width the document wrote keeps that width: the + // browser lays a table out from what is written in it unless it is + // told otherwise, and a long address in a cell made the table wider + // than the page, with a bar to scroll under it. + if (align !== 'margins' + && (props.hasAttributeNS(stylens, 'width') + || props.hasAttributeNS(stylens, 'rel-width'))) { + rule += 'table-layout:fixed;'; + } + // A table wider than the text of the page is drawn to the width of + // the text, its columns narrowed by as much, as an office draws it: + // a table written for a page of another size would otherwise run + // into the margin and be cut off at the edge of the paper. + rule += 'max-width:100%;'; borderModel = props.getAttributeNS(tablens, 'border-model'); if (borderModel === 'collapsing') { @@ -813,6 +1176,35 @@ odf.Style2CSS = function Style2CSS() { insertFrameVisibilityRule(invisibleFrameClasses, "hidden"); } + /** + * The css of a section: the columns it is written in. + * + * A section of two columns is written in two columns by the browser, as + * an office writes it: what a section holds runs from the foot of one + * column to the head of the next. + * @param {!Element} props + * @return {!string} + */ + function getSectionProperties(props) { + var rule = '', + columns = domUtils.getDirectChild(props, stylens, 'columns'), + /**@type{!number}*/ + count = 0, + /**@type{!string}*/ + gap = ''; + if (columns) { + count = parseInt(columns.getAttributeNS(fons, 'column-count'), + 10) || 0; + gap = columns.getAttributeNS(fons, 'column-gap') || ''; + } + if (count > 1) { + rule += 'column-count:' + count + ';'; + if (gap) { + rule += 'column-gap:' + gap + ';'; + } + } + return rule; + } /** * @param {!CSSStyleSheet} sheet * @param {string} family @@ -835,6 +1227,11 @@ odf.Style2CSS = function Style2CSS() { if (properties) { rule += getParagraphProperties(properties); } + properties = domUtils.getDirectChild(node.element, + stylens, 'section-properties'); + if (properties) { + rule += getSectionProperties(properties); + } properties = domUtils.getDirectChild(node.element, stylens, 'graphic-properties'); if (properties) { @@ -873,6 +1270,42 @@ odf.Style2CSS = function Style2CSS() { sheet.insertRule(rule, sheet.cssRules.length); } + /** + * Give a page of a slide or of a drawing the size of A4, where the + * document says none. + * + * A master page names the layout that gives its pages their size, and a + * document need not write either: the standard names no size to fall + * back on, and an office draws such a page at the size of the paper of + * the place it is used in. A4 is taken here, which is the paper of every + * place but a handful, and the pages are drawn rather than left of no + * size at all. + * @param {!CSSStyleSheet} sheet + * @param {!odf.ODFDocumentElement} rootNode + * @return {undefined} + */ + function addPageSizeOfLastResort(sheet, rootNode) { + var masterStyles = rootNode.masterStyles, + /**@type{?Element}*/ + page, + /**@type{!string}*/ + name; + if (!masterStyles) { + return; + } + page = masterStyles.firstElementChild; + while (page) { + if (page.namespaceURI === stylens + && page.localName === "master-page" + && !page.getAttributeNS(stylens, "page-layout-name")) { + name = page.getAttributeNS(stylens, "name") || ""; + sheet.insertRule('draw|page[draw|master-page-name="' + name + + '"] {width:21.001cm;height:29.7cm;}', + sheet.cssRules.length); + } + page = page.nextElementSibling; + } + } /** * @param {!CSSStyleSheet} sheet * @param {!Element} node / @@ -880,6 +1313,7 @@ odf.Style2CSS = function Style2CSS() { */ function addPageStyleRules(sheet, node) { var rule = '', imageProps, url, + background = '', contentLayoutRule = '', pageSizeRule = '', props = domUtils.getDirectChild(node, stylens, 'page-layout-properties'), @@ -903,7 +1337,9 @@ odf.Style2CSS = function Style2CSS() { } } - if (documentType === 'presentation') { + // A drawing is a run of pages, as a presentation is, and its pages are + // of the size their master page gives them in the same way. + if (documentType === 'presentation' || documentType === 'drawing') { masterStyles = domUtils.getDirectChild(/**@type{!Element}*/(node.parentNode.parentNode), officens, 'master-styles'); e = masterStyles && masterStyles.firstElementChild; while (e) { @@ -925,9 +1361,48 @@ odf.Style2CSS = function Style2CSS() { } } else if (documentType === 'text') { + // A document holds a layout for each master page it was written + // with, and the text is drawn on one of them: the first master + // page of the document, which is the one a paragraph is written + // on unless it says otherwise. The rules of every other layout + // were written all the same, and the last of them won, so the + // text was drawn in the margins of the endnotes or of the html + // page rather than in its own. + masterStyles = domUtils.getDirectChild( + /**@type{!Element}*/(node.parentNode.parentNode), + officens, + 'master-styles' + ); + e = masterStyles && masterStyles.firstElementChild; + while (e && !(e.namespaceURI === stylens + && e.localName === "master-page")) { + e = e.nextElementSibling; + } + if (e && e.getAttributeNS(stylens, 'page-layout-name') + !== stylename) { + return; + } contentLayoutRule = 'office|text {' + rule + '}'; rule = ''; + // The background of the page covers the whole, margins included. + background = applySimpleMapping(props, + pageBackgroundPropertySimpleMapping); + if (imageProps && imageProps.getAttributeNS(xlinkns, 'href')) { + background += "background-image: url('odfkit:" + + imageProps.getAttributeNS(xlinkns, 'href') + "');" + + applySimpleMapping(imageProps, bgImageSimpleMapping); + } + if (background) { + // The same fill is given to the sheets a paginated text is + // drawn on, see "TextLayout.js": a page is painted on its own + // there, so the ground of the reader is seen between two of + // them, where the body is painted from the first page to the + // last. + sheet.insertRule('office|body, .webodf-pageSheet {' + + background + '}', sheet.cssRules.length); + } + // TODO: We want to use the simpleMapping for ODTs, but not until we have pagination. // So till then, set only the width. //rule += applySimpleMapping(props, pageSizePropertySimpleMapping); @@ -980,7 +1455,7 @@ odf.Style2CSS = function Style2CSS() { /** * @param {!string} doctype - * @param {!Element} rootNode + * @param {!odf.ODFDocumentElement} rootNode * @param {!CSSStyleSheet} stylesheet * @param {!Object.} fontFaceMap * @param {!Object.>} styleTree @@ -1009,6 +1484,9 @@ odf.Style2CSS = function Style2CSS() { } odfRoot = rootNode; + // Every element is given the class of the style it names, that the + // rules written below are read against. + odf.Style2CSS.stampStyleClasses(rootNode); // make stylesheet empty while (stylesheet.cssRules.length) { @@ -1023,6 +1501,12 @@ odf.Style2CSS = function Style2CSS() { documentType = doctype; defaultFontSize = runtime.getWindow().getComputedStyle(document.body, null).getPropertyValue('font-size') || '12pt'; + // A page of a slide or of a drawing whose master page names no layout + // is drawn at the size of A4, or it would be drawn of no size at all. + if (doctype === "presentation" || doctype === "drawing") { + addPageSizeOfLastResort(stylesheet, rootNode); + } + // add the various styles for (family in familynamespaceprefixes) { if (familynamespaceprefixes.hasOwnProperty(family)) { @@ -1036,3 +1520,113 @@ odf.Style2CSS = function Style2CSS() { } }; }; + +/** + * The attribute an element carries for the style of a name. + * + * A name of the OpenDocument standard holds letters, digits, dots and + * dashes, and a name of an attribute holds no dot: what is not a letter, a + * digit, a dash or an underscore is written as its number between + * underscores, and a letter of the upper case is written under an + * underscore, as an attribute of a document of html knows no case. Two + * names never come to one attribute that way. + * @param {!string} prefix the prefix of the family, "text" and its kin + * @param {!string} name + * @return {!string} + */ +odf.Style2CSS.classOfStyle = function (prefix, name) { + "use strict"; + var written = "", + /**@type{!string}*/ + letter, + /**@type{!number}*/ + i; + for (i = 0; i < name.length; i += 1) { + letter = name.charAt(i); + if (/[A-Z]/.test(letter)) { + written += "_" + letter.toLowerCase(); + } else if (/[a-z0-9\-]/.test(letter)) { + written += letter; + } else { + written += "_" + name.charCodeAt(i).toString(16) + "_"; + } + } + return "odf-" + prefix + "-" + written; +}; + +/** + * The attributes that name a style, by the prefix of the family they belong + * to. + * @const + * @type{!Array.} + */ +odf.Style2CSS.styleNameAttributes = [ + {ns: odf.Namespaces.textns, prefix: "text"}, + {ns: odf.Namespaces.drawns, prefix: "draw"}, + {ns: odf.Namespaces.tablens, prefix: "table"}, + {ns: odf.Namespaces.presentationns, prefix: "presentation"}, + {ns: odf.Namespaces.officens, prefix: "office"} +]; +/** + * Write on one element the attribute of each style it names. + * @param {!Element} element + * @return {undefined} + */ +odf.Style2CSS.stampOne = function (element) { + "use strict"; + var /**@type{!Array.}*/ + written = [], + named = odf.Style2CSS.styleNameAttributes, + /**@type{!Array.}*/ + stale, + /**@type{!number}*/ + borne, + /**@type{!number}*/ + i, + /**@type{!string}*/ + name; + for (i = 0; i < named.length; i += 1) { + name = element.getAttributeNS(named[i].ns, "style-name") || ""; + if (name) { + written.push(odf.Style2CSS.classOfStyle(named[i].prefix, name)); + } + } + // What was written for a name the element no longer carries is taken + // off: a header is drawn from a copy whose style was stamped by the + // canvas and named again after, and the attribute of the stamped name + // would stand beside the one of the true name. + stale = []; + borne = element.attributes.length; + for (i = 0; i < borne; i += 1) { + name = String(/**@type{!Attr}*/(element.attributes.item(i)).name); + if (name.indexOf("odf-") === 0 && written.indexOf(name) === -1) { + stale.push(name); + } + } + stale.forEach(function (/**@type{!string}*/ attribute) { + element.removeAttribute(attribute); + }); + written.forEach(function (/**@type{!string}*/ attribute) { + if (!element.hasAttribute(attribute)) { + element.setAttribute(attribute, ""); + } + }); +}; +/** + * Write on every element of a tree the attribute of the style it names. + * + * A rule is written against an attribute of its own, see "createSelector": + * this writes those attributes on the elements that name the styles. + * @param {!Element} root + * @return {undefined} + */ +odf.Style2CSS.stampStyleClasses = function (root) { + "use strict"; + var elements = root.getElementsByTagName("*"), + /**@type{!number}*/ + i; + odf.Style2CSS.stampOne(root); + for (i = 0; i < elements.length; i += 1) { + odf.Style2CSS.stampOne(/**@type{!Element}*/(elements.item(i))); + } +}; diff --git a/webodf/lib/odf/StyleCache.js b/webodf/lib/odf/StyleCache.js index b734772b4..0f7d00b43 100644 --- a/webodf/lib/odf/StyleCache.js +++ b/webodf/lib/odf/StyleCache.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global runtime, odf*/ diff --git a/webodf/lib/odf/StyleInfo.js b/webodf/lib/odf/StyleInfo.js index 6e9ba9490..afba7ae68 100644 --- a/webodf/lib/odf/StyleInfo.js +++ b/webodf/lib/odf/StyleInfo.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global Node, odf, runtime, xmldom*/ @@ -892,25 +892,134 @@ odf.StyleInfo = function StyleInfo() { * @return {!string|undefined} */ function getStyleName(family, element) { - var stylename, i, - map = elements[element.localName]; - if (map) { - map = map[element.namespaceURI]; - if (map) { - for (i = 0; i < map.length; i += 1) { - if (map[i].keyname === family) { - map = map[i]; - if (element.hasAttributeNS(map.ns, map.localname)) { - stylename = element.getAttributeNS(map.ns, map.localname); - break; - } - } + var /**@type{(!string|undefined)}*/stylename, + /**@type{!number}*/i, + /**@type{(!Object.>|undefined)}*/ + namespaces = elements[element.localName], + /**@type{(!Array.|undefined)}*/ + attributes, + /**@type{!{keyname:!string,ns:!string,localname:!string}}*/ + attribute; + if (namespaces) { + attributes = namespaces[element.namespaceURI]; + } + if (attributes) { + for (i = 0; i < attributes.length; i += 1) { + attribute = attributes[i]; + if (attribute.keyname === family + && element.hasAttributeNS(attribute.ns, attribute.localname)) { + stylename = element.getAttributeNS(attribute.ns, attribute.localname); + break; } } } return stylename; } this.getStyleName = getStyleName; + /** + * Fetch style element associated with the requested name and family + * @param {!string} styleName + * @param {!string} family + * @param {!Array.} styleElements Specific style trees to search. If unspecified will search both automatic + * and user-created styles + * @return {Element} + */ + function getStyleElement(styleName, family, styleElements) { + var node, + nodeStyleName, + styleListElement, + i; + + for (i = 0; i < styleElements.length; i += 1) { + styleListElement = /**@type{!Element}*/(styleElements[i]); + node = styleListElement.firstElementChild; + while (node) { + nodeStyleName = node.getAttributeNS(stylens, 'name'); + if (node.namespaceURI === stylens + && node.localName === "style" + && node.getAttributeNS(stylens, 'family') === family + && nodeStyleName === styleName) { + return node; + } + if (family === "list-style" + && node.namespaceURI === textns + && node.localName === "list-style" + && nodeStyleName === styleName) { + return node; + } + if (family === "data" + && node.namespaceURI === numberns + && nodeStyleName === styleName) { + return node; + } + node = node.nextElementSibling; + } + } + return null; + } + this.getStyleElement = getStyleElement; + + /** + * @param {!Element} styleElement + * @param {!Object.>} properties + * @return {undefined} + */ + function getProperties(styleElement, properties) { + var c = styleElement.firstElementChild, p, ns, + /**@type{!string}*/ + prefix, + /**@type{!string}*/ + n; + while (c) { + for (prefix in properties) { + if (properties.hasOwnProperty(prefix)) { + p = properties[prefix]; + ns = /**@type{!string}*/(odf.Namespaces.namespaceMap[prefix]); + for (n in p) { + if (p.hasOwnProperty(n) && p[n] === null && c.hasAttributeNS(ns, n)) { + p[n] = c.getAttributeNS(ns, n); + } + } + } + } + c = c.nextElementSibling; + } + } + + /** + * Get specific style properties from a style element. + * + * @param {!Element} styles + * @param {!Element} automaticStyles + * @param {!string} styleName + * @param {!string} styleFamily + * @param {!Object.>} properties + * @return {!Object.>} + */ + function getStyleProperties(styles, automaticStyles, styleName, + styleFamily, properties) { + var s; + do { + s = getStyleElement(styleName, styleFamily, [automaticStyles]); + if (s) { + getProperties(s, properties); + styleName = s.getAttributeNS(stylens, "parent-style-name"); + } else { + styleName = ""; + } + } while (styleName !== ""); + while (styleName !== "") { + s = getStyleElement(styleName, styleFamily, [styles]); + if (s) { + getProperties(s, properties); + styleName = s.getAttributeNS(stylens, "parent-style-name"); + } else { + styleName = ""; + } + } + return properties; + } + this.getStyleProperties = getStyleProperties; this.hasDerivedStyles = hasDerivedStyles; this.prefixStyleNames = prefixStyleNames; diff --git a/webodf/lib/odf/StyleParseUtils.js b/webodf/lib/odf/StyleParseUtils.js index 5481d886d..b5f73cab8 100644 --- a/webodf/lib/odf/StyleParseUtils.js +++ b/webodf/lib/odf/StyleParseUtils.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global runtime, odf*/ diff --git a/webodf/lib/odf/StyleTree.js b/webodf/lib/odf/StyleTree.js index 3ea0d4412..c5fce7642 100644 --- a/webodf/lib/odf/StyleTree.js +++ b/webodf/lib/odf/StyleTree.js @@ -18,8 +18,8 @@ * along with WebODF. If not, see . * @licend * - * @source: http://www.webodf.org/ - * @source: https://github.com/kogmbh/WebODF/ + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ */ /*global odf*/ @@ -102,7 +102,12 @@ (node.localName === 'default-style'))) { family = node.getAttributeNS(stylens, 'family'); } else if (node.namespaceURI === textns && - node.localName === 'list-style') { + (node.localName === 'list-style' + || node.localName === 'outline-style')) { + // The numbering of the chapters is written as an outline + // style, that a list of the text names as its own style: + // it belongs to the same family, and a document that uses + // it has no list style of that name to be found. family = "list"; } else if (node.namespaceURI === stylens && (node.localName === 'page-layout' || node.localName === 'default-page-layout')) { diff --git a/webodf/lib/odf/TextLayout.js b/webodf/lib/odf/TextLayout.js new file mode 100644 index 000000000..e13376df9 --- /dev/null +++ b/webodf/lib/odf/TextLayout.js @@ -0,0 +1,3382 @@ +/** + * Copyright (C) 2014 KO GmbH + * Copyright (C) 2026 Daniel Berthereau + * + * @licstart + * This file is part of WebODF. + * + * WebODF is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License (GNU AGPL) + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * WebODF is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with WebODF. If not, see . + * @licend + * + * @source: https://webodf.org/ + * @source: https://github.com/webodf/WebODF/ + */ + + +/*global odf, runtime, webodfcore, Node*/ +/** + * @constructor + */ +odf.TextLayout = function TextLayout() { + "use strict"; + var /**@const@type{!odf.PageMeasureImpl}*/ + measure = odf.PageMeasure, + /**@const@type{!odf.PageFurnitureImpl}*/ + furniture = odf.PageFurniture, + /**@type{?odf.ODFDocumentElement}*/ + styleCacheRoot = null, + /**@type{!Object.}*/ + styleCache = {}, + /**@type{!Object.}*/ + breakCache = {}, + /** + * The heads and the feet that were drawn, to be copied from page to + * page: dropped whenever a text is laid out anew. + * @type{!Array.} + */ + furnitureDrawn = [], + /** + * The master page a style of a paragraph begins, by the name of the + * style: read once, and dropped with the styles themselves. + * @type{!Object.} + */ + masterCache = {}, + domUtils = webodfcore.DomUtils, + odfUtils = odf.OdfUtils, + fons = "urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0", + stylens = "urn:oasis:names:tc:opendocument:xmlns:style:1.0", + textns = "urn:oasis:names:tc:opendocument:xmlns:text:1.0", + drawns = "urn:oasis:names:tc:opendocument:xmlns:drawing:1.0", + tablens = "urn:oasis:names:tc:opendocument:xmlns:table:1.0", + officens = "urn:oasis:names:tc:opendocument:xmlns:office:1.0", + webodfhelperns = "urn:webodf:names:helper", + svgns = "urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0", + dcns = "http://purl.org/dc/elements/1.1/", + metans = "urn:oasis:names:tc:opendocument:xmlns:meta:1.0", + /** + * The fields a header or a footer carries that the metadata of the + * document answers, and where each one is read from. The rest of the + * fields keep the text the document was written with, which is what a + * document says of itself and what an office suite would show as well + * until it is edited again. + * @const + * @type{!Array.} + */ + metaFields = [ + {field: "title", ns: dcns, name: "title"}, + {field: "subject", ns: dcns, name: "subject"}, + {field: "description", ns: dcns, name: "description"}, + {field: "author-name", ns: dcns, name: "creator"}, + {field: "initial-creator", ns: metans, name: "initial-creator"}, + {field: "keywords", ns: metans, name: "keyword"} + ], + /** + * The gap between two pages, in pixels: the one the readers of pdf + * draw, ten pixels, and the one the slides of a presentation are drawn + * with here, see "draw|page + draw|page" in "webodf.css". OpenDocument + * says nothing of it, as it is a matter of the reader and not of the + * document. A zoom is a transform, so the gap follows it as the page + * does. + * @const + * @type{!number} + */ + pageSeparation = 10, + /** + * The way a text is drawn over pages: "pages" lays them one under + * another, each page a box of its own that holds what the page + * holds; "columns" lays them beside one another, each page a column, + * which is how two pages are read side by side; "flow" writes the + * text as one run of text with the pages floating beside it, where + * nothing is ever cut where a page ends. + * @type{!string} + */ + pageMode = "pages", + /** + * How many pages stand side by side on a row: one for a reader who + * scrolls a document, two for one who reads it as a book. + * @type{!number} + */ + pagesPerRow = 1, + /**@type{!boolean}*/ + fontsLanded = false, + /**@type{?function():undefined}*/ + drawnHandler = null, + /** + * Whether the first page stands on its own, on the right of the + * first row, as the first page of a book does. + * @type{!boolean} + */ + firstPageOnItsOwn = false, + + /** + * The plan of the pages of the last layout: the pages are set right + * against it when what is drawn after them makes one of them hold + * one line less. + * @type{?PagePlan} + */ + lastPlan = null, + /** + * The breaking of a text into pages that is under way, if any: it is + * done a few pages at a time, so that a reader is given the first of + * them at once. + * @type{?odf.TextLayout.Filling} + */ + filling = null, + /**@type{?odf.ODFDocumentElement}*/ + fillingRoot = null, + /**@type{?HTMLDivElement}*/ + fillingDiv = null, + /**@type{!number}*/ + fillingRound = 0, + /** + * How many pages the text was broken into, when it is broken into + * columns: there is no chain of boxes to count them there. + * @type{!number} + */ + columnPages = 1, + /** + * Where each page begins across, when the text is broken into + * columns: the pages stand beside one another there. + * @type{!Array.} + */ + columnPageOrigins = [], + /** + * How wide and how tall a page of the document is, of the first + * master page it is written on: what a reader is shown at once when + * the pages stand beside one another. + * @type{!{width:!number,height:!number}} + */ + columnPageSize = {width: 0, height: 0}, + /** + * How wide a lane is left beside each page for the notes of the + * annotations, when the pages stand beside one another: a note is + * drawn in it, beside the line it belongs to. + * @type{!number} + */ + noteLane = 0, + /** + * How many pages are drawn at the most. A document of a thousand pages + * is already far more than a reader shows at once, and the bound is + * what keeps a page height read as something absurd, or a text that + * grows as pages are added to it, from writing pages without end. + * @const + * @type{!number} + */ + maxPages = 2000, + /** + * How many nodes are written at once at the most, before what is + * left of an element is put aside: some pages of a dense text, so + * that a page is measured against a page and not against a book. + * @const + * @type{!number} + */ + nodesToAPage = 2000, + /** + * A4 in pixels at 96 dpi, and the margin LibreOffice writes, for a + * document that declares no page layout at all. + * @const + * @type{!odf.PageMeasure.PageDimensions} + */ + defaultDimensions = { + pageWidth: 794, + pageHeight: 1123, + marginTop: 76, + marginBottom: 76, + columnCount: 1, + columnGap: 0, + marginLeft: 76, + marginRight: 76, + pageSeparation: pageSeparation, + header: {height: 0, gap: 0}, + footer: {height: 0, gap: 0}, + firstPage: {shapes: [], header: null, footer: null, + headerLeft: null, footerLeft: null, headerFirst: null, + footerFirst: null}, + otherPages: {shapes: [], header: null, footer: null, + headerLeft: null, footerLeft: null, headerFirst: null, + footerFirst: null} + }; + /** + * Read a length of the page layout, in pixels, and fall back on the given + * one when the attribute is absent or written in a unit that is not read. + * @param {!Element} properties + * @param {!string} name + * @param {!number} fallback + * @param {!string=} ns the namespace of the attribute, "fo" by default + * @return {!number} + */ + function lengthInPx(properties, name, fallback, ns) { + var value = properties.getAttributeNS(ns || fons, name), + px = fallback; + // A length in a unit that is not read, "em" for one, throws rather + // than answering, and a page that is drawn at the size of A4 is better + // than a page that is not drawn at all. + if (value) { + try { + px = odfUtils.convertToPx(value); + } catch (e) { + runtime.log("The page layout names a length that is not read: " + + value); + px = fallback; + } + } + if (!isFinite(px) || px <= 0) { + px = fallback; + } + return px; + } + /** + * The place an area of the furniture of a page takes: its own height, and + * the gap that holds it away from the text. A header is written above the + * text and a footer below it, and the page layout gives each one a style + * of its own, "style:header-style" and "style:footer-style". + * @param {!Element} layout the "style:page-layout" + * @param {!string} name "header-style" or "footer-style" + * @param {!string} gap "margin-bottom" for a header, "margin-top" for a footer + * @return {!odf.PageMeasure.PageArea} + */ + function readPageArea(layout, name, gap) { + var style = domUtils.getDirectChild(layout, stylens, name), + box = style && domUtils.getDirectChild(style, stylens, + "header-footer-properties"), + area = {height: 0, gap: 0}; + if (box) { + // A header or a foot is written of the height it is given, and + // of the height it asks for at the least where it is given + // none: "svg:height" says the one and "fo:min-height" the + // other, and a document that writes both is drawn of the + // greater, as the room the text is left is the room the + // furniture does not take. + area.height = Math.max(lengthInPx(box, "height", 0, svgns), + lengthInPx(box, "min-height", 0)); + // The space between the furniture and the text gives way where + // the document asks for a dynamic spacing: the furniture grows + // into it, and the text is left the room all the same. Where it + // does not, the space is held and the text has that much less. + area.gap = box.getAttributeNS(stylens, "dynamic-spacing") + === "true" + ? 0 + : lengthInPx(box, gap, 0); + } + return area; + } + /** + * The style of a family a name stands for, wherever it is written: the + * styles of a document and the ones a document writes for one use of one + * element, "office:automatic-styles". + * @param {!odf.ODFDocumentElement} odfroot + * @param {!string} name + * @param {!string} family + * @return {?Element} + */ + function styleOf(odfroot, name, family) { + var /**@type{!Array.}*/ + roots = [odfroot.automaticStyles, odfroot.styles], + /**@type{!string}*/ + key = family + "/" + name, + /**@type{!NodeList}*/ + styles, + /**@type{!Element}*/ + candidate, + /**@type{!number}*/ + i, + /**@type{!number}*/ + r; + if (name === "") { + return null; + } + // A page reads the same few styles as every other page does, and a + // document holds thousands of them: they are read once, and the + // cache is dropped whenever another document is laid out. + if (styleCacheRoot !== odfroot) { + styleCacheRoot = odfroot; + styleCache = {}; + breakCache = {}; + masterCache = {}; + } + if (styleCache.hasOwnProperty(key)) { + return styleCache[key]; + } + styleCache[key] = null; + for (r = 0; r < roots.length; r += 1) { + styles = roots[r].getElementsByTagNameNS(stylens, "style"); + for (i = 0; i < styles.length; i += 1) { + candidate = /**@type{!Element}*/(styles.item(i)); + if (candidate.getAttributeNS(stylens, "name") === name + && candidate.getAttributeNS(stylens, "family") + === family) { + styleCache[key] = candidate; + return candidate; + } + } + } + return null; + } + /** + * The graphic style of a shape, followed up its parents: a shape names a + * style of the family "graphic", that may name another one in turn. + * @param {!odf.ODFDocumentElement} odfroot + * @param {!Element} shape + * @return {?Element} + */ + function graphicStyleOf(odfroot, shape) { + var /**@type{!string}*/ + name = shape.getAttributeNS(drawns, "style-name") || "", + /**@type{!Array.}*/ + roots = [odfroot.automaticStyles, odfroot.styles], + /**@type{?Element}*/ + style, + /**@type{!Element}*/ + candidate, + /**@type{!NodeList}*/ + styles, + /**@type{!number}*/ + depth = 0, + /**@type{!number}*/ + marker, + /**@type{!number}*/ + i, + /**@type{!number}*/ + r; + while (name !== "" && depth < 10) { + style = null; + for (r = 0; r < roots.length && style === null; r += 1) { + styles = roots[r].getElementsByTagNameNS(stylens, "style"); + for (i = 0; i < styles.length && style === null; i += 1) { + candidate = /**@type{!Element}*/(styles.item(i)); + if (candidate.getAttributeNS(stylens, "name") === name + && candidate.getAttributeNS(stylens, "family") + === "graphic") { + style = candidate; + } + } + } + if (style === null) { + // The container prefixes the automatic styles of "styles.xml" + // and every reference to them, so that they do not shadow the + // ones of "content.xml", see "prefixStyleNames": a reference + // that names no style of its own is read again without it. + marker = name.indexOf("_webodf_"); + if (marker === -1) { + return null; + } + name = name.substring(marker + "_webodf_".length); + depth += 1; + } else { + if (domUtils.getDirectChild(style, stylens, + "graphic-properties")) { + return style; + } + name = style.getAttributeNS(stylens, "parent-style-name") || ""; + depth += 1; + } + } + return null; + } + /** + * Whether a shape is drawn behind the text or over it. A watermark is + * written behind, "style:run-through" being "background", and a stamp over + * it, which is the default the standard gives. + * @param {!odf.ODFDocumentElement} odfroot + * @param {!Element} shape + * @return {!boolean} + */ + function isBehindTheText(odfroot, shape) { + var style = graphicStyleOf(odfroot, shape), + props = style && domUtils.getDirectChild(style, stylens, + "graphic-properties"); + return Boolean(props + && props.getAttributeNS(stylens, "run-through") === "background"); + } + /** + * The shapes a master page draws on every page: a watermark, a banner + * along an edge, a note in a margin. The standard allows them beside the + * header and the footer, and it is where they are written, as the margins + * of a page carry no area of their own. + * @param {!odf.ODFDocumentElement} odfroot + * @param {?Element} masterPage + * @return {!Array.} + */ + function shapesOf(odfroot, masterPage) { + var shapes = [], + node = masterPage && masterPage.firstElementChild; + while (node) { + if (node.namespaceURI === drawns) { + shapes.push({ + node: node, + background: isBehindTheText(odfroot, node), + order: parseInt(node.getAttributeNS(drawns, "z-index"), 10) + || 0 + }); + } + node = node.nextElementSibling; + } + return shapes; + } + /** + * Draw the shapes of a master page on one page, where the shape itself + * says where it goes: what a master page draws is placed against the + * sheet, so the box is the sheet and the offsets are the ones written. + * @param {!Array.} shapes + * @param {!HTMLDivElement} box + * @return {undefined} + */ + function fillPageShapes(shapes, box) { + var doc = box.ownerDocument; + shapes.forEach(function (shape) { + var node = shape.node, + copy = doc.importNode(node, true), + x = node.getAttributeNS(svgns, "x"), + y = node.getAttributeNS(svgns, "y"), + width = node.getAttributeNS(svgns, "width"), + height = node.getAttributeNS(svgns, "height"), + /**@type{!HTMLDivElement}*/ + place = /**@type{!HTMLDivElement}*/(doc.createElementNS( + box.namespaceURI, "div")); + // A node of the document is placed by a box of the page around it: + // an element of another namespace is no HTMLElement, so it carries + // no style of its own to write into, and the styles of the + // document are the ones that draw it. + place.style.position = "absolute"; + place.style.left = x || "0"; + place.style.top = y || "0"; + // A watermark is drawn behind the text and a stamp over it, and + // "draw:z-index" tells one shape of a layer from the next. The box + // of the page holds no stacking context of its own, so these are + // read against the text. + place.style.zIndex = String(shape.background + ? -1 - shape.order + : 10 + shape.order); + if (width) { + place.style.width = width; + } + if (height) { + place.style.height = height; + } + place.appendChild(copy); + box.appendChild(place); + }); + } + /** + * What a master page writes at the top and at the bottom of a page. + * @param {!odf.ODFDocumentElement} odfroot + * @param {?Element} masterPage + * @return {!odf.PageMeasure.PageFurniture} + */ + function readFurniture(odfroot, masterPage) { + /** + * @param {!string} name + * @return {?Element} + */ + function child(name) { + return masterPage + ? domUtils.getDirectChild(masterPage, stylens, name) + : null; + } + return { + shapes: shapesOf(odfroot, masterPage), + header: child("header"), + footer: child("footer"), + headerLeft: child("header-left"), + footerLeft: child("footer-left"), + headerFirst: child("header-first"), + footerFirst: child("footer-first") + }; + } + /** + * The master page a document names after the first one: a title page is + * written with a master page of its own, that hands the pages that follow + * to another one by "style:next-style-name". + * @param {!odf.ODFDocumentElement} odfroot + * @param {?Element} masterPage + * @return {?Element} + */ + function nextMasterPage(odfroot, masterPage) { + var name = masterPage + && masterPage.getAttributeNS(stylens, "next-style-name"), + masterPages = odfroot.masterStyles.getElementsByTagNameNS(stylens, + "master-page"), + /**@type{!Element}*/ + page, + i; + if (!name || name === masterPage.getAttributeNS(stylens, "name")) { + return null; + } + for (i = 0; i < masterPages.length; i += 1) { + page = /**@type{!Element}*/(masterPages[i]); + if (page.getAttributeNS(stylens, "name") === name) { + return page; + } + } + return null; + } + /** + * The columns a page or a section is written in. + * @param {!Element} properties the properties of the layout or the style + * @return {!{count:!number,gap:!number}} + */ + function columnsOf(properties) { + var columns = domUtils.getDirectChild(properties, stylens, "columns"), + /**@type{!number}*/ + count = 1; + if (!columns) { + return {count: 1, gap: 0}; + } + count = parseInt(columns.getAttributeNS(fons, "column-count"), 10) + || 1; + return { + count: Math.max(1, count), + gap: lengthInPx(columns, "column-gap", 0) + }; + } + /** + * The size of a page, read from the page layout the first master page + * names. A text document has one master page in all but the rarest cases, + * and the pages are all of that size until the layout follows the master + * page of each paragraph, which it does not do yet. + * @param {!odf.ODFDocumentElement} odfroot + * @param {?Element} masterPage + * @return {!odf.PageMeasure.PageDimensions} + */ + function readPageDimensions(odfroot, masterPage) { + var + /**@type{!string}*/ + layoutName = "", + /**@type{!NodeList}*/ + layouts = odfroot.automaticStyles.getElementsByTagNameNS(stylens, + "page-layout"), + /**@type{?Element}*/ + properties = null, + /**@type{!Element}*/ + pageLayout, + /**@type{!Element}*/ + layout, + /**@type{!odf.PageMeasure.PageDimensions}*/ + dims, + /**@type{!{count:!number,gap:!number}}*/ + columns, + /**@type{!number}*/ + i; + if (masterPage) { + layoutName = masterPage.getAttributeNS(stylens, "page-layout-name") + || ""; + } + for (i = 0; i < layouts.length && properties === null; i += 1) { + layout = /**@type{!Element}*/(layouts.item(i)); + if (layoutName === "" + || layout.getAttributeNS(stylens, "name") === layoutName) { + properties = domUtils.getDirectChild(layout, stylens, + "page-layout-properties"); + pageLayout = layout; + } + } + if (properties === null) { + return defaultDimensions; + } + columns = columnsOf(properties); + dims = { + pageHeight: lengthInPx(properties, "page-height", + defaultDimensions.pageHeight), + pageWidth: lengthInPx(properties, "page-width", + defaultDimensions.pageWidth), + marginTop: defaultDimensions.marginTop, + marginBottom: defaultDimensions.marginBottom, + marginLeft: defaultDimensions.marginLeft, + marginRight: defaultDimensions.marginRight, + pageSeparation: pageSeparation, + columnCount: columns.count, + columnGap: columns.gap, + header: readPageArea(pageLayout, "header-style", "margin-bottom"), + footer: readPageArea(pageLayout, "footer-style", "margin-top"), + firstPage: readFurniture(odfroot, masterPage), + otherPages: readFurniture(odfroot, + nextMasterPage(odfroot, masterPage) || masterPage) + }; + // One margin for the four sides, or one by side. + if (properties.getAttributeNS(fons, "margin")) { + dims.marginTop = lengthInPx(properties, "margin", + defaultDimensions.marginTop); + dims.marginBottom = dims.marginTop; + dims.marginLeft = dims.marginTop; + dims.marginRight = dims.marginTop; + } else { + dims.marginTop = lengthInPx(properties, "margin-top", + defaultDimensions.marginTop); + dims.marginBottom = lengthInPx(properties, "margin-bottom", + defaultDimensions.marginBottom); + dims.marginLeft = lengthInPx(properties, "margin-left", + defaultDimensions.marginLeft); + dims.marginRight = lengthInPx(properties, "margin-right", + defaultDimensions.marginRight); + } + // The header and the footer are written inside the margins of the + // page, between its edge and the text: the text has that much less + // room, and the margins of the layout hold it away from them. + // The room a header takes is the room it takes on every page: the + // pages of a document are of one height, and a title page that carries + // no header is drawn with the same text area as the others. + return dims; + } + this.readPageDimensions = readPageDimensions; + /** + * @param {!HTMLDivElement} pagesDiv + * @return {!number} + */ + function countPages(pagesDiv) { + return pageMode !== "flow" + ? columnPages + : Math.ceil((pagesDiv.childElementCount - 1) / 2); + } + /** + * What the metadata of the document says of itself, for the fields a + * header or a footer may carry. A field that the metadata does not answer + * is left with the text the document was written with. + * @param {!odf.ODFDocumentElement} odfroot + * @return {!Object.} + */ + function readMeta(odfroot) { + var meta = {}, + node; + metaFields.forEach(function (entry) { + node = odfroot.meta + ? domUtils.getDirectChild(odfroot.meta, entry.ns, entry.name) + : null; + if (node && node.textContent) { + meta[entry.field] = node.textContent; + } + }); + return meta; + } + /** + * How many pages the document says it has, from the statistics its + * metadata carries, or zero where it says nothing. A text is broken into + * pages one slice at a time, so the number of pages that are drawn is not + * the number of pages of the document until the last one is broken: the + * footer of the first page would read "1 of 12" while the rest is still + * being laid out. The number the writer of the document recorded is the + * one a reader expects, and it is used until the text is broken whole. + * @param {!odf.ODFDocumentElement} odfroot + * @return {!number} + */ + function recordedPageCount(odfroot) { + var stat = odfroot.meta + ? domUtils.getDirectChild(odfroot.meta, metans, + "document-statistic") + : null, + count = stat + ? parseInt(stat.getAttributeNS(metans, "page-count"), 10) + : NaN; + return isNaN(count) || count < 1 + ? 0 + : count; + } + /** + * The paragraphs and the headings a box holds. + * @param {!Element} box + * @return {!Array.} + */ + function paragraphsOf(box) { + var /**@type{!Array.}*/ + found = [], + /**@type{!Array.}*/ + names = ["p", "h"], + /**@type{!NodeList}*/ + nodes, + /**@type{!number}*/ + i, + /**@type{!number}*/ + n; + for (n = 0; n < names.length; n += 1) { + nodes = box.getElementsByTagNameNS(textns, names[n]); + for (i = 0; i < nodes.length; i += 1) { + found.push(/**@type{!Element}*/(nodes.item(i))); + } + } + return found; + } + /** + * Read the name a document gives a style behind the stamp of the canvas. + * + * A header is drawn twice, once for the page and once for the styles of + * the canvas, which writes a stamp of its own before the name of each + * style it draws a second time: "