mavlink_gitbook.py 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. #! /usr/bin/python
  2. """
  3. This script generates markdown files for all the MAVLink message definition XML at:
  4. https://github.com/mavlink/mavlink/tree/master/message_definitions/v1.0
  5. The files can be imported into a gitbook to display the messages as HTML
  6. The script runs on both Python2 and Python 3. The following libraries must be imported: lxml, requests, bs4.
  7. The file is run in mavlink/doc/ with no arguments. It writes the files to /messages/
  8. """
  9. import lxml.etree as ET
  10. import requests
  11. from bs4 import BeautifulSoup as bs
  12. import re
  13. import os # for walk
  14. xsl_file_name = "mavlink_to_html_table_gitbook.xsl"
  15. xml_message_definitions_dir_name = "../message_definitions/v1.0/"
  16. output_dir = "./messages/"
  17. output_dir_html=output_dir+"_html/"
  18. if not os.path.exists(output_dir_html):
  19. os.makedirs(output_dir_html)
  20. # File for index
  21. index_file_name = "README.md"
  22. index_file_name = output_dir + index_file_name
  23. # Get XSLT
  24. with open(xsl_file_name, 'r') as content_file:
  25. xsl_file = content_file.read()
  26. xslt = ET.fromstring(xsl_file)
  27. #initialise text for index file.
  28. index_text="""<!-- THIS FILE IS AUTO-GENERATED (DO NOT UPDATE GITBOOK): https://github.com/mavlink/mavlink/blob/master/doc/mavlink_gitbook.py -->
  29. # Dialects {#dialects}
  30. MAVLink *dialects* are XML files that define *protocol-* and *vendor-specific* messages, enums and commands.
  31. Dialects may *include* other MAVLink XML files.
  32. 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.
  33. 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)).
  34. > **Note** Vendor forks of MAVLink may contain dialect messages that are not yet merged, and hence will not appear in this documentation.
  35. The dialect files are stored alongside in separate XML files in [mavlink/message definitions](https://github.com/mavlink/mavlink/blob/master/message_definitions/).
  36. The human-readable forms of the XML dialect files are linked below:
  37. """
  38. #Fix up the BeautifulSoup output so to fix build-link errors in the generated gitbook.
  39. ## BS puts each tag/content in its own line. Gitbook generates anchors using the spaces/newlines.
  40. ## This puts displayed text content immediately within tags so that anchors/links generate properly
  41. def fix_content_in_tags(input_html):
  42. #print("fix_content_in_tags was called")
  43. def remove_space_between_content_tags(matchobj):
  44. stripped_string=matchobj.group(1).strip()
  45. return '>%s<' % stripped_string
  46. input_html=re.sub(r'\>(\s+?\w+?.*?)\<', remove_space_between_content_tags, input_html,flags=re.DOTALL)
  47. return input_html
  48. def fix_include_file_extension(input_html):
  49. ## Fixes up file extension .xml.md.unlikely (easier than fixing up the XSLT to strip file extensions!)
  50. input_html=input_html.replace('.xml.md.unlikely','.md')
  51. return input_html
  52. def fix_replace_space_marker(input_html):
  53. ## Above we remove hidden space. I can't seem to regexp just that type of space, so use space markers in text
  54. input_html=input_html.replace('xxx_space_xxx',' ')
  55. return input_html
  56. def strip_text_before_string(original_text,strip_text):
  57. # Strip out all text before some string
  58. index=original_text.find(strip_text)
  59. stripped_string=original_text
  60. if index !=-1 :
  61. stripped_string = stripped_string[index:]
  62. return stripped_string
  63. def inject_top_level_docs(input_html,filename):
  64. #Inject top level heading and other details.
  65. print('FILENAME: %s' % filename)
  66. insert_text='<!-- THIS FILE IS AUTO-GENERATED: https://github.com/mavlink/mavlink/blob/master/doc/mavlink_gitbook.py -->'
  67. if filename == 'common.xml':
  68. insert_text+="""
  69. # MAVLINK Common Message Set
  70. The MAVLink *common* message set is defined in [common.xml](https://github.com/mavlink/mavlink/blob/master/message_definitions/v1.0/common.xml).
  71. It contains the *standard* definitions that are managed by the MAVLink project.
  72. The definitions cover functionality that is considered useful to most ground control stations and autopilots.
  73. 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).
  74. This topic is a human-readable form of [common.xml](https://github.com/mavlink/mavlink/blob/master/message_definitions/v1.0/common.xml).
  75. """
  76. elif filename == 'ardupilotmega.xml':
  77. insert_text+="""
  78. # Dialect: ArduPilotMega
  79. These messages define the ArduPilot specific message set, which is custom to [http://ardupilot.org](http://ardupilot.org).
  80. 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).
  81. > **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.
  82. """
  83. else:
  84. insert_text+='\n# Dialect: %s' % filename.rsplit('.',1)[0]
  85. 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)
  86. insert_text+="""
  87. <span></span>
  88. > **Note** MAVLink 2 messages have an ID > 255 and are marked up using **(MAVLink 2)** in their description.
  89. <span id="mav2_extension_field"></span>
  90. > **Note** MAVLink 2 extension fields that have been added to MAVLink 1 messages are displayed in blue.
  91. <style>
  92. td {
  93. vertical-align:top;
  94. }
  95. </style>
  96. """
  97. # Include HTML in generated content
  98. insert_text+='\n\n{%% include "_html/%s.html" %%}' % filename[:-4]
  99. input_html=insert_text+'\n\n'+input_html
  100. #print(input_html)
  101. return input_html
  102. dialect_files = set()
  103. for subdir, dirs, files in os.walk(xml_message_definitions_dir_name):
  104. for file in files:
  105. print(file)
  106. if not file.endswith('.xml'): #only process xml files.
  107. continue
  108. xml_file_name = xml_message_definitions_dir_name+file
  109. with open(xml_file_name, 'r') as content_file:
  110. xml_file = content_file.read()
  111. dom = ET.fromstring(xml_file)
  112. transform = ET.XSLT(xslt)
  113. newdom = transform(dom)
  114. #Prettify the HTML using BeautifulSoup
  115. soup=bs(str(newdom), "lxml")
  116. prettyHTML=soup.prettify()
  117. #Strip out text before <html> tag in XSLT output
  118. prettyHTML=strip_text_before_string(prettyHTML,'<html>')
  119. prettyHTML = fix_content_in_tags(prettyHTML)
  120. #Replace invalid file extensions (workaround for xslt)
  121. prettyHTML = fix_include_file_extension(prettyHTML)
  122. #Replace space markers with intentional space
  123. prettyHTML = fix_replace_space_marker(prettyHTML)
  124. #Write output html file
  125. output_file_name_html = file.rsplit('.',1)[0]+".html"
  126. output_file_name_html_withdir = output_dir_html+output_file_name_html
  127. print("Output filename (html): %s" % output_file_name_html)
  128. with open(output_file_name_html_withdir, 'w') as out:
  129. out.write(prettyHTML)
  130. #Write output markdown file
  131. output_file_name_prefix = file.rsplit('.',1)[0]
  132. markdown_text=''
  133. #Inject a heading and doc-type intro (markdown format)
  134. markdown_text = inject_top_level_docs(markdown_text,file)
  135. output_file_name_md_withdir = output_dir+output_file_name_prefix+'.md'
  136. print("Output filename (md): %s" % output_file_name_md_withdir)
  137. with open(output_file_name_md_withdir, 'w') as out:
  138. out.write(markdown_text)
  139. # Create sortable list of output file names
  140. if not file=='common.xml':
  141. dialect_files.add(output_file_name_prefix)
  142. for the_file in sorted(dialect_files):
  143. index_text+='\n* [%s.xml](%s.md)' % (the_file,the_file)
  144. #Write the index
  145. with open(index_file_name, 'w') as content_file:
  146. content_file.write(index_text)
  147. print("COMPLETED")