#! /usr/bin/python
"""
This script generates markdown files for all the MAVLink message definition XML at:
https://github.com/mavlink/mavlink/tree/master/message_definitions/v1.0
The files can be imported into a gitbook to display the messages as HTML
The script runs on both Python2 and Python 3. The following libraries must be imported: lxml, requests, bs4.
The file is run in mavlink/doc/ with no arguments. It writes the files to /messages/
"""
import lxml.etree as ET
import requests
from bs4 import BeautifulSoup as bs
import re
import os # for walk
xsl_file_name = "mavlink_to_html_table_gitbook.xsl"
xml_message_definitions_dir_name = "../message_definitions/v1.0/"
output_dir = "./messages/"
output_dir_html=output_dir+"_html/"
if not os.path.exists(output_dir_html):
os.makedirs(output_dir_html)
# File for index
index_file_name = "README.md"
index_file_name = output_dir + index_file_name
# Get XSLT
with open(xsl_file_name, 'r') as content_file:
xsl_file = content_file.read()
xslt = ET.fromstring(xsl_file)
#initialise text for index file.
index_text="""
# Dialects {#dialects}
MAVLink *dialects* are XML files that define *protocol-* and *vendor-specific* messages, enums and commands.
Dialects may *include* other MAVLink XML files.
A typical pattern is for a dialect to include [common.xml](../messages/common.md) (containing the *MAVLink standard definitions*), extending it with vendor or protocol specific messages.
While a dialect can include any other message definition, only only a single level of nesting is supported ([at time of writing](https://github.com/ArduPilot/pymavlink/pull/248)).
> **Note** Vendor forks of MAVLink may contain dialect messages that are not yet merged, and hence will not appear in this documentation.
The dialect files are stored alongside in separate XML files in [mavlink/message definitions](https://github.com/mavlink/mavlink/blob/master/message_definitions/).
The human-readable forms of the XML dialect files are linked below:
"""
#Fix up the BeautifulSoup output so to fix build-link errors in the generated gitbook.
## BS puts each tag/content in its own line. Gitbook generates anchors using the spaces/newlines.
## This puts displayed text content immediately within tags so that anchors/links generate properly
def fix_content_in_tags(input_html):
#print("fix_content_in_tags was called")
def remove_space_between_content_tags(matchobj):
stripped_string=matchobj.group(1).strip()
return '>%s<' % stripped_string
input_html=re.sub(r'\>(\s+?\w+?.*?)\<', remove_space_between_content_tags, input_html,flags=re.DOTALL)
return input_html
def fix_include_file_extension(input_html):
## Fixes up file extension .xml.md.unlikely (easier than fixing up the XSLT to strip file extensions!)
input_html=input_html.replace('.xml.md.unlikely','.md')
return input_html
def fix_replace_space_marker(input_html):
## Above we remove hidden space. I can't seem to regexp just that type of space, so use space markers in text
input_html=input_html.replace('xxx_space_xxx',' ')
return input_html
def strip_text_before_string(original_text,strip_text):
# Strip out all text before some string
index=original_text.find(strip_text)
stripped_string=original_text
if index !=-1 :
stripped_string = stripped_string[index:]
return stripped_string
def inject_top_level_docs(input_html,filename):
#Inject top level heading and other details.
print('FILENAME: %s' % filename)
insert_text=''
if filename == 'common.xml':
insert_text+="""
# MAVLINK Common Message Set
The MAVLink *common* message set is defined in [common.xml](https://github.com/mavlink/mavlink/blob/master/message_definitions/v1.0/common.xml).
It contains the *standard* definitions that are managed by the MAVLink project.
The definitions cover functionality that is considered useful to most ground control stations and autopilots.
MAVLink-compatible systems are expected to use these definitions where possible (if an appropriate message exists) rather than rolling out variants in their own [dialects](../messages/README.md).
This topic is a human-readable form of [common.xml](https://github.com/mavlink/mavlink/blob/master/message_definitions/v1.0/common.xml).
"""
elif filename == 'ardupilotmega.xml':
insert_text+="""
# Dialect: ArduPilotMega
These messages define the ArduPilot specific message set, which is custom to [http://ardupilot.org](http://ardupilot.org).
This topic is a human-readable form of the XML definition file: [ardupilotmega.xml](https://github.com/mavlink/mavlink/blob/master/message_definitions/v1.0/ardupilotmega.xml).
> **Warning** The ArduPilot MAVLink fork of [ardupilotmega.xml](https://github.com/ArduPilot/mavlink/blob/master/message_definitions/v1.0/ardupilotmega.xml) may contain messages that have not yet been merged into this documentation.
"""
else:
insert_text+='\n# Dialect: %s' % filename.rsplit('.',1)[0]
insert_text+='\n\n*This is a human-readable form of the XML definition file: [%s](https://github.com/mavlink/mavlink/blob/master/message_definitions/v1.0/%s).*' % (filename, filename)
insert_text+="""
> **Note** MAVLink 2 messages have an ID > 255 and are marked up using **(MAVLink 2)** in their description.
> **Note** MAVLink 2 extension fields that have been added to MAVLink 1 messages are displayed in blue.
"""
# Include HTML in generated content
insert_text+='\n\n{%% include "_html/%s.html" %%}' % filename[:-4]
input_html=insert_text+'\n\n'+input_html
#print(input_html)
return input_html
dialect_files = set()
for subdir, dirs, files in os.walk(xml_message_definitions_dir_name):
for file in files:
print(file)
if not file.endswith('.xml'): #only process xml files.
continue
xml_file_name = xml_message_definitions_dir_name+file
with open(xml_file_name, 'r') as content_file:
xml_file = content_file.read()
dom = ET.fromstring(xml_file)
transform = ET.XSLT(xslt)
newdom = transform(dom)
#Prettify the HTML using BeautifulSoup
soup=bs(str(newdom), "lxml")
prettyHTML=soup.prettify()
#Strip out text before tag in XSLT output
prettyHTML=strip_text_before_string(prettyHTML,'')
prettyHTML = fix_content_in_tags(prettyHTML)
#Replace invalid file extensions (workaround for xslt)
prettyHTML = fix_include_file_extension(prettyHTML)
#Replace space markers with intentional space
prettyHTML = fix_replace_space_marker(prettyHTML)
#Write output html file
output_file_name_html = file.rsplit('.',1)[0]+".html"
output_file_name_html_withdir = output_dir_html+output_file_name_html
print("Output filename (html): %s" % output_file_name_html)
with open(output_file_name_html_withdir, 'w') as out:
out.write(prettyHTML)
#Write output markdown file
output_file_name_prefix = file.rsplit('.',1)[0]
markdown_text=''
#Inject a heading and doc-type intro (markdown format)
markdown_text = inject_top_level_docs(markdown_text,file)
output_file_name_md_withdir = output_dir+output_file_name_prefix+'.md'
print("Output filename (md): %s" % output_file_name_md_withdir)
with open(output_file_name_md_withdir, 'w') as out:
out.write(markdown_text)
# Create sortable list of output file names
if not file=='common.xml':
dialect_files.add(output_file_name_prefix)
for the_file in sorted(dialect_files):
index_text+='\n* [%s.xml](%s.md)' % (the_file,the_file)
#Write the index
with open(index_file_name, 'w') as content_file:
content_file.write(index_text)
print("COMPLETED")