mirror of
https://github.com/ONLYOFFICE/build_tools.git
synced 2026-04-07 14:06:31 +08:00
Compare commits
34 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 55f811af62 | |||
| 057f0bbbf6 | |||
| 67060cc66e | |||
| c5f6c2e02b | |||
| 71b912e7e6 | |||
| 6e7fd50583 | |||
| cf39498098 | |||
| 1f4e593943 | |||
| 3dfd22c735 | |||
| f92fc0f617 | |||
| 0402a5a07a | |||
| dea91ca6f6 | |||
| 4be0e0cbe2 | |||
| 1e8825e15e | |||
| 9d17f87811 | |||
| 2fff3a7391 | |||
| c0bdb1d62b | |||
| 7c97a9b326 | |||
| b33d92a32e | |||
| d480266a5a | |||
| 98af1ed74b | |||
| 9428ce8b33 | |||
| c1dbdc39f1 | |||
| 5d99680cc4 | |||
| 930b11f19e | |||
| 62aa75d82c | |||
| f926970677 | |||
| 4f6908154c | |||
| 8adc9021e4 | |||
| 4473fd7cf9 | |||
| 29ceaa34bf | |||
| 71cd913944 | |||
| 4ea37abdf3 | |||
| ef8153c053 |
44
Dockerfile
44
Dockerfile
@ -1,4 +1,4 @@
|
||||
FROM ubuntu:20.04
|
||||
FROM ubuntu:24.04
|
||||
|
||||
ENV TZ=Etc/UTC
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
@ -9,25 +9,37 @@ RUN echo 'keyboard-configuration keyboard-configuration/layoutcode string us' |
|
||||
echo 'keyboard-configuration keyboard-configuration/modelcode string pc105' | debconf-set-selections
|
||||
|
||||
RUN apt-get -y update && \
|
||||
apt-get -y install tar \
|
||||
sudo \
|
||||
wget
|
||||
|
||||
RUN wget https://github.com/Kitware/CMake/releases/download/v3.30.0/cmake-3.30.0-linux-x86_64.tar.gz && \
|
||||
tar -xzf cmake-3.30.0-linux-x86_64.tar.gz -C /opt && \
|
||||
ln -s /opt/cmake-3.30.0-linux-x86_64/bin/cmake /usr/local/bin/cmake && \
|
||||
ln -s /opt/cmake-3.30.0-linux-x86_64/bin/ctest /usr/local/bin/ctest && \
|
||||
rm cmake-3.30.0-linux-x86_64.tar.gz
|
||||
apt-get -y install sudo \
|
||||
git \
|
||||
git-lfs \
|
||||
curl \
|
||||
wget \
|
||||
p7zip-full
|
||||
|
||||
ADD . /build_tools
|
||||
WORKDIR /build_tools
|
||||
|
||||
RUN mkdir -p /opt/python3 && \
|
||||
wget -P /opt/python3/ https://github.com/ONLYOFFICE-data/build_tools_data/raw/refs/heads/master/python/python3.tar.gz && \
|
||||
tar -xzf /opt/python3/python3.tar.gz -C /opt/python3 --strip-components=1
|
||||
# Install local Python
|
||||
RUN cd tools/linux && \
|
||||
./python.sh
|
||||
|
||||
ENV PATH="/opt/python3/bin:${PATH}"
|
||||
# Fetch Qt binaries
|
||||
RUN cd tools/linux && \
|
||||
./python3/bin/python3 ./qt_binary_fetch.py amd64
|
||||
|
||||
RUN ln -s /opt/python3/bin/python3.10 /usr/bin/python
|
||||
# Install system dependencies
|
||||
RUN cd tools/linux && \
|
||||
./python3/bin/python3 ./deps.py
|
||||
|
||||
CMD ["sh", "-c", "cd tools/linux && python3 ./automate.py"]
|
||||
# Install CMake
|
||||
RUN cd tools/linux && \
|
||||
./cmake.sh
|
||||
|
||||
# Fetch sysroot
|
||||
RUN cd tools/linux/sysroot && \
|
||||
../python3/bin/python3 ./fetch.py amd64
|
||||
|
||||
ARG BRANCH=master
|
||||
ENV BRANCH=${BRANCH}
|
||||
|
||||
CMD ["sh", "-c", "./tools/linux/python3/bin/python3 ./configure.py --sysroot \"1\" --clean \"0\" --update-light \"1\" --branch \"${BRANCH}\" --update \"1\" --module \"desktop server builder\" --qt-dir \"$(pwd)/tools/linux/qt_build/Qt-5.9.9\" && ./tools/linux/python3/bin/python3 ./make.py"]
|
||||
380
README.md
380
README.md
@ -1,221 +1,218 @@
|
||||
# build_tools
|
||||
<h1>ONLYOFFICE Build Tools</h1>
|
||||
|
||||
## Overview
|
||||
Welcome to the ```build_tools``` repository! This powerful toolkit simplifies the process of compiling [ONLYOFFICE](https://github.com/ONLYOFFICE) products from source on Linux.
|
||||
|
||||
**build_tools** allow you to automatically get and install all the components
|
||||
necessary for the compilation process, all the dependencies required for the
|
||||
**ONLYOFFICE Document Server**, **Document Builder** and **Desktop Editors**
|
||||
correct work, as well as to get the latest version of
|
||||
**ONLYOFFICE products** source code and build all their components.
|
||||
It automatically fetches all the required dependencies and source code to build the latest versions of:
|
||||
|
||||
**Important!** We can only guarantee the correct work of the products built
|
||||
from the `master` branch.
|
||||
* [Docs (Document Server)](https://www.onlyoffice.com/docs?utm_source=github&utm_medium=cpc&utm_campaign=GitHubBuildTools)
|
||||
* [Desktop Editors](https://www.onlyoffice.com/desktop?utm_source=github&utm_medium=cpc&utm_campaign=GitHubBuildTools)
|
||||
* [Document Builder](https://www.onlyoffice.com/document-builder?utm_source=github&utm_medium=cpc&utm_campaign=GitHubBuildTools)
|
||||
|
||||
## How to use - Linux
|
||||
**A quick note:** For the most stable and reliable builds, we strongly recommend compiling from the ```master``` branch of this repository.
|
||||
|
||||
**Note**: The solution has been tested on **Ubuntu 16.04**.
|
||||
## **How do I use it on Linux? 🐧**
|
||||
|
||||
### Installing dependencies
|
||||
>This guide has been tested and verified on **Ubuntu 16.04**.
|
||||
|
||||
You might need to install **Python**, depending on your version of Ubuntu:
|
||||
### **Step 1: Install dependencies**
|
||||
|
||||
First, let's make sure you have **Python** installed, as it's needed to run the build scripts.
|
||||
|
||||
```bash
|
||||
sudo apt-get install -y python
|
||||
```
|
||||
|
||||
### Building ONLYOFFICE products source code
|
||||
### **Step 2: Build the source code**
|
||||
|
||||
1. Clone the build_tools repository:
|
||||
Now, you're ready to build the ONLYOFFICE products.
|
||||
|
||||
```bash
|
||||
git clone https://github.com/ONLYOFFICE/build_tools.git
|
||||
1. **Clone the build_tools repository:**
|
||||
|
||||
This command downloads the build tools to your machine using Git:
|
||||
```bash
|
||||
git clone https://github.com/ONLYOFFICE/build_tools.git
|
||||
```
|
||||
|
||||
2. **Navigate to the scripts directory:**
|
||||
```bash
|
||||
cd build_tools/tools/linux
|
||||
```
|
||||
3. **Run the automation script:**
|
||||
|
||||
This is where the magic happens! Running the script without any options will build all three products: Document Server, Document Builder, and Desktop Editors.
|
||||
|
||||
```bash
|
||||
./automate.py
|
||||
```
|
||||
You can also build ONLYOFFICE products separately. Just run the script with the parameter corresponding to the necessary product. For example, to build *Desktop Editors* and *Document Server*
|
||||
```bash
|
||||
./automate.py desktop server
|
||||
```
|
||||
|
||||
**Perfect!** Once the script finishes, you will find the compiled products in the ```./out``` directory.
|
||||
|
||||
## **Advanced options & different workflows 🚀**
|
||||
|
||||
### **How to use Docker**
|
||||
|
||||
If you prefer using Docker, you can build all products inside a container. This is a great way to keep your local system clean.
|
||||
|
||||
1. **Create an output directory:**
|
||||
|
||||
```bash
|
||||
mkdir out
|
||||
```
|
||||
|
||||
2. Go to the `build_tools/tools/linux` directory:
|
||||
2. **Build the Docker image:**
|
||||
|
||||
```bash
|
||||
cd build_tools/tools/linux
|
||||
```bash
|
||||
docker build --tag onlyoffice-document-editors-builder .
|
||||
```
|
||||
|
||||
3. **Run the container to start the build:**
|
||||
|
||||
This command mounts your local out directory into the container, so the final build files will appear on your machine.
|
||||
|
||||
```bash
|
||||
docker run -v $PWD/out:/build_tools/out onlyoffice-document-editors-builder
|
||||
```
|
||||
|
||||
3. Run the `automate.py` script:
|
||||
You've done it! The results will be in the ```./out``` directory you created.
|
||||
|
||||
```bash
|
||||
./automate.py
|
||||
```
|
||||
## **How to build and run the products separately ▶️**
|
||||
|
||||
If you run the script without any parameters this allows to build **ONLYOFFICE
|
||||
Document Server**, **Document Builder** and **Desktop Editors**.
|
||||
Don't need everything? You can save time by building only the products you need. Just add the product name as an argument to the script.
|
||||
|
||||
The result will be available in the `./out` directory.
|
||||
### Need just the [Document Builder](https://github.com/ONLYOFFICE/DocumentBuilder)❓
|
||||
* How to build
|
||||
|
||||
To build **ONLYOFFICE** products separately run the script with the parameter
|
||||
corresponding to the necessary product.
|
||||
```bash
|
||||
./automate.py builder
|
||||
```
|
||||
* How to run
|
||||
```bash
|
||||
cd ../../out/linux_64/onlyoffice/documentbuilder
|
||||
./docbuilder
|
||||
```
|
||||
|
||||
It’s also possible to build several products at once as shown in the example
|
||||
below.
|
||||
### Need just the [Desktop Editors](https://github.com/ONLYOFFICE/DesktopEditors)❓
|
||||
|
||||
**Example**: Building **Desktop Editors** and **Document Server**
|
||||
* How to build
|
||||
```bash
|
||||
./automate.py desktop
|
||||
```
|
||||
* How to run
|
||||
```bash
|
||||
cd ../../out/linux_64/onlyoffice/desktopeditors
|
||||
LD_LIBRARY_PATH=./ ./DesktopEditors
|
||||
```
|
||||
|
||||
### Need just the [Docs (Document Server)](https://github.com/ONLYOFFICE/DocumentServer)❓
|
||||
* How to build
|
||||
```bash
|
||||
./automate.py server
|
||||
```
|
||||
* How to run
|
||||
|
||||
Running the Document Server is a multi-step process because it relies on a few background services. Let's break it down step by step.
|
||||
|
||||
#### **Step 1. Set up dependencies**
|
||||
|
||||
The Document Server needs a few things to run correctly:
|
||||
|
||||
* **NGINX**: Acts as a web server to handle requests.
|
||||
* **PostgreSQL**: Used as the database to store information.
|
||||
* **RabbitMQ**: A message broker that helps different parts of the server communicate.
|
||||
|
||||
Here are the commands to install and configure them.
|
||||
|
||||
#### **Install and configure NGINX**
|
||||
|
||||
1. Install NGINX
|
||||
```bash
|
||||
sudo apt-get install nginx
|
||||
```
|
||||
2. Disable the default NGINX site
|
||||
```bash
|
||||
sudo rm -f /etc/nginx/sites-enabled/default
|
||||
```
|
||||
3. Set up the new website. To do that create the ```/etc/nginx/sites-available/onlyoffice-documentserver``` file with the following contents:
|
||||
|
||||
```bash
|
||||
./automate.py desktop server
|
||||
map $http_host $this_host {
|
||||
"" $host;
|
||||
default $http_host;
|
||||
}
|
||||
map $http_x_forwarded_proto $the_scheme {
|
||||
default $http_x_forwarded_proto;
|
||||
"" $scheme;
|
||||
}
|
||||
map $http_x_forwarded_host $the_host {
|
||||
default $http_x_forwarded_host;
|
||||
"" $this_host;
|
||||
}
|
||||
map $http_upgrade $proxy_connection {
|
||||
default upgrade;
|
||||
"" close;
|
||||
}
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection $proxy_connection;
|
||||
proxy_set_header X-Forwarded-Host $the_host;
|
||||
proxy_set_header X-Forwarded-Proto $the_scheme;
|
||||
server {
|
||||
listen 0.0.0.0:80;
|
||||
listen [::]:80 default_server;
|
||||
server_tokens off;
|
||||
rewrite ^\/OfficeWeb(\/apps\/.*)$ /web-apps$1 redirect;
|
||||
location / {
|
||||
proxy_pass http://localhost:8000;
|
||||
proxy_http_version 1.1;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Using Docker
|
||||
|
||||
You can also build all **ONLYOFFICE products** at once using Docker.
|
||||
Build the `onlyoffice-document-editors-builder` Docker image using the
|
||||
provided `Dockerfile` and run the corresponding Docker container.
|
||||
|
||||
4. Enable the new site by creating a symbolic link
|
||||
```bash
|
||||
mkdir out
|
||||
docker build --tag onlyoffice-document-editors-builder .
|
||||
docker run -v $PWD/out:/build_tools/out onlyoffice-document-editors-builder
|
||||
sudo ln -s /etc/nginx/sites-available/onlyoffice-documentserver /etc/nginx/sites-enabled/onlyoffice-documentserver
|
||||
```
|
||||
|
||||
The result will be available in the `./out` directory.
|
||||
|
||||
### Building and running ONLYOFFICE products separately
|
||||
|
||||
#### Document Builder
|
||||
|
||||
##### Building Document Builder
|
||||
|
||||
5. Restart NGINX to apply the changes
|
||||
```bash
|
||||
./automate.py builder
|
||||
sudo nginx -s reload
|
||||
```
|
||||
#### **Install and configure PostgreSQL**
|
||||
|
||||
##### Running Document Builder
|
||||
|
||||
```bash
|
||||
cd ../../out/linux_64/onlyoffice/documentbuilder
|
||||
./docbuilder
|
||||
```
|
||||
|
||||
#### Desktop Editors
|
||||
|
||||
##### Building Desktop Editors
|
||||
|
||||
```bash
|
||||
./automate.py desktop
|
||||
```
|
||||
|
||||
##### Running Desktop Editors
|
||||
|
||||
```bash
|
||||
cd ../../out/linux_64/onlyoffice/desktopeditors
|
||||
LD_LIBRARY_PATH=./ ./DesktopEditors
|
||||
```
|
||||
|
||||
#### Document Server
|
||||
|
||||
##### Building Document Server
|
||||
|
||||
```bash
|
||||
./automate.py server
|
||||
```
|
||||
|
||||
##### Installing and configuring Document Server dependencies
|
||||
|
||||
**Document Server** uses **NGINX** as a web server and **PostgreSQL** as a database.
|
||||
**RabbitMQ** is also required for **Document Server** to work correctly.
|
||||
|
||||
###### Installing and configuring NGINX
|
||||
|
||||
1. Install NGINX:
|
||||
|
||||
```bash
|
||||
sudo apt-get install nginx
|
||||
```
|
||||
|
||||
2. Disable the default website:
|
||||
|
||||
```bash
|
||||
sudo rm -f /etc/nginx/sites-enabled/default
|
||||
```
|
||||
|
||||
3. Set up the new website. To do that create the `/etc/nginx/sites-available/onlyoffice-documentserver`
|
||||
file with the following contents:
|
||||
|
||||
```bash
|
||||
map $http_host $this_host {
|
||||
"" $host;
|
||||
default $http_host;
|
||||
}
|
||||
map $http_x_forwarded_proto $the_scheme {
|
||||
default $http_x_forwarded_proto;
|
||||
"" $scheme;
|
||||
}
|
||||
map $http_x_forwarded_host $the_host {
|
||||
default $http_x_forwarded_host;
|
||||
"" $this_host;
|
||||
}
|
||||
map $http_upgrade $proxy_connection {
|
||||
default upgrade;
|
||||
"" close;
|
||||
}
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection $proxy_connection;
|
||||
proxy_set_header X-Forwarded-Host $the_host;
|
||||
proxy_set_header X-Forwarded-Proto $the_scheme;
|
||||
server {
|
||||
listen 0.0.0.0:80;
|
||||
listen [::]:80 default_server;
|
||||
server_tokens off;
|
||||
rewrite ^\/OfficeWeb(\/apps\/.*)$ /web-apps$1 redirect;
|
||||
location / {
|
||||
proxy_pass http://localhost:8000;
|
||||
proxy_http_version 1.1;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
4. Add the symlink to the newly created website to the
|
||||
`/etc/nginx/sites-available` directory:
|
||||
|
||||
```bash
|
||||
sudo ln -s /etc/nginx/sites-available/onlyoffice-documentserver /etc/nginx/sites-enabled/onlyoffice-documentserver
|
||||
```
|
||||
|
||||
5. Restart NGINX to apply the changes:
|
||||
|
||||
```bash
|
||||
sudo nginx -s reload
|
||||
```
|
||||
|
||||
###### Installing and configuring PostgreSQL
|
||||
|
||||
1. Install PostgreSQL:
|
||||
|
||||
1. Install PostgreSQL
|
||||
```bash
|
||||
sudo apt-get install postgresql
|
||||
```
|
||||
|
||||
2. Create the PostgreSQL database and user:
|
||||
|
||||
**Note**: The created database must have **onlyoffice** both for user and password.
|
||||
2. Create a database and user.
|
||||
|
||||
**Note**: The user and password must both be **'onlyoffice'.**
|
||||
```bash
|
||||
sudo -i -u postgres psql -c "CREATE USER onlyoffice WITH PASSWORD 'onlyoffice';"
|
||||
sudo -i -u postgres psql -c "CREATE DATABASE onlyoffice OWNER onlyoffice;"
|
||||
```
|
||||
|
||||
3. Configure the database:
|
||||
|
||||
3. Configure the database:
|
||||
```bash
|
||||
psql -hlocalhost -Uonlyoffice -d onlyoffice -f ../../out/linux_64/onlyoffice/documentserver/server/schema/postgresql/createdb.sql
|
||||
```
|
||||
|
||||
**Note**: Upon that, you will be asked to provide a password for the **onlyoffice**
|
||||
PostgreSQL user. Please enter the **onlyoffice** password.
|
||||
|
||||
###### Installing RabbitMQ
|
||||
Upon that, you will be asked to provide a password for the onlyoffice PostgreSQL user. Please enter the **onlyoffice** password.
|
||||
|
||||
#### **Install RabbitMQ**
|
||||
```bash
|
||||
sudo apt-get install rabbitmq-server
|
||||
```
|
||||
|
||||
###### Generate fonts data
|
||||
Now that you have all the dependencies installed, it's time to generate server files.
|
||||
#### **Step 2. Generate server files**
|
||||
|
||||
Before running the server, you need to generate font and theme data.
|
||||
|
||||
##### **Generate fonts data**
|
||||
|
||||
```bash
|
||||
cd out/linux_64/onlyoffice/documentserver/
|
||||
@ -230,8 +227,7 @@ LD_LIBRARY_PATH=${PWD}/server/FileConverter/bin server/tools/allfontsgen \
|
||||
--use-system="true"
|
||||
```
|
||||
|
||||
###### Generate presentation themes
|
||||
|
||||
##### **Generate presentation themes**
|
||||
```bash
|
||||
cd out/linux_64/onlyoffice/documentserver/
|
||||
LD_LIBRARY_PATH=${PWD}/server/FileConverter/bin server/tools/allthemesgen \
|
||||
@ -240,27 +236,39 @@ LD_LIBRARY_PATH=${PWD}/server/FileConverter/bin server/tools/allthemesgen \
|
||||
--output="${PWD}/sdkjs/common/Images"
|
||||
```
|
||||
|
||||
##### Running Document Server
|
||||
#### **Step 3. Run the Document Server services**
|
||||
|
||||
**Note**: All **Document Server** components run as foreground processes. Thus
|
||||
you need separate terminal consoles to run them or specific tools which will
|
||||
allow to run foreground processes in background mode.
|
||||
All Document Server components run as foreground processes. Thus you need separate terminal consoles to run them or specific tools which will allow to run foreground processes in background mode.
|
||||
|
||||
1. Start the **FileConverter** service:
|
||||
* **Start the FileConverter service:**
|
||||
```bash
|
||||
cd out/linux_64/onlyoffice/documentserver/server/FileConverter
|
||||
LD_LIBRARY_PATH=$PWD/bin \
|
||||
NODE_ENV=development-linux \
|
||||
NODE_CONFIG_DIR=$PWD/../Common/config \
|
||||
./converter
|
||||
```
|
||||
|
||||
```bash
|
||||
cd out/linux_64/onlyoffice/documentserver/server/FileConverter
|
||||
LD_LIBRARY_PATH=$PWD/bin \
|
||||
NODE_ENV=development-linux \
|
||||
NODE_CONFIG_DIR=$PWD/../Common/config \
|
||||
./converter
|
||||
```
|
||||
* **Start the DocService service:**
|
||||
```bash
|
||||
cd out/linux_64/onlyoffice/documentserver/server/DocService
|
||||
NODE_ENV=development-linux \
|
||||
NODE_CONFIG_DIR=$PWD/../Common/config \
|
||||
./docservice
|
||||
```
|
||||
|
||||
2. Start the **DocService** service:
|
||||
## And it's a wrap! 🎉
|
||||
Congratulations! You have successfully used the ```build_tools``` to compile your desired ONLYOFFICE products from the latest source code.
|
||||
|
||||
```bash
|
||||
cd out/linux_64/onlyoffice/documentserver/server/DocService
|
||||
NODE_ENV=development-linux \
|
||||
NODE_CONFIG_DIR=$PWD/../Common/config \
|
||||
./docservice
|
||||
```
|
||||
Everything is now set up. You can go ahead and run your brand-new, self-compiled ONLYOFFICE applications.
|
||||
|
||||
## Need help or have an idea? 💡
|
||||
|
||||
* **🐞 Found a bug?** Please report it by creating an [issue](https://github.com/ONLYOFFICE/build_tools/issues).
|
||||
* **❓ Have a question?** Ask our community and developers on the [ONLYOFFICE Forum](https://community.onlyoffice.com).
|
||||
* **💡 Want to suggest a feature?** Share your ideas on our [feedback platform](https://feedback.onlyoffice.com/forums/966080-your-voice-matters).
|
||||
* **🧑💻 Need help for developers?** Check our [API documentation](https://api.onlyoffice.com/?utm_source=github&utm_medium=cpc&utm_campaign=GitHubBuildTools).
|
||||
|
||||
---
|
||||
|
||||
<p align="center"> Made with ❤️ by the ONLYOFFICE Team </p>
|
||||
|
||||
@ -1,15 +1,11 @@
|
||||
# Docker
|
||||
|
||||
This directory containing instruction for developers,
|
||||
who want to change something in sdkjs or web-apps or server module,
|
||||
but don't want to compile pretty compilcated core product to make those changes.
|
||||
This directory contains instructions for developers,
|
||||
who want to change something in sdkjs, web-apps, or the server module,
|
||||
but don't want to compile the complicated core product to make those changes.
|
||||
|
||||
## System requirements
|
||||
|
||||
**Note**: ARM-based architectures are currently **NOT** supported;
|
||||
attempting to run the images on ARM devices may result in startup failures
|
||||
or other runtime issues.
|
||||
|
||||
### Windows
|
||||
|
||||
You need the latest
|
||||
@ -29,22 +25,22 @@ You need the latest
|
||||
[Docker](https://docs.docker.com/engine/install/)
|
||||
version installed.
|
||||
|
||||
## Create develop Docker Images
|
||||
## Create Development Docker Image
|
||||
|
||||
To create a image with the ability to include external non-minified sdkjs code,
|
||||
To create an image with the ability to include external non-minified sdkjs code,
|
||||
use the following commands:
|
||||
|
||||
### Clone development environment to work dir
|
||||
### Clone development environment to the working directory
|
||||
|
||||
```bash
|
||||
git clone https://github.com/ONLYOFFICE/build_tools.git
|
||||
```
|
||||
|
||||
### Modify Docker Images
|
||||
### Build Docker Image
|
||||
|
||||
**Note**: Do not prefix docker command with sudo.
|
||||
[This](https://docs.docker.com/engine/install/linux-postinstall/#manage-docker-as-a-non-root-user)
|
||||
instruction show how to use docker without sudo.
|
||||
**Note**: Do not prefix the docker command with sudo.
|
||||
[These instructions](https://docs.docker.com/engine/install/linux-postinstall/#manage-docker-as-a-non-root-user)
|
||||
show how to use docker without sudo.
|
||||
|
||||
```bash
|
||||
cd build_tools/develop
|
||||
@ -54,11 +50,11 @@ docker build --no-cache -t documentserver-develop .
|
||||
|
||||
**Note**: The dot at the end is required.
|
||||
|
||||
**Note**: Sometimes script may fail due to network errors. Just restart it.
|
||||
**Note**: Sometimes the build may fail due to network errors. Just restart it.
|
||||
|
||||
## Clone development modules
|
||||
|
||||
Clone development modules to the work dir
|
||||
Clone development modules to the working directory.
|
||||
|
||||
* `sdkjs` repo is located [here](https://github.com/ONLYOFFICE/sdkjs/)
|
||||
* `web-apps` repo is located [here](https://github.com/ONLYOFFICE/web-apps/)
|
||||
@ -76,43 +72,49 @@ To mount external folders to the container,
|
||||
you need to pass the "-v" parameter
|
||||
along with the relative paths to the required folders.
|
||||
The folders `sdkjs` and `web-apps` are required for proper development workflow.
|
||||
The folders `server` is optional
|
||||
The folder `server` is optional.
|
||||
|
||||
**Note**: Run command with the current working directory
|
||||
**Note**: Run the command with the current working directory
|
||||
containing `sdkjs`, `web-apps`...
|
||||
|
||||
**Note**: ONLYOFFICE server uses port 80.
|
||||
Look for another application using port 80 and stop it
|
||||
Look for another application using port 80 and stop it.
|
||||
|
||||
**Note**: Server start with `sdkjs` and `web-apps` takes 15 minutes
|
||||
and takes 20 minutes with `server`
|
||||
**Note**: Starting the server with `sdkjs` and `web-apps` takes 15 minutes,
|
||||
or 20 minutes with `server`.
|
||||
|
||||
### docker run on Windows (PowerShell)
|
||||
|
||||
**Note**: Run PowerShell as administrator to fix EACCES error when installing
|
||||
node_modules
|
||||
node_modules.
|
||||
|
||||
run with `sdkjs` and `web-apps`
|
||||
Run with `sdkjs` and `web-apps`
|
||||
|
||||
```bash
|
||||
```powershell
|
||||
docker run -i -t -p 80:80 --restart=always -e ALLOW_PRIVATE_IP_ADDRESS=true -v $pwd/sdkjs:/var/www/onlyoffice/documentserver/sdkjs -v $pwd/web-apps:/var/www/onlyoffice/documentserver/web-apps documentserver-develop
|
||||
```
|
||||
|
||||
or run with `sdkjs`, `web-apps` and `server`
|
||||
Or run with `sdkjs`, `web-apps`, and `server`
|
||||
|
||||
```powershell
|
||||
docker run -i -t -p 80:80 --restart=always -e ALLOW_PRIVATE_IP_ADDRESS=true -v $pwd/sdkjs:/var/www/onlyoffice/documentserver/sdkjs -v $pwd/web-apps:/var/www/onlyoffice/documentserver/web-apps -v $pwd/server:/var/www/onlyoffice/documentserver/server documentserver-develop
|
||||
```
|
||||
|
||||
**Note**: If using Git Bash instead of PowerShell, you may need to quote the paths:
|
||||
|
||||
```bash
|
||||
docker run -i -t -p 80:80 --restart=always -e ALLOW_PRIVATE_IP_ADDRESS=true -v $pwd/sdkjs:/var/www/onlyoffice/documentserver/sdkjs -v $pwd/web-apps:/var/www/onlyoffice/documentserver/web-apps -v $pwd/server:/var/www/onlyoffice/documentserver/server documentserver-develop
|
||||
docker run -i -t -p 80:80 --restart=always -e ALLOW_PRIVATE_IP_ADDRESS=true -v "$(pwd)/sdkjs":/var/www/onlyoffice/documentserver/sdkjs -v "$(pwd)/web-apps":/var/www/onlyoffice/documentserver/web-apps documentserver-develop
|
||||
```
|
||||
|
||||
### docker run on Linux or macOS
|
||||
|
||||
run with `sdkjs` and `web-apps`
|
||||
Run with `sdkjs` and `web-apps`
|
||||
|
||||
```bash
|
||||
docker run -i -t -p 80:80 --restart=always -e ALLOW_PRIVATE_IP_ADDRESS=true -v $(pwd)/sdkjs:/var/www/onlyoffice/documentserver/sdkjs -v $(pwd)/web-apps:/var/www/onlyoffice/documentserver/web-apps documentserver-develop
|
||||
```
|
||||
|
||||
or run with `sdkjs`, `web-apps` and `server`
|
||||
Or run with `sdkjs`, `web-apps`, and `server`
|
||||
|
||||
```bash
|
||||
docker run -i -t -p 80:80 --restart=always -e ALLOW_PRIVATE_IP_ADDRESS=true -v $(pwd)/sdkjs:/var/www/onlyoffice/documentserver/sdkjs -v $(pwd)/web-apps:/var/www/onlyoffice/documentserver/web-apps -v $(pwd)/server:/var/www/onlyoffice/documentserver/server documentserver-develop
|
||||
@ -120,93 +122,99 @@ docker run -i -t -p 80:80 --restart=always -e ALLOW_PRIVATE_IP_ADDRESS=true -v $
|
||||
|
||||
## Open editor
|
||||
|
||||
After the server starts successfully, you will see Docker log messages like this
|
||||
After the server starts successfully, you will see Docker log messages like this.
|
||||
|
||||
```bash
|
||||
```text
|
||||
[Date] [WARN] [localhost] [docId] [userId] nodeJS
|
||||
```
|
||||
|
||||
To try the document editor, open a browser tab and type
|
||||
[http://localhost/example](http://localhost/example) into the URL bar.
|
||||
|
||||
**Note**: Disable **ad blockers** for localhost page.
|
||||
It may block some scripts (like Analytics.js)
|
||||
**Note**: Disable **ad blockers** for the localhost page.
|
||||
They may block some scripts (like Analytics.js).
|
||||
|
||||
## Modify sources
|
||||
|
||||
### To change something in `sdkjs` do the following steps
|
||||
### To change something in `sdkjs`, do the following steps
|
||||
|
||||
1)Edit source file. Let's insert an image url into each open document.
|
||||
Following command inserts (in case of problems, you can replace URL)
|
||||
1) Edit the source file. Let's insert an image URL into each open document.
|
||||
The following command inserts (in case of problems, you can replace the URL)
|
||||
`this.AddImageUrl(['http://localhost/example/images/logo.png']);`
|
||||
after event
|
||||
`this.sendEvent('asc_onDocumentContentReady');`
|
||||
in file
|
||||
`sdkjs/common/apiBase.js`
|
||||
|
||||
### change sdkjs on Windows (PowerShell)
|
||||
|
||||
```bash
|
||||
(Get-Content sdkjs/common/apiBase.js) -replace "this\.sendEvent\('asc_onDocumentContentReady'\);", "this.sendEvent('asc_onDocumentContentReady');this.AddImageUrl(['http://localhost/example/images/logo.png']);" | Set-Content sdkjs/common/apiBase.js
|
||||
**Windows (PowerShell):**
|
||||
```powershell
|
||||
(Get-Content sdkjs/common/apiBase.js) -replace "this\.sendEvent\('asc_onDocumentContentReady'\);", "this.sendEvent('asc_onDocumentContentReady');this.AddImageUrl(['http://localhost/example/images/logo.png']);" | Set-Content sdkjs/common/apiBase.js
|
||||
```
|
||||
|
||||
### change sdkjs on Linux or macOS
|
||||
|
||||
**Linux:**
|
||||
```bash
|
||||
sed -i "s,this.sendEvent('asc_onDocumentContentReady');,this.sendEvent('asc_onDocumentContentReady');this.AddImageUrl(['http://localhost/example/images/logo.png']);," sdkjs/common/apiBase.js
|
||||
```
|
||||
|
||||
2)Delete browser cache or hard reload the page `Ctrl + Shift + R`
|
||||
**macOS:**
|
||||
```bash
|
||||
sed -i '' "s,this.sendEvent('asc_onDocumentContentReady');,this.sendEvent('asc_onDocumentContentReady');this.AddImageUrl(['http://localhost/example/images/logo.png']);," sdkjs/common/apiBase.js
|
||||
```
|
||||
|
||||
3)Open new file in browser
|
||||
2) Clear the browser cache or hard reload the page (`Ctrl + Shift + R` or `Cmd + Shift + R` on macOS)
|
||||
|
||||
### To change something in `server` do the following steps
|
||||
3) Open a new file in the browser
|
||||
|
||||
1)Edit source file. Let's send `"Hello World!"`
|
||||
chart message every time a document is opened.
|
||||
Following command inserts
|
||||
### To change something in `server`, do the following steps
|
||||
|
||||
1) Edit the source file. Let's send a `"Hello World!"`
|
||||
chat message every time a document is opened.
|
||||
The following command inserts
|
||||
`yield* onMessage(ctx, conn, {"message": "Hello World!"});`
|
||||
in function
|
||||
`sendAuthInfo`
|
||||
in file
|
||||
`server/DocService/sources/DocsCoServer.js`
|
||||
|
||||
### change server on Windows (PowerShell)
|
||||
|
||||
```bash
|
||||
**Windows (PowerShell):**
|
||||
```powershell
|
||||
(Get-Content server/DocService/sources/DocsCoServer.js) -replace 'opt_hasForgotten, opt_openedAt\) \{', 'opt_hasForgotten, opt_openedAt) {yield* onMessage(ctx, conn, {"message": "Hello World!"});' | Set-Content server/DocService/sources/DocsCoServer.js
|
||||
```
|
||||
|
||||
### change server on Linux or macOS
|
||||
|
||||
**Linux:**
|
||||
```bash
|
||||
sed -i 's#opt_hasForgotten, opt_openedAt) {#opt_hasForgotten, opt_openedAt) {yield* onMessage(ctx, conn, {"message": "Hello World!"});#' server/DocService/sources/DocsCoServer.js
|
||||
```
|
||||
|
||||
2)Restart document server process
|
||||
**macOS:**
|
||||
```bash
|
||||
sed -i '' 's#opt_hasForgotten, opt_openedAt) {#opt_hasForgotten, opt_openedAt) {yield* onMessage(ctx, conn, {"message": "Hello World!"});#' server/DocService/sources/DocsCoServer.js
|
||||
```
|
||||
|
||||
**Note**: Look for ``CONTAINER_ID`` in the result of ``docker ps``.
|
||||
2) Restart the document server process
|
||||
|
||||
**Note**: Look for `CONTAINER_ID` in the result of `docker ps`.
|
||||
|
||||
```bash
|
||||
docker exec -it CONTAINER_ID supervisorctl restart all
|
||||
```
|
||||
|
||||
3)Open new file in browser
|
||||
3) Open a new file in the browser
|
||||
|
||||
## Start server with additional functionality(addons)
|
||||
## Start server with additional functionality (addons)
|
||||
|
||||
To get additional functionality and branding you need to connect a branding folder,
|
||||
additional addon folders and pass command line arguments
|
||||
To get additional functionality and branding, you need to connect a branding folder,
|
||||
additional addon folders, and pass command line arguments.
|
||||
|
||||
For example run with `onlyoffice` branding and
|
||||
addons:`sdkjs-forms`, `sdkjs-ooxml`, `web-apps-mobile`
|
||||
For example, run with `onlyoffice` branding and
|
||||
addons: `sdkjs-forms`, `sdkjs-ooxml`, `web-apps-mobile`.
|
||||
|
||||
### docker run on Windows (PowerShell) with branding
|
||||
|
||||
**Note**: Run PowerShell as administrator to fix EACCES error when installing
|
||||
node_modules
|
||||
node_modules.
|
||||
|
||||
```bash
|
||||
```powershell
|
||||
docker run -i -t -p 80:80 --restart=always -e ALLOW_PRIVATE_IP_ADDRESS=true `
|
||||
-v $pwd/sdkjs:/var/www/onlyoffice/documentserver/sdkjs -v $pwd/web-apps:/var/www/onlyoffice/documentserver/web-apps `
|
||||
-v $pwd/onlyoffice:/var/www/onlyoffice/documentserver/onlyoffice -v $pwd/sdkjs-ooxml:/var/www/onlyoffice/documentserver/sdkjs-ooxml -v $pwd/sdkjs-forms:/var/www/onlyoffice/documentserver/sdkjs-forms -v $pwd/web-apps-mobile:/var/www/onlyoffice/documentserver/web-apps-mobile `
|
||||
|
||||
@ -68,8 +68,10 @@ def build_server_with_addons():
|
||||
for addon in addons:
|
||||
if (addon):
|
||||
addon_dir = base.get_script_dir() + "/../../" + addon
|
||||
if (base.is_exist(addon_dir)):
|
||||
if (base.is_exist(addon_dir + "/package.json")):
|
||||
base.print_info("npm ci: " + addon)
|
||||
base.cmd_in_dir(addon_dir, "npm", ["ci"])
|
||||
base.print_info("npm run build: " + addon)
|
||||
base.cmd_in_dir(addon_dir, "npm", ["run", "build"])
|
||||
|
||||
def build_server_develop():
|
||||
|
||||
@ -33,7 +33,7 @@ def make():
|
||||
|
||||
old_cur = os.getcwd()
|
||||
os.chdir(base_dir)
|
||||
base.common_check_version("socketio", "1", clean)
|
||||
base.common_check_version("socketio", "2", clean)
|
||||
os.chdir(old_cur)
|
||||
|
||||
if not base.is_dir(base_dir + "/socket.io-client-cpp"):
|
||||
|
||||
@ -14,7 +14,7 @@ and Plugins (Methods/Events) API using the following Python scripts:
|
||||
|
||||
```bash
|
||||
Node.js v20 and above
|
||||
Python v3.10 and above
|
||||
Python v3.12 and above
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
@ -4,6 +4,8 @@ import re
|
||||
import shutil
|
||||
import argparse
|
||||
import generate_docs_json
|
||||
import json
|
||||
from pathlib import PurePosixPath
|
||||
|
||||
# Configuration files
|
||||
editors = {
|
||||
@ -20,9 +22,36 @@ root = os.path.abspath(os.path.join(os.path.dirname(script_path), '../../../../.
|
||||
|
||||
missing_examples = []
|
||||
used_enumerations = set()
|
||||
|
||||
translations = {}
|
||||
translations_lang = None
|
||||
missed_translations = {}
|
||||
used_translations_keys = {}
|
||||
global_output_dir = ""
|
||||
cur_editor_name = None
|
||||
|
||||
def find_common_path_part(path_full: str, path_suffix: str, anchor: str) -> str:
|
||||
path_full = path_full.replace('\\', '/')
|
||||
path_suffix = path_suffix.replace('\\', '/')
|
||||
|
||||
parts1 = PurePosixPath(path_full).parts
|
||||
parts2 = PurePosixPath(path_suffix).parts
|
||||
|
||||
try:
|
||||
idx1 = [p.lower() for p in parts1].index(anchor.lower())
|
||||
idx2 = [p.lower() for p in parts2].index(anchor.lower())
|
||||
except ValueError:
|
||||
return ""
|
||||
|
||||
common_segments = []
|
||||
|
||||
for p1, p2 in zip(parts1[idx1:], parts2[idx2:]):
|
||||
if p1.lower() == p2.lower():
|
||||
common_segments.append(p1)
|
||||
else:
|
||||
break
|
||||
|
||||
return "/".join(common_segments)
|
||||
|
||||
def load_json(file_path):
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
return json.load(f)
|
||||
@ -36,6 +65,21 @@ def remove_js_comments(text):
|
||||
text = re.sub(r'/\*.*?\*/', '', text, flags=re.DOTALL) # multi-line
|
||||
return text.strip()
|
||||
|
||||
def get_translation(key):
|
||||
def process_part(k):
|
||||
if k not in translations:
|
||||
missed_translations[k] = k
|
||||
else:
|
||||
used_translations_keys[k] = True
|
||||
return translations.get(k, k)
|
||||
|
||||
if '\\\n' in key:
|
||||
parts = key.split('\\\n')
|
||||
translated_parts = [process_part(p) for p in parts]
|
||||
return '\\\n'.join(translated_parts)
|
||||
|
||||
return process_part(key)
|
||||
|
||||
def process_link_tags(text, root=''):
|
||||
"""
|
||||
Finds patterns like {@link ...} and replaces them with Markdown links.
|
||||
@ -43,27 +87,22 @@ def process_link_tags(text, root=''):
|
||||
otherwise, a link to a class method is created.
|
||||
For a method, if an alias is not specified, the name is left in the format 'Class#Method'.
|
||||
"""
|
||||
reserved_links = {
|
||||
'/docbuilder/global#ShapeType': f"{'../../../../../../' if root == '' else '../../../../../' if root == '../' else root}docs/office-api/usage-api/text-document-api/Enumeration/ShapeType.md",
|
||||
'/plugin/config': 'https://api.onlyoffice.com/docs/plugin-and-macros/structure/configuration/',
|
||||
'/docbuilder/basic': 'https://api.onlyoffice.com/docs/office-api/usage-api/text-document-api/'
|
||||
}
|
||||
|
||||
|
||||
def replace_link(match):
|
||||
content = match.group(1).strip() # Example: "/docbuilder/global#ShapeType shape type" or "global#ErrorValue ErrorValue"
|
||||
content = match.group(1).strip() # Example: "global#ShapeType shape type" or "global#ErrorValue ErrorValue
|
||||
parts = content.split()
|
||||
ref = parts[0]
|
||||
label = parts[1] if len(parts) > 1 else None
|
||||
|
||||
if ref.startswith('/'):
|
||||
# Handle reserved links using mapping
|
||||
if ref in reserved_links:
|
||||
url = reserved_links[ref]
|
||||
display_text = label if label else ref
|
||||
return f"[{display_text}]({url})"
|
||||
else:
|
||||
# If the link is not in the mapping, return the original construction
|
||||
return match.group(0)
|
||||
if ref.startswith('/docs/'):
|
||||
url = root + '../../../..' + ref
|
||||
display_text = label if label else ref
|
||||
|
||||
if url.endswith('/'):
|
||||
last_dir = url.rstrip('/').split('/')[-1]
|
||||
url = f"{url}{last_dir}"
|
||||
|
||||
return f"[{display_text}]({url}.md)"
|
||||
elif ref.startswith("global#"):
|
||||
# Handle links to typedef (similar logic as before)
|
||||
typedef_name = ref.split("#")[1]
|
||||
@ -91,7 +130,7 @@ def correct_description(string, root='', isInTable=False):
|
||||
- All '\r' characters are replaced with '\n'.
|
||||
"""
|
||||
if string is None:
|
||||
return 'No description provided.'
|
||||
return get_translation('No description provided.')
|
||||
|
||||
if False == isInTable:
|
||||
# Line breaks
|
||||
@ -100,6 +139,7 @@ def correct_description(string, root='', isInTable=False):
|
||||
string = re.sub(r'<b>', '-**', string)
|
||||
else:
|
||||
string = re.sub(r'<b>', '**', string)
|
||||
string = remove_line_breaks(string)
|
||||
|
||||
string = re.sub(r'</b>', '**', string)
|
||||
|
||||
@ -109,7 +149,7 @@ def correct_description(string, root='', isInTable=False):
|
||||
# Process {@link ...} constructions
|
||||
string = process_link_tags(string, root)
|
||||
|
||||
return string
|
||||
return get_translation(string)
|
||||
|
||||
def correct_default_value(value, enumerations, classes):
|
||||
if value is None or value == '':
|
||||
@ -306,12 +346,12 @@ def generate_data_types_markdown(types, enumerations, classes, root='../../'):
|
||||
|
||||
|
||||
def generate_class_markdown(class_name, methods, properties, enumerations, classes):
|
||||
content = f"# {class_name}\n\nRepresents the {class_name} class.\n\n"
|
||||
content = f"# {class_name}\n\n{get_translation(f"Represents the {class_name} class.")}\n\n"
|
||||
|
||||
content += generate_properties_markdown(properties, enumerations, classes)
|
||||
|
||||
content += "\n## Methods\n\n"
|
||||
content += "| Method | Returns | Description |\n"
|
||||
content += f"\n## {get_translation(f"Methods")}\n\n"
|
||||
content += f"| {get_translation(f"Method")} | {get_translation(f"Returns")} | {get_translation(f"Description")} |\n"
|
||||
content += "| ------ | ------- | ----------- |\n"
|
||||
|
||||
for method in sorted(methods, key=lambda m: m['name']):
|
||||
@ -323,10 +363,10 @@ def generate_class_markdown(class_name, methods, properties, enumerations, class
|
||||
return_type_list = returns[0].get('type', {}).get('names', [])
|
||||
returns_markdown = generate_data_types_markdown(return_type_list, enumerations, classes, '../')
|
||||
else:
|
||||
returns_markdown = "None"
|
||||
returns_markdown = get_translation(f"None")
|
||||
|
||||
# Processing the method description
|
||||
description = remove_line_breaks(correct_description(method.get('description', 'No description provided.'), '../', True))
|
||||
description = correct_description(method.get('description', 'No description provided.'), '../', True)
|
||||
|
||||
# Form a link to the method document
|
||||
method_link = f"[{method_name}](./Methods/{method_name}.md)"
|
||||
@ -348,47 +388,47 @@ def generate_method_markdown(method, enumerations, classes, example_editor_name)
|
||||
|
||||
# Syntax
|
||||
param_list = ', '.join([param['name'] for param in params if '.' not in param['name']]) if params else ''
|
||||
content += f"## Syntax\n\n```javascript\nexpression.{method_name}({param_list});\n```\n\n"
|
||||
content += f"## {get_translation(f"Syntax")}\n\n```javascript\nexpression.{method_name}({param_list});\n```\n\n"
|
||||
if memberof:
|
||||
content += f"`expression` - A variable that represents a [{memberof}](../{memberof}.md) class.\n\n"
|
||||
content += f"`expression` - {get_translation(f"A variable that represents a [{memberof}](../{memberof}.md) class.")}\n\n"
|
||||
|
||||
# Parameters
|
||||
content += "## Parameters\n\n"
|
||||
content += f"## {get_translation(f"Parameters")}\n\n"
|
||||
if params:
|
||||
content += "| **Name** | **Required/Optional** | **Data type** | **Default** | **Description** |\n"
|
||||
content += f"| **{get_translation(f"Name")}** | **{get_translation(f"Required/Optional")}** | **{get_translation(f"Data type")}** | **{get_translation(f"Default")}** | **{get_translation(f"Description")}** |\n"
|
||||
content += "| ------------- | ------------- | ------------- | ------------- | ------------- |\n"
|
||||
for param in params:
|
||||
param_name = param.get('name', 'Unnamed')
|
||||
param_types = param.get('type', {}).get('names', []) if param.get('type') else []
|
||||
param_types_md = generate_data_types_markdown(param_types, enumerations, classes)
|
||||
param_desc = remove_line_breaks(correct_description(param.get('description', 'No description provided.'), '../../', True))
|
||||
param_required = "Required" if not param.get('optional') else "Optional"
|
||||
param_desc = correct_description(param.get('description', 'No description provided.'), '../../', True)
|
||||
param_required = f"{get_translation(f"Required")}" if not param.get('optional') else f"{get_translation(f"Optional")}"
|
||||
param_default = correct_default_value(param.get('defaultvalue', ''), enumerations, classes)
|
||||
|
||||
content += f"| {param_name} | {param_required} | {param_types_md} | {param_default} | {param_desc} |\n"
|
||||
else:
|
||||
content += "This method doesn't have any parameters.\n"
|
||||
content += f"{get_translation("This method doesn't have any parameters.")}\n"
|
||||
|
||||
# Returns
|
||||
content += "\n## Returns\n\n"
|
||||
content += f"\n## {get_translation(f"Returns")}\n\n"
|
||||
if returns:
|
||||
return_type_list = returns[0].get('type', {}).get('names', [])
|
||||
return_type_md = generate_data_types_markdown(return_type_list, enumerations, classes)
|
||||
content += return_type_md
|
||||
else:
|
||||
content += "This method doesn't return any data."
|
||||
content += get_translation(f"This method doesn't return any data.")
|
||||
|
||||
# Example
|
||||
if example:
|
||||
# Separate comment and code, remove JS comments
|
||||
if '```js' in example:
|
||||
comment, code = example.split('```js', 1)
|
||||
comment = remove_js_comments(comment)
|
||||
content += f"\n\n## Example\n\n{comment}\n\n```javascript {example_editor_name}\n{code.strip()}\n"
|
||||
comment = get_translation(comment.strip())
|
||||
content += f"\n\n## {get_translation(f"Example")}\n\n{comment}\n\n```javascript {example_editor_name}\n{code.strip()}\n"
|
||||
else:
|
||||
# If there's no triple-backtick structure, just show it as code
|
||||
cleaned_example = remove_js_comments(example)
|
||||
content += f"\n\n## Example\n\n```javascript {example_editor_name}\n{cleaned_example}\n```\n"
|
||||
content += f"\n\n## {get_translation(f"Example")}\n\n```javascript {example_editor_name}\n{cleaned_example}\n```\n"
|
||||
|
||||
return escape_text_outside_code_blocks(content)
|
||||
|
||||
@ -396,14 +436,14 @@ def generate_properties_markdown(properties, enumerations, classes, root='../'):
|
||||
if properties is None:
|
||||
return ''
|
||||
|
||||
content = "## Properties\n\n"
|
||||
content += "| Name | Type | Description |\n"
|
||||
content = f"## {get_translation(f"Properties")}\n\n"
|
||||
content += f"| {get_translation(f"Name")} | {get_translation(f"Type")} | {get_translation(f"Description")} |\n"
|
||||
content += "| ---- | ---- | ----------- |\n"
|
||||
|
||||
for prop in sorted(properties, key=lambda m: m['name']):
|
||||
prop_name = prop['name']
|
||||
prop_description = prop.get('description', 'No description provided.')
|
||||
prop_description = remove_line_breaks(correct_description(prop_description, root, True))
|
||||
prop_description = correct_description(prop_description, root, True)
|
||||
prop_types = prop['type']['names'] if prop.get('type') else []
|
||||
param_types_md = generate_data_types_markdown(prop_types, enumerations, classes, root)
|
||||
content += f"| {prop_name} | {param_types_md} | {prop_description} |\n"
|
||||
@ -427,8 +467,8 @@ def generate_enumeration_markdown(enumeration, enumerations, classes, example_ed
|
||||
if ptype['type'] == 'TypeUnion':
|
||||
enum_empty = True # is empty enum
|
||||
|
||||
content += "## Type\n\nEnumeration\n\n"
|
||||
content += "## Values\n\n"
|
||||
content += f"## {get_translation(f"Type")}\n\n{get_translation(f"Enumeration")}\n\n"
|
||||
content += f"## {get_translation(f"Values")}\n\n"
|
||||
# Each top-level name in the union
|
||||
for raw_t in enumeration['type']['names']:
|
||||
ts_t = convert_jsdoc_array_to_ts(raw_t)
|
||||
@ -448,25 +488,25 @@ def generate_enumeration_markdown(enumeration, enumerations, classes, example_ed
|
||||
if enum_empty == True:
|
||||
return None
|
||||
elif enumeration['properties'] is not None:
|
||||
content += "## Type\n\nObject\n\n"
|
||||
content += f"## {get_translation(f"Type")}\n\n{get_translation(f"Object")}\n\n"
|
||||
content += generate_properties_markdown(enumeration['properties'], enumerations, classes)
|
||||
else:
|
||||
content += "## Type\n\n"
|
||||
content += f"## {get_translation(f"Type")}\n\n"
|
||||
# If it's not a union and has no properties, simply print the type(s).
|
||||
types = enumeration['type']['names']
|
||||
t_md = generate_data_types_markdown(types, enumerations, classes)
|
||||
t_md = generate_data_types_markdown(types, enumerations, classes, '../')
|
||||
content += t_md + "\n\n"
|
||||
|
||||
# Example
|
||||
if example:
|
||||
if '```js' in example:
|
||||
comment, code = example.split('```js', 1)
|
||||
comment = remove_js_comments(comment)
|
||||
content += f"\n\n## Example\n\n{comment}\n\n```javascript {example_editor_name}\n{code.strip()}\n"
|
||||
comment = get_translation(comment.strip())
|
||||
content += f"\n\n## {get_translation(f"Example")}\n\n{comment}\n\n```javascript {example_editor_name}\n{code.strip()}\n"
|
||||
else:
|
||||
# If there's no triple-backtick structure
|
||||
cleaned_example = remove_js_comments(example)
|
||||
content += f"\n\n## Example\n\n```javascript {example_editor_name}\n{cleaned_example}\n```\n"
|
||||
content += f"\n\n## {get_translation(f"Example")}\n\n```javascript {example_editor_name}\n{cleaned_example}\n```\n"
|
||||
|
||||
return escape_text_outside_code_blocks(content)
|
||||
|
||||
@ -556,7 +596,18 @@ def process_doclets(data, output_dir, editor_name):
|
||||
if not enum.get('example', ''):
|
||||
missing_examples.append(os.path.relpath(enum_file_path, output_dir))
|
||||
|
||||
def generate(output_dir):
|
||||
def generate(output_dir, translations_file):
|
||||
global translations
|
||||
global translations_lang
|
||||
global global_output_dir
|
||||
global_output_dir = output_dir
|
||||
|
||||
if translations_file is not None and os.path.exists(translations_file):
|
||||
translations = load_json(translations_file)
|
||||
translations_lang = os.path.splitext(os.path.basename(translations_file))[0]
|
||||
else:
|
||||
translations = {}
|
||||
|
||||
os.chdir(os.path.dirname(script_path))
|
||||
|
||||
print('Generating Markdown documentation...')
|
||||
@ -575,6 +626,21 @@ def generate(output_dir):
|
||||
used_enumerations.clear()
|
||||
process_doclets(data, output_dir, editor_name)
|
||||
|
||||
if translations_file is not None:
|
||||
target_dir = os.path.dirname(translations_file)
|
||||
|
||||
missed_file_path = os.path.join(target_dir, "missed_translations.json")
|
||||
print(f'Saving missed translations to: {missed_file_path}')
|
||||
with open(missed_file_path, 'w', encoding='utf-8') as f:
|
||||
json.dump(missed_translations, f, ensure_ascii=False, indent=4)
|
||||
|
||||
unused_keys = set(translations.keys()) - set(used_translations_keys.keys())
|
||||
unused_data = {k: translations[k] for k in unused_keys}
|
||||
unused_file_path = os.path.join(target_dir, "unused_translations.json")
|
||||
print(f'Saving unused translations to: {unused_file_path}')
|
||||
with open(unused_file_path, 'w', encoding='utf-8') as f:
|
||||
json.dump(unused_data, f, ensure_ascii=False, indent=4)
|
||||
|
||||
shutil.rmtree(output_dir + 'tmp_json')
|
||||
print('Done')
|
||||
|
||||
@ -587,8 +653,17 @@ if __name__ == "__main__":
|
||||
nargs='?', # Indicates the argument is optional
|
||||
default=f"{root}/api.onlyoffice.com/site/docs/office-api/usage-api/" # Default value
|
||||
)
|
||||
parser.add_argument(
|
||||
"--translations",
|
||||
type=str,
|
||||
help="Path to the JSON file with translations",
|
||||
nargs='?',
|
||||
default=None
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
generate(args.destination)
|
||||
|
||||
generate(args.destination, args.translations)
|
||||
print("START_MISSING_EXAMPLES")
|
||||
print(",".join(missing_examples))
|
||||
print("END_MISSING_EXAMPLES")
|
||||
|
||||
@ -5,6 +5,8 @@ import re
|
||||
import shutil
|
||||
import argparse
|
||||
import generate_docs_events_json
|
||||
import json
|
||||
from pathlib import PurePosixPath
|
||||
|
||||
# Папки для каждого editor_name
|
||||
editors = {
|
||||
@ -20,7 +22,34 @@ root = os.path.abspath(os.path.join(os.path.dirname(script_path), '../../../../.
|
||||
|
||||
missing_examples = []
|
||||
used_enumerations = set()
|
||||
translations = {}
|
||||
translations_lang = None
|
||||
missed_translations = {}
|
||||
used_translations_keys = {}
|
||||
global_output_dir = ""
|
||||
|
||||
def find_common_path_part(path_full: str, path_suffix: str, anchor: str) -> str:
|
||||
path_full = path_full.replace('\\', '/')
|
||||
path_suffix = path_suffix.replace('\\', '/')
|
||||
|
||||
parts1 = PurePosixPath(path_full).parts
|
||||
parts2 = PurePosixPath(path_suffix).parts
|
||||
|
||||
try:
|
||||
idx1 = [p.lower() for p in parts1].index(anchor.lower())
|
||||
idx2 = [p.lower() for p in parts2].index(anchor.lower())
|
||||
except ValueError:
|
||||
return ""
|
||||
|
||||
common_segments = []
|
||||
|
||||
for p1, p2 in zip(parts1[idx1:], parts2[idx2:]):
|
||||
if p1.lower() == p2.lower():
|
||||
common_segments.append(p1)
|
||||
else:
|
||||
break
|
||||
|
||||
return "/".join(common_segments)
|
||||
|
||||
def load_json(path):
|
||||
with open(path, 'r', encoding='utf-8') as f:
|
||||
@ -38,6 +67,20 @@ def remove_js_comments(text):
|
||||
text = re.sub(r'/\*.*?\*/', '', text, flags=re.DOTALL)
|
||||
return text.strip()
|
||||
|
||||
def get_translation(key):
|
||||
def process_part(k):
|
||||
if k not in translations:
|
||||
missed_translations[k] = k
|
||||
else:
|
||||
used_translations_keys[k] = True
|
||||
return translations.get(k, k)
|
||||
|
||||
if '\\\n' in key:
|
||||
parts = key.split('\\\n')
|
||||
translated_parts = [process_part(p) for p in parts]
|
||||
return '\\\n'.join(translated_parts)
|
||||
|
||||
return process_part(key)
|
||||
|
||||
def correct_description(string, root='', isInTable=False):
|
||||
"""
|
||||
@ -49,7 +92,7 @@ def correct_description(string, root='', isInTable=False):
|
||||
- All '\r' characters are replaced with '\n'.
|
||||
"""
|
||||
if string is None:
|
||||
return 'No description provided.'
|
||||
return get_translation('No description provided.')
|
||||
|
||||
if False == isInTable:
|
||||
# Line breaks
|
||||
@ -58,6 +101,7 @@ def correct_description(string, root='', isInTable=False):
|
||||
string = re.sub(r'<b>', '-**', string)
|
||||
else:
|
||||
string = re.sub(r'<b>', '**', string)
|
||||
string = remove_line_breaks(string)
|
||||
|
||||
string = re.sub(r'</b>', '**', string)
|
||||
|
||||
@ -67,7 +111,7 @@ def correct_description(string, root='', isInTable=False):
|
||||
# Process {@link ...} constructions
|
||||
string = process_link_tags(string, root)
|
||||
|
||||
return string
|
||||
return get_translation(string)
|
||||
|
||||
def process_link_tags(text, root=''):
|
||||
"""
|
||||
@ -76,31 +120,22 @@ def process_link_tags(text, root=''):
|
||||
otherwise, a link to a class method is created.
|
||||
For a method, if an alias is not specified, the name is left in the format 'Class#Method'.
|
||||
"""
|
||||
reserved_links = {
|
||||
'/docbuilder/global#ShapeType': f"{'../../../../../../' if root == '' else '../../../../../' if root == '../' else root}docs/office-api/usage-api/text-document-api/Enumeration/ShapeType.md",
|
||||
'/plugin/config': 'https://api.onlyoffice.com/docs/plugin-and-macros/structure/configuration/',
|
||||
'/docbuilder/basic': 'https://api.onlyoffice.com/docs/office-api/usage-api/text-document-api/'
|
||||
}
|
||||
|
||||
|
||||
def replace_link(match):
|
||||
content = match.group(1).strip() # Example: "/docbuilder/global#ShapeType shape type" or "global#ErrorValue ErrorValue"
|
||||
content = match.group(1).strip() # Example: "global#ShapeType shape type" or "global#ErrorValue ErrorValue
|
||||
parts = content.split()
|
||||
ref = parts[0]
|
||||
label = parts[1] if len(parts) > 1 else None
|
||||
|
||||
if ref.startswith('/'):
|
||||
# Handle reserved links using mapping
|
||||
if ref in reserved_links:
|
||||
url = reserved_links[ref]
|
||||
display_text = label if label else ref
|
||||
return f"[{display_text}]({url})"
|
||||
elif ref.startswith('/docs/plugins/'):
|
||||
url = f"../../{ref.split('/docs/plugins/')[1]}.md"
|
||||
display_text = label if label else ref
|
||||
return f"[{display_text}]({url})"
|
||||
else:
|
||||
# If the link is not in the mapping, return the original construction
|
||||
return match.group(0)
|
||||
if ref.startswith('/docs/'):
|
||||
url = root + '../../../..' + ref
|
||||
display_text = label if label else ref
|
||||
|
||||
if url.endswith('/'):
|
||||
last_dir = url.rstrip('/').split('/')[-1]
|
||||
url = f"{url}{last_dir}"
|
||||
|
||||
return f"[{display_text}]({url}.md)"
|
||||
elif ref.startswith("global#"):
|
||||
# Handle links to typedef (similar logic as before)
|
||||
typedef_name = ref.split("#")[1]
|
||||
@ -161,26 +196,26 @@ def escape_text_outside_code_blocks(md):
|
||||
|
||||
def generate_event_markdown(event, enumerations):
|
||||
name = event['name']
|
||||
desc = correct_description(event.get('description', ''))
|
||||
desc = correct_description(event.get('description', ''), '../', True)
|
||||
params = event.get('params', [])
|
||||
|
||||
md = f"# {name}\n\n{desc}\n\n"
|
||||
|
||||
# Parameters
|
||||
md += "## Parameters\n\n"
|
||||
md += f"## {get_translation("Parameters")}\n\n"
|
||||
if params:
|
||||
md += "| **Name** | **Data type** | **Description** |\n"
|
||||
md += f"| **{get_translation("Name")}** | **{get_translation("Data type")}** | **{get_translation("Description")}** |\n"
|
||||
md += "| --------- | ------------- | ----------- |\n"
|
||||
for p in params:
|
||||
t_md = generate_data_types_markdown(
|
||||
p.get('type', {}).get('names', []),
|
||||
enumerations
|
||||
)
|
||||
d = remove_line_breaks(correct_description(p.get('description', ''), isInTable=True))
|
||||
d = correct_description(p.get('description', ''), isInTable=True)
|
||||
md += f"| {p['name']} | {t_md} | {d} |\n"
|
||||
md += "\n"
|
||||
else:
|
||||
md += "This event has no parameters.\n\n"
|
||||
md += f"{get_translation("This event has no parameters.")}\n\n"
|
||||
|
||||
for ex in event.get('examples', []):
|
||||
code = remove_js_comments(ex).strip()
|
||||
@ -213,7 +248,7 @@ def generate_enumeration_markdown(enumeration, enumerations):
|
||||
# If parsedType is missing, just list 'type.names' if available
|
||||
type_names = enumeration['type'].get('names', [])
|
||||
if type_names:
|
||||
content += "## Type\n\n"
|
||||
content += f"## {get_translation("Type")}\n\n"
|
||||
t_md = generate_data_types_markdown(type_names, enumerations)
|
||||
content += t_md + "\n\n"
|
||||
else:
|
||||
@ -221,8 +256,8 @@ def generate_enumeration_markdown(enumeration, enumerations):
|
||||
|
||||
# 1) Handle TypeUnion
|
||||
if ptype == 'TypeUnion':
|
||||
content += "## Type\n\nEnumeration\n\n"
|
||||
content += "## Values\n\n"
|
||||
content += f"## {get_translation("Type")}\n\n{get_translation("Enumeration")}\n\n"
|
||||
content += f"## {get_translation("Values")}\n\n"
|
||||
for raw_t in enumeration['type']['names']:
|
||||
# Attempt linking
|
||||
if any(enum['name'] == raw_t for enum in enumerations):
|
||||
@ -233,11 +268,11 @@ def generate_enumeration_markdown(enumeration, enumerations):
|
||||
|
||||
# 2) Handle TypeApplication (e.g. Object.<string, string>)
|
||||
elif ptype == 'TypeApplication':
|
||||
content += "## Type\n\nObject\n\n"
|
||||
content += f"## {get_translation("Type")}\n\n{get_translation("Object")}\n\n"
|
||||
type_names = enumeration['type'].get('names', [])
|
||||
if type_names:
|
||||
t_md = generate_data_types_markdown(type_names, enumerations)
|
||||
content += f"**Type:** {t_md}\n\n"
|
||||
content += f"**{get_translation("Type")}:** {t_md}\n\n"
|
||||
|
||||
# 3) If properties are present, treat it like an object
|
||||
if enumeration.get('properties') is not None:
|
||||
@ -247,16 +282,16 @@ def generate_enumeration_markdown(enumeration, enumerations):
|
||||
if ptype not in ('TypeUnion', 'TypeApplication'):
|
||||
type_names = enumeration['type'].get('names', [])
|
||||
if type_names:
|
||||
content += "## Type\n\n"
|
||||
content += f"## {get_translation("Type")}\n\n"
|
||||
t_md = generate_data_types_markdown(type_names, enumerations)
|
||||
content += t_md + "\n\n"
|
||||
|
||||
# Process examples array
|
||||
if examples:
|
||||
if len(examples) > 1:
|
||||
content += "\n\n## Examples\n\n"
|
||||
content += f"\n\n## {get_translation("Examples")}\n\n"
|
||||
else:
|
||||
content += "\n\n## Example\n\n"
|
||||
content += f"\n\n## {get_translation("Example")}\n\n"
|
||||
|
||||
for i, ex_line in enumerate(examples, start=1):
|
||||
# Remove JS comments
|
||||
@ -265,15 +300,15 @@ def generate_enumeration_markdown(enumeration, enumerations):
|
||||
# Attempt splitting if the user used ```js
|
||||
if '```js' in cleaned_example:
|
||||
comment, code = cleaned_example.split('```js', 1)
|
||||
comment = comment.strip()
|
||||
comment = get_translation(comment.strip())
|
||||
code = code.strip()
|
||||
if len(examples) > 1:
|
||||
content += f"**Example {i}:**\n\n{comment}\n\n"
|
||||
content += f"**{get_translation("Example")} {i}:**\n\n{comment}\n\n"
|
||||
|
||||
content += f"```javascript\n{code}\n```\n"
|
||||
else:
|
||||
if len(examples) > 1:
|
||||
content += f"**Example {i}:**\n\n{comment}\n\n"
|
||||
content += f"**{get_translation("Example")} {i}:**\n\n{comment}\n\n"
|
||||
# No special fences, just show as code
|
||||
content += f"```javascript\n{cleaned_example}\n```\n"
|
||||
|
||||
@ -284,13 +319,13 @@ def generate_events_summary(events):
|
||||
Create Events.md summary listing all events with their description.
|
||||
"""
|
||||
header = [
|
||||
"# Events\n\n",
|
||||
"| Event | Description |\n",
|
||||
f"# {get_translation("Events")}\n\n",
|
||||
f"| {get_translation("Event")} | {get_translation("Description")} |\n",
|
||||
"| ----- | ----------- |\n"
|
||||
]
|
||||
lines = [
|
||||
f"| [{ev['name']}](./{ev['name']}.md) | "
|
||||
f"{remove_line_breaks(correct_description(ev.get('description', ''), isInTable=True))} |\n"
|
||||
f"{correct_description(ev.get('description', ''), '../', isInTable=True)} |\n"
|
||||
for ev in sorted(events, key=lambda e: e['name'])
|
||||
]
|
||||
return "".join(header + lines)
|
||||
@ -299,14 +334,14 @@ def generate_properties_markdown(properties, enumerations):
|
||||
if properties is None:
|
||||
return ''
|
||||
|
||||
content = "## Properties\n\n"
|
||||
content += "| Name | Type | Description |\n"
|
||||
content = f"## {get_translation("Properties")}\n\n"
|
||||
content += f"| {get_translation("Name")} | {get_translation("Type")} | {get_translation("Description")} |\n"
|
||||
content += "| ---- | ---- | ----------- |\n"
|
||||
|
||||
for prop in sorted(properties, key=lambda m: m['name']):
|
||||
prop_name = prop['name']
|
||||
prop_description = prop.get('description', 'No description provided.')
|
||||
prop_description = remove_line_breaks(correct_description(prop_description, isInTable=True))
|
||||
prop_description = correct_description(prop_description, isInTable=True)
|
||||
prop_types = prop['type']['names'] if prop.get('type') else []
|
||||
param_types_md = generate_data_types_markdown(prop_types, enumerations)
|
||||
content += f"| {prop_name} | {param_types_md} | {prop_description} |\n"
|
||||
@ -375,7 +410,18 @@ def process_events(data, editor_dir):
|
||||
# events summary
|
||||
write_markdown_file(os.path.join(events_dir, "Events.md"), generate_events_summary(events))
|
||||
|
||||
def generate_events(output_dir):
|
||||
def generate_events(output_dir, translations_file):
|
||||
global translations
|
||||
global translations_lang
|
||||
global global_output_dir
|
||||
global_output_dir = output_dir
|
||||
|
||||
if translations_file is not None and os.path.exists(translations_file):
|
||||
translations = load_json(translations_file)
|
||||
translations_lang = os.path.splitext(os.path.basename(translations_file))[0]
|
||||
else:
|
||||
translations = {}
|
||||
|
||||
os.chdir(os.path.dirname(script_path))
|
||||
|
||||
if output_dir.endswith('/'):
|
||||
@ -388,6 +434,21 @@ def generate_events(output_dir):
|
||||
data = load_json(os.path.join(tmp, f"{editor_name}.json"))
|
||||
process_events(data, os.path.join(output_dir, folder))
|
||||
|
||||
if translations_file is not None:
|
||||
target_dir = os.path.dirname(translations_file)
|
||||
|
||||
missed_file_path = os.path.join(target_dir, "missed_translations.json")
|
||||
print(f'Saving missed translations to: {missed_file_path}')
|
||||
with open(missed_file_path, 'w', encoding='utf-8') as f:
|
||||
json.dump(missed_translations, f, ensure_ascii=False, indent=4)
|
||||
|
||||
unused_keys = set(translations.keys()) - set(used_translations_keys.keys())
|
||||
unused_data = {k: translations[k] for k in unused_keys}
|
||||
unused_file_path = os.path.join(target_dir, "unused_translations.json")
|
||||
print(f'Saving unused translations to: {unused_file_path}')
|
||||
with open(unused_file_path, 'w', encoding='utf-8') as f:
|
||||
json.dump(unused_data, f, ensure_ascii=False, indent=4)
|
||||
|
||||
shutil.rmtree(tmp)
|
||||
print("Done. Missing examples:", missing_examples)
|
||||
|
||||
@ -400,5 +461,14 @@ if __name__ == "__main__":
|
||||
default=f"{root}/api.onlyoffice.com/site/docs/plugin-and-macros/interacting-with-editors/",
|
||||
help="Output directory"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--translations",
|
||||
type=str,
|
||||
help="Path to the JSON file with translations",
|
||||
nargs='?',
|
||||
default=None
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
generate_events(args.destination)
|
||||
|
||||
generate_events(args.destination, args.translations)
|
||||
@ -4,6 +4,8 @@ import re
|
||||
import shutil
|
||||
import argparse
|
||||
import generate_docs_methods_json
|
||||
import json
|
||||
from pathlib import PurePosixPath
|
||||
|
||||
# Configuration files
|
||||
editors = {
|
||||
@ -19,9 +21,36 @@ root = os.path.abspath(os.path.join(os.path.dirname(script_path), '../../../../.
|
||||
|
||||
missing_examples = []
|
||||
used_enumerations = set()
|
||||
|
||||
translations = {}
|
||||
translations_lang = None
|
||||
missed_translations = {}
|
||||
used_translations_keys = {}
|
||||
global_output_dir = ""
|
||||
cur_editor_name = None
|
||||
|
||||
def find_common_path_part(path_full: str, path_suffix: str, anchor: str) -> str:
|
||||
path_full = path_full.replace('\\', '/')
|
||||
path_suffix = path_suffix.replace('\\', '/')
|
||||
|
||||
parts1 = PurePosixPath(path_full).parts
|
||||
parts2 = PurePosixPath(path_suffix).parts
|
||||
|
||||
try:
|
||||
idx1 = [p.lower() for p in parts1].index(anchor.lower())
|
||||
idx2 = [p.lower() for p in parts2].index(anchor.lower())
|
||||
except ValueError:
|
||||
return ""
|
||||
|
||||
common_segments = []
|
||||
|
||||
for p1, p2 in zip(parts1[idx1:], parts2[idx2:]):
|
||||
if p1.lower() == p2.lower():
|
||||
common_segments.append(p1)
|
||||
else:
|
||||
break
|
||||
|
||||
return "/".join(common_segments)
|
||||
|
||||
def load_json(file_path):
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
return json.load(f)
|
||||
@ -35,6 +64,21 @@ def remove_js_comments(text):
|
||||
text = re.sub(r'/\*.*?\*/', '', text, flags=re.DOTALL) # multi-line
|
||||
return text.strip()
|
||||
|
||||
def get_translation(key):
|
||||
def process_part(k):
|
||||
if k not in translations:
|
||||
missed_translations[k] = k
|
||||
else:
|
||||
used_translations_keys[k] = True
|
||||
return translations.get(k, k)
|
||||
|
||||
if '\\\n' in key:
|
||||
parts = key.split('\\\n')
|
||||
translated_parts = [process_part(p) for p in parts]
|
||||
return '\\\n'.join(translated_parts)
|
||||
|
||||
return process_part(key)
|
||||
|
||||
def process_link_tags(text, root=''):
|
||||
"""
|
||||
Finds patterns like {@link ...} and replaces them with Markdown links.
|
||||
@ -42,36 +86,28 @@ def process_link_tags(text, root=''):
|
||||
otherwise, a link to a class method is created.
|
||||
For a method, if an alias is not specified, the name is left in the format 'Class#Method'.
|
||||
"""
|
||||
reserved_links = {
|
||||
'/docbuilder/global#ShapeType': f"{'../../../../../' if root == '' else '../../../../' if root == '../' else root}docs/office-api/usage-api/text-document-api/Enumeration/ShapeType.md",
|
||||
'/plugin/config': 'https://api.onlyoffice.com/docs/plugin-and-macros/structure/configuration/',
|
||||
'/docbuilder/basic': 'https://api.onlyoffice.com/docs/office-api/usage-api/text-document-api/'
|
||||
}
|
||||
|
||||
|
||||
def replace_link(match):
|
||||
content = match.group(1).strip() # Example: "/docbuilder/global#ShapeType shape type" or "global#ErrorValue ErrorValue"
|
||||
content = match.group(1).strip() # Example: "global#ShapeType shape type" or "global#ErrorValue ErrorValue
|
||||
parts = content.split()
|
||||
ref = parts[0]
|
||||
label = parts[1] if len(parts) > 1 else None
|
||||
|
||||
if ref.startswith('/'):
|
||||
# Handle reserved links using mapping
|
||||
if ref in reserved_links:
|
||||
url = reserved_links[ref]
|
||||
display_text = label if label else ref
|
||||
return f"[{display_text}]({url})"
|
||||
else:
|
||||
# If the link is not in the mapping, return the original construction
|
||||
return match.group(0)
|
||||
if ref.startswith('/docs/'):
|
||||
url = root + '../../../..' + ref
|
||||
display_text = label if label else ref
|
||||
|
||||
if url.endswith('/'):
|
||||
last_dir = url.rstrip('/').split('/')[-1]
|
||||
url = f"{url}{last_dir}"
|
||||
|
||||
return f"[{display_text}]({url}.md)"
|
||||
elif ref.startswith("global#"):
|
||||
# Handle links to typedef (similar logic as before)
|
||||
typedef_name = ref.split("#")[1]
|
||||
used_enumerations.add(typedef_name)
|
||||
display_text = label if label else typedef_name
|
||||
return f"[{display_text}]({root}Enumeration/{typedef_name}.md)"
|
||||
elif ref.startswith("https"):
|
||||
display_text = label if label else ref # Keep the full notation, e.g., "Api#CreateSlide"
|
||||
return f"[{display_text}]({ref})"
|
||||
else:
|
||||
# Handle links to class methods like ClassName#MethodName
|
||||
try:
|
||||
@ -93,7 +129,7 @@ def correct_description(string, root='', isInTable=False):
|
||||
- All '\r' characters are replaced with '\n'.
|
||||
"""
|
||||
if string is None:
|
||||
return 'No description provided.'
|
||||
return get_translation('No description provided.')
|
||||
|
||||
if False == isInTable:
|
||||
# Line breaks
|
||||
@ -102,6 +138,7 @@ def correct_description(string, root='', isInTable=False):
|
||||
string = re.sub(r'<b>', '-**', string)
|
||||
else:
|
||||
string = re.sub(r'<b>', '**', string)
|
||||
string = remove_line_breaks(string)
|
||||
|
||||
string = re.sub(r'</b>', '**', string)
|
||||
|
||||
@ -111,7 +148,7 @@ def correct_description(string, root='', isInTable=False):
|
||||
# Process {@link ...} constructions
|
||||
string = process_link_tags(string, root)
|
||||
|
||||
return string
|
||||
return get_translation(string)
|
||||
|
||||
def correct_default_value(value, enumerations, classes):
|
||||
if value is None or value == '':
|
||||
@ -309,12 +346,12 @@ def generate_data_types_markdown(types, enumerations, classes, root='../'):
|
||||
return param_types_md
|
||||
|
||||
def generate_class_markdown(class_name, methods, properties, enumerations, classes):
|
||||
content = f"# {class_name}\n\nRepresents the {class_name} class.\n\n"
|
||||
content = f"# {class_name}\n\n{get_translation(f"Represents the {class_name} class.")}\n\n"
|
||||
|
||||
content += generate_properties_markdown(properties, enumerations, classes)
|
||||
|
||||
content += "\n## Methods\n\n"
|
||||
content += "| Method | Returns | Description |\n"
|
||||
content += f"\n## {get_translation("Methods")}\n\n"
|
||||
content += f"| {get_translation("Method")} | {get_translation("Returns")} | {get_translation("Description")} |\n"
|
||||
content += "| ------ | ------- | ----------- |\n"
|
||||
|
||||
for method in sorted(methods, key=lambda m: m['name']):
|
||||
@ -326,10 +363,10 @@ def generate_class_markdown(class_name, methods, properties, enumerations, class
|
||||
return_type_list = returns[0].get('type', {}).get('names', [])
|
||||
returns_markdown = generate_data_types_markdown(return_type_list, enumerations, classes, '../')
|
||||
else:
|
||||
returns_markdown = "None"
|
||||
returns_markdown = get_translation("None")
|
||||
|
||||
# Processing the method description
|
||||
description = remove_line_breaks(correct_description(method.get('description', 'No description provided.'), '../', True))
|
||||
description = correct_description(method.get('description', 'No description provided.'), '../', True)
|
||||
|
||||
# Form a link to the method document
|
||||
method_link = f"[{method_name}](./{method_name}.md)"
|
||||
@ -358,42 +395,42 @@ def generate_method_markdown(method, enumerations, classes):
|
||||
|
||||
# Syntax
|
||||
param_list = ', '.join([param['name'] for param in params if '.' not in param['name']]) if params else ''
|
||||
content += f"## Syntax\n\n```javascript\nexpression.{method_name}({param_list});\n```\n\n"
|
||||
content += f"## {get_translation("Syntax")}\n\n```javascript\nexpression.{method_name}({param_list});\n```\n\n"
|
||||
if memberof:
|
||||
content += f"`expression` - A variable that represents a [{memberof}](Methods.md) class.\n\n"
|
||||
content += f"`expression` - {get_translation(f"A variable that represents a [{memberof}](Methods.md) class.")}\n\n"
|
||||
|
||||
# Parameters
|
||||
content += "## Parameters\n\n"
|
||||
content += f"## {get_translation("Parameters")}\n\n"
|
||||
if params:
|
||||
content += "| **Name** | **Required/Optional** | **Data type** | **Default** | **Description** |\n"
|
||||
content += f"| **{get_translation("Name")}** | **{get_translation("Required/Optional")}** | **{get_translation("Data type")}** | **{get_translation("Default")}** | **{get_translation("Description")}** |\n"
|
||||
content += "| ------------- | ------------- | ------------- | ------------- | ------------- |\n"
|
||||
for param in params:
|
||||
param_name = param.get('name', 'Unnamed')
|
||||
param_types = param.get('type', {}).get('names', []) if param.get('type') else []
|
||||
param_types_md = generate_data_types_markdown(param_types, enumerations, classes)
|
||||
param_desc = remove_line_breaks(correct_description(param.get('description', 'No description provided.'), '../', True))
|
||||
param_required = "Required" if not param.get('optional') else "Optional"
|
||||
param_desc = correct_description(param.get('description', 'No description provided.'), '../', True)
|
||||
param_required = get_translation("Required") if not param.get('optional') else get_translation("Optional")
|
||||
param_default = correct_default_value(param.get('defaultvalue', ''), enumerations, classes)
|
||||
|
||||
content += f"| {param_name} | {param_required} | {param_types_md} | {param_default} | {param_desc} |\n"
|
||||
else:
|
||||
content += "This method doesn't have any parameters.\n"
|
||||
content += f"{get_translation("This method doesn't have any parameters.")}\n"
|
||||
|
||||
# Returns
|
||||
content += "\n## Returns\n\n"
|
||||
content += f"\n## {get_translation("Returns")}\n\n"
|
||||
if returns:
|
||||
return_type_list = returns[0].get('type', {}).get('names', [])
|
||||
return_type_md = generate_data_types_markdown(return_type_list, enumerations, classes)
|
||||
content += return_type_md
|
||||
else:
|
||||
content += "This method doesn't return any data."
|
||||
content += get_translation("This method doesn't return any data.")
|
||||
|
||||
# Process examples array
|
||||
if examples:
|
||||
if len(examples) > 1:
|
||||
content += "\n\n## Examples\n\n"
|
||||
content += f"\n\n## {get_translation("Examples")}\n\n"
|
||||
else:
|
||||
content += "\n\n## Example\n\n"
|
||||
content += f"\n\n## {get_translation("Example")}\n\n"
|
||||
|
||||
for i, ex_line in enumerate(examples, start=1):
|
||||
# Remove JS comments
|
||||
@ -402,15 +439,15 @@ def generate_method_markdown(method, enumerations, classes):
|
||||
# Attempt splitting if the user used ```js
|
||||
if '```js' in cleaned_example:
|
||||
comment, code = cleaned_example.split('```js', 1)
|
||||
comment = comment.strip()
|
||||
comment = get_translation(comment.strip())
|
||||
code = code.strip()
|
||||
if len(examples) > 1:
|
||||
content += f"**Example {i}:**\n\n{comment}\n\n"
|
||||
content += f"**{get_translation("Example")} {i}:**\n\n{comment}\n\n"
|
||||
|
||||
content += f"```javascript\n{code}\n```\n"
|
||||
else:
|
||||
if len(examples) > 1:
|
||||
content += f"**Example {i}:**\n\n{comment}\n\n"
|
||||
content += f"**{get_translation("Example")} {i}:**\n\n{comment}\n\n"
|
||||
# No special fences, just show as code
|
||||
content += f"```javascript\n{cleaned_example}\n```\n"
|
||||
|
||||
@ -420,14 +457,14 @@ def generate_properties_markdown(properties, enumerations, classes, root='../'):
|
||||
if properties is None:
|
||||
return ''
|
||||
|
||||
content = "## Properties\n\n"
|
||||
content += "| Name | Type | Description |\n"
|
||||
content = f"## {get_translation("Properties")}\n\n"
|
||||
content += f"| {get_translation("Name")} | {get_translation("Type")} | {get_translation("Description")} |\n"
|
||||
content += "| ---- | ---- | ----------- |\n"
|
||||
|
||||
for prop in sorted(properties, key=lambda m: m['name']):
|
||||
prop_name = prop['name']
|
||||
prop_description = prop.get('description', 'No description provided.')
|
||||
prop_description = remove_line_breaks(correct_description(prop_description, isInTable=True))
|
||||
prop_description = correct_description(prop_description, root, isInTable=True)
|
||||
prop_types = prop['type']['names'] if prop.get('type') else []
|
||||
param_types_md = generate_data_types_markdown(prop_types, enumerations, classes, root)
|
||||
content += f"| {prop_name} | {param_types_md} | {prop_description} |\n"
|
||||
@ -459,7 +496,7 @@ def generate_enumeration_markdown(enumeration, enumerations, classes):
|
||||
# If parsedType is missing, just list 'type.names' if available
|
||||
type_names = enumeration['type'].get('names', [])
|
||||
if type_names:
|
||||
content += "## Type\n\n"
|
||||
content += f"## {get_translation("Type")}\n\n"
|
||||
t_md = generate_data_types_markdown(type_names, enumerations, classes)
|
||||
content += t_md + "\n\n"
|
||||
else:
|
||||
@ -467,8 +504,8 @@ def generate_enumeration_markdown(enumeration, enumerations, classes):
|
||||
|
||||
# 1) Handle TypeUnion
|
||||
if ptype == 'TypeUnion':
|
||||
content += "## Type\n\nEnumeration\n\n"
|
||||
content += "## Values\n\n"
|
||||
content += f"## {get_translation("Type")}\n\n{get_translation("Enumeration")}\n\n"
|
||||
content += f"## {get_translation("Values")}\n\n"
|
||||
for raw_t in enumeration['type']['names']:
|
||||
# Attempt linking
|
||||
if any(enum['name'] == raw_t for enum in enumerations):
|
||||
@ -481,11 +518,11 @@ def generate_enumeration_markdown(enumeration, enumerations, classes):
|
||||
|
||||
# 2) Handle TypeApplication (e.g. Object.<string, string>)
|
||||
elif ptype == 'TypeApplication':
|
||||
content += "## Type\n\nObject\n\n"
|
||||
content += f"## {get_translation("Type")}\n\n{get_translation("Object")}\n\n"
|
||||
type_names = enumeration['type'].get('names', [])
|
||||
if type_names:
|
||||
t_md = generate_data_types_markdown(type_names, enumerations, classes)
|
||||
content += f"**Type:** {t_md}\n\n"
|
||||
content += f"**{get_translation("Type")}:** {t_md}\n\n"
|
||||
|
||||
# 3) If properties are present, treat it like an object
|
||||
if enumeration.get('properties') is not None:
|
||||
@ -495,16 +532,16 @@ def generate_enumeration_markdown(enumeration, enumerations, classes):
|
||||
if ptype not in ('TypeUnion', 'TypeApplication'):
|
||||
type_names = enumeration['type'].get('names', [])
|
||||
if type_names:
|
||||
content += "## Type\n\n"
|
||||
content += f"## {get_translation("Type")}\n\n"
|
||||
t_md = generate_data_types_markdown(type_names, enumerations, classes)
|
||||
content += t_md + "\n\n"
|
||||
|
||||
# Process examples array
|
||||
if examples:
|
||||
if len(examples) > 1:
|
||||
content += "\n\n## Examples\n\n"
|
||||
content += f"\n\n## {get_translation("Examples")}\n\n"
|
||||
else:
|
||||
content += "\n\n## Example\n\n"
|
||||
content += f"\n\n## {get_translation("Example")}\n\n"
|
||||
|
||||
for i, ex_line in enumerate(examples, start=1):
|
||||
# Remove JS comments
|
||||
@ -513,15 +550,15 @@ def generate_enumeration_markdown(enumeration, enumerations, classes):
|
||||
# Attempt splitting if the user used ```js
|
||||
if '```js' in cleaned_example:
|
||||
comment, code = cleaned_example.split('```js', 1)
|
||||
comment = comment.strip()
|
||||
comment = get_translation(comment.strip())
|
||||
code = code.strip()
|
||||
if len(examples) > 1:
|
||||
content += f"**Example {i}:**\n\n{comment}\n\n"
|
||||
content += f"**{get_translation("Example")} {i}:**\n\n{comment}\n\n"
|
||||
|
||||
content += f"```javascript\n{code}\n```\n"
|
||||
else:
|
||||
if len(examples) > 1:
|
||||
content += f"**Example {i}:**\n\n{comment}\n\n"
|
||||
content += f"**{get_translation("Example")} {i}:**\n\n{comment}\n\n"
|
||||
# No special fences, just show as code
|
||||
content += f"```javascript\n{cleaned_example}\n```\n"
|
||||
|
||||
@ -617,7 +654,18 @@ def process_doclets(data, output_dir, editor_name):
|
||||
if not enum.get('examples', ''):
|
||||
missing_examples.append(os.path.relpath(enum_file_path, output_dir))
|
||||
|
||||
def generate(output_dir):
|
||||
def generate(output_dir, translations_file):
|
||||
global translations
|
||||
global translations_lang
|
||||
global global_output_dir
|
||||
global_output_dir = output_dir
|
||||
|
||||
if translations_file is not None and os.path.exists(translations_file):
|
||||
translations = load_json(translations_file)
|
||||
translations_lang = os.path.splitext(os.path.basename(translations_file))[0]
|
||||
else:
|
||||
translations = {}
|
||||
|
||||
os.chdir(os.path.dirname(script_path))
|
||||
|
||||
print('Generating Markdown documentation...')
|
||||
@ -633,6 +681,21 @@ def generate(output_dir):
|
||||
used_enumerations.clear()
|
||||
process_doclets(data, output_dir, editor_name)
|
||||
|
||||
if translations_file is not None:
|
||||
target_dir = os.path.dirname(translations_file)
|
||||
|
||||
missed_file_path = os.path.join(target_dir, "missed_translations.json")
|
||||
print(f'Saving missed translations to: {missed_file_path}')
|
||||
with open(missed_file_path, 'w', encoding='utf-8') as f:
|
||||
json.dump(missed_translations, f, ensure_ascii=False, indent=4)
|
||||
|
||||
unused_keys = set(translations.keys()) - set(used_translations_keys.keys())
|
||||
unused_data = {k: translations[k] for k in unused_keys}
|
||||
unused_file_path = os.path.join(target_dir, "unused_translations.json")
|
||||
print(f'Saving unused translations to: {unused_file_path}')
|
||||
with open(unused_file_path, 'w', encoding='utf-8') as f:
|
||||
json.dump(unused_data, f, ensure_ascii=False, indent=4)
|
||||
|
||||
shutil.rmtree(output_dir + '/tmp_json')
|
||||
print('Done')
|
||||
|
||||
@ -645,8 +708,17 @@ if __name__ == "__main__":
|
||||
nargs='?', # Indicates the argument is optional
|
||||
default=f"{root}/api.onlyoffice.com/site/docs/plugin-and-macros/interacting-with-editors/" # Default value
|
||||
)
|
||||
parser.add_argument(
|
||||
"--translations",
|
||||
type=str,
|
||||
help="Path to the JSON file with translations",
|
||||
nargs='?',
|
||||
default=None
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
generate(args.destination)
|
||||
|
||||
generate(args.destination, args.translations)
|
||||
print("START_MISSING_EXAMPLES")
|
||||
print(",".join(missing_examples))
|
||||
print("END_MISSING_EXAMPLES")
|
||||
|
||||
54
tools/common/thumbnails_auto.py
Normal file
54
tools/common/thumbnails_auto.py
Normal file
@ -0,0 +1,54 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import sys
|
||||
sys.path.append('../../scripts')
|
||||
import base
|
||||
import os
|
||||
import glob
|
||||
from pathlib import Path
|
||||
|
||||
params = sys.argv[1:]
|
||||
|
||||
if (3 != len(params)):
|
||||
print("use: thumbnails_auto.py path_to_builder_directory path_to_input_files_directory path_to_output_files_directory")
|
||||
exit(0)
|
||||
|
||||
base.configure_common_apps()
|
||||
|
||||
directory_x2t = params[0].replace("\\", "/")
|
||||
directory_input = params[1].replace("\\", "/")
|
||||
directory_output = params[2].replace("\\", "/")
|
||||
|
||||
|
||||
if not os.path.exists(directory_output):
|
||||
os.mkdir(directory_output)
|
||||
|
||||
def rename_dir(input, output):
|
||||
if base.is_dir(u"" + directory_output + u"/" + output):
|
||||
base.delete_dir(u"" + directory_output + u"/" + output)
|
||||
os.rename(u"" + directory_output + u"/" + input, u"" + directory_output + u"/" + output)
|
||||
return
|
||||
|
||||
base.cmd("python", ["thumbnails_old.py", directory_x2t, directory_input, directory_output, "512", "724"])
|
||||
base.cmd("python", ["thumbnails_old.py", directory_x2t, directory_input, directory_output, "1024", "1448"])
|
||||
base.cmd("python", ["thumbnails_old.py", directory_x2t, directory_input, directory_output, "324", "458"])
|
||||
base.cmd("python", ["thumbnails_old.py", directory_x2t, directory_input, directory_output, "648", "916"])
|
||||
base.cmd("python", ["thumbnails_old.py", directory_x2t, directory_input, directory_output, "256", "368"])
|
||||
|
||||
base.cmd("python", ["thumbnails_old.py", directory_x2t, directory_input, directory_output, "400", "566"])
|
||||
base.cmd("python", ["thumbnails_old.py", directory_x2t, directory_input, directory_output, "184", "260"])
|
||||
#base.cmd("python", ["thumbnails.py", directory_x2t, directory_input, directory_output, "792", "1098"])
|
||||
|
||||
#rename_dir("[512x724]", "inside_1x_[512x724]")
|
||||
#rename_dir("[1024x1448]", "inside_2x_[1024x1448]")
|
||||
#rename_dir("[228x316]", "main_1x_[228x316]")
|
||||
#rename_dir("[456x632]", "main_2x_[456x632]")
|
||||
#rename_dir("[256x368]", "mobile_[256x368]")
|
||||
#rename_dir("[792x1098]", "source_[792x1098]")
|
||||
|
||||
dirnames = list(Path(directory_output).iterdir())
|
||||
for dir_name in dirnames:
|
||||
if len(list(Path(dir_name).iterdir())) == 0:
|
||||
print("Delete dir ", dir_name)
|
||||
Path(dir_name).rmdir()
|
||||
#base.delete_dir(dir_name)
|
||||
274
tools/common/thumbnails_old.py
Normal file
274
tools/common/thumbnails_old.py
Normal file
@ -0,0 +1,274 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import sys
|
||||
sys.path.append('../../scripts')
|
||||
import base
|
||||
import os
|
||||
import glob
|
||||
import imagesize
|
||||
import importlib.util
|
||||
from pathlib import Path
|
||||
from os.path import dirname, abspath, join
|
||||
|
||||
# Python 3 compatibility hack
|
||||
try:
|
||||
unicode('')
|
||||
except NameError:
|
||||
unicode = str
|
||||
|
||||
|
||||
params = sys.argv[1:]
|
||||
|
||||
if (5 != len(params)):
|
||||
print("use: thumbnails.py path_to_builder_directory path_to_input_files_directory path_to_output_files_directory width height")
|
||||
exit(0)
|
||||
|
||||
mapping = {"[512x724]": "inside_1x_[512x724]",
|
||||
"[1024x1448]": "inside_2x_[1024x1448]",
|
||||
"[228x316]": "main_1x_[228x316]",
|
||||
"[456x632]": "main_2x_[456x632]",
|
||||
"[256x368]": "mobile_[256x368]",
|
||||
"[792x1098]": "source_[792x1098]",
|
||||
"[324x458]": "main_1x_[324x458]",
|
||||
"[648x916]": "main_2x_[648x916]",
|
||||
"[400x566]": "pop_up_[400x566]",
|
||||
"[184x260]": "desktop[184x260]",
|
||||
}
|
||||
|
||||
cur_path = os.getcwd()
|
||||
base.configure_common_apps()
|
||||
|
||||
directory_x2t = params[0].replace("\\", "/")
|
||||
directory_input = params[1].replace("\\", "/")
|
||||
directory_output = params[2].replace("\\", "/")
|
||||
th_width = params[3]
|
||||
th_height = params[4]
|
||||
|
||||
docbuilder_path = os.path.join(directory_x2t, "docbuilder.py")
|
||||
if not os.path.isfile(docbuilder_path):
|
||||
print(f"ERROR: docbuilder.py not found in '{directory_x2t}'")
|
||||
exit(1)
|
||||
|
||||
spec = importlib.util.spec_from_file_location("docbuilder", docbuilder_path)
|
||||
docbuilder_mod = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(docbuilder_mod)
|
||||
|
||||
CDocBuilder = docbuilder_mod.CDocBuilder
|
||||
CDocBuilderValue = docbuilder_mod.CDocBuilderValue
|
||||
|
||||
#output_dir = directory_output + "/[" + str(th_width) + "x" + str(th_height) + "]"
|
||||
#if base.is_dir(output_dir):
|
||||
# base.delete_dir(output_dir)
|
||||
#base.create_dir(output_dir)
|
||||
|
||||
input_files = []
|
||||
for file in glob.glob(os.path.join(u"" + directory_input, u'*')):
|
||||
input_files.append(file.replace("\\", "/"))
|
||||
|
||||
#print(input_files)
|
||||
temp_dir = os.getcwd().replace("\\", "/") + "/temp"
|
||||
if base.is_dir(temp_dir):
|
||||
base.delete_dir(temp_dir)
|
||||
base.create_dir(temp_dir)
|
||||
|
||||
directory_fonts = directory_x2t + "/sdkjs/common"
|
||||
if not base.is_file(directory_fonts + "/AllFonts.js"):
|
||||
base.cmd_in_dir(directory_x2t, "docbuilder", [], True)
|
||||
|
||||
# # True for fit, False for 100%
|
||||
# isScaleSheetToPage = False
|
||||
#
|
||||
# json_fit_text = "0"
|
||||
# if isScaleSheetToPage:
|
||||
# json_fit_text = "1"
|
||||
#
|
||||
# #json_params += "'fitToWidth':" + json_fit_text + ",'fitToHeight':" + json_fit_text + ","
|
||||
# if isScaleSheetToPage:
|
||||
# json_params = "{'spreadsheetLayout':{'fitToWidth':1,'fitToHeight':1},"
|
||||
# else:
|
||||
# json_params = "{'spreadsheetLayout':{'fitToWidth':0,'fitToHeight':0},"
|
||||
# json_params += "'documentLayout':{'drawPlaceHolders':true,'drawFormHighlight':true,'isPrint':true}}"
|
||||
# json_params = json_params.replace("'", """)
|
||||
|
||||
|
||||
json_params = "{"
|
||||
|
||||
json_params += "'spreadsheetLayout':{"
|
||||
|
||||
# True for fit, False for 100%
|
||||
isScaleSheetToPage = False
|
||||
|
||||
json_fit_text = "0"
|
||||
if isScaleSheetToPage:
|
||||
json_fit_text = "1"
|
||||
|
||||
json_params += "'fitToWidth':" + json_fit_text + ",'fitToHeight':" + json_fit_text + ","
|
||||
|
||||
if True:
|
||||
json_params += "'orientation':'landscape',"
|
||||
|
||||
page_margins = "'pageMargins':{'bottom':10,'footer':5,'header':5,'left':5,'right':5,'top':10}"
|
||||
page_setup = "'pageSetup':{'orientation':1,'width':210,'height':297,'paperUnits':0,'scale':190," \
|
||||
"'printArea':false,'horizontalDpi':600,'verticalDpi':600,'usePrinterDefaults':true,'fitToHeight':1,'fitToWidth':1}"
|
||||
|
||||
json_params += "'sheetsProps':{'0':{'headings':false,'printTitlesWidth':null,'printTitlesHeight':null," + page_margins + "," + page_setup + "}}},"
|
||||
|
||||
json_params += "'documentLayout':{'drawPlaceHolders':true,'drawFormHighlight':true,'isPrint':true},"
|
||||
json_params += "'ignorePrintArea':'false'"
|
||||
json_params += "}"
|
||||
json_params = json_params.replace("'", """)
|
||||
|
||||
if not os.path.exists(directory_output):
|
||||
os.mkdir(directory_output)
|
||||
|
||||
output_len = len(input_files)
|
||||
output_cur = 1
|
||||
for input_file in input_files:
|
||||
if os.path.isdir(input_file):
|
||||
next_dir_name = os.path.basename(input_file)
|
||||
base.cmd("python", ["thumbnails_old.py", directory_x2t, input_file, os.path.join(directory_output, next_dir_name), th_width, th_height])
|
||||
if base.is_dir(temp_dir):
|
||||
base.delete_dir(temp_dir)
|
||||
base.create_dir(temp_dir)
|
||||
continue
|
||||
print("process [" + str(output_cur) + " of " + str(output_len) + "]: " + str(input_file.encode("utf-8")))
|
||||
|
||||
width_page = th_width
|
||||
height_page = th_height
|
||||
|
||||
json_params_file = json_params
|
||||
|
||||
if input_file.lower().endswith('.xlsx'):
|
||||
temp_dir_builder = directory_output + "/temp_builder"
|
||||
if base.is_dir(temp_dir_builder):
|
||||
base.delete_dir(temp_dir_builder)
|
||||
base.create_dir(temp_dir_builder)
|
||||
builder = CDocBuilder()
|
||||
builder.SetTmpFolder(temp_dir_builder)
|
||||
|
||||
builder.OpenFile(input_file)
|
||||
context = builder.GetContext()
|
||||
globalObj = context.GetGlobal()
|
||||
|
||||
cmd = """
|
||||
(function(){
|
||||
Api.getPrintAreaSize = function() {
|
||||
return { Width:0, Height:0 };
|
||||
};
|
||||
var sheet = Api.GetSheets()[0];
|
||||
var usedRange = sheet.GetUsedRange();
|
||||
|
||||
var maxCol = -1;
|
||||
var maxRow = -1;
|
||||
usedRange.ForEach(function (cell) {
|
||||
if (cell.GetRowHeight() === 0 || cell.GetColumnWidth() === 0) {
|
||||
return;
|
||||
}
|
||||
var row0 = cell.GetRow() - 1;
|
||||
var col0 = cell.GetCol() - 1;
|
||||
var hasContent = false;
|
||||
var val = cell.GetValue();
|
||||
if (val !== "" && val !== null && val !== undefined) {
|
||||
hasContent = true;
|
||||
}
|
||||
if (!hasContent) {
|
||||
var formula = cell.GetFormula();
|
||||
if (typeof formula === 'string' && formula.indexOf("=") === 0) {
|
||||
hasContent = true;
|
||||
}
|
||||
}
|
||||
if (hasContent) {
|
||||
if (col0 > maxCol) maxCol = col0;
|
||||
if (row0 > maxRow) maxRow = row0;
|
||||
}
|
||||
});
|
||||
if (maxRow < 0 || maxCol < 0) {
|
||||
return;
|
||||
}
|
||||
var printRange = sheet.GetRange(
|
||||
sheet.GetRangeByNumber(0, 0),
|
||||
sheet.GetRangeByNumber(maxRow, maxCol)
|
||||
);
|
||||
Api.getPrintAreaSize = function() {
|
||||
return { Width:printRange.Width, Height:printRange.Height };
|
||||
};})();
|
||||
"""
|
||||
|
||||
builder.ExecuteCommand(cmd)
|
||||
api = globalObj['Api']
|
||||
|
||||
sizeWH = api.getPrintAreaSize()
|
||||
wPrint = sizeWH.Get("Width").ToDouble()
|
||||
hPrint = sizeWH.Get("Height").ToDouble()
|
||||
|
||||
print("CELL (printSize): " + str(wPrint) + "x" + str(hPrint))
|
||||
|
||||
if (wPrint > 1 and hPrint > wPrint):
|
||||
tmp = width_page
|
||||
width_page = height_page
|
||||
height_page = tmp
|
||||
json_params_file = json_params_file.replace(""width":210,"height":297", ""width":297,"height":210")
|
||||
|
||||
builder.CloseFile()
|
||||
base.delete_dir(temp_dir_builder)
|
||||
|
||||
output_dir = os.path.join(directory_output,
|
||||
os.path.splitext(os.path.basename(input_file))[0])
|
||||
#output_dir = str(output_dir.encode("utf8"))
|
||||
output_dir = abspath(unicode(output_dir))
|
||||
if not os.path.exists(output_dir):
|
||||
os.mkdir(output_dir)
|
||||
output_dir = os.path.join(output_dir,
|
||||
mapping["[" + str(th_width) + "x" + str(th_height) + "]"])
|
||||
#output_dir = str(output_dir.encode("utf8"))
|
||||
#output_dir = dirname(abspath(unicode(output_dir)))
|
||||
output_dir = abspath(unicode(output_dir))
|
||||
if not os.path.exists(output_dir):
|
||||
os.mkdir(output_dir)
|
||||
output_file = output_dir # os.path.join(output_dir, os.path.splitext(os.path.basename(input_file))[0])
|
||||
xml_convert = u"<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
|
||||
xml_convert += u"<TaskQueueDataConvert>"
|
||||
xml_convert += (u"<m_sFileFrom>" + input_file + u"</m_sFileFrom>")
|
||||
xml_convert += (u"<m_sFileTo>" + output_file + u".zip</m_sFileTo>")
|
||||
xml_convert += u"<m_nFormatTo>1029</m_nFormatTo>"
|
||||
xml_convert += (u"<m_sAllFontsPath>" + directory_fonts + u"/AllFonts.js</m_sAllFontsPath>")
|
||||
xml_convert += (u"<m_sFontDir>" + directory_fonts + u"</m_sFontDir>")
|
||||
xml_convert += (u"<m_sJsonParams>" + json_params_file + u"</m_sJsonParams>")
|
||||
xml_convert += u"<m_nDoctParams>1</m_nDoctParams>"
|
||||
xml_convert += u"<m_oThumbnail>"
|
||||
xml_convert += u"<first>false</first>"
|
||||
|
||||
if ((0 != width_page) and (0 != height_page)):
|
||||
xml_convert += u"<aspect>16</aspect>"
|
||||
xml_convert += (u"<width>" + str(width_page) + u"</width>")
|
||||
xml_convert += (u"<height>" + str(height_page) + u"</height>")
|
||||
xml_convert += u"</m_oThumbnail>"
|
||||
xml_convert += u"<m_nDoctParams>1</m_nDoctParams>"
|
||||
xml_convert += (u"<m_sTempDir>" + temp_dir + u"</m_sTempDir>")
|
||||
xml_convert += u"</TaskQueueDataConvert>"
|
||||
base.save_as_script(temp_dir + "/to.xml", [xml_convert])
|
||||
base.cmd_in_dir(directory_x2t, "x2t", [temp_dir + "/to.xml"], True)
|
||||
base.delete_dir(temp_dir)
|
||||
base.create_dir(temp_dir)
|
||||
base.extract_unicode(output_file + u".zip", output_file)
|
||||
if os.path.exists(output_file + ".zip"):
|
||||
try:
|
||||
base.delete_file(output_file + ".zip")
|
||||
except:
|
||||
print("Error in deletin file: ", output_file + ".zip")
|
||||
output_cur += 1
|
||||
#output_file = output_file.replace("\\", "/")
|
||||
#imnames = Path(output_file).glob("*.png")#glob.glob("/" + output_file.replace(":", "") + "/*.png")
|
||||
imnames = [str(pp) for pp in Path(output_file).glob("*.png")]
|
||||
#print(output_file + "/*.png", imnames)
|
||||
#continue
|
||||
if len(imnames) == 0:
|
||||
base.delete_dir(output_file)
|
||||
else:
|
||||
width, height = imagesize.get(imnames[0])
|
||||
print("WxH: ", width, height)
|
||||
if width < height and False: #удалить вертикальные превью
|
||||
base.delete_dir(output_file)
|
||||
|
||||
base.delete_dir(temp_dir)
|
||||
os.chdir(cur_path)
|
||||
Reference in New Issue
Block a user